feat(config,policy): skill sets per kind, master without vault skills, attachments in the vault, crons in local tz

This commit is contained in:
hh
2026-09-01 23:13:29 +02:00
parent 97597ba25a
commit f0842fa8a7
4 changed files with 89 additions and 20 deletions
+27 -2
View File
@@ -27,6 +27,7 @@ __all__ = [
"TRIAGE_DISALLOWED",
"Zones",
"bash_zones",
"forbid_skills",
"requires_skill",
"skill_tracker",
"vault_zones",
@@ -220,15 +221,39 @@ def _pathlike(word: str) -> bool:
)
def _skill_name(call: ToolCall) -> str:
"""Имя скилла из `Skill({"skill": "vault:firefly", ...})`, без плагина."""
name = str(call.input.get("skill") or call.input.get("name") or "")
return name.rsplit(":", 1)[-1]
def skill_tracker() -> PolicyRule:
"""Запоминает открытые скиллы в состоянии сессии (для `requires_skill`)."""
def rule(call: ToolCall) -> None:
if call.tool != "Skill":
return
name = str(call.input.get("skill") or call.input.get("name") or "")
opened: set[str] = call.state.setdefault("skills", set())
opened.add(name.rsplit(":", 1)[-1])
opened.add(_skill_name(call))
return rule
def forbid_skills(names: tuple[str, ...], kinds: tuple[str, ...]) -> PolicyRule:
"""§4.3: скиллы из ``names`` (fnmatch, без учёта регистра) в ``kinds`` закрыты.
Мастер их не открывает - для этого есть ветка; правило отвечает так,
чтобы модель сама её открыла.
"""
patterns = tuple(p.lower() for p in names)
def rule(call: ToolCall) -> Deny | None:
if call.tool != "Skill" or call.kind not in kinds:
return None
name = _skill_name(call)
if not any(fnmatch.fnmatchcase(name.lower(), p) for p in patterns):
return None
return Deny(reason=f"скилл {name} недоступен в {call.kind}: открой ветку")
return rule