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:
@@ -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"
|
||||
|
||||
Reference in New Issue
Block a user