mirror of
https://github.com/hansjone/oclaw.git
synced 2026-10-09 03:30:48 +08:00
Expose skill role binding stored vs env override in Admin API
Add enabled_stored and enabled_env_present on skills binding GET/POST so UIs can explain when AIA_SKILL_ROLE_BINDING_ENABLED overrides SQLite. Add Admin hints for save-required and env override. Add skill_role_binding_enabled_stored/env_present helpers and a unit test. Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
parent
df30b5902f
commit
091a5626e7
4 changed files with 70 additions and 0 deletions
|
|
@ -27,6 +27,8 @@ from oclaw.runtime.skill_role_binding import (
|
||||||
normalize_skill_role_binding,
|
normalize_skill_role_binding,
|
||||||
ordered_binding_roles,
|
ordered_binding_roles,
|
||||||
skill_role_binding_enabled,
|
skill_role_binding_enabled,
|
||||||
|
skill_role_binding_enabled_env_present,
|
||||||
|
skill_role_binding_enabled_stored,
|
||||||
)
|
)
|
||||||
from oclaw.runtime.skills_prompt import collect_skill_catalog_entries
|
from oclaw.runtime.skills_prompt import collect_skill_catalog_entries
|
||||||
from oclaw.runtime.skills import _allowed_tool_names_after_wire_policy, discover_workspace_skill_manifests
|
from oclaw.runtime.skills import _allowed_tool_names_after_wire_policy, discover_workspace_skill_manifests
|
||||||
|
|
@ -352,6 +354,8 @@ def include_skill_routes(
|
||||||
return {
|
return {
|
||||||
"ok": True,
|
"ok": True,
|
||||||
"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_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": str(store.get_setting(SKILL_ROLE_BINDING_MANAGER_INHERIT_SETTING) or "").strip() not in {"0", "false", "False"},
|
||||||
"available_roles": roles,
|
"available_roles": roles,
|
||||||
"installed_skills": items,
|
"installed_skills": items,
|
||||||
|
|
@ -390,6 +394,8 @@ def include_skill_routes(
|
||||||
return {
|
return {
|
||||||
"ok": True,
|
"ok": True,
|
||||||
"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_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": str(store.get_setting(SKILL_ROLE_BINDING_MANAGER_INHERIT_SETTING) or "").strip() not in {"0", "false", "False"},
|
||||||
"available_roles": roles,
|
"available_roles": roles,
|
||||||
"mapping": mapping,
|
"mapping": mapping,
|
||||||
|
|
|
||||||
|
|
@ -7792,6 +7792,15 @@ async function renderSkills() {
|
||||||
|
|
||||||
const skillBindingState = { roles: [], names: [], mapping: {}, enabled: false, managerInherit: true };
|
const skillBindingState = { roles: [], names: [], mapping: {}, enabled: false, managerInherit: true };
|
||||||
const skillBindingStatus = el("div", { class: "muted", text: "" });
|
const skillBindingStatus = el("div", { class: "muted", text: "" });
|
||||||
|
const skillBindingPersistHint = el("div", {
|
||||||
|
class: "muted",
|
||||||
|
style: "margin:6px 0;line-height:1.5;",
|
||||||
|
text: "Checkboxes and per-role skill lists are not saved until you click Save skill role binding.",
|
||||||
|
});
|
||||||
|
const skillBindingEnvHint = el("div", {
|
||||||
|
class: "muted",
|
||||||
|
style: "margin:6px 0;line-height:1.5;display:none;",
|
||||||
|
});
|
||||||
const skillEffectiveState = { items: [] };
|
const skillEffectiveState = { items: [] };
|
||||||
const skillBindingEnabledCb = el("input", { type: "checkbox" });
|
const skillBindingEnabledCb = el("input", { type: "checkbox" });
|
||||||
const skillBindingManagerInheritCb = el("input", { type: "checkbox" });
|
const skillBindingManagerInheritCb = el("input", { type: "checkbox" });
|
||||||
|
|
@ -7910,6 +7919,18 @@ async function renderSkills() {
|
||||||
skillBindingState.managerInherit = Object.prototype.hasOwnProperty.call(r, "manager_inherit") ? !!r.manager_inherit : true;
|
skillBindingState.managerInherit = Object.prototype.hasOwnProperty.call(r, "manager_inherit") ? !!r.manager_inherit : true;
|
||||||
skillBindingEnabledCb.checked = skillBindingState.enabled;
|
skillBindingEnabledCb.checked = skillBindingState.enabled;
|
||||||
skillBindingManagerInheritCb.checked = skillBindingState.managerInherit;
|
skillBindingManagerInheritCb.checked = skillBindingState.managerInherit;
|
||||||
|
if (r.enabled_env_present) {
|
||||||
|
skillBindingEnvHint.style.display = "block";
|
||||||
|
const stored = !!r.enabled_stored;
|
||||||
|
const eff = !!r.enabled;
|
||||||
|
skillBindingEnvHint.textContent =
|
||||||
|
"Process env AIA_SKILL_ROLE_BINDING_ENABLED is set: it overrides the Admin/SQLite value at runtime. " +
|
||||||
|
"The checkbox reflects the effective (env) value. Remove or unset that variable on the oclaw process to use Admin only. " +
|
||||||
|
`(stored in DB: ${String(stored)}, effective: ${String(eff)}).`;
|
||||||
|
} else {
|
||||||
|
skillBindingEnvHint.style.display = "none";
|
||||||
|
skillBindingEnvHint.textContent = "";
|
||||||
|
}
|
||||||
skillRoleSelect.innerHTML = "";
|
skillRoleSelect.innerHTML = "";
|
||||||
skillBindingState.roles.forEach((role) => {
|
skillBindingState.roles.forEach((role) => {
|
||||||
skillRoleSelect.appendChild(el("option", { value: role, text: role }));
|
skillRoleSelect.appendChild(el("option", { value: role, text: role }));
|
||||||
|
|
@ -7942,6 +7963,18 @@ async function renderSkills() {
|
||||||
skillBindingState.managerInherit = Object.prototype.hasOwnProperty.call(r, "manager_inherit") ? !!r.manager_inherit : true;
|
skillBindingState.managerInherit = Object.prototype.hasOwnProperty.call(r, "manager_inherit") ? !!r.manager_inherit : true;
|
||||||
skillBindingEnabledCb.checked = skillBindingState.enabled;
|
skillBindingEnabledCb.checked = skillBindingState.enabled;
|
||||||
skillBindingManagerInheritCb.checked = skillBindingState.managerInherit;
|
skillBindingManagerInheritCb.checked = skillBindingState.managerInherit;
|
||||||
|
if (r.enabled_env_present) {
|
||||||
|
skillBindingEnvHint.style.display = "block";
|
||||||
|
const stored = !!r.enabled_stored;
|
||||||
|
const eff = !!r.enabled;
|
||||||
|
skillBindingEnvHint.textContent =
|
||||||
|
"Process env AIA_SKILL_ROLE_BINDING_ENABLED is set: it overrides the Admin/SQLite value at runtime. " +
|
||||||
|
"The checkbox reflects the effective (env) value. Remove or unset that variable on the oclaw process to use Admin only. " +
|
||||||
|
`(stored in DB: ${String(stored)}, effective: ${String(eff)}).`;
|
||||||
|
} else {
|
||||||
|
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)} manager_inherit=${String(skillBindingState.managerInherit)}`;
|
||||||
renderSkillBindingList();
|
renderSkillBindingList();
|
||||||
renderSkillBindingDashboard();
|
renderSkillBindingDashboard();
|
||||||
|
|
@ -7960,6 +7993,8 @@ async function renderSkills() {
|
||||||
"When enabled, installed workspace skills only appear in the model skills catalog for the selected role. You can optionally inherit manager-bound skills to other roles.",
|
"When enabled, installed workspace skills only appear in the model skills catalog for the selected role. You can optionally inherit manager-bound skills to other roles.",
|
||||||
}),
|
}),
|
||||||
skillBindingStatus,
|
skillBindingStatus,
|
||||||
|
skillBindingPersistHint,
|
||||||
|
skillBindingEnvHint,
|
||||||
el("label", { class: "row", style: "gap:8px;align-items:center;margin-top:6px;" }, [
|
el("label", { class: "row", style: "gap:8px;align-items:center;margin-top:6px;" }, [
|
||||||
skillBindingEnabledCb,
|
skillBindingEnabledCb,
|
||||||
el("span", { text: "Enable role binding (AIA_SKILL_ROLE_BINDING_ENABLED)" }),
|
el("span", { text: "Enable role binding (AIA_SKILL_ROLE_BINDING_ENABLED)" }),
|
||||||
|
|
|
||||||
|
|
@ -25,6 +25,20 @@ def ordered_binding_roles() -> list[str]:
|
||||||
return ["manager", *ordered_specialist_ids()]
|
return ["manager", *ordered_specialist_ids()]
|
||||||
|
|
||||||
|
|
||||||
|
def skill_role_binding_enabled_env_present() -> bool:
|
||||||
|
"""True when ``AIA_SKILL_ROLE_BINDING_ENABLED`` is set in the process environment (any value)."""
|
||||||
|
return bool(str(os.getenv(SKILL_ROLE_BINDING_ENABLED_SETTING) or "").strip())
|
||||||
|
|
||||||
|
|
||||||
|
def skill_role_binding_enabled_stored(*, store: Any) -> bool:
|
||||||
|
"""SQLite/Admin value only; ignores environment (contrast :func:`skill_role_binding_enabled`)."""
|
||||||
|
try:
|
||||||
|
raw = str(store.get_setting(SKILL_ROLE_BINDING_ENABLED_SETTING) or "").strip()
|
||||||
|
except Exception:
|
||||||
|
raw = ""
|
||||||
|
return _truthy(raw) if raw else False
|
||||||
|
|
||||||
|
|
||||||
def skill_role_binding_enabled(*, store: Any) -> bool:
|
def skill_role_binding_enabled(*, store: Any) -> bool:
|
||||||
raw_env = str(os.getenv(SKILL_ROLE_BINDING_ENABLED_SETTING) or "").strip()
|
raw_env = str(os.getenv(SKILL_ROLE_BINDING_ENABLED_SETTING) or "").strip()
|
||||||
if raw_env:
|
if raw_env:
|
||||||
|
|
@ -132,6 +146,8 @@ __all__ = [
|
||||||
"SKILL_ROLE_BINDING_KEY",
|
"SKILL_ROLE_BINDING_KEY",
|
||||||
"SKILL_ROLE_BINDING_ENABLED_SETTING",
|
"SKILL_ROLE_BINDING_ENABLED_SETTING",
|
||||||
"SKILL_ROLE_BINDING_MANAGER_INHERIT_SETTING",
|
"SKILL_ROLE_BINDING_MANAGER_INHERIT_SETTING",
|
||||||
|
"skill_role_binding_enabled_env_present",
|
||||||
|
"skill_role_binding_enabled_stored",
|
||||||
"allowed_workspace_skill_names_for_role",
|
"allowed_workspace_skill_names_for_role",
|
||||||
"load_skill_role_binding_dict",
|
"load_skill_role_binding_dict",
|
||||||
"mapping_has_any_skill_names",
|
"mapping_has_any_skill_names",
|
||||||
|
|
|
||||||
|
|
@ -8,6 +8,9 @@ from oclaw.runtime.skill_role_binding import (
|
||||||
SKILL_ROLE_BINDING_KEY,
|
SKILL_ROLE_BINDING_KEY,
|
||||||
normalize_skill_role_binding,
|
normalize_skill_role_binding,
|
||||||
ordered_binding_roles,
|
ordered_binding_roles,
|
||||||
|
skill_role_binding_enabled,
|
||||||
|
skill_role_binding_enabled_env_present,
|
||||||
|
skill_role_binding_enabled_stored,
|
||||||
)
|
)
|
||||||
from oclaw.runtime.skills_prompt import collect_skill_catalog_entries
|
from oclaw.runtime.skills_prompt import collect_skill_catalog_entries
|
||||||
from oclaw.runtime.skills_workspace_lane import skill_dir_private_lane_segment
|
from oclaw.runtime.skills_workspace_lane import skill_dir_private_lane_segment
|
||||||
|
|
@ -122,3 +125,13 @@ def test_normalize_drops_unknown_skills(tmp_path: Path) -> None:
|
||||||
)
|
)
|
||||||
assert out["manager"] == ["skill-x"]
|
assert out["manager"] == ["skill-x"]
|
||||||
assert out["generalist"] == ["skill-x"]
|
assert out["generalist"] == ["skill-x"]
|
||||||
|
|
||||||
|
|
||||||
|
def test_skill_role_binding_env_overrides_store_value(tmp_path: Path, monkeypatch) -> None:
|
||||||
|
db = tmp_path / "rolebind.sqlite"
|
||||||
|
store = SqliteStore(str(db))
|
||||||
|
store.set_setting(SKILL_ROLE_BINDING_ENABLED_SETTING, "1")
|
||||||
|
monkeypatch.setenv("AIA_SKILL_ROLE_BINDING_ENABLED", "0")
|
||||||
|
assert skill_role_binding_enabled_env_present() is True
|
||||||
|
assert skill_role_binding_enabled_stored(store=store) is True
|
||||||
|
assert skill_role_binding_enabled(store=store) is False
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue