feat: integrate NFC infrared peripherals and orchestration

This commit is contained in:
vee
2026-08-25 11:45:45 +08:00
parent 9ac8fdface
commit 2529b4896a
19 changed files with 3818 additions and 117 deletions
+219 -59
View File
@@ -38,6 +38,8 @@ from cognitive_agent.application.self_extension import ( # noqa: E402
ExtensionIntegrationError,
GatewayError,
SelfExtensionService,
active_peripheral_plan_catalog,
resolve_active_peripheral_command,
)
from cognitive_agent.application.self_repair import SelfRepairService # noqa: E402
from cognitive_agent.infrastructure.can.hardware import ( # noqa: E402
@@ -48,6 +50,7 @@ from cognitive_agent.infrastructure.can.access import ( # noqa: E402
HardwareAccessRequiredError,
)
from cognitive_agent.infrastructure.llm.openai_responses import OpenAIIntentError # noqa: E402
from cognitive_agent.infrastructure.nfc import NfcReaderService # noqa: E402
STATIC_ROOT = REPOSITORY_ROOT / "apps" / "web-console"
@@ -140,6 +143,15 @@ class VehicleDisplaySync:
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."""
@@ -158,6 +170,7 @@ class SeatDemoHandler(BaseHTTPRequestHandler):
service = SeatDemoService()
gesture = GestureCapabilityBridge(REPOSITORY_ROOT)
extension = SelfExtensionService()
nfc = NfcReaderService()
calibration = CalibrationService(
REPOSITORY_ROOT / "artifacts" / "tmp" / "self-calibration",
repository_root=REPOSITORY_ROOT,
@@ -231,7 +244,7 @@ class SeatDemoHandler(BaseHTTPRequestHandler):
self._json(self.service.runtime_info())
return
if path == "/api/vehicle-display-sync":
self._json(self.vehicle_display_sync.snapshot(self.extension.health()))
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)
@@ -296,7 +309,10 @@ class SeatDemoHandler(BaseHTTPRequestHandler):
self._serve_gesture_model()
return
if path == "/api/extension/health":
self._json(self.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/info":
self._handle_extension_gateway("info", {})
@@ -434,17 +450,18 @@ class SeatDemoHandler(BaseHTTPRequestHandler):
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": str(payload.get("provider", "auto")),
"conversation_id": (
str(payload["conversation_id"])
if payload.get("conversation_id")
else None
),
"execution_mode": str(
payload.get("execution_mode", "dry_run")
),
"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(
@@ -503,25 +520,35 @@ class SeatDemoHandler(BaseHTTPRequestHandler):
payload.get("control_transport", "wired_can")
),
)
elif 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._is_unproxied_loopback_request(),
reasoning_effort=reasoning_effort,
)
else:
result = self.service.process(**common_arguments)
result = None
if path in {"/api/requests", "/api/requests/preview"}:
result = self._execute_active_peripheral_request(
utterance,
provider=provider,
conversation_id=conversation_id,
execution_mode=execution_mode,
)
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._is_unproxied_loopback_request(),
reasoning_effort=reasoning_effort,
)
else:
result = self.service.process(**common_arguments)
utterance = str(result.get("utterance") or utterance)
self.vehicle_display_sync.update_task(
status=str(result.get("status", "succeeded")),
@@ -557,6 +584,18 @@ class SeatDemoHandler(BaseHTTPRequestHandler):
{"error": "hardware_safety_gate", "message": str(exc)},
HTTPStatus.FORBIDDEN,
)
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 (ValueError, json.JSONDecodeError) as exc:
fail_display_sync("invalid_request", exc)
self._json({"error": "invalid_request", "message": str(exc)}, HTTPStatus.BAD_REQUEST)
@@ -581,6 +620,94 @@ class SeatDemoHandler(BaseHTTPRequestHandler):
HTTPStatus.INTERNAL_SERVER_ERROR,
)
def _execute_active_peripheral_request(
self,
utterance: str,
*,
provider: str,
conversation_id: str | None,
execution_mode: str,
) -> dict[str, object] | None:
"""Route a command to the NFC-bound peripheral when one is present."""
extension_catalog = self.vehicle_display_sync.extension_catalog()
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": [],
}
)
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,
)
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,
)
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 _stream_control_request(
self,
path: str,
@@ -620,40 +747,55 @@ class SeatDemoHandler(BaseHTTPRequestHandler):
utterance = str(common_arguments.get("utterance") or "")
try:
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")
result = None
if path in {"/api/requests", "/api/requests/preview"}:
result = self._execute_active_peripheral_request(
utterance,
provider=str(common_arguments.get("provider") or "auto"),
conversation_id=(
str(common_arguments["conversation_id"])
if common_arguments.get("conversation_id")
else None
),
approval_token=os.getenv(
"COGNITIVE_AGENT_HARDWARE_APPROVAL_TOKEN"
execution_mode=str(
common_arguments.get("execution_mode") or "dry_run"
),
hardware_client_id=(
str(payload["hardware_client_id"])
if payload.get("hardware_client_id")
else None
),
hardware_access_bypass=self._is_unproxied_loopback_request(),
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,
)
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._is_unproxied_loopback_request(),
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)
self.vehicle_display_sync.update_task(
status=str(result.get("status", "succeeded")),
@@ -700,6 +842,18 @@ class SeatDemoHandler(BaseHTTPRequestHandler):
"error": "hardware_safety_gate",
"message": str(exc),
})
except GatewayError as exc:
emit({
"type": "error",
"error": "gscu_unavailable",
"message": str(exc),
})
except ExtensionIntegrationError as exc:
emit({
"type": "error",
"error": "extension_request_ambiguous",
"message": str(exc),
})
except (ValueError, json.JSONDecodeError) as exc:
emit({
"type": "error",
@@ -1136,6 +1290,9 @@ class SeatDemoHandler(BaseHTTPRequestHandler):
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():
@@ -1464,6 +1621,7 @@ class SeatDemoHandler(BaseHTTPRequestHandler):
"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 'none'",
)
@@ -1482,6 +1640,7 @@ def main() -> None:
server = ThreadingHTTPServer((args.host, args.port), SeatDemoHandler)
SeatDemoHandler.service.start()
SeatDemoHandler.extension.start()
SeatDemoHandler.nfc.start()
SeatDemoHandler.calibration.start()
SeatDemoHandler.repair.start()
if args.host in {"0.0.0.0", "::"}:
@@ -1520,6 +1679,7 @@ def main() -> None:
server.server_close()
SeatDemoHandler.service.close()
SeatDemoHandler.extension.close()
SeatDemoHandler.nfc.close()
SeatDemoHandler.calibration.close()
SeatDemoHandler.repair.close()