fix: restore mode clicks and compose seat-aware assistant scenes

This commit is contained in:
vee
2026-09-12 13:33:38 +08:00
parent 76057be044
commit d09b5967f8
28 changed files with 1247 additions and 44 deletions
@@ -358,7 +358,7 @@ def test_common_scene_templates_are_read_only_callable_catalog_entries(
templates = snapshot["scene_templates"]
assert snapshot["source_status"]["scene_templates"] == "ready"
assert len(templates) == 12
assert len(templates) == 13
assert {item["name"] for item in templates} >= {
"观影模式",
"露营模式",
@@ -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-26"' in html
assert 'href="/assistant-chat.css?v=codex-assistant-27"' in html
assert 'src="/assistant-chat.js?v=codex-assistant-27"' 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-v277"' in service_worker
assert '"/assistant-chat.css?v=codex-assistant-26"' in service_worker
assert '"/assistant-chat.js?v=codex-assistant-26"' in service_worker
assert 'const CACHE_VERSION = "cognitive-agent-pad-v278"' in service_worker
assert '"/assistant-chat.css?v=codex-assistant-27"' in service_worker
assert '"/assistant-chat.js?v=codex-assistant-27"' in service_worker
def test_codex_frontend_uses_one_streaming_endpoint_and_agent_work_events():
@@ -0,0 +1,177 @@
"""Execute the mode preview formatter against actual seat/light parameter shapes."""
from __future__ import annotations
import json
import shutil
import subprocess
from pathlib import Path
import pytest
ROOT = Path(__file__).resolve().parents[2]
@pytest.fixture(scope="module")
def render_rows():
node = shutil.which("node")
if not node:
runtime = (
Path.home() / ".cache" / "codex-runtimes" / "codex-primary-runtime"
/ "dependencies" / "node" / "bin"
)
node = next((str(path) for path in (runtime / "node.exe", runtime / "node") if path.is_file()), None)
assert node, "Node.js is required to validate rendered mode action text"
source = (ROOT / "apps" / "web-console" / "assistant-chat.js").read_text(encoding="utf-8")
start = source.index(" function modeDraftRows(")
end = source.index("\n function ", start + 1)
script = source[start:end] + "\nconst input = JSON.parse(require('node:fs').readFileSync(0, 'utf8'));\n"
script += "console.log(JSON.stringify(modeDraftRows(input.draft, input.actions)));"
def render(actions, instruction=""):
completed = subprocess.run(
[node, "-e", script],
input=json.dumps({"draft": {"instruction": instruction}, "actions": actions}, ensure_ascii=False),
capture_output=True,
text=True,
encoding="utf-8",
check=True,
)
return json.loads(completed.stdout)
return render
def action(capability, parameters, zone="driver"):
return {
"domain": capability.split(".")[0],
"capability": capability,
"target": {"zone": zone},
"parameters": parameters,
}
@pytest.mark.parametrize("kind", ["ventilation", "heating"])
@pytest.mark.parametrize(
("parameters", "expected"),
[
({"state": "on", "level": 1}, "开启 · 高档"),
({"state": "on", "level": 2}, "开启 · 低档"),
({"state": "on"}, "开启 · 低档"),
({"state": "off", "level": 1}, "关闭"),
],
)
def test_driver_native_comfort_uses_its_own_level_scale(render_rows, kind, parameters, expected):
rows = render_rows([action(f"seat.set_driver_{kind}_native", parameters, "front_left")])
assert rows[0]["label"] == f"主驾座椅{'通风' if kind == 'ventilation' else '加热'}"
assert rows[0]["value"] == expected
@pytest.mark.parametrize("kind", ["ventilation", "heating"])
@pytest.mark.parametrize("level", [1, 2, 3, 4, 5])
def test_passenger_levels_are_not_translated_using_driver_can_codes(render_rows, kind, level):
rows = render_rows([action(f"seat.set_{kind}", {"state": "on", "level": level}, "passenger")])
assert rows[0]["label"] == f"副驾座椅{'通风' if kind == 'ventilation' else '加热'}"
assert rows[0]["value"] == f"开启 · {level}档"
@pytest.mark.parametrize(
("parameters", "expected"),
[
({"action": "off", "mode": "relax"}, "关闭"),
({"action": "mode", "mode": "wave"}, "波浪模式"),
({"action": "mode", "mode": "pulse"}, "脉冲模式"),
({"action": "mode", "mode": "stretch"}, "舒展模式"),
({"action": "mode", "mode": "slack"}, "舒缓模式"),
({"action": "mode", "mode": "relax"}, "放松模式"),
({"action": "strength", "strength": 1}, "轻柔力度"),
({"action": "strength", "strength": 2}, "中等力度"),
({"action": "strength", "strength": 3}, "较强力度"),
({"action": "mode"}, "按摩模式待确认"),
({"action": "strength"}, "按摩力度待确认"),
],
)
def test_massage_preview_distinguishes_operation_mode_and_strength(render_rows, parameters, expected):
rows = render_rows([action("seat.set_driver_massage_native", parameters)])
assert rows[0]["label"] == "主驾座椅按摩"
assert rows[0]["value"] == expected
def test_ambient_native_color_and_brightness_remain_distinct_rows(render_rows):
rows = render_rows([
action("ambient_light.set_color_index", {"color_index": 127}, "vehicle"),
action("ambient_light.set_brightness_level", {"brightness_level": 2}, "vehicle"),
])
assert [(row["label"], row["value"]) for row in rows] == [
("氛围灯颜色", "色号 127"),
("氛围灯亮度", "2档"),
]
@pytest.mark.parametrize("brightness", [0, 20, 100])
def test_rgb_illumination_keeps_exact_color_and_brightness(render_rows, brightness):
rows = render_rows([
action("ambient_light.set_illumination", {"color_hex": "#FFB347", "brightness_pct": brightness}, "vehicle"),
])
assert rows[0]["value"] == f"颜色 #FFB347 · 亮度 {brightness}%"
def test_zero_gravity_grouping_keeps_driver_comfort_visible(render_rows):
rows = render_rows([
action("seat.set_longitudinal_position", {"position_pct": 100}, "passenger"),
action("seat.set_backrest_recline", {"position_pct": 81, "angle_deg": 150}, "front_right"),
action("seat.set_driver_massage_native", {"action": "mode", "mode": "relax"}),
], "副驾零重力模式,并开启主驾放松按摩")
assert [(row["label"], row["value"]) for row in rows] == [
("副驾座椅", "零重力模式"),
("主驾座椅按摩", "放松模式"),
]
def test_position_and_power_rows_keep_existing_values(render_rows):
rows = render_rows([
action("seat.set_backrest_recline", {"position_pct": 81, "angle_deg": 150}, "passenger"),
action("ambient_light.set_power", {"state": "off"}, "vehicle"),
action("seat.set_ventilation", {"state": "off", "level": 2}, "passenger"),
])
assert [(row["label"], row["value"]) for row in rows] == [
("副驾靠背角度", "81%(150°)"),
("氛围灯", "关闭"),
("副驾座椅通风", "关闭"),
]
@pytest.mark.parametrize(
("capability", "mode", "label", "value"),
[
("climate.set_control_mode", "auto", "空调控制模式", "自动控制"),
("climate.set_control_mode", "manual", "空调控制模式", "手动控制"),
("climate.set_cycle_mode", "internal", "空调循环", "内循环"),
("climate.set_cycle_mode", "external", "空调循环", "外循环"),
("climate.set_outlet_mode", "face", "空调出风模式", "吹面"),
("climate.set_outlet_mode", "face_feet", "空调出风模式", "吹面 / 吹脚"),
("climate.set_outlet_mode", "feet", "空调出风模式", "吹脚"),
("climate.set_outlet_mode", "feet_defrost", "空调出风模式", "吹脚 / 除霜"),
("climate.set_outlet_mode", "defrost", "空调出风模式", "除霜"),
("climate.set_outlet_mode", "face_feet_defrost", "空调出风模式", "吹面 / 吹脚 / 除霜"),
("climate.set_outlet_mode", "face_defrost", "空调出风模式", "吹面 / 除霜"),
],
)
def test_climate_modes_use_capability_specific_labels(render_rows, capability, mode, label, value):
rows = render_rows([action(capability, {"mode": mode}, "vehicle")])
assert (rows[0]["label"], rows[0]["value"]) == (label, value)
def test_climate_control_circulation_and_outlet_are_not_conflated(render_rows):
rows = render_rows([
action("climate.set_control_mode", {"mode": "auto"}, "vehicle"),
action("climate.set_cycle_mode", {"mode": "internal"}, "vehicle"),
action("climate.set_outlet_mode", {"mode": "face_feet"}, "vehicle"),
action("climate.set_fan_level", {"level": 2}, "vehicle"),
])
assert [(row["label"], row["value"]) for row in rows] == [
("空调控制模式", "自动控制"),
("空调循环", "内循环"),
("空调出风模式", "吹面 / 吹脚"),
("空调风量", "2档"),
]
@@ -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-26") < html.index(
assert html.index("/assistant-chat.js?v=codex-assistant-27") < html.index(
"/energy-monitor.js?v=energy-monitor-4"
)
assert 'const CACHE_VERSION = "cognitive-agent-pad-v277"' in service_worker
assert 'const CACHE_VERSION = "cognitive-agent-pad-v278"' in service_worker
def test_energy_monitor_uses_the_fifth_shared_dock_icon_and_starts_closed() -> None:
+1 -1
View File
@@ -47,7 +47,7 @@ def test_theme_asset_versions_are_consistent_with_the_pwa_shell() -> None:
):
assert asset in html
assert f'"{asset}"' in service_worker
assert 'const CACHE_VERSION = "cognitive-agent-pad-v277"' in service_worker
assert 'const CACHE_VERSION = "cognitive-agent-pad-v278"' in service_worker
def test_vehicle_twin_icons_and_nested_overlays_use_semantic_tokens() -> None:
+41
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]
@@ -285,6 +287,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]); },
$(selector) {
return { "#utterance": input, "#requestForm": form, "#runButton": runButton }[selector];
@@ -355,6 +358,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"')
@@ -36,6 +36,9 @@ EXPECTED_SCENE_CAPABILITIES = {
"seat.set_legrest_height",
"seat.set_longitudinal_position",
"seat.set_ventilation",
"seat.set_driver_ventilation_native",
"seat.set_driver_heating_native",
"seat.set_driver_massage_native",
"window.set_position",
}
@@ -15,6 +15,8 @@ ROOT = Path(__file__).resolve().parents[2]
(
("帮我构建一个适合看书的功能模式", ("看书模式", "")),
("构建一个适合放松的功能模式", ("放松模式", "")),
("构建一个座椅舒适的功能模式", ("座椅舒适模式", "")),
("构建一个让人坐着放松的功能模式", ("坐着放松模式", "")),
("请帮我创建适合阅读的模式吧", ("阅读模式", "")),
("我想生成一个用来读书的模式并预览", ("读书模式", "")),
("请生成用于放松的模式,先看看", ("放松模式", "")),
@@ -67,6 +69,8 @@ def test_explicit_names_are_never_rewritten(
("帮我构建一个适合看书的功能模式", "reading"),
("请创建一个用于阅读的功能模式", "reading"),
("构建一个适合放松的功能模式", "relaxation"),
("构建一个座椅舒适的功能模式", "seat-comfort"),
("构建一个让人坐着放松的功能模式", "relaxation"),
("我想创建一套用来舒缓的模式", "relaxation"),
("帮我构建一个适合看电影的功能模式", "cinema"),
),
@@ -0,0 +1,114 @@
from __future__ import annotations
from copy import deepcopy
import pytest
from jsonschema import Draft202012Validator
from cognitive_agent.application.assistant_capabilities import AssistantCapabilityCatalog
from cognitive_agent.application.mode_profile_intent import parse_mode_profile_create
from cognitive_agent.application.scene_composer import build_scene_output_schema
from cognitive_agent.application.seat_service import SeatDemoService
@pytest.fixture(scope="module")
def catalog():
service = SeatDemoService()
try:
yield AssistantCapabilityCatalog(service)
finally:
service.close()
@pytest.mark.parametrize("message", (
"构建一个座椅舒适的功能模式",
"构建一个让人坐着放松的功能模式",
"可以帮我创建一个适合座椅放松的功能模式吗?",
"构建一个主驾按摩的功能模式",
"创建一个空调舒适的功能模式",
"构建一个适合看书的功能模式,阅读灯打开",
))
def test_mode_creation_is_not_claimed_by_capability_fast_answer(catalog, message):
assert parse_mode_profile_create(message) is not None
assert catalog.fast_answer(message) is None
@pytest.mark.parametrize("message", (
"支持主驾座椅按摩吗?",
"主驾与副驾座椅有哪些功能?",
"座椅通风支持哪些档位?",
"副驾座椅支持按摩吗?",
))
def test_genuine_seat_questions_remain_read_only_capability_answers(catalog, message):
assert parse_mode_profile_create(message) is None
assert catalog.fast_answer(message) is not None
@pytest.mark.parametrize("message", (
"不要构建座椅舒适模式",
"构建一个座椅舒适模式,但不要执行",
"取消创建坐着放松模式",
"如何构建一个座椅舒适模式?",
"解释构建一个座椅舒适的功能模式这句话",
))
def test_negations_and_read_only_seat_mode_requests_never_authorize_creation(message):
assert parse_mode_profile_create(message) is None
def _response(capability, target, parameters):
return {
"requests": [{"capability": capability, "target_scope": target, "parameters": parameters}],
"ambiguities": [],
"confidence": 0.95,
}
@pytest.mark.parametrize(("capability", "target", "parameters", "valid"), (
("seat.set_driver_ventilation_native", "driver", {"state": "on", "level": 2}, True),
("seat.set_driver_heating_native", "driver", {"state": "on", "level": 1}, True),
("seat.set_driver_heating_native", "front_left", {"state": "off"}, True),
("seat.set_driver_massage_native", "driver", {"action": "mode", "mode": "relax"}, True),
("seat.set_driver_massage_native", "driver", {"action": "strength", "strength": 1}, True),
("seat.set_driver_massage_native", "driver", {"action": "off"}, True),
("seat.set_driver_massage_native", "passenger", {"action": "mode", "mode": "relax"}, False),
("seat.set_driver_massage_native", "driver", {"action": "mode"}, False),
("seat.set_driver_massage_native", "driver", {"action": "mode", "strength": 1}, False),
("seat.set_driver_massage_native", "driver", {"action": "off", "mode": "relax"}, False),
("seat.set_driver_massage_native", "driver", {"action": "mode", "mode": "relax", "strength": 1}, False),
("seat.set_driver_massage_native", "driver", {"action": "mode", "mode": "unknown"}, False),
("seat.set_driver_heating_native", "passenger", {"state": "on", "level": 2}, False),
("seat.set_driver_ventilation_native", "driver", {"state": "on", "level": 3}, False),
("seat.set_driver_axis_native", "driver", {"axis": "backrest", "direction": "backward", "duration_ms": 500}, False),
("seat.set_driver_position_native", "driver", {"slot": 1}, False),
("seat.set_backrest_recline", "driver", {"position_pct": 45}, False),
))
def test_scene_schema_respects_real_driver_and_passenger_capabilities(
catalog, capability, target, parameters, valid,
):
context = catalog.scene_composition_context()
validator = Draft202012Validator(build_scene_output_schema(context))
assert validator.is_valid(_response(capability, target, parameters)) is valid
def test_scene_context_removes_driver_capabilities_when_runtime_marks_them_unavailable(catalog):
snapshot = deepcopy(catalog.snapshot())
for capability in snapshot["capabilities"]:
if capability["id"].startswith("seat.set_driver_"):
capability["assistant_callable"] = False
context = catalog.scene_composition_context(snapshot)
assert not any(item["id"].startswith("seat.set_driver_") for item in context["capabilities"])
validator = Draft202012Validator(build_scene_output_schema(context))
assert not validator.is_valid(_response(
"seat.set_driver_massage_native", "driver", {"action": "mode", "mode": "relax"},
))
def test_seat_comfort_detail_answer_uses_seat_specific_natural_language(catalog):
answer = catalog.fast_answer("座椅舒适模式会控制哪些设备?")
assert answer is not None
assert "主驾座椅通风开启低档" in answer.answer
assert "主驾座椅按摩设为放松模式" in answer.answer
assert "副驾座椅通风" in answer.answer
assert "副驾座椅靠背" in answer.answer
assert "action=" not in answer.answer
assert "主驾主驾" not in answer.answer