Merge origin/feature/N7Web into feature/N7Web

Integrate the remote firmware workbench, OTA delivery, and timed mode schedules while preserving the local N7 scene and runtime changes.
This commit is contained in:
Miayui
2026-09-17 12:46:51 +08:00
135 changed files with 30180 additions and 513 deletions
+59 -3
View File
@@ -5,6 +5,8 @@ import shutil
import subprocess
from pathlib import Path
import pytest
from cognitive_agent.application.mode_profile import ModeProfileRegistry
ROOT = Path(__file__).resolve().parents[2]
@@ -44,8 +46,18 @@ def test_runtime_mode_catalog_exposes_safe_executable_action_details() -> None:
if mode["id"] == "office"
)
assert len(office["operations"]) == office["operation_count"] == 10
assert len(office["operations"]) == office["operation_count"] == 12
assert office["operations"][0] == {
"domain": "seat",
"capability": "seat.set_driver_ventilation_native",
"target_scope": "driver",
"operation": "set",
"parameters": {"state": "on", "level": 2},
}
assert next(
operation for operation in office["operations"]
if operation["capability"] == "seat.set_longitudinal_position"
) == {
"domain": "seat",
"capability": "seat.set_longitudinal_position",
"target_scope": "passenger",
@@ -241,7 +253,7 @@ def test_top_mode_action_formatter_uses_device_target_action_and_parameter_value
actions = json.loads(completed.stdout)
assert len(actions) == office["operation_count"]
assert actions[0] == {
assert next(action for action in actions if action["action"] == "调节座椅前后位置") == {
"device": "座椅",
"target": "副驾",
"action": "调节座椅前后位置",
@@ -266,7 +278,11 @@ def test_mode_chip_scroller_keeps_clicks_and_only_captures_real_drags() -> None:
capture = snippet.index("group.setPointerCapture(pointerId);")
assert capture > threshold
assert "let pointerCaptured = false;" in snippet
assert "if (pointerCaptured) group.releasePointerCapture?.(pointerId);" in snippet
assert "const finishedPointerId = pointerId;" in snippet
assert "pointerId = null;" in snippet
assert "group.hasPointerCapture?.(finishedPointerId)" in snippet
assert "group.releasePointerCapture(finishedPointerId);" in snippet
assert 'group.addEventListener("lostpointercapture", finishPointerDrag);' in snippet
assert 'event.stopImmediatePropagation();' in snippet
@@ -309,6 +325,7 @@ const context = {
modeProfiles() { return [savedMode]; },
setModeRailSelection(mode) { calls.push(["select", mode?.id]); },
closeModeGallery(options) { calls.push(["close", options.restoreFocus]); },
hideModeActionPopover() {},
showToast(message) { calls.push(["toast", message]); },
hasExecutableModeCommandFlow(mode) {
const flow = mode?.command_flow;
@@ -418,6 +435,7 @@ const context = {
setModeRailSelection(mode) { calls.push(["select", mode?.id]); },
setModeExecutionState(modeId, status) { calls.push(["state", modeId, status]); },
closeModeGallery(options) { calls.push(["close", options.restoreFocus]); },
hideModeActionPopover() {},
showToast(message) { calls.push(["toast", message]); },
hasExecutableModeCommandFlow(mode) {
const flow = mode?.command_flow;
@@ -484,6 +502,44 @@ console.log(JSON.stringify({ result: context.result, calls }));
}
@pytest.mark.parametrize(("phase", "input_mode"), (
("capturing", "manual"),
("capturing", "wake"),
("finalizing", "manual"),
("finalizing", "wake"),
("arming", "manual"),
))
def test_mode_click_during_dictation_does_not_create_stuck_running_state(
phase: str, input_mode: str,
) -> None:
app = (WEB / "app.js").read_text(encoding="utf-8")
start = app.index("function applyModeCommand")
end = app.index("\n\nfunction applyRuntimeTargetPolicy", start)
script = "const snippet = " + json.dumps(app[start:end]) + ";\n"
script += "const speech = " + json.dumps({"phase": phase, "inputMode": input_mode}) + ";\n"
script += r"""
const vm = require("node:vm");
const calls = [];
const context = {
window: {
CognitiveAgentAssistant: { isPending() { return false; } },
CognitiveAgentSpeech: speech,
},
showToast(message) { calls.push(message); },
};
vm.createContext(context);
vm.runInContext(snippet + '\nthis.result = applyModeCommand("进入办公模式", "office");', context);
console.log(JSON.stringify({ result: context.result, calls }));
"""
completed = subprocess.run(
[node_runtime(), "-e", script], check=True, capture_output=True, encoding="utf-8",
)
assert json.loads(completed.stdout) == {
"result": False,
"calls": ["请等待语音整理完成后再发送"],
}
def test_top_and_management_mode_cards_share_the_assistant_submission_entrypoint() -> None:
app = (WEB / "app.js").read_text(encoding="utf-8")
install_start = app.index('document.addEventListener("DOMContentLoaded"')