"""Application service for the stage-one seat single-domain closed loop.""" from __future__ import annotations import hashlib import json import os import re import sys import threading import time import uuid from collections import Counter from collections.abc import Callable, Mapping from concurrent.futures import ThreadPoolExecutor from copy import deepcopy from datetime import UTC, datetime from pathlib import Path from typing import Any import yaml REPOSITORY_ROOT = Path(__file__).resolve().parents[3] SEAT_SOURCE = REPOSITORY_ROOT / "domains" / "seat" / "src" if str(SEAT_SOURCE) not in sys.path: sys.path.insert(0, str(SEAT_SOURCE)) from seat_domain import ( # noqa: E402 SIMULATED_HARDWARE_FAULTS, HardwareSeatExecutor, SeatCanMatrix, SeatCompiler, SimulatedHardwareSeatExecutor, T2GSeatCompiler, VirtualSeatExecutor, build_function_config, build_function_preset, build_intent_ir, parse_local_intent, validate_semantics, ) from cognitive_agent.application.assistant_plan_guard import ( # noqa: E402 guard_assistant_plan, ) from cognitive_agent.application.capability_registry import CapabilityRegistry # noqa: E402 from cognitive_agent.application.conversation_state import ConversationStateStore # noqa: E402 from cognitive_agent.application.direct_can_control import ( # noqa: E402 build_direct_can_intent, ) from cognitive_agent.application.domain_runtime import ConfiguredDomainRuntime # noqa: E402 from cognitive_agent.application.mode_creation_seat_policy import ( # noqa: E402 ModeCreationSeatPolicyError, creation_seat_scopes, project_seed_operations, project_seed_plan, project_seed_schedule, ) from cognitive_agent.application.mode_device_additions import ( # noqa: E402 mode_device_additions, ) from cognitive_agent.application.mode_profile import ( # noqa: E402 ModeProfile, ModeProfileRegistry, ModeTimeline, ) from cognitive_agent.application.mode_scene_ingress import ( # noqa: E402 ModeSceneIngressConsumer, ) from cognitive_agent.application.mode_profile_intent import ( # noqa: E402 is_mode_profile_query, mode_creation_source_name, normalize_mode_profile_name, parse_mode_profile_create, parse_mode_profile_delete, parse_mode_profile_update, ) from cognitive_agent.application.mode_scene_catalog import ( # noqa: E402 DEFAULT_N7_SCENE_SERVER_ID, DEFAULT_N7_SCENE_SERVER_NAME, ModeSceneCatalogPublisher, ModeSceneMqttBurstPublisher, build_mode_scene_catalog, ) from cognitive_agent.application.mode_schedule import ModeSchedule, ModeScheduleRunner # noqa: E402 from cognitive_agent.application.mode_schedule_service import ModeScheduleServiceMixin # noqa: E402 from cognitive_agent.application.model_plan_adapter import ( # noqa: E402 APPLY_PRESET_CAPABILITY, DYNAMIC_MODE_STOP_CAPABILITY, SAVE_PRESET_CAPABILITY, SEAT_CAPABILITIES, SEAT_MOTION_CAPABILITIES, ModelPlanAdapter, ) from cognitive_agent.application.mqtt_ota_seat_executor import ( # noqa: E402 MqttNativeSeatExecutor, ) from cognitive_agent.application.native_can_feedback import ( # noqa: E402 VehicleSafetyPreflightError, authoritative_physical_ch1_feedback_state, decode_feedback, observe_vehicle_safety, require_stationary_vehicle, ) from cognitive_agent.application.native_can_intent import ( # noqa: E402 DRIVER_SEAT_MASSAGE_CONTINUATION_PATTERN, merge_native_can_intent, ) from cognitive_agent.application.native_can_relative import ( # noqa: E402 NativeCanRelativeResolver, ) from cognitive_agent.application.native_can_runtime import ( # noqa: E402 UNVERIFIED_NATIVE_CAPABILITIES, NativeCanCompileError, NativeCanExecutor, NativeCanRuntime, ) from cognitive_agent.application.scene_templates import SceneTemplateRegistry # noqa: E402 from cognitive_agent.application.schedule_intent import ( # noqa: E402 is_schedule_stop, needs_mode_schedule, ) from cognitive_agent.application.seat_calibration import position_pct_to_angle # noqa: E402 from cognitive_agent.application.semantic_ir import ( # noqa: E402 apply_language_safety_guards, resolve_explicit_seat_scopes, ) from cognitive_agent.application.vehicle_profile import ( # noqa: E402 VehicleProfile, VehicleProfileRegistry, ) from cognitive_agent.application.vehicle_state_monitor import ( # noqa: E402 OBSERVABLE_IDS, REQUIRED_BODY_IDS, REQUIRED_SEAT_IDS, VehicleStateMonitor, ) from cognitive_agent.application.zcan_seat_executor import ( # noqa: E402 ZcanBridgeSeatExecutor, ) from cognitive_agent.infrastructure.can.access import ( # noqa: E402 PHYSICAL_BENCH_ACCESS, ) from cognitive_agent.infrastructure.can.gateway import ( # noqa: E402 ZlgCanBridgeProcess, ZlgChannelConfig, ) from cognitive_agent.infrastructure.can.hardware import ( # noqa: E402 PHYSICAL_BENCH_COORDINATOR, HardwareSafetyError, SeatHardwareSettings, VehicleCanSettings, VehicleMqttSettings, ) from cognitive_agent.infrastructure.can.mqtt_transport import ( # noqa: E402 MQTT_MODE_SCENE_INGRESS_TOPIC, MQTT_MODE_SCENE_TOPIC, MQTT_N7_CONTROL_TRANSFER_TOPIC, MqttCanTransport, ) from cognitive_agent.infrastructure.llm.openai_responses import ( # noqa: E402 REASONING_EFFORTS, OpenAIIntentError, OpenAIIntentInterpreter, ) from cognitive_agent.runtime.contracts import ContractValidator # noqa: E402 def _now() -> str: return datetime.now(UTC).isoformat().replace("+00:00", "Z") def _clean_vehicle_number(value: float | None) -> int | float | None: if value is None: return None return int(value) if float(value).is_integer() else round(float(value), 1) CONVERSATION_ID_PATTERN = re.compile(r"^conv-[a-z0-9-]{6,80}$") MODE_EDITOR_REQUEST_ID_PATTERN = re.compile(r"^req-[a-z0-9-]{6,100}$") SEAT_SCOPE_PATTERN = re.compile(r"\bseats?\b|座椅|坐椅", re.IGNORECASE) DRIVER_SEAT_SCOPE_PATTERN = re.compile( r"\bdriver\b|\bfront[-_ ]?left\b|\bleft[-_ ]?front\b|" r"主驾|主驾驶(?:座|座椅|席)?|驾驶位|驾驶员|驾驶席|驾驶座(?:椅)?|" r"司机位|司机座(?:椅)?|左前", re.IGNORECASE, ) PASSENGER_SEAT_SCOPE_PATTERN = re.compile( r"\bpassenger\b|\bfront[-_ ]?right\b|\bright[-_ ]?front\b|副驾|乘客|乘员|右前", re.IGNORECASE, ) MODE_PROFILE_UPDATE_EXPLICIT_TARGET_PATTERN = re.compile( r"\b(?:vehicle|driver|passenger|front[-_ ]?(?:left|right)|rear)\b|" r"主驾|主驾驶|驾驶位|驾驶员|驾驶席|司机位|副驾|副驾驶|乘客|乘员|" r"全车|整车|所有|全部|前排|后排|二排|三排|左前|右前|左后|右后|" r"主驾左|主驾右|副驾左|副驾右", re.IGNORECASE, ) WIRED_CAN_TRANSPORT = "wired_can" WIRELESS_MQTT_TRANSPORT = "wireless_mqtt" LOCAL_CONTROL_BUDGET_MS = 1000 VEHICLE_CONTROL_TRANSPORTS = frozenset( {WIRED_CAN_TRANSPORT, WIRELESS_MQTT_TRANSPORT} ) def _wireless_mqtt_publish_preflight() -> dict[str, Any]: """Describe the non-physical gate used before an approved MQTT publish.""" return { "transport": WIRELESS_MQTT_TRANSPORT, "status": "delegated", "policy": "mqtt_whitelist_publish", "control_gate": "capability_parameter_and_frame_whitelist", "required_vehicle_frames": [], } EXECUTION_VERIFICATION_STATUSES = frozenset( {"verified_success", "submitted_unverified", "failed_or_blocked"} ) def _operation_verification_status(item: dict[str, Any]) -> str: """Classify one operation without conflating Tx and physical feedback.""" telemetry = item.get("telemetry", {}) explicit = str(telemetry.get("verification_status") or "") if explicit in EXECUTION_VERIFICATION_STATUSES: return explicit if item.get("status") != "succeeded": return "failed_or_blocked" if ( telemetry.get("physical_effect_confirmed") is True or telemetry.get("feedback_frame") is not None or telemetry.get("hardware_feedback") is True or telemetry.get("firmware_verified") is True ): return "verified_success" if ( telemetry.get("command_frame") is not None or telemetry.get("delivery_status") in {"submitted", "queued", "published"} ): return "submitted_unverified" # Dry-run and control-plane operations are complete at their own evidence # level and do not claim that a physical actuator moved. return "verified_success" def _annotate_execution_report(report: dict[str, Any]) -> tuple[str, bool]: """Add task-level evidence and normalize submission success. Returns ``(verification_status, partially_succeeded)``. Support steps such as firmware identity checks do not turn a failed requested operation into a partially successful user task. """ spec = report.setdefault("spec", {}) results = [item for item in spec.get("results", []) if isinstance(item, dict)] operation_results = [ item for item in results if item.get("ref") not in {"seat-controller-firmware"} ] or results statuses_by_ref: dict[str, list[str]] = {} for item in operation_results: statuses_by_ref.setdefault(str(item.get("ref") or "operation"), []).append( _operation_verification_status(item) ) statuses = [ ( "failed_or_blocked" if "failed_or_blocked" in values else "submitted_unverified" if "submitted_unverified" in values else "verified_success" ) for values in statuses_by_ref.values() ] failed = sum(status == "failed_or_blocked" for status in statuses) accepted = len(statuses) - failed partially_succeeded = bool(failed and accepted) verification_status = ( "failed_or_blocked" if failed else "submitted_unverified" if any(status == "submitted_unverified" for status in statuses) else "verified_success" ) spec["verification_status"] = verification_status spec["status"] = ( "partially_succeeded" if partially_succeeded else "failed" if failed else "succeeded" ) return verification_status, partially_succeeded def _has_mixed_driver_passenger_seat_scope(utterance: str) -> bool: """Detect one request that crosses the CH1-driver and CH0-passenger boundary.""" return bool( SEAT_SCOPE_PATTERN.search(utterance) and DRIVER_SEAT_SCOPE_PATTERN.search(utterance) and PASSENGER_SEAT_SCOPE_PATTERN.search(utterance) ) SAVE_MODE_PROFILE_NAME = re.compile( r"(?:固化为|保存为长期|保存成长期|长期保存为|记为长期|命名为)" r"(?P[^,。,.!!\s]{1,64}?模式)" ) # ``、`` is used both as a command separator (``车窗关闭、空调24度``) # and as an enumeration separator (``主驾、副驾车窗关闭``). Treat it as a # boundary only when both sides already look like self-contained controls; # otherwise keep it intact for the target/feature parsers below. CONTROL_CLAUSE_SUBJECT_PATTERN = re.compile( r"座椅|座位|靠背|椅背|坐盆|坐垫|腿托|脚托|滑轨|" r"通风|加热|按摩|零重力|半躺|女王副驾|休闲躺椅|" r"遮阳帘|天幕帘|天幕|车窗|窗户|" r"(?:左前|前左|右前|前右|左后|后左|右后|后右)(?:排)?窗|" r"灯光|室内灯|车内灯|阅读灯|氛围灯|" r"空调|压缩机|温度|风量|风速|内循环|外循环|" r"车门|门锁|后备箱|前大灯|近光灯|远光灯|雾灯|转向灯|双闪|" r"出风口" ) CONTROL_CLAUSE_PREDICATE_PATTERN = re.compile( r"打开|开启|启动|关闭|关掉|关上|停止|暂停|取消|" r"调整|调节|调到|调成|调为|调至|设置|设为|设到|切换|" r"移动|移到|移至|前移|后移|上移|下移|升高|降低|抬起|放倒|" r"调直|立直|竖直|归零|复位|锁上|上锁|落锁|解锁|" r"零重力|半躺|一半|半开|半关|最大|最小|最高|最低|最前|最后|到底|" r"(?:\d+(?:\.\d+)?|[零一二三四五六七八九十百两半]+)" r"(?:%|%|度|℃|档|级|秒|毫秒)" ) # These verbs identify an independent request that may be outside the local # vehicle registry. They are used only to split mixed sentences so an # already deterministic clause never has to be repeated to a hosted model. CLOUD_REQUEST_CLAUSE_PATTERN = re.compile( r"播放|暂停播放|停止播放|导航|搜索|查找|查询|拨打|呼叫|发送|" r"提醒|推荐|讲解|介绍|告诉|规划" ) RESTORE_MODE_PROFILE_PREFIX = re.compile( r"^(?:请(?:帮我)?|帮我)?(?:恢复|还原|重置|初始化)" r"(?P[^,。,.!!\s]{1,64}?模式)" r"(?:的)?(?:初始配置|初始参数|默认配置|原始配置|出厂配置)?$" ) RESTORE_MODE_PROFILE_SUFFIX = re.compile( r"^(?:请(?:帮我)?|帮我)?(?:把|将)?" r"(?P[^,。,.!!\s]{1,64}?模式)" r"(?:恢复|还原|重置|初始化)(?:到|为)?" r"(?:初始配置|初始参数|默认配置|原始配置|出厂配置)?$" ) SEAT_AXIS_TO_CAPABILITY = { axis: capability for capability, axis in SEAT_MOTION_CAPABILITIES.items() } SEAT_FUNCTION_TO_CAPABILITY = { function: capability for capability, function in SEAT_CAPABILITIES.items() } DYNAMIC_MODE_STOP_WORDS = ("停止", "关闭", "结束", "取消", "停下", "退出") DYNAMIC_MODE_WORDS = ( "动态模式", "动态座椅", "木马模式", "木马", "摇摇马", "骑木马", "rocking horse", "动态摇篮", "摇篮模式", ) class SeatDemoService(ModeScheduleServiceMixin): def __init__(self, repository_root: Path = REPOSITORY_ROOT): self.mode_schedule_runner = ModeScheduleRunner() self.root = repository_root self.domain_root = repository_root / "domains" / "seat" self.contracts = ContractValidator(repository_root) self.matrix = SeatCanMatrix( [ self.domain_root / "can" / "seat-thermal-matrix.yaml", self.domain_root / "can" / "seat-motion-virtual-matrix.yaml", ] ) self.compiler = SeatCompiler(self.domain_root / "can" / "signal-mapping.yaml") self.executor = VirtualSeatExecutor(self.matrix) self.simulated_hardware_executor = SimulatedHardwareSeatExecutor(self.matrix) self.simulated_hardware_enabled = os.getenv( "COGNITIVE_AGENT_SIMULATED_HARDWARE_ENABLED", "false" ).strip().lower() in {"1", "true", "yes", "on"} self.hardware_matrix = SeatCanMatrix( [ self.domain_root / "can" / "seat-thermal-hardware-matrix.yaml", self.domain_root / "can" / "seat-motion-hardware-matrix.yaml", ] ) self.hardware_compiler = T2GSeatCompiler( self.domain_root / "can" / "signal-mapping.hardware.yaml", repository_root / "platforms" / "t2g_seat_bench" / "calibration" / "seat.yaml", ) self.hardware_settings = SeatHardwareSettings.from_env(repository_root) self.vehicle_can_settings = VehicleCanSettings.from_env(repository_root) self.vehicle_mqtt_settings = VehicleMqttSettings.from_env() self._vehicle_state_monitor = ( VehicleStateMonitor(self.vehicle_can_settings) if self.vehicle_can_settings.backend == "zlgcan-linux" and self.vehicle_can_settings.state_configured and not self.vehicle_can_settings.configured else None ) self._vehicle_mqtt_transport = MqttCanTransport( self.vehicle_mqtt_settings ) self.native_can_runtime = NativeCanRuntime(repository_root) self.native_can_relative_resolver = NativeCanRelativeResolver(repository_root) self.hardware_execution = PHYSICAL_BENCH_COORDINATOR self.hardware_access = PHYSICAL_BENCH_ACCESS # Shared by the short-lived hardware executor instances. A process must # establish a fresh physical four-axis reference before trusting stored # Hall counts, and a firmware replacement invalidates it again. self._seat_position_memory_state = {"synchronized": False} self.openai = OpenAIIntentInterpreter() self.domain_runtimes = { domain: ConfiguredDomainRuntime(repository_root, domain) for domain in ("climate", "roof", "ambient_light") } self.capability_registry = CapabilityRegistry( repository_root, {"seat", *self.domain_runtimes} ) self.capability_context = self.capability_registry.snapshot() self.semantic_capability_metadata = { capability["name"]: { "risk_level": capability.get("risk_level", "high"), "confirmation_policy": capability.get( "confirmation_policy", "on_ambiguity" ), } for domain in self.capability_context["domains"] for capability in domain["capabilities"] } self.model_plan_adapter = ModelPlanAdapter(self.capability_context) self.vehicle_profiles = VehicleProfileRegistry(repository_root / "config" / "vehicles") configured_mode_root = os.getenv( "COGNITIVE_AGENT_MODE_PROFILE_ROOT", "" ).strip() self.mode_profile_user_root = ( Path(configured_mode_root).expanduser() if configured_mode_root else repository_root / "artifacts" / "mode-profiles" ) if not self.mode_profile_user_root.is_absolute(): self.mode_profile_user_root = repository_root / self.mode_profile_user_root self.mode_profiles = ModeProfileRegistry( repository_root / "config" / "modes", user_root=self.mode_profile_user_root, ) self._mode_scene_catalog_publisher: ModeSceneCatalogPublisher | None = None self._mode_scene_catalog_status: dict[str, Any] = { "enabled": False, "state": "disabled", "path": None, "vehicle_id": self.vehicle_profiles.default().id, "server_id": DEFAULT_N7_SCENE_SERVER_ID, "catalog_revision": None, "scene_count": 0, "last_error": None, } self._mode_scene_mqtt_publisher = ModeSceneMqttBurstPublisher( lambda scene: self._vehicle_mqtt_transport.publish_mode_scene(scene), topic=MQTT_MODE_SCENE_TOPIC, ) self._mode_scene_ingress = ModeSceneIngressConsumer( self.import_n7_mode_scene, ) self._vehicle_mqtt_transport.set_mode_scene_ingress_handler( self._mode_scene_ingress.submit ) self._n7_transfer_lock = threading.RLock() self._n7_transfer_publications: dict[str, float] = {} self.scene_templates = SceneTemplateRegistry( repository_root / "config" / "scene_templates" ) self._artifact_root = repository_root / "artifacts" / "runs" self.conversations = ConversationStateStore( preset_root=repository_root / "artifacts" / "user-presets" ) @property def artifact_root(self) -> Path: return self._artifact_root @artifact_root.setter def artifact_root(self, value: Path) -> None: self._artifact_root = Path(value) if hasattr(self, "conversations"): self.conversations.preset_root = self._artifact_root / "user-presets" def configure_mode_scene_catalog( self, path: Path, *, server_id: str = DEFAULT_N7_SCENE_SERVER_ID, name: str = DEFAULT_N7_SCENE_SERVER_NAME, ) -> dict[str, Any]: """Enable the derived JSON catalog without changing YAML persistence.""" catalog_path = Path(path).expanduser() if not catalog_path.is_absolute(): catalog_path = self.root / catalog_path vehicle_id = self.vehicle_profiles.default().id self._mode_scene_catalog_publisher = ModeSceneCatalogPublisher( catalog_path, vehicle_id, server_id=server_id, name=name, ) self._mode_scene_catalog_status = { "enabled": True, "state": "initializing", "path": str(catalog_path), "vehicle_id": vehicle_id, "server_id": self._mode_scene_catalog_publisher.server_id, "catalog_revision": None, "scene_count": 0, "last_error": None, } # The registry notifies after create, update, delete, restore, and any # explicit reload. The callback only updates a derived projection; YAML # remains the authoritative store used by every existing execution path. self.mode_profiles.add_change_listener(self._synchronize_mode_scene_catalog) return self.mode_scene_catalog_status() def _synchronize_mode_scene_catalog(self, registry: ModeProfileRegistry) -> None: publisher = self._mode_scene_catalog_publisher if publisher is None: return try: catalog = publisher.publish(registry) except Exception as exc: # noqa: BLE001 - derived export must not block controls self._mode_scene_catalog_status.update( { "state": "error", "last_error": str(exc), } ) return self._mode_scene_catalog_status.update( { "state": "ready", "catalog_revision": catalog["catalog_revision"], "scene_count": catalog["scene_count"], "last_error": None, } ) def mode_scene_catalog(self, vehicle_id: str | None = None) -> dict[str, Any]: """Return the current read-only mode-card catalog for an N7 client.""" resolved_vehicle_id = vehicle_id or self.vehicle_profiles.default().id publisher = self._mode_scene_catalog_publisher if publisher is not None and resolved_vehicle_id == publisher.vehicle_id: self._synchronize_mode_scene_catalog(self.mode_profiles) snapshot = publisher.snapshot() if snapshot is not None and self._mode_scene_catalog_status["state"] == "ready": return snapshot return build_mode_scene_catalog( self.mode_profiles, resolved_vehicle_id, server_id=( publisher.server_id if publisher is not None else DEFAULT_N7_SCENE_SERVER_ID ), name=(publisher.name if publisher is not None else DEFAULT_N7_SCENE_SERVER_NAME), ) def mode_scene_catalog_status(self) -> dict[str, Any]: return deepcopy(self._mode_scene_catalog_status) def mode_profile_sync_status(self) -> dict[str, Any]: """Return the lightweight state used by the browser's scene watcher.""" catalog = self.mode_scene_catalog_status() return { "catalog_revision": catalog.get("catalog_revision"), "scene_count": int(catalog.get("scene_count") or 0), "catalog_state": str(catalog.get("state") or "unknown"), "catalog_error": catalog.get("last_error"), "mode_scene_ingress": self._mode_scene_ingress.snapshot(), } def publish_saved_mode_scene( self, mode_id: str, vehicle_id: str | None = None, ) -> dict[str, Any]: """Queue the just-saved card from the canonical scene projection. Both a new user mode and a user override of a repository preset are projected by the same registry. Reading the card back from that projection guarantees that MQTT and ``/n7/scenes`` expose exactly the same post-save JSON object. """ clean_mode_id = str(mode_id).strip() if not clean_mode_id: raise ValueError("mode scene id must not be empty") resolved_vehicle_id = vehicle_id or self.vehicle_profiles.default().id catalog = self.mode_scene_catalog(resolved_vehicle_id) catalog_publisher = self._mode_scene_catalog_publisher if catalog_publisher is not None and ( catalog_publisher.vehicle_id != resolved_vehicle_id or self._mode_scene_catalog_status.get("state") != "ready" ): raise RuntimeError("mode scene catalog is not synchronized for this vehicle") scene = next( ( item for item in catalog.get("scenes", []) if isinstance(item, dict) and item.get("id") == clean_mode_id ), None, ) if scene is None: raise KeyError(clean_mode_id) return self._mode_scene_mqtt_publisher.queue(scene) def publish_created_mode_scene( self, mode_id: str, vehicle_id: str | None = None, ) -> dict[str, Any]: """Backward-compatible alias for callers predating update publishing.""" return self.publish_saved_mode_scene(mode_id, vehicle_id) @staticmethod def _imported_scene_function_configs( scene: dict[str, Any], ) -> dict[str, dict[str, Any]]: """Retain only local peripheral metadata needed for later authorization.""" extension_operations: list[dict[str, Any]] = [] for index, action in enumerate(scene.get("actions", [])): if ( not isinstance(action, dict) or action.get("domain") != "self_extension" or action.get("capability") != "infrared.emit" ): continue target = action.get("target") target_document = ( deepcopy(target) if isinstance(target, dict) else {"device": str(action.get("target_scope") or "")} ) extension_operations.append( { "id": str(action.get("operation_ref") or f"n7-ir-{index + 1}"), "domain": "self_extension", "capability": "infrared.emit", "target_scope": str(action.get("target_scope") or ""), "target": target_document, "operation": str(action.get("operation") or "emit"), "parameters": deepcopy(dict(action.get("parameters") or {})), "parse_source": "from_n7_scene", } ) if not extension_operations: return {} return { "self_extension": { "apiVersion": "cognitive-agent.io/v1alpha1", "kind": "FunctionConfig", "metadata": { "id": f"from-n7-{scene['id']}-self-extension", "name": f"{scene['name']} imported infrared actions", "labels": {"source_topic": MQTT_MODE_SCENE_INGRESS_TOPIC}, }, "spec": { "domain": "self_extension", "operations": extension_operations, }, } } def import_n7_mode_scene( self, scene: Mapping[str, Any], ) -> dict[str, Any]: """Upsert one validated N7 card without publishing it back to the broker.""" document = deepcopy(dict(scene)) actions = document.get("actions") if not isinstance(actions, list) or not actions: raise ValueError("from_n7/scenes card must contain executable actions") operations = [ { "domain": str(action["domain"]), "capability": str(action["capability"]), "target_scope": str(action["target_scope"]), "operation": str(action["operation"]), "parameters": deepcopy(dict(action["parameters"])), "reason": ( f"由 {MQTT_MODE_SCENE_INGRESS_TOPIC} 导入的模式动作。" ), } for action in actions ] timeline = None raw_timeline = document.get("timeline") if raw_timeline is not None: if not isinstance(raw_timeline, dict): raise ValueError("from_n7/scenes timeline must be an object") timeline = ModeTimeline.from_dict(raw_timeline) command_flow = { "schema": "cognitive-agent-mode-command-flow/v1", "source_topic": MQTT_MODE_SCENE_INGRESS_TOPIC, "imported_at": _now(), "source_scene": document, "function_configs": self._imported_scene_function_configs(document), # The execution path recompiles the canonical ModeOperation list # against this PC's capability registry. This marker makes the # card directly executable while avoiding trust in foreign CAN bytes. "plans": { "from_n7_scene": { "spec": { "commands": [ { "operation_ref": str( action.get("operation_ref") or f"action-{index + 1}" ), "at_ms": int(action.get("at_ms", 0)), } for index, action in enumerate(actions) ] } } }, "execution_steps": [], } scope = document.get("scope") or {} vehicle_ids = scope.get("vehicle_ids", []) if isinstance(scope, dict) else [] common = bool(scope.get("common", not vehicle_ids)) if isinstance(scope, dict) else False resolved_vehicle_id = ( None if common and not vehicle_ids else self.vehicle_profiles.default().id ) saved = self.mode_profiles.save_mode( str(document["name"]), operations, mode_id=str(document["id"]), aliases=[str(alias) for alias in document.get("aliases", [])], vehicle_id=resolved_vehicle_id, description=str(document.get("description") or "由 N7 同步的模式卡片。"), icon=str(document.get("icon") or "radio-tower"), instruction=f"进入{document['name']}", command_flow=command_flow, timeline=timeline, sync_origin=MQTT_MODE_SCENE_INGRESS_TOPIC, upstream_origin=str(document.get("origin") or ""), ) return { "status": "imported", "scene_id": saved.id, "scene_name": saved.name, "source_topic": MQTT_MODE_SCENE_INGRESS_TOPIC, "profile_revision": self.mode_profiles.profile_revision(saved), "storage": "persistent_yaml", } @property def driver_only_ch1_execution_active(self) -> bool: """Return whether deployment policy authorizes only the CH1 vehicle path.""" return bool( not self.hardware_settings.enabled and self.vehicle_can_settings.configured and self.vehicle_can_settings.channel_is_allowed and self.vehicle_can_settings.channel == 1 ) def _driver_only_vehicle_execution_active(self, transport: str) -> bool: if self.hardware_settings.enabled: return False if transport == WIRELESS_MQTT_TRANSPORT: return self.vehicle_mqtt_settings.configured return self.driver_only_ch1_execution_active @staticmethod def _validate_control_transport(transport: str) -> str: normalized = str(transport).strip().lower() if normalized not in VEHICLE_CONTROL_TRANSPORTS: raise ValueError( "control_transport must be wired_can or wireless_mqtt" ) return normalized def runtime_info(self) -> dict[str, Any]: domain_runtime_info = [ runtime.runtime_info() for runtime in self.domain_runtimes.values() ] default_vehicle = self.vehicle_profiles.default() hardware_info = self.hardware_settings.public_info() hardware_info.update(self.hardware_execution.public_info()) hardware_info.update(self.hardware_access.public_info()) vehicle_can_info = self.vehicle_can_settings.public_info() if self._vehicle_state_monitor is not None: state_sync_info = self._vehicle_state_monitor.public_info() if self.hardware_settings.configured: # Keep the slower legacy cadence when the same page also polls # the physical passenger-seat path through the shared adapter. state_sync_info["poll_interval_ms"] = 5_000 vehicle_can_info["state_sync"] = state_sync_info elif self.vehicle_can_settings.state_configured: vehicle_can_info["state_sync"] = { "mode": "on_demand_shared_adapter", "state": "ready", "read_only": True, "poll_interval_ms": 5_000, "snapshot_freshness_ms": 5_000, "capture_sequence": None, "observed_frame_ids": [], "last_error": None, } vehicle_mqtt_info = self._vehicle_mqtt_transport.public_info() vehicle_mqtt_info["mode_scene_import"] = self._mode_scene_ingress.snapshot() return { "service": "cabin-cognitive-orchestrator", "domain": "cabin", "platform": "reference_vehicle", "execution_mode": os.getenv( "COGNITIVE_AGENT_EXECUTION_MODE", "dry_run" ).strip(), "execution_modes": ["dry_run", "simulated_hardware", "hardware"], "hardware_execution_allowed": ( self.hardware_settings.configured or self.vehicle_can_settings.configured or self.vehicle_mqtt_settings.configured ), "hardware": hardware_info, "vehicle_can": vehicle_can_info, "vehicle_mqtt": vehicle_mqtt_info, "vehicle_transports": { "default": WIRED_CAN_TRANSPORT, "selection_scope": "browser_request", "options": [ { "id": WIRED_CAN_TRANSPORT, "label": "有线控制", **vehicle_can_info, "target_policy": ( "dual_channel_wired_can0_passenger_can1_vehicle" if self.hardware_settings.configured and self.vehicle_can_settings.configured else vehicle_can_info.get("target_policy") ), "channels": { "seat": 0, "vehicle": self.vehicle_can_settings.channel, }, }, { "id": WIRELESS_MQTT_TRANSPORT, "label": "无线控制", **vehicle_mqtt_info, }, ], }, "vehicle_safety_policy": { "required_channel": 1, "required_gear": "P", "maximum_speed_kmh": 0.05, "fresh_feedback_ids": ["0x342", "0x4EB"], "preflight": "read_only_before_every_physical_request", }, "multi_channel_can": { "adapter_ownership": "single_process", "seat_channel": ( self.hardware_settings.channel if self.hardware_settings.enabled else None ), "vehicle_channel": self.vehicle_can_settings.channel, "channels_must_be_distinct": bool( self.hardware_settings.enabled and self.vehicle_can_settings.enabled ), "active_policy": ( "dual_channel_wired_can0_passenger_can1_vehicle" if self.hardware_settings.configured and self.vehicle_can_settings.configured else "ch1_body_can_driver_only" if self.vehicle_can_settings.configured else "ch1_body_can_read_only" if self.vehicle_can_settings.state_configured else "ch0_passenger_only" if self.hardware_settings.configured else "virtual_only" ), "seat_roles": { "passenger": {"zone": "front_right", "channel": 0}, "driver": {"zone": "front_left", "channel": 1}, }, }, "seat_position_memory_synchronized": bool( self._seat_position_memory_state.get("synchronized", False) ), "simulated_hardware": { "enabled": self.simulated_hardware_enabled, "default_fault": "normal", "fault_scenarios": list(SIMULATED_HARDWARE_FAULTS), "physical_can_opened": False, }, "openai_available": self.openai.available, "openai_model": self.openai.model, "openai_models": self.openai.model_catalog, "openai_reasoning_effort_default": self.openai.reasoning_effort, "openai_reasoning_efforts": list(REASONING_EFFORTS), "openai_responses_endpoint": self.openai.responses_endpoint, "conversation_memory": True, "default_vehicle_id": default_vehicle.id, "vehicle": default_vehicle.public_dict(), "vehicles": self.vehicle_profiles.public_profiles(), "modes": self.mode_profiles.public_modes(default_vehicle.id), "scene_templates": self.scene_templates.public_templates(), "mode_profile_storage": "persistent_yaml", "mode_profile_user_root": str(self.mode_profile_user_root), "mode_scene_catalog": self.mode_scene_catalog_status(), "mode_scene_mqtt": self._mode_scene_mqtt_publisher.snapshot(), "preset_storage": "conversation_durable", "inferred_level_range": [1, 3], "motion_position_range_pct": [0, 100], "capability_domains": [ { "domain": item["domain"], "availability": item["availability"], } for item in self.capability_context["domains"] ], "capabilities": [ capability for domain in self.capability_context["domains"] for capability in domain["capabilities"] ], "matrix": { "messages": len(self.matrix.data["messages"]) + sum(item["matrix"]["messages"] for item in domain_runtime_info), "signals": sum(len(item["signals"]) for item in self.matrix.data["messages"]) + sum(item["matrix"]["signals"] for item in domain_runtime_info), "source": { "seat": self.matrix.data["source"], **{ item["domain"]: item["matrix"]["source"] for item in domain_runtime_info }, }, "limitations": self.matrix.data["limitations"] + [ limitation for item in domain_runtime_info for limitation in item["matrix"]["limitations"] ], }, "domain_runtimes": domain_runtime_info, } def delete_user_mode_profile( self, mode_id: str, vehicle_id: str | None = None, ) -> dict[str, Any]: resolved_vehicle_id = vehicle_id or self.vehicle_profiles.default().id profile = self.mode_profiles.delete_user_mode(mode_id, resolved_vehicle_id) return { "status": "succeeded", "deleted_mode_profile": profile.public_dict(), "vehicle_id": resolved_vehicle_id, } def get_mode_profile_card( self, mode_id: str, vehicle_id: str | None = None, ) -> ModeProfile: resolved_vehicle_id = vehicle_id or self.vehicle_profiles.default().id return self.mode_profiles.get_profile(mode_id, resolved_vehicle_id) @staticmethod def _fallback_scene_action( operation: Any, *, index: int, ) -> dict[str, Any]: return { "type": "control", "at_ms": 0, "domain": operation.domain, "capability": operation.capability, "target_scope": operation.target_scope, "operation": operation.operation, "parameters": deepcopy(operation.parameters), "operation_ref": f"action-{index + 1}", } def _mode_requests_and_n7_transfers( self, profile: ModeProfile, ) -> tuple[list[dict[str, Any]], list[dict[str, Any]]]: """Separate locally understood operations from N7-only capabilities.""" requests = self.mode_profiles.requests_for_profiles([profile]) if profile.sync_origin != MQTT_MODE_SCENE_INGRESS_TOPIC: return requests, [] source_scene = ( profile.command_flow.get("source_scene") if isinstance(profile.command_flow, dict) else None ) source_actions = ( source_scene.get("actions") if isinstance(source_scene, dict) and isinstance(source_scene.get("actions"), list) else [] ) local_requests: list[dict[str, Any]] = [] transfer_actions: list[dict[str, Any]] = [] for index, (operation, request) in enumerate( zip(profile.operations, requests, strict=True) ): if ( operation.domain == "self_extension" and operation.capability == "infrared.emit" ): # The server's durable NFC allowlist owns this local TCP path. # Authorization failures must not be converted into N7 retries. continue registered = self.model_plan_adapter.capabilities.get( operation.capability ) native_candidate = { "domain": operation.domain, "capability": operation.capability, "target": operation.target_scope, "parameters": deepcopy(operation.parameters), "operation": operation.operation, } if registered is not None or self.native_can_runtime.supports( native_candidate ): local_requests.append(request) continue raw_action = ( source_actions[index] if index < len(source_actions) and isinstance(source_actions[index], dict) else None ) action = ( deepcopy(raw_action) if raw_action is not None else self._fallback_scene_action(operation, index=index) ) transfer_actions.append(action) return local_requests, transfer_actions @staticmethod def _has_local_mode_peripheral(profile: ModeProfile) -> bool: return any( operation.domain == "self_extension" and operation.capability == "infrared.emit" for operation in profile.operations ) @staticmethod def _n7_transfer_deferred_requests( actions: list[dict[str, Any]], ) -> list[dict[str, Any]]: return [ { "domain": str(action.get("domain") or ""), "capability": str(action.get("capability") or ""), "target_scope": str(action.get("target_scope") or ""), "desired_change": str(action.get("operation") or "set"), "parameters": deepcopy(dict(action.get("parameters") or {})), "reason": "当前 PC 未注册该能力,将由 N7 端继续处理。", "execution_backend": "n7_vehicle_transfer", "rejection_code": "PC_CAPABILITY_UNAVAILABLE", "transport_submitted": False, } for action in actions ] def _finalize_n7_control_transfer( self, result: dict[str, Any], *, execution_mode: str, local_preview: bool, ) -> dict[str, Any]: """Forward only N7-originated actions never submitted by this PC.""" actions = result.get("n7_transfer_candidates") scene = result.get("n7_source_scene") if ( execution_mode != "hardware" or local_preview or not isinstance(actions, list) or not actions or not isinstance(scene, dict) ): return result if str(result.get("status") or "") not in { "succeeded", "partially_succeeded", "capability_unavailable", }: return result if result.get("execution_outcome") == "failed_or_blocked": return result request_id = str(result.get("request_id") or "").strip() scene_id = str(scene.get("id") or "").strip() scene_name = str(scene.get("name") or "").strip() if not request_id or not scene_id or not scene_name: return result transfer_actions = [deepcopy(action) for action in actions if isinstance(action, dict)] if not transfer_actions: return result payload = { "scene_id": scene_id, "scene_name": scene_name, "request_id": request_id, "operation_count": len(transfer_actions), "actions": transfer_actions, } digest = json.dumps( payload, ensure_ascii=False, sort_keys=True, separators=(",", ":"), ) publication_key = f"{request_id}:{hashlib.sha256(digest.encode('utf-8')).hexdigest()}" now = time.monotonic() with self._n7_transfer_lock: cutoff = now - 300.0 self._n7_transfer_publications = { key: published_at for key, published_at in self._n7_transfer_publications.items() if published_at >= cutoff } if publication_key in self._n7_transfer_publications: result["n7_control_transfer"] = { "state": "duplicate_ignored", "topic": MQTT_N7_CONTROL_TRANSFER_TOPIC, "scene_id": scene_id, "operation_count": len(transfer_actions), } return result try: publication = self._vehicle_mqtt_transport.publish_n7_control_transfer( payload ) except Exception as exc: # noqa: BLE001 - local execution result remains valid result["n7_control_transfer"] = { "state": "publish_failed", "topic": MQTT_N7_CONTROL_TRANSFER_TOPIC, "scene_id": scene_id, "operation_count": len(transfer_actions), "last_error": str(exc)[:240], } return result with self._n7_transfer_lock: self._n7_transfer_publications[publication_key] = now result["n7_control_transfer"] = { "state": "published", "scene_id": scene_id, "operation_count": len(transfer_actions), "payload": payload, **dict(publication), } result["decision_summary"] = [ *list(result.get("decision_summary") or []), ( f"当前 PC 未注册的 {len(transfer_actions)} 条 N7 来源动作已发送至 " f"{MQTT_N7_CONTROL_TRANSFER_TOPIC};PC 已提交或被安全门禁拦截的动作未转发。" ), ] return result def execute_mode_profile_card( self, mode_id: str, *, conversation_id: str | None = None, execution_mode: str = "dry_run", approval_token: str | None = None, vehicle_id: str | None = None, simulation_fault: str = "normal", hardware_client_id: str | None = None, hardware_access_bypass: bool = True, control_transport: str = WIRED_CAN_TRANSPORT, ) -> dict[str, Any]: """Execute a persisted mode by ID without interpreting natural language.""" resolved_vehicle_id = vehicle_id or self.vehicle_profiles.default().id profile = self.mode_profiles.get_profile(mode_id, resolved_vehicle_id) if profile.schedule is None and (not profile.command_flow or not profile.command_flow.get("plans")): raise ValueError("该模式尚未生成可直接执行的控制指令流。") result = self.process( profile.instruction or f"进入{profile.name}", provider="local", conversation_id=conversation_id, execution_mode=execution_mode, approval_token=approval_token, vehicle_id=resolved_vehicle_id, simulation_fault=simulation_fault, hardware_client_id=hardware_client_id, hardware_access_bypass=hardware_access_bypass, control_transport=control_transport, _direct_mode_profile_id=profile.id, ) if profile.schedule is not None: return result public_profile = profile.public_dict() result["provider_used"] = "saved_mode_command_flow" result["utterance"] = profile.name result["matched_modes"] = [public_profile] result["mode_profile"] = public_profile result["orchestration_route"] = "saved_mode_command_flow" result["decision_summary"] = [ f"直接载入已保存的模式“{profile.name}”,未再次解析自然语言。", *list(result.get("decision_summary") or []), ] self._persist(result) return result def save_mode_profile_card( self, mode_id: str, name: str, instruction: str, source_request_id: str, vehicle_id: str | None = None, ) -> dict[str, Any]: """Persist one edited mode from a server-generated control flow.""" return self._persist_mode_profile_card( mode_id, name, instruction, source_request_id, vehicle_id, ) def create_mode_profile_card( self, name: str, instruction: str, source_request_id: str, vehicle_id: str | None = None, ) -> dict[str, Any]: """Create one user mode from a server-generated control flow.""" return self._persist_mode_profile_card( None, name, instruction, source_request_id, vehicle_id, ) def trial_mode_profile_draft( self, name: str, instruction: str, source_request_id: str, vehicle_id: str | None = None, ) -> dict[str, Any]: """Replay the server-owned dry-run evidence for one assistant draft.""" clean_name = str(name).strip() clean_instruction = str(instruction).strip() clean_request_id = str(source_request_id).strip() resolved_vehicle_id = vehicle_id or self.vehicle_profiles.default().id if not clean_name or not clean_instruction: raise ValueError("功能模式草稿名称和指令不能为空。") if not MODE_EDITOR_REQUEST_ID_PATTERN.fullmatch(clean_request_id): raise ValueError("功能模式草稿来源请求编号无效。") result_path = self.artifact_root / clean_request_id / "result.json" if not result_path.is_file(): raise ValueError("未找到功能模式草稿,请重新生成。") try: source_result = json.loads(result_path.read_text(encoding="utf-8")) except (OSError, json.JSONDecodeError) as exc: raise ValueError("功能模式草稿结果无法读取,请重新生成。") from exc if not isinstance(source_result, dict) or source_result.get( "request_id" ) != clean_request_id: raise ValueError("功能模式草稿来源结果不完整,请重新生成。") if ( source_result.get("status") != "succeeded" or source_result.get("execution_mode") != "dry_run" ): raise ValueError("功能模式草稿没有通过完整的模拟校验。") draft = source_result.get("mode_profile_draft") if not isinstance(draft, dict): raise ValueError("来源请求不是可试运行的功能模式草稿。") if str(draft.get("source_request_id") or "").strip() != clean_request_id: raise ValueError("功能模式草稿来源请求编号不匹配,请重新生成。") if str(draft.get("name") or "").strip() != clean_name: raise ValueError("功能模式草稿名称不匹配,请重新生成。") if str(draft.get("instruction") or "").strip() != clean_instruction: raise ValueError("功能模式草稿指令不匹配,请重新生成。") if str(draft.get("vehicle_id") or "").strip() != resolved_vehicle_id: raise ValueError("功能模式草稿不属于当前车型。") intent_ir = source_result.get("intent_ir") spec = intent_ir.get("spec") if isinstance(intent_ir, dict) else None actions = spec.get("actions") if isinstance(spec, dict) else None function_configs = source_result.get("function_configs") operation_count = draft.get("operation_count") if ( not isinstance(actions, list) or not actions or not isinstance(function_configs, dict) or not function_configs or not isinstance(operation_count, int) or isinstance(operation_count, bool) or len(actions) != operation_count ): raise ValueError("功能模式草稿动作证据不完整,请重新生成。") validation = source_result.get("validation", []) validation = validation if isinstance(validation, list) else [] if any( isinstance(issue, dict) and issue.get("severity") == "error" for issue in validation ): raise ValueError("功能模式草稿未通过安全校验,请重新生成。") trial = { "request_id": clean_request_id, "status": "succeeded", "execution_mode": "dry_run", "hardware_execution_allowed": False, "orchestration_route": "mode_profile_draft_trial", "mode_profile_draft": deepcopy(draft), "intent_ir": {"spec": {"actions": deepcopy(actions)}}, "function_configs": deepcopy(function_configs), "execution_report": deepcopy(source_result.get("execution_report")), "validation": deepcopy(validation), "trial_source": "server_validated_preview_artifact", } schedule = self._validated_schedule_artifact(source_result) if schedule is not None: trial["schedule"] = schedule.to_dict() trial["schedule_execution"] = self.mode_schedule_runner.simulate( schedule, lambda phase: {"status": "succeeded", "phase_id": phase.id, "operations": deepcopy(list(phase.operations))}, ) return trial def _persist_mode_profile_card( self, mode_id: str | None, name: str, instruction: str, source_request_id: str, vehicle_id: str | None = None, ) -> dict[str, Any]: """Validate and persist a created or edited mode command flow.""" resolved_vehicle_id = vehicle_id or self.vehicle_profiles.default().id clean_mode_id = str(mode_id or "").strip() clean_name = str(name).strip() clean_instruction = str(instruction).strip() clean_request_id = str(source_request_id).strip() if not clean_name: raise ValueError("模式名称不能为空。") if len(clean_name) > 64: raise ValueError("模式名称不能超过 64 个字符。") if not clean_instruction: raise ValueError("自然语言指令不能为空。") if len(clean_instruction) > 1000: raise ValueError("自然语言指令不能超过 1000 个字符。") if not MODE_EDITOR_REQUEST_ID_PATTERN.fullmatch(clean_request_id): raise ValueError("控制指令流来源请求编号无效。") visible_profiles = self.mode_profiles._profiles_for_vehicle( # noqa: SLF001 resolved_vehicle_id ) profile = ( next( (item for item in visible_profiles if item.id == clean_mode_id), None, ) if clean_mode_id else None ) if clean_mode_id and profile is None: raise KeyError(clean_mode_id) normalized_name = re.sub(r"\s+", "", clean_name).casefold() if any( (profile is None or item.id != profile.id) and re.sub(r"\s+", "", item.name).casefold() == normalized_name for item in visible_profiles ): raise ValueError("该模式名称已被其他模式使用。") result_path = self.artifact_root / clean_request_id / "result.json" if not result_path.is_file(): raise ValueError("未找到“生成指令”按钮产生的控制指令流,请重新生成。") try: source_result = json.loads(result_path.read_text(encoding="utf-8")) except (OSError, json.JSONDecodeError) as exc: raise ValueError("控制指令流来源结果无法读取,请重新生成。") from exc if not isinstance(source_result, dict) or source_result.get( "request_id" ) != clean_request_id: raise ValueError("控制指令流来源结果不完整,请重新生成。") if source_result.get("status") != "succeeded": raise ValueError("控制指令流没有完整通过校验,请重新生成。") source_vehicle = source_result.get("vehicle") source_vehicle_id = ( str(source_vehicle.get("id") or "").strip() if isinstance(source_vehicle, dict) else "" ) if source_vehicle_id and source_vehicle_id != resolved_vehicle_id: raise ValueError("控制指令流不属于当前车型,请重新生成。") source_draft = source_result.get("mode_profile_draft") draft_existing_mode_revision = "" if isinstance(source_draft, dict): draft_request_id = str(source_draft.get("source_request_id") or "").strip() draft_vehicle_id = str(source_draft.get("vehicle_id") or "").strip() draft_instruction = str(source_draft.get("instruction") or "").strip() draft_name = str(source_draft.get("name") or "").strip() draft_existing_mode_id = str( source_draft.get("existing_mode_id") or "" ).strip() draft_existing_mode_revision = str( source_draft.get("existing_mode_revision") or "" ).strip() if draft_request_id != clean_request_id: raise ValueError("功能模式草稿与控制指令流不匹配,请重新生成。") if draft_vehicle_id and draft_vehicle_id != resolved_vehicle_id: raise ValueError("功能模式草稿不属于当前车型,请重新生成。") if draft_instruction and draft_instruction != clean_instruction: raise ValueError("自然语言指令已修改,请重新生成模式预览。") if draft_name and draft_name != clean_name: raise ValueError("功能模式名称已修改,请重新生成模式预览。") if clean_mode_id and not draft_existing_mode_id: raise ValueError("新功能模式预览不能通过已有模式的更新接口保存。") if draft_existing_mode_id and draft_existing_mode_id != clean_mode_id: raise ValueError("该预览属于已有功能模式,请通过对应模式的更新接口保存。") if draft_existing_mode_id and not draft_existing_mode_revision: raise ValueError("功能模式预览版本过旧,请重新生成后再更新。") if ( draft_existing_mode_revision and profile is not None and draft_existing_mode_revision != self.mode_profiles.profile_revision(profile) ): raise ValueError("功能模式已被其他会话更新,请重新生成预览后再保存。") recorded_instruction = str(source_result.get("utterance", "")).strip() if not recorded_instruction: source_spans = { str(item.get("source_span", "")).strip() for item in ( (source_result.get("intent_ir") or {}).get("spec", {}).get( "requests", [] ) ) if isinstance(item, dict) and str(item.get("source_span", "")).strip() } if len(source_spans) == 1: recorded_instruction = next(iter(source_spans)) if recorded_instruction and recorded_instruction != clean_instruction: raise ValueError("自然语言指令已修改,请重新点击“生成指令”。") plans = source_result.get("command_plans") or {} configs = source_result.get("function_configs") or {} if not isinstance(plans, dict) or not isinstance(configs, dict): raise ValueError("解析结果中没有可永久化的控制指令流。") executable_plan_keys = { key for key, plan in plans.items() if isinstance(plan, dict) and isinstance(plan.get("spec"), dict) and isinstance(plan["spec"].get("commands"), list) and plan["spec"]["commands"] } if not executable_plan_keys: raise ValueError("请先点击“生成指令”并成功生成控制指令流。") operations: list[dict[str, Any]] = [] signatures: set[tuple[str, str, str, str]] = set() for key in plans: if key not in executable_plan_keys: continue config = configs.get(key) if not isinstance(config, dict): continue spec = config.get("spec") or {} domain = str(spec.get("domain", "")).strip() for operation in spec.get("operations", []): if not isinstance(operation, dict): continue capability = str(operation.get("capability", "")).strip() operation_target = operation.get("target") or {} target = str( operation_target.get("instance") or operation_target.get("zone") or "" ).strip() parameters = operation.get("parameters") or {} if not domain or not capability or not target or not isinstance( parameters, dict ): continue signature = ( domain, capability, target, json.dumps(parameters, ensure_ascii=False, sort_keys=True), ) if signature in signatures: continue signatures.add(signature) operations.append( { "domain": domain, "capability": capability, "target_scope": target, "operation": "set", "parameters": deepcopy(parameters), "reason": f"由模式编辑器请求 {clean_request_id} 固化。", } ) if not operations: raise ValueError("控制指令流没有关联到可复用的模式操作。") command_flow = { "schema": "cognitive-agent-mode-command-flow/v1", "source_request_id": clean_request_id, "generated_at": _now(), "execution_mode": source_result.get("execution_mode"), "control_transport": source_result.get("control_transport"), "function_configs": { key: deepcopy(configs[key]) for key in plans if key in executable_plan_keys and key in configs }, "plans": {key: deepcopy(plans[key]) for key in plans if key in executable_plan_keys}, # A mode card stores only the validated, deterministic plan. Live # delivery evidence belongs to each later execution, not to the # generation workflow. "execution_steps": [], } schedule = self._validated_schedule_artifact(source_result) if profile is not None and profile.schedule is not None and schedule is None: raise ValueError("动态模式更新必须保留完整时间轴,请按阶段重新生成预览。") if schedule is not None: operations = [deepcopy(op) for phase in schedule.phases for op in phase.operations] saved = self.mode_profiles.save_mode( clean_name, operations, mode_id=profile.id if profile is not None else None, aliases=list(profile.aliases) if profile is not None else [], vehicle_id=resolved_vehicle_id, description=( profile.description if profile is not None else "由自然语言生成的自定义模式。" ), icon=profile.icon if profile is not None else "bookmark-plus", instruction=clean_instruction, command_flow=command_flow, timeline=profile.timeline if profile is not None else None, sync_origin=profile.sync_origin if profile is not None else "", upstream_origin=( profile.upstream_origin if profile is not None else "" ), schedule=schedule, expected_revision=draft_existing_mode_revision or None, create_only=not bool(clean_mode_id), ) public_profile = next( ( item for item in self.mode_profiles.public_modes(resolved_vehicle_id) if item["id"] == saved.id ), saved.public_dict(), ) return { "status": "succeeded", "mode_profile": public_profile, "vehicle_id": resolved_vehicle_id, "storage": "persistent_yaml", "source_request_id": clean_request_id, } def synchronize_hardware_state( self, conversation_id: str, vehicle_id: str | None = None, *, control_transport: str = WIRED_CAN_TRANSPORT, hardware_client_id: str | None = None, hardware_access_bypass: bool = True, ) -> dict[str, Any]: """Read the bench only when no physical execution currently owns it.""" control_transport = self._validate_control_transport(control_transport) mqtt_selected = control_transport == WIRELESS_MQTT_TRANSPORT if hardware_access_bypass: # Wireless CAN0 is a thread-safe view over the persistent MQTT # subscription. HardwareSeatExecutor still serializes this seat # read with seat actuation through its adapter lock, while an # absent CAN0 stream must not block the independent cached CAN1 # dashboard or make control requests fail with hardware_busy. if mqtt_selected: return self._synchronize_hardware_state_once( conversation_id, vehicle_id, control_transport=control_transport, ) with self.hardware_execution.reserve(): return self._synchronize_hardware_state_once( conversation_id, vehicle_id, control_transport=control_transport, ) authorized_client_id = self.hardware_access.ensure_authorized(hardware_client_id) if mqtt_selected: with self.hardware_access.use(authorized_client_id): return self._synchronize_hardware_state_once( conversation_id, vehicle_id, control_transport=control_transport, ) with self.hardware_execution.reserve(): with self.hardware_access.use(authorized_client_id): return self._synchronize_hardware_state_once( conversation_id, vehicle_id, control_transport=control_transport, ) def _synchronize_hardware_state_once( self, conversation_id: str, vehicle_id: str | None = None, *, control_transport: str = WIRED_CAN_TRANSPORT, ) -> dict[str, Any]: """Read the bench and project it into the current passenger conversation.""" if not CONVERSATION_ID_PATTERN.fullmatch(conversation_id): raise ValueError( "conversation_id must start with conv- and contain only lowercase letters, numbers, or hyphens" ) control_transport = self._validate_control_transport(control_transport) mqtt_selected = control_transport == WIRELESS_MQTT_TRANSPORT wired_state_bridge = None if mqtt_selected: if not self.vehicle_mqtt_settings.state_configured: raise HardwareSafetyError("the MQTT CAN0 seat relay is not configured") bus_factory = lambda: self._vehicle_mqtt_transport.bus( channel=0, replay_latest=True, ) else: if not self.hardware_settings.configured: raise HardwareSafetyError( "the passenger-seat CAN0 hardware runtime is not configured" ) if ( self.hardware_settings.native_passenger_can0 and self.vehicle_can_settings.adapter_configured ): wired_state_bridge = self.vehicle_can_settings.bridge( [ ZlgChannelConfig( channel=0, frame_format="classic_can", arbitration_bitrate=self.hardware_settings.bitrate, data_bitrate=self.vehicle_can_settings.data_bitrate, can_fd_iso=True, termination_enabled=( self.hardware_settings.termination_enabled ), ) ], monitor_only=True, ) bus_factory = lambda: wired_state_bridge.channel_bus(0) else: bus_factory = None vehicle_profile = self.vehicle_profiles.get(vehicle_id) try: physical_state = self._new_hardware_executor( bus_factory, remote_transport=mqtt_selected, ).read_physical_state() finally: if wired_state_bridge is not None: wired_state_bridge.close() if mqtt_selected: physical_state["source"] = "wireless_mqtt_ch0" context = self.conversations.synchronize_physical_passenger( conversation_id, physical_state, ) context = self._context_for_vehicle(context, vehicle_profile) return { "status": "succeeded", "conversation_id": conversation_id, "conversation_turn": context["turn_count"], "source": "wireless_mqtt_ch0" if mqtt_selected else "physical_can", "control_transport": control_transport, "captured_at": physical_state.get("captured_at"), "complete": bool(physical_state.get("complete")), "missing": list(physical_state.get("missing", [])), "physical_state": physical_state, "actuator_state": context["devices"], } def synchronize_vehicle_state( self, conversation_id: str, vehicle_id: str | None = None, *, control_transport: str = WIRED_CAN_TRANSPORT, hardware_client_id: str | None = None, hardware_access_bypass: bool = True, ) -> dict[str, Any]: """Passively receive CH1 driver-seat feedback without transmitting CAN.""" control_transport = self._validate_control_transport(control_transport) mqtt_selected = control_transport == WIRELESS_MQTT_TRANSPORT if hardware_access_bypass: # The MQTT snapshot is already decoded and protected by the # transport condition lock. It is independent from physical # execution ownership and should remain readable during CAN0 # waits or an in-flight command. if mqtt_selected: return self._synchronize_vehicle_state_once( conversation_id, vehicle_id, control_transport=control_transport, ) with self.hardware_execution.reserve(): return self._synchronize_vehicle_state_once( conversation_id, vehicle_id, control_transport=control_transport, ) authorized_client_id = self.hardware_access.ensure_authorized(hardware_client_id) if mqtt_selected: with self.hardware_access.use(authorized_client_id): return self._synchronize_vehicle_state_once( conversation_id, vehicle_id, control_transport=control_transport, ) with self.hardware_execution.reserve(): with self.hardware_access.use(authorized_client_id): return self._synchronize_vehicle_state_once( conversation_id, vehicle_id, control_transport=control_transport, ) def start(self) -> None: if self._vehicle_state_monitor is not None: self._vehicle_state_monitor.start() self._mode_scene_mqtt_publisher.start() self._mode_scene_ingress.start() self._vehicle_mqtt_transport.start() def close(self) -> None: self.mode_schedule_runner.close() if self._vehicle_state_monitor is not None: self._vehicle_state_monitor.close() self._mode_scene_mqtt_publisher.close() self._vehicle_mqtt_transport.close() self._mode_scene_ingress.close() def publish_pet_message(self, payload: dict[str, str]) -> dict[str, Any]: """Publish one NFC-authorized pet card through the shared vehicle broker.""" return self._vehicle_mqtt_transport.publish_pet_message(payload) def publish_extension_toy_message( self, payload: dict[str, Any], ) -> dict[str, Any]: """Publish one NFC-detected extension capability announcement.""" return self._vehicle_mqtt_transport.publish_extension_toy_message(payload) def publish_firmware_notification( self, payload: Mapping[str, Any], ) -> dict[str, Any]: """Publish one firmware release notification through the vehicle broker.""" return self._vehicle_mqtt_transport.publish_firmware_notification(payload) def set_extension_control_handler( self, handler: Callable[[bytes], None] | None, ) -> None: """Route ``extension/ctl`` payloads from the shared broker connection.""" self._vehicle_mqtt_transport.set_extension_control_handler(handler) def set_extension_control_rejection_handler( self, handler: Callable[[str, str], None] | None, ) -> None: """Project broker-level ``extension/ctl`` rejections into the UI status.""" self._vehicle_mqtt_transport.set_extension_control_rejection_handler(handler) def _synchronize_vehicle_state_once( self, conversation_id: str, vehicle_id: str | None = None, *, control_transport: str = WIRED_CAN_TRANSPORT, ) -> dict[str, Any]: if not CONVERSATION_ID_PATTERN.fullmatch(conversation_id): raise ValueError( "conversation_id must start with conv- and contain only lowercase letters, numbers, or hyphens" ) control_transport = self._validate_control_transport(control_transport) mqtt_selected = control_transport == WIRELESS_MQTT_TRANSPORT if mqtt_selected: if not self.vehicle_mqtt_settings.state_configured: raise HardwareSafetyError("the MQTT vehicle CAN relay is not configured") elif not self.vehicle_can_settings.state_configured: raise HardwareSafetyError("the CH1 vehicle CAN runtime is not configured") vehicle_profile = self.vehicle_profiles.get(vehicle_id) shared_runtime_enabled = ( self.vehicle_can_settings.backend != "zlgcan-linux" and self.vehicle_can_settings.configured and os.getenv( "COGNITIVE_AGENT_SHARED_CAN_RUNTIME_ENABLED", os.getenv( "COGNITIVE_AGENT_SEAT_SHARED_ZCANPRO_BRIDGE_ENABLED", "true" ), ).strip().lower() in {"1", "true", "yes", "on"} ) bridge = None capture_sequence: int | None = None if mqtt_selected: snapshot = self._vehicle_mqtt_transport.snapshot() feedback_states = list(snapshot.feedback_states) observed_ids = set(snapshot.observed_ids) captured_at = snapshot.captured_at capture_sequence = snapshot.capture_sequence elif self._vehicle_state_monitor is not None: snapshot = self._vehicle_state_monitor.snapshot() feedback_states = list(snapshot.feedback_states) observed_ids = set(snapshot.observed_ids) captured_at = snapshot.captured_at capture_sequence = snapshot.capture_sequence elif shared_runtime_enabled: from cognitive_agent.application.self_repair.service import BRIDGE_MANAGER if not hasattr(BRIDGE_MANAGER, "channel_bus_factory"): raise HardwareSafetyError( "CH1 feedback requires the standalone multi-channel CAN runtime" ) bus = BRIDGE_MANAGER.channel_bus_factory( self.vehicle_can_settings.channel )() else: bridge = self.vehicle_can_settings.bridge( [self.vehicle_can_settings.channel_config()], monitor_only=True, ) bus = bridge.channel_bus(self.vehicle_can_settings.channel) if not mqtt_selected and self._vehicle_state_monitor is None: feedback_states = [] observed_ids = set() captured_at = None # Keep driver-seat completeness compatible with the existing endpoint, # while passively collecting every recognized CH1 body-state frame for # the whole-vehicle dashboard during the same bounded receive window. deadline = ( time.monotonic() + self.vehicle_can_settings.feedback_timeout_ms / 1000 ) try: while time.monotonic() < deadline and observed_ids != OBSERVABLE_IDS: remaining_ms = max(1, int((deadline - time.monotonic()) * 1000)) frame = bus.receive(remaining_ms, include_fd=True) if frame is None: break if frame.channel != self.vehicle_can_settings.channel: continue if frame.arbitration_id not in OBSERVABLE_IDS or len(frame.data) < 8: continue decoded = decode_feedback(frame) if not any( key in decoded for key in ("actuator_state", "domain_state", "vehicle_safety") ): # 0x4EB is multiplexed. Only subtype 0x07 carries the # verified speed signal, so unrelated subframes must not # make the body-state snapshot look complete. continue frame_id = str(decoded.get("frame_id", "")) decoded.update( { "valid": True, "feedback_frame_id": frame_id, "captured_at": _now(), "verification": { "authoritative": True, "channel": 1, "simulated": False, "read_only": True, "frame_id": frame_id, "payload_size": len(frame.data), }, } ) feedback_states.append(decoded) observed_ids.add(frame.arbitration_id) captured_at = str(decoded["captured_at"]) finally: if bridge is not None: bridge.close() else: bus.close() context = self.conversations.synchronize_physical_ch1_feedback( conversation_id, feedback_states, ) context = self._context_for_vehicle(context, vehicle_profile) missing = [ f"0x{frame_id:03X}" for frame_id in sorted(REQUIRED_SEAT_IDS - observed_ids) ] body_state_missing = [ f"0x{frame_id:03X}" for frame_id in sorted(REQUIRED_BODY_IDS - observed_ids) ] return { "status": "succeeded", "conversation_id": conversation_id, "conversation_turn": context["turn_count"], "source": ( "wireless_mqtt_ch1" if mqtt_selected else "physical_can_ch1" ), "control_transport": control_transport, "read_only": True, "captured_at": captured_at or _now(), "capture_sequence": capture_sequence, "complete": not missing, "missing": missing, "body_state_complete": not body_state_missing, "body_state_missing": body_state_missing, "observed_frame_ids": [f"0x{frame_id:03X}" for frame_id in sorted(observed_ids)], "actuator_state": context["devices"], "domain_state": context["domain_devices"], } def _new_hardware_executor( self, bus_factory: Any | None = None, *, remote_transport: bool = False, ) -> HardwareSeatExecutor | MqttNativeSeatExecutor | ZcanBridgeSeatExecutor: shared_bridge_enabled = os.getenv( "COGNITIVE_AGENT_SHARED_CAN_RUNTIME_ENABLED", os.getenv("COGNITIVE_AGENT_SEAT_SHARED_ZCANPRO_BRIDGE_ENABLED", "true"), ).strip().lower() in {"1", "true", "yes", "on"} if ( bus_factory is None and shared_bridge_enabled and self.hardware_settings.backend != "zlgcan-linux" ): # Import lazily so the ordinary dry-run service does not initialize # any self-repair runtime state. Both modules then share the one # process that is allowed to own the Windows ZLG adapter. from cognitive_agent.application.self_repair.service import ( BRIDGE_MANAGER, PENDING_DIR, REQUIRED_BRIDGE_VERSION, STATUS_PATH, ) manager = BRIDGE_MANAGER manager_snapshot = manager.snapshot() if ( manager_snapshot.get("backend") == "standalone_zlg" or Path(str(manager_snapshot.get("zcan_exe") or "")).is_file() ): return ZcanBridgeSeatExecutor( self.hardware_matrix, manager, STATUS_PATH, PENDING_DIR, required_version=REQUIRED_BRIDGE_VERSION, auto_home=self.hardware_settings.auto_home_enabled, ) resolved_bus_factory = bus_factory or self.hardware_settings.bus_factory() executor = HardwareSeatExecutor( self.hardware_matrix, resolved_bus_factory, thermal_timeout_ms=self.hardware_settings.thermal_timeout_ms, ack_timeout_ms=self.hardware_settings.ack_timeout_ms, axis_maximum_x100={ capability: int( self.hardware_compiler.protocol["normalized_position_max"] ) for capability in self.hardware_compiler.axes }, motor_max_hall={ int(axis["motor_selector"]): int(axis["default_max_hall_count"]) for axis in self.hardware_compiler.axes.values() }, stop_all_motor_selector=int( self.hardware_compiler.protocol["stop_all_motor_selector"] ), auto_home=( self.hardware_settings.auto_home_enabled if not remote_transport else False ), home_timeout_s=self.hardware_settings.home_timeout_s, firmware_guard=( None if remote_transport or self.hardware_settings.native_passenger_can0 else self.hardware_settings.firmware_guard( resolved_bus_factory, allow_programming=not remote_transport, ) ), synchronize_position_before_first_motion=( self.hardware_settings.position_sync_enabled if not remote_transport else False ), position_sync_state=self._seat_position_memory_state, require_thermal_feedback=not remote_transport, feedback_required=not remote_transport, ) if remote_transport: return MqttNativeSeatExecutor(executor, resolved_bus_factory) return executor def _new_linux_wired_dual_channel_bridge(self) -> ZlgCanBridgeProcess: """Open CAN0 passenger classic CAN and CAN1 body CAN FD under one owner.""" if not self.hardware_settings.native_passenger_can0: raise HardwareSafetyError( "the shared Linux dual-channel bridge requires native passenger CAN0" ) if not self.vehicle_can_settings.configured: raise HardwareSafetyError( "the shared Linux dual-channel bridge requires configured vehicle CAN1" ) if ( self.hardware_settings.device_type != self.vehicle_can_settings.device_type or self.hardware_settings.device_index != self.vehicle_can_settings.device_index or self.hardware_settings.zlg_dll.resolve() != self.vehicle_can_settings.zlg_dll.resolve() ): raise HardwareSafetyError( "CAN0 and CAN1 must use the same Linux ZLG device and SDK library" ) return self.vehicle_can_settings.bridge( [ ZlgChannelConfig( channel=0, frame_format="classic_can", arbitration_bitrate=self.hardware_settings.bitrate, data_bitrate=self.vehicle_can_settings.data_bitrate, can_fd_iso=True, termination_enabled=( self.hardware_settings.termination_enabled ), ), self.vehicle_can_settings.channel_config(), ] ) def preview_native_can( self, utterance: str, provider: str = "auto", conversation_id: str | None = None, execution_mode: str = "dry_run", approval_token: str | None = None, vehicle_id: str | None = None, simulation_fault: str = "normal", *, hardware_client_id: str | None = None, hardware_access_bypass: bool = True, reasoning_effort: str | None = None, control_transport: str = WIRED_CAN_TRANSPORT, on_commands_validated: Callable[[dict[str, Any]], None] | None = None, assistant_plan: dict[str, Any] | None = None, mode_profile_draft_context: dict[str, Any] | None = None, _cloud_instruction_context: dict[str, Any] | None = None, ) -> dict[str, Any]: """Validate and execute deterministic controls before hosted parsing.""" local_stage_started = time.perf_counter() utterance = utterance.strip() if not utterance: raise ValueError("utterance must not be empty") if provider not in {"auto", "openai", "local"}: raise ValueError("provider must be auto, openai, or local") conversation_id = conversation_id or f"conv-{uuid.uuid4().hex}" if not CONVERSATION_ID_PATTERN.fullmatch(conversation_id): raise ValueError( "conversation_id must start with conv- and contain only lowercase letters, numbers, or hyphens" ) vehicle_profile = self.vehicle_profiles.get(vehicle_id) control_plane_request = self.is_mode_profile_configuration_request(utterance) if control_plane_request: result = self.process( utterance, provider=provider, conversation_id=conversation_id, execution_mode=execution_mode, approval_token=approval_token, vehicle_id=vehicle_profile.id, simulation_fault=simulation_fault, hardware_client_id=hardware_client_id, hardware_access_bypass=hardware_access_bypass, reasoning_effort=reasoning_effort, control_transport=control_transport, _on_commands_validated=on_commands_validated, _assistant_plan=assistant_plan, _mode_profile_draft_context=mode_profile_draft_context, _cloud_instruction_context=_cloud_instruction_context, ) result.update( { "phase": "control_plane_complete", "preview_request_id": result["request_id"], "unresolved_clauses": list( result.get("unresolved_clauses") or [] ), "supplement_required": False, } ) self._persist(result) return result context = self._context_for_vehicle( self.conversations.snapshot(conversation_id), vehicle_profile, ) clause_results = self._local_clause_results(utterance, context) locally_parsed_clauses = [ clause for clause, _, status in clause_results if status != "unparsed" ] locally_resolved_clauses = [ clause for clause, _, status in clause_results if status == "complete" ] locally_unresolved_clauses = [ clause for clause, _, status in clause_results if status != "complete" ] supplied_routing = ( _cloud_instruction_context if isinstance(_cloud_instruction_context, dict) else {} ) def routing_clauses(key: str) -> list[str]: raw = supplied_routing.get(key) if not isinstance(raw, list): return [] return [ text for item in raw[:64] for text in [str(item).strip()[:1000]] if text ] def unique_clauses(*groups: list[str]) -> list[str]: return list(dict.fromkeys(item for group in groups for item in group)) parsed_clauses = unique_clauses( routing_clauses("parsed_clauses"), locally_parsed_clauses, ) resolved_clauses = unique_clauses( routing_clauses("resolved_clauses"), locally_resolved_clauses, ) unresolved_clauses = locally_unresolved_clauses routing_unresolved_clauses = unique_clauses( routing_clauses("unresolved_clauses"), locally_unresolved_clauses, ) original_utterance = str( supplied_routing.get("original_utterance") or utterance ).strip()[:1000] natural_language_routing = { "original_utterance": original_utterance, "parsed_clauses": parsed_clauses, "resolved_clauses": resolved_clauses, "unresolved_clauses": routing_unresolved_clauses, } local_dispatch_elapsed_ms: float | None = None def publish_local_commands(validated: dict[str, Any]) -> None: nonlocal local_dispatch_elapsed_ms local_dispatch_elapsed_ms = round( (time.perf_counter() - local_stage_started) * 1000, 3, ) if on_commands_validated is None: return event = deepcopy(validated) event.update( { "phase": "deterministic_commands_validated", "provider_requested": provider, "unresolved_clauses": unresolved_clauses, "supplement_required": bool( provider != "local" and unresolved_clauses ), "natural_language_routing": deepcopy( natural_language_routing ), "local_control_budget_ms": LOCAL_CONTROL_BUDGET_MS, "local_control_elapsed_ms": local_dispatch_elapsed_ms, "local_control_within_budget": ( local_dispatch_elapsed_ms < LOCAL_CONTROL_BUDGET_MS ), } ) on_commands_validated(event) result = self.process( utterance, provider="local", conversation_id=conversation_id, execution_mode=execution_mode, approval_token=approval_token, vehicle_id=vehicle_profile.id, simulation_fault=simulation_fault, hardware_client_id=hardware_client_id, hardware_access_bypass=hardware_access_bypass, reasoning_effort=reasoning_effort, control_transport=control_transport, _local_preview=True, _allow_empty_control_result=True, _on_commands_validated=publish_local_commands, _assistant_plan=assistant_plan, _mode_profile_draft_context=mode_profile_draft_context, _cloud_instruction_context=natural_language_routing, ) elapsed_ms = round( (time.perf_counter() - local_stage_started) * 1000, 3, ) local_control_elapsed_ms = ( local_dispatch_elapsed_ms if local_dispatch_elapsed_ms is not None else elapsed_ms ) supplement_required = bool( provider != "local" and unresolved_clauses ) result.update( { "phase": "deterministic_preview", "provider_requested": provider, "preview_request_id": result["request_id"], "preview_elapsed_ms": elapsed_ms, "local_control_budget_ms": LOCAL_CONTROL_BUDGET_MS, "local_control_elapsed_ms": local_control_elapsed_ms, "local_control_within_budget": ( local_control_elapsed_ms < LOCAL_CONTROL_BUDGET_MS ), "parsed_clauses": parsed_clauses, "resolved_clauses": resolved_clauses, "unresolved_clauses": unresolved_clauses, "natural_language_routing": deepcopy( natural_language_routing ), "supplement_required": supplement_required, } ) result["decision_summary"] = [ *result.get("decision_summary", []), ( "本地规则阶段已完成白名单校验并先行处理 " f"{sum(len((config.get('spec') or {}).get('operations', [])) for config in result.get('function_configs', {}).values())} 条 CAN 控制指令。" ), ] self._persist(result) manifest = { "version": 1, "preview_request_id": result["request_id"], "utterance": utterance, "provider": provider, "conversation_id": conversation_id, "execution_mode": execution_mode, "vehicle_id": vehicle_profile.id, "simulation_fault": simulation_fault, "hardware_client_id": hardware_client_id, "reasoning_effort": reasoning_effort, "control_transport": control_transport, "parsed_clauses": parsed_clauses, "resolved_clauses": resolved_clauses, "unresolved_clauses": unresolved_clauses, "natural_language_routing": natural_language_routing, "preview_control_signatures": result.get( "parsed_control_signatures", [] ), } self._atomic_write( self.artifact_root / result["request_id"] / "preview.json", json.dumps(manifest, ensure_ascii=False, indent=2) + "\n", ) return result def supplement_native_can( self, preview_request_id: str, *, provider: str | None = None, approval_token: str | None = None, hardware_client_id: str | None = None, hardware_access_bypass: bool = True, reasoning_effort: str | None = None, on_commands_validated: Callable[[dict[str, Any]], None] | None = None, ) -> dict[str, Any]: """Parse only unresolved clauses and merge them with the local result.""" if not MODE_EDITOR_REQUEST_ID_PATTERN.fullmatch(preview_request_id): raise ValueError("invalid preview request id") preview_dir = self.artifact_root / preview_request_id try: manifest = json.loads( (preview_dir / "preview.json").read_text(encoding="utf-8") ) preview = json.loads( (preview_dir / "result.json").read_text(encoding="utf-8") ) except (OSError, json.JSONDecodeError) as exc: raise ValueError("native CAN preview not found") from exc if ( not isinstance(manifest, dict) or manifest.get("version") != 1 or manifest.get("preview_request_id") != preview_request_id or not isinstance(preview, dict) or preview.get("request_id") != preview_request_id ): raise ValueError("invalid native CAN preview manifest") selected_provider = str(manifest.get("provider") or "auto") if provider is not None and provider != selected_provider: raise ValueError("supplement provider does not match preview") selected_effort = manifest.get("reasoning_effort") if reasoning_effort is not None and reasoning_effort != selected_effort: raise ValueError("supplement reasoning effort does not match preview") expected_client_id = manifest.get("hardware_client_id") if hardware_client_id != expected_client_id: raise ValueError("supplement hardware client does not match preview") unresolved_clauses = [ str(item).strip() for item in manifest.get("unresolved_clauses", []) if str(item).strip() ] if not unresolved_clauses: raise ValueError("native CAN preview has no unresolved clauses") excluded_signatures = { tuple(item) for item in manifest.get("preview_control_signatures", []) if isinstance(item, list) } manifest_routing = manifest.get("natural_language_routing") natural_language_routing = ( deepcopy(manifest_routing) if isinstance(manifest_routing, dict) else { "original_utterance": str(manifest.get("utterance") or ""), "parsed_clauses": list(manifest.get("parsed_clauses") or []), "resolved_clauses": list(manifest.get("resolved_clauses") or []), "unresolved_clauses": unresolved_clauses, } ) routing_unresolved = natural_language_routing.get("unresolved_clauses") natural_language_routing["unresolved_clauses"] = list( dict.fromkeys( [ str(item).strip() for item in ( routing_unresolved if isinstance(routing_unresolved, list) else [] ) if str(item).strip() ] + unresolved_clauses ) ) cloud_utterance = ",".join(unresolved_clauses) natural_language_routing["cloud_input_utterance"] = cloud_utterance def publish_cloud_commands(validated: dict[str, Any]) -> None: if on_commands_validated is None: return event = self._merge_native_can_phases(preview, validated) event.update( { "phase": "supplement_commands_validated", "preview_request_id": preview_request_id, "supplement_of": preview_request_id, "supplement_required": False, } ) on_commands_validated(event) supplement = self.process( cloud_utterance, provider=selected_provider, conversation_id=str(manifest["conversation_id"]), execution_mode=str(manifest.get("execution_mode") or "dry_run"), approval_token=approval_token, vehicle_id=str(manifest["vehicle_id"]), simulation_fault=str(manifest.get("simulation_fault") or "normal"), hardware_client_id=hardware_client_id, hardware_access_bypass=hardware_access_bypass, reasoning_effort=( str(selected_effort) if selected_effort is not None else None ), control_transport=str( manifest.get("control_transport") or WIRED_CAN_TRANSPORT ), _excluded_control_signatures=excluded_signatures, _model_parse_source="cloud_supplement", _allow_empty_control_result=True, _on_commands_validated=publish_cloud_commands, _cloud_instruction_context=natural_language_routing, ) merged = self._merge_native_can_phases(preview, supplement) merged.update( { "phase": "two_stage_complete", "utterance": str( preview.get("utterance") if preview.get("orchestration_route") == "unified_can_infrared" else manifest["utterance"] ), "provider_requested": selected_provider, "preview_request_id": preview_request_id, "supplement_of": preview_request_id, "supplement_request_id": supplement["request_id"], "supplement_required": False, "unresolved_clauses": unresolved_clauses, "natural_language_routing": natural_language_routing, } ) self._persist(merged) return merged @staticmethod def _merge_native_can_phases( preview: dict[str, Any], supplement: dict[str, Any], ) -> dict[str, Any]: """Create one durable result without collapsing same-domain phases.""" merged = deepcopy(supplement) def phase_documents( local_documents: dict[str, Any] | None, cloud_documents: dict[str, Any] | None, ) -> dict[str, Any]: return { **{ f"local_{key}": deepcopy(value) for key, value in (local_documents or {}).items() }, **{ f"cloud_{key}": deepcopy(value) for key, value in (cloud_documents or {}).items() }, } configs = phase_documents( preview.get("function_configs"), supplement.get("function_configs"), ) plans = phase_documents( preview.get("command_plans"), supplement.get("command_plans"), ) merged["function_configs"] = configs merged["command_plans"] = plans merged["function_config"] = next(iter(configs.values()), None) merged["command_plan"] = next(iter(plans.values()), None) merged["decision_summary"] = [ *preview.get("decision_summary", []), *supplement.get("decision_summary", []), ] merged["trace_events"] = [ *preview.get("trace_events", []), *supplement.get("trace_events", []), ] merged["validation"] = [ *preview.get("validation", []), *supplement.get("validation", []), ] merged["parsed_control_signatures"] = [ *preview.get("parsed_control_signatures", []), *supplement.get("parsed_control_signatures", []), ] merged["phase_execution_reports"] = { "local_rule": deepcopy(preview.get("execution_report")), "cloud_supplement": deepcopy(supplement.get("execution_report")), **deepcopy(preview.get("phase_execution_reports") or {}), } merged["operation_results"] = [ *deepcopy(preview.get("operation_results") or []), *deepcopy(supplement.get("operation_results") or []), ] if preview.get("orchestration_route") == "unified_can_infrared": merged["orchestration_route"] = "unified_can_infrared" for key in ( "infrared_actions", "peripheral", "control_transports", ): merged[key] = deepcopy(preview.get(key)) merged["provider_used"] = ( f"{supplement.get('provider_used') or 'cloud_supplement'}" "+infrared_local_rules" ) if supplement.get("execution_report") is None: merged["execution_report"] = deepcopy(preview.get("execution_report")) successful = {"succeeded", "partially_succeeded", "no_action"} preview_ok = preview.get("status") in successful supplement_ok = supplement.get("status") in successful has_any_commands = bool(configs and plans) if preview_ok and supplement_ok: merged["status"] = "succeeded" if has_any_commands else "no_action" elif preview_ok and preview.get("function_configs"): merged["status"] = "partially_succeeded" merged["local_preview"] = { "request_id": preview.get("request_id"), "elapsed_ms": preview.get("preview_elapsed_ms"), "command_count": sum( len((config.get("spec") or {}).get("operations", [])) for config in preview.get("function_configs", {}).values() ), } return merged def process_can_commands( self, commands: object, conversation_id: str | None = None, execution_mode: str = "dry_run", approval_token: str | None = None, vehicle_id: str | None = None, simulation_fault: str = "normal", *, hardware_client_id: str | None = None, hardware_access_bypass: bool = True, control_transport: str = WIRED_CAN_TRANSPORT, ) -> dict[str, Any]: """Compile and execute an exact UI command batch without intent parsing.""" resolved_conversation_id = conversation_id or f"conv-{uuid.uuid4().hex}" if not CONVERSATION_ID_PATTERN.fullmatch(resolved_conversation_id): raise ValueError( "conversation_id must start with conv- and contain only lowercase letters, numbers, or hyphens" ) vehicle_profile = self.vehicle_profiles.get(vehicle_id) context = self._context_for_vehicle( self.conversations.snapshot(resolved_conversation_id), vehicle_profile, ) direct_can_batch = build_direct_can_intent(commands, context) normalized_commands, _parsed = direct_can_batch return self.process( ";".join(normalized_commands), provider="local", conversation_id=resolved_conversation_id, execution_mode=execution_mode, approval_token=approval_token, vehicle_id=vehicle_id, simulation_fault=simulation_fault, hardware_client_id=hardware_client_id, hardware_access_bypass=hardware_access_bypass, control_transport=control_transport, _direct_can_batch=direct_can_batch, ) def _adapt_speech_understanding( self, understanding: dict[str, Any], context: dict[str, Any], vehicle_profile: Any, ) -> dict[str, Any]: """Revalidate a server-owned speech result into the normal control IR. Speech is allowed to skip a second natural-language parse, but it never skips the authoritative capability catalog or ModelPlanAdapter. Native CAN actions and configured-domain/seat actions are routed through the same structures used by the rest of one-sentence orchestration. """ if understanding.get("execution_eligible") is not True: raise ValueError("speech understanding is not execution eligible") commands = understanding.get("commands") if not isinstance(commands, list) or not commands: raise ValueError("speech understanding has no executable commands") native_actions: list[dict[str, Any]] = [] model_requests: list[dict[str, Any]] = [] detected_domains: list[str] = [] def add_payload(payload: object, source_text: str) -> None: if not isinstance(payload, dict): raise ValueError("speech command has no validated executor payload") domain = str(payload.get("domain", "")) capability = str(payload.get("capability", "")) target = str(payload.get("target", "")) operation = str(payload.get("operation", "set")) parameters = payload.get("parameters") if not domain or not capability or not target or not isinstance(parameters, dict): raise ValueError("speech executor payload is incomplete") definitions = tuple( definition for definition in self.capability_registry.find(capability) if definition.domain == domain ) if len(definitions) != 1: raise ValueError( f"speech capability is not uniquely registered: {domain}.{capability}" ) if domain not in detected_domains: detected_domains.append(domain) # Catalog availability describes where a capability is declared, # not necessarily which executor owns it. Roof, climate and # ambient capabilities are declared in domain catalogs but some # are byte-exact NativeCanRuntime operations. Keep this routing in # lockstep with CapabilityResolver._executor_binding(). if capability in self.native_can_runtime.supported_capabilities: native_actions.append( { "domain": domain, "capability": capability, "target": target, "operation": operation, "parameters": deepcopy(parameters), "parse_source": "speech_semantic", "source_span": source_text, "reason": "语音语义层已按真实能力目录解析并由服务端重新校验。", } ) return model_requests.append( { "domain": domain, "capability": capability, "target_scope": target, "operation": operation, "parameters": [ {"name": name, "value": value} for name, value in parameters.items() ], "reason": "语音语义层已按真实能力目录解析并由服务端重新校验。", } ) for command in commands: if not isinstance(command, dict): raise ValueError("speech command must be an object") if ( command.get("status") != "active" or command.get("negated") is True or command.get("resolution_status") != "executable" or command.get("validation_issues") ): raise ValueError("speech command failed execution invariants") source_text = str(command.get("source_text") or understanding.get("raw_text") or "") payload = command.get("executor_payload") if command.get("mode_profile_id"): if not isinstance(payload, dict) or not isinstance(payload.get("operations"), list): raise ValueError("speech mode command has no validated operations") for operation_payload in payload["operations"]: add_payload(operation_payload, source_text) else: add_payload(payload, source_text) plan = { "detected_domains": detected_domains, "requests": model_requests, "ambiguities": [], "decision_summary": [ "复用服务端语音理解结果;未对 cleanText 进行第二次自然语言解析。" ], "confidence": min( float(command.get("confidence", 0.0)) for command in commands if isinstance(command, dict) ), } parsed = self.model_plan_adapter.adapt( plan, context=context, vehicle_profile=vehicle_profile, utterance=str(understanding.get("raw_text") or ""), ) parsed["native_can_actions"] = native_actions parsed.setdefault("semantic_constraints", []) parsed.setdefault("unresolved_spans", []) if parsed.get("ambiguities") or parsed.get("deferred_requests"): raise ValueError("speech command no longer passes capability resolution") mapped_count = ( len(parsed.get("actions", ())) + len(parsed.get("domain_actions", ())) + len(parsed.get("native_can_actions", ())) ) expected_count = len(model_requests) + len(native_actions) if mapped_count != expected_count: raise ValueError("speech command mapping lost a validated capability mutation") return parsed def process( self, utterance: str, provider: str = "auto", conversation_id: str | None = None, execution_mode: str = "dry_run", approval_token: str | None = None, vehicle_id: str | None = None, simulation_fault: str = "normal", *, hardware_client_id: str | None = None, hardware_access_bypass: bool = True, reasoning_effort: str | None = None, control_transport: str = WIRED_CAN_TRANSPORT, _local_preview: bool = False, _excluded_control_signatures: set[tuple[Any, ...]] | None = None, _model_parse_source: str = "cloud_model", _allow_empty_control_result: bool = False, _on_commands_validated: Callable[[dict[str, Any]], None] | None = None, _direct_can_batch: tuple[list[str], dict[str, Any]] | None = None, _direct_mode_profile_id: str | None = None, _speech_understanding: dict[str, Any] | None = None, _assistant_plan: dict[str, Any] | None = None, _mode_profile_draft_context: dict[str, Any] | None = None, _cloud_instruction_context: dict[str, Any] | None = None, ) -> dict[str, Any]: control_transport = self._validate_control_transport(control_transport) # Creating a reusable scene is a configuration workflow. Synthesize # and validate it in dry-run even when the console currently targets # the real bench, otherwise virtual domains such as climate or ambient # light would be rejected before the profile can be persisted. draft_reference_name = ( str(_mode_profile_draft_context.get("name") or "").strip() if isinstance(_mode_profile_draft_context, dict) else "" ) draft_update = parse_mode_profile_update( utterance.strip(), expected_mode_name=draft_reference_name or None, ) mode_delete_name = parse_mode_profile_delete(utterance.strip()) mode_profile_query = is_mode_profile_query( utterance, expected_mode_name=draft_reference_name or None, ) if ( self._parse_mode_profile_create_name(utterance.strip()) is not None or draft_update is not None or mode_delete_name is not None or mode_profile_query ): execution_mode = "dry_run" schedule_requested = "模式" in utterance or _direct_mode_profile_id or is_schedule_stop(utterance) schedule_vehicle_id = vehicle_id or (self.vehicle_profiles.default().id if schedule_requested else "") if execution_mode not in {"dry_run", "simulated_hardware", "hardware"}: raise ValueError(f"unsupported execution mode: {execution_mode}") if is_schedule_stop(utterance): return self._cancel_mode_schedule(schedule_vehicle_id, conversation_id, utterance, execution_mode) schedule_profile = (self.mode_profiles.get_profile(_direct_mode_profile_id, schedule_vehicle_id) if _direct_mode_profile_id else None) if schedule_requested and schedule_profile is None and not (draft_update or mode_delete_name or mode_profile_query or parse_mode_profile_create(utterance)): candidates = self.mode_profiles.match_text(utterance, vehicle_id=schedule_vehicle_id) if len(candidates) == 1 and candidates[0].schedule is not None: aliases = "|".join(re.escape(name) for name in (candidates[0].name, *candidates[0].aliases)) if re.fullmatch( r"(?:(?:请|麻烦|帮我|帮忙|给我|我想|我要|想要)\s*){0,4}" r"(?:(?:主驾|副驾|驾驶位|副驾驶|主驾驶)(?:座椅)?\s*)?" r"(?:进入|启动|执行|开启|启用|运行|打开|调用|应用|使用|切换到)\s*" rf"(?:{aliases})(?:吧|一下)?[。!!\s]*", utterance.strip(), ): schedule_profile = candidates[0] if schedule_profile is not None and schedule_profile.schedule is not None: return self._run_mode_schedule( schedule_profile, utterance=utterance, conversation_id=conversation_id, execution_mode=execution_mode, approval_token=approval_token, vehicle_id=schedule_vehicle_id, simulation_fault=simulation_fault, hardware_client_id=hardware_client_id, hardware_access_bypass=hardware_access_bypass, control_transport=control_transport, ) if execution_mode != "hardware": return self._process_once( utterance, provider, conversation_id, execution_mode, approval_token, vehicle_id, simulation_fault, reasoning_effort, control_transport, _local_preview, _excluded_control_signatures, _model_parse_source, _allow_empty_control_result, _on_commands_validated, _direct_can_batch, _direct_mode_profile_id, _speech_understanding, assistant_plan=_assistant_plan, mode_profile_draft_context=_mode_profile_draft_context, cloud_instruction_context=_cloud_instruction_context, ) if control_transport == WIRELESS_MQTT_TRANSPORT: # The head unit owns both physical CAN channels in wireless mode. # Local T2G/ZLG hardware access, firmware profiles and the local # physical-bench lease are unrelated to MQTT relay execution. result = self._process_once( utterance, provider, conversation_id, execution_mode, approval_token, vehicle_id, simulation_fault, reasoning_effort, control_transport, _local_preview, _excluded_control_signatures, _model_parse_source, _allow_empty_control_result, _on_commands_validated, _direct_can_batch, _direct_mode_profile_id, _speech_understanding, assistant_plan=_assistant_plan, mode_profile_draft_context=_mode_profile_draft_context, cloud_instruction_context=_cloud_instruction_context, ) return self._finalize_n7_control_transfer( result, execution_mode=execution_mode, local_preview=_local_preview, ) if hardware_access_bypass or self._driver_only_vehicle_execution_active( control_transport ): with self.hardware_execution.reserve(): result = self._process_once( utterance, provider, conversation_id, execution_mode, approval_token, vehicle_id, simulation_fault, reasoning_effort, control_transport, _local_preview, _excluded_control_signatures, _model_parse_source, _allow_empty_control_result, _on_commands_validated, _direct_can_batch, _direct_mode_profile_id, _speech_understanding, assistant_plan=_assistant_plan, mode_profile_draft_context=_mode_profile_draft_context, cloud_instruction_context=_cloud_instruction_context, ) return self._finalize_n7_control_transfer( result, execution_mode=execution_mode, local_preview=_local_preview, ) authorized_client_id = self.hardware_access.ensure_authorized(hardware_client_id) with self.hardware_execution.reserve(): with self.hardware_access.use(authorized_client_id): result = self._process_once( utterance, provider, conversation_id, execution_mode, approval_token, vehicle_id, simulation_fault, reasoning_effort, control_transport, _local_preview, _excluded_control_signatures, _model_parse_source, _allow_empty_control_result, _on_commands_validated, _direct_can_batch, _direct_mode_profile_id, _speech_understanding, assistant_plan=_assistant_plan, mode_profile_draft_context=_mode_profile_draft_context, cloud_instruction_context=_cloud_instruction_context, ) return self._finalize_n7_control_transfer( result, execution_mode=execution_mode, local_preview=_local_preview, ) def _process_once( self, utterance: str, provider: str = "auto", conversation_id: str | None = None, execution_mode: str = "dry_run", approval_token: str | None = None, vehicle_id: str | None = None, simulation_fault: str = "normal", reasoning_effort: str | None = None, control_transport: str = WIRED_CAN_TRANSPORT, local_preview: bool = False, excluded_control_signatures: set[tuple[Any, ...]] | None = None, model_parse_source: str = "cloud_model", allow_empty_control_result: bool = False, on_commands_validated: Callable[[dict[str, Any]], None] | None = None, direct_can_batch: tuple[list[str], dict[str, Any]] | None = None, direct_mode_profile_id: str | None = None, speech_understanding: dict[str, Any] | None = None, assistant_plan: dict[str, Any] | None = None, allow_fuzzy_mode_profile_match: bool = True, allow_scene_composition: bool = False, scene_scope_utterance: str | None = None, mode_creation_scopes: frozenset[str] | None = None, mode_profile_draft_context: dict[str, Any] | None = None, cloud_instruction_context: dict[str, Any] | None = None, ) -> dict[str, Any]: control_transport = self._validate_control_transport(control_transport) utterance = utterance.strip() if not utterance: raise ValueError("utterance must not be empty") if len(utterance) > 1000: raise ValueError("utterance must be at most 1000 characters") if provider not in {"auto", "openai", "local"}: raise ValueError("provider must be auto, openai, or local") effective_reasoning_effort = ( self.openai.reasoning_effort if reasoning_effort is None else self.openai.validate_reasoning_effort(reasoning_effort) ) if execution_mode not in {"dry_run", "simulated_hardware", "hardware"}: raise ValueError( "execution_mode must be dry_run, simulated_hardware, or hardware" ) simulated_hardware_request = execution_mode == "simulated_hardware" if simulated_hardware_request and not self.simulated_hardware_enabled: raise HardwareSafetyError( "simulated hardware is disabled; set " "COGNITIVE_AGENT_SIMULATED_HARDWARE_ENABLED=true" ) if simulation_fault not in SIMULATED_HARDWARE_FAULTS: raise ValueError( "simulation_fault must be one of " + ", ".join(SIMULATED_HARDWARE_FAULTS) ) mode_save_name = self._parse_mode_profile_save_name(utterance) mode_create_name = self._parse_mode_profile_create_name(utterance) mode_restore_name = self._parse_mode_profile_restore_name(utterance) mode_delete_name = parse_mode_profile_delete(utterance) draft_reference_name = ( str(mode_profile_draft_context.get("name") or "").strip() if isinstance(mode_profile_draft_context, dict) else "" ) mode_update = parse_mode_profile_update( utterance, expected_mode_name=draft_reference_name or None, ) mode_profile_query = is_mode_profile_query( utterance, expected_mode_name=draft_reference_name or None, ) control_plane_request = ( mode_save_name is not None or mode_restore_name is not None or mode_delete_name is not None or mode_update is not None or mode_profile_query ) hardware_request = execution_mode == "hardware" if hardware_request and _has_mixed_driver_passenger_seat_scope(utterance): raise HardwareSafetyError( "hardware mode refuses a mixed CH1 driver/front-left and " "CH0 passenger/front-right seat request" ) conversation_id = conversation_id or f"conv-{uuid.uuid4().hex}" if not CONVERSATION_ID_PATTERN.fullmatch(conversation_id): raise ValueError("conversation_id must start with conv- and contain only lowercase letters, numbers, or hyphens") vehicle_profile = self.vehicle_profiles.get(vehicle_id) direct_mode_profile = ( self.mode_profiles.get_profile( direct_mode_profile_id, vehicle_profile.id, ) if direct_mode_profile_id else None ) if mode_create_name is not None: return self._process_mode_profile_create( utterance, provider, conversation_id, execution_mode, approval_token, vehicle_profile, simulation_fault, mode_create_name, control_transport, assistant_plan=assistant_plan, ) # A request id is also the opaque capability used to reference a # server-owned draft artifact. Keep the full 128 bits of randomness; # the previous six-hex suffix was guessable under multi-user traffic. request_id = ( f"req-{datetime.now(UTC).strftime('%Y%m%d%H%M%S')}-{uuid.uuid4().hex}" ) context = self._context_for_vehicle( self.conversations.snapshot(conversation_id), vehicle_profile, ) context["mode_profiles"] = self.mode_profiles.public_modes(vehicle_profile.id) matched_modes = ( [direct_mode_profile] if direct_mode_profile is not None else [] if ( direct_can_batch is not None or speech_understanding is not None or not allow_fuzzy_mode_profile_match ) else self.mode_profiles.match_text( utterance, vehicle_id=vehicle_profile.id, ) ) traces: list[dict[str, Any]] = [] result: dict[str, Any] = { "request_id": request_id, "utterance": utterance, "conversation_id": conversation_id, "conversation_turn": context["turn_count"], "status": "processing", "provider_requested": provider, "reasoning_effort_requested": effective_reasoning_effort, "reasoning_effort_used": None, "execution_mode": execution_mode, "control_transport": control_transport, "hardware_execution_allowed": ( hardware_request and not control_plane_request ), "simulated_hardware": simulated_hardware_request, "simulation_fault": simulation_fault if simulated_hardware_request else None, "vehicle": vehicle_profile.public_dict(), "matched_modes": [ profile.public_dict() for profile in matched_modes ], "mode_profile": None, "decision_summary": [], "deferred_requests": [], "model_requests": [], "intent_ir": None, "function_config": None, "function_configs": {}, "scene_config": None, "preset_config": None, "validation": [], "command_plan": None, "command_plans": {}, "execution_report": None, "actuator_state": context["devices"], "domain_state": context["domain_devices"], "saved_presets": [ {"name": item["name"], "target": item["target"]} for item in context.get("presets", []) ], "trace_events": traces, "can_commands": ( list(direct_can_batch[0]) if direct_can_batch is not None else [] ), "speech_understanding": ( { "utterance_id": speech_understanding.get("utterance_id"), "raw_text": speech_understanding.get("raw_text"), "clean_text": speech_understanding.get("clean_text"), "semantic_status": speech_understanding.get("semantic_status"), } if speech_understanding is not None else None ), } started = time.perf_counter() if mode_profile_query: result["status"] = "needs_confirmation" result["execution_mode"] = "dry_run" result["hardware_execution_allowed"] = False result["orchestration_route"] = "mode_profile_query" result["validation"] = [ { "severity": "warning", "code": "MODE_PROFILE_QUERY_NOT_CONTROL", "path": "utterance", "message": "该语句是在询问功能模式,不是模式修改或车辆执行指令。", } ] result["decision_summary"] = [ "识别为功能模式问答;未生成或执行任何车辆控制动作。" ] self._persist(result) return result if mode_delete_name is not None: return self._process_mode_profile_delete( utterance, request_id, conversation_id, mode_delete_name, vehicle_profile, result, traces, ) if mode_save_name is not None: return self._process_mode_profile_save( utterance, request_id, conversation_id, context, mode_save_name, vehicle_profile, result, traces, ) if mode_restore_name is not None: return self._process_mode_profile_restore( utterance, request_id, conversation_id, mode_restore_name, vehicle_profile, result, traces, ) if mode_update is not None: mode_name, change_text = mode_update if ( draft_reference_name and normalize_mode_profile_name(draft_reference_name) == normalize_mode_profile_name(mode_name) ): return self._process_mode_profile_draft_update( utterance, request_id, provider, conversation_id, context, mode_name, change_text, vehicle_profile, result, traces, mode_profile_draft_context, approval_token=approval_token, simulation_fault=simulation_fault, control_transport=control_transport, assistant_plan=assistant_plan, ) return self._process_mode_profile_update( utterance, request_id, provider, conversation_id, context, mode_name, change_text, vehicle_profile, result, traces, ) if direct_mode_profile is not None: mode_requests, n7_transfer_actions = ( self._mode_requests_and_n7_transfers(direct_mode_profile) ) if mode_creation_scopes is not None: mode_requests = project_seed_operations(mode_requests, mode_creation_scopes) detected_domains = list( dict.fromkeys( str(item.get("domain") or "") for item in mode_requests if str(item.get("domain") or "") ) ) parsed = self.model_plan_adapter.adapt( { "detected_domains": detected_domains or ["seat"], "requests": mode_requests, "ambiguities": [], "decision_summary": [ f"已载入模式“{direct_mode_profile.name}”保存的结构化操作。" ], "confidence": 1.0, }, context, vehicle_profile, ) saved_native_actions: list[dict[str, Any]] = [] for request in mode_requests: parameters = { str(item.get("name")): deepcopy(item.get("value")) for item in request.get("parameters", []) if isinstance(item, dict) and str(item.get("name") or "") } action = { "domain": str(request.get("domain") or ""), "capability": str(request.get("capability") or ""), "target": str(request.get("target_scope") or "vehicle"), "parameters": parameters, "operation": str(request.get("operation") or "set"), "reason": str(request.get("reason") or ""), "execution_backend": "native_can", "parse_source": "saved_mode_command_flow", } if self.native_can_runtime.supports(action): saved_native_actions.append(action) parsed["native_can_actions"] = [ *list(parsed.get("native_can_actions", [])), *saved_native_actions, ] if n7_transfer_actions: parsed["deferred_requests"] = [ *list(parsed.get("deferred_requests", [])), *self._n7_transfer_deferred_requests( n7_transfer_actions ), ] parsed["_n7_transfer_actions"] = n7_transfer_actions parsed["_n7_scene_profile"] = { "id": direct_mode_profile.id, "name": direct_mode_profile.name, } parsed["detected_domains"] = self._merge_domains( list(parsed.get("detected_domains", [])), [ str(action.get("domain") or "") for action in n7_transfer_actions if str(action.get("domain") or "") ], ) if self._has_local_mode_peripheral(direct_mode_profile): parsed["_saved_mode_local_peripheral"] = True provider_used = "saved_mode_command_flow" provider_notice = None elif direct_can_batch is not None: parsed = deepcopy(direct_can_batch[1]) provider_used = "direct_can_control" provider_notice = None elif speech_understanding is not None: parsed = self._adapt_speech_understanding( speech_understanding, context, vehicle_profile, ) parsed = self._mark_control_parse_source( parsed, "speech_semantic", overwrite=True, ) provider_used = "speech_semantic" provider_notice = "使用服务端缓存并重新校验的语音结构化意图。" else: parsed, provider_used, provider_notice = self._interpret( utterance, provider, context, vehicle_profile, reasoning_effort=reasoning_effort, local_preview=local_preview, model_parse_source=model_parse_source, assistant_plan=assistant_plan, allow_scene_composition=allow_scene_composition, scene_scope_utterance=scene_scope_utterance, cloud_instruction_context=cloud_instruction_context, ) scene_label_only = assistant_plan is not None and allow_scene_composition dynamic_mode_stop = ( not scene_label_only and self._is_dynamic_mode_stop_request(utterance) ) if dynamic_mode_stop: parsed = self._apply_dynamic_mode_stop_request(parsed, utterance) # Reuse the same conservative clause segmentation for the final # native pass. Without this normalization a Chinese enumeration # comma between independent controls can leak an early target # (for example ``副驾``) into a later HVAC clause and can also # hide otherwise valid window/temperature actions. # Name-only scene plans have already been checked against exact # targets by _interpret. Their synthesized 进入 label is not # another atomic instruction: reparsing 座椅舒适模式 here applies # the unscoped-seat driver default and discards valid passenger # actions. Explicit user instructions never enter this branch. segmented_utterance = "" if scene_label_only else ",".join( self._split_intent_clauses(utterance) ) relative_resolution = ( self.native_can_relative_resolver.resolve( segmented_utterance, context, ) if self.native_can_relative_resolver.matches( segmented_utterance ) else None ) native_merge_utterance = segmented_utterance if any( request.get("capability") in {SAVE_PRESET_CAPABILITY, APPLY_PRESET_CAPABILITY} for request in parsed.get("model_requests", []) ): # These registered capabilities currently have one executable # target (the passenger-seat posture store). Keep that # adapter-resolved target from being discarded by the native # parser's general unscoped-seat => driver default. The # original utterance is still used by the language safety # guards below. native_merge_utterance = f"副驾座椅,{utterance}" parsed = merge_native_can_intent( parsed, native_merge_utterance, relative_actions=( relative_resolution.actions if relative_resolution else None ), relative_ambiguities=( relative_resolution.ambiguities if relative_resolution else None ), relative_claimed_controls=( relative_resolution.claimed_controls if relative_resolution else None ), ) if not dynamic_mode_stop: # Apply repository-backed modes after the final native-CAN # merge. Otherwise that merge can discard a valid passenger # mode operation while enforcing its generic unscoped-seat # CH1 default. parsed = self._apply_mode_profiles( parsed, matched_modes, context, vehicle_profile, utterance=utterance, ) parsed = self._mark_control_parse_source(parsed, "local_rule") if direct_mode_profile is not None: parsed = self._mark_control_parse_source( parsed, "saved_mode_command_flow", ) if local_preview: # A preview may discard ordinary model-supplement questions, but # target and safety conflicts are execution gates. Keeping these # makes the assistant path no less strict than normal process(). parsed["ambiguities"] = [ item for item in parsed.get("ambiguities", []) if str(item.get("field", "")) in {"target", "target_scope", "mode_profile", "risk", "safety", "utterance"} or str(item.get("reason", "")).startswith( ("mode_target_", "safety_", "risk_", "mixed_") ) ] parsed["unresolved_spans"] = [ item for item in parsed.get("unresolved_spans", []) if str(item.get("reason", "")).startswith( ("mode_target_", "safety_", "risk_", "mixed_") ) ] parsed["model_requests"] = [] if excluded_control_signatures: parsed = self._filter_excluded_controls( parsed, excluded_control_signatures, ) parsed = apply_language_safety_guards( parsed, utterance, scene_composition=assistant_plan is not None and allow_scene_composition, scene_scope_utterance=scene_scope_utterance, ) parsed = self._partition_native_actions( parsed, promote_supported=( direct_mode_profile is not None or ( hardware_request and ( self.vehicle_mqtt_settings.enabled if control_transport == WIRELESS_MQTT_TRANSPORT else self.vehicle_can_settings.enabled ) ) ), ) n7_transfer_actions = parsed.get("_n7_transfer_actions", []) n7_scene_profile = parsed.get("_n7_scene_profile") if isinstance(n7_transfer_actions, list) and n7_transfer_actions: result["n7_transfer_candidates"] = deepcopy(n7_transfer_actions) if isinstance(n7_scene_profile, dict): result["n7_source_scene"] = deepcopy(n7_scene_profile) result["parsed_control_signatures"] = [ list(self._control_signature(action)) for action in [ *parsed.get("actions", []), *parsed.get("domain_actions", []), *parsed.get("native_can_actions", []), ] ] has_control = any( parsed.get(key) for key in ( "actions", "domain_actions", "native_can_actions", "preset_actions", ) ) if ( ( allow_empty_control_result or parsed.get("_saved_mode_local_peripheral") is True ) and not has_control and not parsed.get("ambiguities") ): result["provider_used"] = provider_used result["decision_summary"] = [ *parsed.get("decision_summary", []), "当前阶段没有新增的已验证 CAN 控制指令。", ] result["deferred_requests"] = parsed.get("deferred_requests", []) result["model_requests"] = parsed.get("model_requests", []) result["intent_ir"] = build_intent_ir( parsed, utterance, request_id, provider_used, self.semantic_capability_metadata, ) result["validation"] = self.contracts.validate(result["intent_ir"]) result["status"] = "no_action" self._trace( traces, request_id, "intent_parse", "succeeded", "当前阶段未产生新的白名单控制动作。", started, {"provider": provider_used, "control_count": 0}, domain="orchestration", ) self._persist(result) return result if hardware_request and not control_plane_request: if ( control_transport == WIRELESS_MQTT_TRANSPORT and parsed.get("domain_actions") and (parsed.get("actions") or parsed.get("native_can_actions")) ): parsed = self._defer_nonwhitelisted_wireless_actions(parsed) if parsed.get("domain_actions"): unsupported = sorted( { str(action.get("capability", "unknown")) for action in parsed["domain_actions"] } ) raise HardwareSafetyError( "hardware mode refuses virtual or unverified vehicle actions: " + ", ".join(unsupported) ) if ( not parsed.get("actions") and not parsed.get("native_can_actions") and not parsed.get("_n7_transfer_actions") ): raise HardwareSafetyError( "hardware mode requires at least one verified seat or vehicle CAN operation" ) if parsed.get("actions") and parsed.get("native_can_actions"): if ( control_transport == WIRED_CAN_TRANSPORT and self.hardware_settings.backend not in {"zlgcan", "zlgcan-linux"} ): raise HardwareSafetyError( "linked seat/vehicle execution requires a shared ZLG backend" ) if control_transport == WIRED_CAN_TRANSPORT: try: seat_channel = int(self.hardware_settings.channel, 0) except ValueError as exc: raise HardwareSafetyError( "ZLG seat CAN channel must be an integer" ) from exc if seat_channel == self.vehicle_can_settings.channel: raise HardwareSafetyError( "seat and vehicle CAN must use different adapter channels" ) self._trace( traces, request_id, "intent_parse", "succeeded", f"使用 {provider_used} 完成结构化意图抽取。", started, { "provider": provider_used, "confidence": parsed["confidence"], "conversation_turn": context["turn_count"], }, ) if provider_notice: parsed["decision_summary"].append(provider_notice) result["provider_used"] = provider_used if "openai:" in provider_used: result["reasoning_effort_used"] = effective_reasoning_effort result["decision_summary"] = parsed["decision_summary"] result["deferred_requests"] = parsed.get("deferred_requests", []) result["model_requests"] = parsed.get("model_requests", []) if ( parsed.get("semantic_constraints") and not parsed.get("actions") and not parsed.get("domain_actions") and not parsed.get("native_can_actions") and not parsed.get("ambiguities") and not parsed.get("unresolved_spans") ): intent_ir = build_intent_ir( parsed, utterance, request_id, provider_used, self.semantic_capability_metadata, ) result["intent_ir"] = intent_ir result["validation"] = self.contracts.validate(intent_ir) result["status"] = "no_action" self._trace( traces, request_id, "safety_check", "succeeded", "检测到禁止或保持约束;未生成、编译或发送任何 CAN 动作。", time.perf_counter(), {"constraint_count": len(intent_ir["spec"]["constraints"]), "execution_eligible": False}, domain="orchestration", ) self._persist(result) return result context_started = time.perf_counter() self._trace( traces, request_id, "context_resolve", "succeeded", ( f"已将会话 {conversation_id} 的第 {context['turn_count']} 轮执行状态" "、域能力注册表和长期模式配置作为模型上下文;" "本地层仅注入已命中的长期模式动作。" ), context_started, { "conversation_id": conversation_id, "turn_count": context["turn_count"], "last_target": context["last_target"], "state_before": context["devices"], "capability_domains": [ item["domain"] for item in self.capability_context["domains"] ], "matched_modes": [profile.id for profile in matched_modes], }, ) route_started = time.perf_counter() detected_domains = parsed.get("detected_domains", ["seat"]) seat_only = detected_domains == ["seat"] self._trace( traces, request_id, "domain_route", "succeeded", ( "仅识别到 seat 领域,使用已验证的座椅单域闭环。" if seat_only else ( f"识别到 {','.join(detected_domains)} 领域," "按领域边界生成独立 FunctionConfig。" ) ), route_started, { "domains": detected_domains, "mode": "single_domain" if len(detected_domains) == 1 else "cross_domain", }, ) if parsed.get("domain_actions") or parsed.get("native_can_actions") or any( domain != "seat" for domain in detected_domains ): return self._process_orchestrated( utterance, request_id, provider_used, conversation_id, context, parsed, result, traces, hardware_request=hardware_request, simulated_hardware_request=simulated_hardware_request, simulation_fault=simulation_fault, vehicle_profile=vehicle_profile, control_transport=control_transport, approval_token=approval_token, on_commands_validated=on_commands_validated, ) intent_ir = build_intent_ir( parsed, utterance, request_id, provider_used, self.semantic_capability_metadata, ) result["intent_ir"] = intent_ir generation_started = time.perf_counter() function_config = build_function_config(parsed, request_id, provider_used) preset_config = None if parsed.get("preset_actions"): preset_action = parsed["preset_actions"][0] preset_config = build_function_preset( preset_action["target"], preset_action["preset_name"], context["devices"][preset_action["target"]], request_id, provider_used, ) result["function_config"] = function_config result["function_configs"] = {"seat": function_config} if function_config else {} result["preset_config"] = preset_config generated_document = function_config or preset_config self._trace( traces, request_id, "function_generate", "succeeded" if generated_document else "waiting_confirmation", ( "已生成座椅 FunctionConfig。" if function_config else ( "已从当前姿态生成 FunctionPreset。" if preset_config else "没有足够信息生成可执行配置。" ) ), generation_started, { "operation_count": len(parsed["actions"]), "preset_operation_count": ( len(preset_config["spec"]["operations"]) if preset_config else 0 ), }, ) schema_started = time.perf_counter() validation = self.contracts.validate(intent_ir) if function_config is not None: validation.extend(self.contracts.validate(function_config)) if preset_config is not None: validation.extend(self.contracts.validate(preset_config)) schema_errors = [issue for issue in validation if issue["severity"] == "error"] self._trace( traces, request_id, "schema_validate", "failed" if schema_errors else "succeeded", f"公共与领域 Schema 校验{'发现错误' if schema_errors else '通过'}。", schema_started, {"error_count": len(schema_errors)}, ) semantic_started = time.perf_counter() semantic_issues = validate_semantics(parsed, function_config) validation.extend(semantic_issues) if hardware_request: self._adapt_validation_for_hardware(validation) semantic_errors = [issue for issue in semantic_issues if issue["severity"] == "error"] self._trace( traces, request_id, "domain_validate", "failed" if semantic_errors else "succeeded", "座椅语义校验阻止了不受支持的请求。" if semantic_errors else "座椅能力和目标映射校验通过。", semantic_started, {"error_count": len(semantic_errors), "warning_count": len(semantic_issues) - len(semantic_errors)}, ) result["validation"] = validation all_errors = [issue for issue in validation if issue["severity"] == "error"] if all_errors: if any(issue["code"] == "AMBIGUOUS_INTENT" for issue in all_errors): result["status"] = "needs_confirmation" else: result["status"] = "validation_failed" self._persist(result) return result if parsed.get("deferred_requests") and not parsed.get("actions"): result["status"] = "capability_unavailable" self._persist(result) return result if preset_config is not None and not parsed.get("actions"): preset_action = parsed["preset_actions"][0] compile_started = time.perf_counter() self._trace( traces, request_id, "compile", "succeeded", "FunctionPreset 属于控制平面数据,不生成 CAN 命令。", compile_started, { "preset_name": preset_action["preset_name"], "command_count": 0, }, ) safety_started = time.perf_counter() self._trace( traces, request_id, "safety_check", "succeeded", "预设仅保存已验证的五轴目标,不改变硬件发送边界。", safety_started, {"hardware_execution_allowed": False}, ) execute_started = time.perf_counter() execution_report = { "apiVersion": "cognitive-agent/v1alpha1", "kind": "ExecutionReport", "metadata": { "id": f"report-{request_id}", "name": "Seat posture preset save report", "version": "1.0.0", "request_id": request_id, "labels": {"control_plane": "preset_store"}, }, "spec": { "mode": "dry_run", "status": "succeeded", "started_at": _now(), "finished_at": _now(), "results": [ { "ref": f"save-{preset_action['target']}-posture-preset", "domain": "seat", "status": "succeeded", "message": ( f"已将 {preset_action['target']} 当前五轴姿态保存为" f"“{preset_action['preset_name']}”。" ), "telemetry": { "operation": "save_posture_preset", "target": preset_action["target"], "preset_name": preset_action["preset_name"], "captured_axis_count": len( preset_config["spec"]["operations"] ), "storage_scope": "conversation_durable", "can_command_emitted": False, }, } ], }, } report_issues = self.contracts.validate(execution_report) validation.extend(report_issues) result["execution_report"] = execution_report if report_issues: result["status"] = "execution_report_invalid" self._persist(result) return result updated_context = self.conversations.save_preset( conversation_id, utterance, preset_action["target"], preset_action["preset_name"], preset_config, parsed["decision_summary"], ) updated_context = self._context_for_vehicle( updated_context, vehicle_profile, ) result["status"] = "succeeded" result["conversation_turn"] = updated_context["turn_count"] result["actuator_state"] = updated_context["devices"] result["saved_presets"] = [ {"name": item["name"], "target": item["target"]} for item in updated_context["presets"] ] self._trace( traces, request_id, "execute", "succeeded", "当前姿态已写入会话预设仓库;未生成 CAN 帧。", execute_started, { "mode": "control_plane", "preset_name": preset_action["preset_name"], }, ) self._trace( traces, request_id, "state_update", "succeeded", f"预设“{preset_action['preset_name']}”已持久化,可在后续指令中直接调用。", time.perf_counter(), { "preset_count": len(updated_context["presets"]), "storage_scope": "conversation_durable", }, ) self._persist(result) return result compile_started = time.perf_counter() try: if hardware_request: if control_transport != WIRELESS_MQTT_TRANSPORT: self.hardware_settings.validate_function_config(function_config) command_plan = self.hardware_compiler.compile( function_config, context["devices"] ) else: command_plan = self.compiler.compile(function_config) except ValueError as exc: compile_issue = { "severity": "error", "code": "CAN_COMPILE_REJECTED", "path": "spec.operations", "message": str(exc), } validation.append(compile_issue) result["validation"] = validation result["status"] = "compile_failed" self._trace( traces, request_id, "compile", "failed", f"座椅 CAN 编译被安全约束阻止:{exc}", compile_started, {"error": str(exc), "hardware_request": hardware_request}, ) self._persist(result) return result plan_issues = self.contracts.validate(command_plan) validation.extend(plan_issues) result["command_plan"] = command_plan result["command_plans"] = {"seat": command_plan} self._trace( traces, request_id, "compile", "failed" if plan_issues else "succeeded", "配置已确定性编译为座椅 CAN 语义信号计划。", compile_started, { "messages": [ command["message"] for command in command_plan["spec"]["commands"] ], "command_count": len(command_plan["spec"]["commands"]), "signal_count": sum( len(command["signals"]) for command in command_plan["spec"]["commands"] ), }, ) if plan_issues: result["status"] = "compile_failed" self._persist(result) return result self._emit_validated_commands(result, on_commands_validated) if hardware_request: self._authorize_compiled_hardware_delivery( parsed, approval_token, control_transport, ) safety_started = time.perf_counter() self._trace( traces, request_id, "safety_check", "succeeded", ( "无线 MQTT CAN0 座椅语义白名单与固件原生 0x300 报文白名单已通过;" "本地 T2G 固件 profile、USB-CAN 与物理台架连接不参与校验。" if hardware_request and control_transport == WIRELESS_MQTT_TRANSPORT else ( "硬件启用、授权令牌、固件 profile 与目标座椅配置门禁已通过;" if self.hardware_settings.approval_required else "硬件启用、调试免令牌、固件 profile 与目标座椅配置门禁已通过;" ) + "板端 Position_Valid 将在发送绝对移动命令前实时检查。" if hardware_request else ( "模拟台架隔离门禁已通过;仅生成故障注入反馈,物理 CAN、ZLG 与 IAR 路径均未打开。" if simulated_hardware_request else "平台、矩阵和策略均限制为 dry-run;硬件发送路径未启用。" ) ), safety_started, { "hardware_execution_allowed": hardware_request, "simulated_hardware": simulated_hardware_request, "simulation_fault": ( simulation_fault if simulated_hardware_request else None ), "position_calibration_gate": ( "remote_native_can0_feedback" if hardware_request and control_transport == WIRELESS_MQTT_TRANSPORT else "runtime_can_preflight" if hardware_request else "not_applicable" ), "mapping_status": ( "mqtt_can0_native_firmware_whitelist" if hardware_request and control_transport == WIRELESS_MQTT_TRANSPORT else "protocol_hil_verified_combined_firmware_hil_pending" if hardware_request else "draft" ), "firmware_profiles": ( sorted(self.hardware_settings.profiles) if hardware_request and control_transport != WIRELESS_MQTT_TRANSPORT else [] ), }, ) execute_started = time.perf_counter() if hardware_request: mqtt_selected = control_transport == WIRELESS_MQTT_TRANSPORT vehicle_safety_preflight = None wired_bridge = None try: if mqtt_selected: vehicle_safety_preflight = _wireless_mqtt_publish_preflight() hardware_executor = self._new_hardware_executor( lambda: self._vehicle_mqtt_transport.bus(channel=0), remote_transport=True, ) elif ( self.hardware_settings.native_passenger_can0 and self.vehicle_can_settings.configured ): wired_bridge = self._new_linux_wired_dual_channel_bridge() vehicle_safety_preflight = observe_vehicle_safety( wired_bridge.channel_bus(1), timeout_ms=min( 5_000, max( 1_000, self.vehicle_can_settings.feedback_timeout_ms, ), ), required_channel=1, ) require_stationary_vehicle(vehicle_safety_preflight) hardware_executor = self._new_hardware_executor( lambda: wired_bridge.channel_bus(0) ) else: hardware_executor = self._new_hardware_executor() execution_report = hardware_executor.execute( command_plan, function_config, context["devices"] ) except VehicleSafetyPreflightError as exc: raise HardwareSafetyError( "CH1 vehicle safety preflight refused passenger CAN0 execution: " f"{exc}" ) from exc finally: if wired_bridge is not None: wired_bridge.close() if vehicle_safety_preflight is not None: for item in execution_report["spec"].get("results", []): item.setdefault("telemetry", {})[ "vehicle_safety_preflight" ] = vehicle_safety_preflight elif simulated_hardware_request: execution_report = self.simulated_hardware_executor.execute( command_plan, function_config, context["devices"], fault_scenario=simulation_fault, ) else: execution_report = self.executor.execute( command_plan, function_config, context["devices"] ) execution_outcome, execution_partially_succeeded = ( _annotate_execution_report(execution_report) ) report_issues = self.contracts.validate(execution_report) validation.extend(report_issues) result["execution_report"] = execution_report result["execution_outcome"] = execution_outcome execution_succeeded = ( not report_issues and execution_report["spec"]["status"] == "succeeded" ) if report_issues: result["status"] = "execution_report_invalid" elif execution_partially_succeeded: result["status"] = "partially_succeeded" elif not execution_succeeded: result["status"] = "execution_failed" elif parsed.get("deferred_requests"): result["status"] = "partially_succeeded" else: result["status"] = "succeeded" execution_results = execution_report["spec"]["results"] stop_all_attempted = any( item.get("telemetry", {}).get("stop_all_attempted") for item in execution_results ) firmware_gate_failed = any( item.get("telemetry", {}).get("error_type") == "FirmwareProgrammingError" for item in execution_results ) failed_execution = next( (item for item in execution_results if item.get("status") == "failed"), None, ) failed_telemetry = ( failed_execution.get("telemetry", {}) if failed_execution else {} ) hardware_failure_summary = ( f"真实 CAN 执行失败:{failed_execution['message']}" if failed_execution else "真实 CAN 执行失败,未获得满足目标的物理反馈。" ) if stop_all_attempted: hardware_failure_summary += " 移动路径已尝试发送 STOP_ALL。" self._trace( traces, request_id, "execute", "succeeded" if execution_succeeded else "failed", ( "真实 CAN 命令已提交;当前反馈窗口未确认车辆效果,状态以实时 CAN 回读为准。" if hardware_request and execution_succeeded and execution_outcome == "submitted_unverified" else "真实 CAN 命令已发送,并收到 IAR 固件的实际状态回读。" if hardware_request and execution_succeeded else ( ( "固件身份验证或自动烧录失败;未发送座椅控制帧。" if firmware_gate_failed else hardware_failure_summary ) if hardware_request else ( "模拟台架已生成隔离的 HIL 反馈,未访问物理 CAN。" if simulated_hardware_request and execution_succeeded else ( f"模拟台架故障已触发:{failed_execution['message']}" if simulated_hardware_request and failed_execution else "虚拟 CAN 已编码命令帧并生成模拟回读。" ) ) ) ), execute_started, { "mode": execution_report["spec"]["mode"], "result_count": len(execution_report["spec"]["results"]), "firmware_gate_failed": firmware_gate_failed, "stop_all_attempted": stop_all_attempted, "motor_result": failed_telemetry.get("motor_result_name"), "failure_category": failed_telemetry.get("failure_category"), "hall_before": failed_telemetry.get("hall_before"), "hall_after": failed_telemetry.get("hall_after"), }, ) if execution_succeeded: execution_notices = [ str(item.get("telemetry", {}).get("user_notice")) for item in execution_results if item.get("telemetry", {}).get("user_notice") ] for notice in execution_notices: if notice not in parsed["decision_summary"]: parsed["decision_summary"].append(notice) state_actions = ( self._hardware_feedback_actions( parsed["actions"], function_config, execution_report ) if hardware_request else parsed["actions"] ) updated_context = self.conversations.apply_successful_actions( conversation_id, utterance, state_actions, parsed["decision_summary"], ) physical_state = execution_report.get("spec", {}).get( "physical_state" ) if hardware_request and isinstance(physical_state, dict): updated_context = self.conversations.synchronize_physical_passenger( conversation_id, physical_state, ) updated_context = self._context_for_vehicle( updated_context, vehicle_profile, ) result["conversation_turn"] = updated_context["turn_count"] result["actuator_state"] = updated_context["devices"] result["domain_state"] = updated_context["domain_devices"] self._trace( traces, request_id, "state_update", "succeeded", f"会话状态已更新为第 {updated_context['turn_count']} 轮,可用于下一条指令。", time.perf_counter(), {"state_after": updated_context["devices"]}, ) elif hardware_request: physical_state = execution_report.get("spec", {}).get( "physical_state" ) if isinstance(physical_state, dict) and ( physical_state.get("position") or physical_state.get("ventilation") or physical_state.get("heating") ): updated_context = self.conversations.synchronize_physical_passenger( conversation_id, physical_state, ) updated_context = self._context_for_vehicle( updated_context, vehicle_profile, ) result["actuator_state"] = updated_context["devices"] result["domain_state"] = updated_context["domain_devices"] self._trace( traces, request_id, "state_sync", "succeeded", "真实执行未完成,但已按 CAN 物理回读同步副驾状态。", time.perf_counter(), {"state_after": updated_context["devices"]}, ) telemetry = next( ( item.get("telemetry", {}) for item in execution_report["spec"]["results"] if item.get("telemetry", {}).get("feedback_frame") ), execution_report["spec"]["results"][0].get("telemetry", {}), ) self._trace( traces, request_id, "telemetry", "succeeded" if execution_succeeded else "failed", ( "IAR 固件反馈帧中的实际状态与请求目标一致。" if hardware_request and execution_succeeded else ( ( f"未获得满足目标的真实硬件反馈:" f"{failed_execution['message']}" if failed_execution else "未获得满足目标的真实硬件反馈。" ) if hardware_request else ( "模拟台架反馈与请求目标一致,物理 CAN 始终关闭。" if simulated_hardware_request and execution_succeeded else ( f"模拟台架故障反馈:{failed_execution['message']}" if simulated_hardware_request and failed_execution else "虚构反馈帧中的最终状态与请求目标一致。" ) ) ) ), time.perf_counter(), { "command_frame": telemetry.get("command_frame", {}).get("data_hex"), "feedback_frame": telemetry.get("feedback_frame", {}).get("data_hex"), "simulated": not hardware_request, "simulation_fault": ( simulation_fault if simulated_hardware_request else None ), }, ) self._persist(result) return result @staticmethod def _adapt_validation_for_hardware( validation: list[dict[str, Any]], ) -> None: """Replace reference-vehicle dry-run warnings with HIL bench semantics.""" hardware_messages = { "LEVEL_COMMAND_SIMULATED": ( "LEVEL_COMMAND_T2G_MAPPED", "目标档位已映射到统一 T2G 固件的 0x29E 档位信号;" "执行结果以 0x4FA 物理 CAN 回读为准。", ), "SYNTHETIC_MOTION_CAN": ( "T2G_MOTION_PROTOCOL_MAPPED", "参考车型的虚拟运动矩阵未用于本次 HIL;座椅移动通过统一 T2G 固件的 " "0x300 命令和 0x301/0x302 物理反馈闭环执行。", ), "DRIVER_MOTION_INTERLOCK_NOT_IMPLEMENTED": ( "BENCH_ONLY_NO_VEHICLE_INTERLOCKS", "当前真实移动路径仅限独立 T2G 台架;尚未接入车速、挡位、乘员和防夹信号," "不得直接用于车载座椅系统。", ), "COMBINED_THERMAL_SIMULATED": ( "COMBINED_THERMAL_T2G_MAPPED", "加热与通风组合目标由统一 T2G 固件执行并以 0x4FA 物理反馈确认;" "接入完整座椅总成前仍需补充热安全互锁验证。", ), "VIRTUAL_EXECUTION_ONLY": ( "REFERENCE_MATRIX_OVERRIDDEN_BY_T2G_BENCH", "参考车型矩阵元数据仍不完整;本次 HIL 仅使用仓库内已验证的 " "T2G 统一固件协议、身份门禁和物理反馈闭环。", ), } for issue in validation: replacement = hardware_messages.get(str(issue.get("code"))) if replacement is not None: issue["code"], issue["message"] = replacement @staticmethod def _hardware_feedback_actions( actions: list[dict[str, Any]], function_config: dict[str, Any], execution_report: dict[str, Any], ) -> list[dict[str, Any]]: """Project authoritative physical feedback into conversation/UI state.""" feedback_by_ref = { item["ref"]: item.get("telemetry", {}) for item in execution_report["spec"]["results"] if _operation_verification_status(item) == "verified_success" } updated: list[dict[str, Any]] = [] operations = function_config["spec"]["operations"] for source_action, operation in zip(actions, operations, strict=True): telemetry = feedback_by_ref.get(operation["id"]) if telemetry is None: # Submission alone is not an authoritative vehicle-state # update. The asynchronous CAN/MQTT state monitor will apply # the change if and when the vehicle reports it. continue action = deepcopy(source_action) if action.get("action_type") == "position": if telemetry.get("effective_position_pct") is not None: action["position_pct"] = int( telemetry["effective_position_pct"] ) if telemetry.get("position_valid") is not None: action["position_valid"] = bool( telemetry["position_valid"] ) if telemetry.get("motion_mode") in { "relative_timed_duty", "timed_user_request", }: action["position_estimated"] = True action["feedback_basis"] = ( "timed_endpoint_or_hall_confirmed_open_loop" if telemetry.get("motion_mode") == "timed_user_request" else "relative_hall_confirmed_open_loop" ) updated.append(action) continue raw = telemetry.get("feedback_value") if raw is not None: raw_value = int(raw) action["state"] = "off" if raw_value <= 1 else "on" action["requested_level"] = max(0, raw_value - 1) updated.append(action) physical_state = execution_report.get("spec", {}).get("physical_state") if isinstance(physical_state, dict): positions = physical_state.get("position", {}) by_axis = { action.get("axis"): action for action in updated if action.get("action_type") == "position" } for axis, feedback in positions.items(): if not isinstance(feedback, dict) or feedback.get("position_pct") is None: continue action = by_axis.get(axis) if action is None: action = { "domain": "seat", "target": "passenger", "action_type": "position", "axis": axis, } updated.append(action) by_axis[axis] = action action["position_pct"] = int(feedback["position_pct"]) if feedback.get("position_valid") is not None: action["position_valid"] = bool(feedback["position_valid"]) action["feedback_basis"] = "physical_can_snapshot" by_function = { action.get("function"): action for action in updated if action.get("function") in {"ventilation", "heating"} } for feature in ("ventilation", "heating"): feedback = physical_state.get(feature) if not isinstance(feedback, dict): continue action = by_function.get(feature) if action is None: action = { "domain": "seat", "target": "passenger", "function": feature, } updated.append(action) by_function[feature] = action action["state"] = ( "on" if feedback.get("state") == "on" else "off" ) action["requested_level"] = int(feedback.get("level", 0)) for action in updated: action["physical_target_alias"] = "t2g_primary_seat" return updated @staticmethod def _physical_ch1_feedback_states( reports: dict[str, dict[str, Any]], ) -> list[dict[str, Any]]: """Return only fully verified decoded CH1 feedback state patches.""" states: list[dict[str, Any]] = [] for report in reports.values(): for item in report.get("spec", {}).get("results", []): feedback_state = authoritative_physical_ch1_feedback_state(item) if feedback_state is not None: states.append(feedback_state) return states @staticmethod def _parse_mode_profile_save_name(utterance: str) -> str | None: explicit = SAVE_MODE_PROFILE_NAME.search(utterance) if explicit is not None: return explicit.group("name").strip() save_like = any(word in utterance for word in ("保存", "记住", "记为")) mode_context = any( word in utterance for word in ("这个模式", "这个配置", "当前模式", "长期", "固化") ) if not save_like or not mode_context: return None generic = re.search( r"(?:保存为|保存成|记为|命名为)(?P[^,。,.!!\s]{1,64}?模式)", utterance, ) return generic.group("name").strip() if generic is not None else None @classmethod def is_mode_profile_configuration_request(cls, utterance: str) -> bool: """Identify mode configuration text that must never reach live executors.""" clean_utterance = str(utterance).strip() if is_mode_profile_query(clean_utterance): # Named-mode questions may contain words such as “控制/执行” and # concrete device names. They are read-only control-plane turns, # so learned-peripheral and live CAN routers must not claim them. return True return any( matcher(clean_utterance) is not None for matcher in ( cls._parse_mode_profile_save_name, cls._parse_mode_profile_create_name, cls._parse_mode_profile_restore_name, parse_mode_profile_delete, cls._parse_mode_profile_update, ) ) @staticmethod def _parse_mode_profile_create_name(utterance: str) -> str | None: explicit = parse_mode_profile_create(utterance) return explicit[0] if explicit is not None else None @staticmethod def _parse_mode_profile_update(utterance: str) -> tuple[str, str] | None: return parse_mode_profile_update(utterance) @staticmethod def _parse_mode_profile_restore_name(utterance: str) -> str | None: explicit = RESTORE_MODE_PROFILE_PREFIX.search(utterance) if explicit is not None: return explicit.group("name").strip() suffix = RESTORE_MODE_PROFILE_SUFFIX.search(utterance) return suffix.group("name").strip() if suffix is not None else None def _apply_mode_profiles( self, parsed: dict[str, Any], matched_modes: list[ModeProfile], context: dict[str, Any], vehicle_profile: VehicleProfile, *, utterance: str, ) -> dict[str, Any]: if not matched_modes: return parsed if any(mode.schedule is not None for mode in matched_modes): rejected = deepcopy(parsed) for key in ("actions", "domain_actions", "native_can_actions", "preset_actions", "preset_events", "deferred_requests", "model_requests"): rejected[key] = [] rejected["ambiguities"] = [{ "field": "mode_profile", "question": "动态模式必须通过时间轴执行,请独立输入“进入模式名称”;不会将各阶段压平执行。", }] return rejected if self._contains_seat_preset_request(parsed): return parsed if len(matched_modes) > 1: rejected = deepcopy(parsed) for key in ( "actions", "domain_actions", "preset_actions", "preset_events", "deferred_requests", "model_requests", ): rejected[key] = [] rejected["ambiguities"] = [ { "field": "mode_profile", "question": "一条请求只能执行一个座椅功能模式,请确认要执行的模式。", } ] rejected["decision_summary"] = [ "同一条请求命中多个功能模式,已停止生成控制指令以避免模式冲突。" ] return rejected target_conflict = self._explicit_mode_target_conflict( matched_modes[0], utterance, ) if target_conflict is not None: rejected = deepcopy(parsed) for key in ( "actions", "domain_actions", "native_can_actions", "preset_actions", "preset_events", "deferred_requests", "model_requests", ): rejected[key] = [] requested_scope, supported_scopes = target_conflict rejected["ambiguities"] = [ *rejected.get("ambiguities", []), { "field": "mode_profile", "question": ( f"该模式的座椅目标为 {', '.join(supported_scopes)}," f"与用户指定的 {requested_scope} 不一致,请重新确认。" ), "reason": "mode_target_scope_mismatch", }, ] rejected["unresolved_spans"] = [ *rejected.get("unresolved_spans", []), { "text": utterance, "reason": "mode_target_scope_mismatch", }, ] rejected["decision_summary"] = [ *rejected.get("decision_summary", []), "用户显式座位与功能模式登记目标不一致,已停止所有模式动作。", ] return rejected parsed = self._apply_implicit_mode_target_policy( parsed, matched_modes, utterance, ) safety_ambiguities = [ item for item in parsed.get("ambiguities", []) if item.get("field") != "capability" ] if safety_ambiguities: return parsed matched_profile = matched_modes[0] mode_requests, n7_transfer_actions = ( self._mode_requests_and_n7_transfers(matched_profile) ) selected_seat_scope = self._explicit_mode_seat_selection( matched_profile, utterance, ) if selected_seat_scope is not None: # A cabin profile can carry independent comfort operations for both # front seats. An explicit single-seat invocation selects only that # seat; it must not authorize its sibling or mutate the stored mode. mode_requests = [ request for request in mode_requests if request["domain"] != "seat" or self._normalized_mode_seat_scope(request["target_scope"]) in selected_seat_scope ] detected_domains = [] for request in mode_requests: if request["domain"] not in detected_domains: detected_domains.append(request["domain"]) mode_plan = { "detected_domains": detected_domains or ["seat"], "requests": mode_requests, "ambiguities": [], "decision_summary": [ "命中长期模式配置:" + "、".join(profile.name for profile in matched_modes) + "。" ], "confidence": max(float(parsed.get("confidence", 0)), 0.97), } mode_parsed = self.model_plan_adapter.adapt( mode_plan, context, vehicle_profile, ) # Native-only comfort capabilities are represented as deferred items by # the generic adapter. A verified stored mode must materialize them too; # otherwise a successful named-mode run silently drops its driver seat. mode_native_actions = [] for item in mode_parsed.get("deferred_requests", []): if item.get("execution_backend") != "native_can": continue action = { "domain": item["domain"], "capability": item["capability"], "target": item["target_scope"], "parameters": deepcopy(item.get("parameters", {})), "operation": item.get("desired_change", "set"), "reason": item.get("reason", ""), "execution_backend": "native_can", } if self.native_can_runtime.supports(action): mode_native_actions.append(action) parsed_has_explicit_request = bool( parsed.get("actions") or parsed.get("domain_actions") or parsed.get("preset_actions") or parsed.get("preset_events") or parsed.get("model_requests") ) merged = deepcopy(parsed) merged["actions"] = self._merge_control_actions( mode_parsed.get("actions", []), parsed.get("actions", []), ) merged["domain_actions"] = self._merge_control_actions( mode_parsed.get("domain_actions", []), parsed.get("domain_actions", []), ) merged["native_can_actions"] = self._merge_control_actions( mode_native_actions, parsed.get("native_can_actions", []), ) for key in ( "preset_actions", "preset_events", "deferred_requests", "ambiguities", "model_requests", ): parsed_values = parsed.get(key, []) if key == "ambiguities" and not parsed_has_explicit_request: # A local parser does not know arbitrary named modes and emits # a generic unsupported-capability ambiguity. A unique, # repository-backed mode match is authoritative for that case. parsed_values = [] merged[key] = self._merge_unique( mode_parsed.get(key, []), parsed_values, ) merged["decision_summary"] = [ *mode_parsed.get("decision_summary", []), *parsed.get("decision_summary", []), ] merged["detected_domains"] = self._merge_domains( mode_parsed.get("detected_domains", []), parsed.get("detected_domains", []), ) merged["confidence"] = max( float(mode_parsed.get("confidence", 0)), float(parsed.get("confidence", 0)), ) if n7_transfer_actions: merged["deferred_requests"] = self._merge_unique( list(merged.get("deferred_requests", [])), self._n7_transfer_deferred_requests(n7_transfer_actions), ) merged["_n7_transfer_actions"] = deepcopy(n7_transfer_actions) merged["_n7_scene_profile"] = { "id": matched_profile.id, "name": matched_profile.name, } merged["detected_domains"] = self._merge_domains( list(merged.get("detected_domains", [])), [ str(action.get("domain") or "") for action in n7_transfer_actions if str(action.get("domain") or "") ], ) if self._has_local_mode_peripheral(matched_profile): merged["_saved_mode_local_peripheral"] = True return merged @classmethod def _explicit_mode_target_conflict( cls, profile: ModeProfile, utterance: str, ) -> tuple[str, tuple[str, ...]] | None: """Reject a named mode when its persisted seat scope contradicts text.""" requested, normalized_scopes = cls._explicit_mode_seat_scopes(profile, utterance) if requested is None or requested == normalized_scopes: return None if cls._explicit_mode_seat_selection(profile, utterance) is not None: return None return ",".join(sorted(requested)), tuple(sorted(normalized_scopes)) @staticmethod def _normalized_mode_seat_scope(scope: str) -> str: return ( "driver" if scope == "front_left" else "passenger" if scope == "front_right" else "rear" if scope in {"rear", "rear_left", "rear_right"} else scope ) @classmethod def _explicit_mode_seat_scopes( cls, profile: ModeProfile, utterance: str, ) -> tuple[frozenset[str] | None, set[str]]: aliases = (profile.id, profile.name, *profile.aliases) mode_clause = next( ( clause for clause in re.split(r"[,,;;。!?!?\n]+", utterance) if any( re.sub(r"\s+", "", alias.casefold()) in re.sub(r"\s+", "", clause.casefold()) for alias in aliases if alias ) ), utterance, ) requested = resolve_explicit_seat_scopes( mode_clause.replace("主副驾", "主驾和副驾"), assume_seat_context=True, ) normalized_scopes = { cls._normalized_mode_seat_scope(operation.target_scope) for operation in profile.operations if operation.domain == "seat" } return requested, normalized_scopes @classmethod def _explicit_mode_seat_selection( cls, profile: ModeProfile, utterance: str, ) -> frozenset[str] | None: """Scope a dual-seat mode invocation, never mode creation or editing.""" requested, supported = cls._explicit_mode_seat_scopes(profile, utterance) if ( requested is None or len(requested) != 1 or supported != {"driver", "passenger"} or not requested.issubset(supported) or cls.is_mode_profile_configuration_request(utterance) or not re.search(r"进入|开启|启动|执行|启用|运行|切换|打开|调用|应用|使用", utterance) ): return None return requested @staticmethod def _apply_implicit_mode_target_policy( parsed: dict[str, Any], matched_modes: list[ModeProfile], utterance: str, ) -> dict[str, Any]: """Let one matched mode own only an omitted seat target. The native CH1 parser can add an implicit-driver ambiguity before or after mode injection. A unique repository-backed mode already carries authoritative target scopes on every operation, so that one tagged default is redundant. Explicit scopes and all other safety questions remain untouched. """ if len(matched_modes) != 1 or ( DRIVER_SEAT_SCOPE_PATTERN.search(utterance) or PASSENGER_SEAT_SCOPE_PATTERN.search(utterance) ): return parsed ambiguities = list(parsed.get("ambiguities", [])) filtered = [ item for item in ambiguities if item.get("reason") != "implicit_driver_default_unresolved" ] if len(filtered) == len(ambiguities): return parsed resolved = deepcopy(parsed) resolved["ambiguities"] = filtered return resolved @staticmethod def _is_dynamic_mode_stop_request(utterance: str) -> bool: compact = re.sub(r"\s+", "", utterance) if any(negation in compact for negation in ("不要停止", "无需停止", "别停止")): return False return any(word in compact for word in DYNAMIC_MODE_STOP_WORDS) and any( word in compact for word in DYNAMIC_MODE_WORDS ) def _apply_dynamic_mode_stop_request( self, parsed: dict[str, Any], utterance: str, ) -> dict[str, Any]: """Make explicit stop language authoritative over a matched start profile.""" stopped = deepcopy(parsed) stopped["actions"] = [ action for action in stopped.get("actions", []) if action.get("action_type") not in {"dynamic_mode", "dynamic_mode_stop"} ] stopped["actions"].append( {"action_type": "dynamic_mode_stop", "target": "passenger"} ) stopped["model_requests"] = [ request for request in stopped.get("model_requests", []) if request.get("capability") not in {"seat.start_dynamic_mode", DYNAMIC_MODE_STOP_CAPABILITY} ] stopped["model_requests"].append( { "domain": "seat", "capability": DYNAMIC_MODE_STOP_CAPABILITY, "target_scope": "passenger", "operation": "deactivate", "parameters": [], "reason": "用户明确要求停止动态座椅循环。", "source_span": utterance, } ) stopped["ambiguities"] = [ item for item in stopped.get("ambiguities", []) if item.get("field") != "capability" ] stopped["detected_domains"] = self._merge_domains( ["seat"], stopped.get("detected_domains", []) ) stopped["decision_summary"] = [ "识别到停止动态座椅模式指令;不会重新启动同名长期模式。", *stopped.get("decision_summary", []), ] stopped["confidence"] = max(float(stopped.get("confidence", 0)), 0.99) return stopped @staticmethod def _contains_seat_preset_request(parsed: dict[str, Any]) -> bool: capabilities = { request.get("capability") for request in parsed.get("model_requests", []) if isinstance(request, dict) } return bool( parsed.get("preset_actions") or parsed.get("preset_events") or SAVE_PRESET_CAPABILITY in capabilities or APPLY_PRESET_CAPABILITY in capabilities ) @classmethod def _merge_control_actions( cls, mode_items: list[dict[str, Any]], parsed_items: list[dict[str, Any]], ) -> list[dict[str, Any]]: parsed_controls = { cls._control_signature(item) for item in parsed_items } candidates = [ *parsed_items, *( item for item in mode_items if cls._control_signature(item) not in parsed_controls ), ] return cls._merge_unique([], candidates) @staticmethod def _control_signature(action: dict[str, Any]) -> tuple[Any, ...]: if action.get("domain"): return ( action.get("domain"), action.get("capability"), action.get("target"), ) if action.get("action_type") == "position": return ( "seat", "position", action.get("target"), action.get("axis"), ) if action.get("action_type") == "dynamic_mode_stop": return ( "seat", "dynamic_mode_stop", action.get("target"), ) if action.get("action_type") == "dynamic_mode": return ( "seat", "dynamic_mode", action.get("target"), action.get("mode_id"), ) return ( "seat", "function", action.get("target"), action.get("function"), ) @staticmethod def _merge_unique( first: list[dict[str, Any]], second: list[dict[str, Any]], ) -> list[dict[str, Any]]: merged = [] seen: set[str] = set() for item in [*first, *second]: signature = json.dumps(item, ensure_ascii=False, sort_keys=True) if signature in seen: continue seen.add(signature) merged.append(item) return merged @staticmethod def _merge_domains( first: list[str], second: list[str], ) -> list[str]: domains = [] for domain in [*first, *second]: if domain not in domains: domains.append(domain) return domains or ["seat"] @staticmethod def _preview_mode_operations( source_result: dict[str, Any], ) -> list[dict[str, Any]]: """Recover every reusable operation from server-compiled preview data.""" plans = source_result.get("command_plans") configs = source_result.get("function_configs") if not isinstance(plans, dict) or not isinstance(configs, dict): return [] operations: list[dict[str, Any]] = [] seen: set[tuple[str, str, str]] = set() for key, plan in plans.items(): if ( not isinstance(plan, dict) or not isinstance(plan.get("spec"), dict) or not isinstance(plan["spec"].get("commands"), list) or not plan["spec"]["commands"] ): continue config = configs.get(key) spec = config.get("spec") if isinstance(config, dict) else None if not isinstance(spec, dict): continue domain = str(spec.get("domain") or "").strip() raw_operations = spec.get("operations") if not domain or not isinstance(raw_operations, list): continue for operation in raw_operations: if not isinstance(operation, dict): continue capability = str(operation.get("capability") or "").strip() raw_target = operation.get("target") target = ( str( raw_target.get("instance") or raw_target.get("zone") or raw_target.get("scope") or "" ).strip() if isinstance(raw_target, dict) else "" ) parameters = operation.get("parameters") signature = (domain, capability, target) if ( not capability or not target or not isinstance(parameters, dict) or signature in seen ): continue reusable_parameters = deepcopy(parameters) if ( capability.startswith("seat.set_") and reusable_parameters.get("position_pct") is not None and reusable_parameters.get("angle_deg") is not None ): # Seat compilation adds the calibrated angle alongside the # requested percentage. The scene contract intentionally # permits one absolute representation only; retain the # portable percentage and let the new compile derive angle. reusable_parameters.pop("angle_deg", None) seen.add(signature) operations.append( { "domain": domain, "capability": capability, "target_scope": target, "operation": "set", "parameters": reusable_parameters, "reason": "由服务端已校验功能模式草稿保留。", } ) return operations def _load_mode_profile_draft_reference( self, reference: dict[str, Any], *, conversation_id: str, mode_name: str, vehicle_id: str, ) -> tuple[dict[str, Any], dict[str, Any], list[dict[str, Any]]]: """Resolve an opaque client reference back to one trusted local artifact.""" if not isinstance(reference, dict) or set(reference) != { "name", "source_request_id", "vehicle_id", }: raise ValueError("待保存功能模式草稿引用格式无效。") clean_name = str(reference.get("name") or "").strip() clean_request_id = str(reference.get("source_request_id") or "").strip() clean_vehicle_id = str(reference.get("vehicle_id") or "").strip() if not MODE_EDITOR_REQUEST_ID_PATTERN.fullmatch(clean_request_id): raise ValueError("待保存功能模式草稿来源请求编号无效。") if ( normalize_mode_profile_name(clean_name) != normalize_mode_profile_name(mode_name) ): raise ValueError("修改请求与待保存功能模式草稿名称不匹配。") if clean_vehicle_id != vehicle_id: raise ValueError("待保存功能模式草稿不属于当前车型。") result_path = self.artifact_root / clean_request_id / "result.json" if not result_path.is_file(): raise ValueError("未找到待保存功能模式草稿,请重新构建。") try: source_result = json.loads(result_path.read_text(encoding="utf-8")) except (OSError, json.JSONDecodeError) as exc: raise ValueError("待保存功能模式草稿无法读取,请重新构建。") from exc if not isinstance(source_result, dict): raise ValueError("待保存功能模式草稿内容无效,请重新构建。") source_vehicle = source_result.get("vehicle") source_vehicle_id = ( str(source_vehicle.get("id") or "").strip() if isinstance(source_vehicle, dict) else "" ) if ( source_result.get("request_id") != clean_request_id or source_result.get("conversation_id") != conversation_id or source_vehicle_id != vehicle_id or source_result.get("status") != "succeeded" or source_result.get("execution_mode") != "dry_run" or source_result.get("hardware_execution_allowed") is not False or source_result.get("orchestration_route") != "mode_profile_create_preview" ): raise ValueError("待保存功能模式草稿与当前会话不匹配,请重新构建。") draft = source_result.get("mode_profile_draft") if not isinstance(draft, dict): raise ValueError("来源请求不是可修改的功能模式草稿。") if ( str(draft.get("name") or "").strip() != clean_name or str(draft.get("source_request_id") or "").strip() != clean_request_id or str(draft.get("vehicle_id") or "").strip() != vehicle_id ): raise ValueError("待保存功能模式草稿引用与来源内容不匹配。") validation = source_result.get("validation") if isinstance(validation, list) and any( isinstance(issue, dict) and issue.get("severity") == "error" for issue in validation ): raise ValueError("待保存功能模式草稿没有通过完整安全校验。") schedule = self._validated_schedule_artifact(source_result) operations = ([deepcopy(op) for phase in schedule.phases for op in phase.operations] if schedule is not None else self._preview_mode_operations(source_result)) operation_count = draft.get("operation_count") if ( not isinstance(operation_count, int) or isinstance(operation_count, bool) or operation_count <= 0 or len(operations) != operation_count ): raise ValueError("待保存功能模式草稿动作证据不完整,请重新构建。") return source_result, draft, operations @staticmethod def _absolute_mode_operations( operations: list[dict[str, Any]], ) -> list[dict[str, Any]]: """Convert bounded motion commands into replayable absolute mode targets.""" normalized: list[dict[str, Any]] = [] window_targets = { "all": ("front_left", "front_right", "rear_left", "rear_right"), "front": ("front_left", "front_right"), "rear": ("rear_left", "rear_right"), } for operation in operations: item = deepcopy(operation) capability = str(item.get("capability") or "") parameters = dict(item.get("parameters") or {}) if capability == "roof.set_shade_motion": positions = {"close": 0, "half": 50, "open": 100} motion = str(parameters.get("motion") or "") if motion not in positions: raise ValueError("遮阳帘停止动作没有可保存的最终位置,请改为明确开度。") item["capability"] = "roof.set_shade_position" item["parameters"] = {"position_pct": positions[motion]} normalized.append(item) continue if capability == "window.set_motion": positions = {"close": 0, "open": 100} motion = str(parameters.get("motion") or "") if motion not in positions: raise ValueError("车窗停止动作没有可保存的最终位置,请改为明确开度。") target = str(item.get("target_scope") or "") targets = window_targets.get(target, (target,)) for resolved_target in targets: absolute = deepcopy(item) absolute["capability"] = "window.set_position" absolute["target_scope"] = resolved_target absolute["parameters"] = {"position_pct": positions[motion]} normalized.append(absolute) continue normalized.append(item) return normalized @staticmethod def _mode_profile_draft_plan( operations: list[dict[str, Any]], ) -> dict[str, Any]: return { "detected_domains": list( dict.fromkeys(str(item["domain"]) for item in operations) ), "requests": [ { "domain": str(item["domain"]), "capability": str(item["capability"]), "target_scope": str(item["target_scope"]), "operation": "set", "duration_ms": None, "parameters": [ {"name": str(name), "value": deepcopy(value)} for name, value in dict(item.get("parameters") or {}).items() ], "reason": "按用户要求修改待保存功能模式草稿。", } for item in operations ], "ambiguities": [], "decision_summary": ["已在服务端合并并重新校验待保存模式动作。"], "confidence": 1.0, } def _mode_device_default_operations( self, mode_name: str, ) -> list[dict[str, Any]]: """Return registered static-scene defaults for a bounded device append.""" template = self.scene_templates.resolve_exact_name(mode_name) if template is None or template.schedule is not None: return [] return [operation.to_yaml() for operation in template.operations] @staticmethod def _mode_update_target_is_explicit(change_text: str, target: str) -> bool: patterns = { "driver": r"主驾|驾驶位|驾驶员|左前", "front_left": r"主驾|驾驶位|驾驶员|左前", "passenger": r"副驾|乘客位|右前", "front_right": r"副驾|乘客位|右前", "front_row": r"前排|前舱", "rear": r"后排|后座|后舱|所有后排", "rear_row": r"后排|后座|后舱|所有后排", "rear_left": r"左后|后排左|左侧后排", "rear_right": r"右后|后排右|右侧后排", "vehicle": r"全车|整车|所有|全部", "all": r"全车|整车|所有|全部", } pattern = patterns.get(str(target)) return bool(pattern and re.search(pattern, change_text)) def _process_mode_profile_draft_update( self, utterance: str, request_id: str, provider: str, conversation_id: str, context: dict[str, Any], mode_name: str, change_text: str, vehicle_profile: VehicleProfile, result: dict[str, Any], traces: list[dict[str, Any]], draft_reference: dict[str, Any], *, approval_token: str | None, simulation_fault: str, control_transport: str, assistant_plan: dict[str, Any] | None, ) -> dict[str, Any]: """Patch an unsaved preview and produce a new atomic dry-run artifact.""" del request_id, traces try: _source_result, source_draft, source_operations = ( self._load_mode_profile_draft_reference( draft_reference, conversation_id=conversation_id, mode_name=mode_name, vehicle_id=vehicle_profile.id, ) ) if source_draft.get("schedule") is not None: from cognitive_agent.application.mode_schedule_edit import revise_schedule return revise_schedule( self, ModeSchedule.from_dict(source_draft["schedule"]), change_text=change_text, utterance=utterance, name=mode_name, conversation_id=conversation_id, vehicle_profile=vehicle_profile, context=context, control_transport=control_transport, source_draft=source_draft, ) base_operations = self._absolute_mode_operations(source_operations) if not change_text: raise ValueError("请说明要调整的设备和明确目标值。") registered_additions = mode_device_additions( change_text, base_operations, self._mode_device_default_operations(mode_name), ) if registered_additions is not None: normalized_changes = self._absolute_mode_operations( registered_additions ) else: parseable_change_text = re.sub( r"改为|改成|变更为|换成|变成|变为", "调整为", change_text, ) unresolved_change_clauses = [ clause for clause, _parsed, status in self._local_clause_results( parseable_change_text, context, ) if status != "complete" ] if unresolved_change_clauses and assistant_plan is None: raise ValueError( "调整说明包含未完整识别的内容:" + ";".join(unresolved_change_clauses) ) change_result = self._process_once( parseable_change_text, provider, conversation_id, "dry_run", approval_token, vehicle_profile.id, simulation_fault, control_transport=control_transport, assistant_plan=assistant_plan, allow_fuzzy_mode_profile_match=False, ) change_operations = self._absolute_mode_operations( self._preview_mode_operations(change_result) ) if ( change_result.get("status") != "succeeded" or not change_operations or change_result.get("supplement_required") is True or bool(change_result.get("unresolved_clauses")) or bool(change_result.get("deferred_requests")) ): raise ValueError("没有从调整说明中得到完整、可复用的设备目标。") validation = change_result.get("validation") if isinstance(validation, list) and any( isinstance(issue, dict) and issue.get("severity") == "error" for issue in validation ): raise ValueError("调整动作没有通过车辆能力或安全校验。") normalized_changes = [] for change in change_operations: same_capability = [ item for item in base_operations if item["domain"] == change["domain"] and item["capability"] == change["capability"] ] normalized = deepcopy(change) if len(same_capability) == 1 and not self._mode_update_target_is_explicit( change_text, str(change["target_scope"]), ): normalized["target_scope"] = same_capability[0]["target_scope"] elif len(same_capability) > 1 and not self._mode_update_target_is_explicit( change_text, str(change["target_scope"]), ): raise ValueError( f"“{mode_name}”中该设备能力包含多个目标,请明确主驾、副驾或具体区域。" ) normalized_changes.append(normalized) change_by_key = { ( str(item["domain"]), str(item["capability"]), str(item["target_scope"]), ): item for item in normalized_changes } base_by_key = { ( str(item["domain"]), str(item["capability"]), str(item["target_scope"]), ): item for item in base_operations } requested_operation_count = len(change_by_key) changed_operation_count = sum( 1 for key, item in change_by_key.items() if key not in base_by_key or dict(base_by_key[key].get("parameters") or {}) != dict(item.get("parameters") or {}) ) merged: list[dict[str, Any]] = [] for operation in base_operations: key = ( str(operation["domain"]), str(operation["capability"]), str(operation["target_scope"]), ) merged.append(deepcopy(change_by_key.pop(key, operation))) merged.extend(deepcopy(list(change_by_key.values()))) revised_result = self._process_mode_profile_create( utterance, "local", conversation_id, "dry_run", approval_token, vehicle_profile, simulation_fault, mode_name, control_transport, assistant_plan=self._mode_profile_draft_plan(merged), source_draft=source_draft, ) revised_draft = revised_result.get("mode_profile_draft") if ( revised_result.get("status") == "succeeded" and isinstance(revised_draft, dict) and revised_draft.get("generation_source") == "draft_revision" ): revised_draft["requested_operation_count"] = ( requested_operation_count ) revised_draft["changed_operation_count"] = changed_operation_count mode_creation = revised_result.get("mode_creation") if isinstance(mode_creation, dict): mode_creation["requested_operation_count"] = ( requested_operation_count ) mode_creation["changed_operation_count"] = ( changed_operation_count ) self._persist(revised_result) return revised_result except ValueError as exc: result["status"] = "needs_confirmation" result["execution_mode"] = "dry_run" result["hardware_execution_allowed"] = False result["orchestration_route"] = "mode_profile_create_preview" result["validation"] = [ { "severity": "error", "code": "MODE_PROFILE_DRAFT_UPDATE_REJECTED", "path": "mode_profile_draft_context", "message": str(exc), } ] result["mode_creation_failure"] = { "code": "MODE_PROFILE_DRAFT_UPDATE_REJECTED", "reason": str(exc), "next_step": "请保留当前模式卡片,并明确要修改的设备、区域和目标值后重试", "unresolved_clauses": [change_text] if change_text else [], "issues": [], } result["decision_summary"] = [ f"未修改待保存的“{mode_name}”草稿;原草稿保持不变。" ] self._persist(result) return result def _process_mode_profile_delete( self, utterance: str, request_id: str, conversation_id: str, mode_name: str, vehicle_profile: VehicleProfile, result: dict[str, Any], traces: list[dict[str, Any]], ) -> dict[str, Any]: """Delete one exact user-owned mode without entering a vehicle executor.""" started = time.perf_counter() result["execution_mode"] = "dry_run" result["hardware_execution_allowed"] = False result["orchestration_route"] = "mode_profile_delete" result["provider_used"] = "mode_profile_registry" result["decision_summary"] = [ f"识别为自定义模式删除请求,目标模式为“{mode_name}”。" ] self._trace( traces, request_id, "intent_parse", "succeeded", "识别到功能模式删除请求;本次不调用模型、CAN 编译或硬件路径。", started, {"mode_name": mode_name}, domain="mode", ) profile = self._resolve_single_mode_profile( mode_name, vehicle_profile, result, traces, request_id, ) if profile is None: self._persist(result) return result if not profile.user_defined: result["status"] = "rejected" result["validation"] = [ { "severity": "error", "code": "MODE_PROFILE_DELETE_PROTECTED", "path": "utterance.mode_profile", "message": ( f"“{profile.name}”是工程预设模式,不能删除;" "仅可删除用户创建或调整后形成的自定义模式。" ), } ] result["decision_summary"].append( f"未删除工程预设模式“{profile.name}”。" ) self._trace( traces, request_id, "execute", "failed", result["validation"][0]["message"], time.perf_counter(), {"mode_profile_id": profile.id, "can_command_emitted": False}, domain="mode", ) self._persist(result) return result try: deleted = self.mode_profiles.delete_user_mode( profile.id, vehicle_profile.id, ) except KeyError: # A concurrent delete is reported as not found instead of leaking # an exception into the assistant's generic vehicle-failure text. result["status"] = "needs_confirmation" result["validation"] = [ { "severity": "error", "code": "MODE_PROFILE_NOT_FOUND", "path": "utterance.mode_profile", "message": f"未找到可删除的自定义模式“{mode_name}”。", } ] self._persist(result) return result deleted_public = deleted.public_dict() result["deleted_mode_profile"] = deleted_public result["matched_modes"] = [] result["decision_summary"].append( f"已删除自定义模式“{deleted.name}”;未生成或发送任何 CAN 帧。" ) execution_report = self._mode_profile_control_report( request_id, "delete_mode_profile", deleted, f"已删除自定义模式“{deleted.name}”。", {"deleted": True, "can_command_emitted": False}, ) validation = self.contracts.validate(execution_report) result["validation"] = validation result["execution_report"] = execution_report if validation: result["status"] = "execution_report_invalid" self._persist(result) return result updated_context = self.conversations.record_mode_profile_event( conversation_id, utterance, deleted_public, [], result["decision_summary"], "mode_profile_delete", ) result["status"] = "succeeded" result["conversation_turn"] = updated_context["turn_count"] result["actuator_state"] = updated_context["devices"] result["domain_state"] = updated_context["domain_devices"] self._trace( traces, request_id, "execute", "succeeded", "自定义模式删除属于控制平面操作,未生成或发送 CAN 帧。", time.perf_counter(), { "mode": "control_plane", "mode_profile_id": deleted.id, "can_command_emitted": False, }, domain="mode", ) self._persist(result) return result def _process_mode_profile_update( self, utterance: str, request_id: str, provider: str, conversation_id: str, context: dict[str, Any], mode_name: str, change_text: str, vehicle_profile: VehicleProfile, result: dict[str, Any], traces: list[dict[str, Any]], ) -> dict[str, Any]: started = time.perf_counter() result["execution_mode"] = "dry_run" result["hardware_execution_allowed"] = False result["orchestration_route"] = "mode_profile_update" result["provider_used"] = "mode_profile_registry" result["decision_summary"] = [ f"识别为长期模式修改请求,目标模式为“{mode_name}”。" ] self._trace( traces, request_id, "intent_parse", "succeeded", "识别到长期模式配置修改请求;本次不调用 CAN 编译路径。", started, {"mode_name": mode_name, "change_text": change_text}, domain="mode", ) profile = self._resolve_single_mode_profile( mode_name, vehicle_profile, result, traces, request_id, ) if profile is None: self._persist(result) return result if profile.schedule is not None: from cognitive_agent.application.mode_schedule_edit import update_saved_schedule return update_saved_schedule( self, profile, change_text=change_text, utterance=utterance, conversation_id=conversation_id, vehicle_profile=vehicle_profile, context=context, result=result, ) if not change_text: result["status"] = "needs_confirmation" result["validation"] = [ { "severity": "error", "code": "MODE_PROFILE_UPDATE_MISSING_CHANGE", "path": "utterance", "message": "请说明要把长期模式中的哪些参数改成什么目标值。", } ] self._persist(result) return result registered_additions = mode_device_additions( change_text, [operation.to_yaml() for operation in profile.operations], self._mode_device_default_operations(mode_name), ) if registered_additions is not None: parsed = { "decision_summary": [ "已从只读场景模板载入缺失设备的登记默认动作。" ], "deferred_requests": [], "model_requests": [], } result["provider_used"] = ( "mode_profile_registry+registered_scene_template" ) result["decision_summary"].extend(parsed["decision_summary"]) result["deferred_requests"] = [] result["model_requests"] = [] mode_operations = self._absolute_mode_operations( registered_additions ) source_actions = [ { "domain": operation["domain"], "capability": operation["capability"], "target": operation["target_scope"], "operation": operation.get("operation", "set"), "parameters": deepcopy(operation.get("parameters", {})), "reason": operation.get( "reason", "从登记场景模板增补的设备默认动作。", ), } for operation in mode_operations ] else: parsed, provider_used, provider_notice = self._interpret( change_text, provider, context, vehicle_profile, ) parsed = apply_language_safety_guards(parsed, change_text) parsed = self._partition_native_actions(parsed) result["provider_used"] = f"mode_profile_registry+{provider_used}" result["decision_summary"].extend(parsed.get("decision_summary", [])) if provider_notice: result["decision_summary"].append(provider_notice) result["deferred_requests"] = parsed.get("deferred_requests", []) result["model_requests"] = parsed.get("model_requests", []) source_actions = [ *parsed.get("actions", []), *parsed.get("domain_actions", []), *parsed.get("native_can_actions", []), ] mode_operations = self._mode_operations_from_actions(source_actions) mode_operations, ambiguous_targets = self._inherit_mode_update_targets( profile, mode_operations, change_text, ) if ambiguous_targets: result["validation"] = [ { "severity": "error", "code": "MODE_PROFILE_UPDATE_TARGET_AMBIGUOUS", "path": "utterance.target_scope", "message": ( "模式中同一能力包含多个区域,请明确要修改的区域:" + "、".join(ambiguous_targets) + "。" ), } ] result["status"] = "needs_confirmation" self._persist(result) return result if not mode_operations: validation = [ { "severity": "error", "code": "NO_MODE_PROFILE_UPDATE_OPERATIONS", "path": "spec.operations", "message": "没有从修改说明中得到可写入长期模式库的可执行配置。", } ] validation.extend( { "severity": "error", "code": "AMBIGUOUS_INTENT", "path": ambiguity.get("field", "utterance"), "message": ambiguity.get("question", "修改说明不完整。"), } for ambiguity in parsed.get("ambiguities", []) ) result["validation"] = validation result["status"] = ( "capability_unavailable" if parsed.get("deferred_requests") and not parsed.get("ambiguities") else "needs_confirmation" ) self._persist(result) return result result["mode_profile_changes"] = deepcopy(mode_operations) updated_profile = self.mode_profiles.update_mode(profile, mode_operations) profile_public = updated_profile.public_dict() if not profile.user_defined and updated_profile.user_defined: profile_public["origin"] = "customized" profile_public["preset_override"] = True source_path = self._display_path(Path(updated_profile.source)) backup_path = self._display_path( Path(updated_profile.source).with_suffix(".yaml.initial.bak") ) result["mode_profile"] = { **profile_public, "source": source_path, "initial_backup_source": backup_path, } result["matched_modes"] = [profile_public] result["model_requests"] = self.mode_profiles.requests_for_profiles( [updated_profile] ) result["decision_summary"].append( f"已更新长期模式“{updated_profile.name}”,原始配置备份保留为初始化来源。" ) self._trace( traces, request_id, "function_generate", "succeeded", "已将修改后的可执行配置写回长期模式 YAML。", time.perf_counter(), { "mode_profile_id": updated_profile.id, "operation_count": len(updated_profile.operations), "updated_operation_count": len(mode_operations), "source": source_path, "initial_backup_source": backup_path, }, domain="mode", ) execution_report = self._mode_profile_control_report( request_id, "update_mode_profile", updated_profile, f"已更新长期模式“{updated_profile.name}”。", { "updated_operation_count": len(mode_operations), "source": source_path, "initial_backup_source": backup_path, }, ) validation = self.contracts.validate(execution_report) result["validation"] = validation result["execution_report"] = execution_report if validation: result["status"] = "execution_report_invalid" self._persist(result) return result updated_context = self.conversations.record_mode_profile_event( conversation_id, utterance, result["mode_profile"], source_actions, result["decision_summary"], "mode_profile_update", ) result["status"] = ( "partially_succeeded" if parsed.get("deferred_requests") else "succeeded" ) result["conversation_turn"] = updated_context["turn_count"] result["actuator_state"] = updated_context["devices"] result["domain_state"] = updated_context["domain_devices"] self._trace( traces, request_id, "execute", "succeeded", "长期模式修改属于控制平面操作,未生成或发送 CAN 帧。", time.perf_counter(), { "mode": "control_plane", "mode_profile_id": updated_profile.id, "can_command_emitted": False, }, domain="mode", ) self._trace( traces, request_id, "state_update", "succeeded", f"长期模式“{updated_profile.name}”已更新,可被后续自然语言请求命中。", time.perf_counter(), { "turn_count": updated_context["turn_count"], "mode_profile_id": updated_profile.id, }, domain="mode", ) self._persist(result) return result @staticmethod def _inherit_mode_update_targets( profile: ModeProfile, operations: list[dict[str, Any]], change_text: str, ) -> tuple[list[dict[str, Any]], list[str]]: """Bind unscoped edits to the unique matching target already in a mode.""" if MODE_PROFILE_UPDATE_EXPLICIT_TARGET_PATTERN.search(change_text): return deepcopy(operations), [] aligned: list[dict[str, Any]] = [] ambiguous: list[str] = [] for raw_operation in operations: operation = deepcopy(raw_operation) candidates = [ existing for existing in profile.operations if existing.domain == str(operation.get("domain") or "") and existing.capability == str(operation.get("capability") or "") ] if len(candidates) == 1: operation["target_scope"] = candidates[0].target_scope elif len(candidates) > 1: ambiguous.append(str(operation.get("capability") or "设备动作")) aligned.append(operation) return aligned, list(dict.fromkeys(ambiguous)) def _process_mode_profile_restore( self, utterance: str, request_id: str, conversation_id: str, mode_name: str, vehicle_profile: VehicleProfile, result: dict[str, Any], traces: list[dict[str, Any]], ) -> dict[str, Any]: started = time.perf_counter() result["provider_used"] = "mode_profile_registry" result["decision_summary"] = [ f"识别为长期模式初始化请求,目标模式为“{mode_name}”。" ] self._trace( traces, request_id, "intent_parse", "succeeded", "识别到长期模式初始化请求;本次不调用模型或 CAN 编译路径。", started, {"mode_name": mode_name}, domain="mode", ) profile = self._resolve_single_mode_profile( mode_name, vehicle_profile, result, traces, request_id, ) if profile is None: self._persist(result) return result try: restored_profile = self.mode_profiles.restore_initial_mode(profile) except ValueError as exc: result["status"] = "needs_confirmation" result["validation"] = [ { "severity": "error", "code": "MODE_PROFILE_INITIAL_BACKUP_MISSING", "path": "mode_profile.initial_backup", "message": str(exc), } ] self._persist(result) return result profile_public = restored_profile.public_dict() source_path = self._display_path(Path(restored_profile.source)) backup_path = self._display_path( Path(restored_profile.source).with_suffix(".yaml.initial.bak") ) result["mode_profile"] = { **profile_public, "source": source_path, "initial_backup_source": backup_path, } result["matched_modes"] = [profile_public] result["model_requests"] = self.mode_profiles.requests_for_profiles( [restored_profile] ) result["decision_summary"].append( f"已将长期模式“{restored_profile.name}”恢复为初始配置。" ) self._trace( traces, request_id, "function_generate", "succeeded", "已从初始备份恢复长期模式 YAML。", time.perf_counter(), { "mode_profile_id": restored_profile.id, "operation_count": len(restored_profile.operations), "source": source_path, "initial_backup_source": backup_path, }, domain="mode", ) execution_report = self._mode_profile_control_report( request_id, "restore_mode_profile", restored_profile, f"已恢复长期模式“{restored_profile.name}”。", { "source": source_path, "initial_backup_source": backup_path, }, ) validation = self.contracts.validate(execution_report) result["validation"] = validation result["execution_report"] = execution_report if validation: result["status"] = "execution_report_invalid" self._persist(result) return result updated_context = self.conversations.record_mode_profile_event( conversation_id, utterance, result["mode_profile"], [], result["decision_summary"], "mode_profile_restore", ) result["status"] = "succeeded" result["conversation_turn"] = updated_context["turn_count"] result["actuator_state"] = updated_context["devices"] result["domain_state"] = updated_context["domain_devices"] self._trace( traces, request_id, "execute", "succeeded", "长期模式初始化属于控制平面操作,未生成或发送 CAN 帧。", time.perf_counter(), { "mode": "control_plane", "mode_profile_id": restored_profile.id, "can_command_emitted": False, }, domain="mode", ) self._trace( traces, request_id, "state_update", "succeeded", f"长期模式“{restored_profile.name}”已恢复,可被后续自然语言请求命中。", time.perf_counter(), { "turn_count": updated_context["turn_count"], "mode_profile_id": restored_profile.id, }, domain="mode", ) self._persist(result) return result def _resolve_single_mode_profile( self, mode_name: str, vehicle_profile: VehicleProfile, result: dict[str, Any], traces: list[dict[str, Any]], request_id: str, ) -> ModeProfile | None: exact = self.mode_profiles.resolve_exact_name( mode_name, vehicle_id=vehicle_profile.id, ) matches = [exact] if exact is not None else [] result["matched_modes"] = [profile.public_dict() for profile in matches] if len(matches) == 1: return matches[0] result["status"] = "needs_confirmation" result["validation"] = [ { "severity": "error", "code": ( "MODE_PROFILE_NOT_FOUND" if not matches else "MODE_PROFILE_AMBIGUOUS" ), "path": "utterance.mode_profile", "message": ( f"未在车型 {vehicle_profile.id} 的长期模式库中找到“{mode_name}”。" if not matches else f"“{mode_name}”命中了多个长期模式,请指定更完整名称。" ), } ] self._trace( traces, request_id, "context_resolve", "failed", result["validation"][0]["message"], time.perf_counter(), { "mode_name": mode_name, "vehicle_id": vehicle_profile.id, "match_count": len(matches), }, domain="mode", ) return None def _mode_profile_control_report( self, request_id: str, operation: str, profile: ModeProfile, message: str, telemetry: dict[str, Any], ) -> dict[str, Any]: return { "apiVersion": "cognitive-agent/v1alpha1", "kind": "ExecutionReport", "metadata": { "id": f"report-{request_id}", "name": "Mode profile control report", "version": "1.0.0", "request_id": request_id, "labels": {"control_plane": "mode_profile_store"}, }, "spec": { "mode": "dry_run", "status": "succeeded", "started_at": _now(), "finished_at": _now(), "results": [ { "ref": f"{operation}-{profile.id}", "domain": "mode", "status": "succeeded", "message": message, "telemetry": { "operation": operation, "mode_profile_id": profile.id, "operation_count": len(profile.operations), "storage_scope": "repository_config", "can_command_emitted": False, **telemetry, }, } ], }, } def _display_path(self, path: Path) -> str: try: return str(path.relative_to(self.root)) except ValueError: return str(path) @staticmethod def _mode_feedback_text(value: object, *, limit: int = 180) -> str: text = re.sub(r"[\x00-\x1f\x7f]+", " ", str(value or "")) return re.sub(r"\s+", " ", text).strip(" ,,。;;::")[:limit] @classmethod def _mode_failure_issue( cls, clause: str, parsed: dict[str, Any], ) -> dict[str, str]: display_clause = cls._mode_feedback_text( re.sub(r"^(?:副驾座椅|主驾座椅)[,,]", "", clause), limit=120, ) ambiguities = [ item for item in parsed.get("ambiguities", []) if isinstance(item, dict) and str(item.get("field") or "") != "capability" ] ambiguity = ambiguities[0] if ambiguities else None field = str(ambiguity.get("field") or "") if ambiguity else "" question = cls._mode_feedback_text( ambiguity.get("question") if ambiguity else "", limit=220, ) # Parser questions may contain both the diagnosis and a concrete # correction. Split those parts so every public failure has the same # reason/next-step contract. reason_from_question = question next_from_question = "" for separator in (";请", "。请"): if separator in question: reason_from_question, suffix = question.split(separator, 1) next_from_question = f"请{suffix}" break if reason_from_question and not re.search( r"(?:CAN|CH[01]|JSON|schema|0x[0-9a-f]+|内部路径)", reason_from_question, re.IGNORECASE, ): reason = f"设置“{display_clause}”未通过:{reason_from_question}" else: reason = ( f"设置“{display_clause}”未被识别为当前支持的座舱控制动作" ) if next_from_question: next_step = next_from_question elif "temperature" in field or "温度" in display_clause: next_step = ( "请只保留一个18至32℃的整数温度,例如“空调调整到24度”,然后重试" ) elif field == "target" or "target_scope" in field: next_step = ( "请改用当前支持的副驾座椅目标,并明确部件与位置,例如" "“副驾腿托调整到80%”" ) elif "position" in field or re.search( r"座椅|靠背|椅背|坐垫|坐盆|腿托|脚托|滑轨", display_clause, ): next_step = ( "请按“副驾 + 座椅部件 + 方向或0到100%位置”重述,例如" "“副驾腿托向上调整到最大位置”或“副驾腿托调整到80%”" ) else: next_step = ( "请按“设备 + 动作 + 明确目标”重述,例如“关闭所有车窗”、" "“副驾靠背调整到80%”或“空调调整到24度”" ) return { "clause": display_clause, "reason": reason, "next_step": cls._mode_feedback_text(next_step, limit=260), } @classmethod def _mode_creation_failure_from_clauses( cls, clause_results: list[tuple[str, dict[str, Any], str]], unresolved_clauses: list[str], ) -> dict[str, Any]: unresolved = set(unresolved_clauses) issues = [ cls._mode_failure_issue(clause, parsed) for clause, parsed, status in clause_results if status != "complete" and clause in unresolved ][:3] if not issues: issues = [ cls._mode_failure_issue(clause, {}) for clause in unresolved_clauses[:3] ] reason = ";".join(dict.fromkeys(item["reason"] for item in issues)) next_step = ";".join( dict.fromkeys(item["next_step"] for item in issues) ) return { "code": "MODE_PROFILE_DRAFT_INCOMPLETE", "reason": reason or "部分座舱设置没有转换为可保存的控制动作", "next_step": next_step or "请将未识别设置改写为明确的设备、动作和目标值后重试", "unresolved_clauses": [item["clause"] for item in issues], "issues": issues, } @staticmethod def _scene_plan_signatures( assistant_plan: dict[str, Any], ) -> Counter[tuple[str, str, str]]: signatures: Counter[tuple[str, str, str]] = Counter() requests = assistant_plan.get("requests", []) if not isinstance(requests, list): return signatures for request in requests: if not isinstance(request, dict): continue domain = str(request.get("domain") or "") capability = str(request.get("capability") or "") target = str(request.get("target_scope") or "") if domain == "seat" and target == "front_right": target = "passenger" if domain and capability and target: signatures[(domain, capability, target)] += 1 return signatures @staticmethod def _scene_action_signatures( actions: list[dict[str, Any]], ) -> Counter[tuple[str, str, str]]: signatures: Counter[tuple[str, str, str]] = Counter() for action in actions: domain = str(action.get("domain") or "") capability = str(action.get("capability") or "") raw_target = action.get("target") target = ( str(raw_target.get("instance") or raw_target.get("zone") or "") if isinstance(raw_target, dict) else str(action.get("target_scope") or raw_target or "") ) if domain == "seat" and target == "front_right": target = "passenger" if domain and capability and target: signatures[(domain, capability, target)] += 1 return signatures @staticmethod def _scene_parameters_preserved( assistant_plan: dict[str, Any], actions: list[dict[str, Any]], ) -> bool: actual_parameters: dict[tuple[str, str, str], dict[str, Any]] = {} for action in actions: domain = str(action.get("domain") or "") capability = str(action.get("capability") or "") raw_target = action.get("target") target = ( str(raw_target.get("instance") or raw_target.get("zone") or "") if isinstance(raw_target, dict) else str(action.get("target_scope") or raw_target or "") ) if domain == "seat" and target == "front_right": target = "passenger" parameters = action.get("parameters") if domain and capability and target and isinstance(parameters, dict): actual_parameters[(domain, capability, target)] = parameters requests = assistant_plan.get("requests", []) if not isinstance(requests, list): return False for request in requests: if not isinstance(request, dict): return False domain = str(request.get("domain") or "") capability = str(request.get("capability") or "") target = str(request.get("target_scope") or "") if domain == "seat" and target == "front_right": target = "passenger" parameters = request.get("parameters") if not isinstance(parameters, list): return False expected = { str(parameter.get("name") or ""): parameter.get("value") for parameter in parameters if isinstance(parameter, dict) } actual = actual_parameters.get((domain, capability, target)) if actual is None or any(actual.get(name) != value for name, value in expected.items()): return False return True @staticmethod def _scrub_failed_scene_preview(result: dict[str, Any]) -> None: failed_intent = result.get("intent_ir") failed_spec = ( failed_intent.get("spec") if isinstance(failed_intent, dict) else None ) if isinstance(failed_spec, dict): failed_spec["actions"] = [] failed_spec["requests"] = [] result["deferred_requests"] = [] result["model_requests"] = [] result["function_config"] = None result["function_configs"] = {} result["command_plan"] = None result["command_plans"] = {} result["scene_config"] = None result["execution_report"] = None def _mode_creation_seat_failure( self, utterance: str, conversation_id: str, vehicle: VehicleProfile, reason: str, ) -> dict[str, Any]: result = { "request_id": f"req-{datetime.now(UTC).strftime('%Y%m%d%H%M%S')}-{uuid.uuid4().hex}", "utterance": utterance, "conversation_id": conversation_id, "vehicle": vehicle.public_dict(), "status": "validation_failed", "execution_mode": "dry_run", "hardware_execution_allowed": False, "orchestration_route": "mode_profile_create_preview", "trace_events": [], "validation": [{"severity": "error", "code": "MODE_SEAT_SCOPE_UNAVAILABLE", "path": "mode_profile_draft.seat_scope", "message": reason}], "mode_creation_failure": {"code": "MODE_SEAT_SCOPE_UNAVAILABLE", "reason": reason, "next_step": "请选择该模式实际支持的座位,或改用当前座位支持的舒适功能"}, "decision_summary": [reason, "没有保存模式或下发控制。"], } self._scrub_failed_scene_preview(result) self._persist(result) return result def _process_mode_profile_create( self, utterance: str, provider: str, conversation_id: str, execution_mode: str, approval_token: str | None, vehicle_profile: VehicleProfile, simulation_fault: str, mode_name: str, control_transport: str, assistant_plan: dict[str, Any] | None = None, source_draft: dict[str, Any] | None = None, ) -> dict[str, Any]: """Build and verify a named-mode draft for explicit user confirmation. A request such as ``构建一个K歌模式`` used to be interpreted as an ordinary temporary scene. Generate the scene through the normal dry-run path, but leave persistence to the existing mode-card API so the assistant can show the exact actions before the user saves them. """ create_parts = ( parse_mode_profile_create(utterance) if source_draft is None else None ) if source_draft is None and create_parts is None: raise ValueError("未识别到安全、明确的功能模式创建请求。") if ( create_parts is not None and normalize_mode_profile_name(create_parts[0]) != normalize_mode_profile_name(mode_name) ): raise ValueError("功能模式名称与创建请求不一致。") requested_instruction = create_parts[1] if create_parts is not None else "" selected_seats = creation_seat_scopes(utterance) if source_draft is None else None if selected_seats is not None and "unsupported" in selected_seats and re.search( r"座椅|座位|靠背|椅背|腿托|脚托|坐垫|坐盆|按摩|零重力", utterance ): return self._mode_creation_seat_failure( utterance, conversation_id, vehicle_profile, "当前模式只支持明确的主驾或副驾座椅,不会将未支持的座位替换为其他座位。", ) raw_plan_metadata = ( assistant_plan.get("metadata") if isinstance(assistant_plan, dict) else None ) scene_template = None if ( isinstance(raw_plan_metadata, dict) and raw_plan_metadata.get("source") == "scene_template" ): template_id = str(raw_plan_metadata.get("template_id") or "").strip() template_revision = str( raw_plan_metadata.get("template_revision") or "" ).strip() try: candidate_template = self.scene_templates.get_template(template_id) except KeyError as exc: raise ValueError("内置常用模式模板已不存在,请重新提交。") from exc exact_template = self.scene_templates.resolve_exact_name(mode_name) if ( candidate_template.revision != template_revision or exact_template is None or exact_template.id != candidate_template.id or requested_instruction or assistant_plan.get("requests") != candidate_template.plan().get("requests") or assistant_plan.get("schedule") != candidate_template.plan().get("schedule") ): raise ValueError("内置常用模式模板已更新,请重新提交。") scene_template = candidate_template source_existing_mode_id = ( str(source_draft.get("existing_mode_id") or "").strip() if isinstance(source_draft, dict) else "" ) if source_draft is not None and source_existing_mode_id: try: existing_profile = self.mode_profiles.get_profile( source_existing_mode_id, vehicle_profile.id, ) except KeyError: existing_profile = None elif source_draft is not None: existing_profile = None else: existing_profile = self.mode_profiles.resolve_exact_name( mode_name, vehicle_id=vehicle_profile.id, ) if scene_template is not None and existing_profile is not None: raise ValueError("同名功能模式已创建,请重新提交以载入最新模式。") source_profile = ( existing_profile if not requested_instruction and source_draft is None else None ) if source_profile is None and source_draft is None and not requested_instruction and scene_template is None: source_name = mode_creation_source_name(mode_name) if source_name != mode_name: source_profile = self.mode_profiles.resolve_exact_name(source_name, vehicle_id=vehicle_profile.id) draft_mode_name = ( str(source_draft.get("name") or mode_name).strip() if isinstance(source_draft, dict) else existing_profile.name if existing_profile is not None else mode_name ) generation_source = ( "draft_revision" if source_draft is not None else "scene_template_copy" if scene_template is not None else "registered_mode_profile" if source_profile is not None else "codex_scene_composition" if assistant_plan is not None and (not requested_instruction or needs_mode_schedule(utterance)) else "explicit_scene_requirements" ) # A name-only request may already describe a repository-backed scene. # Reuse that vehicle-scoped profile as trusted structured input instead # of appending a model prompt to the user's utterance. The latter was # being parsed as a second control clause on the assistant's local fast # path, turning a valid nine-action mode into an incomplete draft. source_utterance = requested_instruction or f"进入{draft_mode_name}" if requested_instruction and selected_seats is not None and len(selected_seats) == 1: # Qualify only explicit seat-device clauses, never an HVAC/window # zone or unrelated control. General one-shot controls keep their # existing target policy; this is scoped to new mode construction. label = "主驾" if selected_seats == {"driver"} else "副驾" instruction_clauses = self._split_intent_clauses(requested_instruction) qualified_clauses = [ f"{label}{clause}" if ( re.search(r"座椅|座位|靠背|椅背|腿托|脚托|坐垫|坐盆|按摩", clause) and resolve_explicit_seat_scopes(clause, assume_seat_context=True) is None ) else clause for clause in instruction_clauses ] # Preserve the user's punctuation and wording unless a seat really # needs qualification; explicit instructions need no rewriting. if qualified_clauses != instruction_clauses: source_utterance = ",".join(qualified_clauses) raw_schedule = (assistant_plan.get("schedule") if isinstance(assistant_plan, dict) else None) if source_profile is not None and source_profile.schedule is not None: raw_schedule = source_profile.schedule.to_dict() if raw_schedule is not None: if isinstance(assistant_plan, dict) and assistant_plan.get("ambiguities"): raise ValueError("动态需求仍有未解决的问题,不能生成部分成功的时间轴,请补充或调整需求。") if source_profile is None and source_draft is None and scene_template is None and not needs_mode_schedule(utterance): raise ValueError("用户未请求动态或分阶段模式,不能擅自生成定时任务。") schedule = ModeSchedule.from_dict(raw_schedule) if selected_seats is not None and (source_profile is not None or scene_template is not None): try: schedule = project_seed_schedule(schedule, selected_seats) except ModeCreationSeatPolicyError as exc: return self._mode_creation_seat_failure(utterance, conversation_id, vehicle_profile, str(exc)) return self._preview_mode_schedule( schedule, name=draft_mode_name, utterance=utterance, conversation_id=conversation_id, vehicle_profile=vehicle_profile, control_transport=control_transport, generation_source=generation_source, source_profile=existing_profile, source_template=scene_template, source_draft=source_draft, scene_scope_utterance="" if source_draft is not None else utterance, ) if needs_mode_schedule(utterance) and scene_template is None and not (source_profile is not None and source_profile.timeline): result = { "request_id": f"req-{datetime.now(UTC).strftime('%Y%m%d%H%M%S')}-{uuid.uuid4().hex}", "utterance": utterance, "conversation_id": conversation_id, "vehicle": vehicle_profile.public_dict(), "validation": [], "trace_events": [], "status": "validation_failed", "execution_mode": "dry_run", "hardware_execution_allowed": False, "orchestration_route": "mode_profile_create_preview", } result["mode_creation_failure"] = { "code": "MODE_SCHEDULE_REQUIRED", "reason": "该需求包含时间变化,但没有生成完整的分阶段时间轴", "next_step": "请重试,或补充每个阶段的设备目标和持续时间;未保存或执行静态替代动作", } self._scrub_failed_scene_preview(result) self._persist(result) return result try: if scene_template is not None and selected_seats is not None: assistant_plan = project_seed_plan(assistant_plan, selected_seats) source_result = self._process_once( source_utterance, provider, conversation_id, "dry_run", approval_token, vehicle_profile.id, simulation_fault, control_transport=control_transport, direct_mode_profile_id=( source_profile.id if source_profile is not None else None ), mode_creation_scopes=selected_seats, assistant_plan=assistant_plan, # ``source_utterance`` is synthesized for a name-only, unknown # scene. It must not fall back to substring matching (for example # ``我的小憩模式`` -> ``小憩模式``), otherwise the exact resolver's # ambiguity and collision checks can be bypassed. allow_fuzzy_mode_profile_match=bool(requested_instruction), allow_scene_composition=( assistant_plan is not None and source_profile is None and not requested_instruction ), # A revision combines a server-loaded draft with changes that # already passed the ordinary text/target authorization path. # Its old display name cannot veto an explicitly requested new # seat. This override is internal, never accepted from payloads. scene_scope_utterance="" if source_draft is not None else utterance, ) except ModeCreationSeatPolicyError as exc: return self._mode_creation_seat_failure(utterance, conversation_id, vehicle_profile, str(exc)) except NativeCanCompileError: # Repository modes and model-composed scenes share the same native # compiler. A stale mode parameter must become a bounded failed # preview, never an uncaught HTTP 500 or a partially usable card. failure_context = self._context_for_vehicle( self.conversations.snapshot(conversation_id), vehicle_profile, ) source_result = { "request_id": ( f"req-{datetime.now(UTC).strftime('%Y%m%d%H%M%S')}-" f"{uuid.uuid4().hex}" ), "utterance": source_utterance, "conversation_id": conversation_id, "conversation_turn": failure_context["turn_count"], "status": "validation_failed", "provider_requested": provider, "provider_used": "mode_profile_native_can_guard", "execution_mode": "dry_run", "control_transport": control_transport, "hardware_execution_allowed": False, "vehicle": vehicle_profile.public_dict(), "matched_modes": ( [source_profile.public_dict()] if source_profile is not None else [] ), "mode_profile": None, "decision_summary": [ f"“{draft_mode_name}”包含当前车型协议无法编译的动作,未生成草稿。" ], "deferred_requests": [], "model_requests": [], "unresolved_clauses": [], "intent_ir": None, "function_config": None, "function_configs": {}, "scene_config": None, "preset_config": None, "validation": [ { "severity": "error", "code": "MODE_PROFILE_NATIVE_COMPILE_FAILED", "path": "mode_profile_draft", "message": "模式动作无法编译为当前车型支持的控制指令。", } ], "command_plan": None, "command_plans": {}, "execution_report": None, "actuator_state": failure_context["devices"], "domain_state": failure_context["domain_devices"], "saved_presets": [], "trace_events": [], "can_commands": [], "orchestration_route": "mode_profile_create_preview", "mode_creation_failure": { "code": "MODE_PROFILE_NATIVE_COMPILE_FAILED", "reason": "模式动作未通过当前车型 CAN 协议编译校验", "next_step": "请检查模式中的设备参数是否符合当前车型能力范围", "unresolved_clauses": [], "issues": [], }, } self._persist(source_result) return source_result draft_context = self._context_for_vehicle( self.conversations.snapshot(conversation_id), vehicle_profile, ) draft_context["mode_profiles"] = self.mode_profiles.public_modes( vehicle_profile.id ) local_clause_results = self._local_clause_results( source_utterance, draft_context, ) unresolved_draft_clauses = [ clause for clause, _parsed, status in local_clause_results if status != "complete" and not ( (requested_instruction or source_profile is not None) and self.mode_profiles.match_text( clause, vehicle_id=vehicle_profile.id, ) ) ] if provider == "local" and assistant_plan is None and unresolved_draft_clauses: source_result["status"] = "needs_confirmation" source_result["unresolved_clauses"] = unresolved_draft_clauses source_result["mode_creation_failure"] = ( self._mode_creation_failure_from_clauses( local_clause_results, unresolved_draft_clauses, ) ) source_result["supplement_required"] = False source_result["validation"] = [ *list(source_result.get("validation", [])), { "severity": "error", "code": "MODE_PROFILE_DRAFT_INCOMPLETE", "path": "mode_profile_draft.instruction", "message": ( "以下模式设置尚未完整解析:" + ";".join(unresolved_draft_clauses) ), }, ] source_result["decision_summary"] = [ f"未能完整构建“{mode_name}”,因此没有生成可保存草稿。", *list(source_result.get("decision_summary", [])), "请补充为当前支持的明确设备动作后重试。", ] self._persist(source_result) return source_result source_intent = source_result.get("intent_ir") source_spec = ( source_intent.get("spec") if isinstance(source_intent, dict) else None ) source_actions = ( source_spec.get("actions", []) if isinstance(source_spec, dict) else [] ) typed_source_actions = [ action for action in source_actions if isinstance(action, dict) ] if isinstance(source_actions, list) else [] if ( source_result.get("status") == "succeeded" and generation_source in { "codex_scene_composition", "draft_revision", "scene_template_copy", } and assistant_plan is not None and ( self._scene_plan_signatures(assistant_plan) != self._scene_action_signatures(typed_source_actions) or not self._scene_parameters_preserved( assistant_plan, typed_source_actions, ) ) ): source_result["status"] = "validation_failed" source_result["validation"] = [ *list(source_result.get("validation", [])), { "severity": "error", "code": "MODE_PROFILE_SCENE_PLAN_INCOMPLETE", "path": "intent_ir.spec.actions", "message": ( "小迪生成的场景候选动作未全部进入当前车型的可复用执行链," "已拒绝生成部分功能模式。" ), }, ] if source_result.get("status") != "succeeded": if generation_source in { "codex_scene_composition", "draft_revision", "scene_template_copy", }: # Final fail-closed barrier for errors discovered after the # plan guard (adapter, schema, compiler, or safety validation). # A failed reusable scene must not expose a trial-able subset. self._scrub_failed_scene_preview(source_result) source_result["mode_creation_failure"] = { "code": "MODE_PROFILE_ACTION_VALIDATION_FAILED", "reason": "场景动作未通过车辆能力或安全校验", "next_step": ( "请检查每项设备目标是否在当前支持范围内;仍失败时可分条输入后重试" ), "unresolved_clauses": [], "issues": [], } source_result["decision_summary"] = [ f"未能完成“{mode_name}”的场景动作验证,因此没有创建长期模式。", *list(source_result.get("decision_summary", [])), ] self._persist(source_result) return source_result intent_ir = source_result.get("intent_ir") intent_spec = intent_ir.get("spec") if isinstance(intent_ir, dict) else None raw_intent_actions = ( intent_spec.get("actions", []) if isinstance(intent_spec, dict) else [] ) reusable_actions = [ item for item in raw_intent_actions if isinstance(item, dict) ] if isinstance(raw_intent_actions, list) else [] if not reusable_actions: source_result["status"] = "needs_confirmation" source_result["mode_creation_failure"] = { "code": "MODE_PROFILE_DRAFT_EMPTY", "reason": "没有识别到可保存的座舱控制动作", "next_step": ( "请按“设备 + 动作 + 明确目标”补充,例如“关闭所有车窗,空调调整到24度”" ), "unresolved_clauses": [], "issues": [], } source_result["validation"] = [ { "severity": "error", "code": "MODE_PROFILE_DRAFT_EMPTY", "path": "intent_ir.spec.actions", "message": "没有生成可保存的功能模式动作,请补充更明确的座舱设置。", } ] source_result["decision_summary"] = [ f"未能为“{mode_name}”生成可保存的场景动作。", *list(source_result.get("decision_summary", [])), ] self._persist(source_result) return source_result domains = list( dict.fromkeys( str(item.get("domain") or "") for item in reusable_actions if str(item.get("domain") or "") ) ) source_request_id = str(source_result.get("request_id") or "") source_existing_revision = ( str(source_draft.get("existing_mode_revision") or "").strip() if isinstance(source_draft, dict) else "" ) existing_binding = ( { "existing_mode_id": source_existing_mode_id, "existing_mode_revision": source_existing_revision, } if source_draft is not None and source_existing_mode_id else { "existing_mode_id": existing_profile.id, "existing_mode_revision": ( self.mode_profiles.profile_revision(existing_profile) ), } if existing_profile is not None else {} ) source_template_binding = ( { "source_template_id": scene_template.id, "source_template_revision": scene_template.revision, } if scene_template is not None else { "source_template_id": str( source_draft.get("source_template_id") or "" ).strip(), "source_template_revision": str( source_draft.get("source_template_revision") or "" ).strip(), } if isinstance(source_draft, dict) and source_draft.get("source_template_id") and source_draft.get("source_template_revision") else {} ) raw_source_revision = ( source_draft.get("draft_revision") if isinstance(source_draft, dict) else None ) source_revision = ( raw_source_revision if isinstance(raw_source_revision, int) and not isinstance(raw_source_revision, bool) and raw_source_revision >= 1 else 1 if source_draft is not None else 0 ) source_result["mode_profile_draft"] = { "name": draft_mode_name, "instruction": source_utterance, "source_request_id": source_request_id, "vehicle_id": vehicle_profile.id, "domains": domains, "operation_count": len(reusable_actions), **existing_binding, **source_template_binding, "generation_source": generation_source, "draft_revision": source_revision + 1, **( { "revises_source_request_id": str( source_draft.get("source_request_id") or "" ).strip() } if isinstance(source_draft, dict) else {} ), } source_result["mode_creation"] = { "requested_utterance": utterance, "source_utterance": source_utterance, "source_request_id": source_request_id, "source_status": source_result.get("status"), "source_provider": source_result.get("provider_used"), "operation_count": len(reusable_actions), **existing_binding, **source_template_binding, "generation_source": generation_source, "draft_revision": source_revision + 1, } source_result["orchestration_route"] = "mode_profile_create_preview" source_result["decision_summary"] = [ ( f"已从当前车型的工程模式目录载入并验证“{source_profile.name}”的" f" {len(reusable_actions)} 项设备动作。" if source_profile is not None else f"已从内置常用模式模板载入并验证“{draft_mode_name}”的" f" {len(reusable_actions)} 项设备动作。" if generation_source == "scene_template_copy" else f"小迪已根据场景语义组合并验证“{draft_mode_name}”的工程能力。" if generation_source == "codex_scene_composition" else f"已调整并重新校验待保存的“{draft_mode_name}”模式草稿。" if generation_source == "draft_revision" else f"已构建并验证“{mode_name}”场景。" ), *list(source_result.get("decision_summary", [])), ( "可先进行一次模拟试运行,确认动作后更新现有功能模式。" if existing_profile is not None else "请确认动作后保存为功能模式,或先进行一次模拟试运行。" ), ] # Overwrite the persisted source result with the draft metadata. The # mode-card endpoint later re-reads this server-owned artifact and # refuses any client-side instruction or request-id substitution. self._persist(source_result) return source_result def _process_mode_profile_save( self, utterance: str, request_id: str, conversation_id: str, context: dict[str, Any], mode_name: str, vehicle_profile: VehicleProfile, result: dict[str, Any], traces: list[dict[str, Any]], ) -> dict[str, Any]: started = time.perf_counter() result["provider_used"] = "mode_profile_registry" result["decision_summary"] = [ f"识别为长期模式固化请求,目标名称为“{mode_name}”。" ] self._trace( traces, request_id, "intent_parse", "succeeded", "识别到长期模式配置保存请求;不调用 CAN 编译路径。", started, {"mode_name": mode_name}, domain="mode", ) source_actions, mode_operations = self._last_mode_source(context) if not mode_operations: result["status"] = "needs_confirmation" result["validation"] = [ { "severity": "error", "code": "NO_RECENT_MODE_SOURCE", "path": "conversation.recent_turns", "message": "当前会话没有可固化为长期模式的最近成功动作。", } ] self._persist(result) return result profile = self.mode_profiles.save_mode( mode_name, mode_operations, vehicle_id=vehicle_profile.id, ) profile_public = profile.public_dict() profile_path = Path(profile.source) try: source_path = str(profile_path.relative_to(self.root)) except ValueError: source_path = str(profile_path) result["mode_profile"] = { **profile_public, "source": source_path, } result["matched_modes"] = [profile_public] result["decision_summary"].append( f"已将最近一次成功动作固化为长期模式“{profile.name}”。" ) result["model_requests"] = self.mode_profiles.requests_for_profiles([profile]) generation_started = time.perf_counter() self._trace( traces, request_id, "function_generate", "succeeded", "已写入长期模式 YAML;该配置将在后续请求中重新生成 FunctionConfig。", generation_started, { "mode_profile_id": profile.id, "operation_count": len(profile.operations), "source": source_path, }, domain="mode", ) execution_report = { "apiVersion": "cognitive-agent/v1alpha1", "kind": "ExecutionReport", "metadata": { "id": f"report-{request_id}", "name": "Mode profile save report", "version": "1.0.0", "request_id": request_id, "labels": {"control_plane": "mode_profile_store"}, }, "spec": { "mode": "dry_run", "status": "succeeded", "started_at": _now(), "finished_at": _now(), "results": [ { "ref": f"save-{profile.id}", "domain": "mode", "status": "succeeded", "message": f"已保存长期模式“{profile.name}”。", "telemetry": { "operation": "save_mode_profile", "mode_profile_id": profile.id, "operation_count": len(profile.operations), "storage_scope": "repository_config", "source": source_path, "can_command_emitted": False, }, } ], }, } validation = self.contracts.validate(execution_report) result["validation"] = validation result["execution_report"] = execution_report if validation: result["status"] = "execution_report_invalid" self._persist(result) return result updated_context = self.conversations.save_mode_profile( conversation_id, utterance, result["mode_profile"], source_actions, result["decision_summary"], ) result["status"] = "succeeded" result["conversation_turn"] = updated_context["turn_count"] result["actuator_state"] = updated_context["devices"] result["domain_state"] = updated_context["domain_devices"] self._trace( traces, request_id, "execute", "succeeded", "长期模式保存属于控制平面操作,未生成或发送 CAN 帧。", time.perf_counter(), { "mode": "control_plane", "mode_profile_id": profile.id, "can_command_emitted": False, }, domain="mode", ) self._trace( traces, request_id, "state_update", "succeeded", f"长期模式“{profile.name}”已登记,可被后续自然语言请求命中。", time.perf_counter(), { "turn_count": updated_context["turn_count"], "mode_profile_id": profile.id, }, domain="mode", ) self._persist(result) return result def _last_mode_source( self, context: dict[str, Any], ) -> tuple[list[dict[str, Any]], list[dict[str, Any]]]: for turn in reversed(context.get("recent_turns", [])): actions = turn.get("actions", []) if not isinstance(actions, list): continue operations = self._mode_operations_from_actions(actions) if operations: return deepcopy(actions), operations return [], [] def _mode_operations_from_actions( self, actions: list[dict[str, Any]], ) -> list[dict[str, Any]]: operations = [] for action in actions: operation = self._mode_operation_from_action(action) if operation is not None: operations.append(operation) return operations def _mode_operation_from_action( self, action: dict[str, Any], ) -> dict[str, Any] | None: domain = action.get("domain") if domain in self.domain_runtimes or self.native_can_runtime.supports(action): return { "domain": domain, "capability": action["capability"], "target_scope": action["target"], "operation": action.get("operation", "set"), "parameters": dict(action.get("parameters", {})), "reason": action.get("reason", "用户固化的跨域模式动作。"), } if action.get("action_type") == "position": capability = SEAT_AXIS_TO_CAPABILITY.get(str(action.get("axis"))) if capability is None: return None parameters = {"position_pct": action["position_pct"]} if action.get("angle_deg") is not None: parameters["angle_deg"] = action["angle_deg"] return { "domain": "seat", "capability": capability, "target_scope": action["target"], "operation": "set", "parameters": parameters, "reason": "用户固化的座椅位置模式动作。", } if action.get("action_type") == "dynamic_mode_stop": return { "domain": "seat", "capability": DYNAMIC_MODE_STOP_CAPABILITY, "target_scope": action["target"], "operation": "deactivate", "parameters": {}, "reason": "用户固化的动态座椅停止动作。", } if action.get("action_type") == "dynamic_mode": return { "domain": "seat", "capability": "seat.start_dynamic_mode", "target_scope": action["target"], "operation": "set", "parameters": {"mode_id": int(action["mode_id"])}, "reason": "用户固化的固件原生动态座椅模式。", } function = action.get("function") capability = SEAT_FUNCTION_TO_CAPABILITY.get(str(function)) if capability is None: return None parameters = {"state": action.get("state")} if action.get("requested_level") is not None: parameters["level"] = action["requested_level"] return { "domain": "seat", "capability": capability, "target_scope": action["target"], "operation": "set", "parameters": parameters, "reason": "用户固化的座椅舒适功能模式动作。", } def _partition_native_actions( self, parsed: dict[str, Any], *, promote_supported: bool = False ) -> dict[str, Any]: """Promote verified CAN_DEV capabilities and leave unsupported domains virtual.""" result = deepcopy(parsed) candidate_native_actions = list(result.get("native_can_actions", [])) native_actions = [ action for action in candidate_native_actions if self.native_can_runtime.supports(action) ] native_signatures = { ( str(action.get("domain")), str(action.get("capability")), str(action.get("target", action.get("target_scope", "vehicle"))), json.dumps(action.get("parameters", {}), sort_keys=True, ensure_ascii=False), ) for action in candidate_native_actions } virtual_actions: list[dict[str, Any]] = [] demoted_signatures: set[tuple[str, str, str, str]] = set() for action in candidate_native_actions: if str(action.get("capability")) not in UNVERIFIED_NATIVE_CAPABILITIES: continue demoted = dict(action) demoted["execution_backend"] = "configured_domain" virtual_actions.append(demoted) demoted_signatures.add( ( str(action.get("domain")), str(action.get("capability")), str(action.get("target", action.get("target_scope", "vehicle"))), json.dumps(action.get("parameters", {}), sort_keys=True, ensure_ascii=False), ) ) for action in result.get("domain_actions", []): signature = ( str(action.get("domain")), str(action.get("capability")), str(action.get("target", action.get("target_scope", "vehicle"))), json.dumps(action.get("parameters", {}), sort_keys=True, ensure_ascii=False), ) if signature in native_signatures: continue if promote_supported and self.native_can_runtime.supports(action): promoted = dict(action) promoted["execution_backend"] = "native_can" native_actions.append(promoted) else: virtual_actions.append(action) deduplicated: list[dict[str, Any]] = [] signatures: set[tuple[str, str, str, str]] = set() for action in native_actions: signature = ( str(action.get("domain")), str(action.get("capability")), str(action.get("target", action.get("target_scope", "vehicle"))), json.dumps(action.get("parameters", {}), sort_keys=True, ensure_ascii=False), ) if signature not in signatures: signatures.add(signature) deduplicated.append(action) result["domain_actions"] = virtual_actions result["native_can_actions"] = deduplicated detected_domains = list(result.get("detected_domains", [])) if result.get("actions") and "seat" not in detected_domains: detected_domains.insert(0, "seat") for action in [*virtual_actions, *deduplicated]: if action["domain"] not in detected_domains: detected_domains.append(action["domain"]) result["detected_domains"] = detected_domains deferred_requests = [ item for item in result.get("deferred_requests", []) if not ( item.get("execution_backend") == "native_can" and ( self.native_can_runtime.supports(item) or ( str(item.get("domain")), str(item.get("capability")), str(item.get("target", item.get("target_scope", "vehicle"))), json.dumps(item.get("parameters", {}), sort_keys=True, ensure_ascii=False), ) in demoted_signatures ) ) ] for item in deferred_requests: if item.get("capability") == "door.set_vehicle_lock": item["execution_backend"] = "unsupported" item["rejection_code"] = "CH1_DOOR_LOCK_CONTROL_UNSUPPORTED" item["reason"] = ( "Current CH1 whole-vehicle door-lock control is unsupported; " "lock feedback is observational only and no verified physical " "command will be built." ) result["deferred_requests"] = deferred_requests return result @staticmethod def _defer_nonwhitelisted_wireless_actions( parsed: dict[str, Any], ) -> dict[str, Any]: """Keep approved MQTT actions executable when a model adds virtual ones.""" result = deepcopy(parsed) deferred_requests = list(result.get("deferred_requests", [])) existing = { ( str(item.get("domain", "")), str(item.get("capability", "")), str(item.get("target", item.get("target_scope", "vehicle"))), ) for item in deferred_requests } for action in result.get("domain_actions", []): target = str( action.get("target", action.get("target_scope", "vehicle")) ) signature = ( str(action.get("domain", "unknown")), str(action.get("capability", "unknown")), target, ) if signature in existing: continue parameters = dict(action.get("parameters", {})) requested_value: Any = parameters if len(parameters) == 1: requested_value = next(iter(parameters.values())) deferred_requests.append( { "domain": signature[0], "capability": signature[1], "target_scope": target, "desired_change": str(action.get("operation", "set")), "requested_value": requested_value, "parameters": parameters, "reason": ( "该模型动作不在无线 MQTT CAN 控制白名单中,已单独跳过;" "同一句中的白名单动作继续发布。" ), "execution_backend": "unsupported", "rejection_code": "WIRELESS_MQTT_CONTROL_NOT_WHITELISTED", } ) existing.add(signature) result["domain_actions"] = [] result["deferred_requests"] = deferred_requests return result @staticmethod def _emit_validated_commands( result: dict[str, Any], callback: Callable[[dict[str, Any]], None] | None, ) -> None: """Expose fully compiled commands before physical execution begins.""" if callback is None: return event = deepcopy(result) event["status"] = "processing" event["phase"] = "commands_validated" event["execution_report"] = None callback(event) def _authorize_compiled_hardware_delivery( self, parsed: dict[str, Any], approval_token: str | None, control_transport: str, ) -> None: """Authorize physical transports after commands are safe to display. Parsing, schema validation and deterministic CAN compilation do not transmit frames. Keeping transport readiness checks after the ``commands_validated`` callback lets every UI target show the same parsed commands, while these gates still run before any executor can open a CAN or MQTT transmission path. """ if parsed.get("actions"): ( self.vehicle_mqtt_settings if control_transport == WIRELESS_MQTT_TRANSPORT else self.hardware_settings ).authorize(approval_token) if parsed.get("native_can_actions"): ( self.vehicle_mqtt_settings if control_transport == WIRELESS_MQTT_TRANSPORT else self.vehicle_can_settings ).authorize(approval_token) def _process_orchestrated( self, utterance: str, request_id: str, provider_used: str, conversation_id: str, context: dict[str, Any], parsed: dict[str, Any], result: dict[str, Any], traces: list[dict[str, Any]], *, hardware_request: bool = False, simulated_hardware_request: bool = False, simulation_fault: str = "normal", vehicle_profile: VehicleProfile | None = None, control_transport: str = WIRED_CAN_TRANSPORT, approval_token: str | None = None, on_commands_validated: Callable[[dict[str, Any]], None] | None = None, ) -> dict[str, Any]: """Run isolated adapters under the selected physical-bus policy.""" vehicle_profile = vehicle_profile or self.vehicle_profiles.default() generation_started = time.perf_counter() intent_ir = build_intent_ir( parsed, utterance, request_id, provider_used, self.semantic_capability_metadata, ) intent_actions = [ *parsed.get("domain_actions", []), *parsed.get("native_can_actions", []), ] for action in intent_actions: if action in parsed.get("native_can_actions", []): capability = self.native_can_runtime.capabilities[action["capability"]] else: capability = self.domain_runtimes[action["domain"]].capabilities[ action["capability"] ] intent_ir["spec"]["requests"].append( { "domain": action["domain"], "goal": ( f"{action['operation']} {action['capability']} " f"for {action['target']}" ), "target": { "device": capability["target_devices"][0], "zone": action["target"], }, "parameters": { "capability": action["capability"], **action["parameters"], }, "source_span": utterance, } ) result["intent_ir"] = intent_ir if intent_ir["spec"]["requires_confirmation"]: validation = self.contracts.validate(intent_ir) validation.extend( { "severity": "error", "code": "AMBIGUOUS_INTENT", "path": item.get("field", "utterance"), "message": item.get("question", "控制目标需要确认。"), } for item in intent_ir["spec"]["ambiguities"] ) validation.extend( { "severity": "error", "code": "UNRESOLVED_UTTERANCE_SPAN", "path": "utterance", "message": ( f"未解析原文“{item['text']}”:{item['reason']}。" ), } for item in intent_ir["spec"]["unresolved_spans"] ) result["validation"] = validation result["status"] = "needs_confirmation" self._trace( traces, request_id, "safety_check", "failed", "语义意图存在歧义或未覆盖片段;未进入 CAN 编译与执行。", time.perf_counter(), { "ambiguity_count": len(intent_ir["spec"]["ambiguities"]), "unresolved_span_count": len(intent_ir["spec"]["unresolved_spans"]), "execution_eligible": False, }, domain="orchestration", ) self._persist(result) return result configs: dict[str, dict[str, Any]] = {} if parsed.get("actions"): seat_config = build_function_config(parsed, request_id, provider_used) if seat_config is not None: configs["seat"] = seat_config domain_actions: dict[str, list[dict[str, Any]]] = {} for action in parsed.get("domain_actions", []): domain_actions.setdefault(action["domain"], []).append(action) for domain in ("climate", "roof", "ambient_light"): if domain in domain_actions: configs[domain] = self.domain_runtimes[domain].build_function_config( domain_actions[domain], request_id, provider_used, ) native_config_keys: set[str] = set() native_actions_by_domain: dict[str, list[dict[str, Any]]] = {} for action in parsed.get("native_can_actions", []): native_actions_by_domain.setdefault(action["domain"], []).append(action) for domain, actions in native_actions_by_domain.items(): key = f"native_{domain}" native_config_keys.add(key) configs[key] = self.native_can_runtime.build_function_config( domain, actions, request_id, provider_used, ) result["function_configs"] = configs result["function_config"] = ( configs.get("seat") or next(iter(configs.values()), None) ) self._trace( traces, request_id, "function_generate", "succeeded" if configs else "waiting_confirmation", ( f"已按领域边界生成 {len(configs)} 份 FunctionConfig。" if configs else "没有足够的绝对目标生成可执行配置。" ), generation_started, { "domains": list(configs), "operation_count": sum( len(config["spec"]["operations"]) for config in configs.values() ), }, domain="orchestration", ) validation_started = time.perf_counter() validation = self.contracts.validate(intent_ir) for config in configs.values(): validation.extend(self.contracts.validate(config)) if parsed.get("actions"): validation.extend(validate_semantics(parsed, configs.get("seat"))) else: for ambiguity in parsed.get("ambiguities", []): validation.append( { "severity": "error", "code": "AMBIGUOUS_INTENT", "path": ambiguity["field"], "message": ambiguity["question"], } ) if not native_config_keys: validation.append( { "severity": "warning", "code": "VIRTUAL_EXECUTION_ONLY", "path": "platform.reference_vehicle", "message": "当前三域矩阵仅允许虚拟执行,禁止实车下发。", } ) if not configs and not parsed.get("ambiguities") and not parsed.get( "deferred_requests" ): validation.append( { "severity": "error", "code": "NO_EXECUTABLE_OPERATION", "path": "spec.operations", "message": "模型没有生成任何可执行领域操作。", } ) if hardware_request: self._adapt_validation_for_hardware(validation) result["validation"] = validation errors = [issue for issue in validation if issue["severity"] == "error"] self._trace( traces, request_id, "schema_validate", "failed" if errors else "succeeded", f"公共和领域 Schema 校验{'发现错误' if errors else '通过'}。", validation_started, {"error_count": len(errors), "document_count": len(configs) + 1}, domain="orchestration", ) if errors: result["status"] = ( "needs_confirmation" if any(issue["code"] == "AMBIGUOUS_INTENT" for issue in errors) else "validation_failed" ) self._persist(result) return result if parsed.get("deferred_requests") and not configs: result["status"] = "capability_unavailable" self._persist(result) return result compile_started = time.perf_counter() plans: dict[str, dict[str, Any]] = {} wireless_hardware_request = ( hardware_request and control_transport == WIRELESS_MQTT_TRANSPORT ) for domain, config in configs.items(): if domain == "seat": if hardware_request: if not wireless_hardware_request: self.hardware_settings.validate_function_config(config) plans[domain] = self.hardware_compiler.compile( config, context["devices"], ) else: plans[domain] = self.compiler.compile(config) elif domain in native_config_keys: plans[domain] = self.native_can_runtime.compile(config) else: runtime = self.domain_runtimes[domain] plans[domain] = runtime.compile(config) validation.extend(self.contracts.validate(plans[domain])) result["command_plans"] = plans result["command_plan"] = plans.get("seat") or next( iter(plans.values()), None ) scene_config = ( self._build_scene_config(request_id, configs, plans) if len(configs) > 1 else None ) result["scene_config"] = scene_config if scene_config is not None: validation.extend(self.contracts.validate(scene_config)) compile_errors = [ issue for issue in validation if issue["severity"] == "error" ] self._trace( traces, request_id, "compile", "failed" if compile_errors else "succeeded", ( f"{len(configs)} 份配置已编译为相互隔离的 CAN 命令计划" + (",并生成 SceneConfig。" if scene_config else "。") ), compile_started, { "domains": list(plans), "command_count": sum( len(plan["spec"]["commands"]) for plan in plans.values() ), "scene_generated": scene_config is not None, }, domain="orchestration", ) if compile_errors: result["status"] = "compile_failed" self._persist(result) return result self._emit_validated_commands(result, on_commands_validated) if hardware_request: self._authorize_compiled_hardware_delivery( parsed, approval_token, control_transport, ) self._trace( traces, request_id, "safety_check", "succeeded", ( "所有动作已通过无线 MQTT 能力、参数及 CAN 帧白名单门禁。" if ( hardware_request and control_transport == WIRELESS_MQTT_TRANSPORT ) else "所有动作已通过 CH1 车身 CAN 白名单和物理反馈门禁。" if hardware_request and native_config_keys and "seat" not in configs else "副驾 T2G 台架动作已通过独立硬件安全门。" if hardware_request else ( "模拟台架仅对座椅结果注入故障;全部领域的物理发送路径保持关闭。" if simulated_hardware_request else "所有领域均为 dry-run;矩阵缺少实车完整性信息,硬件发送保持禁用。" ) ), time.perf_counter(), { "hardware_execution_allowed": hardware_request, "simulated_hardware": simulated_hardware_request, "simulation_fault": ( simulation_fault if simulated_hardware_request else None ), "hardware_domains": ( [configs[key]["spec"]["domain"] for key in native_config_keys] + (["seat"] if "seat" in configs else []) if hardware_request else [] ), "virtual_domains": [ domain for domain in configs if not hardware_request ], "domains": list(configs), }, domain="orchestration", ) execute_started = time.perf_counter() execution_started_at = _now() reports = self._execute_orchestrated_reports( configs, plans, native_config_keys, context, hardware_request=hardware_request, simulated_hardware_request=simulated_hardware_request, simulation_fault=simulation_fault, control_transport=control_transport, ) for domain_report in reports.values(): _annotate_execution_report(domain_report) report_results = [ item for domain_report in reports.values() for item in domain_report["spec"]["results"] ] operation_results = [ item for item in report_results if item.get("ref") != "seat-controller-firmware" ] operation_domains = { str(item.get("domain")) for item in operation_results } successful_domains = { domain for domain in operation_domains if all( _operation_verification_status(item) != "failed_or_blocked" for item in operation_results if str(item.get("domain")) == domain ) } failed_domains = { str(item.get("domain")) for item in operation_results if _operation_verification_status(item) == "failed_or_blocked" } execution_succeeded = not failed_domains execution_partially_succeeded = bool(successful_domains and failed_domains) execution_outcome = ( "failed_or_blocked" if failed_domains else "submitted_unverified" if any( _operation_verification_status(item) == "submitted_unverified" for item in operation_results ) else "verified_success" ) verified_domains = { domain for domain in operation_domains if all( _operation_verification_status(item) == "verified_success" for item in operation_results if str(item.get("domain")) == domain ) } physical_state = ( reports.get("seat", {}).get("spec", {}).get("physical_state") if hardware_request else None ) simulated_seat = simulated_hardware_request and "seat" in reports report_spec: dict[str, Any] = { "mode": "hil" if hardware_request or simulated_seat else "dry_run", "status": ( "partially_succeeded" if execution_partially_succeeded else "succeeded" if execution_succeeded else "failed" ), "verification_status": execution_outcome, "started_at": execution_started_at, "finished_at": _now(), "results": report_results, } if isinstance(physical_state, dict): report_spec["physical_state"] = physical_state execution_report = { "apiVersion": "cognitive-agent/v1alpha1", "kind": "ExecutionReport", "metadata": { "id": f"report-{request_id}", "name": ( "Cabin orchestration mixed HIL execution report" if hardware_request else ( "Cabin orchestration simulated hardware execution report" if simulated_seat else "Cabin orchestration virtual execution report" ) ), "version": "1.0.0", "request_id": request_id, "labels": { "simulation": "mixed" if hardware_request else "true", "seat_feedback": ( "wireless_mqtt_ch0" if wireless_hardware_request and "seat" in configs else "wireless_mqtt_ch1" if wireless_hardware_request and native_config_keys else "physical_can_ch1" if hardware_request and native_config_keys and "seat" not in configs else "physical_can" if hardware_request else ( "simulated_hardware" if simulated_seat else "simulated" ) ), "fault_scenario": ( simulation_fault if simulated_seat else "none" ), "orchestration": "cross_domain" if len(configs) > 1 else "single_domain", }, }, "spec": report_spec, } report_issues = self.contracts.validate(execution_report) validation.extend(report_issues) result["execution_report"] = execution_report result["execution_outcome"] = execution_outcome result["validation"] = validation if report_issues: result["status"] = "execution_report_invalid" elif execution_partially_succeeded: result["status"] = "partially_succeeded" elif not execution_succeeded: result["status"] = "execution_failed" elif parsed.get("deferred_requests"): result["status"] = "partially_succeeded" else: result["status"] = "succeeded" failed_execution = next( (item for item in report_results if item.get("status") == "failed"), None, ) self._trace( traces, request_id, "execute", "succeeded" if execution_succeeded and not report_issues else "failed", ( "无线 MQTT CAN0/CAN1 白名单控制帧已提交;" "车辆实际状态以状态话题回读为准。" if wireless_hardware_request and execution_succeeded else "有线 CAN0/CAN1 白名单控制帧已提交;" "当前反馈窗口未确认车辆效果,状态以实时 CAN 回读为准。" if hardware_request and execution_succeeded and execution_outcome == "submitted_unverified" else "CH1 车身 CAN 动作已按目标字段物理回读闭环执行。" if hardware_request and native_config_keys and execution_succeeded else "副驾座椅已按独立 T2G 物理 CAN 回读闭环执行。" if hardware_request and execution_succeeded else ( ( "跨域指令部分执行成功;成功域=" f"{','.join(sorted(successful_domains))},失败域=" f"{','.join(sorted(failed_domains))}:" f"{failed_execution['message']}" ) if execution_partially_succeeded and failed_execution else f"真实 CAN 执行失败:{failed_execution['message']}" if hardware_request and failed_execution else ( f"模拟台架已执行 {len(report_results)} 个领域操作,物理 CAN 未打开。" if simulated_seat and execution_succeeded else ( f"模拟台架故障已触发:{failed_execution['message']}" if simulated_seat and failed_execution else ( "本次没有座椅动作,故障注入未应用;" f"已虚拟执行 {len(report_results)} 个领域操作。" if simulated_hardware_request else f"已虚拟执行 {len(report_results)} 个领域操作并汇总反馈。" ) ) ) ) ), execute_started, { "mode": execution_report["spec"]["mode"], "simulation": execution_report["metadata"]["labels"]["simulation"], "domains": list(configs), "result_count": len(report_results), "failed_domain": ( failed_execution.get("domain") if failed_execution else None ), "successful_domains": sorted(successful_domains), "failed_domains": sorted(failed_domains), "partially_succeeded": execution_partially_succeeded, "execution_outcome": execution_outcome, }, domain="orchestration", ) if (execution_succeeded or execution_partially_succeeded) and not report_issues: seat_actions = ( parsed.get("actions", []) if execution_succeeded or "seat" in successful_domains else [] ) if hardware_request and seat_actions and "seat" in reports: seat_actions = self._hardware_feedback_actions( seat_actions, configs["seat"], reports["seat"], ) all_actions = [ *seat_actions, *[ action for action in parsed.get("domain_actions", []) if ( not hardware_request and ( execution_succeeded or str(action.get("domain")) in successful_domains ) ) or ( hardware_request and str(action.get("domain")) in verified_domains ) ], *[ { **action, **( {"feedback_source": "physical_can_ch1"} if hardware_request else {} ), } for action in parsed.get("native_can_actions", []) if not hardware_request and ( execution_succeeded or str(action.get("domain")) in successful_domains ) ], ] updated_context = self.conversations.apply_successful_actions( conversation_id, utterance, all_actions, parsed["decision_summary"], ) if hardware_request and isinstance(physical_state, dict): updated_context = self.conversations.synchronize_physical_passenger( conversation_id, physical_state, ) if hardware_request: physical_ch1_feedback = self._physical_ch1_feedback_states(reports) if physical_ch1_feedback: updated_context = self.conversations.synchronize_physical_ch1_feedback( conversation_id, physical_ch1_feedback, ) updated_context = self._context_for_vehicle( updated_context, vehicle_profile, ) result["conversation_turn"] = updated_context["turn_count"] result["actuator_state"] = updated_context["devices"] result["domain_state"] = updated_context["domain_devices"] self._trace( traces, request_id, "state_update", "succeeded", ( f"跨域会话状态已更新为第 {updated_context['turn_count']} 轮。" if execution_succeeded else "仅成功设备已写入会话状态;失败设备保持执行前状态。" ), time.perf_counter(), { "seat_state": updated_context["devices"], "domain_state": updated_context["domain_devices"], "successful_domains": sorted(successful_domains), "failed_domains": sorted(failed_domains), }, domain="orchestration", ) elif hardware_request and isinstance(physical_state, dict) and ( physical_state.get("position") or physical_state.get("ventilation") or physical_state.get("heating") ): updated_context = self.conversations.synchronize_physical_passenger( conversation_id, physical_state, ) updated_context = self._context_for_vehicle( updated_context, vehicle_profile, ) result["actuator_state"] = updated_context["devices"] result["domain_state"] = updated_context["domain_devices"] self._trace( traces, request_id, "state_sync", "succeeded", "跨域真实执行未完成,但已按 CAN 回读同步副驾物理状态。", time.perf_counter(), {"seat_state": updated_context["devices"].get("passenger", {})}, domain="orchestration", ) self._persist(result) return result def _execute_orchestrated_reports( self, configs: dict[str, dict[str, Any]], plans: dict[str, dict[str, Any]], native_config_keys: set[str], context: dict[str, Any], *, hardware_request: bool, simulated_hardware_request: bool, simulation_fault: str, control_transport: str = WIRED_CAN_TRANSPORT, ) -> dict[str, dict[str, Any]]: reports: dict[str, dict[str, Any]] = {} bridge = None seat_bus_factory = None native_bus_factory = None vehicle_safety_preflight: dict[str, Any] | None = None mqtt_selected = control_transport == WIRELESS_MQTT_TRANSPORT wired_native_passenger = bool( not mqtt_selected and "seat" in configs and self.hardware_settings.native_passenger_can0 and self.vehicle_can_settings.configured ) if hardware_request and ( native_config_keys or (mqtt_selected and "seat" in configs) or wired_native_passenger ): shared_runtime_enabled = ( not mqtt_selected and self.vehicle_can_settings.backend != "zlgcan-linux" and os.getenv( "COGNITIVE_AGENT_SHARED_CAN_RUNTIME_ENABLED", os.getenv( "COGNITIVE_AGENT_SEAT_SHARED_ZCANPRO_BRIDGE_ENABLED", "true" ), ).strip().lower() in {"1", "true", "yes", "on"} ) if mqtt_selected: native_bus_factory = lambda: self._vehicle_mqtt_transport.bus(channel=1) if "seat" in configs: seat_bus_factory = lambda: self._vehicle_mqtt_transport.bus(channel=0) elif shared_runtime_enabled: from cognitive_agent.application.self_repair.service import ( BRIDGE_MANAGER, ) if not hasattr(BRIDGE_MANAGER, "channel_bus_factory"): raise HardwareSafetyError( "vehicle CAN requires the standalone multi-channel runtime; " "the ZCANPRO compatibility backend owns CH0 only" ) native_bus_factory = BRIDGE_MANAGER.channel_bus_factory( self.vehicle_can_settings.channel ) # Seat commands continue through the high-level request bridge # on CH0; passing a raw bus factory here would bypass its one- # owner queue and allow self-repair to race orchestration. seat_bus_factory = None elif wired_native_passenger: bridge = self._new_linux_wired_dual_channel_bridge() native_bus_factory = lambda: bridge.channel_bus(1) seat_bus_factory = lambda: bridge.channel_bus(0) else: channel_configs = [self.vehicle_can_settings.channel_config()] if "seat" in configs: seat_channel = int(self.hardware_settings.channel, 0) channel_configs.insert( 0, ZlgChannelConfig( channel=seat_channel, frame_format="classic_can", arbitration_bitrate=self.hardware_settings.bitrate, data_bitrate=self.vehicle_can_settings.data_bitrate, can_fd_iso=True, ), ) bridge = self.vehicle_can_settings.bridge(channel_configs) native_bus_factory = lambda: bridge.channel_bus( self.vehicle_can_settings.channel ) if "seat" in configs: seat_channel = int(self.hardware_settings.channel, 0) seat_bus_factory = lambda: bridge.channel_bus(seat_channel) if mqtt_selected: # The head unit is the physical CAN owner. Once an action has # passed the capability/parameter compiler, MqttCanTransport # applies the final channel, ID and frame-format allow-list at # publish time. Missing 0x342/0x4EB state frames must not # suppress an otherwise approved MQTT command. vehicle_safety_preflight = _wireless_mqtt_publish_preflight() else: # The wired bridge is open but no control frame has been # submitted yet. Require two independent, fresh CH1 status # frames before entering any actuator executor; CH0 traffic is # ignored by the observer. try: vehicle_safety_preflight = observe_vehicle_safety( native_bus_factory(), timeout_ms=min( 5_000, max(1_000, self.vehicle_can_settings.feedback_timeout_ms), ), required_channel=1, ) require_stationary_vehicle(vehicle_safety_preflight) except VehicleSafetyPreflightError as exc: if bridge is not None: bridge.close() raise HardwareSafetyError( "CH1 vehicle safety preflight refused physical execution: " f"{exc}" ) from exc except Exception: if bridge is not None: bridge.close() raise try: def execute_native_configs() -> dict[str, dict[str, Any]]: native_reports: dict[str, dict[str, Any]] = {} for key in sorted(native_config_keys): if not hardware_request: native_reports[key] = self.native_can_runtime.execute_virtual( plans[key], configs[key] ) continue executor = NativeCanExecutor( native_bus_factory, feedback_timeout_ms=( self.vehicle_mqtt_settings.feedback_timeout_ms if control_transport == WIRELESS_MQTT_TRANSPORT else self.vehicle_can_settings.feedback_timeout_ms ), feedback_required=not mqtt_selected, ) native_reports[key] = executor.execute( self.native_can_runtime, plans[key], configs[key], ) native_reports[key]["spec"]["vehicle_safety_preflight"] = ( vehicle_safety_preflight ) for item in native_reports[key]["spec"].get("results", []): item.setdefault("telemetry", {})[ "vehicle_safety_preflight" ] = vehicle_safety_preflight return native_reports def execute_seat_config() -> dict[str, Any]: config = configs["seat"] if hardware_request: seat_executor = ( self._new_hardware_executor( seat_bus_factory, remote_transport=mqtt_selected, ) if seat_bus_factory is not None else self._new_hardware_executor() ) return seat_executor.execute( plans["seat"], config, context["devices"] ) if simulated_hardware_request: return self.simulated_hardware_executor.execute( plans["seat"], config, context["devices"], fault_scenario=simulation_fault, ) return self.executor.execute( plans["seat"], config, context["devices"] ) if hardware_request and native_config_keys and "seat" in configs: # Both channel views share one adapter owner; executor threads let # the two channels start within normal USB scheduling jitter. with ThreadPoolExecutor(max_workers=2) as pool: native_future = pool.submit(execute_native_configs) seat_future = pool.submit(execute_seat_config) reports.update(native_future.result()) reports["seat"] = seat_future.result() else: reports.update(execute_native_configs()) if "seat" in configs: reports["seat"] = execute_seat_config() for domain, config in configs.items(): if domain in native_config_keys or domain == "seat": continue reports[domain] = self.domain_runtimes[domain].execute( plans[domain], config ) finally: if bridge is not None: bridge.close() return reports @staticmethod def _build_scene_config( request_id: str, configs: dict[str, dict[str, Any]], plans: dict[str, dict[str, Any]], ) -> dict[str, Any]: function_refs = [] steps = [] for domain in configs: reference_id = f"{domain}_function" function_refs.append( { "id": reference_id, "domain": domain, "config_ref": f"functions/{domain}.yaml", "sha256": plans[domain]["spec"]["source"]["config_sha256"], } ) steps.append( { "id": f"execute_{domain}", "function_ref": reference_id, "start_offset_ms": 0, "failure_policy": "abort" if domain == "seat" else "continue", } ) return { "apiVersion": "cognitive-agent/v1alpha1", "kind": "SceneConfig", "metadata": { "id": f"scene-{request_id}", "name": "Generated cabin scene", "version": "1.0.0", "request_id": request_id, "generated_at": _now(), "labels": {"execution_scope": "virtual_only"}, }, "spec": { "function_refs": function_refs, "steps": steps, "execution": { "mode": "parallel", "default_failure_policy": "continue", "max_parallelism": min(4, len(configs)), "scene_timeout_ms": 30000, }, }, } @staticmethod def _split_intent_clauses(utterance: str) -> list[str]: def looks_like_control(value: str) -> bool: return bool( CONTROL_CLAUSE_SUBJECT_PATTERN.search(value) and CONTROL_CLAUSE_PREDICATE_PATTERN.search(value) ) def looks_like_independent_request(value: str) -> bool: return looks_like_control(value) or bool( CLOUD_REQUEST_CLAUSE_PATTERN.search(value) ) def split_independent_controls( value: str, separator_pattern: str, ) -> list[str]: tokens = re.split(f"({separator_pattern})", value) current = tokens[0].strip() separated: list[str] = [] for index in range(1, len(tokens), 2): separator = tokens[index] next_item = tokens[index + 1].strip() if ( looks_like_independent_request(current) and looks_like_independent_request(next_item) and ( looks_like_control(current) or looks_like_control(next_item) ) ): if current: separated.append(current) current = next_item else: current = f"{current}{separator}{next_item}".strip() if current: separated.append(current) return separated coarse_fragments = [ item.strip() for item in re.split(r"[,,。;;::!!??\r\n]+", utterance) if item.strip() ] fragments: list[str] = [] for coarse_fragment in coarse_fragments: list_fragments = split_independent_controls(coarse_fragment, r"、") for list_fragment in list_fragments: fragments.extend( split_independent_controls( list_fragment, r"然后|随后|接着|最后|同时|并且|以及|再|并", ) ) clauses: list[str] = [] for fragment in fragments: fragment = re.sub( r"^(?:然后|随后|接着|最后|同时|并且|以及|并)", "", fragment, ).strip() fragment = re.sub( r"^再(?=(?:打开|开启|启动|关闭|关掉|关上|停止|暂停|取消))", "", fragment, ).strip() if not fragment: continue # A massage mode/strength phrase may omit both "主驾" and "按摩" # after the first clause. Keep the whole request atomic so the ON # fallback does not emit relax before an explicit later mode. massage_continuation = bool( clauses and "按摩" in clauses[-1] and DRIVER_SEAT_MASSAGE_CONTINUATION_PATTERN.search(fragment) ) # An exclusion is a qualifier of the control immediately before it, # not an independent command. Keeping both pieces together lets # the deterministic window parser remove the excluded targets # before the command list is finalized. exclusion_qualifier = bool( clauses and re.search(r"除外|除去|不包括|不含", fragment) ) if massage_continuation or exclusion_qualifier: clauses[-1] = f"{clauses[-1]},{fragment}" else: clauses.append(fragment) return clauses or [utterance.strip()] @staticmethod def _local_clause_status(parsed: dict[str, Any], clause: str) -> str: """Classify one deterministic parse as complete, partial, or unparsed.""" controls = any( parsed.get(key) for key in ( "actions", "domain_actions", "native_can_actions", "preset_actions", "preset_events", "semantic_constraints", ) ) real_ambiguities = [ item for item in parsed.get("ambiguities", []) if str(item.get("field", "")) != "capability" ] unresolved = bool(parsed.get("unresolved_spans")) # Scene/fuzzy-goal language may contain valid atomic controls and still # ask for additional orchestration. Keep the trusted local controls but # give only that clause to the model for the uncovered meaning. fuzzy_goal = bool( re.search( r"观影|影院|看电影|休息|睡眠|露营|播放|音乐|场景|" r"舒适(?:点|一些)?|凉快(?:点|一些)?|暖和(?:点|一些)?", clause, re.IGNORECASE, ) ) if controls and not real_ambiguities and not unresolved and not fuzzy_goal: return "complete" if controls: return "partial" return "unparsed" @classmethod def _merge_parsed_results( cls, results: list[dict[str, Any]] ) -> dict[str, Any]: """Merge clause-level parses while retaining their source order.""" merged: dict[str, Any] = { "actions": [], "domain_actions": [], "native_can_actions": [], "preset_actions": [], "preset_events": [], "deferred_requests": [], "model_requests": [], "ambiguities": [], "decision_summary": [], "semantic_constraints": [], "unresolved_spans": [], "detected_domains": [], "confidence": 0.0, } list_fields = tuple( key for key in merged if key not in {"detected_domains", "confidence"} ) for parsed in results: for key in list_fields: merged[key] = cls._merge_unique( merged[key], list(parsed.get(key, [])) ) merged["detected_domains"] = cls._merge_domains( merged["detected_domains"], list(parsed.get("detected_domains", [])), ) merged["confidence"] = max( float(merged["confidence"]), float(parsed.get("confidence", 0)) ) return merged @classmethod def _merge_local_and_model_intent( cls, local: dict[str, Any], model: dict[str, Any], ) -> dict[str, Any]: """Merge a fallback model plan without overriding local controls.""" merged = deepcopy(model) for key in ("actions", "domain_actions", "native_can_actions"): local_items = list(local.get(key, [])) local_signatures = { cls._control_signature(item) for item in local_items } model_items = [ item for item in model.get(key, []) if cls._control_signature(item) not in local_signatures ] merged[key] = cls._merge_unique(local_items, model_items) for key in ( "preset_actions", "preset_events", "deferred_requests", "model_requests", "ambiguities", "semantic_constraints", "unresolved_spans", ): merged[key] = cls._merge_unique( list(local.get(key, [])), list(model.get(key, [])) ) merged["decision_summary"] = cls._merge_unique( list(local.get("decision_summary", [])), list(model.get("decision_summary", [])), ) merged["detected_domains"] = cls._merge_domains( list(local.get("detected_domains", [])), list(model.get("detected_domains", [])), ) merged["confidence"] = max( float(local.get("confidence", 0)), float(model.get("confidence", 0)) ) return merged @staticmethod def _mark_control_parse_source( parsed: dict[str, Any], source: str, *, overwrite: bool = False, ) -> dict[str, Any]: """Attach per-control provenance without changing semantic content.""" marked = deepcopy(parsed) for key in ("actions", "domain_actions", "native_can_actions"): for action in marked.get(key, []): if overwrite or not action.get("parse_source"): action["parse_source"] = source return marked def _local_clause_results( self, utterance: str, context: dict[str, Any], ) -> list[tuple[str, dict[str, Any], str]]: """Parse each clause deterministically and preserve inherited seat scope.""" clause_results: list[tuple[str, dict[str, Any], str]] = [] def parse_clause(clause: str) -> dict[str, Any]: local = parse_local_intent(clause, context) relative = ( self.native_can_relative_resolver.resolve(clause, context) if self.native_can_relative_resolver.matches(clause) else None ) local = merge_native_can_intent( local, clause, relative_actions=relative.actions if relative else None, relative_ambiguities=relative.ambiguities if relative else None, relative_claimed_controls=( relative.claimed_controls if relative else None ), ) return self._mark_control_parse_source(local, "local_rule") def has_seat_domain(parsed: dict[str, Any]) -> bool: if parsed.get("actions") or "seat" in parsed.get("detected_domains", []): return True for key in ( "domain_actions", "native_can_actions", "deferred_requests", "model_requests", ): if any(item.get("domain") == "seat" for item in parsed.get(key, [])): return True return any( str(item.get("field", "")).startswith("seat.") for item in parsed.get("ambiguities", []) ) inherited_seat_scope: str | None = None for raw_clause in self._split_intent_clauses(utterance): clause = raw_clause local = parse_clause(clause) clause_has_seat = has_seat_domain(local) explicit_driver = bool(DRIVER_SEAT_SCOPE_PATTERN.search(raw_clause)) explicit_passenger = bool(PASSENGER_SEAT_SCOPE_PATTERN.search(raw_clause)) if clause_has_seat and explicit_driver != explicit_passenger: inherited_seat_scope = "driver" if explicit_driver else "passenger" elif clause_has_seat and inherited_seat_scope: scope_label = ( "主驾座椅" if inherited_seat_scope == "driver" else "副驾座椅" ) clause = f"{scope_label},{raw_clause}" local = parse_clause(clause) clause_results.append( (clause, local, self._local_clause_status(local, clause)) ) return clause_results @classmethod def _trusted_local_preview( cls, clause_results: list[tuple[str, dict[str, Any], str]], ) -> dict[str, Any]: """Keep deterministic controls while deferring incomplete text to GPT.""" trusted = cls._merge_parsed_results( [parsed for _, parsed, status in clause_results if status != "unparsed"] ) # Ambiguities and unresolved spans belong to the supplement phase. A # deterministic action still goes through the normal schema, compiler, # whitelist and physical execution gates before it can be sent. trusted["ambiguities"] = [] trusted["unresolved_spans"] = [] trusted["model_requests"] = [] return trusted @classmethod def _filter_excluded_controls( cls, parsed: dict[str, Any], excluded: set[tuple[Any, ...]], ) -> dict[str, Any]: filtered = deepcopy(parsed) for key in ("actions", "domain_actions", "native_can_actions"): filtered[key] = [ action for action in filtered.get(key, []) if cls._control_signature(action) not in excluded ] return filtered def _interpret( self, utterance: str, provider: str, context: dict[str, Any], vehicle_profile: VehicleProfile | None = None, *, reasoning_effort: str | None = None, local_preview: bool = False, model_parse_source: str = "cloud_model", assistant_plan: dict[str, Any] | None = None, allow_scene_composition: bool = False, scene_scope_utterance: str | None = None, cloud_instruction_context: dict[str, Any] | None = None, ) -> tuple[dict[str, Any], str, str | None]: vehicle_profile = vehicle_profile or self.vehicle_profiles.default() if assistant_plan is not None and allow_scene_composition: # ``utterance`` is a synthesized ``进入`` carrier for a # name-only scene (and for a server-owned draft revision). Words # inside that label are not user-authored atomic instructions: # e.g. ``外循环通风模式`` previously leaked ``通风`` into the seat # parser and added an AMBIGUOUS_INTENT beside an otherwise valid # climate plan. In this narrow path the complete assistant plan is # the candidate source of actions; it still passes the scene # allowlist, registered capability contract, ModelPlanAdapter, # schema/safety validation and native compiler below. clause_results = [ (utterance, self._merge_parsed_results([]), "unparsed") ] else: clause_results = self._local_clause_results(utterance, context) trusted_local = self._merge_parsed_results( [parsed for _, parsed, status in clause_results if status != "unparsed"] ) preview_local = self._trusted_local_preview(clause_results) all_local = self._merge_parsed_results( [parsed for _, parsed, _ in clause_results] ) fallback_clauses = [ clause for clause, _, status in clause_results if status != "complete" ] trusted_controls = any( trusted_local.get(key) for key in ("actions", "domain_actions", "native_can_actions") ) safe_fallback = trusted_local if trusted_controls else all_local # A Codex-authored plan is an untrusted normalization hint for only # the clauses the deterministic parser could not finish. The guard # binds every proposed capability back to that server-owned text and # prevents it from replacing a locally resolved capability/target. # ModelPlanAdapter then applies the same target, parameter, range and # runtime-availability checks as the established hosted-model path. if assistant_plan is not None and fallback_clauses: fallback_utterance = ",".join(fallback_clauses) guarded = guard_assistant_plan( assistant_plan, fallback_text=fallback_utterance, capability_context=self.capability_context, executable_capabilities={ str(capability.get("name") or "") for domain in self.capability_context.get("domains", []) for capability in domain.get("capabilities", []) if domain.get("availability") == "executable" or str(capability.get("name") or "") in self.native_can_runtime.supported_capabilities }, fallback_intents=[ parsed for _, parsed, status in clause_results if status != "complete" ], trusted_local=trusted_local, allow_scene_composition=allow_scene_composition, scene_scope_utterance=scene_scope_utterance, ) model = self.model_plan_adapter.adapt( guarded.plan, context, vehicle_profile, utterance=fallback_utterance, ) if allow_scene_composition: promoted_native: list[dict[str, Any]] = [] remaining_domain_actions: list[dict[str, Any]] = [] for action in model.get("domain_actions", []): if self.native_can_runtime.supports(action): promoted = dict(action) promoted["execution_backend"] = "native_can" promoted_native.append(promoted) else: remaining_domain_actions.append(action) remaining_deferred: list[dict[str, Any]] = [] for deferred in model.get("deferred_requests", []): candidate = { "domain": str(deferred.get("domain") or ""), "capability": str(deferred.get("capability") or ""), "target": str( deferred.get("target_scope") or deferred.get("target") or "" ), "operation": str(deferred.get("desired_change") or "set"), "parameters": dict(deferred.get("parameters") or {}), "reason": "小迪生成的场景候选动作已通过工程能力契约校验。", "execution_backend": "native_can", } if self.native_can_runtime.supports(candidate): promoted_native.append(candidate) else: remaining_deferred.append(deferred) model.setdefault("ambiguities", []).append( { "field": "assistant_plan", "question": ( f"能力 {candidate['capability']} 无法进入当前车型的" "可复用执行链。" ), } ) model["native_can_actions"] = [ *model.get("native_can_actions", []), *promoted_native, ] model["domain_actions"] = remaining_domain_actions model["deferred_requests"] = remaining_deferred if allow_scene_composition and model.get("ambiguities"): # Adapter validation is the second half of the scene gate: it # owns targets, required parameters, types and ranges. If any # candidate fails there, discard every sibling action so the # reusable draft can never become a silently partial scene. for key in ( "actions", "domain_actions", "native_can_actions", "deferred_requests", "preset_actions", "preset_events", "model_requests", ): model[key] = [] model = self._mark_control_parse_source( model, model_parse_source, overwrite=True, ) return ( self._merge_local_and_model_intent(trusted_local, model), "local_rules+codex_app_server", "本地规则结果优先;小迪生成的结构化计划仅补足未解析子句,并已重新通过能力注册表校验。", ) if provider == "local" or not fallback_clauses: return ( ( preview_local if provider == "local" and local_preview else all_local if provider == "local" else trusted_local ), "local_rules", None, ) if not self.openai.available: if provider == "openai": raise ValueError("存在本地规则无法完整解析的子句;GPT 模式需要设置 OPENAI_API_KEY") return ( safe_fallback, "local_rules", "未配置 OPENAI_API_KEY;已保留本地规则结果,未解析子句没有下发控制。", ) fallback_utterance = ",".join(fallback_clauses) try: interpret_options = {} if reasoning_effort is not None: interpret_options["reasoning_effort"] = reasoning_effort hosted_context = deepcopy(context) if isinstance(cloud_instruction_context, dict): routing_context = deepcopy(cloud_instruction_context) else: routing_context = { "original_utterance": utterance, "parsed_clauses": [ clause for clause, _, status in clause_results if status != "unparsed" ], "resolved_clauses": [ clause for clause, _, status in clause_results if status == "complete" ], "unresolved_clauses": fallback_clauses, } routing_context["cloud_input_utterance"] = fallback_utterance hosted_context["natural_language_routing"] = routing_context plan = self.openai.interpret( fallback_utterance, hosted_context, self.capability_context, **interpret_options, ) model = self.model_plan_adapter.adapt( plan, context, vehicle_profile, utterance=fallback_utterance, ) model = self._mark_control_parse_source( model, model_parse_source, overwrite=True, ) return ( self._merge_local_and_model_intent(trusted_local, model), f"local_rules+openai:{self.openai.model}", "本地规则先完成确定性解析;仅未覆盖或有歧义的子句交由 GPT 补充。", ) except OpenAIIntentError: return ( safe_fallback, "local_rules_fallback", "GPT 调用失败,已降级到本地规则解析(逐子句);已保留确定性结果,CAN 编译和执行路径不变。", ) @staticmethod def _context_for_vehicle( context: dict[str, Any], vehicle_profile: VehicleProfile, ) -> dict[str, Any]: projected = deepcopy(context) projected["vehicle"] = vehicle_profile.public_dict() for target_state in projected.get("devices", {}).values(): positions = target_state.get("position", {}) for axis, state in positions.items(): if not isinstance(state, dict): continue motor = vehicle_profile.seat_motors.get(str(axis)) if motor is None or motor.display_angle is None: continue position_pct = state.get("position_pct") if ( not isinstance(position_pct, (int, float)) or isinstance(position_pct, bool) ): continue current_angle = state.get("angle_deg") legacy_angle = position_pct_to_angle(str(axis), float(position_pct)) should_project = current_angle is None or ( legacy_angle is not None and isinstance(current_angle, (int, float)) and not isinstance(current_angle, bool) and abs(float(current_angle) - float(legacy_angle)) < 0.0001 ) if should_project: state["angle_deg"] = _clean_vehicle_number( motor.display_angle_for_pct(int(position_pct)) ) return projected @staticmethod def _trace( traces: list[dict[str, Any]], request_id: str, stage: str, status: str, summary: str, started: float, details: dict[str, Any], domain: str = "seat", ) -> None: traces.append( { "apiVersion": "cognitive-agent/v1alpha1", "kind": "TraceEvent", "event_id": f"evt-{len(traces) + 1:02d}-{request_id}", "request_id": request_id, "timestamp": _now(), "stage": stage, "status": status, "domain": domain, "summary": summary, "details": details, "duration_ms": max(0, int((time.perf_counter() - started) * 1000)), } ) def persist_composed_result(self, result: dict[str, Any]) -> None: """Persist a validated result composed with a non-CAN control domain.""" request_id = str(result.get("request_id") or "") if not MODE_EDITOR_REQUEST_ID_PATTERN.fullmatch(request_id): raise ValueError("invalid composed request id") result.setdefault("trace_events", []) self._persist(result) def _persist(self, result: dict[str, Any]) -> None: request_dir = self.artifact_root / result["request_id"] (request_dir / "functions").mkdir(parents=True, exist_ok=True) (request_dir / "presets").mkdir(parents=True, exist_ok=True) (request_dir / "command-plans").mkdir(parents=True, exist_ok=True) try: artifact_path = request_dir.relative_to(self.root) except ValueError: artifact_path = request_dir result["artifact_path"] = str(artifact_path) self._write_yaml(request_dir / "intent.yaml", result.get("intent_ir")) function_configs = result.get("function_configs") or {} if function_configs: for domain, document in function_configs.items(): self._write_yaml( request_dir / "functions" / f"{domain}.yaml", document ) else: self._write_yaml( request_dir / "functions" / "seat.yaml", result.get("function_config"), ) self._write_yaml(request_dir / "scene.yaml", result.get("scene_config")) self._write_yaml( request_dir / "presets" / "seat.yaml", result.get("preset_config") ) command_plans = result.get("command_plans") or {} if command_plans: for domain, document in command_plans.items(): self._write_yaml( request_dir / "command-plans" / f"{domain}.yaml", document ) else: self._write_yaml( request_dir / "command-plans" / "seat.yaml", result.get("command_plan"), ) self._write_yaml( request_dir / "execution-report.yaml", result.get("execution_report") ) trace_text = "".join( json.dumps(event, ensure_ascii=False) + "\n" for event in result["trace_events"] ) self._atomic_write(request_dir / "trace.jsonl", trace_text) self._atomic_write( request_dir / "result.json", json.dumps(result, ensure_ascii=False, indent=2) + "\n", ) def _write_yaml(self, path: Path, document: dict[str, Any] | None) -> None: if document is None: return text = yaml.safe_dump(document, allow_unicode=True, sort_keys=False) self._atomic_write(path, text) @staticmethod def _atomic_write(path: Path, content: str) -> None: temporary = path.with_suffix(path.suffix + ".tmp") temporary.write_text(content, encoding="utf-8") temporary.replace(path)