"""Dependency-light HTTP server for the cabin orchestration demo.""" from __future__ import annotations import argparse import hmac import ipaddress import json import mimetypes import os import re import socket import sys import threading import time from collections.abc import Callable, Mapping from copy import deepcopy from http import HTTPStatus from http.cookies import CookieError, SimpleCookie from http.server import BaseHTTPRequestHandler, ThreadingHTTPServer from pathlib import Path from urllib.parse import ParseResult, parse_qs, quote, unquote, urlparse from cognitive_agent.config_loader import load_local_env REPOSITORY_ROOT = Path(__file__).resolve().parents[2] # Repository-local launch configuration is deliberate and must not be shadowed # by stale variables inherited from an IDE or an earlier model experiment. load_local_env(REPOSITORY_ROOT / ".env", override=True) load_local_env(REPOSITORY_ROOT / ".env.auth", override=True) from cognitive_agent.application.assistant_capabilities import ( # noqa: E402 AssistantCapabilityCatalog, ) from cognitive_agent.application.calibration.service import ( # noqa: E402 ApiError as CalibrationApiError, ) from cognitive_agent.application.calibration.service import ( # noqa: E402 CalibrationService, ) from cognitive_agent.application.codex_assistant import ( # noqa: E402 CodexAssistantError, CodexAssistantService, CodexVehicleToolError, ) from cognitive_agent.application.firmware_workbench import ( # noqa: E402 FirmwareWorkbenchError, FirmwareWorkbenchService, ) from cognitive_agent.application.gesture_bridge import GestureCapabilityBridge # noqa: E402 from cognitive_agent.application.mode_profile_intent import ( # noqa: E402 normalize_mode_profile_name, parse_mode_profile_update, ) from cognitive_agent.application.mode_schedule_service import public_schedule_status # noqa: E402 from cognitive_agent.application.seat_service import ( # noqa: E402 LOCAL_CONTROL_BUDGET_MS, SeatDemoService, ) from cognitive_agent.application.self_extension import ( # noqa: E402 ExtensionIntegrationError, GatewayError, SelfExtensionService, active_peripheral_plan_catalog, partition_active_peripheral_intent, resolve_active_peripheral_command, ) from cognitive_agent.application.self_repair import SelfRepairService # noqa: E402 from cognitive_agent.auth import ( # noqa: E402 AuthConfig, AuthRuntime, is_json_content_type, origin_matches_host, verify_password, ) from cognitive_agent.infrastructure.can.access import ( # noqa: E402 HardwareAccessRequiredError, ) from cognitive_agent.infrastructure.can.gateway import CanGatewayError # noqa: E402 from cognitive_agent.infrastructure.can.hardware import ( # noqa: E402 HardwareBusyError, HardwareSafetyError, ) from cognitive_agent.infrastructure.codex.app_server import ( # noqa: E402 CodexAppServerClient, CodexAppServerError, ) from cognitive_agent.infrastructure.llm.openai_responses import OpenAIIntentError # noqa: E402 from cognitive_agent.infrastructure.nfc import NfcReaderService # noqa: E402 from cognitive_agent.infrastructure.speech import ( # noqa: E402 SpeechRecognitionError, SpeechTranscriber, ) STATIC_ROOT = REPOSITORY_ROOT / "apps" / "web-console" DEFAULT_MODE_SCENE_CATALOG_PATH = ( REPOSITORY_ROOT.parent / "scenses" / "demo-scenes.json" ) mimetypes.add_type("application/manifest+json", ".webmanifest") mimetypes.add_type("model/gltf-binary", ".glb") REQUEST_ID_PATTERN = re.compile(r"^req-[a-z0-9-]+$") PUBLIC_AUTH_PATHS = { "/login", "/login.html", "/login.css", "/login.js", "/network.js", "/api/auth/status", "/api/auth/login", "/api/firmware-workbench/releases/latest", } PUBLIC_OTA_DOWNLOAD_PATH = re.compile( r"/api/firmware-workbench/releases/[a-z0-9-]+/download" ) NON_DISPATCH_DISPLAY_SOURCES = frozenset({"mode_editor_compile"}) class VehicleDisplaySync: """Process-local snapshot consumed by passive vehicle display clients.""" def __init__(self) -> None: self._lock = threading.Lock() self._revision = 0 self._snapshot: dict[str, object] = { "revision": 0, "updated_at": None, "task": { "status": "idle", "utterance": "", "source": "head_unit", "result": None, "error": None, }, "extension": { "catalog": [], "last_action": None, "result": None, }, } @staticmethod def _safe_copy(value: object) -> object: return json.loads(json.dumps(value, ensure_ascii=False, default=str)) def update_task( self, *, status: str, utterance: str, source: str = "head_unit", result: object = None, error: object = None, ) -> None: with self._lock: self._revision += 1 self._snapshot["revision"] = self._revision self._snapshot["updated_at"] = time.time() self._snapshot["task"] = { "status": status, "utterance": utterance, "source": source or "head_unit", "result": self._safe_copy(result), "error": self._safe_copy(error), } def update_extension( self, *, catalog: object | None = None, action: object | None = None, result: object | None = None, ) -> None: with self._lock: self._revision += 1 current = dict(self._snapshot.get("extension", {})) if catalog is not None: current["catalog"] = self._safe_copy(catalog) if action is not None: current["last_action"] = self._safe_copy(action) if result is not None: current["result"] = self._safe_copy(result) self._snapshot["revision"] = self._revision self._snapshot["updated_at"] = time.time() self._snapshot["extension"] = current def snapshot(self, health: dict[str, object]) -> dict[str, object]: with self._lock: payload = self._safe_copy(self._snapshot) assert isinstance(payload, dict) payload["extension_health"] = self._safe_copy(health) return payload def extension_catalog(self) -> list[dict[str, object]]: with self._lock: extension = self._snapshot.get("extension", {}) catalog = extension.get("catalog", []) if isinstance(extension, dict) else [] payload = self._safe_copy(catalog) if not isinstance(payload, list): return [] return [item for item in payload if isinstance(item, dict)] def _lan_ipv4_address() -> str | None: """Return the IPv4 address selected by the host's default route.""" probe = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) try: probe.connect(("192.0.2.1", 80)) address = str(probe.getsockname()[0]) return None if address.startswith("127.") else address except OSError: return None finally: probe.close() class SeatDemoHandler(BaseHTTPRequestHandler): service = SeatDemoService() firmware_workbench = FirmwareWorkbenchService(REPOSITORY_ROOT) # Codex defaults to its own empty temporary workspace. A deployment may # opt into another isolated directory through CODEX_ASSISTANT_WORKSPACE, # but it must never inherit access to this application's source tree. assistant = CodexAssistantService( CodexAppServerClient(), fast_implicit_controls=True, ) gesture = GestureCapabilityBridge(REPOSITORY_ROOT) extension = SelfExtensionService() nfc = NfcReaderService() calibration = CalibrationService( REPOSITORY_ROOT / "artifacts" / "tmp" / "self-calibration", repository_root=REPOSITORY_ROOT, ) repair = SelfRepairService() speech = SpeechTranscriber(REPOSITORY_ROOT) # Reject excess live-preview work before reading request bodies. The # transcriber owns a second, stricter gate so non-HTTP callers are covered too. speech_preview_http_slots = threading.BoundedSemaphore(6) auth = AuthRuntime(AuthConfig.from_env()) vehicle_display_sync = VehicleDisplaySync() assistant_capability_catalog: AssistantCapabilityCatalog | None = None server_version = "CognitiveAgentCabinDemo/2.0" def do_GET(self) -> None: # noqa: N802 - stdlib handler API parsed_url = urlparse(self.path) path = unquote(parsed_url.path) if path == "/favicon.ico": self.send_response(HTTPStatus.NO_CONTENT) self._send_security_headers() self.end_headers() return if path == "/api/auth/status": username = self._session_username() self._json( { "enabled": self.auth.config.enabled, "authenticated": not self.auth.config.enabled or username is not None, "username": username, "session_ttl_seconds": self.auth.config.session_ttl_seconds, "requests_per_minute": self.auth.config.requests_per_minute, } ) return if path == "/api/health": assistant_status = self.assistant.status() assistant_available = bool(assistant_status.get("available")) self._json( { "status": "ok" if assistant_available else "unavailable", "service": "cabin-cognitive-orchestrator", "assistant": { "engine": str(assistant_status.get("engine") or "codex-app-server"), "status": str(assistant_status.get("status") or "unknown"), "available": assistant_available, }, }, HTTPStatus.OK if assistant_available else HTTPStatus.SERVICE_UNAVAILABLE, ) return if path == "/n7/scenes": vehicle_id = str(parse_qs(parsed_url.query).get("vehicle_id", [""])[0]) or None self._json( self.service.mode_scene_catalog(vehicle_id), headers={"Access-Control-Allow-Origin": "*"}, ) return if path in {"/login", "/login.html"}: if self.auth.config.enabled and self._session_username() is not None: self._redirect("/") return self._serve_static_file("login.html") return if not self._authorize(path): return if path in {"/hardware-control", "/hardware-control.html"}: if not self._require_local_decision_point(): return self._serve_static_file("hardware-control.html") return if path in {"/hardware-control.js", "/hardware-control.css"}: if not self._require_local_decision_point(): return self._serve_static_file(path.lstrip("/")) return if path == "/api/local/hardware-access": if not self._require_local_decision_point(): return self._json(self.service.hardware_access.admin_snapshot()) return if path == "/api/hardware/access/status": query = parse_qs(parsed_url.query) try: self._json( self.service.hardware_access.status( str(query.get("client_id", [""])[0]) ) ) except ValueError as exc: self._json( {"error": "invalid_request", "message": str(exc)}, HTTPStatus.BAD_REQUEST, ) return if path == "/api/assistant/status": self._json(self.assistant.status()) return if path == "/api/assistant/capabilities": self._json(self._assistant_capability_catalog().snapshot()) return if path == "/api/firmware-workbench/state": self._json(self.firmware_workbench.snapshot()) return if path == "/api/firmware-workbench/releases/latest": try: self._json(self.firmware_workbench.latest_release()) except FirmwareWorkbenchError as exc: self._json( {"error": exc.code, "message": str(exc)}, HTTPStatus(exc.status) ) return firmware_task_match = re.fullmatch( r"/api/firmware-workbench/tasks/(?P[a-z0-9-]+)", path ) if firmware_task_match: try: self._json( self.firmware_workbench.task(firmware_task_match.group("task_id")) ) except FirmwareWorkbenchError as exc: self._json( {"error": exc.code, "message": str(exc)}, HTTPStatus(exc.status) ) return firmware_artifact_download_match = re.fullmatch( r"/api/firmware-workbench/artifacts/(?P[a-z0-9-]+)/download", path, ) if firmware_artifact_download_match: self._serve_firmware_download( "artifact", firmware_artifact_download_match.group("artifact_id") ) return firmware_release_download_match = re.fullmatch( r"/api/firmware-workbench/releases/(?P[a-z0-9-]+)/download", path, ) if firmware_release_download_match: self._serve_firmware_download( "release", firmware_release_download_match.group("release_id") ) return firmware_artifact_match = re.fullmatch( r"/api/firmware-workbench/artifacts/(?P[a-z0-9-]+)", path ) if firmware_artifact_match: try: self._json( self.firmware_workbench.artifact( firmware_artifact_match.group("artifact_id") ) ) except FirmwareWorkbenchError as exc: self._json( {"error": exc.code, "message": str(exc)}, HTTPStatus(exc.status) ) return if path == "/api/runtime": self._json(self.service.runtime_info()) return if path == "/api/modes/schedule/status": query = parse_qs(parsed_url.query) try: vehicle = self.service.vehicle_profiles.get(str(query.get("vehicle_id", [""])[0]) or None) self._json(public_schedule_status(self.service.mode_schedule_runner.status(vehicle.id))) except ValueError as exc: self._json({"error": "invalid_request", "message": str(exc)}, HTTPStatus.BAD_REQUEST) return if path == "/api/vehicle-display-sync": self._json(self.vehicle_display_sync.snapshot(self._extension_health())) return if path in {"/api/hardware/seat-state", "/api/hardware/vehicle-state"}: query = parse_qs(parsed_url.query) conversation_id = str(query.get("conversation_id", [""])[0]) vehicle_id = str(query.get("vehicle_id", [""])[0]) or None control_transport = str( query.get("control_transport", ["wired_can"])[0] ) hardware_client_id = str(query.get("hardware_client_id", [""])[0]) or None try: self._json( ( self.service.synchronize_vehicle_state if path == "/api/hardware/vehicle-state" else self.service.synchronize_hardware_state )( conversation_id, vehicle_id, control_transport=control_transport, hardware_client_id=hardware_client_id, hardware_access_bypass=self._hardware_access_bypass_allowed(), ) ) except HardwareAccessRequiredError as exc: self._json( { "error": "hardware_access_required", "message": str(exc), "access_state": exc.state, }, HTTPStatus.FORBIDDEN, ) except HardwareBusyError as exc: self._json( { "error": "hardware_busy", "message": str(exc), "retry_after_seconds": exc.retry_after_seconds, }, HTTPStatus.CONFLICT, {"Retry-After": str(exc.retry_after_seconds)}, ) except ValueError as exc: self._json( {"error": "invalid_request", "message": str(exc)}, HTTPStatus.BAD_REQUEST, ) except Exception as exc: # noqa: BLE001 - surface passive CAN availability self.log_error("physical CAN state synchronization failed: %s", exc) self._json( { "error": "hardware_state_unavailable", "message": str(exc), }, HTTPStatus.SERVICE_UNAVAILABLE, ) return if path == "/api/gesture/runtime": self._json(self.gesture.runtime_info()) return if path == "/api/gesture/model": self._serve_gesture_model() return if path == "/api/extension/health": self._json(self._extension_health()) return if path == "/api/extension/nfc/status": self._json({"success": True, **self.nfc.snapshot()}) return if path == "/api/extension/gscu/slots": try: self._json(self.extension.list_infrared_slots()) except GatewayError as exc: self._json( {"success": False, "error": "catalog_unavailable", "message": str(exc)}, HTTPStatus.SERVICE_UNAVAILABLE, ) return if path == "/api/extension/peripherals": try: catalog = self.extension.list_peripheral_capabilities() self._json( { "success": True, "catalog": catalog, "count": len(catalog), } ) except GatewayError as exc: self._json( { "success": False, "error": "catalog_unavailable", "message": str(exc), }, HTTPStatus.SERVICE_UNAVAILABLE, ) return if path == "/api/extension/gscu/info": self._handle_extension_gateway("info", {}) return if path.startswith("/api/calibration/"): if not self._require_calibration_admin(parsed_url): return if path == "/api/calibration/state": self._json(self.calibration.snapshot()) return if path == "/api/calibration/events": self._serve_calibration_events() return if path == "/api/calibration/video/stream": self._serve_calibration_video() return self._json({"error": "not_found"}, HTTPStatus.NOT_FOUND) return if path.startswith("/api/self-repair/"): if not self._require_self_repair_admin(parsed_url): return if path == "/api/self-repair/state": self._json(self.repair.snapshot()) return if path == "/api/self-repair/health": self._json(self.repair.health()) return self._json({"error": "not_found"}, HTTPStatus.NOT_FOUND) return if path == "/api/speech/status": self._json(self.speech.status()) return if path.startswith("/api/requests/"): self._serve_request(path.removeprefix("/api/requests/")) return self._serve_static(path, parsed_url.query) def do_OPTIONS(self) -> None: # noqa: N802 - stdlib handler API path = unquote(urlparse(self.path).path) if path != "/n7/scenes": self._json({"error": "not_found"}, HTTPStatus.NOT_FOUND) return self.send_response(HTTPStatus.NO_CONTENT) self.send_header("Access-Control-Allow-Origin", "*") self.send_header("Access-Control-Allow-Methods", "GET, OPTIONS") self.send_header("Access-Control-Allow-Headers", "Accept, Content-Type") self.send_header("Content-Length", "0") self.send_header("Cache-Control", "no-store") self._send_security_headers() self.end_headers() def do_POST(self) -> None: # noqa: N802 - stdlib handler API path = unquote(urlparse(self.path).path) if not self._require_same_origin(): return if path == "/api/internal/codex/vehicle-control": if not self._require_json_content_type(): return self._handle_codex_vehicle_control() return if path == "/api/auth/login": if not self._require_json_content_type(): return self._login() return if path == "/api/auth/logout": self._logout() return if not self._authorize(path): return if path == "/api/assistant/stream": if not self._require_json_content_type(): return self._handle_assistant_stream() return if path == "/api/speech/transcribe": self._handle_speech_transcription() return if path == "/api/speech/preview": self._handle_speech_preview() return if not self._require_json_content_type(): return if path == "/api/firmware-workbench/tasks" or path.startswith( "/api/firmware-workbench/" ): self._handle_firmware_workbench_post(path) return if path in { "/api/hardware/access/request", "/api/hardware/access/release", "/api/local/hardware-access/approve", "/api/local/hardware-access/deny", "/api/local/hardware-access/revoke", }: self._handle_hardware_access_post(path) return if path.startswith("/api/extension/"): self._handle_extension_post(path) return if path.startswith("/api/calibration/"): self._handle_calibration_post(path) return if path.startswith("/api/self-repair/"): self._handle_self_repair_post(path) return if path == "/api/mode-profile-drafts/trial": self._handle_mode_profile_draft_trial() return if path == "/api/mode-profiles" or path.startswith("/api/mode-profiles/"): self._handle_mode_profile_post(path) return supplement_match = re.fullmatch( r"/api/requests/(?Preq-[a-z0-9-]+)/supplement", path, ) if ( path not in { "/api/requests", "/api/requests/preview", "/api/can/commands", } and not supplement_match ): self._json({"error": "not_found"}, HTTPStatus.NOT_FOUND) return utterance = "" source = "head_unit" sync_vehicle_task = True def fail_display_sync(code: str, error: Exception) -> None: if not utterance or not sync_vehicle_task: return self.vehicle_display_sync.update_task( status="failed", utterance=utterance, source=source, error={ "code": code, "message": str(error), "outcome": "failed_or_blocked", }, ) try: username = self._session_username() or "local" retry_after = self.auth.api_requests.retry_after(username) if self.auth.config.enabled and retry_after: self._json( { "error": "rate_limited", "message": "请求过于频繁,请稍后再试。", }, HTTPStatus.TOO_MANY_REQUESTS, {"Retry-After": str(retry_after)}, ) return if self.auth.config.enabled: self.auth.api_requests.record(username) payload = self._read_json_body() direct_can_commands = ( payload.get("commands") if path == "/api/can/commands" else None ) utterance = ( ";".join( item if isinstance(item, str) else repr(item) for item in direct_can_commands ) if isinstance(direct_can_commands, list) else str(payload.get("utterance", "")) ) source = str(payload.get("client_type", "head_unit")) sync_vehicle_task = source not in NON_DISPATCH_DISPLAY_SOURCES if utterance and sync_vehicle_task: self.vehicle_display_sync.update_task( status="running", utterance=utterance, source=source, ) hardware_client_id = ( str(payload["hardware_client_id"]) if payload.get("hardware_client_id") else None ) reasoning_effort = ( str(payload["reasoning_effort"]) if payload.get("reasoning_effort") is not None else None ) provider = str(payload.get("provider", "auto")) execution_mode = str(payload.get("execution_mode", "dry_run")) conversation_id = ( str(payload["conversation_id"]) if payload.get("conversation_id") else None ) common_arguments = { "utterance": utterance, "provider": provider, "conversation_id": conversation_id, "execution_mode": execution_mode, # Hardware authorization is a server deployment policy. The # browser never receives or transmits this secret. "approval_token": os.getenv( "COGNITIVE_AGENT_HARDWARE_APPROVAL_TOKEN" ), "vehicle_id": ( str(payload["vehicle_id"]) if payload.get("vehicle_id") else None ), "simulation_fault": str( payload.get("simulation_fault", "normal") ), "hardware_client_id": hardware_client_id, "hardware_access_bypass": self._hardware_access_bypass_allowed(), "reasoning_effort": reasoning_effort, "control_transport": str( payload.get("control_transport", "wired_can") ), } speech_utterance_id = str( payload.get("speech_utterance_id", "") ).strip() speech_understanding: dict[str, object] | None = None speech_peripheral_route = False if speech_utterance_id: if path != "/api/requests": raise ValueError( "speech semantic execution is only accepted by /api/requests" ) speech_understanding = self.speech.cached_result( speech_utterance_id ) clean_text = str( speech_understanding.get("clean_text", "") ).strip() raw_text = str( speech_understanding.get("raw_text", "") ).strip() peripheral_routing = self._speech_peripheral_routing(raw_text) peripheral_transcript = bool( peripheral_routing.get("execution_eligible") and raw_text ) expected_text = raw_text if peripheral_transcript else clean_text if utterance != expected_text: raise SpeechRecognitionError( "speech_text_changed", "语音文本已被修改,请确认修改后的内容再执行。", status=409, ) speech_peripheral_route = peripheral_transcript if ( speech_understanding.get("execution_eligible") is not True and not speech_peripheral_route ): raise SpeechRecognitionError( "speech_not_executable", "语音意图尚未通过能力或参数校验,已拒绝执行。", status=422, ) provider = "local" common_arguments["provider"] = provider if not speech_peripheral_route: common_arguments["_speech_understanding"] = speech_understanding if ( path != "/api/can/commands" and "application/x-ndjson" in self.headers.get("Accept", "") ): self._stream_control_request( path, supplement_match, payload, common_arguments, source=source, ) return if path == "/api/can/commands": result = self.service.process_can_commands( direct_can_commands, conversation_id=( str(payload["conversation_id"]) if payload.get("conversation_id") else None ), execution_mode=str(payload.get("execution_mode", "dry_run")), approval_token=os.getenv( "COGNITIVE_AGENT_HARDWARE_APPROVAL_TOKEN" ), vehicle_id=( str(payload["vehicle_id"]) if payload.get("vehicle_id") else None ), simulation_fault=str( payload.get("simulation_fault", "normal") ), hardware_client_id=hardware_client_id, hardware_access_bypass=self._hardware_access_bypass_allowed(), control_transport=str( payload.get("control_transport", "wired_can") ), ) else: result = None if path in {"/api/requests", "/api/requests/preview"}: result = self._execute_request_with_peripherals( path, common_arguments, ) if result is None: if path == "/api/requests/preview": result = self.service.preview_native_can(**common_arguments) elif supplement_match: result = self.service.supplement_native_can( supplement_match.group("request_id"), provider=( str(payload["provider"]) if payload.get("provider") else None ), approval_token=os.getenv( "COGNITIVE_AGENT_HARDWARE_APPROVAL_TOKEN" ), hardware_client_id=hardware_client_id, hardware_access_bypass=self._hardware_access_bypass_allowed(), reasoning_effort=reasoning_effort, ) else: result = self.service.process(**common_arguments) if speech_peripheral_route and speech_understanding is not None: result["speech_understanding"] = { "utterance_id": speech_utterance_id, "raw_text": speech_understanding.get("raw_text"), "clean_text": speech_understanding.get("clean_text"), "semantic_status": speech_understanding.get("semantic_status"), "routing": "validated_peripheral_text", } utterance = str(result.get("utterance") or utterance) if sync_vehicle_task: self.vehicle_display_sync.update_task( status=str(result.get("status", "succeeded")), utterance=utterance, source=source, result=result, ) self._json(result, HTTPStatus.CREATED) except HardwareAccessRequiredError as exc: fail_display_sync("hardware_access_required", exc) self._json( { "error": "hardware_access_required", "message": str(exc), "outcome": "failed_or_blocked", "access_state": exc.state, }, HTTPStatus.FORBIDDEN, ) except HardwareBusyError as exc: fail_display_sync("hardware_busy", exc) self._json( { "error": "hardware_busy", "message": str(exc), "outcome": "failed_or_blocked", "retry_after_seconds": exc.retry_after_seconds, }, HTTPStatus.CONFLICT, {"Retry-After": str(exc.retry_after_seconds)}, ) except HardwareSafetyError as exc: fail_display_sync("hardware_safety_gate", exc) self._json( { "error": "hardware_safety_gate", "message": str(exc), "outcome": "failed_or_blocked", }, HTTPStatus.FORBIDDEN, ) except CanGatewayError as exc: fail_display_sync("can_transport_failed", exc) self._json( { "error": "can_transport_failed", "message": str(exc), "outcome": "failed_or_blocked", }, HTTPStatus.SERVICE_UNAVAILABLE, ) except GatewayError as exc: fail_display_sync("gscu_unavailable", exc) self._json( {"error": "gscu_unavailable", "message": str(exc)}, HTTPStatus.SERVICE_UNAVAILABLE, ) except ExtensionIntegrationError as exc: fail_display_sync("extension_request_ambiguous", exc) self._json( {"error": "extension_request_ambiguous", "message": str(exc)}, HTTPStatus.UNPROCESSABLE_ENTITY, ) except SpeechRecognitionError as exc: fail_display_sync(exc.code, exc) self._json( {"error": exc.code, "message": str(exc)}, HTTPStatus(exc.status), ) except (ValueError, json.JSONDecodeError) as exc: fail_display_sync("invalid_request", exc) self._json( { "error": "invalid_request", "message": str(exc), "outcome": "failed_or_blocked", }, HTTPStatus.BAD_REQUEST, ) except OpenAIIntentError as exc: fail_display_sync("openai_request_failed", exc) self.log_error("OpenAI intent request failed: %s", exc) self._json( { "error": "openai_request_failed", "message": "GPT 意图解析失败,请检查 API Key、模型权限或网络状态。", "outcome": "failed_or_blocked", }, HTTPStatus.BAD_GATEWAY, ) except Exception as exc: # noqa: BLE001 - keep the demo API responsive fail_display_sync("internal_error", exc) self.log_error("request failed: %s", exc) self._json( { "error": "internal_error", "message": "请求处理失败,请查看服务端日志。", "outcome": "failed_or_blocked", "detail": str(exc), }, HTTPStatus.INTERNAL_SERVER_ERROR, ) def _execute_active_peripheral_request( self, utterance: str, *, provider: str, conversation_id: str | None, execution_mode: str, on_commands_validated: Callable[[dict[str, object]], None] | None = None, natural_language_routing: Mapping[str, object] | None = None, ) -> dict[str, object] | None: """Route a command to the NFC-bound peripheral when one is present.""" # The browser may disappear or reload between learning and execution. # Natural-language routing therefore reads the durable, validated # whitelist instead of trusting the display-sync snapshot. extension_catalog = self.extension.list_peripheral_capabilities() nfc_snapshot = self.nfc.snapshot() plan_catalog = active_peripheral_plan_catalog( utterance, extension_catalog, nfc_snapshot, ) peripheral = None if not plan_catalog: peripheral = resolve_active_peripheral_command( utterance, extension_catalog, nfc_snapshot, ) if not plan_catalog and peripheral is None: return None if conversation_id and not re.fullmatch( r"conv-[a-z0-9-]+", conversation_id ): raise ValueError( "conversation_id must start with conv- and contain only " "lowercase letters, numbers, or hyphens" ) conversation_turn = 1 if conversation_id: context = self.service.conversations.snapshot(conversation_id) conversation_turn = int(context.get("turn_count", 0)) + 1 if plan_catalog: plan_result = self.extension.plan( { "utterance": utterance, "peripherals": plan_catalog, "vehicle_devices": [], "provider": provider, **( { "natural_language_routing": dict( natural_language_routing ) } if natural_language_routing is not None else {} ), } ) if plan_result.get("needs_confirmation"): raise ExtensionIntegrationError( str(plan_result.get("question") or "请明确要控制的外设。") ) result = self.extension.execute_peripheral_plan_request( utterance, plan_result, plan_catalog, conversation_id=conversation_id, conversation_turn=conversation_turn, execution_mode=execution_mode, provider_requested=provider, on_commands_validated=on_commands_validated, ) self.vehicle_display_sync.update_extension( action={ "type": "sequence_emit", "source": "main_orchestration", "step_count": len(plan_result.get("steps", [])), }, result=result, ) return result assert peripheral is not None result = self.extension.execute_peripheral_request( utterance, peripheral, conversation_id=conversation_id, conversation_turn=conversation_turn, execution_mode=execution_mode, provider_requested=provider, on_commands_validated=on_commands_validated, ) self.vehicle_display_sync.update_extension( action={ "type": "emit", "source": "main_orchestration", "slot": peripheral["slot"], "capability_id": peripheral.get("id"), }, result=result, ) return result def _speech_peripheral_routing(self, utterance: str) -> dict[str, object]: """Validate whether an ASR transcript may enter peripheral routing. Vehicle speech keeps using its cached structured semantic payload. A learned peripheral cannot be represented by the static vehicle capability catalog, so it may instead reuse the exact server-owned ASR transcript only after the live NFC binding and learned action resolve. """ text = str(utterance or "").strip() if not text: return {"execution_eligible": False, "action_count": 0} try: catalog = self.extension.list_peripheral_capabilities() nfc_snapshot = self.nfc.snapshot() partition = partition_active_peripheral_intent( text, catalog, nfc_snapshot, ) entries = list(partition.get("peripheral_actions") or []) unresolved = list( partition.get("unresolved_peripheral_clauses") or [] ) denied = list(partition.get("denied_peripheral_actions") or []) plan_catalog = active_peripheral_plan_catalog( text, catalog, nfc_snapshot, ) direct = None if not entries and not plan_catalog and not unresolved and not denied: direct = resolve_active_peripheral_command( text, catalog, nfc_snapshot, ) except (ExtensionIntegrationError, GatewayError, ValueError): return {"execution_eligible": False, "action_count": 0} action_count = len(entries) or (1 if direct is not None else 0) if plan_catalog: action_count = max(action_count, 1) return { "execution_eligible": bool(action_count and not unresolved and not denied), "action_count": action_count, "has_ordered_plan": bool(plan_catalog), } @staticmethod def _residual_has_vehicle_language(utterance: str) -> bool: """Recognize only obvious vehicle-domain text in a claimed mixed request.""" return bool( re.search( r"空调|温度|风量|循环|主驾|副驾|座椅|车窗|车门|后门|" r"遮阳帘|阅读灯|氛围灯|室内灯|出风口|风口|" r"\b(?:ac|seat|window|door|shade|light|outlet)\b", utterance, flags=re.IGNORECASE, ) ) def _execute_local_peripheral_actions( self, utterance: str, entries: list[dict[str, object]], *, provider: str, conversation_id: str | None, execution_mode: str, on_commands_validated: Callable[[dict[str, object]], None] | None = None, ) -> dict[str, object]: """Execute already validated infrared capabilities.""" capabilities = [ { **capability, "parse_source": str(capability.get("parse_source") or "local_rule"), } for entry in entries if isinstance(entry, dict) and isinstance(entry.get("capability"), dict) for capability in [dict(entry["capability"])] ] if not capabilities: raise ExtensionIntegrationError("没有可执行的已学习红外能力。") conversation_turn = 1 if conversation_id: context = self.service.conversations.snapshot(conversation_id) conversation_turn = int(context.get("turn_count", 0)) + 1 if len(capabilities) == 1: result = self.extension.execute_peripheral_request( utterance, capabilities[0], conversation_id=conversation_id, conversation_turn=conversation_turn, execution_mode=execution_mode, provider_requested=provider, on_commands_validated=on_commands_validated, ) else: result = self.extension.execute_peripheral_plan_request( utterance, { "summary": f"本地规则命中 {len(capabilities)} 个已学习红外动作。", "provider_used": "infrared_local_rules", "steps": [ { "kind": "peripheral", "capability_id": capability["id"], } for capability in capabilities ], }, capabilities, conversation_id=conversation_id, conversation_turn=conversation_turn, execution_mode=execution_mode, provider_requested=provider, on_commands_validated=on_commands_validated, ) result["provider_used"] = "infrared_local_rules" self.vehicle_display_sync.update_extension( action={ "type": "emit_sequence" if len(capabilities) > 1 else "emit", "source": "main_orchestration_local_rule", "capability_ids": [item["id"] for item in capabilities], "slots": [item["slot"] for item in capabilities], }, result=result, ) return result @staticmethod def _merge_can_and_peripheral_results( utterance: str, can_result: dict[str, object], infrared_result: dict[str, object], entries: list[dict[str, object]], ) -> dict[str, object]: """Create one response while retaining each transport's evidence.""" merged = deepcopy(can_result) can_status = str(can_result.get("status") or "failed") infrared_status = str(infrared_result.get("status") or "failed") successful = {"succeeded", "partially_succeeded", "no_action"} can_ok = can_status in successful infrared_ok = infrared_status in successful function_configs = deepcopy(can_result.get("function_configs") or {}) function_configs.update( deepcopy(infrared_result.get("function_configs") or {}) ) command_plans = deepcopy(can_result.get("command_plans") or {}) command_plans.update(deepcopy(infrared_result.get("command_plans") or {})) trace_events = [ *deepcopy(list(infrared_result.get("trace_events") or [])), *deepcopy(list(can_result.get("trace_events") or [])), ] can_operations = list(can_result.get("operation_results") or []) infrared_operations = list(infrared_result.get("operation_results") or []) operation_results = [*can_operations, *infrared_operations] can_report = deepcopy(can_result.get("execution_report")) infrared_report = deepcopy(infrared_result.get("execution_report")) execution_report = can_report or infrared_report if isinstance(execution_report, dict): spec = execution_report.setdefault("spec", {}) if execution_report is can_report and isinstance(infrared_report, dict): spec["results"] = [ *list(spec.get("results") or []), *list((infrared_report.get("spec") or {}).get("results") or []), ] spec["status"] = ( "succeeded" if can_ok and infrared_ok else "partially_succeeded" ) metadata = execution_report.setdefault("metadata", {}) labels = metadata.setdefault("labels", {}) labels["route"] = "unified_can_infrared" infrared_actions = [ { "index": entry.get("index"), "clause": entry.get("clause"), "capability_id": capability.get("id"), "device_id": capability.get("device_id"), "device_name": capability.get("device_name"), "action": capability.get("action"), "slot": capability.get("slot"), "parse_source": str( capability.get("parse_source") or "local_rule" ), "transport": str(infrared_result.get("control_transport") or "gscu_tcp"), } for entry in entries for capability in [entry.get("capability")] if isinstance(capability, dict) ] merged.update( { "utterance": utterance, "status": ( "succeeded" if can_ok and infrared_ok else "partially_succeeded" if can_ok or infrared_ok else "failed" ), "success": can_ok and infrared_ok, "orchestration_route": "unified_can_infrared", "provider_used": ( f"{can_result.get('provider_used') or 'vehicle'}+" f"{infrared_result.get('provider_used') or 'infrared_local_rules'}" ), "decision_summary": [ *list(can_result.get("decision_summary") or []), *list(infrared_result.get("decision_summary") or []), "CAN 与红外动作已通过独立白名单校验并按各自传输链路执行。", ], "function_configs": function_configs, "command_plans": command_plans, "function_config": next(iter(function_configs.values()), None), "command_plan": next(iter(command_plans.values()), None), "trace_events": trace_events, "execution_report": execution_report, "operation_results": operation_results, "infrared_actions": infrared_actions, "peripheral": infrared_result.get("peripheral"), "control_transports": { "vehicle": can_result.get("control_transport"), "infrared": infrared_result.get("control_transport"), }, "phase_execution_reports": { **dict(can_result.get("phase_execution_reports") or {}), "vehicle": can_report, "infrared": infrared_report, }, } ) return merged @staticmethod def _merge_peripheral_phase_results( utterance: str, local_result: dict[str, object], cloud_result: dict[str, object], ) -> dict[str, object]: """Merge local and hosted IR phases without replaying local actions.""" merged = deepcopy(cloud_result) def phase_documents( local_documents: object, cloud_documents: object, ) -> dict[str, object]: local_mapping = local_documents if isinstance(local_documents, Mapping) else {} cloud_mapping = cloud_documents if isinstance(cloud_documents, Mapping) else {} return { **{ f"local_{key}": deepcopy(value) for key, value in local_mapping.items() }, **{ f"cloud_{key}": deepcopy(value) for key, value in cloud_mapping.items() }, } function_configs = phase_documents( local_result.get("function_configs"), cloud_result.get("function_configs"), ) command_plans = phase_documents( local_result.get("command_plans"), cloud_result.get("command_plans"), ) successful = {"succeeded", "no_action"} local_ok = str(local_result.get("status") or "") in successful cloud_ok = str(cloud_result.get("status") or "") in successful local_request_id = str(local_result.get("request_id") or "") cloud_request_id = str(cloud_result.get("request_id") or "") merged.update( { "utterance": utterance, "status": ( "succeeded" if local_ok and cloud_ok else "partially_succeeded" if local_ok or cloud_ok else "failed" ), "success": local_ok and cloud_ok, "orchestration_route": "two_stage_nfc_peripheral", "provider_used": ( f"{local_result.get('provider_used') or 'infrared_local_rules'}+" f"{cloud_result.get('provider_used') or 'cloud_model'}" ), "preview_request_id": local_request_id, "supplement_of": local_request_id, "supplement_request_id": cloud_request_id, "decision_summary": [ *list(local_result.get("decision_summary") or []), *list(cloud_result.get("decision_summary") or []), "本地红外动作先行下发;云端只补充未解析子句。", ], "function_configs": function_configs, "command_plans": command_plans, "function_config": next(iter(function_configs.values()), None), "command_plan": next(iter(command_plans.values()), None), "trace_events": [ *deepcopy(list(local_result.get("trace_events") or [])), *deepcopy(list(cloud_result.get("trace_events") or [])), ], "validation": [ *deepcopy(list(local_result.get("validation") or [])), *deepcopy(list(cloud_result.get("validation") or [])), ], "operation_results": [ *deepcopy(list(local_result.get("operation_results") or [])), *deepcopy(list(cloud_result.get("operation_results") or [])), ], "phase_execution_reports": { "infrared_local": deepcopy(local_result.get("execution_report")), "infrared_cloud": deepcopy(cloud_result.get("execution_report")), }, "execution_report": deepcopy(cloud_result.get("execution_report")) or deepcopy(local_result.get("execution_report")), "peripheral": deepcopy( cloud_result.get("peripheral") or local_result.get("peripheral") ), } ) for key in ( "execution_mode", "control_transport", "local_control_budget_ms", "local_control_elapsed_ms", "local_control_within_budget", "natural_language_routing", ): if local_result.get(key) is not None: merged[key] = deepcopy(local_result[key]) return merged @staticmethod def _apply_nfc_command_denials( result: dict[str, object], entries: list[dict[str, object]], ) -> dict[str, object]: """Expose command-level NFC denials without discarding allowed work.""" if not entries: return result denied_actions = [ { "index": entry.get("index"), "clause": entry.get("clause"), "capability_id": capability.get("id"), "device_id": capability.get("device_id"), "device_name": capability.get("device_name"), "action": capability.get("action"), "slot": capability.get("slot"), "reason": str(entry.get("reason") or "nfc_command_disabled"), } for entry in entries for capability in [entry.get("capability")] if isinstance(entry, dict) and isinstance(capability, dict) ] if not denied_actions: return result result["denied_peripheral_actions"] = denied_actions result["authorization_filtered"] = True summaries = list(result.get("decision_summary") or []) labels = "、".join( str(item.get("clause") or item.get("action") or "未命名动作") for item in denied_actions ) summaries.append( f"当前 NFC 未授权 {len(denied_actions)} 条红外命令,已过滤:{labels}。" ) result["decision_summary"] = summaries prior_status = str(result.get("status") or "failed") if prior_status in {"succeeded", "partially_succeeded", "no_action"}: result["status"] = "partially_succeeded" result["success"] = False execution_report = result.get("execution_report") if isinstance(execution_report, dict): spec = execution_report.get("spec") if isinstance(spec, dict): spec["status"] = "partially_succeeded" return result def _execute_request_with_peripherals( self, path: str, common_arguments: dict[str, object], *, on_commands_validated: object | None = None, ) -> dict[str, object] | None: """Route IR-only or mixed IR/CAN requests without changing CAN parsing.""" local_stage_started = time.perf_counter() utterance = str(common_arguments.get("utterance") or "").strip() if self.service.is_mode_profile_configuration_request(utterance): # Creating, saving, updating, or restoring a reusable mode is # configuration, never a live-control command. Bypass the # NFC/peripheral executor so a learned device name embedded in the # mode sentence cannot claim it before SeatDemoService handles it. mode_arguments = { **common_arguments, "execution_mode": "dry_run", } callback = ( on_commands_validated if callable(on_commands_validated) else None ) if path == "/api/requests/preview": return self.service.preview_native_can( **mode_arguments, on_commands_validated=callback, ) return self.service.process( **mode_arguments, _on_commands_validated=callback, ) catalog = self.extension.list_peripheral_capabilities() nfc_snapshot = self.nfc.snapshot() partition = partition_active_peripheral_intent( utterance, catalog, nfc_snapshot, ) entries = list(partition.get("peripheral_actions") or []) residual = str(partition.get("residual_utterance") or "").strip() unresolved = list(partition.get("unresolved_peripheral_clauses") or []) denied = list(partition.get("denied_peripheral_actions") or []) parsed_peripheral_clauses = list( dict.fromkeys( str(entry.get("clause") or "").strip() for entry in entries if isinstance(entry, dict) and str(entry.get("clause") or "").strip() ) ) unresolved_peripheral_clauses = list( dict.fromkeys(str(item).strip() for item in unresolved if str(item).strip()) ) natural_language_routing: dict[str, object] = { "original_utterance": utterance, "parsed_clauses": parsed_peripheral_clauses, "resolved_clauses": parsed_peripheral_clauses, "unresolved_clauses": unresolved_peripheral_clauses, } provider = str(common_arguments.get("provider") or "auto") conversation_id = ( str(common_arguments["conversation_id"]) if common_arguments.get("conversation_id") else None ) execution_mode = str(common_arguments.get("execution_mode") or "dry_run") callback = on_commands_validated if callable(on_commands_validated) else None peripheral_validation_published = False peripheral_validation_elapsed_ms: float | None = None def publish_peripheral_validation(result: dict[str, object]) -> None: nonlocal peripheral_validation_elapsed_ms, peripheral_validation_published peripheral_validation_published = True peripheral_validation_elapsed_ms = round( (time.perf_counter() - local_stage_started) * 1000, 3, ) if callback is None: return validated = deepcopy(result) validated["status"] = "processing" validated["phase"] = "commands_validated" validated.update( { "local_control_budget_ms": LOCAL_CONTROL_BUDGET_MS, "local_control_elapsed_ms": peripheral_validation_elapsed_ms, "local_control_within_budget": ( peripheral_validation_elapsed_ms < LOCAL_CONTROL_BUDGET_MS ), "natural_language_routing": deepcopy( natural_language_routing ), } ) callback(validated) infrared_result: dict[str, object] | None = None can_result: dict[str, object] | None = None cloud_supplement_error: str | None = None # Preserve the legacy all-peripheral planner when there is no hosted # ambiguity to resolve. It retains explicit delay/order semantics. if ( entries and residual and not unresolved and not self._residual_has_vehicle_language(residual) and bool( re.search( r"然后|随后|接着|之后|" r"(?:\d+(?:\.\d+)?|[一二三四五六七八九十两半]+)" r"(?:毫秒|秒(?:钟)?|分钟|小时|ms(?![A-Za-z])|s(?![A-Za-z]))", residual, flags=re.IGNORECASE, ) ) ): planned = self._execute_active_peripheral_request( utterance, provider=provider, conversation_id=conversation_id, execution_mode=execution_mode, on_commands_validated=publish_peripheral_validation, natural_language_routing=natural_language_routing, ) if planned is not None: if not peripheral_validation_published: publish_peripheral_validation(planned) if peripheral_validation_elapsed_ms is not None: planned.update( { "local_control_budget_ms": LOCAL_CONTROL_BUDGET_MS, "local_control_elapsed_ms": ( peripheral_validation_elapsed_ms ), "local_control_within_budget": ( peripheral_validation_elapsed_ms < LOCAL_CONTROL_BUDGET_MS ), "natural_language_routing": deepcopy( natural_language_routing ), } ) return planned if not entries and not denied and not unresolved: result = self._execute_active_peripheral_request( utterance, provider=provider, conversation_id=conversation_id, execution_mode=execution_mode, on_commands_validated=publish_peripheral_validation, natural_language_routing=natural_language_routing, ) if result is not None: if not peripheral_validation_published: publish_peripheral_validation(result) if peripheral_validation_elapsed_ms is not None: result.update( { "local_control_budget_ms": LOCAL_CONTROL_BUDGET_MS, "local_control_elapsed_ms": ( peripheral_validation_elapsed_ms ), "local_control_within_budget": ( peripheral_validation_elapsed_ms < LOCAL_CONTROL_BUDGET_MS ), "natural_language_routing": deepcopy( natural_language_routing ), } ) return result # Compile, publish and execute every deterministic IR action before # any hosted planner is contacted. if entries: peripheral_validation_published = False infrared_result = self._execute_local_peripheral_actions( utterance, entries, provider=provider, conversation_id=conversation_id, execution_mode=execution_mode, on_commands_validated=publish_peripheral_validation, ) if not peripheral_validation_published: publish_peripheral_validation(infrared_result) if peripheral_validation_elapsed_ms is not None: infrared_result.update( { "local_control_budget_ms": LOCAL_CONTROL_BUDGET_MS, "local_control_elapsed_ms": ( peripheral_validation_elapsed_ms ), "local_control_within_budget": ( peripheral_validation_elapsed_ms < LOCAL_CONTROL_BUDGET_MS ), "natural_language_routing": deepcopy( natural_language_routing ), } ) # The native preview is also strictly local. Its own manifest records # unresolved CAN clauses for the later supplement endpoint. if residual: can_arguments = { **common_arguments, "utterance": residual, "_cloud_instruction_context": natural_language_routing, } can_validation_callback = callback if callback is not None and infrared_result is not None: def publish_combined_validation( validated_can: dict[str, object], ) -> None: validated = self._merge_can_and_peripheral_results( utterance, validated_can, infrared_result, entries, ) validated = self._apply_nfc_command_denials(validated, denied) validated["status"] = "processing" validated["phase"] = "commands_validated" callback(validated) can_validation_callback = publish_combined_validation if path == "/api/requests/preview": can_result = self.service.preview_native_can( **can_arguments, on_commands_validated=can_validation_callback, ) else: can_result = self.service.process( **can_arguments, _on_commands_validated=can_validation_callback, ) routed = can_result.get("natural_language_routing") if isinstance(routed, dict): natural_language_routing = deepcopy(routed) # Hosted IR planning is deliberately last. Only the unresolved IR # text is submitted, while the trusted context retains the full # sentence and the clauses already handled above. if unresolved and provider != "local": try: candidates = list(partition.get("unresolved_candidates") or []) if not candidates: raise ExtensionIntegrationError( "没有可供云端补充选择的已学习红外能力。" ) cloud_utterance = ",".join(unresolved_peripheral_clauses) plan_result = self.extension.plan( { "utterance": cloud_utterance, "peripherals": candidates, "vehicle_devices": [], "provider": provider, "natural_language_routing": { **natural_language_routing, "cloud_input_utterance": cloud_utterance, }, } ) if plan_result.get("needs_confirmation"): raise ExtensionIntegrationError( str( plan_result.get("question") or "请明确要控制的红外动作。" ) ) cloud_steps = list(plan_result.get("steps") or []) if not cloud_steps: raise ExtensionIntegrationError( "云端模型没有命中已学习红外能力。" ) candidate_by_id = { str(item.get("id")): { **item, "parse_source": "cloud_model", } for item in candidates if isinstance(item, dict) and item.get("id") } selected_cloud = [ candidate_by_id[str(step.get("capability_id"))] for step in cloud_steps if isinstance(step, dict) and step.get("kind") == "peripheral" and str(step.get("capability_id")) in candidate_by_id ] if not selected_cloud: raise ExtensionIntegrationError( "云端模型没有返回允许执行的已学习红外能力。" ) conversation_turn = 1 if conversation_id: context = self.service.conversations.snapshot(conversation_id) conversation_turn = int(context.get("turn_count", 0)) + 1 cloud_infrared_result = ( self.extension.execute_peripheral_plan_request( utterance, plan_result, selected_cloud, conversation_id=conversation_id, conversation_turn=conversation_turn, execution_mode=execution_mode, provider_requested=provider, ) ) next_index = max( [ int(entry.get("index", 0)) for entry in entries if isinstance(entry, dict) ], default=-1, ) + 1 entries.extend( { "index": next_index + index, "clause": cloud_utterance, "capability": capability, } for index, capability in enumerate(selected_cloud) ) infrared_result = ( self._merge_peripheral_phase_results( utterance, infrared_result, cloud_infrared_result, ) if infrared_result is not None else cloud_infrared_result ) except ExtensionIntegrationError as exc: cloud_supplement_error = str(exc) if infrared_result is None and can_result is None: raise if can_result is not None and infrared_result is not None: result = self._merge_can_and_peripheral_results( utterance, can_result, infrared_result, entries, ) elif can_result is not None: result = can_result result["utterance"] = utterance elif infrared_result is not None: result = infrared_result elif unresolved and provider == "local": local_elapsed_ms = round( (time.perf_counter() - local_stage_started) * 1000, 3, ) request_id = f"req-local-preview-{os.urandom(8).hex()}" result = { "request_id": request_id, "preview_request_id": request_id, "utterance": utterance, "conversation_id": conversation_id, "status": "no_action", "success": False, "phase": "deterministic_preview", "provider_requested": provider, "provider_used": "local_rules", "execution_mode": execution_mode, "decision_summary": [ "本地规则未生成控制;仅未解析子句等待云端补充。" ], "function_config": None, "function_configs": {}, "command_plan": None, "command_plans": {}, "trace_events": [], "validation": [], "execution_report": None, "operation_results": [], "parsed_control_signatures": [], "preview_elapsed_ms": local_elapsed_ms, "local_control_budget_ms": LOCAL_CONTROL_BUDGET_MS, "local_control_elapsed_ms": local_elapsed_ms, "local_control_within_budget": ( local_elapsed_ms < LOCAL_CONTROL_BUDGET_MS ), "supplement_required": True, } else: labels = "、".join( str(item.get("clause") or "未命名动作") for item in denied if isinstance(item, dict) ) raise ExtensionIntegrationError( f"当前 NFC 未授权执行以下红外命令:{labels}。" ) result["natural_language_routing"] = deepcopy(natural_language_routing) for key in ("parsed_clauses", "resolved_clauses", "unresolved_clauses"): value = natural_language_routing.get(key) if isinstance(value, list): result[key] = deepcopy(value) if cloud_supplement_error: result["cloud_supplement_error"] = { "stage": "unresolved_peripheral_clauses", "message": cloud_supplement_error, } result["status"] = "partially_succeeded" result["success"] = False result["decision_summary"] = [ *list(result.get("decision_summary") or []), "明确控制已由本地规则先行处理;未解析的红外子句云端补充失败。", ] result = self._apply_nfc_command_denials(result, denied) self.service.persist_composed_result(result) if callback is not None: callback(result) return result def _stream_control_request( self, path: str, supplement_match: re.Match[str] | None, payload: dict[str, object], common_arguments: dict[str, object], *, source: str, ) -> None: """Stream validated command plans before physical execution finishes.""" sync_vehicle_task = source not in NON_DISPATCH_DISPLAY_SOURCES self.send_response(HTTPStatus.OK) self.send_header("Content-Type", "application/x-ndjson; charset=utf-8") self.send_header("Cache-Control", "no-store") self.send_header("X-Accel-Buffering", "no") self._send_security_headers() self.end_headers() stream_open = True def emit(event: dict[str, object]) -> None: nonlocal stream_open if not stream_open: return content = (json.dumps(event, ensure_ascii=False) + "\n").encode( "utf-8" ) try: self.wfile.write(content) self.wfile.flush() except (BrokenPipeError, ConnectionResetError): # The submitted control request must continue even if the # browser closes while the physical execution is in progress. stream_open = False def on_commands_validated(result: dict[str, object]) -> None: emit({"type": "validated_commands", "payload": result}) def emit_terminal_error( code: str, message: str, *, detail: str | None = None, extra: dict[str, object] | None = None, ) -> None: """Publish one terminal failure using can_read_trans outcome terms.""" error = { "code": code, "message": message, "outcome": "failed_or_blocked", **({"detail": detail} if detail else {}), } if sync_vehicle_task: self.vehicle_display_sync.update_task( status="failed", utterance=utterance, source=source, error=error, ) emit({ "type": "error", "error": code, "message": message, "outcome": "failed_or_blocked", **({"detail": detail} if detail else {}), **(extra or {}), }) utterance = str(common_arguments.get("utterance") or "") try: result = None if path in {"/api/requests", "/api/requests/preview"}: result = self._execute_request_with_peripherals( path, common_arguments, on_commands_validated=on_commands_validated, ) if result is None: if path == "/api/requests/preview": result = self.service.preview_native_can( **common_arguments, on_commands_validated=on_commands_validated, ) elif supplement_match: result = self.service.supplement_native_can( supplement_match.group("request_id"), provider=( str(payload["provider"]) if payload.get("provider") else None ), approval_token=os.getenv( "COGNITIVE_AGENT_HARDWARE_APPROVAL_TOKEN" ), hardware_client_id=( str(payload["hardware_client_id"]) if payload.get("hardware_client_id") else None ), hardware_access_bypass=self._hardware_access_bypass_allowed(), reasoning_effort=( str(payload["reasoning_effort"]) if payload.get("reasoning_effort") is not None else None ), on_commands_validated=on_commands_validated, ) else: result = self.service.process( **common_arguments, _on_commands_validated=on_commands_validated, ) utterance = str(result.get("utterance") or utterance) if sync_vehicle_task: self.vehicle_display_sync.update_task( status=str(result.get("status", "succeeded")), utterance=utterance, source=source, result=result, ) emit({"type": "result", "payload": result}) except HardwareAccessRequiredError as exc: emit_terminal_error( "hardware_access_required", str(exc), extra={"access_state": exc.state}, ) except HardwareBusyError as exc: emit_terminal_error( "hardware_busy", str(exc), extra={"retry_after_seconds": exc.retry_after_seconds}, ) except HardwareSafetyError as exc: emit_terminal_error("hardware_safety_gate", str(exc)) except CanGatewayError as exc: emit_terminal_error("can_transport_failed", str(exc)) except GatewayError as exc: emit_terminal_error("gscu_unavailable", str(exc)) except ExtensionIntegrationError as exc: emit_terminal_error("extension_request_ambiguous", str(exc)) except (ValueError, json.JSONDecodeError) as exc: emit_terminal_error("invalid_request", str(exc)) except OpenAIIntentError as exc: self.log_error("OpenAI intent request failed: %s", exc) emit_terminal_error( "openai_request_failed", "GPT 意图解析失败,请检查 API Key、模型权限或网络状态。", detail=str(exc), ) except Exception as exc: # noqa: BLE001 - stream a terminal event self.log_error("streaming request failed: %s", exc) emit_terminal_error( "internal_error", "请求处理失败,请查看服务端日志。", detail=str(exc), ) def _assistant_capability_catalog(self) -> AssistantCapabilityCatalog: """Bind the assistant to the same live registries used by every service.""" if self.assistant_capability_catalog is not None: return self.assistant_capability_catalog vehicle_service = ( self.service if hasattr(self.service, "capability_registry") else SeatDemoHandler.service ) return AssistantCapabilityCatalog( vehicle_service, peripheral_provider=self.extension.list_peripheral_capabilities, nfc_provider=self.nfc.snapshot, speech_provider=self.speech.status, gesture_provider=self.gesture.runtime_info, calibration_provider=self.calibration.snapshot, self_repair_provider=self.repair.health, ) def _handle_assistant_stream(self) -> None: """Serve one unified answer/control turn as incremental NDJSON.""" try: username = self._session_username() principal = username or f"anonymous:{self._client_key()}" retry_after = self.auth.api_requests.retry_after(principal) if retry_after: self._json( {"error": "rate_limited", "message": "请求过于频繁,请稍后再试。"}, HTTPStatus.TOO_MANY_REQUESTS, {"Retry-After": str(retry_after)}, ) return self.auth.api_requests.record(principal) payload = self._read_json_body(max_bytes=64 * 1024) except (ValueError, json.JSONDecodeError) as exc: self._json( {"error": "invalid_request", "message": str(exc)}, HTTPStatus.BAD_REQUEST, ) return message = str(payload.get("message") or payload.get("utterance") or "").strip() conversation_id = str(payload.get("conversation_id") or "").strip() if not conversation_id: conversation_id = f"conv-{os.urandom(16).hex()}" history_payload = payload.get("history") history = history_payload if isinstance(history_payload, list) else [] source = str(payload.get("client_type") or "web_console_assistant") hardware_client_id = ( str(payload["hardware_client_id"]) if payload.get("hardware_client_id") else None ) reasoning_effort = ( str(payload["reasoning_effort"]) if payload.get("reasoning_effort") is not None else "low" ) mode_profile_draft_context = payload.get("mode_profile_draft_context") parsed_mode_update = parse_mode_profile_update( message, expected_mode_name=( mode_profile_draft_context.get("name") if isinstance(mode_profile_draft_context, Mapping) else None ), ) matching_mode_profile_draft_context = ( dict(mode_profile_draft_context) if isinstance(mode_profile_draft_context, Mapping) and parsed_mode_update is not None and normalize_mode_profile_name(parsed_mode_update[0]) == normalize_mode_profile_name( mode_profile_draft_context.get("name") ) else None ) requested_vehicle_id = ( str(payload["vehicle_id"]) if payload.get("vehicle_id") else None ) common_arguments: dict[str, object] = { "utterance": message, # Explicit controls take the deterministic preview path first. # ``auto`` permits a second phase only when that preview reports # genuinely unresolved clauses; fully local requests never invoke # a hosted model. "provider": "auto", "conversation_id": conversation_id, "execution_mode": str(payload.get("execution_mode") or "dry_run"), "approval_token": os.getenv("COGNITIVE_AGENT_HARDWARE_APPROVAL_TOKEN"), "vehicle_id": requested_vehicle_id, "simulation_fault": str(payload.get("simulation_fault") or "normal"), "hardware_client_id": hardware_client_id, "hardware_access_bypass": self._hardware_access_bypass_allowed(), "reasoning_effort": reasoning_effort, "control_transport": str(payload.get("control_transport") or "wired_can"), **( { "mode_profile_draft_context": ( matching_mode_profile_draft_context ) } if matching_mode_profile_draft_context is not None else {} ), } capability_catalog = self._assistant_capability_catalog() try: capability_snapshot = capability_catalog.snapshot( vehicle_id=requested_vehicle_id ) except ValueError as exc: self._json( {"error": "invalid_request", "message": str(exc)}, HTTPStatus.BAD_REQUEST, ) return capability_context = capability_catalog.compact_context(capability_snapshot) scene_capability_context = capability_catalog.scene_composition_context( capability_snapshot ) fast_answer = capability_catalog.fast_answer(message, capability_snapshot) quick_control_provider = getattr(capability_catalog, "quick_controls", None) quick_controls = ( quick_control_provider(capability_snapshot) if callable(quick_control_provider) else () ) self.send_response(HTTPStatus.OK) self.send_header("Content-Type", "application/x-ndjson; charset=utf-8") self.send_header("Cache-Control", "no-store") self.send_header("X-Accel-Buffering", "no") self._send_security_headers() self.end_headers() stream_open = True def emit(event: dict[str, object]) -> bool: nonlocal stream_open if not stream_open: return False content = (json.dumps(event, ensure_ascii=False) + "\n").encode("utf-8") try: self.wfile.write(content) self.wfile.flush() return True except (BrokenPipeError, ConnectionResetError): stream_open = False return False def record_control_failure(code: str, public_message: str) -> None: self.vehicle_display_sync.update_task( status="failed", utterance=message, source=source, error={"code": code, "message": public_message}, ) local_preview_result: dict[str, object] | None = None def preview_control( instruction: str, on_commands_validated: Callable[[dict[str, object]], None], ) -> dict[str, object]: """Run only deterministic phases and retain their merge evidence.""" nonlocal local_preview_result self.vehicle_display_sync.update_task( status="running", utterance=message, source=source, ) preview_arguments = { **common_arguments, "utterance": instruction, "provider": "local", } result = self._execute_request_with_peripherals( "/api/requests/preview", preview_arguments, on_commands_validated=on_commands_validated, ) if result is None: result = self.service.preview_native_can( **preview_arguments, on_commands_validated=on_commands_validated, ) local_preview_result = result self.vehicle_display_sync.update_task( status=str(result.get("status") or "succeeded"), utterance=message, source=source, result=result, ) return result def supplement_control( instruction: str, on_commands_validated: Callable[[dict[str, object]], None], assistant_plan: Mapping[str, object] | None = None, ) -> dict[str, object]: """Execute a Codex proposal for unresolved text without replay.""" preview = local_preview_result if preview is None: raise CodexAssistantError("local control preview is missing") routing_value = preview.get("natural_language_routing") routing = ( deepcopy(routing_value) if isinstance(routing_value, Mapping) else { "original_utterance": message, "parsed_clauses": [], "resolved_clauses": [], "unresolved_clauses": [instruction], } ) routing["cloud_input_utterance"] = instruction excluded_signatures = { tuple(item) for item in preview.get("parsed_control_signatures", []) if isinstance(item, list) } def publish_supplement(validated: dict[str, object]) -> None: merged_event = self.service._merge_native_can_phases( preview, validated, ) merged_event.update( { "phase": "supplement_commands_validated", "preview_request_id": preview.get("request_id"), "supplement_of": preview.get("request_id"), "natural_language_routing": deepcopy(routing), } ) on_commands_validated(merged_event) process_arguments = { key: value for key, value in common_arguments.items() if key not in { "utterance", "provider", "mode_profile_draft_context", } } if assistant_plan is not None: supplement = self.service.process( instruction, provider="local", **process_arguments, _excluded_control_signatures=excluded_signatures, _model_parse_source="codex_supplement", _allow_empty_control_result=True, _on_commands_validated=publish_supplement, _assistant_plan=dict(assistant_plan), _cloud_instruction_context=routing, ) else: supplement_arguments = { **common_arguments, "utterance": instruction, "provider": "local", } supplement = self._execute_request_with_peripherals( "/api/requests", supplement_arguments, on_commands_validated=publish_supplement, ) if supplement is None: supplement = self.service.process( instruction, provider="local", **process_arguments, _excluded_control_signatures=excluded_signatures, _model_parse_source="codex_supplement", _allow_empty_control_result=True, _on_commands_validated=publish_supplement, _cloud_instruction_context=routing, ) merged = self.service._merge_native_can_phases(preview, supplement) merged.update( { "phase": "two_stage_complete", "utterance": message, "preview_request_id": preview.get("request_id"), "supplement_of": preview.get("request_id"), "supplement_request_id": supplement.get("request_id"), "supplement_required": False, "natural_language_routing": routing, } ) self.service.persist_composed_result(merged) self.vehicle_display_sync.update_task( status=str(merged.get("status") or "succeeded"), utterance=message, source=source, result=merged, ) return merged def execute_control( instruction: str, on_commands_validated: Callable[[dict[str, object]], None], assistant_plan: Mapping[str, object] | None = None, ) -> dict[str, object]: self.vehicle_display_sync.update_task( status="running", utterance=message, source=source, ) turn_arguments = { **common_arguments, "utterance": instruction, # Codex has already normalized this agent-tool request. Keep # the safety execution pass deterministic; explicit user text # uses preview_control/supplement_control above instead. "provider": "local", **({"assistant_plan": dict(assistant_plan)} if assistant_plan is not None else {}), } try: result = self._execute_request_with_peripherals( "/api/requests/preview", turn_arguments, on_commands_validated=on_commands_validated, ) if result is None: result = self.service.preview_native_can( **turn_arguments, on_commands_validated=on_commands_validated, ) if result.get("supplement_required"): result = self.service.supplement_native_can( str(result["request_id"]), provider="local", approval_token=os.getenv("COGNITIVE_AGENT_HARDWARE_APPROVAL_TOKEN"), hardware_client_id=hardware_client_id, hardware_access_bypass=self._hardware_access_bypass_allowed(), reasoning_effort=reasoning_effort, on_commands_validated=on_commands_validated, ) except HardwareAccessRequiredError: record_control_failure("hardware_access_required", "真实车控当前不可用。") raise except HardwareBusyError: record_control_failure("hardware_busy", "车辆控制通道正在使用中。") raise except HardwareSafetyError: record_control_failure("hardware_safety_gate", "操作未通过车辆安全校验。") raise except CanGatewayError: record_control_failure("can_transport_failed", "车辆控制通道当前不可用。") raise except (GatewayError, ExtensionIntegrationError): record_control_failure("device_control_failed", "外设控制没有完成。") raise except Exception: record_control_failure("vehicle_control_failed", "车辆控制没有完成。") raise self.vehicle_display_sync.update_task( status=str(result.get("status") or "succeeded"), utterance=message, source=source, result=result, ) return result try: self.assistant.stream( message, conversation_id=conversation_id, principal=principal, history=history, emit=emit, execute_control=execute_control, preview_control=preview_control, supplement_control=supplement_control, capability_snapshot=capability_snapshot, capability_context=capability_context, scene_capability_context=scene_capability_context, control_terms=capability_catalog.control_terms(capability_snapshot), query_capabilities=capability_catalog.query, authoritative_answer=fast_answer.answer if fast_answer else None, authoritative_topic=( getattr(fast_answer, "topic", None) if fast_answer else None ), authoritative_suggestions=( [ item.as_dict() for item in getattr(fast_answer, "suggestions", ()) ] if fast_answer else [] ), quick_control_commands=[item.command for item in quick_controls], mode_profile_draft_context=mode_profile_draft_context, ) except HardwareAccessRequiredError as exc: self.vehicle_display_sync.update_task( status="failed", utterance=message, source=source, error={"code": "hardware_access_required", "message": str(exc)}, ) emit( { "type": "error", "error": "hardware_access_required", "message": f"暂时不能执行真实车控:{exc}", "access_state": exc.state, } ) except HardwareBusyError as exc: record_control_failure("hardware_busy", "车辆控制通道正在使用中。") emit( { "type": "error", "error": "hardware_busy", "message": f"车辆控制通道正在使用中:{exc}", "retry_after_seconds": exc.retry_after_seconds, } ) except HardwareSafetyError as exc: record_control_failure("hardware_safety_gate", "操作未通过车辆安全校验。") emit( { "type": "error", "error": "hardware_safety_gate", "message": f"该操作未通过车辆安全校验:{exc}", } ) except CanGatewayError as exc: record_control_failure("can_transport_failed", "车辆控制通道当前不可用。") emit( { "type": "error", "error": "can_transport_failed", "message": f"车辆控制通道当前不可用:{exc}", } ) except (GatewayError, ExtensionIntegrationError) as exc: record_control_failure("device_control_failed", "外设控制没有完成。") emit( { "type": "error", "error": "device_control_failed", "message": f"外设控制没有完成:{exc}", } ) except (CodexAppServerError, CodexAssistantError, OpenAIIntentError) as exc: self.log_error("assistant model request failed: %s", exc) emit( { "type": "error", "error": "assistant_model_failed", "message": "小迪暂时没有响应,请稍后再试。", } ) except (ValueError, json.JSONDecodeError) as exc: emit({"type": "error", "error": "invalid_request", "message": str(exc)}) except Exception as exc: # noqa: BLE001 - stream one safe terminal event self.log_error("assistant request failed: %s", exc) emit( { "type": "error", "error": "internal_error", "message": "请求处理失败,请稍后再试。", } ) def _handle_codex_vehicle_control(self) -> None: """Run an allowlisted MCP tool over the authenticated loopback bridge.""" try: content_length = int(self.headers.get("Content-Length", "0")) except ValueError: content_length = 0 if not self._is_unproxied_loopback_request(): self._json( {"error": "local_access_required", "message": "仅允许本机 Codex 工具调用。"}, HTTPStatus.FORBIDDEN, ) self._drain_rejected_request_body(content_length, max_bytes=32 * 1024) return if not self.assistant.authenticate_internal_request( self.headers.get("X-N7-Internal-Token", "") ): self._json( {"error": "invalid_internal_token", "message": "Codex 工具认证失败。"}, HTTPStatus.FORBIDDEN, ) self._drain_rejected_request_body(content_length, max_bytes=32 * 1024) return try: payload = self._read_json_body(max_bytes=32 * 1024) tool_name = str(payload.get("tool") or "execute_cockpit_request") request_token = str(payload.get("request_token") or "") if tool_name == "get_n7web_capabilities": result = self.assistant.get_n7web_capabilities( request_token, str(payload.get("query") or ""), ) elif tool_name == "execute_cockpit_request": plan = payload.get("plan") if plan is not None and not isinstance(plan, dict): raise ValueError("plan must be an object") result = self.assistant.execute_cockpit_request( request_token, plan, ) else: raise ValueError("unsupported Codex application tool") self._json({"success": True, "result": result}) except (ValueError, json.JSONDecodeError) as exc: self._json( {"error": "invalid_request", "message": str(exc)}, HTTPStatus.BAD_REQUEST, ) except CodexVehicleToolError as exc: self._json( {"error": "vehicle_control_failed", "message": str(exc)}, HTTPStatus.CONFLICT, ) except Exception as exc: # noqa: BLE001 - return a safe MCP failure self.log_error("Codex vehicle tool failed: %s", exc) self._json( {"error": "vehicle_control_failed", "message": str(exc)}, HTTPStatus.CONFLICT, ) def do_PUT(self) -> None: # noqa: N802 - stdlib handler API path = unquote(urlparse(self.path).path) if not self._authorize(path): return if not self._require_same_origin() or not self._require_json_content_type(): return match = re.fullmatch(r"/api/extension/gscu/slots/(?P\d+)", path) if match is None: self._json({"error": "not_found"}, HTTPStatus.NOT_FOUND) return try: payload = self._read_json_body(max_bytes=4096) self._json( self.extension.save_infrared_slot(match.group("slot"), payload), HTTPStatus.OK, ) except (GatewayError, ValueError, json.JSONDecodeError) as exc: self._json( {"success": False, "error": "invalid_request", "message": str(exc)}, HTTPStatus.BAD_REQUEST, ) def do_DELETE(self) -> None: # noqa: N802 - stdlib handler API parsed_url = urlparse(self.path) path = unquote(parsed_url.path) if not self._authorize(path): return if not self._require_same_origin(): return slot_match = re.fullmatch(r"/api/extension/gscu/slots/(?P\d+)", path) if slot_match is not None: try: self._json( self.extension.delete_infrared_slot(slot_match.group("slot")), HTTPStatus.OK, ) except GatewayError as exc: self._json( {"success": False, "error": "invalid_request", "message": str(exc)}, HTTPStatus.BAD_REQUEST, ) return firmware_artifact_match = re.fullmatch( r"/api/firmware-workbench/artifacts/(?P[a-z0-9-]+)", path, ) if firmware_artifact_match is not None: try: self._json( self.firmware_workbench.delete_artifact( firmware_artifact_match.group("artifact_id") ), HTTPStatus.OK, ) except FirmwareWorkbenchError as exc: self._json( {"error": exc.code, "message": str(exc)}, HTTPStatus(exc.status), ) return prefix = "/api/mode-profiles/" if not path.startswith(prefix): self._json({"error": "not_found"}, HTTPStatus.NOT_FOUND) return mode_id = path.removeprefix(prefix).strip() vehicle_id = str(parse_qs(parsed_url.query).get("vehicle_id", [""])[0]) or None try: self._json( self.service.delete_user_mode_profile(mode_id, vehicle_id), HTTPStatus.OK, ) except KeyError: self._json( {"error": "not_found", "message": "未找到可删除的用户自定义模式。"}, HTTPStatus.NOT_FOUND, ) except PermissionError as exc: self._json( {"error": "preset_mode_protected", "message": str(exc)}, HTTPStatus.FORBIDDEN, ) except ValueError as exc: self._json( {"error": "invalid_request", "message": str(exc)}, HTTPStatus.BAD_REQUEST, ) def _handle_mode_profile_post(self, path: str) -> None: execute_match = re.fullmatch( r"/api/mode-profiles/(?P[a-z0-9][a-z0-9_-]{0,63})/execute", path, ) if execute_match is not None: self._handle_mode_profile_execute(execute_match.group("mode_id")) return create_mode = path == "/api/mode-profiles" mode_id = path.removeprefix("/api/mode-profiles/").strip() if not create_mode and not mode_id: self._json( {"error": "invalid_request", "message": "模式编号不能为空。"}, HTTPStatus.BAD_REQUEST, ) return try: payload = self._read_json_body(max_bytes=16 * 1024) arguments = ( str(payload.get("name", "")), str(payload.get("instruction", "")), str(payload.get("source_request_id", "")), str(payload.get("vehicle_id", "")) or None, ) result = ( self.service.create_mode_profile_card(*arguments) if create_mode else self.service.save_mode_profile_card(mode_id, *arguments) ) self._json(result, HTTPStatus.CREATED if create_mode else HTTPStatus.OK) except KeyError: self._json( {"error": "not_found", "message": "未找到需要编辑的模式卡片。"}, HTTPStatus.NOT_FOUND, ) except (ValueError, json.JSONDecodeError) as exc: self._json( {"error": "invalid_request", "message": str(exc)}, HTTPStatus.BAD_REQUEST, ) def _handle_mode_profile_draft_trial(self) -> None: try: payload = self._read_json_body(max_bytes=16 * 1024) result = self.service.trial_mode_profile_draft( str(payload.get("name", "")), str(payload.get("instruction", "")), str(payload.get("source_request_id", "")), str(payload.get("vehicle_id", "")) or None, ) self._json(result, HTTPStatus.OK) except (ValueError, json.JSONDecodeError) as exc: self._json( {"error": "invalid_request", "message": str(exc)}, HTTPStatus.BAD_REQUEST, ) def _saved_mode_infrared_entries( self, profile: object, ) -> list[dict[str, object]]: command_flow = getattr(profile, "command_flow", None) if not isinstance(command_flow, dict): return [] configurations = command_flow.get("function_configs") or {} extension_config = None if isinstance(configurations, dict): extension_config = configurations.get("self_extension") if not isinstance(extension_config, dict): extension_config = next( ( config for config in configurations.values() if isinstance(config, dict) and str((config.get("spec") or {}).get("domain")) == "self_extension" ), None, ) operations = ( (extension_config.get("spec") or {}).get("operations") or [] if isinstance(extension_config, dict) else [] ) if not operations: return [] snapshot = self.nfc.snapshot() card = snapshot.get("card") if isinstance(snapshot, dict) else None active_uid = re.sub( r"[^0-9A-Fa-f]", "", str((card or {}).get("uid") or ""), ).upper() if not active_uid or str((card or {}).get("action") or "present") != "present": raise ExtensionIntegrationError( "该模式包含 NFC 红外指令,请先放置生成此模式时绑定的 NFC 卡。" ) catalog = self.extension.list_peripheral_capabilities() entries: list[dict[str, object]] = [] for index, operation in enumerate(operations): if not isinstance(operation, dict) or operation.get("capability") != "infrared.emit": continue parameters = operation.get("parameters") or {} target = operation.get("target") or {} try: slot = int(parameters.get("slot")) except (TypeError, ValueError) as exc: raise ExtensionIntegrationError( "已保存模式包含无效的红外槽位。" ) from exc device_id = str(target.get("device") or "").strip() capability = next( ( item for item in catalog if item.get("status") == "learned" and item.get("enabled", True) is True and int(item.get("slot") or 0) == slot and str(item.get("device_id") or "") == device_id and re.sub( r"[^0-9A-Fa-f]", "", str(item.get("card_uid") or ""), ).upper() == active_uid ), None, ) if capability is None: raise ExtensionIntegrationError( f"当前 NFC 未授权模式中的红外命令:{device_id} / IR {slot}。" ) entries.append( { "index": index, "clause": str(parameters.get("action") or f"IR {slot}"), "capability": { **capability, "parse_source": "saved_mode_command_flow", }, } ) return entries def _handle_mode_profile_execute(self, mode_id: str) -> None: utterance = "" source = "web_console_mode_card" try: payload = self._read_json_body(max_bytes=16 * 1024) vehicle_id = str(payload.get("vehicle_id", "")) or None profile = self.service.get_mode_profile_card(mode_id, vehicle_id) utterance = profile.name source = str(payload.get("client_type") or source) execution_mode = str(payload.get("execution_mode", "dry_run")) control_transport = str( payload.get("control_transport", "wired_can") ) conversation_id = ( str(payload["conversation_id"]) if payload.get("conversation_id") else None ) hardware_client_id = ( str(payload["hardware_client_id"]) if payload.get("hardware_client_id") else None ) self.vehicle_display_sync.update_task( status="running", utterance=utterance, source=source, ) # Validate the complete saved flow, including the live NFC # binding, before any transport is allowed to execute. entries = self._saved_mode_infrared_entries(profile) can_result = self.service.execute_mode_profile_card( mode_id, conversation_id=conversation_id, execution_mode=execution_mode, approval_token=os.getenv( "COGNITIVE_AGENT_HARDWARE_APPROVAL_TOKEN" ), vehicle_id=vehicle_id, simulation_fault=str(payload.get("simulation_fault", "normal")), hardware_client_id=hardware_client_id, hardware_access_bypass=self._hardware_access_bypass_allowed(), control_transport=control_transport, ) if entries: infrared_result = self._execute_local_peripheral_actions( utterance, entries, provider="saved_mode_command_flow", conversation_id=conversation_id, execution_mode=execution_mode, ) result = self._merge_can_and_peripheral_results( utterance, can_result, infrared_result, entries, ) result["provider_used"] = "saved_mode_command_flow" result["orchestration_route"] = "saved_mode_command_flow" result["mode_profile"] = profile.public_dict() self.service.persist_composed_result(result) else: result = can_result self.vehicle_display_sync.update_task( status=str(result.get("status") or "succeeded"), utterance=utterance, source=source, result=result, ) self._json(result, HTTPStatus.OK) except KeyError: self._json( {"error": "not_found", "message": "未找到需要执行的模式卡片。"}, HTTPStatus.NOT_FOUND, ) except HardwareAccessRequiredError as exc: self._json( { "error": "hardware_access_required", "message": str(exc), "access_state": exc.state, }, HTTPStatus.FORBIDDEN, ) except HardwareBusyError as exc: self._json( { "error": "hardware_busy", "message": str(exc), "retry_after_seconds": exc.retry_after_seconds, }, HTTPStatus.CONFLICT, ) except (HardwareSafetyError, CanGatewayError, GatewayError) as exc: if utterance: self.vehicle_display_sync.update_task( status="failed", utterance=utterance, source=source, error={"code": "mode_execution_failed", "message": str(exc)}, ) self._json( {"error": "mode_execution_failed", "message": str(exc)}, HTTPStatus.SERVICE_UNAVAILABLE, ) except (ExtensionIntegrationError, ValueError, json.JSONDecodeError) as exc: if utterance: self.vehicle_display_sync.update_task( status="failed", utterance=utterance, source=source, error={"code": "invalid_mode_command_flow", "message": str(exc)}, ) self._json( {"error": "invalid_mode_command_flow", "message": str(exc)}, HTTPStatus.BAD_REQUEST, ) def _handle_extension_post(self, path: str) -> None: action_map = { "/api/extension/gscu/info": "info", "/api/extension/gscu/learn": "learn", "/api/extension/gscu/cancel": "cancel", "/api/extension/gscu/emit": "emit", "/api/extension/gscu/read": "read", "/api/extension/gscu/write": "write", "/api/extension/gscu/restart": "restart", "/api/extension/gscu/reset": "reset", "/api/extension/gscu/wifi-lock": "wifi_lock", "/api/extension/gscu/raw": "raw", } try: payload = self._read_json_body(max_bytes=256 * 1024) if path in { "/api/extension/gscu/erase", "/api/extension/gscu/erase-all", }: result = self.extension.erase_all_infrared(dict(payload)) self.vehicle_display_sync.update_extension( action={"type": "erase_all"}, result=result, ) self._json(result) return action = action_map.get(path) if action: self._handle_extension_gateway(action, payload) return if path == "/api/extension/gscu/config": result = self.extension.configure_tcp_gateway(dict(payload)) self.vehicle_display_sync.update_extension( action={ "type": "gateway_config", "listen_host": result.get("listen_host"), "port": result.get("port"), }, result=result, ) self._json(result) return if path == "/api/extension/display-sync": catalog = payload.get("peripherals", payload.get("catalog", [])) if not isinstance(catalog, list) or len(catalog) > 248: raise ValueError("外设能力目录必须是至多 248 项的数组。") durable_sync = self.extension.sync_peripheral_catalog(catalog) sync_result = { "status": "ready", "count": len(catalog), "learned_count": durable_sync["count"], } self.vehicle_display_sync.update_extension( catalog=catalog, action={ "type": "hardware_detected", "device": payload.get("device"), }, result=sync_result, ) self._json({"success": True, **sync_result}) return if path == "/api/extension/assistant/plan": self.vehicle_display_sync.update_extension( catalog=payload.get("peripherals", payload.get("catalog", [])), action={"type": "plan", "command": payload.get("utterance", "")}, ) result = self.extension.plan(payload) self.vehicle_display_sync.update_extension( result=result, ) self._json(result) return if path == "/api/extension/vehicle/execute": result = self.extension.execute_vehicle( payload, self.service, hardware_access_bypass=self._hardware_access_bypass_allowed(), ) self.vehicle_display_sync.update_task( status=str(result.get("status", "succeeded")), utterance=str(payload.get("command", payload.get("utterance", ""))), source=str(payload.get("client_type", "head_unit")), result=result, ) self.vehicle_display_sync.update_extension( catalog=payload.get("catalog") if "catalog" in payload else None, action={"type": "vehicle_execute"}, result=result, ) self._json(result) return self._json({"error": "not_found"}, HTTPStatus.NOT_FOUND) except GatewayError as exc: self._json( {"success": False, "error": "gscu_unavailable", "message": str(exc)}, HTTPStatus.SERVICE_UNAVAILABLE, ) except ExtensionIntegrationError as exc: self._json( {"success": False, "error": "extension_unavailable", "message": str(exc)}, HTTPStatus.SERVICE_UNAVAILABLE, ) except HardwareAccessRequiredError as exc: self._json( { "success": False, "error": "hardware_access_required", "message": str(exc), "access_state": exc.state, }, HTTPStatus.FORBIDDEN, ) except HardwareBusyError as exc: self._json( { "success": False, "error": "hardware_busy", "message": str(exc), "retry_after_seconds": exc.retry_after_seconds, }, HTTPStatus.CONFLICT, {"Retry-After": str(exc.retry_after_seconds)}, ) except HardwareSafetyError as exc: self._json( {"success": False, "error": "hardware_safety_gate", "message": str(exc)}, HTTPStatus.FORBIDDEN, ) except OpenAIIntentError as exc: self.log_error("extension vehicle intent request failed: %s", exc) self._json( { "success": False, "error": "openai_request_failed", "message": "车端 GPT 意图解析失败,请检查模型配置。", }, HTTPStatus.BAD_GATEWAY, ) except (ValueError, json.JSONDecodeError) as exc: self._json( {"success": False, "error": "invalid_request", "message": str(exc)}, HTTPStatus.BAD_REQUEST, ) except Exception as exc: # noqa: BLE001 - keep extension APIs responsive self.log_error("self-extension request failed: %s", exc) self._json( { "success": False, "error": "internal_error", "message": "自扩展服务处理失败,请查看服务端日志。", }, HTTPStatus.INTERNAL_SERVER_ERROR, ) def _handle_calibration_post(self, path: str) -> None: if not self._require_calibration_admin(): return try: payload = self._read_json_body(max_bytes=256 * 1024) action_map = { "/api/calibration/can/connect": lambda: self.calibration.connect_can( dict(payload.get("can", payload)) ), "/api/calibration/start": lambda: self.calibration.calibrate( None if payload.get("axis", "all") == "all" else int(payload["axis"]) ), "/api/calibration/move": lambda: self.calibration.move( int(payload["axis"]), int(payload["permille"]) ), "/api/calibration/seek": lambda: self.calibration.seek( int(payload["axis"]), bool(payload.get("far")) ), "/api/calibration/stop": self.calibration.stop, "/api/calibration/session/reset": self.calibration.reset_virtual_session, "/api/calibration/settings": lambda: self.calibration.update_settings( dict(payload) ), "/api/calibration/mqtt/connect": self.calibration.connect_mqtt, "/api/calibration/mqtt/disconnect": self.calibration.disconnect_mqtt, "/api/calibration/mqtt/test": self.calibration.mqtt_test, "/api/calibration/video/start": self.calibration.start_video, "/api/calibration/video/stop": self.calibration.stop_video, } action = action_map.get(path) if action is None: self._json({"error": "not_found"}, HTTPStatus.NOT_FOUND) return requested_mode = str( dict(payload.get("can", {})).get( "mode", self.calibration.snapshot()["transport"]["mode"] ) ) motion_paths = { "/api/calibration/can/connect", "/api/calibration/start", "/api/calibration/move", "/api/calibration/seek", "/api/calibration/stop", } if path in motion_paths and requested_mode == "hardware": if not self._require_local_decision_point(): return self._json({"ok": True, "state": action()}) except CalibrationApiError as exc: self._json( {"ok": False, "error": "calibration_error", "message": str(exc)}, HTTPStatus(exc.status), ) except (KeyError, TypeError, ValueError, json.JSONDecodeError) as exc: self._json( {"ok": False, "error": "invalid_request", "message": str(exc)}, HTTPStatus.BAD_REQUEST, ) except Exception as exc: # noqa: BLE001 - keep calibration APIs responsive self.log_error("self-calibration request failed: %s", exc) self._json( { "ok": False, "error": "internal_error", "message": "自标定服务处理失败,请查看服务端日志。", }, HTTPStatus.INTERNAL_SERVER_ERROR, ) def _handle_self_repair_post(self, path: str) -> None: if not self._require_self_repair_admin(): return try: payload = self._read_json_body(max_bytes=256 * 1024) physical_paths = { "/api/self-repair/hardware/start", "/api/self-repair/hardware/stop", "/api/self-repair/emergency-stop", "/api/self-repair/firmware/build-flash", "/api/self-repair/open-log", } if ( path in physical_paths or ( path in { "/api/self-repair/runtime", "/api/self-repair/healing/start", "/api/self-repair/healing/reset", } and str(payload.get("execution_mode", "shadow")) == "bench" ) ) and not self._require_local_decision_point(): return if path == "/api/self-repair/healing/start": self._json({"ok": True, "healing": self.repair.start_healing(payload)}) return if path == "/api/self-repair/runtime": self._json({"ok": True, "runtime": self.repair.set_runtime(payload)}) return if path == "/api/self-repair/runtime/reset-statistics": self._json({ "ok": True, "runtime": self.repair.reset_runtime_statistics(), }) return if path == "/api/self-repair/healing/run-all": self._json({"ok": True, "batch": self.repair.start_batch(payload)}) return if path == "/api/self-repair/healing/reset": self._json({"ok": True, **self.repair.reset_healing(payload)}) return if path == "/api/self-repair/hardware/start": self._json({"ok": True, "session": self.repair.start_hardware()}) return if path == "/api/self-repair/hardware/stop": self._json({"ok": True, "session": self.repair.stop_hardware()}) return if path == "/api/self-repair/hardware/self-test": self._json( {"ok": True, "self_test": self.repair.run_hardware_self_test()} ) return if path == "/api/self-repair/emergency-stop": self._json({"ok": True, "request": self.repair.emergency_stop()}) return if path == "/api/self-repair/snapshot": self._json({"ok": True, "path": str(self.repair.save_snapshot())}) return if path == "/api/self-repair/firmware/build-flash": self._json( { "ok": True, "firmware": self.repair.build_and_flash_firmware(payload), } ) return if path == "/api/self-repair/open-log": opened = self.repair.open_log_dir() self._json({"ok": opened}) return self._json({"error": "not_found"}, HTTPStatus.NOT_FOUND) except (KeyError, TypeError, ValueError, json.JSONDecodeError) as exc: self._json( {"ok": False, "error": "invalid_request", "message": str(exc)}, HTTPStatus.BAD_REQUEST, ) except Exception as exc: # noqa: BLE001 - keep self-repair API responsive self.log_error("self-repair request failed: %s", exc) self._json( { "ok": False, "error": "internal_error", "message": "自修复服务处理失败,请查看服务端日志。", }, HTTPStatus.INTERNAL_SERVER_ERROR, ) def _require_self_repair_admin(self, parsed_url: ParseResult | None = None) -> bool: role = self.headers.get("X-Cognitive-Agent-Role", "").strip().lower() if not role and parsed_url is not None: role = str(parse_qs(parsed_url.query).get("role", [""])[0]).lower() if hmac.compare_digest(role, "admin"): return True self._json( {"error": "admin_required", "message": "自修复仅对 admin 开放。"}, HTTPStatus.FORBIDDEN, ) return False def _require_calibration_admin(self, parsed_url: ParseResult | None = None) -> bool: role = self.headers.get("X-Cognitive-Agent-Role", "").strip().lower() if not role and parsed_url is not None: role = str(parse_qs(parsed_url.query).get("role", [""])[0]).lower() if hmac.compare_digest(role, "admin"): return True self._json( { "error": "admin_required", "message": "自标定仅对 admin 开放。", }, HTTPStatus.FORBIDDEN, ) return False def _serve_calibration_events(self) -> None: self.send_response(HTTPStatus.OK) self.send_header("Content-Type", "text/event-stream; charset=utf-8") self.send_header("Cache-Control", "no-cache") self.send_header("Connection", "keep-alive") self.send_header("X-Accel-Buffering", "no") self._send_security_headers() self.end_headers() version = -1 try: while True: state = self.calibration.wait_for_version(version, 12.0) version = state["version"] data = json.dumps(state, ensure_ascii=False, separators=(",", ":")) self.wfile.write(f"data: {data}\n\n".encode("utf-8")) self.wfile.flush() except (BrokenPipeError, ConnectionResetError, ConnectionAbortedError): pass def _serve_calibration_video(self) -> None: self._json( { "error": "legacy_video_transport_removed", "message": "ZED 画面已改为浏览器直连 WebRTC/WHEP。", }, HTTPStatus.GONE, ) def _handle_extension_gateway(self, action: str, payload: dict[str, object]) -> None: try: result = self.extension.execute_gateway(action, dict(payload)) if action != "info": self.vehicle_display_sync.update_extension( action={"type": action, **dict(payload)}, result=result, ) self._json(result) except GatewayError as exc: self._json( {"success": False, "error": "gscu_unavailable", "message": str(exc)}, HTTPStatus.SERVICE_UNAVAILABLE, ) def _extension_health(self) -> dict[str, object]: return {**self.extension.health(), "nfc": self.nfc.snapshot()} def _handle_hardware_access_post(self, path: str) -> None: local_path = path.startswith("/api/local/") if local_path and not self._require_local_decision_point(): return try: payload = self._read_json_body(max_bytes=4096) client_id = str(payload.get("client_id", "")) if path == "/api/hardware/access/request": status = self.service.hardware_access.request_access( client_id, str(payload.get("label", "")), self._client_key(), ) self._json( status, HTTPStatus.OK if status["state"] == "approved" else HTTPStatus.ACCEPTED, ) return if path == "/api/hardware/access/release": self._json(self.service.hardware_access.release(client_id)) return if path == "/api/local/hardware-access/approve": self._json(self.service.hardware_access.approve(client_id)) return if path == "/api/local/hardware-access/deny": self._json(self.service.hardware_access.deny(client_id)) return if path == "/api/local/hardware-access/revoke": self._json( self.service.hardware_access.revoke(client_id or None) ) return except (ValueError, json.JSONDecodeError) as exc: self._json( {"error": "invalid_request", "message": str(exc)}, HTTPStatus.BAD_REQUEST, ) def _read_json_body(self, max_bytes: int = 32768) -> dict[str, object]: content_length = int(self.headers.get("Content-Length", "0")) if content_length <= 0 or content_length > max_bytes: raise ValueError(f"request body must be between 1 and {max_bytes} bytes") payload = json.loads(self.rfile.read(content_length).decode("utf-8")) if not isinstance(payload, dict): raise ValueError("request body must be a JSON object") return payload def _handle_firmware_workbench_post(self, path: str) -> None: try: payload = self._read_json_body(max_bytes=16 * 1024) if path == "/api/firmware-workbench/tasks": result = self.firmware_workbench.start_task( str(payload.get("instruction", "")), str(payload.get("baseline_id", "time_only")), ) self._json(result, HTTPStatus.ACCEPTED) return task_action_match = re.fullmatch( r"/api/firmware-workbench/tasks/(?P[a-z0-9-]+)/(?Pcancel|retry)", path, ) if task_action_match: task_id = task_action_match.group("task_id") if task_action_match.group("action") == "cancel": self._json(self.firmware_workbench.cancel_task(task_id)) else: self._json( self.firmware_workbench.retry_task(task_id), HTTPStatus.ACCEPTED ) return artifact_publish_match = re.fullmatch( r"/api/firmware-workbench/artifacts/(?P[a-z0-9-]+)/publish", path, ) if artifact_publish_match: self._json( self.firmware_workbench.publish_artifact( artifact_publish_match.group("artifact_id") ) ) return self._json({"error": "not_found"}, HTTPStatus.NOT_FOUND) except (ValueError, json.JSONDecodeError) as exc: self._json( {"error": "invalid_request", "message": str(exc)}, HTTPStatus.BAD_REQUEST, ) except FirmwareWorkbenchError as exc: self._json({"error": exc.code, "message": str(exc)}, HTTPStatus(exc.status)) def _serve_firmware_download(self, kind: str, item_id: str) -> None: try: if kind == "release": path, filename = self.firmware_workbench.resolve_release_download(item_id) else: path, filename = self.firmware_workbench.resolve_artifact_download(item_id) content = path.read_bytes() self.send_response(HTTPStatus.OK) self.send_header("Content-Type", "application/zip") self.send_header("Content-Length", str(len(content))) self.send_header("Cache-Control", "private, no-store") self.send_header( "Content-Disposition", f"attachment; filename*=UTF-8''{quote(filename, safe='.-_')}", ) self._send_security_headers() self.end_headers() self.wfile.write(content) except FirmwareWorkbenchError as exc: self._json({"error": exc.code, "message": str(exc)}, HTTPStatus(exc.status)) def _require_json_content_type(self) -> bool: if is_json_content_type(self.headers.get("Content-Type", "")): return True self._json( { "error": "unsupported_media_type", "message": "请求 Content-Type 必须为 application/json。", }, HTTPStatus.UNSUPPORTED_MEDIA_TYPE, ) return False def _require_same_origin(self) -> bool: """Reject browser cross-origin mutations while retaining local CLI calls.""" origin = self.headers.get("Origin", "").strip() if not origin: return True scheme = "https" if self._is_https() else "http" if origin_matches_host(origin, self.headers.get("Host", ""), scheme): return True self._json( { "error": "cross_origin_request", "message": "拒绝跨来源的状态变更请求。", }, HTTPStatus.FORBIDDEN, ) return False def _handle_speech_transcription(self) -> None: content_type = self.headers.get("Content-Type", "").split(";", 1)[0].strip().lower() if content_type not in {"audio/wav", "audio/wave", "audio/x-wav"}: self._json( { "error": "unsupported_media_type", "message": "语音听写接口仅接受 16 位 PCM WAV 音频。", }, HTTPStatus.UNSUPPORTED_MEDIA_TYPE, ) return try: content_length = int(self.headers.get("Content-Length", "0")) if content_length <= 0 or content_length > 2 * 1024 * 1024: raise SpeechRecognitionError( "invalid_audio_size", "录音数据为空或超过大小限制。", status=413, ) transcription_arguments: dict[str, object] = { "utterance_id": self.headers.get("X-Speech-Utterance-Id") } requested_mode = ( self.headers.get("X-Speech-Input-Mode", "").strip().lower() ) legacy_wake_required = ( self.headers.get("X-Speech-Wake-Word-Required", "").strip() == "1" ) input_mode = requested_mode or ( "wake" if legacy_wake_required else "manual" ) if input_mode not in {"manual", "wake"}: raise SpeechRecognitionError( "invalid_speech_input_mode", "语音输入模式仅支持 manual 或 wake。", status=400, ) if requested_mode == "manual" and legacy_wake_required: raise SpeechRecognitionError( "speech_input_mode_conflict", "手动语音模式不能同时要求唤醒词。", status=400, ) transcription_arguments["require_wake_word"] = input_mode == "wake" if bool(getattr(self.speech, "supports_input_modes", False)): transcription_arguments["input_mode"] = input_mode result = self.speech.transcribe_wav( self.rfile.read(content_length), **transcription_arguments, ) result = {**result, "input_mode": input_mode} raw_text = str(result.get("raw_text") or "").strip() peripheral_routing = self._speech_peripheral_routing(raw_text) if peripheral_routing.get("execution_eligible"): result = { **result, "routing_text": raw_text, "peripheral_routing": peripheral_routing, } self._json(result) except SpeechRecognitionError as exc: self._json( {"error": exc.code, "message": str(exc)}, HTTPStatus(exc.status), ) except (TypeError, ValueError): self._json( {"error": "invalid_audio", "message": "录音请求格式无效。"}, HTTPStatus.BAD_REQUEST, ) def _handle_speech_preview(self) -> None: """Return semantic-free partial text for an in-progress microphone session.""" content_type = self.headers.get("Content-Type", "").split(";", 1)[0].strip().lower() if content_type not in {"audio/wav", "audio/wave", "audio/x-wav"}: self._json( { "error": "unsupported_media_type", "message": "实时语音接口仅接受 16 位 PCM WAV 音频。", }, HTTPStatus.UNSUPPORTED_MEDIA_TYPE, ) return slot_acquired = False content_length = 0 request_body_consumed = False try: content_length = int(self.headers.get("Content-Length", "0")) if content_length <= 0 or content_length > 2 * 1024 * 1024: raise SpeechRecognitionError( "invalid_audio_size", "实时录音数据为空或超过大小限制。", status=413, ) sequence = int(self.headers.get("X-Speech-Preview-Sequence", "-1")) if sequence < 0 or sequence > 100_000: raise SpeechRecognitionError( "invalid_preview_sequence", "实时转写序号无效。", status=400, ) if not self.speech_preview_http_slots.acquire(blocking=False): raise SpeechRecognitionError( "preview_overloaded", "实时语音请求过多,请稍后重试。", status=429, retry_after_seconds=1, ) slot_acquired = True preview_arguments: dict[str, object] = { "utterance_id": self.headers.get("X-Speech-Utterance-Id"), "sequence": sequence, } input_mode = ( self.headers.get("X-Speech-Input-Mode", "wake").strip().lower() or "wake" ) if input_mode not in {"manual", "wake"}: raise SpeechRecognitionError( "invalid_speech_input_mode", "语音输入模式仅支持 manual 或 wake。", status=400, ) supports_input_modes = bool( getattr(self.speech, "supports_input_modes", False) ) if supports_input_modes: preview_arguments["input_mode"] = input_mode content = self.rfile.read(content_length) request_body_consumed = True result = self.speech.transcribe_preview_wav( content, **preview_arguments, ) if input_mode == "manual" and not supports_input_modes: raw_text = str(result.get("raw_text") or result.get("text") or "") wake_word = dict(result.get("wake_word") or {}) wake_word.update({"required": False, "remainder": raw_text}) result = { **result, "raw_text": raw_text, "text": raw_text, "live_text": raw_text, "wake_word": wake_word, } result = {**result, "input_mode": input_mode} self._json(result) except SpeechRecognitionError as exc: error_payload: dict[str, object] = { "error": exc.code, "message": str(exc), } error_headers: dict[str, str] = {} if exc.retry_after_seconds is not None: error_payload["retry_after_seconds"] = exc.retry_after_seconds error_headers["Retry-After"] = str(exc.retry_after_seconds) self._json( error_payload, HTTPStatus(exc.status), error_headers, ) if not request_body_consumed: self._drain_rejected_request_body(content_length) except (TypeError, ValueError): self._json( {"error": "invalid_preview", "message": "实时语音请求格式无效。"}, HTTPStatus.BAD_REQUEST, ) if not request_body_consumed: self._drain_rejected_request_body(content_length) finally: if slot_acquired: self.speech_preview_http_slots.release() def _drain_rejected_request_body( self, content_length: int, *, max_bytes: int = 2 * 1024 * 1024, ) -> None: """Gracefully finish an early-rejected POST without processing its payload. Closing a Windows socket while its peer's request body is still unread can replace an already-written HTTP error with TCP RST. Flush the rejection first, then briefly discard a valid-sized body so clients reliably receive the 4xx response. Callers choose a strict size ceiling, and authorization or concurrency gates still run before any payload is read or decoded. """ if content_length <= 0 or content_length > max_bytes: return self.close_connection = True try: self.wfile.flush() except OSError: return previous_timeout = self.connection.gettimeout() try: self.connection.settimeout(0.25) remaining = content_length while remaining > 0: chunk = self.rfile.read(min(remaining, 64 * 1024)) if not chunk: break remaining -= len(chunk) except (OSError, ValueError): pass finally: try: self.connection.settimeout(previous_timeout) except OSError: pass def _authorize(self, path: str) -> bool: if ( not self.auth.config.enabled or path in PUBLIC_AUTH_PATHS or PUBLIC_OTA_DOWNLOAD_PATH.fullmatch(path) ): return True if self._session_username() is not None: return True if path.startswith("/api/"): self._json( { "error": "authentication_required", "message": "请登录后访问。", }, HTTPStatus.UNAUTHORIZED, ) else: requested = path if path.startswith("/") else "/" self._redirect(f"/login?next={quote(requested, safe='/')}") return False def _login(self) -> None: if not self.auth.config.enabled: self._json({"enabled": False, "authenticated": True}) return client_key = self._client_key() retry_after = self.auth.login_failures.retry_after(client_key) if retry_after: self._json( { "error": "login_rate_limited", "message": "登录尝试过多,请稍后再试。", }, HTTPStatus.TOO_MANY_REQUESTS, {"Retry-After": str(retry_after)}, ) return try: content_length = int(self.headers.get("Content-Length", "0")) if content_length <= 0 or content_length > 4096: raise ValueError("invalid login body") payload = json.loads(self.rfile.read(content_length).decode("utf-8")) if not isinstance(payload, dict): raise ValueError("invalid login body") username = str(payload.get("username", ""))[:128] password = str(payload.get("password", ""))[:1024] except (ValueError, json.JSONDecodeError): self._json( {"error": "invalid_request", "message": "登录请求格式无效。"}, HTTPStatus.BAD_REQUEST, ) return password_valid = verify_password(password, self.auth.config.password_hash) username_valid = hmac.compare_digest( username.encode("utf-8"), self.auth.config.username.encode("utf-8"), ) if not (username_valid and password_valid): self.auth.login_failures.record(client_key) self._json( {"error": "invalid_credentials", "message": "用户名或密码错误。"}, HTTPStatus.UNAUTHORIZED, ) return self.auth.login_failures.clear(client_key) token = self.auth.sessions.issue(self.auth.config.username) attributes = [ f"{self.auth.config.cookie_name}={token}", "Path=/", "HttpOnly", "SameSite=Strict", f"Max-Age={self.auth.config.session_ttl_seconds}", ] if self._is_https(): attributes.append("Secure") self._json( { "authenticated": True, "username": self.auth.config.username, }, headers={"Set-Cookie": "; ".join(attributes)}, ) def _logout(self) -> None: attributes = [ f"{self.auth.config.cookie_name}=", "Path=/", "HttpOnly", "SameSite=Strict", "Max-Age=0", ] if self._is_https(): attributes.append("Secure") self._json( {"authenticated": False}, headers={"Set-Cookie": "; ".join(attributes)}, ) def _session_username(self) -> str | None: if not self.auth.config.enabled: return None cookie_header = self.headers.get("Cookie", "") if not cookie_header or len(cookie_header) > 4096: return None try: cookies = SimpleCookie() cookies.load(cookie_header) morsel = cookies.get(self.auth.config.cookie_name) return self.auth.sessions.verify(morsel.value if morsel else "") except (CookieError, ValueError): return None def _client_key(self) -> str: address = self.client_address[0] direct_address = ipaddress.ip_address(address) if self.auth.config.trust_proxy_headers and direct_address.is_loopback: forwarded = ( self.headers.get("CF-Connecting-IP") or self.headers.get("X-Forwarded-For", "").split(",", 1)[0] ).strip() try: address = str(ipaddress.ip_address(forwarded)) except ValueError: pass return address def _is_unproxied_loopback_request(self) -> bool: """Distinguish the local decision console from traffic via the tunnel.""" try: direct_address = ipaddress.ip_address(self.client_address[0]) except ValueError: return False proxy_headers = ( "Forwarded", "X-Forwarded-For", "X-Real-IP", "CF-Connecting-IP", ) return direct_address.is_loopback and not any( self.headers.get(name, "").strip() for name in proxy_headers ) def _hardware_access_bypass_allowed(self) -> bool: """Allow implicit hardware access only for authenticated local sessions.""" return ( self.auth.config.enabled and self._is_unproxied_loopback_request() and self._session_username() is not None ) def _require_local_decision_point(self) -> bool: if self._is_unproxied_loopback_request(): return True self._json( { "error": "local_access_required", "message": "真实台架审批与停止权限仅在台架本机开放。", }, HTTPStatus.FORBIDDEN, ) return False def _is_https(self) -> bool: if not self.auth.config.trust_proxy_headers: return False if self.headers.get("X-Forwarded-Proto", "").lower() == "https": return True return '"scheme":"https"' in self.headers.get("CF-Visitor", "").replace(" ", "") def _serve_request(self, request_id: str) -> None: if not REQUEST_ID_PATTERN.fullmatch(request_id): self._json({"error": "invalid_request_id"}, HTTPStatus.BAD_REQUEST) return result_path = self.service.artifact_root / request_id / "result.json" if not result_path.is_file(): self._json({"error": "not_found"}, HTTPStatus.NOT_FOUND) return self._json(json.loads(result_path.read_text(encoding="utf-8"))) def _serve_static(self, path: str, query: str = "") -> None: relative_path = "index.html" if path in {"", "/"} else path.lstrip("/") versioned = bool(parse_qs(query).get("v")) self._serve_static_file(relative_path, versioned=versioned) def _serve_static_file(self, relative_path: str, *, versioned: bool = False) -> None: candidate = (STATIC_ROOT / relative_path).resolve() try: candidate.relative_to(STATIC_ROOT.resolve()) except ValueError: self._json({"error": "not_found"}, HTTPStatus.NOT_FOUND) return if not candidate.is_file() and ( Path(relative_path).suffix or relative_path.startswith(("assets/", "vendor/", "icons/")) ): self._json({"error": "not_found"}, HTTPStatus.NOT_FOUND) return if not candidate.is_file(): candidate = STATIC_ROOT / "index.html" content_type, _ = mimetypes.guess_type(candidate.name) content = candidate.read_bytes() self.send_response(HTTPStatus.OK) resolved_content_type = content_type or "application/octet-stream" if resolved_content_type.startswith("text/") or resolved_content_type in { "application/javascript", "application/json", "application/manifest+json", "image/svg+xml", }: resolved_content_type = f"{resolved_content_type}; charset=utf-8" self.send_header( "Content-Type", resolved_content_type, ) self.send_header("Content-Length", str(len(content))) cache_control = "no-cache" if relative_path.startswith(("vendor/", "icons/")) or ( relative_path.startswith("assets/") and versioned ): cache_control = "public, max-age=31536000, immutable" elif relative_path.startswith("assets/"): cache_control = "no-cache" elif candidate.name == "service-worker.js": cache_control = "no-store" elif candidate.name in {"index.html", "login.html"}: cache_control = "no-cache" self.send_header("Cache-Control", cache_control) if candidate.name == "service-worker.js": self.send_header("Service-Worker-Allowed", "/") self._send_security_headers() self.end_headers() self.wfile.write(content) def _serve_gesture_model(self) -> None: path = self.gesture.model_path if not path.is_file(): self._json( {"error": "gesture_model_unavailable", "message": "手势模型文件不可用。"}, HTTPStatus.NOT_FOUND, ) return content = path.read_bytes() self.send_response(HTTPStatus.OK) self.send_header("Content-Type", "application/octet-stream") self.send_header("Content-Length", str(len(content))) self.send_header("Cache-Control", "private, max-age=3600") self._send_security_headers() self.end_headers() self.wfile.write(content) def _json( self, payload: object, status: HTTPStatus = HTTPStatus.OK, headers: dict[str, str] | None = None, ) -> None: content = json.dumps(payload, ensure_ascii=False).encode("utf-8") self.send_response(status) self.send_header("Content-Type", "application/json; charset=utf-8") self.send_header("Content-Length", str(len(content))) self.send_header("Cache-Control", "no-store") for name, value in (headers or {}).items(): self.send_header(name, value) self._send_security_headers() self.end_headers() self.wfile.write(content) def _redirect(self, location: str) -> None: self.send_response(HTTPStatus.SEE_OTHER) self.send_header("Location", location) self.send_header("Cache-Control", "no-store") self._send_security_headers() self.end_headers() def _send_security_headers(self) -> None: allow_same_origin_frame = ( unquote(urlparse(self.path).path) == "/firmware-workbench.html" ) self.send_header("X-Content-Type-Options", "nosniff") self.send_header( "X-Frame-Options", "SAMEORIGIN" if allow_same_origin_frame else "DENY" ) self.send_header("Referrer-Policy", "no-referrer") self.send_header( "Permissions-Policy", "camera=(self), microphone=(self), geolocation=(), payment=(), usb=()", ) self.send_header( "Content-Security-Policy", "default-src 'self'; " "script-src 'self' 'wasm-unsafe-eval'; " "style-src 'self'; " "img-src 'self' data: blob:; " "media-src 'self' blob:; " "frame-src 'self' http://*:3001; " "connect-src 'self' blob: http://172.20.10.6:8889; " "object-src 'none'; base-uri 'none'; frame-ancestors " + ("'self'" if allow_same_origin_frame else "'none'"), ) if self._is_https(): self.send_header( "Strict-Transport-Security", "max-age=31536000; includeSubDomains", ) def main() -> None: parser = argparse.ArgumentParser(description="Run the cabin orchestration demo") parser.add_argument("--host", default=os.getenv("HOST", "127.0.0.1")) parser.add_argument("--port", type=int, default=int(os.getenv("PORT", "8000"))) parser.add_argument( "--scene-catalog", type=Path, default=Path( os.getenv("N7_SCENE_CATALOG_PATH", "").strip() or DEFAULT_MODE_SCENE_CATALOG_PATH ), help="JSON path synchronized from preset and user-created mode cards", ) parser.add_argument( "--scene-server-id", default=os.getenv("N7_SCENE_SERVER_ID", "n7-cognitive-agent"), help="server_id written to the N7 scene catalog", ) args = parser.parse_args() scene_catalog_status = SeatDemoHandler.service.configure_mode_scene_catalog( args.scene_catalog, server_id=args.scene_server_id, ) server = ThreadingHTTPServer((args.host, args.port), SeatDemoHandler) SeatDemoHandler.speech.start() SeatDemoHandler.assistant.start( f"http://127.0.0.1:{server.server_port}/api/internal/codex/vehicle-control" ) SeatDemoHandler.service.start() SeatDemoHandler.extension.start() SeatDemoHandler.nfc.start() SeatDemoHandler.calibration.start() SeatDemoHandler.repair.start() if args.host in {"0.0.0.0", "::"}: lan_address = _lan_ipv4_address() print(f"Cabin orchestration demo (local): http://127.0.0.1:{args.port}", flush=True) if lan_address: print( f"Cabin orchestration demo (LAN): http://{lan_address}:{args.port}", flush=True, ) print( "LAN mode: devices on reachable networks can use the GPT-backed API.", flush=True, ) else: print(f"Cabin orchestration demo: http://{args.host}:{args.port}", flush=True) if scene_catalog_status["state"] == "ready": print( "N7 mode scene catalog: " f"{scene_catalog_status['path']} " f"({scene_catalog_status['scene_count']} scenes)", flush=True, ) else: print( "N7 mode scene catalog synchronization unavailable: " f"{scene_catalog_status.get('last_error') or 'unknown error'}", file=sys.stderr, flush=True, ) hardware_execution_ready = any( ( SeatDemoHandler.service.hardware_settings.configured, SeatDemoHandler.service.vehicle_can_settings.configured, SeatDemoHandler.service.vehicle_mqtt_settings.configured, ) ) print( "Execution modes: dry_run + simulated_hardware " f"({'enabled' if SeatDemoHandler.service.simulated_hardware_enabled else 'locked'}) " "+ hardware " f"({'configured' if hardware_execution_ready else 'locked'})", flush=True, ) try: server.serve_forever() except KeyboardInterrupt: pass finally: server.server_close() SeatDemoHandler.speech.close() SeatDemoHandler.assistant.close() SeatDemoHandler.service.close() SeatDemoHandler.extension.close() SeatDemoHandler.nfc.close() SeatDemoHandler.calibration.close() SeatDemoHandler.repair.close() SeatDemoHandler.firmware_workbench.close() if __name__ == "__main__": main()