from __future__ import annotations import json import shutil import subprocess from pathlib import Path ROOT = Path(__file__).resolve().parents[2] def source(name: str) -> str: web_root = ROOT / "apps" / "web-console" content = (web_root / name).read_text(encoding="utf-8") if name == "index.html": content += "\n" + (web_root / "src" / "App.vue").read_text(encoding="utf-8") return content def test_frontend_only_exposes_user_orchestration_and_extension() -> None: web_root = ROOT / "apps" / "web-console" html = source("index.html") console = source("console-v2.js") for removed in ( "self-calibration.js", "self-calibration.css", "self-repair.js", "self-repair.css", "self-repair-seat3d.js", "self-evolution.js", "self-evolution.css", ): assert not (web_root / removed).exists() assert removed not in html assert "511工程-智能座舱" not in console assert '["self-orchestration", "self-extension"]' in console assert 'id="vehicleExtensionSync"' in console assert 'getRole: () => "user"' in console assert '"admin"' not in console assert "v2RoleSelect" not in console def test_retired_layout_is_removed_instead_of_permanently_hidden() -> None: html = source("index.html") app = source("app.js") console = source("console-v2.js") styles = source("console-v2.css") for retired_markup in ( 'class="topbar"', 'class="example-group"', 'id="safetyStrip"', 'class="pipeline"', 'id="gestureBackdrop"', ): assert retired_markup not in html for retired_runtime_hook in ( "v2-legacy-parking", "v2-legacy-hidden", "#connectionBadge", "#modelBadge", "#hardwareBadge", "#executionBadge", "#logoutButton", "#gestureToggleButton", "#pwaBadge", ): assert retired_runtime_hook not in app assert retired_runtime_hook not in console assert retired_runtime_hook not in styles def test_extension_pet_and_transient_vehicle_state_restart_contract() -> None: extension = source("self-extension.js") styles = source("self-extension.css") assert 'id="extensionDesktopPet"' in extension assert "function initializeDesktopPet()" in extension assert "saved.vehicleExecutions = {};" in extension assert "saved.lastProtocol = null;" in extension assert 'src="/assets/pets/self-extension-cat.png?v=3"' in extension assert 'class="v2-extension-pet-entrance-canvas"' in extension assert '"/assets/pets/self-extension-cat-entrance.png?v=3"' in extension assert "const ENTRANCE_KEYFRAME_COUNT = 12" in extension assert "const ENTRANCE_DURATION_MS = 2300" in extension assert "const ENTRANCE_WALK_STEPS = 5" in extension assert "const ENTRANCE_SETTLE_START_FRAME = 5" in extension assert "0, 1, 2, 3, 4, 3, 2, 1, 0, 1, 2, 3, 4" in extension assert "const easedStepProgress = 0.5 - (Math.cos(Math.PI * stepProgress) / 2)" in extension assert "function playEntrance()" in extension assert 'pet.dataset.entering = "true"' in extension assert "const drawEntrancePose =" in extension assert "window.requestAnimationFrame(renderEntrance)" in extension assert "function createThemedPetDataUrl(" in extension assert "blueDominance < 24" in extension assert 'window.addEventListener("cognitive-agent:demo-role-change"' not in extension assert 'class="v2-extension-pet-blink"' in extension assert "const scheduleBlink =" in extension assert 'pet.dataset.blinking = "true"' in extension assert 'class="v2-extension-pet-mouth-closed"' in extension assert "const animateMouth = () =>" in extension assert "mouth.animate?.(" in extension assert "{ opacity: 0, offset: 0.09 }" in extension assert "{ opacity: 0, offset: 0.41 }" in extension assert "{ opacity: 0, offset: 0.73 }" in extension assert "{ duration: 1260" in extension assert 'class="v2-extension-pet-tail-canvas"' in extension assert "const runTailWave =" in extension assert "const requestTailWave =" in extension assert 'prefers-reduced-motion: reduce' not in extension assert "tailWaveQueued = true" in extension assert "window.setTimeout(() => runTailWave(true), 180)" in extension assert "scheduleTailWave" not in extension assert 'pet.dataset.tailWaving = "true"' in extension assert "const tailMotionFrames = [" in extension assert "const sampleTailOffset =" in extension assert "sampleTailOffset(tailPose, distanceFromBase)" in extension assert "clampToSegment = false" in extension assert "Math.max(Math.min(start, end)" in extension assert "const sampleTravelingTailOffset =" not in extension assert 'document.visibilityState !== "visible"' in extension assert ".v2-extension-pet-body" in styles assert ".v2-extension-pet-entrance-canvas" in styles assert '.v2-extension-pet[data-entering="true"] .v2-extension-pet-image' in styles assert '.v2-extension-pet[data-blinking="true"] .v2-extension-pet-blink' in styles assert ".v2-extension-pet-mouth-closed" in styles assert "width: 176px;" in styles assert "font-size: 13px;" in styles assert '.v2-extension-pet[data-tail-waving="true"] .v2-extension-pet-tail-canvas' in styles assert '.v2-extension-pet[data-tail-waving="true"] .v2-extension-pet-image' in styles assert "position: fixed;" in styles assert "extension-pet-float" not in styles assert '[data-module="self-extension"][data-demo-role="admin"] .v2-sidebar' not in styles assert '[data-module="self-extension"][data-demo-role="admin"] .v2-avatar' not in styles def test_orchestration_session_reset_remains_available() -> None: orchestration = source("app.js") assert 'sessionStorage.removeItem("cognitive-agent.conversation-id")' in orchestration def test_vehicle_device_status_cards_prioritize_seats_and_expose_manual_controls() -> None: console = source("console-v2.js") styles = source("console-v2.css") passenger = console.index('id: "passenger", name: "副驾座椅"') driver = console.index('id: "driver", name: "主驾座椅"') windows = console.index('id: "windows", name: "车窗"') assert passenger < driver < windows for key_status in ( '["左前窗", readText("#frontLeftWindow")]', '["右前窗", readText("#frontRightWindow")]', '["左后窗", readText("#rearLeftWindow")]', '["右后窗", readText("#rearRightWindow")]', '["当前位置", readText("#shadePositionValue")]', '["按摩模式", massageMode]', '["按摩强度", massageStrength]', '["前排亮度", frontBrightness]', '["前排颜色", frontColor]', '["后排亮度", rearBrightness]', '["后排颜色", rearColor]', ): assert key_status in console assert 'data-device-control-toggle="${device.id}"' in console assert 'data-device-control-apply="${device.id}"' in console assert 'new CustomEvent("cognitive-agent:vehicle-can-control-request"' in console assert "window.CognitiveAgentConsole?.executeCanCommands" in console assert "device.commands(values).filter(Boolean)" in console assert 'controllable: false' in console assert '.v2-device-control-row input[type="range"]' in styles assert ".v2-device-control-select" in styles assert ".v2-device-control-apply" in styles def test_ambient_status_uses_its_own_power_feedback_not_combined_light_badge() -> None: console = source("console-v2.js") ambient_start = console.index('id: "ambient", name: "氛围灯"') ambient_end = console.index('id: "headlight", name: "前大灯"', ambient_start) ambient = console[ambient_start:ambient_end] assert 'function ambientPowerState()' in console assert 'dataset.ambientPower' in console assert 'const powerState = ambientPowerState();' in ambient assert 'powerState === "on" ? "开启"' in ambient assert 'ambientPowerState() === "off" ? "off" : "on"' in ambient assert 'readText("#ambientPower")' not in ambient def test_passenger_can0_status_ui_uses_only_seat_ota_four_axis_contract() -> None: app = source("app.js") console = source("console-v2.js") assert 'new Set(["longitudinal", "height", "backrest_recline", "legrest_height"])' in app assert 'if (thermalRow) thermalRow.hidden = seat === "passenger";' in app assert 'height: seatHeight,' in app assert '"seat.set_cushion_tilt": "调节座椅高度"' in app assert 'rows: () => [["前后", seatAxisValue("passenger", "longitudinal")], ["高度", seatAxisValue("passenger", "cushion_tilt")]' in console assert 'rangeControl("cushion_tilt", "高度"' in console assert 'rangeControl("legrest_extension"' not in console def test_left_sidebar_exposes_collapsed_parsed_can_command_panel() -> None: console = source("console-v2.js") styles = source("console-v2.css") assert 'make("details", "v2-can-command-panel"' in console assert 'canCommandPanel.id = "v2CanCommandPanel"' in console assert 'id="v2CanCommandCount">0' in console assert 'id="v2CanCommandList"' in console assert 'id="v2CommandTiming"' in console assert "panel.open = true;" in console assert "if (ui.sidebarCollapsed) setSidebarCollapsed(false);" in console assert "function parsedCanCommandForOperation(operation = {})" in console assert "function parsedCanCommands(result = {})" in console assert "function semanticControlInstruction(operation = {})" in console assert "function compiledCommandsForOperation(result, configKey, operation = {})" in console assert "function parsedCommandTimingRows(result = {}, timingState = {})" in console assert "result.validated_control_commands" in console assert "getParsedCanCommands: (result) => parsedCanCommands(result)" in console assert "const configurations = result.function_configs || {};" in console assert "if (!configurationEntries.length && result.function_config)" in console assert "const safetyBlocked = executionFailed && !submitted" in console assert 'safetyBlocked\n ? "安全阻止"' in console assert 'data-delivery-state="${escapeHtml(item.deliveryState)}"' in console assert 'const command = (category, text, channel = "CAN1", frame = "")' in console assert 'data-can-channel="${escapeHtml(item.channel)}"' in console assert 'class="v2-can-command-channel"' in console assert 'class="v2-can-command-layer v2-can-command-semantic"' in console assert 'class="v2-can-command-layer v2-can-command-actual"' in console assert console.index("语义控制指令") < console.index("实际控制指令") assert 'recordTiming: true' in console assert 'reveal: true' in console assert 'window.addEventListener("cognitive-agent:speech-state"' in console assert 'window.addEventListener("cognitive-agent:speech-result"' in console assert 'window.addEventListener("cognitive-agent:control-request-start"' in console assert 'window.addEventListener("cognitive-agent:result"' in console assert '$("#requestForm")?.addEventListener("submit"' in console assert "function commandResultMatchesTimedRequest(result = {})" in console assert "matchesTimedRequest && !validatedCommandsVisible" in console for command_contract in ( "door rear ${action}", "door vehicle ${lockState}", "window ${target} ${motion}", "window ${target} position ${position}", "ac ${onOff}", "ac fan ${level}", "ac mode ${mode}", "ac temp ${target} ${temperature}", "ac control ${mode}", "ac cycle ${mode}", "outlet ${target} percent ${horizontal} ${vertical}", "outlet ${target} position ${horizontal} ${vertical}", "seat driver ${feature} ${setting}", "seat driver massage strength ${strength}", "seat driver ${axis} ${direction} ${duration}", "seat driver memory recall ${position}", "seat driver position ${position}", "light indoor ${onOff}", "light reading all ${onOff}", "light ambient color ${color}", "light ambient brightness ${level}", "Ambient_RGB_Control_32B_2B(color=${color}, brightness=${brightness}%)", "Ambient_Mode_Control_496_04(mode=${mode})", "Climate_Control_064_04(${target}=${temperature}°C)", "shade position ${position}", "seat passenger ${axis[1]} position ${position}", "seat passenger dynamic ${dynamic}", "seat passenger dynamic stop", "nativeCan0Frame(0x01, axis[2], 0, position * 100, 30000)", "nativeCan0Frame(0x0B, axis[2], 0, 0x4B52, 120)", "nativeCan0Frame(0x0E, 0, 0, mode, 0)", "nativeCan0Frame(0x0F, 0, 0, 0, 0)", ): assert command_contract in console assert ".v2-can-command-panel[open]" in styles assert ".v2-can-command-list li code" in styles assert ".v2-can-command-channel" in styles assert ".v2-can-command-delivery" in styles assert ".v2-command-timing" in styles assert ".v2-can-command-semantic" in styles assert ".v2-can-command-actual" in styles assert '.v2-can-command-delivery[data-delivery-state="submitted"]' in styles assert 'li[data-can-channel="CAN0"]' in styles assert ".v2-sidebar-collapsed .v2-can-command-panel" in styles def test_parsed_can_command_panel_combines_can0_seat_and_can1_vehicle_commands() -> None: node = shutil.which("node") if node is None: return console = source("console-v2.js") start = console.index(" const normalizedCanTarget") end = console.index("\n\n function renderParsedCanCommands", start) helpers = console[start:end] sample = { "function_configs": { "seat": { "spec": { "operations": [ { "capability": "seat.set_backrest_recline", "target": {"zone": "front_right"}, "parameters": {"position_pct": 36, "angle_deg": 120}, "parse_source": "local_rule", }, ] } }, "native_climate": { "spec": { "operations": [ { "capability": "climate.set_temperature", "target": {"zone": "driver"}, "parameters": {"temperature_c": 24}, "parse_source": "cloud_supplement", } ] } }, }, "command_plans": { "seat": {"spec": {"commands": [{}]}}, "native_climate": {"spec": {"commands": [{}]}}, }, } script = ( f"{helpers}\n" f"const sample = {json.dumps(sample)};\n" "process.stdout.write(JSON.stringify(parsedCanCommands(sample)));" ) completed = subprocess.run( [node, "-"], input=script, check=True, capture_output=True, text=True, encoding="utf-8", ) assert json.loads(completed.stdout) == [ { "channel": "CAN0", "category": "副驾座椅靠背调节", "command": "seat passenger backrest position 36", "frame": "0x300 · 01 12 10 0E 30 75", "parseSource": "local_rule", "semantic": "seat.set_backrest_recline(target=front-right, angle_deg=120, position_pct=36)", "operationRef": "", "deliveryState": "not_submitted", "deliveryLabel": "未下发", "deliveryTransport": "有线 CAN", }, { "channel": "CAN1", "category": "空调温度", "command": "ac temp driver 24", "parseSource": "cloud_supplement", "semantic": "climate.set_temperature(target=driver, temperature_c=24)", "operationRef": "", "deliveryState": "not_submitted", "deliveryLabel": "未下发", "deliveryTransport": "有线 CAN", }, ] def test_local_mixed_command_uses_compiled_can_and_ir_details_with_stage_timings() -> None: node = shutil.which("node") if node is None: return console = source("console-v2.js") start = console.index(" const normalizedCanTarget") end = console.index("\n\n function renderParsedCanCommands", start) helpers = console[start:end] sample = { "provider_used": "local_rules", "function_configs": { "native_climate": { "spec": { "domain": "climate", "operations": [ { "id": "climate-temperature-1", "capability": "climate.set_temperature", "target": {"zone": "driver"}, "parameters": {"temperature_c": 23}, "parse_source": "local_rule", } ], } }, "self_extension": { "spec": { "domain": "self_extension", "operations": [ { "id": "infrared-1", "capability": "infrared.emit", "target": {"device": "toy-001", "name": "闪灯玩具"}, "parameters": {"slot": 1, "action": "开启"}, "parse_source": "local_rule", } ], } }, }, # Deliberately reverse the plan order and include a distracting command; # the renderer must join only on the exact operation_ref. "command_plans": { "self_extension": { "spec": { "domain": "self_extension", "commands": [ { "operation_ref": "unrelated-ir", "message": "infrared.emit", "signals": {"slot": 9}, }, { "operation_ref": "infrared-1", "bus": "TCP", "message": "infrared.emit", "signals": {"slot": 1}, }, ], } }, "native_climate": { "spec": { "domain": "climate", "commands": [ { "operation_ref": "climate-temperature-1", "bus": "body_can", "message": "Native_0x1DE", "signals": { "debug_command": "ac temp driver 23", "frame_id": "0x1DE", "data_hex": "00 00 00 00 17 00 00 00", }, } ], } }, }, "trace_events": [ {"stage": "local_peripheral_plan", "duration_ms": 1}, {"stage": "nfc_peripheral_sequence", "duration_ms": 0}, {"stage": "intent_parse", "duration_ms": 12}, {"stage": "context_resolve", "duration_ms": 1}, {"stage": "compile", "duration_ms": 2}, {"stage": "execute", "duration_ms": 999}, ], } timing_state = { "inputMode": "speech", "speechStartedAt": 0, "speechResultAt": 80, "speechTimings": { "audio_read_ms": 1, "vad_ms": 2, "asr_ms": 30, "assembly_ms": 1, "semantic_ms": 3, "command_resolution_ms": 1, "recovery_ms": 0, "total_ms": 37, }, "requestStartedAt": 100, "validatedAt": 125, "displayedAt": 126, "renderDurationMs": 1, } browser_projection = { "provider_used": "local_rules+infrared_local_rules", "execution_mode": "dry_run", "validated_control_commands": [ { "operation": sample["function_configs"]["native_climate"]["spec"][ "operations" ][0], "domain": "climate", "channel": "CAN1", "actual_command": "ac temp driver 23", "frames": [ { "label": "CAN1 帧", "value": "0x1DE · 00 00 00 00 17 00 00 00", } ], }, { "operation": sample["function_configs"]["self_extension"]["spec"][ "operations" ][0], "domain": "self_extension", "channel": "IR", "actual_command": "infrared emit 1", "frames": [ { "label": "GSCU1B TCP", "value": '{"action":"emit","data":{"no":1},"type":"infrared"}', } ], }, ], "trace_events": sample["trace_events"][:3], } script = ( f"{helpers}\n" f"const sample = {json.dumps(sample, ensure_ascii=False)};\n" f"const browserProjection = {json.dumps(browser_projection, ensure_ascii=False)};\n" f"const timingState = {json.dumps(timing_state)};\n" "process.stdout.write(JSON.stringify({" "commands: parsedCanCommands(sample), " "projectedCommands: parsedCanCommands(browserProjection), " "timings: parsedCommandTimingRows(sample, timingState)" "}));" ) completed = subprocess.run( [node, "-"], input=script, check=True, capture_output=True, text=True, encoding="utf-8", ) parsed = json.loads(completed.stdout) climate, infrared = parsed["commands"] projected_climate, projected_infrared = parsed["projectedCommands"] assert climate["semantic"] == ( "climate.set_temperature(target=driver, temperature_c=23)" ) assert climate["command"] == "ac temp driver 23" assert climate["frame"] == "0x1DE · 00 00 00 00 17 00 00 00" assert climate["frameLabel"] == "CAN1 帧" assert climate["parseSource"] == "local_rule" assert infrared["semantic"] == ( "infrared.emit(target=toy-001, action=开启, slot=1)" ) assert infrared["command"] == "infrared emit 1" assert infrared["frameLabel"] == "GSCU1B TCP" assert json.loads(infrared["frame"])["data"]["no"] == 1 assert infrared["parseSource"] == "local_rule" assert projected_climate["semantic"] == climate["semantic"] assert projected_climate["command"] == climate["command"] assert projected_climate["frames"] == climate["frames"] assert projected_infrared["semantic"] == infrared["semantic"] assert projected_infrared["command"] == infrared["command"] assert projected_infrared["frames"] == infrared["frames"] timing_by_key = {item["key"]: item for item in parsed["timings"]} assert timing_by_key["local_peripheral_plan-0"]["label"] == "红外本地规则编译" assert timing_by_key["nfc_peripheral_sequence-1"]["label"] == "红外指令序列生成" assert timing_by_key["intent_parse-2"]["label"] == "本地规则意图解析" assert timing_by_key["request_to_validated"]["durationMs"] == 25 assert timing_by_key["dom_render"]["durationMs"] == 1 assert timing_by_key["request_to_display"]["durationMs"] == 26 assert timing_by_key["speech_to_display"]["durationMs"] == 126 assert timing_by_key["total_ms"]["durationMs"] == 37 assert all(item["durationMs"] >= 0 for item in parsed["timings"]) assert not any(item["key"].startswith("execute") for item in parsed["timings"]) def test_duplicate_operation_refs_do_not_borrow_another_configs_plan() -> None: node = shutil.which("node") if node is None: return console = source("console-v2.js") start = console.index(" const normalizedCanTarget") end = console.index("\n\n function renderParsedCanCommands", start) helpers = console[start:end] sample = { "function_configs": { "native_climate": { "spec": { "domain": "climate", "operations": [ { "id": "op-1", "capability": "climate.set_temperature", "target": {"zone": "driver"}, "parameters": {"temperature_c": 23}, } ], } }, "native_seat": { "spec": { "domain": "seat", "operations": [ { "id": "op-1", "capability": "seat.set_heating", "target": {"zone": "passenger"}, "parameters": {"level": 2}, } ], } }, }, "command_plans": { "native_seat": { "spec": { "domain": "seat", "commands": [ { "operation_ref": "op-1", "bus": "seat_can0", "signals": { "debug_command": "seat passenger heat 2", "frame_id": "0x300", "data_hex": "01 02 03 04 05 06 07 08", }, } ], } } }, } script = ( f"{helpers}\n" f"const sample = {json.dumps(sample)};\n" "process.stdout.write(JSON.stringify(parsedCanCommands(sample)));" ) completed = subprocess.run( [node, "-"], input=script, check=True, capture_output=True, text=True, encoding="utf-8", ) parsed = json.loads(completed.stdout) by_semantic = {item["semantic"]: item for item in parsed} climate = by_semantic[ "climate.set_temperature(target=driver, temperature_c=23)" ] seat = by_semantic["seat.set_heating(target=passenger, level=2)"] assert climate["command"] == "ac temp driver 23" assert "frame" not in climate assert climate["channel"] == "CAN1" assert seat["command"] == "seat passenger heat 2" assert seat["channel"] == "CAN0" def test_command_timing_rejects_stale_request_events() -> None: node = shutil.which("node") if node is None: return console = source("console-v2.js") correlation_start = console.index(" function commandCorrelationIds") correlation_end = console.index( "\n\n function rememberValidatedCommandContext", correlation_start, ) correlation_helper = console[correlation_start:correlation_end] start = console.index(" const normalizedCanTarget") end = console.index("\n\n function renderParsedCanCommands", start) helpers = console[start:end] script = ( "let commandTimingState = {requestStartedAt: 1, requestId: '', " "utterance: '空调温度设置到23度,打开闪烁灯'};\n" f"{correlation_helper}\n" f"{helpers}\n" "const first = commandResultMatchesTimedRequest({request_id: 'req-current', " "utterance: '空调温度设置到23度,打开闪烁灯'});\n" "const staleId = commandResultMatchesTimedRequest({request_id: 'req-old', " "utterance: '空调温度设置到23度,打开闪烁灯'});\n" "const staleText = commandResultMatchesTimedRequest({request_id: 'req-current', " "utterance: '打开车窗'});\n" "process.stdout.write(JSON.stringify({first, staleId, staleText, " "boundRequestId: commandTimingState.requestId}));" ) completed = subprocess.run( [node, "-"], input=script, check=True, capture_output=True, text=True, encoding="utf-8", ) assert json.loads(completed.stdout) == { "first": True, "staleId": False, "staleText": False, "boundRequestId": "req-current", } def test_parsed_command_delivery_is_independent_of_transport_and_target() -> None: node = shutil.which("node") if node is None: return console = source("console-v2.js") start = console.index(" const normalizedCanTarget") end = console.index("\n\n function renderParsedCanCommands", start) helpers = console[start:end] base = { "function_configs": { "native_climate": { "spec": { "operations": [ { "id": "climate-power", "capability": "climate.set_power", "target": {"zone": "vehicle"}, "parameters": {"state": "on"}, "parse_source": "local_rule", } ] } } } } blocked_shade = { "function_configs": { "native_roof": { "spec": { "operations": [ { "id": "shade-position", "capability": "roof.set_shade_position", "target": {"zone": "vehicle"}, "parameters": {"position_pct": 30}, "parse_source": "local_rule", } ] } } }, "execution_mode": "hardware", "control_transport": "wireless_mqtt", "execution_report": { "spec": { "results": [ { "ref": "shade-position", "status": "failed", "message": ( "no fresh valid 0x41A shade position; " "refusing blind motion" ), "telemetry": { "verification_status": "failed_or_blocked" }, } ] } }, } samples = [ {**base, "execution_mode": "dry_run", "control_transport": "wired_can"}, { **base, "execution_mode": "dry_run", "control_transport": "wireless_mqtt", }, { **base, "execution_mode": "hardware", "control_transport": "wired_can", "execution_report": { "spec": { "results": [ { "ref": "climate-power", "status": "succeeded", "telemetry": {"sent_frames": [{"frame_id": "0x1DE"}]}, } ] } }, }, { **base, "execution_mode": "hardware", "control_transport": "wireless_mqtt", "execution_report": { "spec": { "results": [ { "ref": "climate-power", "status": "succeeded", "telemetry": {"delivery_status": "queued"}, } ] } }, }, blocked_shade, ] script = ( f"{helpers}\n" f"const samples = {json.dumps(samples)};\n" "process.stdout.write(JSON.stringify(samples.map((sample) => " "parsedCanCommands(sample))));" ) completed = subprocess.run( [node, "-"], input=script, check=True, capture_output=True, text=True, encoding="utf-8", ) parsed = json.loads(completed.stdout) assert [len(commands) for commands in parsed] == [1, 1, 1, 1, 1] assert [commands[0]["command"] for commands in parsed] == [ "ac on", "ac on", "ac on", "ac on", "shade position 30", ] assert [commands[0]["deliveryLabel"] for commands in parsed] == [ "未下发", "未下发", "已下发 · 待状态确认", "已下发 · 待状态确认", "安全阻止", ] assert [commands[0]["deliveryState"] for commands in parsed] == [ "not_submitted", "not_submitted", "submitted", "submitted", "blocked", ] assert "0x41A 实时位置" in parsed[-1][0]["deliveryDetail"] assert [commands[0]["deliveryTransport"] for commands in parsed] == [ "有线 CAN", "无线 CAN", "有线 CAN", "无线 CAN", "无线 CAN", ] def test_delivery_status_distinguishes_blocked_failed_aborted_and_verified() -> None: node = shutil.which("node") if node is None: return console = source("console-v2.js") assert 'class="v2-can-command-delivery-detail"' in console assert "状态说明" in console start = console.index(" const normalizedCanTarget") end = console.index("\n\n function renderParsedCanCommands", start) helpers = console[start:end] def sample( operation: dict, *, status: str, message: str = "", telemetry: dict | None = None, ) -> dict: return { "execution_mode": "hardware", "control_transport": "wireless_mqtt", "function_configs": { "sample": {"spec": {"operations": [operation]}} }, "execution_report": { "spec": { "results": [ { "ref": operation["id"], "status": status, "message": message, "telemetry": telemetry or {}, } ] } }, } samples = [ sample( { "id": "window-blocked", "capability": "window.set_position", "target": {"zone": "front-right"}, "parameters": {"position_pct": 20}, }, status="failed", message="未收到有效且新鲜的 0x388 车窗位置,拒绝启动机构", telemetry={"verification_status": "failed_or_blocked"}, ), sample( { "id": "infrared-dispatch-failed", "capability": "infrared.emit", "target": {"device": "reading-light", "name": "阅读灯"}, "parameters": {"action": "开启", "slot": 2}, }, status="failed", message="GSCU1B 尚未连接到本地 TCP 桥接", telemetry={"delivery_status": "failed"}, ), sample( { "id": "infrared-aborted", "capability": "infrared.emit", "target": {"device": "reading-light", "name": "阅读灯"}, "parameters": {"action": "切换为暖光", "slot": 5}, }, status="aborted", message="前序红外指令失败,未执行", telemetry={"delivery_status": "not_submitted"}, ), sample( { "id": "climate-verified", "capability": "climate.set_power", "target": {"zone": "vehicle"}, "parameters": {"state": "on"}, }, status="succeeded", telemetry={ "delivery_status": "delivered", "physical_effect_confirmed": True, }, ), sample( { "id": "climate-post-submit-failed", "capability": "climate.set_power", "target": {"zone": "vehicle"}, "parameters": {"state": "on"}, }, status="failed", message="网关提交后执行失败", telemetry={"delivery_status": "submitted"}, ), ] script = ( f"{helpers}\n" f"const samples = {json.dumps(samples, ensure_ascii=False)};\n" "process.stdout.write(JSON.stringify(samples.map((sample) => " "parsedCanCommands(sample)[0])));" ) completed = subprocess.run( [node, "-"], input=script, check=True, capture_output=True, text=True, encoding="utf-8", ) commands = json.loads(completed.stdout) assert [item["deliveryLabel"] for item in commands] == [ "安全阻止", "下发失败", "已中止", "执行已确认", "执行失败", ] assert [item["deliveryState"] for item in commands] == [ "blocked", "failed", "aborted", "submitted", "failed", ] assert "0x388 实时位置" in commands[0]["deliveryDetail"] assert commands[1]["deliveryDetail"] == "GSCU1B 尚未连接到本地 TCP 桥接" def test_mode_editor_renders_readable_commands_with_expandable_mqtt_sequences() -> None: app = source("app.js") console = source("console-v2.js") styles = source("styles.css") html = source("index.html") assert 'class="mode-editor-flow-preview empty"' in html assert "function modeEditorReadableCommands(flow)" in app assert "function modeEditorMessagesForOperation(flow, readable, operationRef, plan)" in app assert 'command?.signals?.special_handler === "driver_seat_memory_recall"' in app assert "Array.isArray(special?.frame_sequence)" in app assert "function modeEditorAppendCommandCard(preview, item, flow)" in app assert 'card.className = "mode-editor-command-card"' in app assert 'title.textContent = flow?.control_transport === "wireless_mqtt" ? "MQTT 消息序列"' in app assert 'source: "published"' in app assert 'source: flow?.control_transport === "wireless_mqtt" ? "projected" : "can"' in app assert "function_configs: functionConfigs" in app assert "execution_steps: []" in app assert "requestNaturalLanguageControl(instruction" in app assert 'executionMode: "dry_run"' in app assert 'clientType: "mode_editor_compile"' in app assert "previewOnly: true" in app compile_flow = app.split( "async function executeModeEditorInstruction", 1 )[1].split("async function completeModeEditor", 1)[0] assert "renderResult(" not in compile_flow assert "cognitive-agent:validated-can-commands" not in compile_flow assert "cognitive-agent:result" not in compile_flow assert 'const nonDispatchTaskSources = new Set(["mode_editor_compile"])' in console assert "if (isDispatchTask(task))" in console assert "下发控制指令" in console assert "已解析控制指令" not in console assert 'async function executeSavedModeProfile(mode, { command = "" } = {})' in app assert "/api/mode-profiles/${encodeURIComponent(mode.id)}/execute" in app assert "assistant?.announceModeExecution?.(mode, payload" in app assert "void executeSavedModeProfile(mode, { command: modeCommand });" in app assert "announceModeExecution" in source("assistant-chat.js") assert 'const savedModeExecution = result.orchestration_route === "saved_mode_command_flow";' in app assert "`${result.mode_profile.name}执行完成`" in app assert "生成指令" in html assert ">执行" not in html assert 'id="hardwareNote"' not in html assert 'id="hardwareAliasNote"' not in html assert 'id="vehicleTransportNote"' not in html assert ".mode-editor-command-card" in styles assert ".mode-editor-command-meta" in styles assert ".mode-editor-message-sequence" in styles assert '.mode-editor-command-card[data-can-channel="CAN0"]' in styles def test_mode_editor_prioritizes_can_flow_display_area() -> None: app = source("app.js") html = source("index.html") styles = source("styles.css") assert 'class="mode-editor-setup"' in html assert 'class="mode-editor-inputs"' in html assert "grid-template-rows: minmax(0, 30%) minmax(0, 1fr) auto auto;" in styles assert "grid-template-columns: minmax(150px, 0.32fr) minmax(0, 1fr);" in styles assert ".mode-editor-instruction-field textarea" in styles assert "resize: none;" in styles assert ".mode-editor-flow-preview" in styles assert "overflow: auto;" in styles assert 'id="modeEditorFlowExpand"' in html assert ".mode-editor.flow-expanded" in styles assert ".mode-editor-backdrop.flow-expanded" in styles assert "width: 100vw;" in styles assert "height: 100dvh;" in styles assert "grid-template-rows: minmax(0, 1fr);" in styles assert "touch-action: pan-y;" in styles assert "scrollbar-gutter: stable;" in styles assert "max-height: 220px;" not in styles assert "function setModeEditorFlowExpanded(expanded)" in app assert '$("#modeEditorFlowExpand").addEventListener("click", toggleModeEditorFlowExpanded);' in app def test_mode_editor_prepares_one_to_many_mqtt_message_sequences() -> None: node = shutil.which("node") if node is None: return app = source("app.js") console = source("console-v2.js") console_start = console.index(" const normalizedCanTarget") console_end = console.index("\n\n function renderParsedCanCommands", console_start) console_helpers = console[console_start:console_end] app_start = app.index("function modeEditorCommandCategory") app_end = app.index("\n\nfunction modeEditorAppendCommandCard", app_start) app_helpers = app[app_start:app_end] sample = { "control_transport": "wireless_mqtt", "function_configs": { "native_climate": { "spec": { "operations": [ { "id": "temperature", "capability": "climate.set_temperature", "target": {"zone": "driver"}, "parameters": {"temperature_c": 25}, } ] } } }, "plans": { "native_climate": { "spec": { "commands": [ { "operation_ref": "temperature", "signals": { "frame_id": "0x1DE", "data_hex": "00 00 00 00 19 00 00 00", "is_fd": False, "bitrate_switch": False, "debug_command": "ac temp driver 25", }, "send_offset_ms": 20, "repeat": {"count": 3, "period_ms": 100}, } ] } } }, "execution_steps": [], } script = ( f"{console_helpers}\n" "const state = {runtime:{vehicle_mqtt:{control_topic:" '"n7/n7-headunit/can/command",qos:1,feedback_timeout_ms:1500}}};\n' "const window = {CognitiveAgentConsoleV2:{getParsedCanCommands:" "(result)=>parsedCanCommands(result)}};\n" f"{app_helpers}\n" f"const flow = {json.dumps(sample)};\n" "process.stdout.write(JSON.stringify(modeEditorReadableCommands(flow)));" ) completed = subprocess.run( [node, "-"], input=script, check=True, capture_output=True, text=True, encoding="utf-8", ) commands = json.loads(completed.stdout) assert len(commands) == 1 assert commands[0]["channel"] == "CAN1" assert commands[0]["category"] == "空调温度" assert commands[0]["command"] == "ac temp driver 25" assert [message["offset_ms"] for message in commands[0]["messages"]] == [ 20, 120, 220, ] assert all( message["topic"] == "n7/n7-headunit/can/command" and message["payload"]["canId"] == "0x1DE" and message["payload"]["command"] == "ac temp driver 25" for message in commands[0]["messages"] ) def test_left_sidebar_extracts_speech_and_peripheral_control_commands() -> None: node = shutil.which("node") if node is None: return console = source("console-v2.js") helper_start = console.index(" const normalizedCanTarget") helper_end = console.index("\n\n function renderParsedCanCommands", helper_start) helpers = console[helper_start:helper_end] sample = { "execution_mode": "dry_run", "function_configs": { "native_climate": { "spec": { "operations": [ { "id": "speech-temperature", "capability": "climate.set_temperature", "target": {"device": "hvac", "zone": "driver"}, "parameters": {"temperature_c": 24}, "parse_source": "speech_semantic", } ] } }, "self_extension": { "spec": { "operations": [ { "id": "speech-peripheral", "capability": "infrared.emit", "target": { "device": "third-party-light", "name": "第三方氛围灯", }, "parameters": {"action": "开启", "slot": 9}, "parse_source": "local_rule", } ] } }, }, } script = ( f"{helpers}\n" f"const result = {json.dumps(sample, ensure_ascii=False)};\n" "process.stdout.write(JSON.stringify({" "commands:parsedCanCommands(result)," 'speechLabel:parsedCommandSourceLabel("speech_semantic")' "}));" ) completed = subprocess.run( [node, "-"], input=script, check=True, capture_output=True, text=True, encoding="utf-8", ) output = json.loads(completed.stdout) assert output["speechLabel"] == "语音语义" assert len(output["commands"]) == 2 assert {item["channel"] for item in output["commands"]} == {"CAN1", "IR"} assert {item["command"] for item in output["commands"]} == { "ac temp driver 24", "infrared emit 9", } def test_interactive_console_uses_one_top_right_icon_dock_for_all_five_panels() -> None: console = source("console-v2.js") assistant = source("assistant-chat.js") energy = source("energy-monitor.js") styles = source("console-v2.css") extension = source("self-extension.js") assert 'const resetButton = $("#newConversation", originalForm);' in console assert 'const runButton = $("#runButton", originalForm);' in console assert "if (resetButton) composer.append(resetButton);" in console assert "if (runButton) composer.append(runButton);" in console assert ".v2-request-form .command-composer" in styles assert "overflow: hidden;" in styles assert "transform: translateY(-50%);" in styles assert console.index('id="v2DevicePanel"') < console.index('id="v2ExtensionPanel"') assert 'class="v2-side-status-stack"' in console assert 'class="v2-panel-control-dock"' in console assert "function buildModuleSwitcher(mainPanel, modeRail)" in console assert 'const modeRailBar = $(".mode-rail-bar", modeRail);' in console assert "if (modeRailBar) modeRailBar.prepend(switcher);" in console assert 'const homepageHeader = moduleSwitcher.closest(".v2-mode-rail") || moduleSwitcher;' in extension assert 'homepageHeader.insertAdjacentElement("afterend", root);' in extension assert 'class="v2-unified-brand"' not in console assert "网页控制链路" not in console assert '
' not in console assert 'panelDock.append(sidebarToggle)' in console assert 'panelDock.append(deviceToggle, extensionToggle)' in console assert 'class="v2-side-panel-dock"' not in console assert 'deviceToggle.dataset.panelToggle = "devices"' in console assert 'extensionToggle.dataset.panelToggle = "extensions"' in console assert 'data-lucide="car-front"' in console assert 'data-lucide="usb"' in console assert 'data-lucide="bot-message-square"' in assistant assert 'const panelDock = $("#v2PanelControlDock")' in assistant assert "panelDock.insertBefore(trigger, firstRightPanelToggle)" in assistant assert 'trigger.className = "assistant-drawer-toggle v2-panel-control-button"' in assistant assert 'trigger.className = "energy-monitor-toggle v2-panel-control-button"' in energy assert '$("#v2PanelControlDock").append(trigger)' in energy assert 'trigger.setAttribute("aria-controls", "energyMonitorLayer")' in energy assert "panel.hidden = !visible;" in console assert 'rightRail.hidden = collapsed;' in console assert 'rightRail.toggleAttribute("inert", collapsed)' in console assert "data-panel-toggle" not in extension assert 'setSidePanelVisibility("devices", false)' in console assert 'setSidePanelVisibility("extensions", false)' in console assert "setDrawerOpen(true)" in assistant assert ".v2-panel-control-dock" in styles assert "#v2PanelControlDock > .v2-panel-control-button" in styles assert '.v2-panel-control-button[aria-pressed="true"]' in styles assert "background: var(--primary);" in styles assert "width: 38px !important;" in styles assert "Unified homepage header: panel controls and function modes share one row." in styles assert "grid-template-columns: auto auto minmax(0, 1fr) auto;" in styles final_styles = styles.split("/* AI assistant light-blue final override", 1)[1] assert 'data-open-panels="none"' in final_styles assert "grid-template-columns: minmax(0, 1fr);" in final_styles assert ".v2-unified-right-rail[hidden]" in final_styles def test_runtime_hardware_coverage_defines_the_linked_readiness_state() -> None: app = source("app.js") definition = "const linkedHardwareReady = seatHardwareReady && vehicleHardwareReady;" assert definition in app assert app.index(definition) < app.index(": linkedHardwareReady") def test_left_sync_rail_can_collapse_and_log_aligns_with_lower_workspace() -> None: console = source("console-v2.js") styles = source("console-v2.css") assert 'make("section", "v2-sync-sidebar"' in console assert '
' in console assert "syncOverview?.append(sidebarFooter);" in console assert '$(".v2-sync-sidebar-heading", syncSidebar)?.append(sidebarToggle);' not in console assert 'commandPanel.id = "v2SidebarPanel"' in console assert 'sidebarToggle.setAttribute("aria-controls", commandPanel.id)' in console assert "sidebarCollapsed: true" in console assert 'sidebar.toggleAttribute("inert", ui.sidebarCollapsed)' in console assert 'button.setAttribute("aria-pressed", String(expanded))' in console assert "function syncSidebarLogBaseline()" in console assert "if (!sidebar || !lowerPanel || ui.sidebarCollapsed) return;" in console assert "const requiredOverviewHeight = overviewRect" in console assert "overviewBottom - overviewRect.top + overviewPaddingBottom + 10" in console assert 'sidebar.style.setProperty("--v2-sync-log-top"' in console assert 'window.addEventListener("resize", syncSidebarLogBaseline);' in console assert "requestAnimationFrame(syncSidebarLogBaseline);" in console assert "--v2-sync-log-top: 291px;" in styles assert "grid-template-rows: calc(var(--v2-sync-log-top) - 10px) minmax(0, 1fr);" in styles assert "grid-auto-rows: max-content;" in styles assert "align-content: start;" in styles assert "min-height: max-content;" in styles assert "align-self: start;" in styles assert "overflow: visible;" in styles assert ".v2-sync-sidebar > .v2-sync-log" in styles final_styles = styles.split("/* AI assistant light-blue final override", 1)[1] assert "grid-template-columns: 0 minmax(0, 1fr);" in final_styles assert ".command-panel.v2-sidebar" in final_styles assert "visibility: hidden;" in final_styles def test_twin_device_status_popover_has_clear_close_paths() -> None: console = source("console-v2.js") styles = source("console-v2.css") assert 'data-twin-popover-close aria-label="关闭${escapeHtml(device.name)}状态详情"' in console assert 'popover.setAttribute("role", "dialog");' in console assert 'popover.setAttribute("aria-label", "车端设备状态详情");' in console assert 'event.target.closest("[data-twin-popover-close]")' in console assert 'event.target.closest("[data-twin-device], #v2TwinDevicePopover")' in console assert "const samePinned = displaySync.pinnedDevice === focus.dataset.deviceFocus;" in console assert "clearTwinDevicePreview({ force: true });" in console assert ".v2-twin-popover-close" in styles assert ".v2-twin-popover-close:focus-visible" in styles def test_twin_device_hotspots_use_responsive_hover_and_leader_labels() -> None: app = source("app.js") console = source("console-v2.js") styles = source("console-v2.css") assert "previewClearTimer: null" in console assert "hoverSuppressedUntil: 0" in console assert 'class="v2-twin-hotspot-label"' in console assert 'button.addEventListener("pointerenter"' in console assert 'button.addEventListener("pointerleave"' in console assert "scheduleTwinDevicePreviewClear();" in console assert "}, 45);" in console assert "performance.now() + 160" in console assert 'hotspots.addEventListener("pointerover"' not in console assert 'hotspots.addEventListener("pointerout"' not in console assert ".v2-twin-hotspots button::before" in styles assert ".v2-twin-hotspots button::after" in styles assert ".v2-twin-hotspot-label" in styles assert "width: 94px;" in styles assert "max-width: 94px;" in styles assert "box-sizing: border-box;" in styles assert "text-overflow: ellipsis;" in styles assert "function twinDeviceWorldAnchor(" in app assert "function positionTwinDeviceHotspots()" in app assert "projectedHotspot.copy(hotspotWorldAnchor).project(camera)" in app assert 'button.dataset.anchorSource = "projected-3d"' in app assert 'hotspotLayer.dataset.anchorLayout = "projected-3d"' in app assert "clampLabelCoordinate(" in app assert "positionTwinDeviceHotspots();" in app assert "function positionTwinDevicePopover(" in console assert "function twinPopoverIntersectionArea(" in console assert 'Object.keys(availableSpace).sort' in console assert 'popover.dataset.anchorDevice = deviceId' in console assert 'popover.dataset.placement = best.placement' in console assert 'popover.dataset.shadeOverlap = String(Math.round(best.shadeOverlap))' in console assert 'popover.dataset.labelOverlap = String(Math.round(best.labelOverlap))' in console assert 'popover.dataset.layoutSignature === geometrySignature' in console assert 'window.CognitiveAgentConsoleV2?.positionTwinDevicePopover?.();' in app popover_styles = styles.split(".console-v2 .v2-twin-device-popover {", 1)[1].split("}", 1)[0] assert "top: 52px;" not in popover_styles assert "left: 50%;" not in popover_styles assert "transform: translateX(-50%);" not in popover_styles assert 'width: min(280px, calc(100% - 24px));' in styles assert 'repeat(auto-fit, minmax(44px, 1fr))' in styles for device_id in ( "windows", "shade", "driver", "passenger", "reading", "ambient", "headlight", "climate", ): assert f"{device_id}: {{ normalized:" in app assert '.v2-twin-device-popover[data-pinned="false"]' in styles assert "pointer-events: none;" in styles def test_vehicle_twin_keeps_whole_car_centered_and_removes_axis_callouts() -> None: html = source("index.html") app = source("app.js") styles = source("console-v2.css") assert 'twin.view.targetX = 0;' in app assert 'twin.view.yaw = -0.62;' in app assert 'canvas.dataset.framing = "whole-vehicle-centered"' in app assert "1.28 / Math.max(0.64, camera.aspect)" in app assert 'class="twin-callout-layer"' not in html assert 'class="twin-callout' not in html assert "data-callout=" not in html assert "function positionTwinCallouts()" not in app assert "renderTwinCalloutValues" not in app assert ".joint-twin-shell > .twin-band" in styles assert "container-type: inline-size;" in styles assert "@container (max-width: 420px)" in styles assert "grid-template-columns: minmax(0, 1fr);" in styles def test_validated_can_commands_survive_same_task_running_poll_and_keep_error_detail() -> None: console = source("console-v2.js") app = source("app.js") assert "function validatedCommandsBelongToTask(task = {})" in console assert "if (!validatedCommandsBelongToTask(task))" in console assert "A stale" in console assert "must not erase those commands" in console assert "rememberValidatedCommandContext(result);" in console assert "resetValidatedCommandContext();" in console assert "error.detail || error.message" in console assert "failed_or_blocked: \"失败或已阻止\"" in console assert "requestError.outcome = phaseEvent.outcome;" in app assert "requestError.detail = phaseEvent.detail;" in app def test_compact_mode_strip_supports_wheel_drag_and_keyboard_navigation() -> None: app = source("app.js") styles = source("console-v2.css") scroller = app.split("function installModeChipScroller()", 1)[1].split( "function populateModeChips", 1 )[0] assert 'group.dataset.modeScrollerBound = "true"' in scroller assert "group.tabIndex = 0" in scroller assert "滚轮、拖动或方向键横向浏览" in scroller assert 'group.addEventListener("wheel"' in scroller assert "Math.abs(event.deltaX) > Math.abs(event.deltaY)" in scroller assert "scrollBy(dominantDelta * multiplier)" in scroller assert "event.preventDefault()" in scroller assert "{ passive: false }" in scroller assert 'group.addEventListener("pointerdown"' in scroller assert 'event.pointerType !== "mouse"' in scroller assert "group.setPointerCapture(pointerId)" in scroller assert scroller.index("group.setPointerCapture(pointerId)") > scroller.index( "Math.abs(distance) < 5" ) assert "let pointerCaptured = false;" in scroller assert 'group.addEventListener("pointermove"' in scroller assert "Math.abs(distance) < 5" in scroller assert 'group.classList.add("is-dragging")' in scroller assert 'group.addEventListener("pointerup", finishPointerDrag)' in scroller assert 'group.addEventListener("pointercancel", finishPointerDrag)' in scroller assert "event.stopImmediatePropagation()" in scroller assert 'group.addEventListener("keydown"' in scroller for key in ("ArrowLeft", "ArrowRight", "PageUp", "PageDown", "Home", "End"): assert f'event.key === "{key}"' in scroller assert "new MutationObserver" in scroller assert 'new ResizeObserver(updateScrollState).observe(group)' in scroller assert "installModeChipScroller();" in app mode_strip_styles = styles.split("/* Compact function-mode strip", 1)[1] assert "overflow-x: auto" in mode_strip_styles assert "overflow-y: hidden" in mode_strip_styles assert "touch-action: pan-x" in mode_strip_styles assert "cursor: grab" in mode_strip_styles assert ".mode-chips:focus-visible" in mode_strip_styles assert ".mode-chips.is-dragging" in mode_strip_styles assert "cursor: grabbing" in mode_strip_styles def test_assistant_mode_preview_groups_validated_actions_for_the_card() -> None: node = shutil.which("node") if node is None: return assistant = source("assistant-chat.js") helper_start = assistant.index(" function modeDraftRows") helper_end = assistant.index("\n\n async function saveModeProfileDraft", helper_start) helper = assistant[helper_start:helper_end] draft = { "name": "观影模式", "instruction": "遮阳帘关闭,车窗关闭,副驾座椅调整为零重力模式,灯光全部关闭", } actions = [ { "domain": "roof", "capability": "roof.set_shade_motion", "target": {"zone": "vehicle"}, "parameters": {"motion": "close"}, }, { "domain": "window", "capability": "window.set_motion", "target": {"zone": "all"}, "parameters": {"motion": "close"}, }, { "domain": "seat", "capability": "seat.set_longitudinal_position", "target": {"zone": "front_right", "instance": "passenger"}, "parameters": {"position_pct": 100}, }, { "domain": "seat", "capability": "seat.set_backrest_recline", "target": {"zone": "front_right", "instance": "passenger"}, "parameters": {"position_pct": 81, "angle_deg": 150}, }, { "domain": "interior_light", "capability": "interior_light.set_indoor_power", "target": {"zone": "vehicle"}, "parameters": {"state": "off"}, }, { "domain": "ambient_light", "capability": "ambient_light.set_power", "target": {"zone": "vehicle"}, "parameters": {"state": "off"}, }, ] script = ( f"{helper}\n" f"const draft = {json.dumps(draft, ensure_ascii=False)};\n" f"const actions = {json.dumps(actions, ensure_ascii=False)};\n" "process.stdout.write(JSON.stringify(modeDraftRows(draft, actions)));" ) completed = subprocess.run( [node, "-"], input=script, check=True, capture_output=True, text=True, encoding="utf-8", ) assert json.loads(completed.stdout) == [ {"label": "遮阳帘", "value": "关闭", "icon": "blinds", "order": 0}, {"label": "全部车窗", "value": "关闭", "icon": "panels-top-left", "order": 6}, {"label": "副驾座椅", "value": "零重力模式", "icon": "armchair", "order": 11}, {"label": "座舱灯光", "value": "全部关闭", "icon": "lightbulb-off", "order": 24}, ]