fix: compose named cockpit modes from capabilities
This commit is contained in:
@@ -178,6 +178,7 @@ class FakeVehicleService:
|
||||
def __init__(self) -> None:
|
||||
self.calls: list[dict[str, object]] = []
|
||||
self.mode_save_calls: list[dict[str, object]] = []
|
||||
self.mode_trial_calls: list[dict[str, object]] = []
|
||||
self.fail = False
|
||||
|
||||
def preview_native_can(self, **kwargs: object) -> dict[str, object]:
|
||||
@@ -286,6 +287,7 @@ class FakeVehicleService:
|
||||
"conversation_id": kwargs["conversation_id"],
|
||||
"status": "succeeded",
|
||||
"execution_mode": "dry_run",
|
||||
"hardware_execution_allowed": False,
|
||||
"supplement_required": False,
|
||||
"orchestration_route": "mode_profile_create_preview",
|
||||
"mode_profile_draft": {
|
||||
@@ -361,6 +363,31 @@ class FakeVehicleService:
|
||||
"source_request_id": source_request_id,
|
||||
}
|
||||
|
||||
def trial_mode_profile_draft(
|
||||
self,
|
||||
name: str,
|
||||
instruction: str,
|
||||
source_request_id: str,
|
||||
vehicle_id: str | None = None,
|
||||
) -> dict[str, object]:
|
||||
self.mode_trial_calls.append(
|
||||
{
|
||||
"name": name,
|
||||
"instruction": instruction,
|
||||
"source_request_id": source_request_id,
|
||||
"vehicle_id": vehicle_id,
|
||||
}
|
||||
)
|
||||
return {
|
||||
"request_id": source_request_id,
|
||||
"status": "succeeded",
|
||||
"execution_mode": "dry_run",
|
||||
"hardware_execution_allowed": False,
|
||||
"orchestration_route": "mode_profile_draft_trial",
|
||||
"intent_ir": {"spec": {"actions": []}},
|
||||
"function_configs": {},
|
||||
}
|
||||
|
||||
|
||||
def _auth_disabled() -> AuthRuntime:
|
||||
return AuthRuntime(AuthConfig(False, "", "", b"", 3600, 5, 300, 120, False))
|
||||
@@ -402,6 +429,7 @@ class TestCodexAssistantWebApi:
|
||||
def setup_method(self) -> None:
|
||||
AssistantApiHandler.service.calls.clear()
|
||||
AssistantApiHandler.service.mode_save_calls.clear()
|
||||
AssistantApiHandler.service.mode_trial_calls.clear()
|
||||
AssistantApiHandler.service.fail = False
|
||||
FAKE_CODEX.available = True
|
||||
FAKE_CODEX.turn_calls.clear()
|
||||
@@ -636,6 +664,32 @@ class TestCodexAssistantWebApi:
|
||||
assert "预览" in events[-1]["answer"]
|
||||
assert "已添加" not in events[-1]["answer"]
|
||||
|
||||
def test_mode_draft_trial_reuses_the_server_owned_preview(self) -> None:
|
||||
status, content_type, payload = self._request_json(
|
||||
"/api/mode-profile-drafts/trial",
|
||||
{
|
||||
"name": "午休模式",
|
||||
"instruction": "进入午休模式",
|
||||
"source_request_id": "req-api-mode-trial",
|
||||
"vehicle_id": "denza_n7",
|
||||
},
|
||||
)
|
||||
|
||||
assert status == 200
|
||||
assert content_type == "application/json"
|
||||
assert payload["status"] == "succeeded"
|
||||
assert payload["execution_mode"] == "dry_run"
|
||||
assert payload["hardware_execution_allowed"] is False
|
||||
assert payload["orchestration_route"] == "mode_profile_draft_trial"
|
||||
assert AssistantApiHandler.service.mode_trial_calls == [
|
||||
{
|
||||
"name": "午休模式",
|
||||
"instruction": "进入午休模式",
|
||||
"source_request_id": "req-api-mode-trial",
|
||||
"vehicle_id": "denza_n7",
|
||||
}
|
||||
]
|
||||
|
||||
def test_exact_movie_mode_request_streams_a_nine_action_preview(self) -> None:
|
||||
events = self.post_assistant(
|
||||
{
|
||||
|
||||
@@ -19,6 +19,7 @@ from cognitive_agent.application.self_extension import (
|
||||
GatewayError,
|
||||
SelfExtensionService,
|
||||
SimulationGateway,
|
||||
partition_active_peripheral_intent,
|
||||
)
|
||||
from cognitive_agent.auth import AuthConfig, AuthRuntime, hash_password
|
||||
from cognitive_agent.infrastructure.can.access import HardwareAccessController
|
||||
@@ -620,7 +621,7 @@ operations:
|
||||
def test_static_console_and_runtime_endpoint(self) -> None:
|
||||
with self.opener.open(f"{self.base_url}/", timeout=3) as response:
|
||||
html = response.read().decode("utf-8")
|
||||
with self.opener.open(f"{self.base_url}/app.js?v=dual-seat-126", timeout=3) as response:
|
||||
with self.opener.open(f"{self.base_url}/app.js?v=dual-seat-127", timeout=3) as response:
|
||||
app_script = response.read().decode("utf-8")
|
||||
with self.opener.open(
|
||||
f"{self.base_url}/gesture-control.js?v=android-pad-9", timeout=3
|
||||
@@ -655,7 +656,7 @@ operations:
|
||||
) as response:
|
||||
extension_styles = response.read().decode("utf-8").replace("\r\n", "\n")
|
||||
with self.opener.open(
|
||||
f"{self.base_url}/assistant-chat.js?v=codex-assistant-20", timeout=3
|
||||
f"{self.base_url}/assistant-chat.js?v=codex-assistant-22", timeout=3
|
||||
) as response:
|
||||
assistant_script = response.read().decode("utf-8")
|
||||
with self.opener.open(
|
||||
@@ -696,7 +697,7 @@ operations:
|
||||
self.assertIn("CMD 0x431 · FB 0x41A · CH1", html)
|
||||
self.assertIn("CMD 0x431 · FB 0x388 · CH1", html)
|
||||
self.assertNotIn("透明车体 · 实体设备", html)
|
||||
self.assertIn('src="/app.js?v=dual-seat-126"', html)
|
||||
self.assertIn('src="/app.js?v=dual-seat-127"', html)
|
||||
self.assertIn('href="/styles.css?v=dual-seat-23"', html)
|
||||
self.assertIn('src="/gesture-control.js?v=android-pad-9"', html)
|
||||
self.assertNotIn('class="vehicle-system-hud"', html)
|
||||
@@ -718,7 +719,7 @@ operations:
|
||||
self.assertIn('aria-label="发送消息"', html)
|
||||
self.assertIn('id="assistantTranscript"', html)
|
||||
self.assertIn('href="/assistant-chat.css?v=codex-assistant-22"', html)
|
||||
self.assertIn('src="/assistant-chat.js?v=codex-assistant-20"', html)
|
||||
self.assertIn('src="/assistant-chat.js?v=codex-assistant-22"', html)
|
||||
self.assertIn('href="/energy-monitor.css?v=energy-monitor-4"', html)
|
||||
self.assertIn('src="/energy-monitor.js?v=energy-monitor-4"', html)
|
||||
self.assertNotIn('"admin"', console_script)
|
||||
@@ -991,14 +992,14 @@ operations:
|
||||
self.assertIn("CAN 指令已提交,等待车辆状态确认", console_script)
|
||||
self.assertIn('canvas.addEventListener("webglcontextlost"', app_script)
|
||||
self.assertIn('"cognitive-agent:module-change"', app_script)
|
||||
self.assertIn('const CACHE_VERSION = "cognitive-agent-pad-v269"', service_worker)
|
||||
self.assertIn('const CACHE_VERSION = "cognitive-agent-pad-v271"', service_worker)
|
||||
self.assertIn('/console-v2.css?v=delivery-state-65', service_worker)
|
||||
self.assertIn('/console-v2.js?v=delivery-state-62', service_worker)
|
||||
self.assertIn('/assistant-chat.css?v=codex-assistant-22', service_worker)
|
||||
self.assertIn('/assistant-chat.js?v=codex-assistant-20', service_worker)
|
||||
self.assertIn('/assistant-chat.js?v=codex-assistant-22', service_worker)
|
||||
self.assertIn('/energy-monitor.css?v=energy-monitor-4', service_worker)
|
||||
self.assertIn('/energy-monitor.js?v=energy-monitor-4', service_worker)
|
||||
self.assertIn('"/app.js?v=dual-seat-126"', service_worker)
|
||||
self.assertIn('"/app.js?v=dual-seat-127"', service_worker)
|
||||
self.assertIn('"/speech-input.js?v=sensevoice-12"', service_worker)
|
||||
self.assertIn('"/speech-input.css?v=sensevoice-7"', service_worker)
|
||||
self.assertIn('"/speech-audio-worklet.js?v=sensevoice-5"', service_worker)
|
||||
@@ -1455,7 +1456,7 @@ operations:
|
||||
) as response:
|
||||
extension_script = response.read().decode("utf-8")
|
||||
with self.opener.open(
|
||||
f"{self.base_url}/app.js?v=dual-seat-126", timeout=3
|
||||
f"{self.base_url}/app.js?v=dual-seat-127", timeout=3
|
||||
) as response:
|
||||
app_script = response.read().decode("utf-8")
|
||||
|
||||
@@ -1814,6 +1815,92 @@ operations:
|
||||
self.assertEqual(operation["domain"], "self_extension")
|
||||
self.assertTrue(operation["telemetry"]["gateway_acknowledged"])
|
||||
|
||||
def test_mode_creation_cannot_be_claimed_by_live_nfc_peripheral(self) -> None:
|
||||
original_extension = SeatDemoHandler.extension
|
||||
original_nfc = SeatDemoHandler.nfc
|
||||
with tempfile.TemporaryDirectory() as directory:
|
||||
gateway = SimulationGateway()
|
||||
extension = SelfExtensionService(
|
||||
gateway,
|
||||
catalog_db_path=Path(directory, "mode-create-peripheral.sqlite3"),
|
||||
)
|
||||
extension.sync_peripheral_catalog(
|
||||
[
|
||||
{
|
||||
"id": "ir-slot:flashing-toy:1",
|
||||
"status": "learned",
|
||||
"enabled": True,
|
||||
"device_id": "flashing-toy",
|
||||
"device_name": "闪灯玩具",
|
||||
"object_names": ["闪灯玩具"],
|
||||
"card_uid": "2CC8B103",
|
||||
"action": "打开",
|
||||
"action_names": ["打开", "开启"],
|
||||
"risk": "low",
|
||||
"slot": 1,
|
||||
}
|
||||
]
|
||||
)
|
||||
nfc_snapshot = {
|
||||
"revision": 1,
|
||||
"reader": {"online": True},
|
||||
"card": {"action": "present", "uid": "2CC8B103"},
|
||||
}
|
||||
utterance = "创建一个打开闪灯玩具模式"
|
||||
partition = partition_active_peripheral_intent(
|
||||
utterance,
|
||||
extension.list_peripheral_capabilities(),
|
||||
nfc_snapshot,
|
||||
)
|
||||
self.assertTrue(partition["peripheral_actions"])
|
||||
|
||||
SeatDemoHandler.extension = extension
|
||||
SeatDemoHandler.nfc = SimpleNamespace(snapshot=lambda: nfc_snapshot)
|
||||
request = urllib.request.Request(
|
||||
f"{self.base_url}/api/requests/preview",
|
||||
method="POST",
|
||||
headers={"Content-Type": "application/json"},
|
||||
data=json.dumps(
|
||||
{
|
||||
"utterance": utterance,
|
||||
"provider": "local",
|
||||
"conversation_id": "conv-mode-create-peripheral-boundary",
|
||||
"execution_mode": "hardware",
|
||||
"client_type": "web_console_assistant",
|
||||
},
|
||||
ensure_ascii=False,
|
||||
).encode("utf-8"),
|
||||
)
|
||||
try:
|
||||
with patch.object(gateway, "send", wraps=gateway.send) as send:
|
||||
with self.opener.open(request, timeout=3) as response:
|
||||
result = json.loads(response.read().decode("utf-8"))
|
||||
self.assertEqual(send.call_count, 0)
|
||||
finally:
|
||||
extension.close()
|
||||
SeatDemoHandler.extension = original_extension
|
||||
SeatDemoHandler.nfc = original_nfc
|
||||
|
||||
self.assertEqual(result["execution_mode"], "dry_run")
|
||||
self.assertFalse(result["hardware_execution_allowed"])
|
||||
self.assertNotEqual(result.get("orchestration_route"), "nfc_peripheral")
|
||||
self.assertNotIn("peripheral", result)
|
||||
|
||||
def test_all_mode_configuration_phrases_are_control_plane_only(self) -> None:
|
||||
for utterance in (
|
||||
"创建一个打开闪灯玩具模式",
|
||||
"固化为打开闪灯玩具模式",
|
||||
"修改打开闪灯玩具模式,空调调整到24度",
|
||||
"恢复打开闪灯玩具模式初始配置",
|
||||
):
|
||||
with self.subTest(utterance=utterance):
|
||||
self.assertTrue(
|
||||
self.service.is_mode_profile_configuration_request(utterance)
|
||||
)
|
||||
self.assertFalse(
|
||||
self.service.is_mode_profile_configuration_request("进入小憩模式")
|
||||
)
|
||||
|
||||
def test_speech_routes_exact_asr_text_to_learned_peripheral_and_ui_config(
|
||||
self,
|
||||
) -> None:
|
||||
|
||||
Reference in New Issue
Block a user