merge: sync feature/N7Web into feature/N7Web-Tech

# Conflicts:
#	apps/web-console/service-worker.js
#	src/cognitive_agent/application/mode_profile.py
#	src/cognitive_agent/application/mode_scene_catalog.py
#	src/cognitive_agent/application/seat_service.py
#	tests/e2e/test_seat_web_api.py
#	tests/unit/test_assistant_frontend_contract.py
#	tests/unit/test_vue_frontend_contract.py
This commit is contained in:
Miayui
2026-09-17 11:16:48 +08:00
31 changed files with 2925 additions and 127 deletions
+127 -1
View File
@@ -359,6 +359,9 @@ class SeatDemoHandler(BaseHTTPRequestHandler):
if path == "/api/assistant/capabilities":
self._json(self._assistant_capability_catalog().snapshot())
return
if path == "/api/mode-profiles/status":
self._json(self.service.mode_profile_sync_status())
return
if path == "/api/runtime":
self._json(self.service.runtime_info())
return
@@ -1601,6 +1604,107 @@ class SeatDemoHandler(BaseHTTPRequestHandler):
spec["status"] = str(result.get("status") or "blocked")
return result
def _exact_n7_mode_invocation(
self,
utterance: str,
vehicle_id: str | None,
) -> object | None:
"""Resolve a standalone spoken/text invocation of one N7-synced card."""
if not str(utterance).strip():
return None
registry = getattr(self.service, "mode_profiles", None)
match_text = getattr(registry, "match_text", None)
if not callable(match_text):
# Lightweight service doubles used by peripheral-only routes do
# not own a mode registry. They must keep their existing routing
# behaviour instead of failing before the peripheral parser runs.
return None
matches = [
profile
for profile in match_text(
utterance,
vehicle_id=vehicle_id,
)
if getattr(profile, "sync_origin", "") == "from_n7/scenes"
]
# ``ModeProfileRegistry.match_text`` applies the bounded, exact
# invocation grammar to N7-synchronised cards. Keeping that rule in
# one place prevents text, speech and card-click routes from drifting.
return matches[0] if len(matches) == 1 else None
def _execute_n7_named_mode_request(
self,
path: str,
common_arguments: dict[str, object],
) -> dict[str, object] | None:
utterance = str(common_arguments.get("utterance") or "").strip()
vehicle_id = (
str(common_arguments["vehicle_id"])
if common_arguments.get("vehicle_id")
else None
)
profile = self._exact_n7_mode_invocation(utterance, vehicle_id)
if profile is None:
return None
execution_mode = (
"dry_run"
if path == "/api/requests/preview"
else str(common_arguments.get("execution_mode") or "dry_run")
)
conversation_id = (
str(common_arguments["conversation_id"])
if common_arguments.get("conversation_id")
else None
)
entries = self._saved_mode_infrared_entries(profile)
can_result = self.service.execute_mode_profile_card(
str(getattr(profile, "id")),
conversation_id=conversation_id,
execution_mode=execution_mode,
approval_token=(
str(common_arguments["approval_token"])
if common_arguments.get("approval_token")
else None
),
vehicle_id=vehicle_id,
simulation_fault=str(
common_arguments.get("simulation_fault") or "normal"
),
hardware_client_id=(
str(common_arguments["hardware_client_id"])
if common_arguments.get("hardware_client_id")
else None
),
hardware_access_bypass=bool(
common_arguments.get("hardware_access_bypass", True)
),
control_transport=str(
common_arguments.get("control_transport") or "wired_can"
),
)
if not entries:
can_result["utterance"] = utterance
return can_result
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)
return result
def _execute_request_with_peripherals(
self,
path: str,
@@ -1635,6 +1739,12 @@ class SeatDemoHandler(BaseHTTPRequestHandler):
**mode_arguments,
_on_commands_validated=callback,
)
n7_mode_result = self._execute_n7_named_mode_request(
path,
common_arguments,
)
if n7_mode_result is not None:
return n7_mode_result
catalog = self._peripheral_routing_catalog()
nfc_snapshot = self.nfc.snapshot()
partition = partition_active_peripheral_intent(
@@ -4208,6 +4318,22 @@ class SeatDemoHandler(BaseHTTPRequestHandler):
)
class N7WebHttpServer(ThreadingHTTPServer):
"""Prevent multiple Windows processes from silently sharing one port."""
daemon_threads = True
allow_reuse_address = os.name != "nt"
def server_bind(self) -> None:
if os.name == "nt" and hasattr(socket, "SO_EXCLUSIVEADDRUSE"):
self.socket.setsockopt(
socket.SOL_SOCKET,
socket.SO_EXCLUSIVEADDRUSE,
1,
)
super().server_bind()
def main() -> None:
parser = argparse.ArgumentParser(description="Run the cabin orchestration demo")
parser.add_argument("--host", default=os.getenv("HOST", "127.0.0.1"))
@@ -4231,7 +4357,7 @@ def main() -> None:
args.scene_catalog,
server_id=args.scene_server_id,
)
server = ThreadingHTTPServer((args.host, args.port), SeatDemoHandler)
server = N7WebHttpServer((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"