feat: add seat-scoped static and dynamic assistant modes

This commit is contained in:
vee
2026-09-12 14:56:30 +08:00
parent d09b5967f8
commit d8dad22ec0
60 changed files with 5847 additions and 149 deletions
@@ -24,11 +24,13 @@ from cognitive_agent.application.assistant_plan_guard import (
SCENE_COMPOSABLE_CAPABILITIES,
)
from cognitive_agent.application.mode_profile import ModeOperation
from cognitive_agent.application.mode_profile_intent import mode_creation_source_name
from cognitive_agent.application.mode_schedule import ModeSchedule
_TEMPLATE_ID = re.compile(r"^[a-z0-9][a-z0-9_-]{0,63}$")
_SURROUNDING_PUNCTUATION = " \t\r\n,。,.!!??;;::'\"‘’“”《》<>【】[]()()"
_DOCUMENT_FIELDS = frozenset(
{"id", "name", "aliases", "description", "icon", "operations"}
{"id", "name", "aliases", "description", "icon", "operations", "schedule"}
)
@@ -66,6 +68,7 @@ class SceneTemplate:
operations: tuple[ModeOperation, ...]
source: str
revision: str
schedule: ModeSchedule | None = None
@classmethod
def from_yaml(cls, path: Path) -> "SceneTemplate":
@@ -100,7 +103,13 @@ class SceneTemplate:
alias_keys.add(key)
aliases.append(alias)
schedule = (ModeSchedule.from_dict(document["schedule"])
if document.get("schedule") is not None else None)
raw_operations = document.get("operations")
if schedule is not None:
if raw_operations:
raise ValueError(f"{path} cannot mix static operations and a schedule")
raw_operations = list(schedule.phases[0].operations)
if not isinstance(raw_operations, list) or not raw_operations:
raise ValueError(f"{path} must contain at least one operation")
if len(raw_operations) > MAX_PLAN_REQUESTS:
@@ -155,6 +164,8 @@ class SceneTemplate:
),
),
}
if schedule is not None:
semantic_document["schedule"] = schedule.to_dict()
canonical = json.dumps(
semantic_document,
ensure_ascii=False,
@@ -171,6 +182,7 @@ class SceneTemplate:
operations=tuple(operations),
source=str(path),
revision=revision,
schedule=schedule,
)
def plan(self) -> dict[str, Any]:
@@ -179,7 +191,7 @@ class SceneTemplate:
request = operation.to_request(self.name)
request["duration_ms"] = None
requests.append(request)
return {
plan = {
"detected_domains": list(
dict.fromkeys(operation.domain for operation in self.operations)
),
@@ -195,6 +207,9 @@ class SceneTemplate:
"template_revision": self.revision,
},
}
if self.schedule is not None:
plan["schedule"] = self.schedule.to_dict()
return plan
def public_dict(self, *, include_plan: bool = False) -> dict[str, Any]:
result: dict[str, Any] = {
@@ -204,13 +219,16 @@ class SceneTemplate:
"description": self.description,
"icon": self.icon,
"domains": list(dict.fromkeys(item.domain for item in self.operations)),
"operation_count": len(self.operations),
"operation_count": self.schedule.operation_count if self.schedule else len(self.operations),
"template_revision": self.revision,
"origin": "builtin_scene_template",
"read_only": True,
}
if include_plan:
result["plan"] = self.plan()
if self.schedule is not None:
result["schedule"] = self.schedule.to_dict()
result["domains"] = sorted({op["domain"] for phase in self.schedule.phases for op in phase.operations})
return result
@@ -257,7 +275,11 @@ class SceneTemplateRegistry:
def resolve_exact_name(self, value: object) -> SceneTemplate | None:
key = normalize_scene_template_name(value)
return self._by_name.get(key) if key else None
exact = self._by_name.get(key) if key else None
if exact is not None:
return exact
subject = normalize_scene_template_name(mode_creation_source_name(value))
return self._by_name.get(subject) if subject and subject != key else None
def get_template(self, template_id: str) -> SceneTemplate:
try: