feat: add seat-scoped static and dynamic assistant modes

This commit is contained in:
vee
2026-09-12 14:56:30 +08:00
parent d09b5967f8
commit d8dad22ec0
60 changed files with 5847 additions and 149 deletions
+109
View File
@@ -0,0 +1,109 @@
"""Full browser -> deterministic assistant -> schedule trial/save, without hardware."""
from __future__ import annotations
import os
import shutil
import subprocess
import threading
from http import HTTPStatus
from http.server import ThreadingHTTPServer
from pathlib import Path
from unittest.mock import MagicMock, patch
from urllib.parse import urlparse
import pytest
from cognitive_agent.application.codex_assistant import CodexAssistantService
from cognitive_agent.application.seat_service import SeatDemoService
from cognitive_agent.auth import AuthConfig, AuthRuntime
from cognitive_agent.server import REPOSITORY_ROOT, SeatDemoHandler
def test_mode_schedule_real_browser_preview_trial_and_save(tmp_path: Path) -> None:
node = shutil.which("node")
if not node:
runtime = Path.home() / ".cache" / "codex-runtimes" / "codex-primary-runtime" / "dependencies" / "node" / "bin"
node = next((str(path) for path in (runtime / "node.exe", runtime / "node") if path.is_file()), None)
if not node:
pytest.skip("Node.js is required for the schedule browser regression")
env = dict(os.environ)
modules = Path(node).resolve().parent.parent / "node_modules"
if modules.is_dir():
env["NODE_PATH"] = os.pathsep.join(value for value in (str(modules), env.get("NODE_PATH")) if value)
probe = subprocess.run([node, "-e", "require.resolve('playwright')"], env=env, capture_output=True, encoding="utf-8", check=False)
if probe.returncode:
pytest.skip("Playwright is required for the schedule browser regression")
with patch.dict(os.environ, {
"COGNITIVE_AGENT_HARDWARE_EXECUTION_ENABLED": "false",
"COGNITIVE_AGENT_VEHICLE_CAN_ENABLED": "false",
"COGNITIVE_AGENT_WIRED_PASSENGER_CAN_ENABLED": "false",
"COGNITIVE_AGENT_VEHICLE_MQTT_ENABLED": "false",
"COGNITIVE_AGENT_EXECUTION_MODE": "dry_run",
"COGNITIVE_AGENT_MODE_PROFILE_ROOT": str(tmp_path / "user-modes"),
}):
service = SeatDemoService(REPOSITORY_ROOT)
service.artifact_root = tmp_path / "artifacts"
# A failed deterministic route must fail visibly, never launch a model or
# make network calls to an external assistant during the browser test.
client = MagicMock()
client.available = False
client.status = "test-offline"
assistant = CodexAssistantService(client, fast_implicit_controls=True)
forbidden = []
class BrowserHandler(SeatDemoHandler):
auth = AuthRuntime(AuthConfig(False, "", "", b"", 3600, 5, 300, 120, False))
def do_GET(self) -> None: # noqa: N802
path = urlparse(self.path).path
if path in {"/api/hardware/seat-state", "/api/hardware/vehicle-state"}:
# Both legacy GET paths can touch physical adapters or firmware.
self._json({"status": "unavailable", "hardware_execution_allowed": False})
return
if path == "/api/health":
self._json({"status": "ok", "assistant": {"status": "ready", "available": True}})
return
super().do_GET()
def do_POST(self) -> None: # noqa: N802
path = urlparse(self.path).path
if path == "/api/extension/display-sync":
self._json({"status": "ok"})
return
if path not in {"/api/assistant/stream", "/api/mode-profile-drafts/trial", "/api/mode-profiles"}:
forbidden.append(path)
self._json({"error": "browser_test_write_forbidden"}, HTTPStatus.FORBIDDEN)
return
super().do_POST()
def _read_json_body(self, **kwargs):
payload = super()._read_json_body(**kwargs)
if payload.get("execution_mode", "dry_run") != "dry_run":
forbidden.append(f"execution_mode:{payload.get('execution_mode')}")
raise ValueError("Browser regression permits dry_run only")
return payload
def log_message(self, *_args: object) -> None:
pass
BrowserHandler.service = service
BrowserHandler.assistant = assistant
server = ThreadingHTTPServer(("127.0.0.1", 0), BrowserHandler)
thread = threading.Thread(target=server.serve_forever, daemon=True)
thread.start()
try:
completed = subprocess.run([
node, str(Path(__file__).with_name("mode_schedule_browser.cjs")),
f"http://127.0.0.1:{server.server_port}", str(tmp_path),
], env=env, capture_output=True, encoding="utf-8", timeout=180, check=False)
assert completed.returncode == 0, completed.stdout + completed.stderr
assert not forbidden, forbidden
assert len(list((tmp_path / "user-modes").rglob("*.yaml"))) == 2
finally:
server.shutdown()
server.server_close()
thread.join(timeout=3)
assistant.close()
service.close()