feat: improve mode and peripheral orchestration

This commit is contained in:
Miayui
2026-09-21 00:13:47 +08:00
parent 89707a42e2
commit 448c8b31a0
39 changed files with 3068 additions and 277 deletions
@@ -560,6 +560,64 @@ test("mode creation always requests dry-run even while the console is in hardwar
assert.equal(Object.hasOwn(request, "hardware_client_id"), false);
});
test("hardware requests use only the console's validated hardware client id", async () => {
const events = [
{ type: "message.accepted", route: "mode_profile_execute", engine: "n7web-mode-profile-runner" },
{ type: "answer.completed", route: "mode_profile_execute", answer: "阅读模式执行完成。" },
];
const validHarness = createHarness(events, "hardware");
validHarness.sandbox.CognitiveAgentConsole.getHardwareClientId = (
() => "hwc-runtime-client-1234"
);
validHarness.input.value = "进入阅读模式";
await validHarness.sandbox.CognitiveAgentAssistant.submit();
const validRequest = JSON.parse(validHarness.requestCalls[0].options.body);
assert.equal(validRequest.hardware_client_id, "hwc-runtime-client-1234");
const invalidHarness = createHarness(events, "hardware");
invalidHarness.sandbox.CognitiveAgentConsole.getHardwareClientId = (
() => "legacy_invalid_client"
);
invalidHarness.input.value = "进入浪漫观影模式";
await invalidHarness.sandbox.CognitiveAgentAssistant.submit();
const invalidRequest = JSON.parse(invalidHarness.requestCalls[0].options.body);
assert.equal(Object.hasOwn(invalidRequest, "hardware_client_id"), false);
});
test("partially completed imported mode uses neutral delegated wording", () => {
const harness = createHarness([]);
harness.sandbox.CognitiveAgentAssistant.announceModeExecution(
{ id: "cinema", name: "影院模式", command_flow: { function_configs: {}, plans: {} } },
{
status: "partially_succeeded",
execution_mode: "hardware",
execution_outcome: "submitted_unverified",
deferred_requests: [
{ execution_backend: "n7_vehicle_transfer" },
{ execution_backend: "n7_vehicle_transfer" },
],
function_configs: {},
command_plans: {},
execution_report: null,
operation_results: [],
},
{ command: "进入影院模式" },
);
const assistantMessage = harness.messages.children.at(-1);
assert.equal(
assistantMessage.dataset.messageContent,
"影院模式指令已提交,未由 PC 执行的 2 项动作将由车机继续处理。",
);
const actionStatus = assistantMessage.querySelector(".assistant-action-status").children.at(-1);
assert.equal(actionStatus.textContent, "功能模式指令已提交");
});
test("a follow-up turn carries only the active authoritative draft reference", async () => {
const events = modeDraftEvents("req-draft-one", 24);
const harness = createHarness(events);
+34 -3
View File
@@ -21,17 +21,17 @@ def test_console_contains_ai_assistant_surface():
assert 'placeholder="向 AI助手提问或交代工作"' in template
assert 'aria-label="发送消息"' in template
assert 'import "../assistant-chat.css";' in vite_entry
assert 'src="/assistant-chat.js?v=codex-assistant-35"' in html
assert 'src="/assistant-chat.js?v=codex-assistant-39-video-media"' in html
def test_codex_assistant_assets_are_versioned_together_in_the_service_worker():
service_worker = (ROOT / "apps/web-console/service-worker.js").read_text(encoding="utf-8")
assert (
'const CACHE_VERSION = "cognitive-agent-pad-v329-mode-preview"'
'const CACHE_VERSION = "cognitive-agent-pad-v334-video-media"'
in service_worker
)
assert '"/assistant-chat.css?v=codex-assistant-30"' in service_worker
assert '"/assistant-chat.js?v=codex-assistant-35"' in service_worker
assert '"/assistant-chat.js?v=codex-assistant-39-video-media"' in service_worker
def test_codex_frontend_uses_one_streaming_endpoint_and_agent_work_events():
@@ -95,6 +95,8 @@ def test_validated_controls_are_mirrored_into_a_compact_assistant_card():
assert 'count.className = "assistant-dispatched-controls-count"' in script
assert 'list.className = "assistant-dispatched-controls-list"' in script
assert "value.textContent = assistantControlValue(item)" in script
assert 'return ({ 1: "3档", 2: "1档" })[level]' in script
assert '{ 1: "3档(高)", 2: "1档(低)" }' in script
assert "语义控制指令:" in script
assert "实际控制指令:" in script
assert "appendDispatchedControls(message, event.payload)" in script
@@ -105,6 +107,9 @@ def test_validated_controls_are_mirrored_into_a_compact_assistant_card():
assert "appendDispatchedControls(message, modeExecutionPreviewResult(profile))" in script
assert "beginModeExecution," in script
assert "completeModeExecution," in script
assert "功能模式部分完成" not in script
assert "功能模式指令已提交" in script
assert "未由 PC 执行的" in script
assert "const operationByRef = new Map()" in script
assert 'deliveryState: "submitting"' in script
assert 'deliveryLabel: "正在下发"' in script
@@ -113,6 +118,10 @@ def test_validated_controls_are_mirrored_into_a_compact_assistant_card():
assert '"seat.set_legrest_height": "腿托"' in script
assert '"seat.set_legrest_extension": "腿托"' in script
assert '"ambient_light.set_illumination": "氛围灯颜色与亮度"' in script
assert '"android.launch_app": "打开视频媒体"' in script
assert 'if (capability === "android.launch_app")' in script
assert "parameters.package_name" in script
assert "parameters.activity_name" in script
assert '.replaceAll("_", "-")' in script
assert ".assistant-dispatched-controls > summary" in css
assert ".assistant-dispatched-controls-list > li" in css
@@ -130,6 +139,16 @@ def test_explicit_nfc_authorization_block_is_rendered_as_safety_blocked():
assert '? "安全阻止"' in console
def test_n7_video_launch_uses_chinese_mode_card_labels():
app = (ROOT / "apps/web-console/app.js").read_text(encoding="utf-8")
assert 'android: "车机媒体"' in app
assert 'headunit: "车机"' in app
assert '"android.launch_app": "打开视频媒体"' in app
assert 'package_name: "应用包名"' in app
assert 'activity_name: "启动页面"' in app
def test_latest_dispatched_control_group_is_the_only_group_expanded():
assistant = (ROOT / "apps/web-console/assistant-chat.js").read_text(
encoding="utf-8"
@@ -189,6 +208,18 @@ def test_codex_request_keeps_trusted_vehicle_context_without_legacy_model_routin
assert 'const executionMode = modeConfigurationPreview ? "dry_run"' in request_body
def test_codex_request_reuses_only_the_console_validated_hardware_client_id():
app = (ROOT / "apps/web-console/app.js").read_text(encoding="utf-8")
assistant = (ROOT / "apps/web-console/assistant-chat.js").read_text(encoding="utf-8")
assert "const HARDWARE_CLIENT_ID_PATTERN" in app
assert "function validatedHardwareClientId()" in app
assert "getHardwareClientId()" in app
assert "return validatedHardwareClientId();" in app
assert "window.CognitiveAgentConsole?.getHardwareClientId?.()" in assistant
assert "/^hwc-[a-z0-9-]{8,80}$/.test(value)" in assistant
def test_starting_a_new_codex_conversation_rotates_the_conversation_id():
script = (ROOT / "apps/web-console/assistant-chat.js").read_text(encoding="utf-8")
assert 'const CONVERSATION_STORAGE_KEY = "cognitive-agent.conversation-id"' in script
@@ -24,6 +24,14 @@ def compiled_mixed_result() -> dict[str, object]:
"provider_used": "local_rules+infrared_local_rules",
"execution_mode": "dry_run",
"control_transport": "wired_can",
"parallel_dispatch": {
"enabled": True,
"preflight_status": "succeeded",
"dispatch": {
"started_skew_ms": 1.2,
"total_ms": 120.5,
},
},
"function_configs": {
"native_climate": {
"spec": {
@@ -155,6 +163,10 @@ class ValidatedControlProjectionTest(unittest.TestCase):
"compile",
],
)
self.assertEqual(
projection["parallel_dispatch"]["preflight_status"],
"succeeded",
)
serialized = json.dumps(projection, ensure_ascii=False)
self.assertNotIn("command_plans", serialized)
self.assertNotIn("data_hex", serialized)
@@ -362,6 +374,10 @@ class ValidatedControlProjectionTest(unittest.TestCase):
)
self.assertNotIn("command_plans", safe_result)
self.assertNotIn("validated_control_commands", safe_result)
self.assertEqual(
safe_result["parallel_dispatch"]["preflight_status"],
"succeeded",
)
def test_agent_tool_path_queues_projection_for_the_browser_thread(self) -> None:
client = type("UnavailableClient", (), {"available": False})()
@@ -724,6 +724,50 @@ class Can1SemanticWhitelistTests(unittest.TestCase):
self.assertEqual(preview["unresolved_clauses"], [])
self.assertEqual(self._debug_commands(preview), [])
def test_driver_ventilation_numeric_gear_followup_uses_occupant_scale(self) -> None:
cases = (
(
"打开主驾座椅通风,挡位调到3档",
"seat driver vent high",
1,
),
(
"打开主驾座椅通风,挡位调到1档",
"seat driver vent low",
2,
),
)
for index, (utterance, command, internal_level) in enumerate(cases):
with self.subTest(utterance=utterance):
result = self.service.process(
utterance,
provider="local",
conversation_id=f"conv-driver-vent-gear-{index}",
execution_mode="dry_run",
)
self.assertEqual(result["status"], "succeeded")
self.assertEqual(self._debug_commands(result), [command])
operation = result["function_configs"]["native_seat"]["spec"][
"operations"
][0]
self.assertEqual(operation["parameters"]["level"], internal_level)
middle = self.service.process(
"打开主驾座椅通风,挡位调到2档",
provider="local",
conversation_id="conv-driver-vent-gear-middle",
execution_mode="dry_run",
)
self.assertEqual(middle["status"], "needs_confirmation")
self.assertEqual(self._debug_commands(middle), [])
self.assertIn(
"driver_seat_thermal_middle_level_unsupported",
{
item.get("reason")
for item in middle["intent_ir"]["spec"]["ambiguities"]
},
)
def test_vehicle_position_aliases_stay_scoped_to_the_device_domain(self) -> None:
cases = (
("打开左前车窗", ["window front-left open"]),
@@ -523,6 +523,56 @@ def test_registered_mode_execution_uses_fast_local_route_without_codex(
assert "模拟编排" in result["answer"]
def test_partially_supported_imported_mode_uses_submitted_not_partial_wording() -> None:
client = FakeCodexClient(available=False)
assistant = CodexAssistantService(client)
catalog = {
"modes": [
{
"id": "cinema",
"name": "影院模式",
"aliases": [],
"assistant_callable": True,
}
],
"source_status": {"modes": "ready"},
}
def execute(instruction: str, progress: Callable[[dict[str, object]], None]):
result = {
"request_id": "req-imported-cinema-partial",
"utterance": instruction,
"status": "partially_succeeded",
"execution_mode": "hardware",
"execution_outcome": "submitted_unverified",
"deferred_requests": [
{
"execution_backend": "n7_vehicle_transfer",
"capability": "android.launch_app",
}
],
}
progress(result)
return result
result, events = _stream(
assistant,
"进入影院模式",
execute_control=execute,
capability_snapshot=catalog,
)
assert result["route"] == "mode_profile_execute"
assert result["success"] is True
assert "已提交" in result["answer"]
assert "部分完成" not in result["answer"]
assert "未能完成" not in result["answer"]
assert any(
event.get("display_text") == "功能模式已提交,等待车辆状态确认"
for event in events
)
def test_registered_name_only_mode_keeps_the_fast_local_route_without_codex() -> None:
client = FakeCodexClient(available=False)
assistant = CodexAssistantService(client)
+42
View File
@@ -554,6 +554,48 @@ class DriverSeatNativeCanTests(unittest.TestCase):
self.assertEqual(bus.send_calls, 4)
self.assertEqual(len(bus.sent), 3)
def test_window_already_at_target_is_verified_success_without_resend(self) -> None:
config = self.runtime.build_function_config(
"window",
[
{
"domain": "window",
"capability": "window.set_position",
"target": "rear_left",
"operation": "set",
"parameters": {"position_pct": 0},
}
],
"req-window-already-closed",
"local_rules",
)
plan = self.runtime.compile(config)
feedback = CanFrame(
0x388,
bytes.fromhex("00 00 01 00 01 00 66 00"),
channel=1,
is_fd=True,
bitrate_switch=True,
)
bus = _FakeBus([feedback])
report = NativeCanExecutor(
lambda: bus,
feedback_timeout_ms=1,
feedback_required=False,
).execute(self.runtime, plan, config)
self.assertEqual(report["spec"]["status"], "succeeded")
operation = report["spec"]["results"][0]
self.assertEqual(operation["status"], "succeeded")
self.assertEqual(operation["telemetry"]["sent_frames"], [])
self.assertEqual(
operation["telemetry"]["verification_status"],
"verified_success",
)
self.assertEqual(operation["telemetry"]["delivery_status"], "not_required")
self.assertTrue(operation["telemetry"]["physical_effect_confirmed"])
def test_memory_compile_rejects_missing_or_malformed_learned_slots(self) -> None:
with tempfile.TemporaryDirectory() as directory:
path = Path(directory) / "driver_memory.json"
@@ -37,11 +37,11 @@ def test_energy_monitor_assets_are_versioned_and_cached() -> None:
assert f'"{asset}"' in service_worker
assert 'import "../energy-monitor.css";' in vite_entry
assert "/energy-monitor.js?v=energy-monitor-5-performance" in html
assert html.index("/assistant-chat.js?v=codex-assistant-35") < html.index(
assert html.index("/assistant-chat.js?v=codex-assistant-39-video-media") < html.index(
"/energy-monitor.js?v=energy-monitor-5-performance"
)
assert (
'const CACHE_VERSION = "cognitive-agent-pad-v329-mode-preview"'
'const CACHE_VERSION = "cognitive-agent-pad-v334-video-media"'
in service_worker
)
+46 -4
View File
@@ -157,6 +157,42 @@ def test_authorization_requires_exact_enabled_learned_bound_ability() -> None:
authorize_extension_control_actions(parsed, disallowed)
def test_authorization_injects_same_device_power_on_before_color() -> None:
parsed = parse_extension_control_payload(
encoded(request(action(3, "切换为红色")))
)
catalog = [
capability("light-on", 2, "打开"),
capability("light-red", 3, "切换为红色"),
]
authorized = authorize_extension_control_actions(parsed, catalog)
ordered = sorted(
authorized,
key=lambda item: (item["at_ms"], item["sequence_order"]),
)
assert [item["action"]["parameters"]["slot"] for item in ordered] == [2, 3]
assert ordered[0]["auto_inserted"] is True
assert ordered[0]["dependency"] == "power_on_before_color"
assert ordered[1]["auto_inserted"] is False
def test_authorization_rejects_color_without_same_device_power_on() -> None:
parsed = parse_extension_control_payload(
encoded(request(action(3, "切换为红色")))
)
with pytest.raises(
ExtensionControlAuthorizationError,
match="power-on action",
):
authorize_extension_control_actions(
parsed,
[capability("light-red", 3, "切换为红色")],
)
def test_consumer_validates_entire_request_then_executes_by_at_ms() -> None:
catalog = [
capability("light-on", 2, "打开"),
@@ -184,15 +220,21 @@ def test_consumer_validates_entire_request_then_executes_by_at_ms() -> None:
)
)
assert wait_until(lambda: consumer.snapshot()["state"] == "completed")
assert [item[1]["parameters"]["slot"] for item in executions] == [3, 2]
assert [item[1]["parameters"]["slot"] for item in executions] == [2, 3, 2]
status = consumer.snapshot()
assert status["processed_count"] == 1
assert status["executed_action_count"] == 2
assert [item["slot"] for item in status["last_results"]] == [3, 2]
assert [item["slot"] for item in status["action_statuses"]] == [3, 2]
assert status["executed_action_count"] == 3
assert [item["slot"] for item in status["last_results"]] == [2, 3, 2]
assert [item["slot"] for item in status["action_statuses"]] == [2, 3, 2]
assert [item["auto_inserted"] for item in status["action_statuses"]] == [
True,
False,
False,
]
assert [item["state"] for item in status["action_statuses"]] == [
"submitted_unverified",
"submitted_unverified",
"submitted_unverified",
]
assert all(
item["state_label"] == "已下发 · 物理效果未确认"
@@ -92,12 +92,12 @@ def test_performance_assets_have_a_new_cache_generation() -> None:
worker = source("service-worker.js")
for asset in (
"/app.js?v=dual-seat-145-n7-dispatch",
"/console-v2.js?v=local-first-77-performance",
"/self-extension.js?v=nfc-mqtt-56",
"/app.js?v=dual-seat-147-video-media",
"/console-v2.js?v=local-first-79-cinema-status",
"/self-extension.js?v=nfc-persistent-57",
"/energy-monitor.js?v=energy-monitor-5-performance",
):
assert asset in html
assert f'"{asset}"' in worker
assert 'const CACHE_VERSION = "cognitive-agent-pad-v329-mode-preview"' in worker
assert 'const CACHE_VERSION = "cognitive-agent-pad-v334-video-media"' in worker
assert '"/codepen-glass-theme.css?v=creative-cloud-6-performance"' in worker
+1 -1
View File
@@ -54,7 +54,7 @@ def test_theme_asset_versions_are_consistent_with_the_pwa_shell() -> None:
assert source_asset in main
assert f'"{cached_asset}"' in service_worker
assert 'import "./styles/design-system.css"' in main
assert 'const CACHE_VERSION = "cognitive-agent-pad-v329-mode-preview"' in service_worker
assert 'const CACHE_VERSION = "cognitive-agent-pad-v334-video-media"' in service_worker
def test_reference_glass_theme_covers_the_complete_workspace() -> None:
+312
View File
@@ -205,6 +205,55 @@ def cinema_scene_card() -> dict[str, object]:
}
def cinema_peripheral_catalog() -> list[dict[str, object]]:
def capability(
identity: str,
device_id: str,
device_name: str,
action: str,
slot: int,
) -> dict[str, object]:
return {
"id": identity,
"status": "learned",
"enabled": True,
"device_id": device_id,
"device_name": device_name,
"device_aliases": [device_name],
"object_names": [device_name],
"card_uid": "2CC8B103" if "flash" in device_id else "1A3AB103",
"action": action,
"aliases": [action],
"action_names": [action],
"risk": "low",
"slot": slot,
}
return [
capability("flash-on", "flash-light", "闪烁灯", "打开", 1),
capability("flash-blue", "flash-light", "闪烁灯", "切换为蓝色", 3),
capability("flash-red", "flash-light", "闪烁灯", "切换为红色", 4),
capability("strip-on", "light-strip", "灯带", "打开", 21),
capability("strip-blue", "light-strip", "灯带", "切换为蓝色", 23),
capability("strip-red", "light-strip", "灯带", "切换为红色", 24),
]
def cinema_video_launch_action() -> dict[str, object]:
return {
"type": "android_app_launch",
"at_ms": 3000,
"domain": "android",
"capability": "android.launch_app",
"target_scope": "headunit",
"operation": "launch",
"parameters": {
"activity_name": "com.byd.videoplay.activity.WelComeActivity",
"package_name": "com.byd.videoplay",
},
}
def wait_until(predicate, timeout: float = 1.5) -> bool:
deadline = time.monotonic() + timeout
while time.monotonic() < deadline:
@@ -337,6 +386,269 @@ def test_import_upserts_yaml_refreshes_catalog_and_does_not_echo(
assert "upstream_origin: user" in yaml_text
def test_later_n7_cinema_mode_inherits_base_and_adds_external_light_colors(
tmp_path: Path,
) -> None:
service = configured_service(tmp_path)
service.set_mode_scene_peripheral_catalog_provider(cinema_peripheral_catalog)
base = cinema_scene_card()
base_action_count = len(base["actions"])
base_result = service.import_n7_mode_scene(base)
assert "cinema_augmentation" not in base_result
variant = cinema_scene_card()
variant.update(
id="blue-cinema",
label="蓝色影院模式",
name="蓝色影院模式",
aliases=["蓝色观影模式"],
description="在车机影院模式基础上增加蓝色外设灯光。",
domains=["climate"],
operation_count=1,
actions=[
{
"type": "climate_temperature",
"at_ms": 0,
"domain": "climate",
"capability": "climate.set_temperature",
"target_scope": "vehicle",
"operation": "set",
"parameters": {"temperature_c": 23},
}
],
profile_revision="blue-cinema-revision",
)
imported = service.import_n7_mode_scene(variant)
augmentation = imported["cinema_augmentation"]
assert augmentation == {
"base_scene_id": "cinema",
"base_action_count": base_action_count,
"incoming_action_count": 1,
"peripheral_action_count": 4,
"merged_action_count": base_action_count + 4,
}
profile = service.get_mode_profile_card("blue-cinema", "denza_n7")
source_scene = profile.command_flow["source_scene"]
actions = source_scene["actions"]
assert source_scene["operation_count"] == base_action_count + 4
assert "self_extension" in source_scene["domains"]
temperatures = [
action["parameters"]["temperature_c"]
for action in actions
if action["capability"] == "climate.set_temperature"
]
assert temperatures == [23]
peripheral_actions = [
action
for action in actions
if action["capability"] == "infrared.emit"
]
assert [
(
action["target_scope"],
action["parameters"]["action"],
action["parameters"]["slot"],
)
for action in peripheral_actions
] == [
("flash-light", "打开", 1),
("flash-light", "切换为蓝色", 3),
("light-strip", "打开", 21),
("light-strip", "切换为蓝色", 23),
]
repeated = service.import_n7_mode_scene(variant)
assert (
repeated["cinema_augmentation"]["merged_action_count"]
== base_action_count + 4
)
reloaded = service.get_mode_profile_card("blue-cinema", "denza_n7")
assert (
len(reloaded.command_flow["source_scene"]["actions"])
== base_action_count + 4
)
def test_n7_cinema_variant_is_not_augmented_before_base_arrives(
tmp_path: Path,
) -> None:
service = configured_service(tmp_path)
service.set_mode_scene_peripheral_catalog_provider(cinema_peripheral_catalog)
variant = cinema_scene_card()
variant.update(
id="family-movie",
name="家庭影院模式",
label="家庭影院模式",
profile_revision="family-movie-revision",
)
imported = service.import_n7_mode_scene(variant)
assert "cinema_augmentation" not in imported
profile = service.get_mode_profile_card("family-movie", "denza_n7")
assert not any(
operation.capability == "infrared.emit"
for operation in profile.operations
)
def test_created_romantic_cinema_persists_and_transfers_n7_video_launch(
tmp_path: Path,
) -> None:
service = configured_service(tmp_path)
base = cinema_scene_card()
base["actions"] = [*base["actions"], cinema_video_launch_action()]
base["operation_count"] = len(base["actions"])
base["domains"] = [*base["domains"], "android"]
service.import_n7_mode_scene(base)
request_id = "req-romantic-cinema-video"
instruction = "进入浪漫观影模式"
result_dir = service.artifact_root / request_id
result_dir.mkdir(parents=True)
(result_dir / "result.json").write_text(
json.dumps(
{
"request_id": request_id,
"status": "succeeded",
"execution_mode": "dry_run",
"control_transport": "wireless_mqtt",
"utterance": instruction,
"vehicle": {"id": "denza_n7"},
"function_configs": {
"climate": {
"spec": {
"domain": "climate",
"operations": [
{
"id": "vehicle-climate-power",
"capability": "climate.set_power",
"target": {"zone": "vehicle"},
"parameters": {"state": "on"},
}
],
}
}
},
"command_plans": {
"climate": {
"spec": {
"commands": [
{
"operation_ref": "vehicle-climate-power",
"message": "climate.set_power",
}
]
}
}
},
},
ensure_ascii=False,
),
encoding="utf-8",
)
saved = service.create_mode_profile_card(
"浪漫观影模式",
instruction,
request_id,
"denza_n7",
)
profile = service.get_mode_profile_card(
str(saved["mode_profile"]["id"]),
"denza_n7",
)
assert [
operation.capability for operation in profile.operations
] == ["climate.set_power", "android.launch_app"]
assert profile.command_flow["n7_transfer_actions"] == [
cinema_video_launch_action()
]
local_requests, transfer_actions = service._mode_requests_and_n7_transfers(
profile
)
assert [request["capability"] for request in local_requests] == [
"climate.set_power"
]
assert transfer_actions == [cinema_video_launch_action()]
exported = next(
scene
for scene in service.mode_scene_catalog("denza_n7")["scenes"]
if scene["id"] == profile.id
)
video = next(
action
for action in exported["actions"]
if action["capability"] == "android.launch_app"
)
assert video == cinema_video_launch_action()
def test_existing_local_cinema_mode_publishes_video_action_only_in_hardware(
tmp_path: Path,
) -> None:
service = configured_service(tmp_path)
base = cinema_scene_card()
base["actions"] = [*base["actions"], cinema_video_launch_action()]
base["operation_count"] = len(base["actions"])
base["domains"] = [*base["domains"], "android"]
service.import_n7_mode_scene(base)
profile = service.mode_profiles.save_mode(
"浪漫观影模式",
[
{
"domain": "android",
"capability": "android.launch_app",
"target_scope": "headunit",
"operation": "launch",
"parameters": deepcopy(cinema_video_launch_action()["parameters"]),
}
],
vehicle_id="denza_n7",
instruction="进入浪漫观影模式",
command_flow={
"schema": "cognitive-agent-mode-command-flow/v1",
"plans": {"saved": {"spec": {"commands": [{"message": "saved"}]}}},
},
)
publisher = MagicMock(
return_value={
"topic": "transto_n7/ctl",
"qos": 1,
"retain": False,
"delivery_status": "published",
"message_id": 19,
}
)
service._vehicle_mqtt_transport.publish_n7_control_transfer = publisher
preview = service.execute_mode_profile_card(
profile.id,
execution_mode="dry_run",
control_transport="wireless_mqtt",
)
publisher.assert_not_called()
assert preview["n7_transfer_candidates"] == [cinema_video_launch_action()]
executed = service.execute_mode_profile_card(
profile.id,
execution_mode="hardware",
control_transport="wireless_mqtt",
)
assert executed["n7_control_transfer"]["state"] == "published"
publisher.assert_called_once()
payload = publisher.call_args.args[0]
assert payload["scene_id"] == profile.id
assert payload["scene_name"] == "浪漫观影模式"
assert payload["operation_count"] == 1
assert payload["actions"] == [cinema_video_launch_action()]
def test_unknown_n7_action_is_forwarded_once_only_during_hardware_execution(
tmp_path: Path,
) -> None:
+9 -7
View File
@@ -17,7 +17,7 @@ def test_unified_console_exposes_device_id_based_nfc_authorization() -> None:
vite_entry = source("src/main.js")
assert 'data-open-nfc-binding' in script
assert '靠近读卡器后' in script
assert '请将 NFC 卡靠近读卡器' in script
assert '<h2>NFC 设备授权</h2>' in script
assert 'NFC 设备授权' in script
assert 'api("nfc/status")' in script
@@ -55,23 +55,25 @@ def test_unified_console_exposes_device_id_based_nfc_authorization() -> None:
assert '学习指令已下发,但物理槽位回读为空' in script
assert 'signalSource: "n7web"' in script
assert 'if (capability.signalSource === "n7web") return' in script
assert 'const activeCatalog = sensedCardUid' in script
assert 'normalizeCardUid(item.card_uid) === sensedCardUid' in script
assert 'const persistentCatalog = (' in script
assert 'durablePeripheralCatalogHydrated' in script
assert 'durablePeripheralCatalog' in script
assert 'normalizeCardUid(item.card_uid)' in script
assert '&& item.status === "learned"' in script
assert 'slot: capability.slot' in script
assert 'device_aliases: device?.aliases || []' in script
assert 'async function hydratePeripheralCatalog()' in script
assert 'api("peripherals")' in script
assert 'signalSource: "backend_catalog"' in script
assert '当前未感应到外设' in script
assert '将已绑定设备 ID 的 NFC 卡靠近读卡器后显示命令' in script
assert '当前没有已绑定外设' in script
assert '外设绑定 NFC 后将持续显示,解除绑定后移除' in script
assert '.v2-nfc-inline' in styles
assert '.v2-nfc-dialog' in styles
assert '.v2-nfc-command-list' in styles
assert '.v2-nfc-command-option' in styles
assert '.v2-nfc-select-all' in styles
assert 'self-extension.js?v=nfc-mqtt-56' in html
assert 'self-extension.js?v=nfc-persistent-57' in html
assert 'import "../self-extension.css";' in vite_entry
@@ -219,5 +221,5 @@ def test_main_parsed_control_panel_renders_validated_infrared_operations() -> No
assert "下发控制指令" in console
assert "已解析控制指令" not in console
assert 'li[data-can-channel="IR"]' in styles
assert "console-v2.js?v=local-first-77-performance" in html
assert "console-v2.js?v=local-first-79-cinema-status" in html
assert 'speech_semantic: "语音语义"' in console
@@ -0,0 +1,305 @@
from __future__ import annotations
import threading
import time
from types import SimpleNamespace
from unittest.mock import patch
import pytest
from cognitive_agent.server import SeatDemoHandler
def _result(mode: str, *, status: str = "succeeded") -> dict[str, object]:
return {
"request_id": f"req-{mode}-{status}",
"status": status,
"success": status == "succeeded",
"execution_mode": mode,
"decision_summary": [],
"function_configs": {},
"command_plans": {},
"trace_events": [],
"operation_results": [],
"execution_report": None,
"control_transport": "test",
}
def _entries() -> list[dict[str, object]]:
return [
{
"index": 0,
"clause": "打开闪烁灯",
"capability": {
"id": "ir-slot:flash:2",
"device_id": "flash",
"device_name": "闪烁灯",
"action": "打开",
"slot": 2,
"parse_source": "local_rule",
},
}
]
def test_hardware_branches_preflight_then_start_together() -> None:
handler = object.__new__(SeatDemoHandler)
lock = threading.Lock()
preflight_completed: set[str] = set()
hardware_started: dict[str, float] = {}
def mark_preflight(name: str) -> dict[str, object]:
time.sleep(0.03)
with lock:
preflight_completed.add(name)
return _result("dry_run")
def execute_can() -> dict[str, object]:
with lock:
assert preflight_completed == {"can", "peripheral"}
hardware_started["can"] = time.perf_counter()
time.sleep(0.12)
return _result("hardware")
def execute_peripheral(
_utterance: str,
_entries_value: list[dict[str, object]],
*,
execution_mode: str,
**_kwargs: object,
) -> dict[str, object]:
if execution_mode == "dry_run":
return mark_preflight("peripheral")
with lock:
assert preflight_completed == {"can", "peripheral"}
hardware_started["peripheral"] = time.perf_counter()
time.sleep(0.12)
return _result("hardware")
with patch.object(
handler,
"_execute_local_peripheral_actions",
side_effect=execute_peripheral,
):
result = handler._execute_can_and_peripheral_branches(
utterance="打开空调和闪烁灯",
entries=_entries(),
execution_mode="hardware",
provider="local",
conversation_id="conv-parallel-unit",
can_execute=execute_can,
can_preflight=lambda: mark_preflight("can"),
parallel_eligible=True,
)
assert result["status"] == "succeeded"
assert result["parallel_dispatch"]["enabled"] is True
assert result["parallel_dispatch"]["preflight_status"] == "succeeded"
assert abs(hardware_started["can"] - hardware_started["peripheral"]) < 0.05
dispatch = result["parallel_dispatch"]["dispatch"]
branch_durations = [
item["duration_ms"] for item in dispatch["branches"].values()
]
assert dispatch["total_ms"] < sum(branch_durations)
def test_failed_preflight_starts_neither_hardware_branch() -> None:
handler = object.__new__(SeatDemoHandler)
hardware_calls: list[str] = []
def execute_peripheral(
_utterance: str,
_entries_value: list[dict[str, object]],
*,
execution_mode: str,
**_kwargs: object,
) -> dict[str, object]:
if execution_mode == "hardware":
hardware_calls.append("peripheral")
return _result(execution_mode)
with patch.object(
handler,
"_execute_local_peripheral_actions",
side_effect=execute_peripheral,
):
result = handler._execute_can_and_peripheral_branches(
utterance="打开空调和闪烁灯",
entries=_entries(),
execution_mode="hardware",
provider="local",
conversation_id="conv-parallel-preflight-failure",
can_execute=lambda: hardware_calls.append("can") or _result("hardware"),
can_preflight=lambda: _result(
"dry_run",
status="validation_failed",
),
parallel_eligible=True,
)
assert hardware_calls == []
assert result["status"] == "validation_failed"
assert result["execution_mode"] == "dry_run"
assert result["hardware_execution_allowed"] is False
assert result["parallel_dispatch"]["enabled"] is False
assert result["parallel_dispatch"]["preflight_status"] == "failed"
def test_wireless_parallel_dispatch_does_not_require_local_bench_lease() -> None:
handler = object.__new__(SeatDemoHandler)
authorization_calls: list[str | None] = []
handler.service = SimpleNamespace(
hardware_access=SimpleNamespace(
ensure_authorized=lambda client_id: authorization_calls.append(client_id)
)
)
handler._ensure_parallel_hardware_authorized(
execution_mode="hardware",
control_transport="wireless_mqtt",
hardware_access_bypass=False,
hardware_client_id=None,
)
assert authorization_calls == []
def test_wired_parallel_dispatch_keeps_local_bench_lease_gate() -> None:
handler = object.__new__(SeatDemoHandler)
authorization_calls: list[str | None] = []
handler.service = SimpleNamespace(
hardware_access=SimpleNamespace(
ensure_authorized=lambda client_id: authorization_calls.append(client_id)
)
)
handler._ensure_parallel_hardware_authorized(
execution_mode="hardware",
control_transport="wired_can",
hardware_access_bypass=False,
hardware_client_id="hwc-wired-parallel-test",
)
assert authorization_calls == ["hwc-wired-parallel-test"]
@pytest.mark.parametrize(
"utterance",
(
"先打开空调,再打开闪烁灯",
"打开空调,然后打开闪烁灯",
"打开空调,2秒后打开闪烁灯",
),
)
def test_explicit_cross_transport_order_disables_parallelism(
utterance: str,
) -> None:
assert SeatDemoHandler._mixed_request_requires_transport_order(utterance)
def test_ineligible_flow_keeps_existing_can_then_peripheral_order() -> None:
handler = object.__new__(SeatDemoHandler)
calls: list[str] = []
def execute_peripheral(
_utterance: str,
_entries_value: list[dict[str, object]],
*,
execution_mode: str,
**_kwargs: object,
) -> dict[str, object]:
calls.append(f"peripheral:{execution_mode}")
return _result(execution_mode)
def unexpected_preflight() -> dict[str, object]:
raise AssertionError("ordered flow must not run a duplicate preflight")
with patch.object(
handler,
"_execute_local_peripheral_actions",
side_effect=execute_peripheral,
):
result = handler._execute_can_and_peripheral_branches(
utterance="先打开空调,再打开闪烁灯",
entries=_entries(),
execution_mode="hardware",
provider="local",
conversation_id="conv-ordered-unit",
can_execute=lambda: calls.append("can:hardware") or _result("hardware"),
can_preflight=unexpected_preflight,
parallel_eligible=False,
)
assert calls == ["can:hardware", "peripheral:hardware"]
assert result["status"] == "succeeded"
assert "parallel_dispatch" not in result
def test_saved_static_mode_uses_parallel_can_and_peripheral_path() -> None:
handler = object.__new__(SeatDemoHandler)
can_modes: list[str] = []
peripheral_modes: list[str] = []
persisted: list[dict[str, object]] = []
profile = SimpleNamespace(
id="mode-parallel",
schedule=None,
command_flow={"execution_steps": []},
public_dict=lambda: {"id": "mode-parallel", "name": "并行模式"},
)
def execute_mode_profile_card(
_mode_id: str,
**kwargs: object,
) -> dict[str, object]:
mode = str(kwargs.get("execution_mode") or "")
can_modes.append(mode)
return _result(mode)
handler.service = SimpleNamespace(
execute_mode_profile_card=execute_mode_profile_card,
persist_composed_result=lambda result: persisted.append(result),
)
def execute_peripheral(
_utterance: str,
_entries_value: list[dict[str, object]],
*,
execution_mode: str,
**_kwargs: object,
) -> dict[str, object]:
peripheral_modes.append(execution_mode)
return _result(execution_mode)
with (
patch.object(
handler,
"_exact_local_peripheral_mode_invocation",
return_value=profile,
),
patch.object(handler, "_exact_n7_mode_invocation", return_value=None),
patch.object(handler, "_saved_mode_infrared_entries", return_value=_entries()),
patch.object(
handler,
"_execute_local_peripheral_actions",
side_effect=execute_peripheral,
),
):
result = handler._execute_n7_named_mode_request(
"/api/requests",
{
"utterance": "进入并行模式",
"execution_mode": "hardware",
"provider": "local",
"conversation_id": "conv-saved-parallel",
"vehicle_id": "denza_n7",
"control_transport": "wired_can",
},
)
assert result is not None
assert can_modes == ["dry_run", "hardware"]
assert peripheral_modes == ["dry_run", "hardware"]
assert result["parallel_dispatch"]["enabled"] is True
assert result["provider_used"] == "saved_mode_command_flow"
assert persisted == [result]
@@ -937,6 +937,22 @@ def test_delivery_status_distinguishes_blocked_failed_aborted_and_verified() ->
"physical_effect_confirmed": True,
},
),
sample(
{
"id": "window-feedback-confirmed",
"capability": "window.set_position",
"target": {"zone": "rear-left"},
"parameters": {"position_pct": 0},
},
status="failed",
message="MQTT 白名单控制帧已进入客户端发布队列;车辆效果以状态话题回读为准。",
telemetry={
"delivery_status": "submitted",
"verification_status": "verified_success",
"feedback_match": True,
"physical_effect_confirmed": True,
},
),
sample(
{
"id": "climate-post-submit-failed",
@@ -970,6 +986,7 @@ def test_delivery_status_distinguishes_blocked_failed_aborted_and_verified() ->
"下发失败",
"已中止",
"执行已确认",
"执行已确认",
"执行失败",
]
assert [item["deliveryState"] for item in commands] == [
@@ -977,6 +994,7 @@ def test_delivery_status_distinguishes_blocked_failed_aborted_and_verified() ->
"failed",
"aborted",
"submitted",
"submitted",
"failed",
]
assert "0x388 实时位置" in commands[0]["deliveryDetail"]
+122 -4
View File
@@ -1406,6 +1406,15 @@ class SelfExtensionTests(unittest.TestCase):
def test_main_peripheral_sequence_executes_validated_actions_and_delays(self) -> None:
service = SelfExtensionService(SimulationGateway())
catalog = [
{
"id": "on",
"status": "learned",
"device_id": "third-party-light",
"device_name": "第三方氛围灯",
"card_uid": "1A53DD95",
"action": "打开灯光",
"slot": 1,
},
{
"id": "red",
"status": "learned",
@@ -1457,16 +1466,24 @@ class SelfExtensionTests(unittest.TestCase):
self.assertEqual(
result["orchestration_route"], "nfc_peripheral_sequence"
)
self.assertEqual(result["sequence"]["step_count"], 3)
self.assertEqual(result["sequence"]["action_count"], 2)
self.assertEqual(result["sequence"]["emit_count"], 2)
self.assertEqual(result["sequence"]["step_count"], 4)
self.assertEqual(result["sequence"]["action_count"], 3)
self.assertEqual(result["sequence"]["emit_count"], 3)
self.assertEqual(result["sequence"]["total_delay_ms"], 5000)
self.assertEqual(result["sequence"]["steady_blink_fallbacks"], 1)
self.assertEqual(
[item["status"] for item in result["operation_results"]],
["succeeded", "succeeded", "succeeded"],
["succeeded", "succeeded", "succeeded", "succeeded"],
)
self.assertEqual(
[
item["parameters"]["slot"]
for item in result["function_config"]["spec"]["operations"]
],
[1, 3, 4],
)
sleep.assert_any_call(5.0)
sleep.assert_any_call(0.8)
self.assertNotIn(0.5, [call.args[0] for call in sleep.call_args_list])
def test_failed_first_emit_retains_complete_compiled_infrared_plan(self) -> None:
@@ -1630,6 +1647,107 @@ class SelfExtensionTests(unittest.TestCase):
)
)
def test_color_only_request_automatically_executes_power_on_first(self) -> None:
with TemporaryDirectory() as directory:
service = SelfExtensionService(
SimulationGateway(),
catalog_db_path=Path(directory, "infrared.sqlite3"),
)
service.sync_peripheral_catalog(
[
{
"id": "flash-on",
"status": "learned",
"enabled": True,
"device_id": "flash-light",
"device_name": "闪烁灯",
"object_names": ["闪烁灯"],
"card_uid": "2CC8B103",
"action": "打开",
"action_names": ["打开", "开启", "启动"],
"slot": 1,
},
{
"id": "flash-blue",
"status": "learned",
"enabled": True,
"device_id": "flash-light",
"device_name": "闪烁灯",
"object_names": ["闪烁灯"],
"card_uid": "2CC8B103",
"action": "切换为蓝色",
"action_names": ["蓝色", "设置为蓝色"],
"slot": 3,
},
]
)
color = next(
item
for item in service.list_peripheral_capabilities()
if item["id"] == "flash-blue"
)
result = service.execute_peripheral_request(
"把闪烁灯设置为蓝色",
color,
conversation_id="conv-color-power-dependency",
execution_mode="dry_run",
)
self.assertEqual(result["status"], "succeeded")
self.assertEqual(result["sequence"]["action_count"], 2)
self.assertEqual(
[
item["parameters"]["slot"]
for item in result["function_config"]["spec"]["operations"]
],
[1, 3],
)
self.assertEqual(
[
item["signals"]["slot"]
for item in result["command_plan"]["spec"]["commands"]
],
[1, 3],
)
service.close()
def test_color_request_is_rejected_when_power_on_is_not_authorized(self) -> None:
with TemporaryDirectory() as directory:
service = SelfExtensionService(
SimulationGateway(),
catalog_db_path=Path(directory, "infrared.sqlite3"),
)
service.sync_peripheral_catalog(
[
{
"id": "strip-red",
"status": "learned",
"enabled": True,
"device_id": "light-strip",
"device_name": "灯带",
"object_names": ["灯带"],
"card_uid": "1A3AB103",
"action": "切换为红色",
"action_names": ["红色", "设置为红色"],
"slot": 24,
}
]
)
color = service.list_peripheral_capabilities()[0]
with self.assertRaisesRegex(
ExtensionIntegrationError,
"设置颜色前需要先打开",
):
service.execute_peripheral_request(
"把灯带设置为红色",
color,
conversation_id="conv-color-missing-power",
execution_mode="dry_run",
)
service.close()
def test_color_sequence_never_cycles_the_power_off_capability(self) -> None:
service = SelfExtensionService(SimulationGateway())
catalog = [
@@ -37,11 +37,29 @@ def test_peripheral_catalog_sync_is_serialized_and_tracks_backend_revision() ->
)
assert "peripheralCatalogSyncQueue" in sync
assert "const nextCatalog = peripheralCatalog();" in sync
assert "catalog_revision: peripheralCatalogRevision" in sync
assert "result.catalog_revision" in sync
assert "applyDurablePeripheralCatalog(" in sync
assert "if (throwOnError) throw error;" in sync
def test_backend_catalog_is_authoritative_for_persistent_device_cards() -> None:
source = SOURCE_PATH.read_text(encoding="utf-8")
hydrate = _function_source(
source,
" async function hydratePeripheralCatalog() {",
"\n function vehicleCatalog",
)
bootstrap = source[source.index(" void hydratePeripheralCatalog()") :]
assert "applyDurablePeripheralCatalog(" in hydrate
assert "if (!catalog.length)" in hydrate
assert "renderAll();" in hydrate
assert ".finally(() => syncPeripheralCatalog())" not in bootstrap
assert "return syncPeripheralCatalog();" in bootstrap
def test_bound_card_can_be_reassigned_to_a_new_device() -> None:
source = SOURCE_PATH.read_text(encoding="utf-8")
render = _function_source(
+16 -9
View File
@@ -429,23 +429,26 @@ class SpeechCapabilityAlignmentTests(unittest.TestCase):
result = self.processor.process("打开座椅通风并调到三档")
self.assertEqual(
[command.capability for command in result.commands],
[
"seat.set_driver_ventilation_native",
"seat.set_driver_ventilation_native",
],
["seat.set_driver_ventilation_native"],
)
self.assertEqual(
[command.target_scope for command in result.commands],
["driver", "driver"],
["driver"],
)
self.assertIn("3档", result.clean_text)
self.assertEqual(result.semantic_status, "recognized_unsupported")
self.assertFalse(result.execution_eligible)
self.assertEqual(result.commands[0].parameters, {"state": "on", "level": 1})
self.assertEqual(result.commands[0].qualifiers["display_level"], "3档")
self.assertEqual(result.semantic_status, "recognized_executable")
self.assertTrue(result.execution_eligible)
passenger = self.processor.process("打开副驾座椅通风并调到三档")
self.assertEqual(
[command.target_scope for command in passenger.commands],
["passenger", "passenger"],
["passenger"],
)
self.assertEqual(
passenger.commands[0].parameters,
{"state": "on", "level": 3},
)
self.assertTrue(passenger.execution_eligible)
@@ -489,7 +492,11 @@ class SpeechCapabilityAlignmentTests(unittest.TestCase):
self.assertNotIn("设置为", result.clean_text)
explicit_level = self.processor.process("主驾座椅通风开到一档")
self.assertEqual(explicit_level.commands[0].parameters["level"], 1)
self.assertEqual(explicit_level.commands[0].parameters["level"], 2)
self.assertEqual(
explicit_level.commands[0].qualifiers["display_level"],
"1档",
)
def test_discourse_connectors_preserve_actions_without_leaking_scope(self) -> None:
cases = (
+1 -1
View File
@@ -31,7 +31,7 @@ def test_vue_vite_application_is_the_primary_frontend_entry() -> None:
assert package["scripts"]["build"] == "vite build"
assert 'import vue from "@vitejs/plugin-vue"' in vite
assert 'outDir: outputRoot' in vite
assert 'const CACHE_VERSION = "cognitive-agent-pad-v329-mode-preview"' in service_worker
assert 'const CACHE_VERSION = "cognitive-agent-pad-v334-video-media"' in service_worker
assert 'createHash("sha256")' in vite
assert 'runtime-assets.json' in vite
assert 'rewriteRuntimeReferences' in vite