fix: restore mode clicks and compose seat-aware assistant scenes
This commit is contained in:
@@ -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:
|
||||
|
||||
@@ -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:
|
||||
|
||||
@@ -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
|
||||
Reference in New Issue
Block a user