diff --git a/interfaces/admin/chat_api.py b/interfaces/admin/chat_api.py index ff847cc0..455a6ba1 100644 --- a/interfaces/admin/chat_api.py +++ b/interfaces/admin/chat_api.py @@ -758,20 +758,13 @@ def _resolve_user_menu_chat_settings( """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_specialist_key = _chat_user_mode_setting_key(tenant_id=tenant_id, user_id=user_id, field="specialist") - user_confirm_strategy_key = _chat_user_mode_setting_key(tenant_id=tenant_id, user_id=user_id, field="confirm_strategy") - user_plan_agent_version_key = _chat_user_mode_setting_key( - tenant_id=tenant_id, user_id=user_id, field="plan_agent_version" - ) mode_raw = str(store.get_setting(user_mode_key) or "").strip() specialist_raw = str(store.get_setting(user_specialist_key) or "").strip() - confirm_raw = str(store.get_setting(user_confirm_strategy_key) or "").strip() - plan_agent_raw = str(store.get_setting(user_plan_agent_version_key) or "").strip() interaction_mode = normalize_interaction_mode(mode_raw or "expert") specialist = normalize_requested_specialist(specialist_raw or "generalist") specialist = _apply_specialist_flags(store, specialist) - confirm_strategy = _normalize_confirm_strategy({"confirm_strategy": (confirm_raw or "strict")}) - plan_agent_version = _normalize_plan_agent_version({"plan_agent_version": (plan_agent_raw or "v1")}) - return interaction_mode, specialist, confirm_strategy, plan_agent_version + # Legacy plan-mode fields kept as wire constants for older clients. + return interaction_mode, specialist, "strict", "v1" def _persist_user_menu_chat_settings( @@ -784,6 +777,7 @@ def _persist_user_menu_chat_settings( confirm_strategy: str, plan_agent_version: str, ) -> None: + del confirm_strategy, plan_agent_version store.set_setting( _chat_user_mode_setting_key(tenant_id=tenant_id, user_id=user_id, field="interaction_mode"), interaction_mode, @@ -792,15 +786,6 @@ def _persist_user_menu_chat_settings( _chat_user_mode_setting_key(tenant_id=tenant_id, user_id=user_id, field="specialist"), specialist, ) - store.set_setting( - _chat_user_mode_setting_key(tenant_id=tenant_id, user_id=user_id, field="confirm_strategy"), - confirm_strategy, - ) - store.set_setting( - _chat_user_mode_setting_key(tenant_id=tenant_id, user_id=user_id, field="plan_agent_version"), - plan_agent_version, - ) - store.set_setting("AIA_EXPERT_PLAN_CONFIRM_STRATEGY", confirm_strategy) def _resolve_session_dialog_chat_settings( diff --git a/interfaces/admin/routes.py b/interfaces/admin/routes.py index a57f5d44..ca25490b 100644 --- a/interfaces/admin/routes.py +++ b/interfaces/admin/routes.py @@ -440,17 +440,15 @@ def build_admin_router() -> APIRouter: def _ordered_specialists() -> list[str]: base = [str(k).strip().lower() for k in discover_specialist_ids() if str(k).strip()] - preferred = [x for x in ("generalist", "ops", "image") if x in set(base)] + preferred = [x for x in ("generalist", "ops", "memory") if x in set(base)] return preferred + [x for x in base if x not in set(preferred)] def _ordered_mcp_roles() -> list[str]: - specs = _ordered_specialists() - return ["manager", *[x for x in specs if x != "manager"]] + return _ordered_specialists() def _ordered_roles() -> list[str]: """Canonical role list used by Admin preview APIs.""" - specs = _ordered_specialists() - return ["manager", *[x for x in specs if x != "manager"]] + return _ordered_specialists() _EXPERT_ALLOWED_FILES = {"SOUL.md", "ROLE_SYSTEM.md"} @@ -3197,7 +3195,7 @@ def build_admin_router() -> APIRouter: ctx = _resolve_auth(store, authorization) _require_permission(ctx, "admin:tenant:write") available = _ordered_mcp_roles() - raw = str(store.get_setting("mcp_allowed_specialists") or "").strip() or "generalist,manager,ops" + raw = str(store.get_setting("mcp_allowed_specialists") or "").strip() or "generalist,ops" allowed = [x.strip().lower() for x in raw.split(",") if x.strip()] allowed_set = set(allowed) ordered = [x for x in available if x in allowed_set] @@ -3220,7 +3218,7 @@ def build_admin_router() -> APIRouter: items = raw if isinstance(raw, list) else [] allowset = {str(x).strip().lower() for x in items if str(x).strip().lower() in available_set} if not allowset: - defaults = {x for x in ("generalist", "manager") if x in available_set} + defaults = {x for x in ("generalist", "ops") if x in available_set} allowset = defaults if defaults else (set(available[:1]) if available else set()) ordered = [x for x in available if x in allowset] store.set_setting("mcp_allowed_specialists", ",".join(ordered)) diff --git a/interfaces/admin/skills_api.py b/interfaces/admin/skills_api.py index 8f4ff339..e72b889e 100644 --- a/interfaces/admin/skills_api.py +++ b/interfaces/admin/skills_api.py @@ -22,7 +22,6 @@ from runtime.skill_installer import ( from runtime.skill_role_binding import ( SKILL_ROLE_BINDING_ENABLED_SETTING, SKILL_ROLE_BINDING_KEY, - SKILL_ROLE_BINDING_MANAGER_INHERIT_SETTING, load_skill_role_binding_dict, normalize_skill_role_binding, ordered_binding_roles, @@ -348,7 +347,7 @@ def include_skill_routes( "enabled": bool(skill_role_binding_enabled(store=store)), "enabled_stored": bool(skill_role_binding_enabled_stored(store=store)), "enabled_env_present": bool(skill_role_binding_enabled_env_present()), - "manager_inherit": str(store.get_setting(SKILL_ROLE_BINDING_MANAGER_INHERIT_SETTING) or "").strip() not in {"0", "false", "False"}, + "manager_inherit": False, "available_roles": roles, "installed_skills": items, "mapping": mapping, @@ -365,8 +364,6 @@ def include_skill_routes( _require_tenant_write(ctx) if "enabled" in payload: store.set_setting(SKILL_ROLE_BINDING_ENABLED_SETTING, "1" if bool(payload.get("enabled")) else "0") - if "manager_inherit" in payload: - store.set_setting(SKILL_ROLE_BINDING_MANAGER_INHERIT_SETTING, "1" if bool(payload.get("manager_inherit")) else "0") roles, _prev_mapping, valid = _normalized_skill_binding(store) mapping_raw = payload.get("mapping") if isinstance(payload.get("mapping"), dict) else {} mapping = normalize_skill_role_binding( @@ -388,7 +385,7 @@ def include_skill_routes( "enabled": bool(skill_role_binding_enabled(store=store)), "enabled_stored": bool(skill_role_binding_enabled_stored(store=store)), "enabled_env_present": bool(skill_role_binding_enabled_env_present()), - "manager_inherit": str(store.get_setting(SKILL_ROLE_BINDING_MANAGER_INHERIT_SETTING) or "").strip() not in {"0", "false", "False"}, + "manager_inherit": False, "available_roles": roles, "mapping": mapping, } @@ -401,7 +398,7 @@ def include_skill_routes( roles, mapping, _valid = _normalized_skill_binding(store) role_rows: list[dict[str, Any]] = [] for role in roles: - specialist = "generalist" if role == "manager" else role + specialist = role ex = build_gateway_executor(store=store, specialist=specialist) tools = getattr(ex, "tools", None) model = getattr(ex, "model", None) @@ -432,10 +429,8 @@ def include_skill_routes( base_url=base_url, ) direct_set = set(mapping.get(role) or []) - manager_set = set(mapping.get("manager") or []) workspace_total = 0 workspace_direct = 0 - workspace_inherited = 0 workspace_resolved_tool_match = 0 workspace_docs_only = 0 mcp_total = 0 @@ -456,8 +451,6 @@ def include_skill_routes( docs_only_names.append(str(nm)) if nm in direct_set: workspace_direct += 1 - elif role != "manager" and nm in manager_set: - workspace_inherited += 1 elif str(nm).startswith("mcp__"): mcp_total += 1 else: @@ -468,7 +461,7 @@ def include_skill_routes( "total": len(entries), "workspace_total": workspace_total, "workspace_direct": workspace_direct, - "workspace_inherited_manager": workspace_inherited, + "workspace_inherited_manager": 0, "workspace_resolved_tool_match": workspace_resolved_tool_match, "workspace_docs_only": workspace_docs_only, "mcp_total": mcp_total, diff --git a/interfaces/admin/static/app.js b/interfaces/admin/static/app.js index d19e07d4..8c2ea8b3 100644 --- a/interfaces/admin/static/app.js +++ b/interfaces/admin/static/app.js @@ -1583,7 +1583,7 @@ async function renderStack() { apiGetNoHang("/admin/api/runtime/anomalies"), apiGetNoHang("/admin/api/runtime/scan-artifacts"), apiGetNoHang("/admin/api/runtime/prewarm/status"), - apiGetNoHang("/admin/api/runtime/prewarm/prompts?role=manager"), + apiGetNoHang("/admin/api/runtime/prewarm/prompts?role=generalist"), apiGetNoHang("/admin/api/chat/settings/specialist-flags"), apiGetNoHang("/admin/api/chat/settings/channel-dispatch/weixin"), apiGetNoHang("/admin/api/chat/settings/channel-dispatch/whatsapp"), @@ -2220,7 +2220,7 @@ async function renderStack() { : [el("tr", {}, [el("td", { class: "muted", text: "—", colspan: "5" })])]; const promptsSectionBody = el("div"); const promptsRoleSelect = el("select", { class: "input" }, [ - el("option", { value: "manager", text: "manager (default)" }), + el("option", { value: "generalist", text: "generalist (default)" }), el("option", { value: "", text: currentLang === "zh" ? "全部角色" : "all roles" }), ]); const renderPromptCards = (resp) => { @@ -2327,7 +2327,7 @@ async function renderStack() { ]), el("div", { class: "card" }, [ el("div", { class: "card__title", text: currentLang === "zh" ? "预热后提示词(按专家)" : "Prewarmed Prompts by Role" }), - el("div", { class: "muted", text: currentLang === "zh" ? "展示 manager + 各专家预热后的提示词内容。" : "Shows prewarmed prompt content for manager and specialists." }), + el("div", { class: "muted", text: currentLang === "zh" ? "展示各专家预热后的提示词内容。" : "Shows prewarmed prompt content for specialists." }), el("div", { class: "row" }, [ el("label", { text: currentLang === "zh" ? "角色筛选" : "Role filter" }), promptsRoleSelect, @@ -7794,7 +7794,7 @@ async function renderSkills() { marketDetailPre, ]); - const skillBindingState = { roles: [], names: [], mapping: {}, enabled: false, managerInherit: true }; + const skillBindingState = { roles: [], names: [], mapping: {}, enabled: false }; const skillBindingStatus = el("div", { class: "muted", text: "" }); const skillBindingPersistHint = el("div", { class: "muted", @@ -7807,7 +7807,6 @@ async function renderSkills() { }); const skillEffectiveState = { items: [] }; const skillBindingEnabledCb = el("input", { type: "checkbox" }); - const skillBindingManagerInheritCb = el("input", { type: "checkbox" }); const skillRoleSelect = el("select", { class: "input" }, []); const skillBindingListWrap = el("div"); const skillBindingDashTbody = el("tbody"); @@ -7816,11 +7815,9 @@ async function renderSkills() { skillBindingDashTbody.innerHTML = ""; const roles = Array.isArray(skillBindingState.roles) ? skillBindingState.roles : []; const mapping = skillBindingState.mapping && typeof skillBindingState.mapping === "object" ? skillBindingState.mapping : {}; - const managerBound = new Set(Array.isArray(mapping.manager) ? mapping.manager.map((x) => String(x)) : []); - const inheritManager = !!skillBindingState.managerInherit; roles.forEach((role) => { const direct = new Set(Array.isArray(mapping[role]) ? mapping[role].map((x) => String(x)) : []); - const effective = new Set([...(role === "manager" || !inheritManager ? [] : Array.from(managerBound)), ...Array.from(direct)]); + const effective = new Set(Array.from(direct)); skillBindingDashTbody.appendChild( el("tr", {}, [ el("td", { text: role }), @@ -7860,7 +7857,6 @@ async function renderSkills() { el("td", { text: String(x.total || 0) }), el("td", { text: String(x.workspace_total || 0) }), el("td", { text: String(x.workspace_direct || 0) }), - el("td", { text: String(x.workspace_inherited_manager || 0) }), el("td", { text: String(x.workspace_resolved_tool_match || 0) }), el("td", { text: String(x.workspace_docs_only || 0) }), el("td", { text: String(x.mcp_total || 0) }), @@ -7871,7 +7867,7 @@ async function renderSkills() { ); }); if (!rows.length) { - skillEffectiveTbody.appendChild(el("tr", {}, [el("td", { text: "-", colspan: "11" })])); + skillEffectiveTbody.appendChild(el("tr", {}, [el("td", { text: "-", colspan: "10" })])); } }; const loadSkillEffective = async () => { @@ -7920,9 +7916,7 @@ async function renderSkills() { .filter(Boolean); skillBindingState.mapping = r.mapping && typeof r.mapping === "object" ? { ...r.mapping } : {}; skillBindingState.enabled = !!r.enabled; - skillBindingState.managerInherit = Object.prototype.hasOwnProperty.call(r, "manager_inherit") ? !!r.manager_inherit : true; skillBindingEnabledCb.checked = skillBindingState.enabled; - skillBindingManagerInheritCb.checked = skillBindingState.managerInherit; if (r.enabled_env_present) { skillBindingEnvHint.style.display = "block"; const stored = !!r.enabled_stored; @@ -7959,14 +7953,11 @@ async function renderSkills() { try { const r = await apiPost("/admin/api/skills/binding", { enabled: !!skillBindingEnabledCb.checked, - manager_inherit: !!skillBindingManagerInheritCb.checked, mapping: skillBindingState.mapping, }); skillBindingState.mapping = r.mapping && typeof r.mapping === "object" ? { ...r.mapping } : {}; skillBindingState.enabled = !!r.enabled; - skillBindingState.managerInherit = Object.prototype.hasOwnProperty.call(r, "manager_inherit") ? !!r.manager_inherit : true; skillBindingEnabledCb.checked = skillBindingState.enabled; - skillBindingManagerInheritCb.checked = skillBindingState.managerInherit; if (r.enabled_env_present) { skillBindingEnvHint.style.display = "block"; const stored = !!r.enabled_stored; @@ -7979,7 +7970,7 @@ async function renderSkills() { skillBindingEnvHint.style.display = "none"; skillBindingEnvHint.textContent = ""; } - skillBindingStatus.textContent = `saved: enabled=${String(r.enabled)} manager_inherit=${String(skillBindingState.managerInherit)}`; + skillBindingStatus.textContent = `saved: enabled=${String(r.enabled)}`; renderSkillBindingList(); renderSkillBindingDashboard(); await loadSkillEffective(); @@ -7989,12 +7980,12 @@ async function renderSkills() { }, }); const skillBindingBox = el("details", { style: "margin:10px 0 14px 0;" }, [ - el("summary", { text: "Skill role binding (manager + specialists)", style: "cursor:pointer;user-select:none;" }), + el("summary", { text: "Skill role binding (specialists)", style: "cursor:pointer;user-select:none;" }), el("div", { class: "muted", style: "margin:8px 0;line-height:1.5;", text: - "When enabled, the model skills catalog is filtered per role: each role sees only skills bound to that role (and optionally manager-bound skills), plus skills under skills/_workspace/public/. If nothing is bound yet, non-public skills are hidden until you assign them.", + "When enabled, the model skills catalog is filtered per role: each role sees only skills bound to that role, plus skills under skills/_workspace/public/. If nothing is bound yet, non-public skills are hidden until you assign them.", }), skillBindingStatus, skillBindingPersistHint, @@ -8003,10 +7994,6 @@ async function renderSkills() { skillBindingEnabledCb, el("span", { text: "Enable role binding (AIA_SKILL_ROLE_BINDING_ENABLED)" }), ]), - el("label", { class: "row", style: "gap:8px;align-items:center;margin-top:6px;" }, [ - skillBindingManagerInheritCb, - el("span", { text: "Inherit manager skills to other roles (AIA_SKILL_ROLE_BINDING_MANAGER_INHERIT)" }), - ]), el("div", { class: "row", style: "gap:8px;flex-wrap:wrap;margin-top:8px;align-items:center;" }, [ el("label", { text: "Role" }), skillRoleSelect, @@ -8019,7 +8006,7 @@ async function renderSkills() { skillBindingDashTbody, ]), ]), - el("div", { class: "muted", style: "margin:8px 0 4px 0;", text: "Effective skills dashboard (binding + inherited + MCP + tools)" }), + el("div", { class: "muted", style: "margin:8px 0 4px 0;", text: "Effective skills dashboard (binding + MCP + tools)" }), el("div", { class: "table-wrap" }, [ el("table", { class: "table table--compact" }, [ el("thead", {}, [el("tr", {}, [ @@ -8027,7 +8014,6 @@ async function renderSkills() { el("th", { text: "total effective" }), el("th", { text: "workspace" }), el("th", { text: "direct bind" }), - el("th", { text: "inherited manager" }), el("th", { text: "workspace resolved" }), el("th", { text: "workspace docs-only" }), el("th", { text: "mcp converted" }), @@ -8165,9 +8151,8 @@ async function renderSkills() { const rolesTotal = Number(body.roles_total || 0); const totalInternal = Number(summary.total_internal_tools || 0); const totalWired = Number(summary.total_wired_tools || 0); - const totalPermBan = Number(summary.total_perm_ban_9999 || 0); const items = Array.isArray(body.items) ? body.items : []; - selfCheckSummary.textContent = `roles=${rolesTotal} internal=${totalInternal} wired=${totalWired} permBan9999=${totalPermBan}`; + selfCheckSummary.textContent = `roles=${rolesTotal} internal=${totalInternal} wired=${totalWired}`; selfCheckTbody.innerHTML = ""; items.forEach((x) => { const removedMcp = Number(x.removed_mcp_total || 0); @@ -8194,7 +8179,6 @@ async function renderSkills() { el("td", { text: String(x.raw_count || 0) }), el("td", { text: String(x.wired_count || 0) }), el("td", { text: String(x.removed_mcp_total || 0) }), - el("td", { text: String(x.policy_perm_ban_9999 || 0) }), ]), ); }); @@ -8252,7 +8236,6 @@ async function renderSkills() { el("th", { text: "raw" }), el("th", { text: "wired" }), el("th", { text: "removed mcp" }), - el("th", { text: "perm ban(9999)" }), ])]), selfCheckTbody, ]), @@ -8888,7 +8871,7 @@ async function renderSkills() { await loadAudits(); await loadSkillBinding(); await loadSkillMode(); - const rolesForPreview = (Array.isArray(skillBindingState.roles) && skillBindingState.roles.length ? skillBindingState.roles : ["generalist", "ops", "image", "manager"]).map((x) => String(x)); + const rolesForPreview = (Array.isArray(skillBindingState.roles) && skillBindingState.roles.length ? skillBindingState.roles : ["generalist", "ops", "memory"]).map((x) => String(x)); internalRoleSelect.innerHTML = ""; rolesForPreview.forEach((role) => internalRoleSelect.appendChild(el("option", { value: role, text: role }))); internalRoleSelect.value = rolesForPreview.includes("generalist") ? "generalist" : rolesForPreview[0]; diff --git a/runtime/agent_context/loader.py b/runtime/agent_context/loader.py index d7d092a2..54c31605 100644 --- a/runtime/agent_context/loader.py +++ b/runtime/agent_context/loader.py @@ -24,8 +24,8 @@ def _read_text(path: Path) -> str: def _workspace_for_role(role: str) -> str: r = str(role or "").strip().lower() - if r == "manager": - return "main" + if r in {"manager", "manager_self", "main"}: + return "generalist" return r or "generalist" diff --git a/runtime/agents/factory.py b/runtime/agents/factory.py index 8c6c353e..c1dae699 100644 --- a/runtime/agents/factory.py +++ b/runtime/agents/factory.py @@ -11,7 +11,6 @@ from runtime.agents.specialists import ( SpecialistProfile, normalize_specialist_id, agent_role_ids, - MANAGER_AGENT_ID, specialist_ids, default_system_prefix_for_specialist, default_tool_tags_for_specialist, @@ -196,7 +195,7 @@ def _build_executor_components( return active_model, active_mode return _build_chat_model_for_profile(bound_pid, allow_runtime_overrides=False) - manager_model, manager_mode = _pick_model_for_role(MANAGER_AGENT_ID) + manager_model, manager_mode = _pick_model_for_role("generalist") specialist_models: dict[str, object] = {} specialist_modes: dict[str, str] = {} for sid in specialist_ids(): diff --git a/runtime/agents/specialists.py b/runtime/agents/specialists.py index d9d39d8f..c1314488 100644 --- a/runtime/agents/specialists.py +++ b/runtime/agents/specialists.py @@ -69,7 +69,7 @@ def specialist_ids() -> tuple[SpecialistId, ...]: def agent_role_ids() -> tuple[AgentRoleId, ...]: - return (MANAGER_AGENT_ID, *specialist_ids()) + return specialist_ids() def expert_name_for_specialist(specialist_id: SpecialistId) -> str: @@ -101,6 +101,8 @@ def model_role_for_specialist(specialist_id: SpecialistId) -> AgentRoleId: def normalize_specialist_id(specialist_id: SpecialistId | None) -> SpecialistId: sid = (specialist_id or "").strip().lower() + if sid in {"manager", "manager_self", "main", "comprehensive"}: + return "generalist" if sid in _REMOVED_SPECIALIST_IDS: return "generalist" if sid in SPECIALISTS: @@ -125,6 +127,11 @@ def parse_agent_profile_bindings(raw: str | None) -> dict[AgentRoleId, str]: return out if not isinstance(obj, dict): return out + # Fold legacy manager profile into generalist when generalist empty. + mgr = str(obj.get("manager") or "").strip() + if mgr and not str(obj.get("generalist") or "").strip(): + obj = dict(obj) + obj["generalist"] = mgr for rid in agent_role_ids(): v = obj.get(rid) if v is None: diff --git a/runtime/direct_loop.py b/runtime/direct_loop.py index 488d7f5a..b1ea9697 100644 --- a/runtime/direct_loop.py +++ b/runtime/direct_loop.py @@ -171,7 +171,6 @@ def _tool_wire_settings_signature(store: Any) -> tuple[bool, str]: f"skill_rt={str(store.get_setting('AIA_SKILL_RUNTIME_ENABLED') or '')}", f"skill_disabled={str(store.get_setting('AIA_SKILL_DISABLED_NAMES') or '')}", f"bind_en={str(store.get_setting('AIA_SKILL_ROLE_BINDING_ENABLED') or '')}", - f"bind_inherit={str(store.get_setting('AIA_SKILL_ROLE_BINDING_MANAGER_INHERIT') or '')}", ] ) return runtime_enabled, sig diff --git a/runtime/gateway.py b/runtime/gateway.py index dbd9b232..da1c8ce6 100644 --- a/runtime/gateway.py +++ b/runtime/gateway.py @@ -519,8 +519,7 @@ class OclawGateway: cache_key = ( f"base={base_url}|skill_rt={str(self.store.get_setting('AIA_SKILL_RUNTIME_ENABLED') or '')}|" f"skill_disabled={str(self.store.get_setting('AIA_SKILL_DISABLED_NAMES') or '')}|" - f"bind_en={str(self.store.get_setting('AIA_SKILL_ROLE_BINDING_ENABLED') or '')}|" - f"bind_inherit={str(self.store.get_setting('AIA_SKILL_ROLE_BINDING_MANAGER_INHERIT') or '')}" + f"bind_en={str(self.store.get_setting('AIA_SKILL_ROLE_BINDING_ENABLED') or '')}" ) now = time.time() with _SKILL_MANIFEST_CACHE_LOCK: diff --git a/runtime/prompt_prebuild.py b/runtime/prompt_prebuild.py index 2f69931d..28f2ded4 100644 --- a/runtime/prompt_prebuild.py +++ b/runtime/prompt_prebuild.py @@ -8,14 +8,9 @@ from svc.persistence.assistant_store import get_assistant_store from runtime.agent_context import build_role_system_context from runtime.agents.specialists import discover_specialist_ids from runtime.direct_loop import tool_wire_freeze_status, warm_tool_wire_cache -from runtime.skill_role_binding import SKILL_ROLE_BINDING_KEY -from runtime.skills import workspace_skills_layout_signature from runtime.system_prompt import get_executor_prompt_static, warm_executor_prompt_cache from runtime.tools.catalog import default_registry -from runtime.workspaces.experts import expert_workspace_signature_token, list_experts -_MANAGER_PREBUILD_CACHE_LOCK = threading.Lock() -_MANAGER_PREBUILD_CACHE: dict[tuple[Any, ...], dict[str, Any]] = {} _RUNTIME_PREWARM_LOCK = threading.Lock() _RUNTIME_PREWARM_RUNNING = False _RUNTIME_PREWARM_LAST: dict[str, Any] = { @@ -31,47 +26,6 @@ _RUNTIME_PREWARM_HISTORY: list[dict[str, Any]] = [] _RUNTIME_PREWARM_HISTORY_LIMIT = 40 -def _manager_settings_signature(store: Any) -> tuple[str, ...]: - keys = ( - "AIA_SKILL_RUNTIME_ENABLED", - "AIA_SKILL_DISABLED_NAMES", - "AIA_SKILL_ROLE_BINDING_ENABLED", - "AIA_SKILL_ROLE_BINDING_MANAGER_INHERIT", - SKILL_ROLE_BINDING_KEY, - "AIA_CHAT_SPECIALIST_FLAGS_JSON", - ) - parts: list[str] = [] - for key in keys: - try: - val = str(store.get_setting(key) or "") - except Exception: - val = "" - parts.append(f"{key}={val}") - return tuple(parts) - - -def _compact_line(text: str, *, limit: int = 80) -> str: - s = " ".join(str(text or "").strip().split()) - if len(s) <= limit: - return s - return s[: max(0, limit - 1)] + "…" - - -def _build_expert_desc_map() -> dict[str, str]: - out: dict[str, str] = {} - for row in list_experts(): - eid = str(row.get("id") or "").strip().lower() - if not eid: - continue - files = row.get("files") if isinstance(row, dict) else {} - if not isinstance(files, dict): - continue - desc = _compact_line(str(files.get("ROLE_SYSTEM.md") or ""), limit=80) - if desc: - out[eid] = desc - return out - - def get_manager_prompt_prebuild( *, store: Any, @@ -79,18 +33,8 @@ def get_manager_prompt_prebuild( base_url: str, memory_enabled: bool, ) -> dict[str, Any]: - cache_key = ( - str(base_url or "").strip(), - bool(memory_enabled), - expert_workspace_signature_token(), - _manager_settings_signature(store), - workspace_skills_layout_signature(), - ) - with _MANAGER_PREBUILD_CACHE_LOCK: - cached = _MANAGER_PREBUILD_CACHE.get(cache_key) - if isinstance(cached, dict): - return dict(cached) - + """Legacy stub: Manager prompt packing removed; return specialist catalog only.""" + del store, registry, base_url allowed_fixed = [str(x).strip().lower() for x in discover_specialist_ids() if str(x).strip()] if not allowed_fixed: allowed_fixed = ["generalist"] @@ -99,38 +43,17 @@ def get_manager_prompt_prebuild( if "generalist" not in allowed_fixed: allowed_fixed.insert(0, "generalist") allowed_fixed_quoted = ", ".join([f'"{x}"' for x in allowed_fixed]) - - desc_by_id = _build_expert_desc_map() - candidate_lines = [f"- {sid}: {desc_by_id.get(sid) or 'no description'}" for sid in allowed_fixed] - dynamic_hint = ( - f"\n{chr(10).join(candidate_lines)}\n" - "不要假设固定专家集合。" - ) - manager_context = build_role_system_context( - "manager", - template_vars={"MANAGER_DYNAMIC_EXPERTS_HINT": dynamic_hint}, - ) - out = { - "manager_context": manager_context, + return { + "manager_context": "", "allowed_fixed": tuple(allowed_fixed), "allowed_fixed_quoted": allowed_fixed_quoted, } - with _MANAGER_PREBUILD_CACHE_LOCK: - _MANAGER_PREBUILD_CACHE[cache_key] = dict(out) - if len(_MANAGER_PREBUILD_CACHE) > 64: - _MANAGER_PREBUILD_CACHE.clear() - return out def warm_startup_prompt_prebuild(*, store: Any, registry: Any, base_url: str, memory_enabled: bool) -> dict[str, Any]: + del memory_enabled t0 = time.perf_counter() - manager_pack = get_manager_prompt_prebuild( - store=store, - registry=registry, - base_url=base_url, - memory_enabled=memory_enabled, - ) - role_systems: dict[str, str] = {"manager": str(manager_pack.get("manager_context") or "")} + role_systems: dict[str, str] = {} for sid in discover_specialist_ids(): role_systems[str(sid)] = build_role_system_context(str(sid)) role_warm = warm_executor_prompt_cache( @@ -144,7 +67,7 @@ def warm_startup_prompt_prebuild(*, store: Any, registry: Any, base_url: str, me return { "ok": True, "elapsed_ms": elapsed_ms, - "manager_candidates": int(len(manager_pack.get("allowed_fixed") or [])), + "manager_candidates": 0, "roles_warmed": int(role_warm.get("roles_warmed") or 0), } @@ -187,7 +110,7 @@ def run_runtime_prewarm( base_url=base_url, memory_enabled=bool(memory_enabled), ) - roles = ["manager", *list(discover_specialist_ids())] + roles = list(discover_specialist_ids()) tool_stats = warm_tool_wire_cache( store=own_store, tools=registry, @@ -245,24 +168,16 @@ def runtime_prewarm_prompts_snapshot( own_store = store if store is not None else get_assistant_store() registry = default_registry(store=own_store) target = str(role or "").strip().lower() - allowed_roles = ["manager", *list(discover_specialist_ids())] + if target in {"manager", "manager_self", "main"}: + target = "generalist" + allowed_roles = list(discover_specialist_ids()) if target and target not in allowed_roles: return {"ok": False, "error": "invalid_role", "allowed_roles": allowed_roles} selected_roles = [target] if target else allowed_roles - manager_pack = get_manager_prompt_prebuild( - store=own_store, - registry=registry, - base_url=base_url, - memory_enabled=memory_enabled, - ) prompts: dict[str, dict[str, Any]] = {} for rid in selected_roles: - base_system = ( - str(manager_pack.get("manager_context") or "") - if rid == "manager" - else build_role_system_context(str(rid)) - ) + base_system = build_role_system_context(str(rid)) executor_system = get_executor_prompt_static( store=own_store, tools=registry, diff --git a/runtime/skill_role_binding.py b/runtime/skill_role_binding.py index 879a9b79..98680080 100644 --- a/runtime/skill_role_binding.py +++ b/runtime/skill_role_binding.py @@ -8,6 +8,7 @@ from runtime.agents.specialists import discover_specialist_ids SKILL_ROLE_BINDING_KEY = "skill_role_binding" SKILL_ROLE_BINDING_ENABLED_SETTING = "AIA_SKILL_ROLE_BINDING_ENABLED" +# Deprecated: Manager inherit removed; kept so old settings keys do not crash readers. SKILL_ROLE_BINDING_MANAGER_INHERIT_SETTING = "AIA_SKILL_ROLE_BINDING_MANAGER_INHERIT" @@ -22,7 +23,8 @@ def ordered_specialist_ids() -> list[str]: def ordered_binding_roles() -> list[str]: - return ["manager", *ordered_specialist_ids()] + """Roles that appear in skill-binding UI/maps (specialists only; no manager).""" + return ordered_specialist_ids() def skill_role_binding_enabled_env_present() -> bool: @@ -80,24 +82,33 @@ def normalize_skill_role_binding( roles = available_roles if available_roles is not None else ordered_binding_roles() role_set = set(roles) out: dict[str, list[str]] = {r: [] for r in roles} - for k, v in (mapping_raw or {}).items(): - rk = str(k or "").strip().lower() + + def _append(rk: str, items: Any) -> None: if rk not in role_set: - continue - items = v if isinstance(v, list) else [] - seen: set[str] = set() - for x in items: + return + rows = items if isinstance(items, list) else [] + seen = set(out[rk]) + for x in rows: nm = str(x or "").strip() if not nm or nm not in valid_skill_names or nm in seen: continue seen.add(nm) out[rk].append(nm) + + for k, v in (mapping_raw or {}).items(): + rk = str(k or "").strip().lower() + # Legacy Manager bindings fold into generalist. + if rk == "manager": + rk = "generalist" + _append(rk, v) return out def allowed_workspace_skill_names_for_role(*, store: Any, role: str) -> set[str]: - """Union of manager-bound skills and skills bound to the given specialist role.""" + """Skills bound to the given specialist role, plus public workspace skills.""" r = str(role or "").strip().lower() + if r == "manager": + r = "generalist" if not r: return set() from runtime.skills import discover_public_workspace_skill_names @@ -107,18 +118,8 @@ def allowed_workspace_skill_names_for_role(*, store: Any, role: str) -> set[str] mapping_raw=load_skill_role_binding_dict(store), valid_skill_names=_all_installed_skill_names(store), ) - try: - raw_env = str(os.getenv(SKILL_ROLE_BINDING_MANAGER_INHERIT_SETTING) or "").strip() - if raw_env: - inherit_mgr = _truthy(raw_env) - else: - raw = str(store.get_setting(SKILL_ROLE_BINDING_MANAGER_INHERIT_SETTING) or "").strip() - inherit_mgr = _truthy(raw) if raw else True - except Exception: - inherit_mgr = True - mgr = {str(x).strip() for x in (mapping.get("manager") or []) if str(x).strip()} if inherit_mgr else set() sp = {str(x).strip() for x in (mapping.get(r) or []) if str(x).strip()} - return mgr | sp | public + return sp | public def _all_installed_skill_names(store: Any) -> set[str]: diff --git a/runtime/system_prompt.py b/runtime/system_prompt.py index 47fdbe5c..cff2e7f8 100644 --- a/runtime/system_prompt.py +++ b/runtime/system_prompt.py @@ -85,7 +85,6 @@ def _executor_prompt_settings_signature(store: Any) -> tuple[str, ...]: "AIA_SKILLS_PROMPT_IN_SYSTEM", "AIA_SKILL_DISABLED_NAMES", "AIA_SKILL_ROLE_BINDING_ENABLED", - "AIA_SKILL_ROLE_BINDING_MANAGER_INHERIT", SKILL_ROLE_BINDING_KEY, "AIA_PROJECT_CONTEXT_MAX_FILE_CHARS", "AIA_PROJECT_CONTEXT_MAX_TOTAL_CHARS", diff --git a/runtime/tools/mcp/adapter.py b/runtime/tools/mcp/adapter.py index 3c89561c..8d547310 100644 --- a/runtime/tools/mcp/adapter.py +++ b/runtime/tools/mcp/adapter.py @@ -230,10 +230,8 @@ def materialize_mcp_tools_for_specialist( return "mcp-remote" in joined and "/api/v1/mcps/webparser/sse" in joined sp = str(specialist or "").strip().lower() - if sp == "manager": - # Manager is a first-class binding role in admin UI/config. - # We keep it separate from generalist instead of aliasing. - sp = "manager" + if sp in {"manager", "manager_self", "main"}: + sp = "generalist" # Preferred mapping: specialist -> server_ids binding_server_ids: set[str] | None = None try: @@ -243,6 +241,9 @@ def materialize_mcp_tools_for_specialist( obj = json.loads(raw_binding) if isinstance(obj, dict): rows = obj.get(sp) + # Legacy: if this specialist has no key, try manager bindings for generalist. + if rows is None and sp == "generalist" and "manager" in obj: + rows = obj.get("manager") # 缺键或 null:视为未配置该专家的绑定 → 走下方「仅 coarse allowlist」逻辑(可见全部已启用 MCP)。 # 仅当键存在且为 JSON 数组时,才按白名单过滤(含空数组 = 刻意不给该专家任何 MCP)。 if rows is None: @@ -262,8 +263,12 @@ def materialize_mcp_tools_for_specialist( except Exception: raw_allowed = "" if not raw_allowed: - raw_allowed = str(os.getenv("AIA_MCP_SPECIALISTS") or "generalist,manager,ops").strip() + raw_allowed = str(os.getenv("AIA_MCP_SPECIALISTS") or "generalist,ops").strip() allowed = {x.strip().lower() for x in raw_allowed.split(",") if x.strip()} + allowed.discard("manager") + allowed.discard("manager_self") + if "generalist" not in allowed and "ops" in allowed: + pass if binding_server_ids is None and sp and sp not in allowed: return [] out: list[ToolSpec] = [] diff --git a/tests/test_admin_skills_api.py b/tests/test_admin_skills_api.py index 5249b624..4a9c1988 100644 --- a/tests/test_admin_skills_api.py +++ b/tests/test_admin_skills_api.py @@ -118,12 +118,12 @@ class AdminSkillsApiTests(unittest.TestCase): self.assertIn("available_roles", body) self.assertIn("mapping", body) roles = list(body.get("available_roles") or []) - self.assertIn("manager", roles) + self.assertNotIn("manager", roles) + self.assertIn("generalist", roles) mapping = dict(body.get("mapping") or {}) for r in roles: mapping.setdefault(r, []) mapping["generalist"] = ["bind_demo_skill"] - mapping["manager"] = ["bind_demo_skill"] s = self.client.post( "/admin/api/skills/binding", json={"enabled": True, "mapping": mapping}, @@ -167,10 +167,8 @@ class AdminSkillsApiTests(unittest.TestCase): json={ "enabled": True, "mapping": { - "manager": ["effective_demo_skill"], - "generalist": [], + "generalist": ["effective_demo_skill"], "ops": [], - "image": [], "memory": [], }, }, @@ -182,12 +180,12 @@ class AdminSkillsApiTests(unittest.TestCase): body = r.json() or {} self.assertTrue(body.get("ok")) items = list(body.get("items") or []) - self.assertTrue(any(str(x.get("role") or "") == "manager" for x in items)) - mgr = next((x for x in items if str(x.get("role") or "") == "manager"), {}) - self.assertGreaterEqual(int(mgr.get("workspace_total") or 0), 1) - self.assertGreaterEqual(int(mgr.get("total") or 0), int(mgr.get("workspace_total") or 0)) - self.assertIn("workspace_docs_only", mgr) - self.assertIn("workspace_resolved_tool_match", mgr) + self.assertFalse(any(str(x.get("role") or "") == "manager" for x in items)) + gen = next((x for x in items if str(x.get("role") or "") == "generalist"), {}) + self.assertGreaterEqual(int(gen.get("workspace_total") or 0), 1) + self.assertGreaterEqual(int(gen.get("total") or 0), int(gen.get("workspace_total") or 0)) + self.assertIn("workspace_docs_only", gen) + self.assertIn("workspace_resolved_tool_match", gen) def test_skills_retry_install_registry(self) -> None: pkg = Path(self._tmp.name) / "pkg2" diff --git a/tests/test_agent_context_loader.py b/tests/test_agent_context_loader.py index 31eed568..776333c7 100644 --- a/tests/test_agent_context_loader.py +++ b/tests/test_agent_context_loader.py @@ -13,15 +13,16 @@ def _set_project_root(monkeypatch: pytest.MonkeyPatch, tmp_path: Path) -> None: def test_build_role_system_context_reads_runtime_workspaces(monkeypatch: pytest.MonkeyPatch, tmp_path: Path) -> None: _set_project_root(monkeypatch, tmp_path) - ws = tmp_path / "runtime" / "workspaces" / "main" + ws = tmp_path / "runtime" / "workspaces" / "generalist" ws.mkdir(parents=True, exist_ok=True) - (ws / "SOUL.md").write_text("main soul", encoding="utf-8") - (ws / "ROLE_SYSTEM.md").write_text("main role system", encoding="utf-8") + (ws / "SOUL.md").write_text("generalist soul", encoding="utf-8") + (ws / "ROLE_SYSTEM.md").write_text("generalist role system", encoding="utf-8") + # Legacy manager/main aliases resolve to the generalist workspace. out = loader_mod.build_role_system_context("manager") assert "# SOUL" in out - assert "main soul" in out + assert "generalist soul" in out assert "# ROLE_SYSTEM" in out - assert "main role system" in out + assert "generalist role system" in out def test_build_role_system_context_cache_invalidates_on_file_change( diff --git a/tests/test_mcp_adapter.py b/tests/test_mcp_adapter.py index 65bf44a7..35da69b9 100644 --- a/tests/test_mcp_adapter.py +++ b/tests/test_mcp_adapter.py @@ -114,14 +114,22 @@ class McpAdapterTests(unittest.TestCase): server_id="echo-b", tools=[{"tool_name": "ping_b", "description": "Ping B", "parameters": {"type": "object", "properties": {}}}], ) + # Legacy "manager" alias resolves to generalist; when generalist has its own + # binding, that wins (manager key is not merged). store.set_setting("mcp_specialist_server_binding", '{"manager":["echo-a"],"generalist":["echo-b"]}') m_specs = materialize_mcp_tools_for_specialist(store, specialist="manager") g_specs = materialize_mcp_tools_for_specialist(store, specialist="generalist") m_names = {x.name for x in m_specs} g_names = {x.name for x in g_specs} - self.assertIn("mcp__echo-a__ping_a", m_names) - self.assertNotIn("mcp__echo-b__ping_b", m_names) + self.assertIn("mcp__echo-b__ping_b", m_names) + self.assertNotIn("mcp__echo-a__ping_a", m_names) self.assertIn("mcp__echo-b__ping_b", g_names) + # When only manager key exists, generalist falls back to that binding. + store.set_setting("mcp_specialist_server_binding", '{"manager":["echo-a"]}') + g2 = materialize_mcp_tools_for_specialist(store, specialist="generalist") + g2_names = {x.name for x in g2} + self.assertIn("mcp__echo-a__ping_a", g2_names) + self.assertNotIn("mcp__echo-b__ping_b", g2_names) def test_binding_empty_json_object_falls_back_to_all_mcp_for_specialist(self) -> None: """{} 不应把每个专家都当成「已绑定但列表为空」而屏蔽全部 MCP。""" diff --git a/tests/test_prompt_prebuild.py b/tests/test_prompt_prebuild.py index 9ed1d48c..6d1f24ce 100644 --- a/tests/test_prompt_prebuild.py +++ b/tests/test_prompt_prebuild.py @@ -13,22 +13,8 @@ class _DummyStore: return str(self._settings.get(key, "")) -def test_get_manager_prompt_prebuild_includes_structured_skills(monkeypatch) -> None: +def test_get_manager_prompt_prebuild_is_legacy_stub(monkeypatch) -> None: monkeypatch.setattr(pp, "discover_specialist_ids", lambda: ("generalist", "ops", "memory", "image")) - monkeypatch.setattr( - pp, - "list_experts", - lambda: [ - {"id": "generalist", "files": {"ROLE_SYSTEM.md": "General specialist for broad tasks."}}, - {"id": "ops", "files": {"ROLE_SYSTEM.md": "Ops specialist for runtime and services."}}, - ], - ) - monkeypatch.setattr(pp, "expert_workspace_signature_token", lambda: ("sig",)) - - def _ctx(_role: str, template_vars: dict[str, Any] | None = None) -> str: - return f"CTX\n{str((template_vars or {}).get('MANAGER_DYNAMIC_EXPERTS_HINT') or '')}" - - monkeypatch.setattr(pp, "build_role_system_context", _ctx) out = pp.get_manager_prompt_prebuild( store=_DummyStore(), @@ -37,20 +23,11 @@ def test_get_manager_prompt_prebuild_includes_structured_skills(monkeypatch) -> memory_enabled=True, ) assert "generalist" in str(out.get("allowed_fixed") or "") - assert "- generalist:" in str(out.get("manager_context") or "") - assert "General specialist for broad tasks." in str(out.get("manager_context") or "") + assert "ops" in str(out.get("allowed_fixed") or "") + assert str(out.get("manager_context") or "") == "" -def test_warm_startup_prompt_prebuild_warms_all_roles(monkeypatch) -> None: - monkeypatch.setattr( - pp, - "get_manager_prompt_prebuild", - lambda **_k: { - "manager_context": "manager_ctx", - "allowed_fixed": ("generalist", "ops"), - "allowed_fixed_quoted": '"generalist", "ops"', - }, - ) +def test_warm_startup_prompt_prebuild_warms_specialists_only(monkeypatch) -> None: monkeypatch.setattr(pp, "discover_specialist_ids", lambda: ("generalist", "ops")) monkeypatch.setattr(pp, "build_role_system_context", lambda role, template_vars=None: f"{role}_ctx") @@ -71,20 +48,13 @@ def test_warm_startup_prompt_prebuild_warms_all_roles(monkeypatch) -> None: assert out["ok"] is True role_map = captured.get("role_base_systems") if isinstance(captured, dict) else {} assert isinstance(role_map, dict) - assert "manager" in role_map + assert "manager" not in role_map assert "generalist" in role_map assert "ops" in role_map def test_runtime_prewarm_prompts_snapshot_returns_roles(monkeypatch) -> None: monkeypatch.setattr(pp, "discover_specialist_ids", lambda: ("generalist", "ops")) - monkeypatch.setattr( - pp, - "get_manager_prompt_prebuild", - lambda **_k: { - "manager_context": "manager_ctx", - }, - ) monkeypatch.setattr(pp, "build_role_system_context", lambda role, template_vars=None: f"{role}_ctx") monkeypatch.setattr(pp, "get_executor_prompt_static", lambda **kwargs: f"exec::{kwargs.get('skill_binding_role')}") monkeypatch.setattr(pp, "default_registry", lambda **kwargs: object()) @@ -92,54 +62,32 @@ def test_runtime_prewarm_prompts_snapshot_returns_roles(monkeypatch) -> None: out = pp.runtime_prewarm_prompts_snapshot(store=_DummyStore()) assert out["ok"] is True prompts = out.get("prompts") or {} - assert "manager" in prompts + assert "manager" not in prompts assert "generalist" in prompts assert "ops" in prompts - assert prompts["manager"].get("system_prompt") == "exec::manager" assert prompts["generalist"].get("system_prompt") == "exec::generalist" assert prompts["ops"].get("system_prompt") == "exec::ops" - assert "manager_system_prompt" not in prompts["manager"] - assert "executor_system_prompt" not in prompts["manager"] - assert "manager_user_scaffold" not in prompts["manager"] + + aliased = pp.runtime_prewarm_prompts_snapshot(store=_DummyStore(), role="manager") + assert aliased["ok"] is True + assert aliased.get("roles") == ["generalist"] + assert "generalist" in (aliased.get("prompts") or {}) -def test_manager_prompt_prebuild_cache_invalidates_on_workspace_revision_change(monkeypatch) -> None: - token = {"v": 1} - calls = {"ctx": 0} - - monkeypatch.setattr(pp, "discover_specialist_ids", lambda: ("generalist", "ops")) - monkeypatch.setattr( - pp, - "list_experts", - lambda: [{"id": "generalist", "files": {"ROLE_SYSTEM.md": "General specialist"}}], - ) - monkeypatch.setattr(pp, "expert_workspace_signature_token", lambda: ("revision", token["v"])) - - def _ctx(_role: str, template_vars: dict[str, Any] | None = None) -> str: - calls["ctx"] += 1 - return f"CTX\n{str((template_vars or {}).get('MANAGER_DYNAMIC_EXPERTS_HINT') or '')}" - - monkeypatch.setattr(pp, "build_role_system_context", _ctx) - - _ = pp.get_manager_prompt_prebuild( +def test_manager_prompt_prebuild_stub_is_stateless(monkeypatch) -> None: + monkeypatch.setattr(pp, "discover_specialist_ids", lambda: ("generalist", "ops", "memory")) + a = pp.get_manager_prompt_prebuild( store=_DummyStore(), registry=object(), base_url="", memory_enabled=True, ) - _ = pp.get_manager_prompt_prebuild( + b = pp.get_manager_prompt_prebuild( store=_DummyStore(), registry=object(), base_url="", - memory_enabled=True, + memory_enabled=False, ) - assert calls["ctx"] == 1 - - token["v"] = 2 - _ = pp.get_manager_prompt_prebuild( - store=_DummyStore(), - registry=object(), - base_url="", - memory_enabled=True, - ) - assert calls["ctx"] == 2 + assert a.get("manager_context") == "" + assert "memory" not in (b.get("allowed_fixed") or ()) + assert "memory" in (a.get("allowed_fixed") or ()) diff --git a/tests/test_skill_role_binding_catalog.py b/tests/test_skill_role_binding_catalog.py index a42f271c..d09e08e4 100644 --- a/tests/test_skill_role_binding_catalog.py +++ b/tests/test_skill_role_binding_catalog.py @@ -40,6 +40,7 @@ def test_collect_respects_role_binding_union(tmp_path: Path, monkeypatch) -> Non store.set_setting(SKILL_ROLE_BINDING_ENABLED_SETTING, "1") mapping = {r: [] for r in ordered_binding_roles()} mapping["generalist"] = ["skill-alpha"] + # Legacy manager bindings fold into generalist. mapping["manager"] = ["skill-beta"] store.set_setting(SKILL_ROLE_BINDING_KEY, json.dumps(mapping)) @@ -141,14 +142,15 @@ def test_collect_unfiltered_when_binding_disabled(tmp_path: Path, monkeypatch) - def test_normalize_drops_unknown_skills(tmp_path: Path) -> None: - roles = ["manager", "generalist"] + roles = ["generalist", "ops"] out = normalize_skill_role_binding( - mapping_raw={"manager": ["nope", "skill-x"], "generalist": ["skill-x"]}, + mapping_raw={"manager": ["nope", "skill-x"], "generalist": ["skill-x"], "ops": ["nope"]}, valid_skill_names={"skill-x"}, available_roles=roles, ) - assert out["manager"] == ["skill-x"] + assert "manager" not in out assert out["generalist"] == ["skill-x"] + assert out["ops"] == [] def test_skill_role_binding_env_overrides_store_value(tmp_path: Path, monkeypatch) -> None: diff --git a/tests/test_specialists_registry.py b/tests/test_specialists_registry.py index df0277f7..04ac4693 100644 --- a/tests/test_specialists_registry.py +++ b/tests/test_specialists_registry.py @@ -11,7 +11,8 @@ def test_unknown_dynamic_specialist_defaults_to_minimum_expert_permissions(monke def test_agent_role_ids_uses_runtime_discovery(monkeypatch) -> None: monkeypatch.setattr(specialists_mod, "discover_specialist_ids", lambda: ("generalist", "ops", "qa")) got = specialists_mod.agent_role_ids() - assert got[0] == specialists_mod.MANAGER_AGENT_ID + assert got[0] == "generalist" + assert "manager" not in set(got) assert "qa" in set(got) diff --git a/tests/test_tool_wire_freeze_switch.py b/tests/test_tool_wire_freeze_switch.py index 43b67603..4bd99e50 100644 --- a/tests/test_tool_wire_freeze_switch.py +++ b/tests/test_tool_wire_freeze_switch.py @@ -15,7 +15,7 @@ def test_tool_wire_freeze_default_on(monkeypatch) -> None: monkeypatch.setattr(dl, "_prepare_llm_tools", lambda **kwargs: []) monkeypatch.setenv("AIA_TOOL_WIRE_FROZEN_ON_STARTUP", "") store = _DummyStore() - _ = dl.warm_tool_wire_cache(store=store, tools=object(), base_url="", roles=["manager"]) + _ = dl.warm_tool_wire_cache(store=store, tools=object(), base_url="", roles=["generalist"]) st = dl.tool_wire_freeze_status(store=store) assert st["enabled"] is True assert st["frozen"] is True @@ -24,7 +24,7 @@ def test_tool_wire_freeze_default_on(monkeypatch) -> None: def test_tool_wire_freeze_disabled_by_setting(monkeypatch) -> None: monkeypatch.setattr(dl, "_prepare_llm_tools", lambda **kwargs: []) store = _DummyStore({"AIA_TOOL_WIRE_FROZEN_ON_STARTUP": "0"}) - _ = dl.warm_tool_wire_cache(store=store, tools=object(), base_url="", roles=["manager"]) + _ = dl.warm_tool_wire_cache(store=store, tools=object(), base_url="", roles=["generalist"]) st = dl.tool_wire_freeze_status(store=store) assert st["enabled"] is False assert st["frozen"] is False