优化后端
This commit is contained in:
@@ -309,6 +309,30 @@ test("assistant bridges request start and validated local controls to the consol
|
||||
value: "0x1DE · 00 00 00 00 17 00 00 00",
|
||||
}],
|
||||
},
|
||||
{
|
||||
operation: {
|
||||
id: "reading-light-1",
|
||||
capability: "interior_light.set_reading_power",
|
||||
target: { instance: "all" },
|
||||
parameters: { state: "off" },
|
||||
parse_source: "local_rule",
|
||||
},
|
||||
channel: "CAN1",
|
||||
actual_command: "light reading all off",
|
||||
frames: [],
|
||||
},
|
||||
{
|
||||
operation: {
|
||||
id: "roof-shade-1",
|
||||
capability: "roof.set_shade_motion",
|
||||
target: { instance: "shade" },
|
||||
parameters: { motion: "close" },
|
||||
parse_source: "local_rule",
|
||||
},
|
||||
channel: "CAN1",
|
||||
actual_command: "shade close",
|
||||
frames: [],
|
||||
},
|
||||
],
|
||||
trace_events: [{ stage: "compile", duration_ms: 2 }],
|
||||
};
|
||||
@@ -338,6 +362,25 @@ test("assistant bridges request start and validated local controls to the consol
|
||||
assert.deepEqual(JSON.parse(JSON.stringify(validated)), [payload]);
|
||||
assert.equal(harness.requestCalls.length, 1);
|
||||
assert.equal(JSON.parse(harness.requestCalls[0].options.body).execution_mode, "dry_run");
|
||||
|
||||
const assistantArticle = harness.messages.children[1];
|
||||
const cards = assistantArticle.querySelectorAll(".assistant-dispatched-controls");
|
||||
assert.equal(cards.length, 1);
|
||||
assert.equal(cards[0].open, true);
|
||||
assert.equal(
|
||||
cards[0].querySelector(".assistant-dispatched-controls-count").textContent,
|
||||
"3",
|
||||
);
|
||||
const rows = cards[0].querySelector(".assistant-dispatched-controls-list").children;
|
||||
assert.deepEqual(
|
||||
rows.map((row) => row.querySelector(".assistant-dispatched-control-label").textContent),
|
||||
["空调温度", "阅读灯", "遮阳帘"],
|
||||
);
|
||||
assert.deepEqual(
|
||||
rows.map((row) => row.querySelector(".assistant-dispatched-control-value").textContent),
|
||||
["23°C", "关闭", "关闭"],
|
||||
);
|
||||
assert.match(rows[0].title, /实际控制指令:ac temp driver 23/);
|
||||
});
|
||||
|
||||
test("unsupported-control suggestions render once and click through as a new command", async () => {
|
||||
|
||||
@@ -13,15 +13,15 @@ def test_console_contains_ai_assistant_surface():
|
||||
assert 'aria-label="AI助手对话"' in html
|
||||
assert 'placeholder="向 AI助手提问或交代工作"' in html
|
||||
assert 'aria-label="发送消息"' in html
|
||||
assert 'href="/assistant-chat.css?v=codex-assistant-26"' in html
|
||||
assert 'src="/assistant-chat.js?v=codex-assistant-27"' in html
|
||||
assert 'href="/assistant-chat.css?v=codex-assistant-28"' in html
|
||||
assert 'src="/assistant-chat.js?v=codex-assistant-32"' 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-v282"' in service_worker
|
||||
assert '"/assistant-chat.css?v=codex-assistant-26"' in service_worker
|
||||
assert '"/assistant-chat.js?v=codex-assistant-27"' in service_worker
|
||||
assert 'const CACHE_VERSION = "cognitive-agent-pad-v290"' in service_worker
|
||||
assert '"/assistant-chat.css?v=codex-assistant-28"' in service_worker
|
||||
assert '"/assistant-chat.js?v=codex-assistant-32"' in service_worker
|
||||
|
||||
|
||||
def test_codex_frontend_uses_one_streaming_endpoint_and_agent_work_events():
|
||||
@@ -73,6 +73,31 @@ def test_unsupported_control_suggestions_are_clickable_accessible_and_deduplicat
|
||||
assert ".assistant-control-suggestion.is-sending" in css
|
||||
|
||||
|
||||
def test_validated_controls_are_mirrored_into_a_compact_assistant_card():
|
||||
script = (ROOT / "apps/web-console/assistant-chat.js").read_text(encoding="utf-8")
|
||||
css = (ROOT / "apps/web-console/assistant-chat.css").read_text(encoding="utf-8")
|
||||
|
||||
assert "function appendDispatchedControls(message, result = {})" in script
|
||||
assert "window.CognitiveAgentConsoleV2?.getParsedCanCommands?.(result)" in script
|
||||
assert "fallbackAssistantControlCommands(result)" in script
|
||||
assert 'card.className = "assistant-dispatched-controls"' in script
|
||||
assert 'heading.textContent = "下发控制指令"' in script
|
||||
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 "语义控制指令:" in script
|
||||
assert "实际控制指令:" in script
|
||||
assert "appendDispatchedControls(message, event.payload)" in script
|
||||
assert "function announceModeExecution(" in script
|
||||
assert "announceModeExecution," in script
|
||||
assert '"ambient_light.set_illumination": "氛围灯颜色与亮度"' in script
|
||||
assert '.replaceAll("_", "-")' in script
|
||||
assert ".assistant-dispatched-controls > summary" in css
|
||||
assert ".assistant-dispatched-controls-list > li" in css
|
||||
assert ".assistant-dispatched-control-value" in css
|
||||
assert ".assistant-dispatched-controls[open]" in css
|
||||
|
||||
|
||||
def test_codex_request_keeps_trusted_vehicle_context_without_legacy_model_routing():
|
||||
script = (ROOT / "apps/web-console/assistant-chat.js").read_text(encoding="utf-8")
|
||||
request_body = script.split("function requestBody(message)", 1)[1].split(
|
||||
|
||||
@@ -35,10 +35,10 @@ def test_energy_monitor_assets_are_versioned_and_cached() -> None:
|
||||
):
|
||||
assert asset in html
|
||||
assert f'"{asset}"' in service_worker
|
||||
assert html.index("/assistant-chat.js?v=codex-assistant-27") < html.index(
|
||||
assert html.index("/assistant-chat.js?v=codex-assistant-32") < html.index(
|
||||
"/energy-monitor.js?v=energy-monitor-4"
|
||||
)
|
||||
assert 'const CACHE_VERSION = "cognitive-agent-pad-v282"' in service_worker
|
||||
assert 'const CACHE_VERSION = "cognitive-agent-pad-v290"' in service_worker
|
||||
|
||||
|
||||
def test_energy_monitor_uses_the_fifth_shared_dock_icon_and_starts_closed() -> None:
|
||||
|
||||
@@ -41,13 +41,13 @@ def test_theme_asset_versions_are_consistent_with_the_pwa_shell() -> None:
|
||||
|
||||
for asset in (
|
||||
"/styles.css?v=dual-seat-23",
|
||||
"/console-v2.css?v=delivery-state-68",
|
||||
"/console-v2.css?v=delivery-state-69",
|
||||
"/self-extension.css?v=gscu-tcp-43",
|
||||
"/speech-input.css?v=sensevoice-7",
|
||||
):
|
||||
assert asset in html
|
||||
assert f'"{asset}"' in service_worker
|
||||
assert 'const CACHE_VERSION = "cognitive-agent-pad-v282"' in service_worker
|
||||
assert 'const CACHE_VERSION = "cognitive-agent-pad-v290"' in service_worker
|
||||
|
||||
|
||||
def test_vehicle_twin_icons_and_nested_overlays_use_semantic_tokens() -> None:
|
||||
|
||||
@@ -259,6 +259,39 @@ class FrontendVehicleStateSyncContractTests(unittest.TestCase):
|
||||
)
|
||||
self.assertNotIn("passenger: mergedActuatorState.driver", render_source)
|
||||
|
||||
def test_state_polling_surfaces_structured_403_reason(self) -> None:
|
||||
passenger_source = function_source(
|
||||
self.app_source,
|
||||
"async function synchronizePassengerSeatState() {",
|
||||
"\nasync function synchronizeDriverSeatState()",
|
||||
)
|
||||
driver_source = function_source(
|
||||
self.app_source,
|
||||
"async function synchronizeDriverSeatState() {",
|
||||
"\nasync function synchronizePhysicalSeatState()",
|
||||
)
|
||||
live_status_source = function_source(
|
||||
self.app_source,
|
||||
"function renderCabinLiveStatus(",
|
||||
"\nfunction refreshIcons",
|
||||
)
|
||||
telemetry_source = function_source(
|
||||
self.app_source,
|
||||
"function recordVehicleStateTelemetry(payload) {",
|
||||
"\nfunction applyAuthoritativeVehicleState",
|
||||
)
|
||||
|
||||
self.assertIn("throw await apiResponseError(response)", passenger_source)
|
||||
self.assertIn("throw await apiResponseError(response)", driver_source)
|
||||
self.assertIn("errorStatus: Number(error.status) || null", driver_source)
|
||||
self.assertIn("errorReason: error.serverMessage", driver_source)
|
||||
self.assertIn("snapshot?.errorStatus === 403", live_status_source)
|
||||
self.assertIn("`${transportLabel} · 403:${reason}`", live_status_source)
|
||||
self.assertIn('telemetryState.textContent = "403 拒绝"', live_status_source)
|
||||
self.assertIn('setSummarySystem("403 · 状态读取被拒绝", "warning")', live_status_source)
|
||||
self.assertIn("errorStatus: null", telemetry_source)
|
||||
self.assertIn('errorReason: ""', telemetry_source)
|
||||
|
||||
def test_persistent_linux_monitor_uses_backend_selected_fast_polling(self) -> None:
|
||||
interval_source = function_source(
|
||||
self.app_source,
|
||||
|
||||
@@ -250,7 +250,32 @@ def test_top_mode_action_formatter_uses_device_target_action_and_parameter_value
|
||||
}
|
||||
|
||||
|
||||
def test_mode_cards_open_assistant_and_submit_enter_mode_command() -> None:
|
||||
def test_mode_chip_scroller_keeps_clicks_and_only_captures_real_drags() -> None:
|
||||
app = (WEB / "app.js").read_text(encoding="utf-8")
|
||||
start = app.index("function installModeChipScroller")
|
||||
end = app.index("\n\nfunction ensureModeActionPopover", start)
|
||||
snippet = app[start:end]
|
||||
|
||||
threshold = snippet.index('if (!dragged && Math.abs(distance) < 5) return;')
|
||||
capture = snippet.index("group.setPointerCapture(pointerId);")
|
||||
assert capture > threshold
|
||||
assert "let pointerCaptured = false;" in snippet
|
||||
assert "if (pointerCaptured) group.releasePointerCapture?.(pointerId);" in snippet
|
||||
assert 'event.stopImmediatePropagation();' in snippet
|
||||
|
||||
|
||||
def test_top_mode_triangle_is_an_explicit_execution_target() -> None:
|
||||
app = (WEB / "app.js").read_text(encoding="utf-8")
|
||||
vue_ui = (WEB / "vue-ui.js").read_text(encoding="utf-8")
|
||||
handlers = app[app.index('document.addEventListener("DOMContentLoaded"') :]
|
||||
|
||||
assert '"data-mode-run": "true"' in vue_ui
|
||||
assert 'runIcon.dataset.modeRun = "true";' in app
|
||||
assert 'const runControl = target?.closest("[data-mode-run]");' in handlers
|
||||
assert '(runControl || target)?.closest("button[data-mode-command]")' in handlers
|
||||
|
||||
|
||||
def test_saved_mode_cards_open_assistant_and_execute_the_saved_command_flow() -> None:
|
||||
app = (WEB / "app.js").read_text(encoding="utf-8")
|
||||
start = app.index("function applyModeCommand")
|
||||
end = app.index("\n\nfunction applyRuntimeTargetPolicy", start)
|
||||
@@ -258,12 +283,6 @@ def test_mode_cards_open_assistant_and_submit_enter_mode_command() -> None:
|
||||
script = "const snippet = " + json.dumps(snippet) + ";\n" + r"""
|
||||
const vm = require("node:vm");
|
||||
const calls = [];
|
||||
const input = {
|
||||
value: "",
|
||||
dataset: { speechStage: "final", speechUtteranceId: "old", speechValue: "old" },
|
||||
dispatchEvent(event) { calls.push(["input", event.type, event.bubbles, this.value]); },
|
||||
};
|
||||
const form = { requestSubmit() { calls.push(["submit", input.value]); } };
|
||||
const runButton = { disabled: false };
|
||||
const savedMode = {
|
||||
id: "reading-user",
|
||||
@@ -283,11 +302,14 @@ const context = {
|
||||
},
|
||||
modeProfiles() { return [savedMode]; },
|
||||
setModeRailSelection(mode) { calls.push(["select", mode?.id]); },
|
||||
setModeExecutionState(modeId, status) { calls.push(["state", modeId, status]); },
|
||||
closeModeGallery(options) { calls.push(["close", options.restoreFocus]); },
|
||||
showToast(message) { calls.push(["toast", message]); },
|
||||
executeSavedModeProfile(mode, options) {
|
||||
calls.push(["execute", mode.id, options.command]);
|
||||
return Promise.resolve(true);
|
||||
},
|
||||
$(selector) {
|
||||
return { "#utterance": input, "#requestForm": form, "#runButton": runButton }[selector];
|
||||
return { "#runButton": runButton }[selector];
|
||||
},
|
||||
};
|
||||
vm.createContext(context);
|
||||
@@ -295,8 +317,6 @@ vm.runInContext(snippet + '\nthis.result = applyModeCommand(" 进入阅读模式
|
||||
console.log(JSON.stringify({
|
||||
result: context.result,
|
||||
calls,
|
||||
value: input.value,
|
||||
speechDataset: input.dataset,
|
||||
pendingModeProfileId: context.state.pendingModeProfileId,
|
||||
}));
|
||||
"""
|
||||
@@ -314,14 +334,71 @@ console.log(JSON.stringify({
|
||||
["select", "reading-user"],
|
||||
["close", False],
|
||||
["open"],
|
||||
["input", "input", True, "进入阅读模式"],
|
||||
["state", "reading-user", "running"],
|
||||
["submit", "进入阅读模式"],
|
||||
["execute", "reading-user", "进入阅读模式"],
|
||||
]
|
||||
assert result["value"] == "进入阅读模式"
|
||||
assert result["speechDataset"] == {}
|
||||
assert result["pendingModeProfileId"] == "reading-user"
|
||||
assert "executeSavedModeProfile" not in snippet
|
||||
assert result["pendingModeProfileId"] is None
|
||||
assert "executeSavedModeProfile" in snippet
|
||||
|
||||
|
||||
def test_mode_card_without_saved_flow_falls_back_to_assistant_submission() -> None:
|
||||
app = (WEB / "app.js").read_text(encoding="utf-8")
|
||||
start = app.index("function applyModeCommand")
|
||||
end = app.index("\n\nfunction applyRuntimeTargetPolicy", start)
|
||||
snippet = app[start:end]
|
||||
script = "const snippet = " + json.dumps(snippet) + ";\n" + r"""
|
||||
const vm = require("node:vm");
|
||||
const calls = [];
|
||||
const input = {
|
||||
value: "",
|
||||
dataset: { speechStage: "final", speechUtteranceId: "old", speechValue: "old" },
|
||||
dispatchEvent(event) { calls.push(["input", event.type, event.bubbles, this.value]); },
|
||||
};
|
||||
const form = { requestSubmit() { calls.push(["submit", input.value]); } };
|
||||
const runButton = { disabled: false };
|
||||
const unsavedMode = { id: "future-mode", name: "未来模式" };
|
||||
const context = {
|
||||
state: { pendingModeProfileId: null },
|
||||
Event: class Event {
|
||||
constructor(type, options = {}) { this.type = type; this.bubbles = options.bubbles; }
|
||||
},
|
||||
window: {
|
||||
CognitiveAgentAssistant: {
|
||||
isPending() { return false; },
|
||||
open() { calls.push(["open"]); },
|
||||
},
|
||||
},
|
||||
modeProfiles() { return [unsavedMode]; },
|
||||
setModeRailSelection(mode) { calls.push(["select", mode?.id]); },
|
||||
setModeExecutionState(modeId, status) { calls.push(["state", modeId, status]); },
|
||||
closeModeGallery(options) { calls.push(["close", options.restoreFocus]); },
|
||||
showToast(message) { calls.push(["toast", message]); },
|
||||
$(selector) {
|
||||
return { "#utterance": input, "#requestForm": form, "#runButton": runButton }[selector];
|
||||
},
|
||||
};
|
||||
vm.createContext(context);
|
||||
vm.runInContext(snippet + '\nthis.result = applyModeCommand(" 进入未来模式 ", "future-mode");', context);
|
||||
console.log(JSON.stringify({ result: context.result, calls, value: input.value }));
|
||||
"""
|
||||
completed = subprocess.run(
|
||||
[node_runtime(), "-e", script],
|
||||
check=True,
|
||||
capture_output=True,
|
||||
text=True,
|
||||
encoding="utf-8",
|
||||
)
|
||||
result = json.loads(completed.stdout)
|
||||
|
||||
assert result["result"] is True
|
||||
assert result["calls"] == [
|
||||
["select", "future-mode"],
|
||||
["close", False],
|
||||
["open"],
|
||||
["input", "input", True, "进入未来模式"],
|
||||
["state", "future-mode", "running"],
|
||||
["submit", "进入未来模式"],
|
||||
]
|
||||
assert result["value"] == "进入未来模式"
|
||||
|
||||
|
||||
def test_mode_cards_do_not_submit_while_assistant_is_busy() -> None:
|
||||
|
||||
@@ -741,8 +741,9 @@ class MqttCanTransportTests(unittest.TestCase):
|
||||
by_id["0x41A"]["domain_state"]["roof"]["shade_position_pct"],
|
||||
92,
|
||||
)
|
||||
self.assertIsNone(
|
||||
by_id["0x42E"]["domain_state"]["ambient_light"]["front_color_index"]
|
||||
self.assertEqual(
|
||||
by_id["0x42E"]["domain_state"]["ambient_light"]["front_color_index"],
|
||||
100,
|
||||
)
|
||||
self.assertEqual(
|
||||
by_id["0x3FF"]["domain_state"]["interior_light"]["ambient_illuminance_lux"],
|
||||
|
||||
@@ -100,9 +100,23 @@ class NativeCanFeedbackTests(unittest.TestCase):
|
||||
self.assertFalse(feedback_matches("indoor_light:on", feedback))
|
||||
self.assertTrue(feedback_matches("ambient_power:off", feedback))
|
||||
|
||||
def test_can1_matrix_rejects_reserved_ambient_color_values(self) -> None:
|
||||
def test_n7_profile_accepts_full_seven_bit_ambient_color_index(self) -> None:
|
||||
decoded = decode_feedback(
|
||||
self.frame(0x42E, "11 64 32 03 90 02 00 C6")
|
||||
self.frame(0x42E, "11 C1 20 03 D0 02 00 C6")
|
||||
)["domain_state"]["ambient_light"]
|
||||
|
||||
self.assertEqual(decoded["front_color_index"], 65)
|
||||
self.assertEqual(decoded["rear_color_index"], 65)
|
||||
self.assertTrue(
|
||||
feedback_matches(
|
||||
"ambient_color:65",
|
||||
self.frame(0x42E, "11 C1 20 03 D0 02 00 C6"),
|
||||
)
|
||||
)
|
||||
|
||||
def test_can1_matrix_rejects_zero_ambient_color_index(self) -> None:
|
||||
decoded = decode_feedback(
|
||||
self.frame(0x42E, "11 00 00 03 D0 02 00 C6")
|
||||
)["domain_state"]["ambient_light"]
|
||||
|
||||
self.assertIsNone(decoded["front_color_index"])
|
||||
|
||||
@@ -60,7 +60,7 @@ def test_unified_console_exposes_device_id_based_nfc_authorization() -> None:
|
||||
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=gscu-tcp-43' in html
|
||||
assert 'self-extension.js?v=gscu-tcp-44' in html
|
||||
assert 'self-extension.css?v=gscu-tcp-43' in html
|
||||
|
||||
|
||||
@@ -140,5 +140,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-66" in html
|
||||
assert "console-v2.js?v=local-first-70" in html
|
||||
assert 'speech_semantic: "语音语义"' in console
|
||||
|
||||
@@ -30,7 +30,7 @@ def test_frontend_only_exposes_user_orchestration_and_extension() -> None:
|
||||
assert not (web_root / removed).exists()
|
||||
assert removed not in html
|
||||
|
||||
assert "511工程-智能座舱" in console
|
||||
assert "511工程-智能座舱" not in console
|
||||
assert '["self-orchestration", "self-extension"]' in console
|
||||
assert 'id="vehicleExtensionSync"' in console
|
||||
assert 'getRole: () => "user"' in console
|
||||
@@ -174,6 +174,20 @@ def test_vehicle_device_status_cards_prioritize_seats_and_expose_manual_controls
|
||||
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")
|
||||
@@ -207,7 +221,8 @@ def test_left_sidebar_exposes_collapsed_parsed_can_command_panel() -> None:
|
||||
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 'deliveryLabel: submitted ? "下发" : "未下发"' 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
|
||||
@@ -247,6 +262,9 @@ def test_left_sidebar_exposes_collapsed_parsed_can_command_panel() -> None:
|
||||
"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}",
|
||||
@@ -705,6 +723,42 @@ def test_parsed_command_delivery_is_independent_of_transport_and_target() -> Non
|
||||
}
|
||||
}
|
||||
}
|
||||
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"},
|
||||
{
|
||||
@@ -744,6 +798,7 @@ def test_parsed_command_delivery_is_independent_of_transport_and_target() -> Non
|
||||
}
|
||||
},
|
||||
},
|
||||
blocked_shade,
|
||||
]
|
||||
script = (
|
||||
f"{helpers}\n"
|
||||
@@ -761,19 +816,35 @@ def test_parsed_command_delivery_is_independent_of_transport_and_target() -> Non
|
||||
)
|
||||
|
||||
parsed = json.loads(completed.stdout)
|
||||
assert [len(commands) for commands in parsed] == [1, 1, 1, 1]
|
||||
assert [commands[0]["command"] for commands in parsed] == ["ac on"] * 4
|
||||
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",
|
||||
]
|
||||
|
||||
|
||||
@@ -803,9 +874,11 @@ def test_mode_editor_renders_readable_commands_with_expandable_mqtt_sequences()
|
||||
assert "if (isDispatchTask(task))" in console
|
||||
assert "下发控制指令" in console
|
||||
assert "已解析控制指令" not in console
|
||||
assert "function executeSavedModeProfile(mode)" in app
|
||||
assert 'async function executeSavedModeProfile(mode, { command = "" } = {})' in app
|
||||
assert "/api/mode-profiles/${encodeURIComponent(mode.id)}/execute" in app
|
||||
assert '$("#utterance").value = mode.name || mode.id;' 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
|
||||
|
||||
@@ -1021,6 +1094,14 @@ def test_interactive_console_uses_one_top_right_icon_dock_for_all_five_panels()
|
||||
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 '<div class="v2-sync-sidebar-heading">' 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
|
||||
@@ -1046,12 +1127,22 @@ def test_interactive_console_uses_one_top_right_icon_dock_for_all_five_panels()
|
||||
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")
|
||||
@@ -1210,7 +1301,11 @@ def test_compact_mode_strip_supports_wheel_drag_and_keyboard_navigation() -> Non
|
||||
|
||||
assert 'group.addEventListener("pointerdown"' in scroller
|
||||
assert 'event.pointerType !== "mouse"' in scroller
|
||||
assert "group.setPointerCapture?.(pointerId)" 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
|
||||
|
||||
@@ -0,0 +1,98 @@
|
||||
from pathlib import Path
|
||||
|
||||
|
||||
ROOT = Path(__file__).resolve().parents[2]
|
||||
WEB = ROOT / "apps" / "web-console"
|
||||
|
||||
|
||||
def source(name: str) -> str:
|
||||
return (WEB / name).read_text(encoding="utf-8")
|
||||
|
||||
|
||||
def test_spatial_ui_contract_is_loaded_last_and_cached() -> None:
|
||||
html = source("index.html")
|
||||
service_worker = source("service-worker.js")
|
||||
asset = "/ui-consistency.css?v=spatial-ui-4"
|
||||
|
||||
assert asset in html
|
||||
assert f'"{asset}"' in service_worker
|
||||
assert html.index("/energy-monitor.css") < html.index(asset)
|
||||
|
||||
|
||||
def test_same_kind_surfaces_share_geometry_tokens() -> None:
|
||||
styles = source("ui-consistency.css")
|
||||
|
||||
for token in (
|
||||
"--ui-control-height-compact:",
|
||||
"--ui-control-height:",
|
||||
"--ui-radius-control:",
|
||||
"--ui-radius-item:",
|
||||
"--ui-radius-section:",
|
||||
"--ui-radius-panel:",
|
||||
"--ui-radius-modal:",
|
||||
"--ui-shadow-item:",
|
||||
"--ui-shadow-panel:",
|
||||
"--ui-shadow-modal:",
|
||||
):
|
||||
assert token in styles
|
||||
|
||||
for selector in (
|
||||
".v2-operation-shell",
|
||||
".joint-twin-shell > .twin-band",
|
||||
"#v2DevicePanel",
|
||||
"#v2ExtensionPanel .v2-extension-sync-mount",
|
||||
".mode-gallery",
|
||||
".mode-editor",
|
||||
".energy-monitor-page",
|
||||
".v2-synced-device",
|
||||
".v2-extension-gateway",
|
||||
".mode-gallery-card",
|
||||
".energy-kpi",
|
||||
):
|
||||
assert selector in styles
|
||||
|
||||
|
||||
def test_same_kind_controls_share_sizes_and_focus_treatment() -> None:
|
||||
styles = source("ui-consistency.css")
|
||||
|
||||
for selector in (
|
||||
"#v2PanelControlDock > .v2-panel-control-button",
|
||||
".v2-mode-rail .mode-rail-actions > button",
|
||||
".v2-device-adjust-toggle",
|
||||
".v2-extension-nfc-launch",
|
||||
".energy-trend-tabs button",
|
||||
".mode-gallery-close",
|
||||
".mode-editor-header > button",
|
||||
"#energyMonitorClose",
|
||||
):
|
||||
assert selector in styles
|
||||
|
||||
assert "box-shadow: var(--ui-focus-ring)" in styles
|
||||
assert "outline: none" in styles
|
||||
|
||||
|
||||
def test_compact_sidebar_overrides_cannot_break_shared_geometry() -> None:
|
||||
styles = source("ui-consistency.css")
|
||||
|
||||
assert ".v2-sync-execution-field," in styles
|
||||
assert ".v2-can-command-panel" in styles
|
||||
assert "border-radius: var(--ui-radius-item) !important" in styles
|
||||
assert ".v2-sync-log {" in styles
|
||||
assert "border-radius: var(--ui-radius-section) !important" in styles
|
||||
|
||||
|
||||
def test_assistant_composer_hint_stays_inside_narrow_input() -> None:
|
||||
styles = source("ui-consistency.css")
|
||||
|
||||
assert ".v2-request-form .composer-hint" in styles
|
||||
assert "left: 16px !important" in styles
|
||||
assert "right: auto !important" in styles
|
||||
assert "max-width: calc(100% - 132px)" in styles
|
||||
|
||||
|
||||
def test_failed_delivery_uses_one_semantic_status_treatment() -> None:
|
||||
styles = source("ui-consistency.css")
|
||||
|
||||
assert '[data-delivery-state="blocked"]' in styles
|
||||
assert '[data-delivery-state="failed"]' in styles
|
||||
assert "color: var(--destructive)" in styles
|
||||
@@ -0,0 +1,85 @@
|
||||
from __future__ import annotations
|
||||
|
||||
from pathlib import Path
|
||||
|
||||
|
||||
ROOT = Path(__file__).resolve().parents[2]
|
||||
WEB = ROOT / "apps" / "web-console"
|
||||
|
||||
|
||||
def source(name: str) -> str:
|
||||
return (WEB / name).read_text(encoding="utf-8")
|
||||
|
||||
|
||||
def test_vue_presentation_assets_are_local_ordered_and_cached() -> None:
|
||||
html = source("index.html")
|
||||
service_worker = source("service-worker.js")
|
||||
runtime = "/vendor/vue/3.5.21/vue.runtime.global.prod.js"
|
||||
presentation = "/vue-ui.js?v=vue-presentation-3"
|
||||
|
||||
assert html.index(runtime) < html.index(presentation) < html.index(
|
||||
"/app.js?v=dual-seat-135"
|
||||
)
|
||||
assert html.index(presentation) < html.index(
|
||||
"/assistant-chat.js?v=codex-assistant-32"
|
||||
)
|
||||
assert f'"{runtime}"' in service_worker
|
||||
assert f'"{presentation}"' in service_worker
|
||||
assert 'const CACHE_VERSION = "cognitive-agent-pad-v290"' in service_worker
|
||||
assert "unpkg.com" not in html
|
||||
assert "cdn.jsdelivr.net" not in html
|
||||
|
||||
|
||||
def test_vendored_vue_runtime_and_license_are_present() -> None:
|
||||
runtime = WEB / "vendor" / "vue" / "3.5.21" / "vue.runtime.global.prod.js"
|
||||
license_file = WEB / "vendor" / "vue" / "3.5.21" / "LICENSE"
|
||||
|
||||
assert runtime.is_file()
|
||||
assert runtime.stat().st_size > 50_000
|
||||
assert "vue v3.5.21" in runtime.read_text(encoding="utf-8")[:500]
|
||||
assert license_file.is_file()
|
||||
assert "The MIT License" in license_file.read_text(encoding="utf-8")
|
||||
|
||||
|
||||
def test_vue_islands_render_modes_and_dispatched_controls() -> None:
|
||||
vue_ui = source("vue-ui.js")
|
||||
app = source("app.js")
|
||||
assistant = source("assistant-chat.js")
|
||||
styles = source("assistant-chat.css")
|
||||
|
||||
assert 'name: "N7ModeChipRail"' in vue_ui
|
||||
assert 'name: "N7DispatchedControlsCard"' in vue_ui
|
||||
assert 'container.dataset.vueSurface = "mode-chips"' in vue_ui
|
||||
assert 'container.dataset.vueSurface = "dispatched-controls"' in vue_ui
|
||||
assert "renderModeChips" in vue_ui
|
||||
assert "renderDispatchedControls" in vue_ui
|
||||
assert "window.CognitiveAgentVueUI = Object.freeze" in vue_ui
|
||||
assert '"data-mode-run": "true"' in vue_ui
|
||||
|
||||
assert "function vueModeChipEntries(modes)" in app
|
||||
assert "window.CognitiveAgentVueUI?.renderModeChips?.(" in app
|
||||
assert "window.CognitiveAgentVueUI?.renderDispatchedControls" in assistant
|
||||
assert 'host.className = "assistant-dispatched-controls-vue-host"' in assistant
|
||||
assert "deliveryLabel: String(item.deliveryLabel" in assistant
|
||||
assert "deliveryDetail: String(item.deliveryDetail" in assistant
|
||||
assert "下发状态:" in vue_ui
|
||||
assert ".assistant-dispatched-controls-vue-host" in styles
|
||||
|
||||
|
||||
def test_existing_dom_renderers_remain_as_no_vue_fallbacks() -> None:
|
||||
app = source("app.js")
|
||||
assistant = source("assistant-chat.js")
|
||||
|
||||
mode_vue_call = app.index("window.CognitiveAgentVueUI?.renderModeChips?.(")
|
||||
mode_fallback = app.index("const runtimeModeIds = new Set", mode_vue_call)
|
||||
assert mode_fallback > mode_vue_call
|
||||
|
||||
command_vue_call = assistant.index(
|
||||
"window.CognitiveAgentVueUI?.renderDispatchedControls"
|
||||
)
|
||||
command_fallback = assistant.index(
|
||||
'document.createElement("details")', command_vue_call
|
||||
)
|
||||
assert command_fallback > command_vue_call
|
||||
assert "renderDispatchedControls: () => false" in source("vue-ui.js")
|
||||
assert "renderModeChips: () => false" in source("vue-ui.js")
|
||||
Reference in New Issue
Block a user