feat: plan agent v2, global chat mode, and user-mode v2 flag sync

- Add runtime/plan_agent_v2 package and shims; gateway/direct_loop/WS wiring

- Admin chat: interaction mode and specialist only in user menu; session API stores memory_mode and execution_mode only

- POST /admin/api/chat/user-mode mirrors plan_agent_version to AIA_EXPERT_PLAN_AGENT_V2_ENABLED (v2 to 1, v1 to 0)

- Composer cleanup (hidden mode select, no reasoning toggle in meta bar); tests and _local/system.env.example

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
oliver 2026-05-02 18:41:45 +08:00
parent ae44cbcad5
commit 37522f492a
49 changed files with 4146 additions and 238 deletions

View file

@ -14,6 +14,8 @@
"thinking": { "type": "string" },
"interaction_mode": { "type": "string" },
"specialist": { "type": "string" },
"execution_mode": { "type": "string" },
"plan_agent_version": { "type": "string" },
"relay_share_envelope": { "type": "object" },
"acp_parent_run_id": { "type": "string" },
"acp_child_run_id": { "type": "string" },

View file

@ -51,6 +51,12 @@ def build_gateway_context(
p = dict(params or {})
interaction_mode = str(p.get("interaction_mode") or "comprehensive").strip().lower() or "comprehensive"
specialist = str(p.get("specialist") or "generalist").strip().lower() or "generalist"
execution_mode = str(p.get("execution_mode") or "agent").strip().lower() or "agent"
if execution_mode not in {"agent", "plan"}:
execution_mode = "agent"
plan_agent_version = str(p.get("plan_agent_version") or "v1").strip().lower() or "v1"
if plan_agent_version not in {"v1", "v2"}:
plan_agent_version = "v1"
if interaction_mode != "expert":
specialist = "generalist"
raw_env = dict(p.get("relay_share_envelope") or {}) if isinstance(p.get("relay_share_envelope"), dict) else {}
@ -62,6 +68,8 @@ def build_gateway_context(
"idempotencyKey": str(p.get("idempotencyKey") or uuid.uuid4().hex),
"interaction_mode": interaction_mode,
"specialist": specialist,
"execution_mode": execution_mode,
"plan_agent_version": plan_agent_version,
"relay_share_envelope": norm_env if ok_env else {},
"acp_parent_run_id": str(p.get("acp_parent_run_id") or ""),
"acp_child_run_id": str(p.get("acp_child_run_id") or ""),
@ -81,6 +89,12 @@ def build_gateway_context(
message = str(p.get("message") or "").strip()
interaction_mode = str(p.get("interaction_mode") or "comprehensive").strip().lower() or "comprehensive"
specialist = str(p.get("specialist") or "generalist").strip().lower() or "generalist"
execution_mode = str(p.get("execution_mode") or "agent").strip().lower() or "agent"
if execution_mode not in {"agent", "plan"}:
execution_mode = "agent"
plan_agent_version = str(p.get("plan_agent_version") or "v1").strip().lower() or "v1"
if plan_agent_version not in {"v1", "v2"}:
plan_agent_version = "v1"
if interaction_mode != "expert":
specialist = "generalist"
raw_env = dict(p.get("relay_share_envelope") or {}) if isinstance(p.get("relay_share_envelope"), dict) else {}
@ -92,6 +106,8 @@ def build_gateway_context(
"idempotencyKey": run_id,
"interaction_mode": interaction_mode,
"specialist": specialist,
"execution_mode": execution_mode,
"plan_agent_version": plan_agent_version,
"relay_share_envelope": norm_env if ok_env else {},
"acp_parent_run_id": str(p.get("acp_parent_run_id") or ""),
"acp_child_run_id": str(p.get("acp_child_run_id") or ""),

View file

@ -89,6 +89,12 @@ async def run_agent_turn_via_bridge(
accepted_ms = now_ms()
msg_text = str(p.get("message") or "").strip()
attachments = list(p.get("attachments") or [])
execution_mode = str(p.get("execution_mode") or "agent").strip().lower() or "agent"
if execution_mode not in {"agent", "plan"}:
execution_mode = "agent"
plan_agent_version = str(p.get("plan_agent_version") or "v1").strip().lower() or "v1"
if plan_agent_version not in {"v1", "v2"}:
plan_agent_version = "v1"
store = SqliteStore(db_path())
gw = OclawGateway(store=store)
@ -190,6 +196,8 @@ async def run_agent_turn_via_bridge(
metadata={
"interaction_mode": str(p.get("interaction_mode") or "comprehensive"),
"selected_specialist": str(p.get("specialist") or "generalist"),
"execution_mode": execution_mode,
"plan_agent_version": plan_agent_version,
"relay_share_envelope": dict(p.get("relay_share_envelope") or {})
if isinstance(p.get("relay_share_envelope"), dict)
else {},
@ -329,6 +337,7 @@ async def run_agent_turn_via_bridge(
"selectedSpecialist": str(p.get("specialist") or "generalist"),
"interactionMode": str(p.get("interaction_mode") or "comprehensive"),
"dispatchReason": "execution_failed",
"executionMode": execution_mode,
"managerSelectedSpecialist": str(p.get("specialist") or "generalist"),
"requestedSpecialist": str(p.get("specialist") or "generalist"),
"dynamicAgentUsed": False,
@ -464,6 +473,7 @@ async def run_agent_turn_via_bridge(
"selectedSpecialist": str(getattr(result, "selected_specialist", "generalist") or "generalist"),
"interactionMode": str(getattr(result, "interaction_mode", "comprehensive") or "comprehensive"),
"dispatchReason": str(getattr(result, "dispatch_reason", "") or ""),
"executionMode": execution_mode,
"managerSelectedSpecialist": str(getattr(result, "manager_selected_specialist", "generalist") or "generalist"),
"requestedSpecialist": str(getattr(result, "requested_specialist", "generalist") or "generalist"),
"dynamicAgentUsed": bool(getattr(result, "dynamic_agent_used", False) or False),