mirror of
https://github.com/hansjone/oclaw.git
synced 2026-10-09 04:40:45 +08:00
Converge manager out of product roles and drop dead inherit prefs.
Specialists-only binding/prewarm/MCP allowlists fold legacy manager aliases into generalist; stop persisting confirm/plan prefs and strip inherit from prompt signatures. Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
parent
bc20b41ee2
commit
454c841af7
20 changed files with 131 additions and 290 deletions
|
|
@ -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(
|
||||
|
|
|
|||
|
|
@ -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))
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
|
|
|
|||
|
|
@ -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];
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue