Drop dead admin/chat protocol fields and fix MCP specialist docs.

Align AIA_MCP_SPECIALISTS defaults, remove inherit/perm-ban/wire-effective noise from admin APIs, and stop echoing confirm_strategy/plan_agent_version on chat mode endpoints.

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
oliver 2026-08-11 03:13:59 +08:00
parent 454c841af7
commit 8fce428540
12 changed files with 47 additions and 169 deletions

View file

@ -220,7 +220,7 @@
## MCP 与工具线侧 ## MCP 与工具线侧
- `AIA_MCP_SPECIALISTS` - `AIA_MCP_SPECIALISTS`
- 默认:`generalist,manager,ops` - 默认:`generalist,ops`
- 作用:未配置 `mcp_specialist_server_binding` 时,允许使用 MCP 的 specialist 列表 - 作用:未配置 `mcp_specialist_server_binding` 时,允许使用 MCP 的 specialist 列表
- 生效:`runtime/tools/mcp/adapter.py` - 生效:`runtime/tools/mcp/adapter.py`

View file

@ -737,24 +737,12 @@ def _normalize_execution_mode(payload: dict[str, Any] | None) -> str:
return raw if raw in {"agent", "plan"} else "agent" return raw if raw in {"agent", "plan"} else "agent"
def _normalize_confirm_strategy(payload: dict[str, Any] | None) -> str:
"""Plan-mode confirm gate removed; keep a fixed wire default for older clients."""
del payload
return "strict"
def _normalize_plan_agent_version(payload: dict[str, Any] | None) -> str:
"""Plan agent v2 removed; always wire as classic v1."""
del payload
return "v1"
def _resolve_user_menu_chat_settings( def _resolve_user_menu_chat_settings(
*, *,
store: SqliteStore, store: SqliteStore,
tenant_id: str, tenant_id: str,
user_id: str, user_id: str,
) -> tuple[str, str, str, str]: ) -> tuple[str, str]:
"""User-wide settings (⋯ menu): mode + specialist — all sessions share these keys.""" """User-wide settings (⋯ menu): mode + specialist — all sessions share these keys."""
user_mode_key = _chat_user_mode_setting_key(tenant_id=tenant_id, user_id=user_id, field="interaction_mode") user_mode_key = _chat_user_mode_setting_key(tenant_id=tenant_id, user_id=user_id, field="interaction_mode")
user_specialist_key = _chat_user_mode_setting_key(tenant_id=tenant_id, user_id=user_id, field="specialist") user_specialist_key = _chat_user_mode_setting_key(tenant_id=tenant_id, user_id=user_id, field="specialist")
@ -763,8 +751,7 @@ def _resolve_user_menu_chat_settings(
interaction_mode = normalize_interaction_mode(mode_raw or "expert") interaction_mode = normalize_interaction_mode(mode_raw or "expert")
specialist = normalize_requested_specialist(specialist_raw or "generalist") specialist = normalize_requested_specialist(specialist_raw or "generalist")
specialist = _apply_specialist_flags(store, specialist) specialist = _apply_specialist_flags(store, specialist)
# Legacy plan-mode fields kept as wire constants for older clients. return interaction_mode, specialist
return interaction_mode, specialist, "strict", "v1"
def _persist_user_menu_chat_settings( def _persist_user_menu_chat_settings(
@ -774,10 +761,7 @@ def _persist_user_menu_chat_settings(
user_id: str, user_id: str,
interaction_mode: str, interaction_mode: str,
specialist: str, specialist: str,
confirm_strategy: str,
plan_agent_version: str,
) -> None: ) -> None:
del confirm_strategy, plan_agent_version
store.set_setting( store.set_setting(
_chat_user_mode_setting_key(tenant_id=tenant_id, user_id=user_id, field="interaction_mode"), _chat_user_mode_setting_key(tenant_id=tenant_id, user_id=user_id, field="interaction_mode"),
interaction_mode, interaction_mode,
@ -834,13 +818,13 @@ def _resolve_mode_settings(
tenant_id: str, tenant_id: str,
user_id: str, user_id: str,
session_id: str, session_id: str,
) -> tuple[str, str, str, str, str, str]: ) -> tuple[str, str, str, str]:
"""User-wide mode/specialist + session memory/exec (+ legacy confirm/plan_agent defaults) for gateway + REST send.""" """User-wide mode/specialist + session memory/exec for gateway + REST send."""
u_im, u_sp, u_cs, u_pav = _resolve_user_menu_chat_settings(store=store, tenant_id=tenant_id, user_id=user_id) u_im, u_sp = _resolve_user_menu_chat_settings(store=store, tenant_id=tenant_id, user_id=user_id)
s_mm, s_em = _resolve_session_dialog_chat_settings( s_mm, s_em = _resolve_session_dialog_chat_settings(
store=store, tenant_id=tenant_id, user_id=user_id, session_id=str(session_id) store=store, tenant_id=tenant_id, user_id=user_id, session_id=str(session_id)
) )
return u_im, u_sp, s_mm, s_em, u_cs, u_pav return u_im, u_sp, s_mm, s_em
def _seed_new_session_dialog_from_user_defaults( def _seed_new_session_dialog_from_user_defaults(
@ -1255,21 +1239,16 @@ def include_chat_routes(router: APIRouter, *, resolve_auth: Callable[[SqliteStor
s_mm, s_em = _resolve_session_dialog_chat_settings( s_mm, s_em = _resolve_session_dialog_chat_settings(
store=store, tenant_id=tenant_id, user_id=user_id, session_id=str(session_id) store=store, tenant_id=tenant_id, user_id=user_id, session_id=str(session_id)
) )
u_im, u_sp, u_cs, u_pav = _resolve_user_menu_chat_settings(store=store, tenant_id=tenant_id, user_id=user_id) u_im, u_sp = _resolve_user_menu_chat_settings(store=store, tenant_id=tenant_id, user_id=user_id)
return { return {
"ok": True, "ok": True,
"interaction_mode": u_im, "interaction_mode": u_im,
"specialist": u_sp, "specialist": u_sp,
"memory_mode": s_mm, "memory_mode": s_mm,
"execution_mode": s_em, "execution_mode": s_em,
"confirm_strategy": u_cs,
"plan_agent_version": u_pav,
"plan_agent_v2_globally_enabled": False,
"global_menu": { "global_menu": {
"interaction_mode": u_im, "interaction_mode": u_im,
"specialist": u_sp, "specialist": u_sp,
"confirm_strategy": u_cs,
"plan_agent_version": u_pav,
}, },
} }
@ -1300,21 +1279,16 @@ def include_chat_routes(router: APIRouter, *, resolve_auth: Callable[[SqliteStor
s_mm, s_em = _resolve_session_dialog_chat_settings( s_mm, s_em = _resolve_session_dialog_chat_settings(
store=store, tenant_id=tenant_id, user_id=user_id, session_id=str(session_id) store=store, tenant_id=tenant_id, user_id=user_id, session_id=str(session_id)
) )
u_im, u_sp, u_cs, u_pav = _resolve_user_menu_chat_settings(store=store, tenant_id=tenant_id, user_id=user_id) u_im, u_sp = _resolve_user_menu_chat_settings(store=store, tenant_id=tenant_id, user_id=user_id)
return { return {
"ok": True, "ok": True,
"interaction_mode": u_im, "interaction_mode": u_im,
"specialist": u_sp, "specialist": u_sp,
"memory_mode": s_mm, "memory_mode": s_mm,
"execution_mode": s_em, "execution_mode": s_em,
"confirm_strategy": u_cs,
"plan_agent_version": u_pav,
"plan_agent_v2_globally_enabled": False,
"global_menu": { "global_menu": {
"interaction_mode": u_im, "interaction_mode": u_im,
"specialist": u_sp, "specialist": u_sp,
"confirm_strategy": u_cs,
"plan_agent_version": u_pav,
}, },
} }
@ -1326,14 +1300,11 @@ def include_chat_routes(router: APIRouter, *, resolve_auth: Callable[[SqliteStor
ctx = resolve_auth(store, authorization) ctx = resolve_auth(store, authorization)
tenant_id = str(ctx.get("tenant_id") or "") tenant_id = str(ctx.get("tenant_id") or "")
user_id = str(ctx.get("user_id") or "") user_id = str(ctx.get("user_id") or "")
u_im, u_sp, u_cs, u_pav = _resolve_user_menu_chat_settings(store=store, tenant_id=tenant_id, user_id=user_id) u_im, u_sp = _resolve_user_menu_chat_settings(store=store, tenant_id=tenant_id, user_id=user_id)
return { return {
"ok": True, "ok": True,
"interaction_mode": u_im, "interaction_mode": u_im,
"specialist": u_sp, "specialist": u_sp,
"confirm_strategy": u_cs,
"plan_agent_version": u_pav,
"plan_agent_v2_globally_enabled": False,
} }
@chat.post("/user-mode") @chat.post("/user-mode")
@ -1349,27 +1320,18 @@ def include_chat_routes(router: APIRouter, *, resolve_auth: Callable[[SqliteStor
interaction_mode = normalize_interaction_mode(payload.get("interaction_mode")) interaction_mode = normalize_interaction_mode(payload.get("interaction_mode"))
specialist = normalize_requested_specialist(payload.get("specialist")) specialist = normalize_requested_specialist(payload.get("specialist"))
specialist = _apply_specialist_flags(store, specialist) specialist = _apply_specialist_flags(store, specialist)
confirm_strategy = _normalize_confirm_strategy(payload)
plan_agent_version = _normalize_plan_agent_version(payload)
_persist_user_menu_chat_settings( _persist_user_menu_chat_settings(
store=store, store=store,
tenant_id=tenant_id, tenant_id=tenant_id,
user_id=user_id, user_id=user_id,
interaction_mode=interaction_mode, interaction_mode=interaction_mode,
specialist=specialist, specialist=specialist,
confirm_strategy=confirm_strategy,
plan_agent_version=plan_agent_version,
) )
u_im, u_sp, u_cs, u_pav = _resolve_user_menu_chat_settings(store=store, tenant_id=tenant_id, user_id=user_id) u_im, u_sp = _resolve_user_menu_chat_settings(store=store, tenant_id=tenant_id, user_id=user_id)
# Plan mode removed; keep setting key cleared for leftover clients.
store.set_setting("AIA_EXPERT_PLAN_AGENT_V2_ENABLED", "0")
return { return {
"ok": True, "ok": True,
"interaction_mode": u_im, "interaction_mode": u_im,
"specialist": u_sp, "specialist": u_sp,
"confirm_strategy": u_cs,
"plan_agent_version": u_pav,
"plan_agent_v2_globally_enabled": False,
} }
@chat.get("/admin/user-stats") @chat.get("/admin/user-stats")
@ -1945,42 +1907,33 @@ def include_chat_routes(router: APIRouter, *, resolve_auth: Callable[[SqliteStor
memory_mode = _normalize_memory_mode(payload) memory_mode = _normalize_memory_mode(payload)
execution_mode = _normalize_execution_mode(payload) execution_mode = _normalize_execution_mode(payload)
if "interaction_mode" not in payload and "chat_mode" not in payload: if "interaction_mode" not in payload and "chat_mode" not in payload:
interaction_mode, _, _, _, _, _ = _resolve_mode_settings( interaction_mode, _, _, _ = _resolve_mode_settings(
store=store, store=store,
tenant_id=tenant_id, tenant_id=tenant_id,
user_id=user_id, user_id=user_id,
session_id=str(session_id), session_id=str(session_id),
) )
if "specialist" not in payload: if "specialist" not in payload:
_, selected_specialist, _, _, _, _ = _resolve_mode_settings( _, selected_specialist, _, _ = _resolve_mode_settings(
store=store, store=store,
tenant_id=tenant_id, tenant_id=tenant_id,
user_id=user_id, user_id=user_id,
session_id=str(session_id), session_id=str(session_id),
) )
if "memory_mode" not in payload: if "memory_mode" not in payload:
_, _, memory_mode, _, _, _ = _resolve_mode_settings( _, _, memory_mode, _ = _resolve_mode_settings(
store=store, store=store,
tenant_id=tenant_id, tenant_id=tenant_id,
user_id=user_id, user_id=user_id,
session_id=str(session_id), session_id=str(session_id),
) )
if "execution_mode" not in payload: if "execution_mode" not in payload:
_, _, _, execution_mode, _, _ = _resolve_mode_settings( _, _, _, execution_mode = _resolve_mode_settings(
store=store, store=store,
tenant_id=tenant_id, tenant_id=tenant_id,
user_id=user_id, user_id=user_id,
session_id=str(session_id), session_id=str(session_id),
) )
if "plan_agent_version" not in payload:
_, _, _, _, _, plan_agent_version = _resolve_mode_settings(
store=store,
tenant_id=tenant_id,
user_id=user_id,
session_id=str(session_id),
)
else:
plan_agent_version = _normalize_plan_agent_version(payload)
selected_specialist = _apply_specialist_flags(store, selected_specialist) selected_specialist = _apply_specialist_flags(store, selected_specialist)
if not text_raw and not attachments: if not text_raw and not attachments:
raise HTTPException(status_code=400, detail="text_or_attachments_required") raise HTTPException(status_code=400, detail="text_or_attachments_required")
@ -2036,7 +1989,6 @@ def include_chat_routes(router: APIRouter, *, resolve_auth: Callable[[SqliteStor
"selected_specialist": selected_specialist, "selected_specialist": selected_specialist,
"memory_mode": memory_mode, "memory_mode": memory_mode,
"execution_mode": execution_mode, "execution_mode": execution_mode,
"plan_agent_version": plan_agent_version,
}, },
) )
gw_result = gw.handle_turn( gw_result = gw.handle_turn(
@ -2089,42 +2041,33 @@ def include_chat_routes(router: APIRouter, *, resolve_auth: Callable[[SqliteStor
memory_mode = _normalize_memory_mode(payload) memory_mode = _normalize_memory_mode(payload)
execution_mode = _normalize_execution_mode(payload) execution_mode = _normalize_execution_mode(payload)
if "interaction_mode" not in payload and "chat_mode" not in payload: if "interaction_mode" not in payload and "chat_mode" not in payload:
interaction_mode, _, _, _, _, _ = _resolve_mode_settings( interaction_mode, _, _, _ = _resolve_mode_settings(
store=store, store=store,
tenant_id=tenant_id, tenant_id=tenant_id,
user_id=user_id, user_id=user_id,
session_id=str(session_id), session_id=str(session_id),
) )
if "specialist" not in payload: if "specialist" not in payload:
_, selected_specialist, _, _, _, _ = _resolve_mode_settings( _, selected_specialist, _, _ = _resolve_mode_settings(
store=store, store=store,
tenant_id=tenant_id, tenant_id=tenant_id,
user_id=user_id, user_id=user_id,
session_id=str(session_id), session_id=str(session_id),
) )
if "memory_mode" not in payload: if "memory_mode" not in payload:
_, _, memory_mode, _, _, _ = _resolve_mode_settings( _, _, memory_mode, _ = _resolve_mode_settings(
store=store, store=store,
tenant_id=tenant_id, tenant_id=tenant_id,
user_id=user_id, user_id=user_id,
session_id=str(session_id), session_id=str(session_id),
) )
if "execution_mode" not in payload: if "execution_mode" not in payload:
_, _, _, execution_mode, _, _ = _resolve_mode_settings( _, _, _, execution_mode = _resolve_mode_settings(
store=store, store=store,
tenant_id=tenant_id, tenant_id=tenant_id,
user_id=user_id, user_id=user_id,
session_id=str(session_id), session_id=str(session_id),
) )
if "plan_agent_version" not in payload:
_, _, _, _, _, plan_agent_version = _resolve_mode_settings(
store=store,
tenant_id=tenant_id,
user_id=user_id,
session_id=str(session_id),
)
else:
plan_agent_version = _normalize_plan_agent_version(payload)
selected_specialist = _apply_specialist_flags(store, selected_specialist) selected_specialist = _apply_specialist_flags(store, selected_specialist)
if not text_raw and not attachments: if not text_raw and not attachments:
raise HTTPException(status_code=400, detail="text_or_attachments_required") raise HTTPException(status_code=400, detail="text_or_attachments_required")
@ -2169,7 +2112,6 @@ def include_chat_routes(router: APIRouter, *, resolve_auth: Callable[[SqliteStor
"selected_specialist": selected_specialist, "selected_specialist": selected_specialist,
"memory_mode": memory_mode, "memory_mode": memory_mode,
"execution_mode": execution_mode, "execution_mode": execution_mode,
"plan_agent_version": plan_agent_version,
} }
_DONE = object() _DONE = object()

View file

@ -612,9 +612,7 @@ def build_admin_router() -> APIRouter:
"removed_total": len(llm_plan.removed_names), "removed_total": len(llm_plan.removed_names),
"removed_mcp_total": len(llm_plan.removed_mcp_names), "removed_mcp_total": len(llm_plan.removed_mcp_names),
"changed_total": len(llm_plan.changed_names), "changed_total": len(llm_plan.changed_names),
"policy_perm_ban_9999": 0,
"mcp_enabled": bool(llm_plan.mcp_enabled), "mcp_enabled": bool(llm_plan.mcp_enabled),
"wire_policy_effective": False,
} }
) )
@ -625,7 +623,6 @@ def build_admin_router() -> APIRouter:
"summary": { "summary": {
"total_internal_tools": total_internal, "total_internal_tools": total_internal,
"total_wired_tools": total_wired, "total_wired_tools": total_wired,
"total_perm_ban_9999": 0,
}, },
"items": items, "items": items,
} }
@ -665,8 +662,6 @@ def build_admin_router() -> APIRouter:
"max_json_bytes": plan.max_json_bytes, "max_json_bytes": plan.max_json_bytes,
"mcp_enabled": plan.mcp_enabled, "mcp_enabled": plan.mcp_enabled,
"role_mode": plan.role_mode, "role_mode": plan.role_mode,
"wire_policy_effective": plan.wire_policy_effective,
"policy_keys": plan.policy_keys,
"raw_count": len(plan.tools_raw), "raw_count": len(plan.tools_raw),
"wired_count": len(plan.tools_wired), "wired_count": len(plan.tools_wired),
"removed_mcp_names": plan.removed_mcp_names, "removed_mcp_names": plan.removed_mcp_names,

View file

@ -347,7 +347,6 @@ def include_skill_routes(
"enabled": bool(skill_role_binding_enabled(store=store)), "enabled": bool(skill_role_binding_enabled(store=store)),
"enabled_stored": bool(skill_role_binding_enabled_stored(store=store)), "enabled_stored": bool(skill_role_binding_enabled_stored(store=store)),
"enabled_env_present": bool(skill_role_binding_enabled_env_present()), "enabled_env_present": bool(skill_role_binding_enabled_env_present()),
"manager_inherit": False,
"available_roles": roles, "available_roles": roles,
"installed_skills": items, "installed_skills": items,
"mapping": mapping, "mapping": mapping,
@ -385,7 +384,6 @@ def include_skill_routes(
"enabled": bool(skill_role_binding_enabled(store=store)), "enabled": bool(skill_role_binding_enabled(store=store)),
"enabled_stored": bool(skill_role_binding_enabled_stored(store=store)), "enabled_stored": bool(skill_role_binding_enabled_stored(store=store)),
"enabled_env_present": bool(skill_role_binding_enabled_env_present()), "enabled_env_present": bool(skill_role_binding_enabled_env_present()),
"manager_inherit": False,
"available_roles": roles, "available_roles": roles,
"mapping": mapping, "mapping": mapping,
} }
@ -409,7 +407,6 @@ def include_skill_routes(
"total": 0, "total": 0,
"workspace_total": 0, "workspace_total": 0,
"workspace_direct": 0, "workspace_direct": 0,
"workspace_inherited_manager": 0,
"mcp_total": 0, "mcp_total": 0,
"tool_total": 0, "tool_total": 0,
"names_preview": [], "names_preview": [],
@ -461,7 +458,6 @@ def include_skill_routes(
"total": len(entries), "total": len(entries),
"workspace_total": workspace_total, "workspace_total": workspace_total,
"workspace_direct": workspace_direct, "workspace_direct": workspace_direct,
"workspace_inherited_manager": 0,
"workspace_resolved_tool_match": workspace_resolved_tool_match, "workspace_resolved_tool_match": workspace_resolved_tool_match,
"workspace_docs_only": workspace_docs_only, "workspace_docs_only": workspace_docs_only,
"mcp_total": mcp_total, "mcp_total": mcp_total,

View file

@ -8409,8 +8409,6 @@ async function renderSkills() {
{ {
removed_mcp_names: llmToolsState.removed_mcp_names, removed_mcp_names: llmToolsState.removed_mcp_names,
role_mode: resp.role_mode, role_mode: resp.role_mode,
wire_policy_effective: !!resp.wire_policy_effective,
policy_keys: resp.policy_keys,
skipped_public: resp.skipped_public || [], skipped_public: resp.skipped_public || [],
skipped_expert: resp.skipped_expert || [], skipped_expert: resp.skipped_expert || [],
}, },

View file

@ -402,8 +402,6 @@ const CHAT_USER_MENU_MODE_KEY = "ops_chat_user_menu_mode";
const CHAT_REASONING_TOGGLE_KEY = "ops_chat_reasoning_toggle"; const CHAT_REASONING_TOGGLE_KEY = "ops_chat_reasoning_toggle";
const EXECUTION_MODE_AGENT = "agent"; const EXECUTION_MODE_AGENT = "agent";
const EXECUTION_MODE_PLAN = "plan"; const EXECUTION_MODE_PLAN = "plan";
const CONFIRM_STRATEGY_STRICT = "strict";
const PLAN_AGENT_V1 = "v1";
/** Default on: reasoning/tool fold matches streamed behavior; new browsers have no localStorage yet. */ /** Default on: reasoning/tool fold matches streamed behavior; new browsers have no localStorage yet. */
const ADMIN_CHAT_SHOW_TOOL_OUTPUT_DEFAULT = true; const ADMIN_CHAT_SHOW_TOOL_OUTPUT_DEFAULT = true;
const REASONING_BLOCK_MAX_CHARS = 12000; const REASONING_BLOCK_MAX_CHARS = 12000;
@ -3539,8 +3537,6 @@ async function renderChatUi() {
const resp = await apiPost("/admin/api/chat/user-mode", { const resp = await apiPost("/admin/api/chat/user-mode", {
interaction_mode: "expert", interaction_mode: "expert",
specialist, specialist,
confirm_strategy: CONFIRM_STRATEGY_STRICT,
plan_agent_version: PLAN_AGENT_V1,
}); });
localStorage.setItem(CHAT_USER_MENU_MODE_KEY, specialist); localStorage.setItem(CHAT_USER_MENU_MODE_KEY, specialist);
globalMenuModeValue = specialist; globalMenuModeValue = specialist;
@ -4422,7 +4418,6 @@ async function renderChatUi() {
specialist: String(specialist || "generalist"), specialist: String(specialist || "generalist"),
memory_mode: String(memoryMode || "default"), memory_mode: String(memoryMode || "default"),
execution_mode: String(executionMode || "agent"), execution_mode: String(executionMode || "agent"),
plan_agent_version: PLAN_AGENT_V1,
lang: currentLang === "en" ? "en" : "zh", lang: currentLang === "en" ? "en" : "zh",
}, },
}; };

View file

@ -67,9 +67,6 @@ def build_gateway_context(
execution_mode = str(p.get("execution_mode") or "agent").strip().lower() or "agent" execution_mode = str(p.get("execution_mode") or "agent").strip().lower() or "agent"
if execution_mode not in {"agent", "plan"}: if execution_mode not in {"agent", "plan"}:
execution_mode = "agent" execution_mode = "agent"
plan_agent_version = str(p.get("plan_agent_version") or "v1").strip().lower() or "v1"
if plan_agent_version != "v1":
plan_agent_version = "v1"
if interaction_mode != "expert": if interaction_mode != "expert":
interaction_mode = "expert" interaction_mode = "expert"
raw_env = dict(p.get("relay_share_envelope") or {}) if isinstance(p.get("relay_share_envelope"), dict) else {} raw_env = dict(p.get("relay_share_envelope") or {}) if isinstance(p.get("relay_share_envelope"), dict) else {}
@ -82,7 +79,6 @@ def build_gateway_context(
"interaction_mode": interaction_mode, "interaction_mode": interaction_mode,
"specialist": specialist, "specialist": specialist,
"execution_mode": execution_mode, "execution_mode": execution_mode,
"plan_agent_version": plan_agent_version,
"relay_share_envelope": norm_env if ok_env else {}, "relay_share_envelope": norm_env if ok_env else {},
"acp_parent_run_id": str(p.get("acp_parent_run_id") or ""), "acp_parent_run_id": str(p.get("acp_parent_run_id") or ""),
"acp_child_run_id": str(p.get("acp_child_run_id") or ""), "acp_child_run_id": str(p.get("acp_child_run_id") or ""),
@ -106,9 +102,6 @@ def build_gateway_context(
execution_mode = str(p.get("execution_mode") or "agent").strip().lower() or "agent" execution_mode = str(p.get("execution_mode") or "agent").strip().lower() or "agent"
if execution_mode not in {"agent", "plan"}: if execution_mode not in {"agent", "plan"}:
execution_mode = "agent" execution_mode = "agent"
plan_agent_version = str(p.get("plan_agent_version") or "v1").strip().lower() or "v1"
if plan_agent_version != "v1":
plan_agent_version = "v1"
if interaction_mode != "expert": if interaction_mode != "expert":
interaction_mode = "expert" interaction_mode = "expert"
raw_env = dict(p.get("relay_share_envelope") or {}) if isinstance(p.get("relay_share_envelope"), dict) else {} raw_env = dict(p.get("relay_share_envelope") or {}) if isinstance(p.get("relay_share_envelope"), dict) else {}
@ -121,7 +114,6 @@ def build_gateway_context(
"interaction_mode": interaction_mode, "interaction_mode": interaction_mode,
"specialist": specialist, "specialist": specialist,
"execution_mode": execution_mode, "execution_mode": execution_mode,
"plan_agent_version": plan_agent_version,
"relay_share_envelope": norm_env if ok_env else {}, "relay_share_envelope": norm_env if ok_env else {},
"acp_parent_run_id": str(p.get("acp_parent_run_id") or ""), "acp_parent_run_id": str(p.get("acp_parent_run_id") or ""),
"acp_child_run_id": str(p.get("acp_child_run_id") or ""), "acp_child_run_id": str(p.get("acp_child_run_id") or ""),

View file

@ -177,9 +177,6 @@ async def run_agent_turn_via_bridge(
execution_mode = str(p.get("execution_mode") or "agent").strip().lower() or "agent" execution_mode = str(p.get("execution_mode") or "agent").strip().lower() or "agent"
if execution_mode not in {"agent", "plan"}: if execution_mode not in {"agent", "plan"}:
execution_mode = "agent" execution_mode = "agent"
plan_agent_version = str(p.get("plan_agent_version") or "v1").strip().lower() or "v1"
if plan_agent_version != "v1":
plan_agent_version = "v1"
store = get_assistant_store() store = get_assistant_store()
gw = OclawGateway(store=store) gw = OclawGateway(store=store)
@ -292,7 +289,6 @@ async def run_agent_turn_via_bridge(
"interaction_mode": str(p.get("interaction_mode") or "expert"), "interaction_mode": str(p.get("interaction_mode") or "expert"),
"selected_specialist": str(p.get("specialist") or "generalist"), "selected_specialist": str(p.get("specialist") or "generalist"),
"execution_mode": execution_mode, "execution_mode": execution_mode,
"plan_agent_version": plan_agent_version,
"relay_share_envelope": dict(p.get("relay_share_envelope") or {}) "relay_share_envelope": dict(p.get("relay_share_envelope") or {})
if isinstance(p.get("relay_share_envelope"), dict) if isinstance(p.get("relay_share_envelope"), dict)
else {}, else {},

View file

@ -210,7 +210,7 @@ def main() -> int:
print("session/mode", gj, file=sys.stderr) print("session/mode", gj, file=sys.stderr)
return 1 return 1
print("\n--- 当前会话模式(将原样带入 POST) ---") print("\n--- 当前会话模式(将原样带入 POST) ---")
print(json.dumps({k: gj.get(k) for k in ("interaction_mode", "specialist", "memory_mode", "execution_mode", "confirm_strategy", "plan_agent_version") if k in gj}, ensure_ascii=False)) print(json.dumps({k: gj.get(k) for k in ("interaction_mode", "specialist", "memory_mode", "execution_mode") if k in gj}, ensure_ascii=False))
payload = { payload = {
"text": str(args.text), "text": str(args.text),

View file

@ -172,46 +172,31 @@ class AdminChatStreamAsyncTaskTests(unittest.TestCase):
payload = json.loads(str(task.payload or "{}")) payload = json.loads(str(task.payload or "{}"))
self.assertEqual(str(payload.get("selected_specialist") or ""), "generalist") self.assertEqual(str(payload.get("selected_specialist") or ""), "generalist")
def test_user_mode_always_clears_plan_agent_v2_flag(self) -> None: def test_user_mode_roundtrip_without_legacy_fields(self) -> None:
"""Plan mode removed: POST /user-mode always keeps AIA_EXPERT_PLAN_AGENT_V2_ENABLED=0."""
token = self._login() token = self._login()
headers = { headers = {
"authorization": f"Bearer {token}", "authorization": f"Bearer {token}",
"accept": "application/json", "accept": "application/json",
"content-type": "application/json", "content-type": "application/json",
} }
r2 = self.client.post( r = self.client.post(
"/admin/api/chat/user-mode", "/admin/api/chat/user-mode",
headers=headers, headers=headers,
json={ json={
"interaction_mode": "expert", "interaction_mode": "expert",
"specialist": "generalist", "specialist": "generalist",
"confirm_strategy": "strict", "confirm_strategy": "auto",
"plan_agent_version": "v2", "plan_agent_version": "v2",
}, },
) )
self.assertEqual(r2.status_code, 200) self.assertEqual(r.status_code, 200)
body2 = r2.json() body = r.json()
self.assertTrue(body2.get("ok"), body2) self.assertTrue(body.get("ok"), body)
self.assertFalse(body2.get("plan_agent_v2_globally_enabled"), body2) self.assertEqual(str(body.get("interaction_mode") or ""), "expert")
self.assertEqual(str(body2.get("plan_agent_version") or ""), "v1") self.assertEqual(str(body.get("specialist") or ""), "generalist")
self.assertEqual(str(self.store.get_setting("AIA_EXPERT_PLAN_AGENT_V2_ENABLED") or "").strip(), "0") self.assertNotIn("confirm_strategy", body)
self.assertNotIn("plan_agent_version", body)
r1 = self.client.post( self.assertNotIn("plan_agent_v2_globally_enabled", body)
"/admin/api/chat/user-mode",
headers=headers,
json={
"interaction_mode": "expert",
"specialist": "generalist",
"confirm_strategy": "strict",
"plan_agent_version": "v1",
},
)
self.assertEqual(r1.status_code, 200)
body1 = r1.json()
self.assertTrue(body1.get("ok"), body1)
self.assertFalse(body1.get("plan_agent_v2_globally_enabled"), body1)
self.assertEqual(str(self.store.get_setting("AIA_EXPERT_PLAN_AGENT_V2_ENABLED") or "").strip(), "0")
def test_session_mode_setting_roundtrip(self) -> None: def test_session_mode_setting_roundtrip(self) -> None:
token = self._login() token = self._login()
@ -226,8 +211,6 @@ class AdminChatStreamAsyncTaskTests(unittest.TestCase):
json={ json={
"interaction_mode": "expert", "interaction_mode": "expert",
"specialist": "generalist", "specialist": "generalist",
"confirm_strategy": "auto",
"plan_agent_version": "v1",
}, },
) )
resp1 = self.client.post( resp1 = self.client.post(
@ -242,7 +225,8 @@ class AdminChatStreamAsyncTaskTests(unittest.TestCase):
# Session POST ignores interaction/specialist; user menu still has generalist. # Session POST ignores interaction/specialist; user menu still has generalist.
self.assertEqual(str(body1.get("specialist") or ""), "generalist") self.assertEqual(str(body1.get("specialist") or ""), "generalist")
self.assertEqual(str(body1.get("memory_mode") or ""), "store_only") self.assertEqual(str(body1.get("memory_mode") or ""), "store_only")
self.assertEqual(str(body1.get("confirm_strategy") or ""), "strict") self.assertNotIn("confirm_strategy", body1)
self.assertNotIn("plan_agent_version", body1)
resp2 = self.client.get( resp2 = self.client.get(
f"/admin/api/chat/sessions/{self.session_id}/mode", f"/admin/api/chat/sessions/{self.session_id}/mode",
@ -254,7 +238,8 @@ class AdminChatStreamAsyncTaskTests(unittest.TestCase):
self.assertEqual(str(body2.get("interaction_mode") or ""), "expert") self.assertEqual(str(body2.get("interaction_mode") or ""), "expert")
self.assertEqual(str(body2.get("specialist") or ""), "generalist") self.assertEqual(str(body2.get("specialist") or ""), "generalist")
self.assertEqual(str(body2.get("memory_mode") or ""), "store_only") self.assertEqual(str(body2.get("memory_mode") or ""), "store_only")
self.assertEqual(str(body2.get("confirm_strategy") or ""), "strict") self.assertNotIn("confirm_strategy", body2)
self.assertNotIn("plan_agent_version", body2)
def test_messages_use_session_mode_when_payload_omits_mode(self) -> None: def test_messages_use_session_mode_when_payload_omits_mode(self) -> None:
token = self._login() token = self._login()
@ -269,8 +254,6 @@ class AdminChatStreamAsyncTaskTests(unittest.TestCase):
json={ json={
"interaction_mode": "expert", "interaction_mode": "expert",
"specialist": "ops", "specialist": "ops",
"confirm_strategy": "strict",
"plan_agent_version": "v1",
}, },
) )
_ = self.client.post( _ = self.client.post(
@ -308,8 +291,6 @@ class AdminChatStreamAsyncTaskTests(unittest.TestCase):
json={ json={
"interaction_mode": "expert", "interaction_mode": "expert",
"specialist": "ops", "specialist": "ops",
"confirm_strategy": "strict",
"plan_agent_version": "v1",
}, },
) )
self.assertEqual(pref.status_code, 200) self.assertEqual(pref.status_code, 200)
@ -336,7 +317,8 @@ class AdminChatStreamAsyncTaskTests(unittest.TestCase):
self.assertEqual(str(mode_body.get("specialist") or ""), "ops") self.assertEqual(str(mode_body.get("specialist") or ""), "ops")
self.assertEqual(str(mode_body.get("memory_mode") or ""), "default") self.assertEqual(str(mode_body.get("memory_mode") or ""), "default")
self.assertEqual(str(mode_body.get("execution_mode") or ""), "agent") self.assertEqual(str(mode_body.get("execution_mode") or ""), "agent")
self.assertEqual(str(mode_body.get("confirm_strategy") or ""), "strict") self.assertNotIn("confirm_strategy", mode_body)
self.assertNotIn("plan_agent_version", mode_body)
gm = mode_body.get("global_menu") if isinstance(mode_body.get("global_menu"), dict) else {} gm = mode_body.get("global_menu") if isinstance(mode_body.get("global_menu"), dict) else {}
self.assertEqual(str(gm.get("interaction_mode") or ""), "expert") self.assertEqual(str(gm.get("interaction_mode") or ""), "expert")
self.assertEqual(str(gm.get("specialist") or ""), "ops") self.assertEqual(str(gm.get("specialist") or ""), "ops")
@ -369,7 +351,7 @@ class AdminChatStreamAsyncTaskTests(unittest.TestCase):
self.assertEqual(str(mode_body.get("interaction_mode") or ""), "expert") self.assertEqual(str(mode_body.get("interaction_mode") or ""), "expert")
self.assertEqual(str(mode_body.get("specialist") or ""), "generalist") self.assertEqual(str(mode_body.get("specialist") or ""), "generalist")
self.assertEqual(str(mode_body.get("memory_mode") or ""), "default") self.assertEqual(str(mode_body.get("memory_mode") or ""), "default")
self.assertEqual(str(mode_body.get("confirm_strategy") or ""), "strict") self.assertNotIn("confirm_strategy", mode_body)
def test_admin_dynamic_expert_stats_endpoint(self) -> None: def test_admin_dynamic_expert_stats_endpoint(self) -> None:
token = self._login() token = self._login()

View file

@ -52,14 +52,15 @@ class AdminToolsSelfCheckApiTests(unittest.TestCase):
summary = dict(body.get("summary") or {}) summary = dict(body.get("summary") or {})
self.assertIn("total_internal_tools", summary) self.assertIn("total_internal_tools", summary)
self.assertIn("total_wired_tools", summary) self.assertIn("total_wired_tools", summary)
self.assertIn("total_perm_ban_9999", summary) self.assertNotIn("total_perm_ban_9999", summary)
items = list(body.get("items") or []) items = list(body.get("items") or [])
self.assertTrue(items) self.assertTrue(items)
one = dict(items[0] if items else {}) one = dict(items[0] if items else {})
self.assertIn("role", one) self.assertIn("role", one)
self.assertIn("role_mode", one) self.assertIn("role_mode", one)
self.assertIn("wired_count", one) self.assertIn("wired_count", one)
self.assertIn("policy_perm_ban_9999", one) self.assertNotIn("policy_perm_ban_9999", one)
self.assertNotIn("wire_policy_effective", one)
if __name__ == "__main__": if __name__ == "__main__":

View file

@ -34,51 +34,32 @@ def test_mode_session_and_user_prefs_merge_for_gateway() -> None:
user_id="u1", user_id="u1",
interaction_mode="expert", interaction_mode="expert",
specialist="generalist", specialist="generalist",
confirm_strategy="auto",
plan_agent_version="v2",
) )
interaction_mode, specialist, memory_mode, execution_mode, confirm_strategy, plan_agent_version = ( interaction_mode, specialist, memory_mode, execution_mode = _resolve_mode_settings(
_resolve_mode_settings(store=store, tenant_id="t1", user_id="u1", session_id="s1") store=store, tenant_id="t1", user_id="u1", session_id="s1"
) )
assert interaction_mode == "expert" assert interaction_mode == "expert"
assert specialist == "generalist" assert specialist == "generalist"
assert memory_mode == "default" assert memory_mode == "default"
assert execution_mode == "plan" assert execution_mode == "plan"
assert confirm_strategy == "strict"
assert plan_agent_version == "v1"
def test_mode_session_defaults_when_session_keys_missing() -> None: def test_mode_session_defaults_when_session_keys_missing() -> None:
store = _DummyStore() store = _DummyStore()
store.set_setting("chat.user.mode.t1.u1.confirm_strategy", "invalid") interaction_mode, specialist, memory_mode, execution_mode = _resolve_mode_settings(
interaction_mode, specialist, memory_mode, execution_mode, confirm_strategy, plan_agent_version = ( store=store, tenant_id="t1", user_id="u1", session_id="s1"
_resolve_mode_settings(store=store, tenant_id="t1", user_id="u1", session_id="s1")
) )
assert interaction_mode == "expert" assert interaction_mode == "expert"
assert specialist == "generalist" assert specialist == "generalist"
assert memory_mode == "default" assert memory_mode == "default"
assert execution_mode == "agent" assert execution_mode == "agent"
assert confirm_strategy == "strict"
assert plan_agent_version == "v1"
def test_invalid_execution_mode_on_session_dialog_falls_back_to_agent() -> None: def test_invalid_execution_mode_on_session_dialog_falls_back_to_agent() -> None:
store = _DummyStore() store = _DummyStore()
store.set_setting("chat.session.mode.t1.u1.s1.execution_mode", "invalid") store.set_setting("chat.session.mode.t1.u1.s1.execution_mode", "invalid")
interaction_mode, specialist, memory_mode, execution_mode, confirm_strategy, plan_agent_version = ( interaction_mode, specialist, memory_mode, execution_mode = _resolve_mode_settings(
_resolve_mode_settings(store=store, tenant_id="t1", user_id="u1", session_id="s1") store=store, tenant_id="t1", user_id="u1", session_id="s1"
) )
assert execution_mode == "agent" assert execution_mode == "agent"
assert interaction_mode == "expert" assert interaction_mode == "expert"
def test_legacy_confirm_and_plan_agent_prefs_coerce_to_defaults() -> None:
store = _DummyStore()
store.set_setting("chat.user.mode.t1.u1.confirm_strategy", "auto")
store.set_setting("chat.user.mode.t1.u1.plan_agent_version", "v2")
interaction_mode, specialist, memory_mode, execution_mode, confirm_strategy, plan_agent_version = (
_resolve_mode_settings(store=store, tenant_id="t1", user_id="u1", session_id="s1")
)
assert confirm_strategy == "strict"
assert plan_agent_version == "v1"
assert specialist == "generalist"