feat: make mode creation preview-only and cache-safe

This commit is contained in:
Miayui
2026-09-20 19:32:03 +08:00
parent bfff1b515c
commit 89707a42e2
28 changed files with 1375 additions and 165 deletions
+75 -23
View File
@@ -986,6 +986,17 @@ operations:
vite_entry = (web_console_root / "src" / "main.js").read_text(
encoding="utf-8"
)
runtime_manifest_path = web_console_root / "dist" / "runtime-assets.json"
runtime_manifest = (
json.loads(runtime_manifest_path.read_text(encoding="utf-8"))
if runtime_manifest_path.is_file()
else {"assets": {}}
)
runtime_assets = runtime_manifest.get("assets", {})
def runtime_url(file_name: str, source_url: str) -> str:
hashed = runtime_assets.get(file_name)
return f"/{hashed}" if hashed else source_url
with self.opener.open(f"{self.base_url}/app.js?v=dual-seat-145-n7-dispatch", timeout=3) as response:
app_script = response.read().decode("utf-8")
with self.opener.open(
@@ -1021,7 +1032,7 @@ operations:
) as response:
extension_styles = response.read().decode("utf-8").replace("\r\n", "\n")
with self.opener.open(
f"{self.base_url}/assistant-chat.js?v=codex-assistant-34", timeout=3
f"{self.base_url}/assistant-chat.js?v=codex-assistant-35", timeout=3
) as response:
assistant_script = response.read().decode("utf-8")
with self.opener.open(
@@ -1072,19 +1083,31 @@ operations:
self.assertIn("CMD 0x431 · FB 0x41A · CH1", template)
self.assertIn("CMD 0x431 · FB 0x388 · CH1", template)
self.assertNotIn("透明车体 · 实体设备", template)
self.assertIn('src="/app.js?v=dual-seat-145-n7-dispatch"', html)
self.assertIn(
f'src="{runtime_url("app.js", "/app.js?v=dual-seat-145-n7-dispatch")}"',
html,
)
self.assertIn('import "../styles.css";', vite_entry)
self.assertNotIn('class="vehicle-system-hud"', template)
self.assertIn('src="/console-v2.js?v=local-first-77-performance"', html)
self.assertIn(
f'src="{runtime_url("console-v2.js", "/console-v2.js?v=local-first-77-performance")}"',
html,
)
self.assertIn('import "../console-v2.css";', vite_entry)
self.assertIn('src="/speech-input.js?v=sensevoice-13"', html)
self.assertIn(
f'src="{runtime_url("speech-input.js", "/speech-input.js?v=sensevoice-13")}"',
html,
)
self.assertIn('import "../speech-input.css";', vite_entry)
self.assertIn('id="speechDictationButton"', template)
self.assertIn('class="speech-waveform"', template)
self.assertIn('<body class="console-v2">', html)
self.assertIn('id="v2BootScreen"', template)
self.assertIn('正在加载车机控制与实时状态', template)
self.assertIn('src="/self-extension.js?v=nfc-mqtt-56"', html)
self.assertIn(
f'src="{runtime_url("self-extension.js", "/self-extension.js?v=nfc-mqtt-56")}"',
html,
)
self.assertIn('import "../self-extension.css";', vite_entry)
self.assertNotIn("self-calibration", template)
self.assertNotIn("self-repair", template)
@@ -1093,9 +1116,15 @@ operations:
self.assertIn('aria-label="发送消息"', template)
self.assertIn('id="assistantTranscript"', template)
self.assertIn('import "../assistant-chat.css";', vite_entry)
self.assertIn('src="/assistant-chat.js?v=codex-assistant-34"', html)
self.assertIn(
f'src="{runtime_url("assistant-chat.js", "/assistant-chat.js?v=codex-assistant-35")}"',
html,
)
self.assertIn('import "../energy-monitor.css";', vite_entry)
self.assertIn('src="/energy-monitor.js?v=energy-monitor-5-performance"', html)
self.assertIn(
f'src="{runtime_url("energy-monitor.js", "/energy-monitor.js?v=energy-monitor-5-performance")}"',
html,
)
self.assertIn('import "../codepen-glass-theme.css";', vite_entry)
self.assertEqual(extension_control_status["source_label"], "车端 MQTT")
self.assertIn("action_statuses", extension_control_status)
@@ -1384,23 +1413,46 @@ operations:
self.assertIn("CAN 指令已提交,等待车辆状态确认", console_script)
self.assertIn('canvas.addEventListener("webglcontextlost"', app_script)
self.assertIn('"cognitive-agent:module-change"', app_script)
self.assertIn(
'const CACHE_VERSION = "cognitive-agent-pad-v328-nfc-mqtt"',
service_worker,
)
self.assertIn('/console-v2.css?v=delivery-state-69', service_worker)
self.assertIn('/console-v2.js?v=local-first-77-performance', service_worker)
self.assertIn('/assistant-chat.css?v=codex-assistant-29', service_worker)
self.assertIn('/assistant-chat.js?v=codex-assistant-34', service_worker)
self.assertIn('/energy-monitor.css?v=energy-monitor-5-dialog-size', service_worker)
self.assertIn('/energy-monitor.js?v=energy-monitor-5-performance', service_worker)
self.assertIn('"/app.js?v=dual-seat-145-n7-dispatch"', service_worker)
self.assertIn('/firmware-workbench-entry.css?v=2', service_worker)
self.assertIn('/firmware-workbench-entry.js?v=3', service_worker)
self.assertIn('/firmware-workbench-theme.js?v=1', service_worker)
if runtime_assets:
self.assertIn(
f'const CACHE_VERSION = "cognitive-agent-pad-build-{runtime_manifest["build_hash"]}"',
service_worker,
)
for asset_name in (
"console-v2.css",
"console-v2.js",
"assistant-chat.css",
"assistant-chat.js",
"energy-monitor.css",
"energy-monitor.js",
"app.js",
"firmware-workbench-entry.css",
"firmware-workbench-entry.js",
"firmware-workbench-theme.js",
):
self.assertIn(f'/{runtime_assets[asset_name]}', service_worker)
else:
self.assertIn(
'const CACHE_VERSION = "cognitive-agent-pad-v329-mode-preview"',
service_worker,
)
self.assertIn('/console-v2.css?v=delivery-state-69', service_worker)
self.assertIn('/console-v2.js?v=local-first-77-performance', service_worker)
self.assertIn('/assistant-chat.css?v=codex-assistant-30', service_worker)
self.assertIn('/assistant-chat.js?v=codex-assistant-35', service_worker)
self.assertIn('/energy-monitor.css?v=energy-monitor-5-dialog-size', service_worker)
self.assertIn('/energy-monitor.js?v=energy-monitor-5-performance', service_worker)
self.assertIn('"/app.js?v=dual-seat-145-n7-dispatch"', service_worker)
self.assertIn('/firmware-workbench-entry.css?v=2', service_worker)
self.assertIn('/firmware-workbench-entry.js?v=3', service_worker)
self.assertIn('/firmware-workbench-theme.js?v=1', service_worker)
self.assertIn('/firmware-workbench.html', service_worker)
self.assertIn('"/speech-input.js?v=sensevoice-13"', service_worker)
self.assertIn('"/speech-input.css?v=sensevoice-7"', service_worker)
if runtime_assets:
self.assertIn(f'"/{runtime_assets["speech-input.js"]}"', service_worker)
self.assertIn(f'"/{runtime_assets["speech-input.css"]}"', service_worker)
else:
self.assertIn('"/speech-input.js?v=sensevoice-13"', service_worker)
self.assertIn('"/speech-input.css?v=sensevoice-7"', service_worker)
self.assertIn('"/speech-audio-worklet.js?v=sensevoice-5"', service_worker)
self.assertIn('fetch("/api/speech/preview"', speech_script)
self.assertIn('fetch("/api/speech/transcribe"', speech_script)
@@ -264,7 +264,7 @@ def test_unsupported_or_out_of_range_addition_rejects_all_changes_atomically(add
assert not _hashes(user_root)
def test_adding_devices_to_full_twelve_action_draft_never_truncates_original_actions(addition_service):
def test_adding_devices_to_full_twenty_action_draft_never_truncates_original_actions(addition_service):
service, _, user_root = addition_service
operations = [
("climate.set_power", "vehicle", {"state": "on"}),
@@ -279,6 +279,14 @@ def test_adding_devices_to_full_twelve_action_draft_never_truncates_original_act
("ambient_light.set_power", "vehicle", {"state": "off"}),
("interior_light.set_indoor_power", "vehicle", {"state": "off"}),
("interior_light.set_reading_power", "all", {"state": "off"}),
("window.set_position", "rear_left", {"position_pct": 0}),
("window.set_position", "rear_right", {"position_pct": 0}),
("ambient_light.set_mode", "vehicle", {"mode": "romantic"}),
("ambient_light.set_brightness_level", "vehicle", {"brightness_level": 2}),
("ambient_light.set_color_index", "vehicle", {"color_index": 6}),
("seat.set_heating", "passenger", {"state": "on", "level": 1}),
("seat.set_backrest_recline", "passenger", {"position_pct": 45}),
("seat.set_legrest_height", "passenger", {"position_pct": 35}),
]
plan = {
"detected_domains": sorted({cap.split(".", 1)[0] for cap, _, _ in operations}),
@@ -287,11 +295,11 @@ def test_adding_devices_to_full_twelve_action_draft_never_truncates_original_act
"operation": "set", "parameters": [{"name": name, "value": value} for name, value in params.items()],
"duration_ms": None, "reason": "固定目标边界回归。",
} for cap, target, params in operations],
"ambiguities": [], "confidence": 0.95, "decision_summary": ["验证12项原动作保留。"],
"ambiguities": [], "confidence": 0.95, "decision_summary": ["验证20项原动作保留。"],
}
original = _preview(service, "构建一个满载参数模式", plan=plan)
_assert_draft(original)
assert len(_actions(original)) == 12
assert len(_actions(original)) == 20
source_path = service.artifact_root / original["request_id"] / "result.json"
source_hash = hashlib.sha256(source_path.read_bytes()).hexdigest()
try:
@@ -1193,26 +1193,34 @@ MAXIMUM_SCENE_REQUESTS = (
_scene_request("ambient_light.set_power", "vehicle", {"state": "off"}),
_scene_request("interior_light.set_indoor_power", "vehicle", {"state": "off"}),
_scene_request("interior_light.set_reading_power", "all", {"state": "off"}),
_scene_request("window.set_position", "rear_left", {"position_pct": 0}),
_scene_request("window.set_position", "rear_right", {"position_pct": 0}),
_scene_request("ambient_light.set_mode", "vehicle", {"mode": "romantic"}),
_scene_request("ambient_light.set_brightness_level", "vehicle", {"brightness_level": 2}),
_scene_request("ambient_light.set_color_index", "vehicle", {"color_index": 6}),
_scene_request("seat.set_heating", "passenger", {"state": "on", "level": 1}),
_scene_request("seat.set_backrest_recline", "passenger", {"position_pct": 45}),
_scene_request("seat.set_legrest_height", "passenger", {"position_pct": 35}),
)
def test_scene_request_limit_accepts_exactly_twelve_verified_actions(
def test_scene_request_limit_accepts_exactly_twenty_verified_actions(
mode_service: tuple[SeatDemoService, Path],
) -> None:
service, user_root = mode_service
assert len(MAXIMUM_SCENE_REQUESTS) == MAX_PLAN_REQUESTS == 12
assert len(MAXIMUM_SCENE_REQUESTS) == MAX_PLAN_REQUESTS == 20
result = _preview_without_hardware(
service,
"构建一个十二动作模式",
conversation_id="conv-twelve-scene-actions",
"构建一个二十动作模式",
conversation_id="conv-twenty-scene-actions",
assistant_plan=_scene_plan(*MAXIMUM_SCENE_REQUESTS),
)
_assert_successful_preview(result, name="十二动作模式", operation_count=12)
_assert_successful_preview(result, name="二十动作模式", operation_count=20)
assert not list(user_root.rglob("*.yaml"))
def test_scene_request_limit_rejects_thirteen_before_any_hardware_or_persistence(
def test_scene_request_limit_rejects_twenty_one_before_any_hardware_or_persistence(
mode_service: tuple[SeatDemoService, Path],
) -> None:
service, user_root = mode_service
@@ -1225,8 +1233,8 @@ def test_scene_request_limit_rejects_thirteen_before_any_hardware_or_persistence
with pytest.raises(ValueError, match="too many requests"):
_preview_without_hardware(
service,
"构建一个十三动作模式",
conversation_id="conv-thirteen-scene-actions",
"构建一个二十一动作模式",
conversation_id="conv-twenty-one-scene-actions",
assistant_plan=_scene_plan(*oversized),
)
assert not list(user_root.rglob("*.yaml"))
@@ -26,17 +26,18 @@ CALLBACK_URL = "http://127.0.0.1:8765/api/internal/codex/vehicle-control"
@pytest.mark.parametrize(
("message", "expected_name"),
("message", "expected_name", "expected_operation_count"),
(
("构建一个星夜模式", "星夜模式"),
("创建学习模式", "学习模式"),
("创建中秋欢快模式", "中秋欢快模式"),
("构建一个星夜模式", "星夜模式", 13),
("创建学习模式", "学习模式", 13),
("创建中秋欢快模式", "中秋欢快模式", 13),
),
)
def test_structured_scene_turn_reaches_the_complete_local_dry_run_pipeline(
tmp_path: Path,
message: str,
expected_name: str,
expected_operation_count: int,
) -> None:
service = SeatDemoService(REPOSITORY_ROOT)
service.artifact_root = tmp_path / "runs"
@@ -107,20 +108,37 @@ def test_structured_scene_turn_reaches_the_complete_local_dry_run_pipeline(
assert result["orchestration_route"] == "mode_profile_create_preview"
assert draft["name"] == expected_name
assert draft["generation_source"] == "codex_scene_composition"
assert draft["operation_count"] == 2
assert draft["operation_count"] == expected_operation_count
capabilities = {
action["capability"] for action in result["actions"]
}
assert capabilities == {
expected_capabilities = {
"climate.set_power",
"climate.set_temperature",
"climate.set_fan_level",
"climate.set_cycle_mode",
"window.set_position",
"roof.set_shade_position",
"seat.set_ventilation",
"seat.set_backrest_recline",
"seat.set_legrest_height",
"ambient_light.set_power",
}
assert capabilities == expected_capabilities
window_actions = [
action for action in result["actions"]
if action["capability"] == "window.set_position"
]
assert {
action["target"]["zone"] for action in window_actions
} == {"front_left", "front_right", "rear_left", "rear_right"}
assert all(action["parameters"] == {"position_pct": 0} for action in window_actions)
assert not list((tmp_path / "user-modes").rglob("*.yaml"))
assert any(event.get("type") == "action.completed" for event in events)
assert all("request_token" not in str(event) for event in events)
def test_mid_autumn_cloud_scene_requires_authorized_flashing_light(
def test_mid_autumn_cloud_scene_requires_native_and_decorative_light_controls(
tmp_path: Path,
) -> None:
service = SeatDemoService(REPOSITORY_ROOT)
@@ -161,6 +179,45 @@ def test_mid_autumn_cloud_scene_requires_authorized_flashing_light(
"risk": "low",
"slot": 3,
},
{
"id": "ir-slot:light-strip:21",
"status": "learned",
"enabled": True,
"device_id": "light-strip",
"device_name": "灯带",
"object_names": ["灯带", "LED 灯条"],
"card_uid": "A1B2C3D4",
"action": "打开",
"action_names": ["打开", "开启"],
"risk": "low",
"slot": 21,
},
{
"id": "ir-slot:light-strip:23",
"status": "learned",
"enabled": True,
"device_id": "light-strip",
"device_name": "灯带",
"object_names": ["灯带", "LED 灯条"],
"card_uid": "A1B2C3D4",
"action": "切换为红色",
"action_names": ["切换为红色", "设置为红色"],
"risk": "low",
"slot": 23,
},
{
"id": "ir-slot:ambient-light:31",
"status": "learned",
"enabled": True,
"device_id": "ambient-light",
"device_name": "外设氛围灯",
"object_names": ["外设氛围灯", "氛围彩灯玩具"],
"card_uid": "B1C2D3E4",
"action": "打开",
"action_names": ["打开", "开启"],
"risk": "low",
"slot": 31,
},
]
)
handler = object.__new__(SeatDemoHandler)
@@ -235,5 +292,29 @@ def test_mid_autumn_cloud_scene_requires_authorized_flashing_light(
assert response["success"] is True
assert result["status"] == "succeeded"
assert result["execution_mode"] == "dry_run"
assert [item["slot"] for item in result["infrared_actions"]] == [2, 3]
assert [item["slot"] for item in result["infrared_actions"]] == [
2,
31,
21,
]
capabilities = {
action["capability"]
for action in result["actions"]
}
assert "climate.set_temperature" in capabilities
assert "ambient_light.set_power" in capabilities
assert "roof.set_shade_position" in capabilities
assert {
"climate.set_power",
"climate.set_temperature",
"climate.set_fan_level",
"climate.set_cycle_mode",
"window.set_position",
"seat.set_ventilation",
"seat.set_backrest_recline",
"seat.set_legrest_height",
"ambient_light.set_power",
"infrared.emit",
} <= capabilities
assert result["mode_profile_draft"]["operation_count"] == 16
assert result["mode_profile_draft"]["domains"][-1] == "self_extension"
@@ -15,7 +15,13 @@ from cognitive_agent.application.codex_assistant import (
_catalog_exact_scene_template_plan,
)
from cognitive_agent.application.mode_profile import ModeProfileRegistry
from cognitive_agent.application.scene_composer import normalize_scene_output
from cognitive_agent.application.scene_composer import (
normalize_scene_output,
scene_context_for_mode_creation,
scene_required_native_fallbacks,
scene_required_peripheral_refs,
scene_semantic_guidance,
)
from cognitive_agent.application.seat_service import REPOSITORY_ROOT, SeatDemoService
from cognitive_agent.application.self_extension import (
ExtensionIntegrationError,
@@ -212,6 +218,33 @@ def test_scene_composer_mixed_can_ir_preview_save_and_revalidate(
*template_external["requests"],
],
}
template_context = scene_context_for_mode_creation(
context,
"创建一个浪漫模式",
)
template_semantics = scene_semantic_guidance("浪漫模式")
template_native_fallbacks = scene_required_native_fallbacks(
"浪漫模式",
template_context,
include_default_bundle=True,
)
template_semantics["required_native_domains"] = [
str(item["domain"])
for item in template_native_fallbacks
]
template_semantics["native_fallback_requests"] = template_native_fallbacks
template_semantics["peripheral_policy"] = {
"required_refs": scene_required_peripheral_refs(
"浪漫模式",
template_context,
include_default_bundle=True,
),
}
template_context["scene_semantics"] = template_semantics
augmented_template = CodexAssistantService._with_required_scene_peripherals(
augmented_template,
template_context,
)
template_preview = handler._preview_mode_with_scene_peripherals(
"/api/requests/preview",
{
@@ -224,11 +257,30 @@ def test_scene_composer_mixed_can_ir_preview_save_and_revalidate(
on_commands_validated=None,
)
assert template_preview["status"] == "succeeded"
assert template_preview["mode_profile_draft"]["source_template_id"] == "romantic"
assert template_preview["mode_profile_draft"]["operation_count"] == (
len(template_preview["intent_ir"]["spec"]["actions"])
assert template_preview["mode_profile_draft"]["generation_source"] == (
"codex_scene_composition"
)
assert template_preview["mode_profile_draft"]["domains"][-1] == "self_extension"
template_actions = template_preview["intent_ir"]["spec"]["actions"]
assert template_preview["mode_profile_draft"]["operation_count"] == (
len(template_actions)
)
assert {
"climate",
"ambient_light",
"roof",
"self_extension",
}.issubset(template_preview["mode_profile_draft"]["domains"])
template_capabilities = {
action["capability"]
for action in template_actions
}
assert any(
capability.startswith("climate.")
for capability in template_capabilities
)
assert "ambient_light.set_power" in template_capabilities
assert "roof.set_shade_position" in template_capabilities
assert "infrared.emit" in template_capabilities
explicit = handler._execute_request_with_peripherals(
"/api/requests/preview",
+51 -21
View File
@@ -60,6 +60,27 @@ def _action_map(
}
def _assert_default_control_bundle(result: Mapping[str, object]) -> None:
actions = _action_map(result)
assert actions["climate.set_power", "vehicle"] == {"state": "on"}
assert ("climate.set_temperature", "vehicle") in actions
assert ("climate.set_fan_level", "vehicle") in actions
assert ("climate.set_cycle_mode", "vehicle") in actions
for target in ("front_left", "front_right", "rear_left", "rear_right"):
assert actions["window.set_position", target] == {"position_pct": 0}
assert ("roof.set_shade_position", "vehicle") in actions
assert actions["seat.set_ventilation", "passenger"] == {
"state": "on",
"level": 1,
}
assert ("seat.set_backrest_recline", "passenger") in actions
assert ("seat.set_legrest_height", "passenger") in actions
assert any(
capability.startswith("ambient_light.")
for capability, _target in actions
)
def _draft_reference(result: Mapping[str, object]) -> dict[str, str]:
draft = result.get("mode_profile_draft")
assert isinstance(draft, Mapping)
@@ -225,7 +246,7 @@ def test_all_production_scene_templates_complete_the_full_local_dry_run(
assert not list(user_root.rglob("*.yaml"))
def test_viewing_template_creates_an_unbound_audited_copy_without_codex(
def test_viewing_template_seeds_a_complete_unbound_preview_without_codex(
hybrid_service: tuple[SeatDemoService, AssistantCapabilityCatalog, Path],
) -> None:
service, catalog, user_root = hybrid_service
@@ -249,15 +270,16 @@ def test_viewing_template_creates_an_unbound_audited_copy_without_codex(
assert raw_result["execution_mode"] == "dry_run"
assert raw_result["hardware_execution_allowed"] is False
assert draft["name"] == "观影模式"
assert draft["generation_source"] == "scene_template_copy"
assert draft["source_template_id"] == "cinema"
assert draft["source_template_revision"]
assert draft["generation_source"] == "codex_scene_composition"
assert "source_template_id" not in draft
assert "source_template_revision" not in draft
assert "existing_mode_id" not in draft
assert "existing_mode_revision" not in draft
assert raw_result["mode_creation"]["source_template_id"] == "cinema"
assert passed_plans[0]["metadata"]["source"] == "scene_template"
assert "source_template_id" not in raw_result["mode_creation"]
assert "metadata" not in passed_plans[0]
_assert_default_control_bundle(raw_result)
assert events[0]["engine"] == "n7web-scene-template"
assert "独立草稿" in response["answer"]
assert "创建阶段不会下发" in response["answer"]
assert not list(user_root.rglob("*.yaml"))
client.start_structured_turn.assert_not_called()
client.start_turn.assert_not_called()
@@ -319,7 +341,6 @@ def test_purpose_descriptions_use_a_concise_name_and_zero_model_template_route(
conversation_id=f"conv-template-{case_id}",
)
template = service.scene_templates.get_template(template_id)
draft = raw_result["mode_profile_draft"]
assert response["route"] == "scene_template"
assert response["success"] is True
@@ -328,13 +349,11 @@ def test_purpose_descriptions_use_a_concise_name_and_zero_model_template_route(
assert raw_result["execution_mode"] == "dry_run"
assert raw_result["hardware_execution_allowed"] is False
assert draft["name"] == expected_name
assert draft["generation_source"] == "scene_template_copy"
assert draft["source_template_id"] == template_id
assert draft["operation_count"] == sum(
operation.domain != "seat" or operation.target_scope == "passenger"
for operation in template.operations
)
assert passed_plans[0]["metadata"]["template_id"] == template_id
assert draft["generation_source"] == "codex_scene_composition"
assert "source_template_id" not in draft
assert draft["operation_count"] == len(_actions(raw_result))
assert "metadata" not in passed_plans[0]
_assert_default_control_bundle(raw_result)
assert events[0]["engine"] == "n7web-scene-template"
assert not list(user_root.rglob("*.yaml"))
client.start_structured_turn.assert_not_called()
@@ -396,7 +415,14 @@ def test_seating_comfort_requests_preview_only_passenger_with_no_model_or_hardwa
assert result["execution_mode"] == "dry_run"
assert result["hardware_execution_allowed"] is False
assert result["mode_profile_draft"]["name"] == expected_name
assert result["mode_profile_draft"]["source_template_id"] == template_id
if "座椅" in message:
assert result["mode_profile_draft"]["source_template_id"] == template_id
else:
assert result["mode_profile_draft"]["generation_source"] == (
"codex_scene_composition"
)
assert "source_template_id" not in result["mode_profile_draft"]
_assert_default_control_bundle(result)
seat_actions = {
(capability, target): parameters
for (capability, target), parameters in _action_map(result).items()
@@ -524,8 +550,10 @@ def test_template_alias_keeps_the_user_requested_draft_name(
draft = raw_result["mode_profile_draft"]
assert response["route"] == "scene_template"
assert draft["name"] == "影院模式"
assert draft["source_template_id"] == "cinema"
assert passed_plans[0]["metadata"]["template_id"] == "cinema"
assert draft["generation_source"] == "codex_scene_composition"
assert "source_template_id" not in draft
assert "metadata" not in passed_plans[0]
_assert_default_control_bundle(raw_result)
client.start_structured_turn.assert_not_called()
client.start_turn.assert_not_called()
@@ -574,7 +602,7 @@ def test_template_copy_can_be_revised_twice_without_losing_sibling_actions(
assert shade_revised["execution_mode"] == final["execution_mode"] == "dry_run"
assert shade_revised["hardware_execution_allowed"] is False
assert final["hardware_execution_allowed"] is False
assert len(built_actions) == len(shade_actions) == len(final_actions) == 10
assert len(built_actions) == len(shade_actions) == len(final_actions) == 16
assert built_actions[shade_key] == {"position_pct": 0}
assert shade_actions[shade_key] == final_actions[shade_key] == {"position_pct": 100}
assert built_actions[temperature_key] == shade_actions[temperature_key] == {"temperature_c": 24}
@@ -592,7 +620,8 @@ def test_template_copy_can_be_revised_twice_without_losing_sibling_actions(
assert final_draft["draft_revision"] == 3
assert second_draft["generation_source"] == "draft_revision"
assert final_draft["generation_source"] == "draft_revision"
assert second_draft["source_template_id"] == final_draft["source_template_id"] == ("cinema")
assert "source_template_id" not in second_draft
assert "source_template_id" not in final_draft
assert "existing_mode_id" not in final_draft
assert not list(user_root.rglob("*.yaml"))
@@ -626,7 +655,8 @@ def test_saving_a_template_copy_never_mutates_template_and_registered_mode_wins_
assert list(user_root.rglob(f"{saved_profile['id']}.yaml"))
assert _template_hashes() == hashes_before
assert service.scene_templates.get_template("cinema").revision == template_revision
assert first_plans[0]["metadata"]["template_id"] == "cinema"
assert "metadata" not in first_plans[0]
_assert_default_control_bundle(built)
first_client.start_structured_turn.assert_not_called()
response, registered, events, second_client, passed_plans = _assistant_preview(
+18 -2
View File
@@ -138,7 +138,7 @@ function ndjsonResponse(events) {
};
}
function createHarness(events) {
function createHarness(events, executionMode = "dry_run") {
const document = new MockEventTarget();
const windowEvents = new MockEventTarget();
const body = new MockElement("body");
@@ -209,7 +209,7 @@ function createHarness(events) {
sandbox.removeEventListener = windowEvents.removeEventListener.bind(windowEvents);
sandbox.dispatchEvent = windowEvents.dispatchEvent.bind(windowEvents);
sandbox.CognitiveAgentConsole = {
getExecutionMode: () => "dry_run",
getExecutionMode: () => executionMode,
async refreshModeProfiles(modeProfileId) {
refreshedModeProfiles.push(modeProfileId);
},
@@ -367,6 +367,10 @@ test("assistant bridges request start and validated local controls to the consol
const cards = assistantArticle.querySelectorAll(".assistant-dispatched-controls");
assert.equal(cards.length, 1);
assert.equal(cards[0].open, true);
assert.equal(
cards[0].children[0].children[1].textContent,
"控制指令预览(未下发)",
);
assert.equal(
cards[0].querySelector(".assistant-dispatched-controls-count").textContent,
"3",
@@ -381,6 +385,7 @@ test("assistant bridges request start and validated local controls to the consol
["23°C", "关闭", "关闭"],
);
assert.match(rows[0].title, /实际控制指令:ac temp driver 23/);
assert.match(rows[0].title, /下发状态:仅预览 · 未下发/);
});
test("unsupported-control suggestions render once and click through as a new command", async () => {
@@ -544,6 +549,17 @@ function modeDraftEvents(sourceRequestId, temperatureC) {
];
}
test("mode creation always requests dry-run even while the console is in hardware mode", async () => {
const harness = createHarness(modeDraftEvents("req-draft-hardware-safe", 24), "hardware");
harness.input.value = "创建一个午休模式";
await harness.sandbox.CognitiveAgentAssistant.submit();
const request = JSON.parse(harness.requestCalls[0].options.body);
assert.equal(request.execution_mode, "dry_run");
assert.equal(Object.hasOwn(request, "hardware_client_id"), false);
});
test("a follow-up turn carries only the active authoritative draft reference", async () => {
const events = modeDraftEvents("req-draft-one", 24);
const harness = createHarness(events);
@@ -21,17 +21,17 @@ def test_console_contains_ai_assistant_surface():
assert 'placeholder="向 AI助手提问或交代工作"' in template
assert 'aria-label="发送消息"' in template
assert 'import "../assistant-chat.css";' in vite_entry
assert 'src="/assistant-chat.js?v=codex-assistant-34"' in html
assert 'src="/assistant-chat.js?v=codex-assistant-35"' 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-v328-nfc-mqtt"'
'const CACHE_VERSION = "cognitive-agent-pad-v329-mode-preview"'
in service_worker
)
assert '"/assistant-chat.css?v=codex-assistant-29"' in service_worker
assert '"/assistant-chat.js?v=codex-assistant-34"' in service_worker
assert '"/assistant-chat.css?v=codex-assistant-30"' in service_worker
assert '"/assistant-chat.js?v=codex-assistant-35"' in service_worker
def test_codex_frontend_uses_one_streaming_endpoint_and_agent_work_events():
@@ -91,7 +91,7 @@ def test_validated_controls_are_mirrored_into_a_compact_assistant_card():
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 'const headingText = previewOnly ? "控制指令预览(未下发)" : "下发控制指令"' 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
@@ -186,6 +186,7 @@ def test_codex_request_keeps_trusted_vehicle_context_without_legacy_model_routin
assert "history" not in request_body
assert "provider" not in request_body
assert "reasoning_effort" not in request_body
assert 'const executionMode = modeConfigurationPreview ? "dry_run"' in request_body
def test_starting_a_new_codex_conversation_rotates_the_conversation_id():
@@ -239,7 +240,9 @@ def test_assistant_mode_creation_previews_then_saves_to_the_compact_mode_strip()
assert 'event.payload.orchestration_route === "mode_profile_create_preview"' in assistant
assert "appendModeDraftState(" in assistant
assert "保存为功能模式" in assistant
assert "先试运行" in assistant
assert "重新校验(不下发)" in assistant
assert "创建阶段仅生成并校验控制方案,不会向 CAN 或红外外设发送指令" in assistant
assert 'label = String(action?.target?.name || action?.target?.device || "红外外设")' in assistant
save_flow = assistant.split(
"async function saveModeProfileDraft", 1
@@ -175,3 +175,35 @@ def test_climate_control_circulation_and_outlet_are_not_conflated(render_rows):
("空调出风模式", "吹面 / 吹脚"),
("空调风量", "2档"),
]
def test_mode_preview_localizes_external_device_name_and_action_parameters(render_rows):
rows = render_rows([
{
"domain": "self_extension",
"capability": "infrared.emit",
"target": {"device": "flashing-light", "name": "闪烁灯"},
"parameters": {"action": "打开", "slot": 7},
},
])
assert [(row["label"], row["value"]) for row in rows] == [
("闪烁灯", "动作:打开"),
]
assert "infrared.emit" not in json.dumps(rows, ensure_ascii=False)
def test_default_bundle_rows_show_power_window_and_grouped_passenger_comfort(render_rows):
rows = render_rows([
action("climate.set_power", {"state": "on"}, "vehicle"),
action("window.set_position", {"position_pct": 0}, "front_left"),
action("seat.set_ventilation", {"state": "on", "level": 1}, "passenger"),
action("seat.set_backrest_recline", {"position_pct": 45}, "passenger"),
action("seat.set_legrest_height", {"position_pct": 35}, "passenger"),
])
assert [(row["label"], row["value"]) for row in rows] == [
("空调电源", "开启"),
("主驾车窗", "完全关闭(目标开度 0%)"),
("副驾座椅舒适模式", "通风开启 1档 · 靠背 45% · 腿托 35%"),
]
@@ -37,11 +37,11 @@ def test_energy_monitor_assets_are_versioned_and_cached() -> None:
assert f'"{asset}"' in service_worker
assert 'import "../energy-monitor.css";' in vite_entry
assert "/energy-monitor.js?v=energy-monitor-5-performance" in html
assert html.index("/assistant-chat.js?v=codex-assistant-34") < html.index(
assert html.index("/assistant-chat.js?v=codex-assistant-35") < html.index(
"/energy-monitor.js?v=energy-monitor-5-performance"
)
assert (
'const CACHE_VERSION = "cognitive-agent-pad-v328-nfc-mqtt"'
'const CACHE_VERSION = "cognitive-agent-pad-v329-mode-preview"'
in service_worker
)
@@ -99,5 +99,5 @@ def test_performance_assets_have_a_new_cache_generation() -> None:
):
assert asset in html
assert f'"{asset}"' in worker
assert 'const CACHE_VERSION = "cognitive-agent-pad-v328-nfc-mqtt"' in worker
assert 'const CACHE_VERSION = "cognitive-agent-pad-v329-mode-preview"' in worker
assert '"/codepen-glass-theme.css?v=creative-cloud-6-performance"' in worker
+1 -1
View File
@@ -54,7 +54,7 @@ def test_theme_asset_versions_are_consistent_with_the_pwa_shell() -> None:
assert source_asset in main
assert f'"{cached_asset}"' in service_worker
assert 'import "./styles/design-system.css"' in main
assert 'const CACHE_VERSION = "cognitive-agent-pad-v328-nfc-mqtt"' in service_worker
assert 'const CACHE_VERSION = "cognitive-agent-pad-v329-mode-preview"' in service_worker
def test_reference_glass_theme_covers_the_complete_workspace() -> None:
+71 -7
View File
@@ -15,7 +15,10 @@ from cognitive_agent.application.codex_assistant import (
CodexAssistantService,
_mode_profile_create_parts,
)
from cognitive_agent.application.scene_composer import scene_semantic_guidance
from cognitive_agent.application.scene_composer import (
scene_required_native_fallbacks,
scene_semantic_guidance,
)
CONTROL_TERMS = ("空调", "座椅", "车窗", "遮阳帘", "氛围灯", "阅读灯", "出风口")
@@ -563,6 +566,21 @@ def test_exact_scene_template_keeps_native_base_and_adds_only_selected_ir_ref()
def _scene_context_with_flashing_light() -> dict[str, object]:
context = _scene_context()
context["capabilities"].append(
{
"id": "ambient_light.set_power",
"domain": "ambient_light",
"target_scopes": ["vehicle"],
"parameters": [
{
"name": "state",
"type": "string",
"allowed_values": ["on", "off"],
"required": True,
}
],
}
)
context["peripheral_actions"] = [
{
"ref": "ir:flash-open-000000000001",
@@ -588,6 +606,30 @@ def _scene_context_with_flashing_light() -> dict[str, object]:
"action_names": ["关闭", "熄灭"],
"semantic_tags": ["闪烁灯", "灯光"],
},
{
"ref": "ir:strip-open-000000000001",
"device_label": "灯带",
"object_names": ["灯带", "LED 灯条"],
"action_label": "打开",
"action_names": ["打开", "开启"],
"semantic_tags": ["灯带", "灯光"],
},
{
"ref": "ir:strip-red-0000000000002",
"device_label": "灯带",
"object_names": ["灯带", "LED 灯条"],
"action_label": "切换为红色",
"action_names": ["切换为红色", "设置为红色"],
"semantic_tags": ["灯带", "红色", "灯光"],
},
{
"ref": "ir:ambient-open-0000000001",
"device_label": "外设氛围灯",
"object_names": ["外设氛围灯", "氛围灯"],
"action_label": "打开",
"action_names": ["打开", "开启"],
"semantic_tags": ["氛围灯", "灯光"],
},
]
context["device_inventory"] = [
{
@@ -612,7 +654,7 @@ def _peripheral_refs(plan: Mapping[str, object]) -> list[str]:
]
def test_romantic_template_always_adds_authorized_flashing_light() -> None:
def test_romantic_template_always_adds_authorized_decorative_lights() -> None:
client = StructuredSceneClient()
client.structured_answers.put(
json.dumps(
@@ -647,14 +689,19 @@ def test_romantic_template_always_adds_authorized_flashing_light() -> None:
assert result["success"] is True
assert observed[0]["requests"][:2] == template["plan"]["requests"]
assert any(
request.get("domain") == "ambient_light"
for request in observed[0]["requests"]
)
assert _peripheral_refs(observed[0]) == [
"ir:flash-open-000000000001",
"ir:flash-red-0000000000002",
"ir:ambient-open-0000000001",
"ir:strip-open-000000000001",
]
assert "ir:flash-close-00000000003" not in _peripheral_refs(observed[0])
def test_mid_autumn_cloud_composition_receives_all_abilities_and_adds_flashing_light() -> None:
def test_mid_autumn_cloud_composition_adds_required_native_and_external_controls() -> None:
client = StructuredSceneClient()
assistant = CodexAssistantService(client)
observed: list[dict[str, object]] = []
@@ -681,18 +728,20 @@ def test_mid_autumn_cloud_composition_receives_all_abilities_and_adds_flashing_l
assert result["route"] == "scene_composer"
call_context = client.structured_calls[0]["application_context"]
assert len(call_context["capabilities"]) == len(scene_context["capabilities"])
assert len(call_context["peripheral_actions"]) == 3
assert len(call_context["peripheral_actions"]) == 6
assert call_context["scene_semantics"]["family"] == "festival_celebration"
assert call_context["scene_semantics"]["peripheral_policy"][
"required_refs"
] == [
"ir:flash-open-000000000001",
"ir:flash-red-0000000000002",
"ir:ambient-open-0000000001",
"ir:strip-open-000000000001",
]
assert "as many relevant" in client.thread_instructions[0]
assert _peripheral_refs(observed[0]) == [
"ir:flash-open-000000000001",
"ir:flash-red-0000000000002",
"ir:ambient-open-0000000001",
"ir:strip-open-000000000001",
]
@@ -1103,6 +1152,21 @@ def test_unknown_mode_uses_one_structured_turn_then_local_dry_run() -> None:
"cloud_input_utterance": "构建一个星夜模式",
}
expected_semantics = scene_semantic_guidance("星夜模式")
required_native = scene_required_native_fallbacks(
"星夜模式",
expected_context,
include_default_bundle=True,
)
expected_semantics["required_native_domains"] = ["climate", "roof"]
expected_semantics["required_native_controls"] = [
{
"capability": str(item["capability"]),
"target_scope": str(item["target_scope"]),
}
for item in required_native
]
expected_semantics["native_fallback_requests"] = required_native
expected_semantics["default_control_bundle_applied"] = True
expected_semantics["peripheral_policy"] = {
"available_action_count": 0,
"required_refs": [],
+128 -3
View File
@@ -11,6 +11,8 @@ from cognitive_agent.application.assistant_capabilities import AssistantCapabili
from cognitive_agent.application.scene_composer import (
build_scene_output_schema,
normalize_scene_output,
scene_has_explicit_control_object,
scene_required_native_fallbacks,
scene_required_peripheral_refs,
scene_semantic_guidance,
)
@@ -464,7 +466,7 @@ def test_learning_mode_is_grounded_as_free_semantic_composition() -> None:
@pytest.mark.parametrize("mode_name", ("浪漫模式", "中秋欢快模式"))
def test_romantic_and_mid_autumn_require_flashing_light_actions(
def test_romantic_and_mid_autumn_require_decorative_light_actions(
mode_name: str,
) -> None:
context = _context()
@@ -490,6 +492,27 @@ def test_romantic_and_mid_autumn_require_flashing_light_actions(
"action_label": "关闭",
"action_names": ["关闭", "熄灭"],
},
{
"ref": "ir:strip-open",
"device_label": "灯带",
"object_names": ["灯带", "LED 灯条"],
"action_label": "打开",
"action_names": ["打开", "开启"],
},
{
"ref": "ir:strip-red",
"device_label": "灯带",
"object_names": ["灯带", "LED 灯条"],
"action_label": "切换为红色",
"action_names": ["切换为红色", "设置为红色"],
},
{
"ref": "ir:ambient-open",
"device_label": "外设氛围灯",
"object_names": ["外设氛围灯", "氛围灯"],
"action_label": "打开",
"action_names": ["打开", "开启"],
},
]
required = scene_required_peripheral_refs(mode_name, context)
semantics = scene_semantic_guidance(mode_name)
@@ -497,7 +520,109 @@ def test_romantic_and_mid_autumn_require_flashing_light_actions(
context["scene_semantics"] = semantics
schema = build_scene_output_schema(context)
assert required == ["ir:flash-open", "ir:flash-red"]
assert schema["properties"]["requests"]["maxItems"] == 10
assert required == [
"ir:flash-open",
"ir:strip-open",
]
assert scene_required_peripheral_refs(
mode_name,
context,
include_default_bundle=True,
) == [
"ir:flash-open",
"ir:ambient-open",
"ir:strip-open",
]
assert schema["properties"]["requests"]["maxItems"] == 18
if "中秋" in mode_name:
assert semantics["family"] == "festival_celebration"
@pytest.mark.parametrize(
("mode_name", "expected_shade"),
(("浪漫模式", 0), ("中秋欢快模式", 100)),
)
def test_required_native_fallbacks_cover_climate_ambient_and_shade(
mode_name: str,
expected_shade: int,
) -> None:
context = _context()
context["capabilities"].append(
{
"id": "ambient_light.set_power",
"domain": "ambient_light",
"target_scopes": ["vehicle"],
"parameters": [
{
"name": "state",
"type": "string",
"allowed_values": ["on", "off"],
"required": True,
}
],
}
)
fallbacks = scene_required_native_fallbacks(mode_name, context)
assert [item["domain"] for item in fallbacks] == [
"climate",
"ambient_light",
"roof",
]
assert fallbacks[0]["capability"] == "climate.set_temperature"
assert fallbacks[1]["capability"] == "ambient_light.set_power"
assert fallbacks[2]["capability"] == "roof.set_shade_position"
assert fallbacks[2]["parameters"] == [
{"name": "position_pct", "value": expected_shade}
]
def test_name_only_scene_default_bundle_covers_all_required_native_controls() -> None:
service = SeatDemoService()
catalog = AssistantCapabilityCatalog(service)
context = catalog.scene_composition_context(catalog.snapshot())
fallbacks = scene_required_native_fallbacks(
"星夜模式",
context,
include_default_bundle=True,
)
semantics = scene_semantic_guidance("星夜模式")
semantics["native_fallback_requests"] = fallbacks
semantics["peripheral_policy"] = {"required_refs": []}
context["scene_semantics"] = semantics
assert [(item["capability"], item["target_scope"]) for item in fallbacks] == [
("climate.set_power", "vehicle"),
("climate.set_temperature", "vehicle"),
("climate.set_fan_level", "vehicle"),
("climate.set_cycle_mode", "vehicle"),
("window.set_position", "front_left"),
("window.set_position", "front_right"),
("window.set_position", "rear_left"),
("window.set_position", "rear_right"),
("roof.set_shade_position", "vehicle"),
("seat.set_ventilation", "passenger"),
("seat.set_backrest_recline", "passenger"),
("seat.set_legrest_height", "passenger"),
("ambient_light.set_power", "vehicle"),
]
assert build_scene_output_schema(context)["properties"]["requests"]["maxItems"] == 7
@pytest.mark.parametrize(
("message", "expected"),
(
("创建浪漫中秋模式", False),
("创建浪漫中秋模式,空调温度设置为25度", True),
("创建灯光主题模式", True),
("创建主驾浪漫模式", True),
("创建节日模式", False),
),
)
def test_scene_detects_whether_user_named_a_concrete_control_object(
message: str,
expected: bool,
) -> None:
assert scene_has_explicit_control_object(message, _context()) is expected
+2 -2
View File
@@ -111,7 +111,7 @@ def test_initialize_and_tool_catalog_expose_only_safe_arguments() -> None:
properties = tools[1]["inputSchema"]["properties"]
assert set(properties) == {"request_token", "plan"}
assert properties["plan"]["additionalProperties"] is False
assert properties["plan"]["properties"]["requests"]["maxItems"] == 12
assert properties["plan"]["properties"]["requests"]["maxItems"] == 20
assert not {
"approval_token",
"hardware_access_bypass",
@@ -149,7 +149,7 @@ def test_structured_plan_is_validated_and_forwarded_without_freeform_instruction
"plan",
[
{"requests": []},
{**_sample_plan(), "requests": _sample_plan()["requests"] * 13},
{**_sample_plan(), "requests": _sample_plan()["requests"] * 21},
{
**_sample_plan(),
"requests": [{**_sample_plan()["requests"][0], "raw_can": "01 02 03"}],
+4 -1
View File
@@ -31,7 +31,10 @@ def test_vue_vite_application_is_the_primary_frontend_entry() -> None:
assert package["scripts"]["build"] == "vite build"
assert 'import vue from "@vitejs/plugin-vue"' in vite
assert 'outDir: outputRoot' in vite
assert 'const CACHE_VERSION = "cognitive-agent-pad-v328-nfc-mqtt"' in service_worker
assert 'const CACHE_VERSION = "cognitive-agent-pad-v329-mode-preview"' in service_worker
assert 'createHash("sha256")' in vite
assert 'runtime-assets.json' in vite
assert 'rewriteRuntimeReferences' in vite
assert "unpkg.com" not in html
assert "cdn.jsdelivr.net" not in html