feat: integrate production cockpit AI assistant

This commit is contained in:
vee
2026-09-08 17:46:07 +08:00
parent 5e228e12ae
commit 93103c2579
66 changed files with 11046 additions and 374 deletions
@@ -0,0 +1,273 @@
"""Read-only, repository-backed templates for common cabin scenes.
Scene templates are deliberately separate from saved mode profiles. Matching a
template produces a new, unsaved draft; it never makes the template executable
as a persisted mode and never lets a later edit overwrite repository YAML.
"""
from __future__ import annotations
import hashlib
import json
import math
import re
from collections.abc import Mapping
from dataclasses import dataclass
from pathlib import Path
from typing import Any
import yaml
from cognitive_agent.application.assistant_plan_guard import (
MAX_PLAN_PARAMETERS,
MAX_PLAN_REQUESTS,
SCENE_COMPOSABLE_CAPABILITIES,
)
from cognitive_agent.application.mode_profile import ModeOperation
_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"}
)
def normalize_scene_template_name(value: object) -> str:
"""Normalize an exact user-facing name without enabling fuzzy matching."""
return "".join(str(value or "").strip(_SURROUNDING_PUNCTUATION).casefold().split())
def _clean_text(value: object, *, limit: int, field: str) -> str:
text = " ".join(str(value or "").replace("\x00", " ").split())
if not text:
raise ValueError(f"scene template {field} must not be empty")
if len(text) > limit:
raise ValueError(f"scene template {field} must be at most {limit} characters")
return text
def _json_scalar(value: object) -> bool:
return (
isinstance(value, (str, bool, int))
or (isinstance(value, float) and math.isfinite(value))
)
@dataclass(frozen=True)
class SceneTemplate:
"""One immutable common-scene seed made only from absolute set actions."""
id: str
name: str
aliases: tuple[str, ...]
description: str
icon: str
operations: tuple[ModeOperation, ...]
source: str
revision: str
@classmethod
def from_yaml(cls, path: Path) -> "SceneTemplate":
document = yaml.safe_load(path.read_text(encoding="utf-8"))
if not isinstance(document, Mapping):
raise ValueError(f"{path} must contain a YAML object")
unknown_fields = set(document) - _DOCUMENT_FIELDS
if unknown_fields:
raise ValueError(
f"{path} contains unsupported fields: {', '.join(sorted(unknown_fields))}"
)
template_id = str(document.get("id") or "").strip()
if not _TEMPLATE_ID.fullmatch(template_id):
raise ValueError(f"{path} contains an invalid scene template id")
name = _clean_text(document.get("name"), limit=64, field="name")
raw_aliases = document.get("aliases", [])
if not isinstance(raw_aliases, list):
raise ValueError(f"{path} aliases must be an array")
if any(not isinstance(raw_alias, str) for raw_alias in raw_aliases):
raise ValueError(f"{path} aliases must contain only strings")
alias_values = [name, *raw_aliases]
if name.endswith("模式") and len(name) > 2:
alias_values.append(name.removesuffix("模式"))
aliases: list[str] = []
alias_keys: set[str] = set()
for raw_alias in alias_values:
alias = _clean_text(raw_alias, limit=64, field="alias")
key = normalize_scene_template_name(alias)
if not key or key in alias_keys:
continue
alias_keys.add(key)
aliases.append(alias)
raw_operations = document.get("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:
raise ValueError(
f"{path} contains more than {MAX_PLAN_REQUESTS} scene operations"
)
operations: list[ModeOperation] = []
operation_keys: set[tuple[str, str, str]] = set()
for index, raw_operation in enumerate(raw_operations):
if not isinstance(raw_operation, dict):
raise ValueError(f"{path} operation {index} must be an object")
operation = ModeOperation.from_dict(raw_operation)
if operation.operation != "set":
raise ValueError(f"{path} operation {index} must use absolute set")
if not operation.domain or not operation.capability or not operation.target_scope:
raise ValueError(f"{path} operation {index} is incomplete")
if operation.capability not in SCENE_COMPOSABLE_CAPABILITIES:
raise ValueError(
f"{path} operation {index} uses a non-composable capability"
)
if operation.domain != operation.capability.partition(".")[0]:
raise ValueError(
f"{path} operation {index} has a capability/domain mismatch"
)
if not operation.parameters or len(operation.parameters) > MAX_PLAN_PARAMETERS:
raise ValueError(f"{path} operation {index} has invalid parameters")
if any(
not isinstance(parameter_name, str)
or not parameter_name.strip()
or not _json_scalar(parameter_value)
for parameter_name, parameter_value in operation.parameters.items()
):
raise ValueError(f"{path} operation {index} has non-scalar parameters")
key = (operation.domain, operation.capability, operation.target_scope)
if key in operation_keys:
raise ValueError(f"{path} repeats capability and target {key!r}")
operation_keys.add(key)
operations.append(operation)
semantic_document = {
"id": template_id,
"name": name,
"aliases": sorted(aliases, key=normalize_scene_template_name),
"description": str(document.get("description") or ""),
"icon": str(document.get("icon") or "sparkles"),
"operations": sorted(
(operation.to_yaml() for operation in operations),
key=lambda item: (
str(item.get("domain") or ""),
str(item.get("capability") or ""),
str(item.get("target_scope") or ""),
),
),
}
canonical = json.dumps(
semantic_document,
ensure_ascii=False,
sort_keys=True,
separators=(",", ":"),
)
revision = hashlib.sha256(canonical.encode("utf-8")).hexdigest()[:16]
return cls(
id=template_id,
name=name,
aliases=tuple(aliases),
description=str(document.get("description") or "")[:500],
icon=str(document.get("icon") or "sparkles")[:64],
operations=tuple(operations),
source=str(path),
revision=revision,
)
def plan(self) -> dict[str, Any]:
requests = []
for operation in self.operations:
request = operation.to_request(self.name)
request["duration_ms"] = None
requests.append(request)
return {
"detected_domains": list(
dict.fromkeys(operation.domain for operation in self.operations)
),
"requests": requests,
"ambiguities": [],
"decision_summary": [
f"已命中内置常用模式模板“{self.name}”,等待工程安全链复核。"
],
"confidence": 1.0,
"metadata": {
"source": "scene_template",
"template_id": self.id,
"template_revision": self.revision,
},
}
def public_dict(self, *, include_plan: bool = False) -> dict[str, Any]:
result: dict[str, Any] = {
"id": self.id,
"name": self.name,
"aliases": list(self.aliases),
"description": self.description,
"icon": self.icon,
"domains": list(dict.fromkeys(item.domain for item in self.operations)),
"operation_count": len(self.operations),
"template_revision": self.revision,
"origin": "builtin_scene_template",
"read_only": True,
}
if include_plan:
result["plan"] = self.plan()
return result
class SceneTemplateRegistry:
"""Load and exactly resolve the current project's common scene templates."""
def __init__(self, template_root: Path):
self.template_root = Path(template_root)
self._templates: tuple[SceneTemplate, ...] = ()
self._by_id: dict[str, SceneTemplate] = {}
self._by_name: dict[str, SceneTemplate] = {}
self.reload()
def reload(self) -> None:
templates = tuple(
SceneTemplate.from_yaml(path)
for path in sorted(self.template_root.rglob("*.yaml"))
if path.is_file()
) if self.template_root.exists() else ()
by_id: dict[str, SceneTemplate] = {}
by_name: dict[str, SceneTemplate] = {}
for template in templates:
if template.id in by_id:
raise ValueError(f"duplicate scene template id: {template.id}")
by_id[template.id] = template
for value in (template.id, template.name, *template.aliases):
key = normalize_scene_template_name(value)
current = by_name.get(key)
if current is not None and current.id != template.id:
raise ValueError(
f"ambiguous scene template name {value!r}: "
f"{current.id}, {template.id}"
)
by_name[key] = template
self._templates = templates
self._by_id = by_id
self._by_name = by_name
def templates(self) -> list[SceneTemplate]:
return list(self._templates)
def public_templates(self, *, include_plan: bool = False) -> list[dict[str, Any]]:
return [template.public_dict(include_plan=include_plan) for template in self._templates]
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
def get_template(self, template_id: str) -> SceneTemplate:
try:
return self._by_id[str(template_id).strip()]
except KeyError as exc:
raise KeyError(str(template_id).strip()) from exc
__all__ = [
"SceneTemplate",
"SceneTemplateRegistry",
"normalize_scene_template_name",
]