merge: sync feature/N7Web into feature/N7Web-Tech

# Conflicts:
#	apps/web-console/app.js
#	apps/web-console/assistant-chat.js
#	apps/web-console/index.html
#	apps/web-console/service-worker.js
#	src/cognitive_agent/server.py
#	tests/e2e/test_seat_web_api.py
#	tests/unit/test_assistant_frontend_contract.py
#	tests/unit/test_energy_monitor_frontend_contract.py
#	tests/unit/test_frontend_theme_contract.py
#	tests/unit/test_mode_scene_catalog.py
This commit is contained in:
Miayui
2026-09-16 15:03:54 +08:00
68 changed files with 7292 additions and 123 deletions
@@ -715,9 +715,16 @@ def test_every_catalog_mode_rejects_incompatible_explicit_seat_scopes(
for mode_index, mode in enumerate(modes):
assert isinstance(mode, Mapping)
profile = vehicle_service.mode_profiles.get_profile(str(mode["id"]), "denza_n7")
has_driver = any(
operation.domain == "seat" and operation.target_scope in {"driver", "front_left"}
for operation in profile.operations
)
for scope_index, scope in enumerate(
("主驾", "驾驶员", "后排", "二排", "前排", "所有座椅", "全车座椅")
):
if has_driver and scope in {"主驾", "驾驶员"}:
continue # Valid dual-seat subsets have a separate exact-action matrix.
message = f"请让{scope}进入{mode['name']}"
result = vehicle_service.preview_native_can(
message,
@@ -754,7 +761,7 @@ def test_every_catalog_mode_rejects_incompatible_explicit_seat_scopes(
def test_preview_and_process_share_mode_target_safety_gate(
vehicle_service: SeatDemoService,
) -> None:
message = "主驾开启小憩模式"
message = "主驾开启零重力模式"
direct = vehicle_service.process(
message,
provider="local",
@@ -754,7 +754,11 @@ def test_incomplete_ambiguous_or_unsupported_scene_never_creates_partial_draft(
assert result["status"] != "succeeded"
assert result.get("mode_profile_draft") is None
assert result.get("orchestration_route") != "mode_profile_create_preview"
# Seat policy can reject on the creation route before compiling any action.
assert not (result.get("intent_ir") or {}).get("spec", {}).get("actions")
assert not result.get("actions")
assert result.get("command_plan") is None
assert not result.get("command_plans")
assert result.get("validation")
failure = result.get("mode_creation_failure")
assert isinstance(failure, dict)
@@ -0,0 +1,471 @@
"""Creation selects one seat by default without remapping incompatible actuators."""
from __future__ import annotations
import json
import queue
from copy import deepcopy
from unittest.mock import MagicMock, patch
import pytest
from jsonschema import Draft202012Validator
from cognitive_agent.application.assistant_capabilities import AssistantCapabilityCatalog
from cognitive_agent.application.codex_assistant import ActiveCodexTurn, CodexAssistantService
from cognitive_agent.application.mode_profile import ModeProfileRegistry
from cognitive_agent.application.scene_composer import normalize_scene_output
from cognitive_agent.application.seat_service import REPOSITORY_ROOT, SeatDemoService
@pytest.fixture
def policy_service(tmp_path, monkeypatch):
for variable in (
"COGNITIVE_AGENT_HARDWARE_EXECUTION_ENABLED",
"COGNITIVE_AGENT_VEHICLE_CAN_ENABLED",
"COGNITIVE_AGENT_WIRED_PASSENGER_CAN_ENABLED",
"COGNITIVE_AGENT_VEHICLE_MQTT_ENABLED",
):
monkeypatch.setenv(variable, "false")
service = SeatDemoService(REPOSITORY_ROOT)
service.artifact_root = tmp_path / "artifacts"
user_root = tmp_path / "user-modes"
service.mode_profiles = ModeProfileRegistry(
REPOSITORY_ROOT / "config" / "modes", user_root=user_root,
)
try:
with (
patch.object(service.hardware_execution, "reserve", side_effect=AssertionError("physical CAN forbidden")) as reserve,
patch.object(service._vehicle_mqtt_transport, "bus", side_effect=AssertionError("physical MQTT forbidden")) as mqtt,
):
yield service, AssistantCapabilityCatalog(service), user_root
reserve.assert_not_called()
mqtt.assert_not_called()
finally:
service.close()
def _actions(result):
return (result.get("intent_ir") or {}).get("spec", {}).get("actions", [])
def _seat_scopes(result):
return {
action["target"]["instance"] for action in _actions(result)
if action["domain"] == "seat"
}
def _operation_key(operation):
target = operation.get("target_scope")
if target is None:
raw_target = operation.get("target") or {}
target = raw_target.get("instance") or raw_target.get("zone")
return (
operation["capability"], target,
json.dumps(operation["parameters"], ensure_ascii=False, sort_keys=True),
)
def _preview(service, message, *, plan=None, draft=None):
return service.preview_native_can(
message, provider="local", conversation_id="conv-seat-policy",
execution_mode="hardware", vehicle_id="denza_n7",
assistant_plan=plan, mode_profile_draft_context=draft,
)
def _assert_created(result, scopes):
assert result["status"] == "succeeded", result.get("validation")
assert result.get("mode_profile_draft"), result
assert result["execution_mode"] == "dry_run"
assert result["hardware_execution_allowed"] is False
assert _seat_scopes(result) == set(scopes)
schedule = result.get("schedule")
if schedule:
for phase in schedule["phases"]:
assert {
op["target_scope"] for op in phase["operations"] if op["domain"] == "seat"
} <= set(scopes)
assert result["mode_profile_draft"]["schedule"] == schedule
def _assistant_preview(service, catalog, message, *, client=None):
if client is None:
client = MagicMock()
client.available = False
assistant = CodexAssistantService(client)
snapshot = catalog.snapshot(vehicle_id="denza_n7")
fast = catalog.fast_answer(message, snapshot)
results, events = [], []
def execute(instruction, progress, assistant_plan=None):
result = _preview(service, instruction, plan=assistant_plan)
results.append(result)
return result
response = assistant.stream(
message, conversation_id="conv-seat-policy", principal="seat-policy-test", history=[],
authoritative_answer=fast.answer if fast else None,
emit=lambda event: events.append(event) or True,
execute_control=execute, capability_snapshot=snapshot,
capability_context=catalog.compact_context(snapshot),
scene_capability_context=catalog.scene_composition_context(snapshot),
control_terms=catalog.control_terms(snapshot),
)
assert len(results) == 1, (response, results)
return response, results[0], client
@pytest.mark.parametrize("name", [
"阅读模式", "观影模式", "办公模式", "座椅舒适模式", "放松模式", "座椅动态模式", "上车预热模式",
])
@pytest.mark.parametrize(("prefix", "scopes"), [
("", {"passenger"}), ("主驾", {"driver"}), ("主驾和副驾", {"driver", "passenger"}),
])
def test_registered_modes_use_selected_seat_through_assistant_fast_path(policy_service, name, prefix, scopes):
service, catalog, user_root = policy_service
response, preview, client = _assistant_preview(service, catalog, f"构建一个{prefix}{name}")
assert response["success"] is True, (response, preview)
_assert_created(preview, scopes)
client.start_thread.assert_not_called()
client.start_turn.assert_not_called()
client.start_structured_turn.assert_not_called()
assert not list(user_root.rglob("*.yaml"))
@pytest.mark.parametrize(("message", "scopes"), [
("构建一个两个座椅的阅读模式", {"driver", "passenger"}),
("构建一个两张座椅的阅读模式", {"driver", "passenger"}),
("构建一个两座椅的阅读模式", {"driver", "passenger"}),
("构建一个前排两座的阅读模式", {"driver", "passenger"}),
("构建一个副驾驶席阅读模式", {"passenger"}),
("构建一个副驾驶位阅读模式", {"passenger"}),
("构建一个副驾驶座椅阅读模式", {"passenger"}),
("构建一个两个座椅的动态模式", {"driver", "passenger"}),
("构建一个两张座椅的动态模式", {"driver", "passenger"}),
("构建一个两座椅的动态模式", {"driver", "passenger"}),
("构建一个前排两座的动态模式", {"driver", "passenger"}),
("构建一个副驾驶座椅动态模式", {"passenger"}),
])
def test_registered_seat_aliases_select_correct_targets_without_model(policy_service, message, scopes):
service, catalog, _ = policy_service
response, preview, client = _assistant_preview(service, catalog, message)
assert response["success"] is True, (
response["answer"], preview.get("validation"), preview.get("mode_profile_draft"),
)
_assert_created(preview, scopes)
assert response["route"] == "scene_template"
client.start_structured_turn.assert_not_called()
client.start_turn.assert_not_called()
@pytest.mark.parametrize("template_id", [
"reading", "cinema", "camping", "quiet-night", "reception", "romantic",
"winter-warmth", "summer-cooling", "long-trip-comfort", "relaxation", "seat-comfort",
"seat-dynamic", "boarding-warmup",
])
@pytest.mark.parametrize(("prefix", "scope"), [("", "passenger"), ("主驾", "driver")])
def test_trusted_template_selection_keeps_nonseat_devices_and_native_parameters(policy_service, template_id, prefix, scope):
service, _, _ = policy_service
template = service.scene_templates.get_template(template_id)
original = deepcopy(template.plan())
result = _preview(service, f"构建一个{prefix}{template.name}", plan=template.plan())
_assert_created(result, {scope})
raw_operations = (
[op for phase in original["schedule"]["phases"] for op in phase["operations"]]
if original.get("schedule") else [op.to_yaml() for op in template.operations]
)
expected = [
_operation_key(op) for op in raw_operations
if op["domain"] != "seat" or op["target_scope"] == scope
]
assert sorted(map(_operation_key, _actions(result))) == sorted(expected)
assert template.plan() == original, "projecting a request must not mutate the registered template"
def _model_output(scopes, *, dynamic=False):
requests = []
if "driver" in scopes:
requests.append({
"capability": "seat.set_driver_massage_native", "target_scope": "driver",
"parameters": {"action": "mode", "mode": "relax"},
})
if "passenger" in scopes:
requests.append({
"capability": "seat.set_backrest_recline", "target_scope": "passenger",
"parameters": {"position_pct": 45},
})
requests.append({
"capability": "climate.set_temperature", "target_scope": "vehicle",
"parameters": {"temperature_c": 24},
})
if not dynamic:
return {"requests": requests, "ambiguities": [], "confidence": 0.95}
return {
"requests": [], "ambiguities": [], "confidence": 0.95,
"schedule": {
"repeat_count": 2,
"phases": [
{"id": "relax", "name": "舒缓", "duration_ms": 30000, "requests": deepcopy(requests)},
{"id": "support", "name": "支撑", "duration_ms": 60000, "requests": deepcopy(requests)},
],
},
}
@pytest.mark.parametrize("dynamic", [False, True])
@pytest.mark.parametrize(("prefix", "scopes"), [
("", {"passenger"}), ("主驾", {"driver"}), ("主驾和副驾", {"driver", "passenger"}),
])
def test_unregistered_model_plan_preserves_correct_explicit_or_default_targets(policy_service, dynamic, prefix, scopes):
service, catalog, _ = policy_service
plan = normalize_scene_output(
_model_output(scopes, dynamic=dynamic), catalog.scene_composition_context(), dynamic=dynamic,
)
dynamic_label = "动态" if dynamic else ""
result = _preview(service, f"构建一个{prefix}星河释压{dynamic_label}模式", plan=plan)
_assert_created(result, scopes)
assert result["mode_profile_draft"]["generation_source"] == "codex_scene_composition"
@pytest.mark.parametrize("dynamic", [False, True])
@pytest.mark.parametrize("prefix", ["", "主驾", "副驾"])
def test_unregistered_model_cannot_invent_cross_seat_actions(policy_service, dynamic, prefix):
service, catalog, user_root = policy_service
plan = normalize_scene_output(
_model_output({"driver", "passenger"}, dynamic=dynamic),
catalog.scene_composition_context(), dynamic=dynamic,
)
try:
result = _preview(service, f"构建一个{prefix}星河释压{'动态' if dynamic else ''}模式", plan=plan)
except ValueError:
pass
else:
assert result["status"] != "succeeded"
assert not result.get("mode_profile_draft")
assert not _actions(result)
assert result["hardware_execution_allowed"] is False
assert not list(user_root.rglob("*.yaml"))
@pytest.mark.parametrize(("prefix", "requested_scope", "unexpected_scope"), [
("", "passenger", "driver"), ("主驾", "driver", "passenger"),
])
def test_unexpected_seat_only_in_later_dynamic_phase_rejects_entire_draft(
policy_service, prefix, requested_scope, unexpected_scope,
):
service, catalog, user_root = policy_service
output = _model_output({requested_scope}, dynamic=True)
output["schedule"]["phases"][1]["requests"] = _model_output({unexpected_scope})["requests"]
plan = normalize_scene_output(output, catalog.scene_composition_context(), dynamic=True)
try:
result = _preview(service, f"构建一个{prefix}星河释压动态模式", plan=plan)
except ValueError:
pass
else:
assert result["status"] != "succeeded"
assert not result.get("mode_profile_draft")
assert not _actions(result)
assert not result.get("command_plans")
assert service.mode_schedule_runner.status("denza_n7")["status"] == "idle"
assert not list(user_root.rglob("*.yaml"))
@pytest.mark.parametrize("message", [
"构建一个办公模式,空调先24度持续30秒,然后24度持续60秒,循环2次",
"构建一个办公模式,主驾座椅分两个阶段动态舒缓",
])
def test_existing_mode_binding_cannot_authorize_cross_seat_model_schedule(policy_service, message):
service, catalog, user_root = policy_service
original = service.mode_profiles.get_profile("office", "denza_n7")
revision = service.mode_profiles.profile_revision(original)
plan = normalize_scene_output(
_model_output({"driver", "passenger"}, dynamic=True),
catalog.scene_composition_context(), dynamic=True,
)
try:
result = _preview(service, message, plan=plan)
except ValueError:
pass
else:
assert result["status"] != "succeeded", "binding an existing mode is not authorization for model-added seats"
assert not result.get("mode_profile_draft")
assert not _actions(result)
assert not result.get("command_plans")
assert service.mode_profiles.profile_revision(service.mode_profiles.get_profile("office", "denza_n7")) == revision
assert not list(user_root.rglob("*.yaml"))
@pytest.mark.parametrize(("message", "scope"), [
("构建一个办公模式,空调先24度持续30秒,然后24度持续60秒,循环2次", "passenger"),
("构建一个办公模式,主驾座椅分两个阶段动态舒缓", "driver"),
])
def test_existing_mode_binding_keeps_valid_selected_seat_schedule_saveable(policy_service, message, scope):
service, catalog, _ = policy_service
plan = normalize_scene_output(
_model_output({scope}, dynamic=True), catalog.scene_composition_context(), dynamic=True,
)
result = _preview(service, message, plan=plan)
_assert_created(result, {scope})
draft = result["mode_profile_draft"]
assert draft["existing_mode_id"] == "office"
assert draft["existing_mode_revision"]
saved = service.save_mode_profile_card(
draft["existing_mode_id"], draft["name"], draft["instruction"],
draft["source_request_id"], draft["vehicle_id"],
)
assert saved["status"] == "succeeded"
assert saved["mode_profile"]["schedule"] == result["schedule"]
executed = service.execute_mode_profile_card(
saved["mode_profile"]["id"], execution_mode="dry_run", vehicle_id="denza_n7",
)
assert executed["status"] == "succeeded"
assert _seat_scopes(executed) == {scope}
@pytest.mark.parametrize("dynamic", [False, True])
def test_default_seat_preference_does_not_force_seat_actions_into_nonseat_scene(policy_service, dynamic):
service, catalog, _ = policy_service
plan = normalize_scene_output(
_model_output(set(), dynamic=dynamic), catalog.scene_composition_context(), dynamic=dynamic,
)
result = _preview(service, f"构建一个星河恒温{'动态' if dynamic else ''}模式", plan=plan)
_assert_created(result, set())
assert all(action["domain"] == "climate" for action in _actions(result))
def test_explicit_atomic_mode_requirements_default_unqualified_seat_to_passenger(policy_service):
service, _, _ = policy_service
result = _preview(service, "构建一个自定义支撑模式,座椅靠背调整到45%,空调24度")
_assert_created(result, {"passenger"})
assert {_operation_key(action) for action in _actions(result)} == {
("seat.set_backrest_recline", "passenger", '{"position_pct": 45}'),
# Only the seat default changes; the existing local HVAC zone stays driver.
("climate.set_temperature", "driver", '{"temperature_c": 24}'),
}
@pytest.mark.parametrize("dynamic", [False, True])
def test_passenger_position_capability_is_not_retargeted_to_driver(policy_service, dynamic):
service, catalog, _ = policy_service
output = _model_output({"passenger"}, dynamic=dynamic)
requests = output["schedule"]["phases"][0]["requests"] if dynamic else output["requests"]
requests[0]["target_scope"] = "driver"
try:
plan = normalize_scene_output(output, catalog.scene_composition_context(), dynamic=dynamic)
result = _preview(service, f"构建一个主驾星河释压{'动态' if dynamic else ''}模式", plan=plan)
except ValueError:
return
assert result["status"] != "succeeded"
assert not result.get("mode_profile_draft")
assert not _actions(result)
@pytest.mark.parametrize(("message", "scopes"), [
("构建一个主驾空调与座椅舒适模式", {"passenger"}),
("构建一个主驾阅读灯与座椅舒适模式", {"passenger"}),
("构建一个副驾空调与主驾座椅舒适模式", {"driver"}),
("构建一个主驾阅读灯与副驾座椅舒适模式", {"passenger"}),
])
def test_other_device_zones_do_not_select_or_broaden_seat_scope(policy_service, message, scopes):
service, catalog, _ = policy_service
plan = normalize_scene_output(_model_output(scopes), catalog.scene_composition_context())
result = _preview(service, message, plan=plan)
_assert_created(result, scopes)
@pytest.mark.parametrize(("template_id", "prefix", "scope"), [
("reading", "", "passenger"), ("reading", "主驾", "driver"),
("seat-dynamic", "", "passenger"), ("seat-dynamic", "主驾", "driver"),
("boarding-warmup", "", "passenger"), ("boarding-warmup", "主驾", "driver"),
])
def test_save_reload_and_simulated_execution_preserve_selected_seat(policy_service, template_id, prefix, scope):
service, _, user_root = policy_service
template = service.scene_templates.get_template(template_id)
preview = _preview(service, f"构建一个{prefix}{template.name}", plan=template.plan())
_assert_created(preview, {scope})
draft = preview["mode_profile_draft"]
saved = service.create_mode_profile_card(
draft["name"], draft["instruction"], draft["source_request_id"], draft["vehicle_id"],
)
assert saved["status"] == "succeeded", saved
service.mode_profiles = ModeProfileRegistry(REPOSITORY_ROOT / "config" / "modes", user_root=user_root)
executed = service.execute_mode_profile_card(
saved["mode_profile"]["id"], conversation_id="conv-seat-policy-replay",
execution_mode="dry_run", vehicle_id="denza_n7",
)
assert executed["status"] == "succeeded", executed.get("validation")
assert executed["hardware_execution_allowed"] is False
assert _seat_scopes(executed) == {scope}
assert sorted(map(_operation_key, _actions(executed))) == sorted(map(_operation_key, _actions(preview)))
if preview.get("schedule"):
assert executed["schedule"] == preview["schedule"]
assert executed["schedule_execution"]["completed"] is True
assert all(
_seat_scopes(phase["result"]) <= {scope}
for phase in executed["schedule_execution"]["phase_results"]
)
@pytest.mark.parametrize(("initial_scope", "prefix"), [("driver", "主驾"), ("passenger", "")])
def test_unrelated_draft_edit_retains_original_seat_and_explicit_edit_may_extend_it(policy_service, initial_scope, prefix):
service, catalog, _ = policy_service
name = f"{prefix}星河释压模式"
built = _preview(
service, f"构建一个{name}",
plan=normalize_scene_output(_model_output({initial_scope}), catalog.scene_composition_context()),
)
_assert_created(built, {initial_scope})
draft = built["mode_profile_draft"]
revised = _preview(
service, f"把{name}的空调温度调整到26度",
draft={key: draft[key] for key in ("name", "source_request_id", "vehicle_id")},
)
_assert_created(revised, {initial_scope})
original_seat_ops = {_operation_key(a) for a in _actions(built) if a["domain"] == "seat"}
assert {_operation_key(a) for a in _actions(revised) if a["domain"] == "seat"} == original_seat_ops
added = "副驾座椅通风调整到1档" if initial_scope == "driver" else "主驾座椅通风调整到低档"
draft = revised["mode_profile_draft"]
extended = _preview(
service, f"把{name}的{added}",
draft={key: draft[key] for key in ("name", "source_request_id", "vehicle_id")},
)
_assert_created(extended, {"driver", "passenger"})
assert {_operation_key(a) for a in _actions(extended) if a["domain"] == "seat"} >= original_seat_ops
def _structured_client(output):
client = MagicMock()
client.available = True
client.status = "ready"
client.start_thread.return_value = "thread-seat-policy"
client.start_turn.side_effect = AssertionError("scene fell back to unconstrained agent")
def start(thread_id, text, *, application_context, output_schema, timeout=None):
Draft202012Validator(output_schema).validate(output)
turn = ActiveCodexTurn(thread_id=thread_id, turn_id="turn-seat-policy", events=queue.Queue())
turn.events.put({"method": "item/completed", "params": {"item": {
"id": "answer", "type": "agentMessage", "text": json.dumps(output, ensure_ascii=False),
}}})
turn.events.put({"method": "turn/completed", "params": {"turn": {
"id": turn.turn_id, "status": "completed", "items": [], "error": None,
}}})
return turn
client.start_structured_turn.side_effect = start
return client
@pytest.mark.parametrize(("prefix", "scopes"), [("", {"passenger"}), ("主驾", {"driver"})])
@pytest.mark.parametrize("dynamic", [False, True])
def test_unregistered_assistant_structured_path_uses_seat_policy(policy_service, prefix, scopes, dynamic):
service, catalog, _ = policy_service
client = _structured_client(_model_output(scopes, dynamic=dynamic))
response, preview, _ = _assistant_preview(
service, catalog, f"构建一个{prefix}星河释压{'动态' if dynamic else ''}模式", client=client,
)
assert response["success"] is True, (response, preview)
assert response["route"] == "scene_composer"
_assert_created(preview, scopes)
client.start_structured_turn.assert_called_once()
client.start_turn.assert_not_called()
@@ -1237,6 +1237,10 @@ def test_known_mode_name_reuses_repository_profile_even_if_a_plan_is_supplied(
) -> None:
service, user_root = mode_service
nap = service.mode_profiles.get_profile("nap", "denza_n7")
selected_operations = [
operation for operation in nap.operations
if operation.domain != "seat" or operation.target_scope in {"passenger", "front_right"}
]
result = _preview_without_hardware(
service,
"构建一个小憩模式",
@@ -1249,15 +1253,16 @@ def test_known_mode_name_reuses_repository_profile_even_if_a_plan_is_supplied(
draft = _assert_successful_preview(
result,
name=nap.name,
operation_count=len(nap.operations),
operation_count=len(selected_operations),
)
assert draft["generation_source"] == "registered_mode_profile"
assert draft["existing_mode_id"] == "nap"
expected = {
(operation.capability, operation.target_scope): operation.parameters
for operation in nap.operations
for operation in selected_operations
}
actual = _action_map(result)
assert {target for capability, target in actual if capability.startswith("seat.")} == {"passenger"}
for (capability, target), parameters in expected.items():
rendered_target = "passenger" if target == "front_right" and capability.startswith("seat.") else target
for name, value in parameters.items():
@@ -0,0 +1,670 @@
"""Dynamic scene creation through the real assistant, compiler and saved-mode APIs."""
from __future__ import annotations
import json
import queue
from copy import deepcopy
from threading import Event
from unittest.mock import MagicMock, patch
import pytest
from jsonschema import Draft202012Validator
from cognitive_agent.application.assistant_capabilities import AssistantCapabilityCatalog
from cognitive_agent.application.codex_assistant import ActiveCodexTurn, CodexAssistantService
from cognitive_agent.application.mode_profile import ModeProfileRegistry
from cognitive_agent.application.mode_schedule import ModeSchedule
from cognitive_agent.application.mode_schedule_service import public_schedule_status
from cognitive_agent.application.scene_composer import (
SceneCompositionError,
build_scene_output_schema,
normalize_scene_output,
)
from cognitive_agent.application.schedule_intent import needs_mode_schedule
from cognitive_agent.application.seat_service import REPOSITORY_ROOT, SeatDemoService
from cognitive_agent.infrastructure.can.access import HardwareAccessRequiredError
@pytest.fixture
def schedule_service(tmp_path, monkeypatch):
for variable in (
"COGNITIVE_AGENT_HARDWARE_EXECUTION_ENABLED", "COGNITIVE_AGENT_VEHICLE_CAN_ENABLED",
"COGNITIVE_AGENT_WIRED_PASSENGER_CAN_ENABLED", "COGNITIVE_AGENT_VEHICLE_MQTT_ENABLED",
):
monkeypatch.setenv(variable, "false")
service = SeatDemoService(REPOSITORY_ROOT)
service.artifact_root = tmp_path / "artifacts"
user_root = tmp_path / "user-modes"
service.mode_profiles = ModeProfileRegistry(REPOSITORY_ROOT / "config" / "modes", user_root=user_root)
with (
patch.object(service.hardware_execution, "reserve", side_effect=AssertionError("physical vehicle access forbidden")) as reserve,
patch.object(service._vehicle_mqtt_transport, "bus", side_effect=AssertionError("physical MQTT access forbidden")),
):
try:
yield service, AssistantCapabilityCatalog(service), user_root
finally:
service.close()
reserve.assert_not_called()
def assistant_preview(service, catalog, message, conversation="conv-schedule-flow", client=None):
if client is None:
client = MagicMock()
client.available = False
assistant = CodexAssistantService(client)
snapshot = catalog.snapshot(vehicle_id="denza_n7")
fast = catalog.fast_answer(message, snapshot)
results, events, passed_plans = [], [], []
def execute(instruction, progress, assistant_plan=None):
passed_plans.append(deepcopy(assistant_plan))
result = service.preview_native_can(
instruction, provider="local", conversation_id=conversation,
execution_mode="hardware", vehicle_id="denza_n7",
on_commands_validated=progress, assistant_plan=assistant_plan,
)
results.append(result)
return result
response = assistant.stream(
message, conversation_id=conversation, principal="schedule-test", history=[],
authoritative_answer=fast.answer if fast else None,
emit=lambda event: events.append(event) or True,
execute_control=execute, capability_snapshot=snapshot,
capability_context=catalog.compact_context(snapshot),
scene_capability_context=catalog.scene_composition_context(snapshot),
control_terms=catalog.control_terms(snapshot),
)
assert len(results) == 1, (response, results)
return response, results[0], events, passed_plans, client
def create_saved(service, catalog, message="构建一个上车预热的功能模式"):
response, preview, _, _, _ = assistant_preview(service, catalog, message)
assert response["success"] is True, (response, preview)
draft = preview["mode_profile_draft"]
saved = service.create_mode_profile_card(
draft["name"], draft["instruction"], draft["source_request_id"], draft["vehicle_id"],
)
assert saved["status"] == "succeeded"
return preview, saved["mode_profile"]
@pytest.mark.parametrize(("message", "template_id", "count", "repeats", "elapsed"), [
("构建一个座椅的动态模式", "seat-dynamic", 4, 5, 300000),
("构建一个上车预热的功能模式", "boarding-warmup", 7, 1, 420000),
("帮我创建一个座椅动态模式吧", "seat-dynamic", 4, 5, 300000),
("我想构建一个上车预热模式并预览", "boarding-warmup", 7, 1, 420000),
("构建一个上车暖舱模式", "boarding-warmup", 7, 1, 420000),
("构建一个空调预热模式", "boarding-warmup", 7, 1, 420000),
("构建一个动态座椅模式", "seat-dynamic", 4, 5, 300000),
("构建一个座椅交替放松模式", "seat-dynamic", 4, 5, 300000),
])
def test_user_temporal_requests_create_complete_multistage_template_without_model(
schedule_service, message, template_id, count, repeats, elapsed,
):
service, catalog, user_root = schedule_service
response, preview, events, plans, client = assistant_preview(service, catalog, message)
assert response["success"] is True, (response, preview)
assert response["route"] == "scene_template"
assert preview["status"] == "succeeded"
assert preview["execution_mode"] == "dry_run"
assert preview["hardware_execution_allowed"] is False
assert preview["orchestration_route"] == "mode_profile_create_preview"
draft = preview["mode_profile_draft"]
assert draft["source_template_id"] == template_id
assert draft["generation_source"] == "scene_template_copy"
assert draft["operation_count"] == count
expected_schedule = deepcopy(plans[0]["schedule"])
for phase in expected_schedule["phases"]:
phase["operations"] = [
op for op in phase["operations"]
if op["domain"] != "seat" or op["target_scope"] == "passenger"
]
assert draft["schedule"] == preview["schedule"] == expected_schedule
schedule = ModeSchedule.from_dict(preview["schedule"])
assert schedule.repeat_count == repeats
assert schedule.total_duration_ms == elapsed
actions = preview["intent_ir"]["spec"]["actions"]
assert len(actions) == count
assert len({action["id"] for action in actions}) == count
assert {item["phase_id"] for item in preview["schedule_phase_previews"]} == {phase.id for phase in schedule.phases}
assert len(preview["schedule_phase_previews"]) == 2
assert all(item["status"] == "succeeded" for item in preview["schedule_phase_previews"])
assert all(any(key.startswith(phase.id + "-") for key in preview["command_plans"]) for phase in schedule.phases)
assert {
op["target_scope"] for phase in schedule.phases for op in phase.operations
if op["domain"] == "seat"
} == {"passenger"}
assert events
client.start_thread.assert_not_called()
client.start_turn.assert_not_called()
client.start_structured_turn.assert_not_called()
assert not list(user_root.rglob("*.yaml"))
assert service.mode_schedule_runner.status("denza_n7")["status"] == "idle"
@pytest.mark.parametrize("message", ["构建一个座椅的动态模式", "构建一个上车预热的功能模式"])
def test_draft_trial_preserves_all_stages_repeats_and_virtual_time_without_thread(schedule_service, message):
service, catalog, _ = schedule_service
_, preview, _, _, _ = assistant_preview(service, catalog, message)
draft = preview["mode_profile_draft"]
with patch("cognitive_agent.application.mode_schedule.Thread", side_effect=AssertionError("dry-run spawned worker")):
trial = service.trial_mode_profile_draft(
draft["name"], draft["instruction"], draft["source_request_id"], draft["vehicle_id"],
)
assert trial["status"] == "succeeded"
assert trial["hardware_execution_allowed"] is False
assert trial["schedule"] == preview["schedule"]
execution = trial["schedule_execution"]
schedule = ModeSchedule.from_dict(preview["schedule"])
assert execution["completed"] is True
assert execution["virtual_elapsed_ms"] == schedule.total_duration_ms
assert execution["executed_phases"] == len(schedule.phases) * schedule.repeat_count
assert [step["phase_id"] for step in execution["phase_results"]] == [phase.id for phase in schedule.phases] * schedule.repeat_count
assert len(trial["intent_ir"]["spec"]["actions"]) == schedule.operation_count
@pytest.mark.parametrize("message", ["构建一个座椅的动态模式", "构建一个上车预热的功能模式"])
def test_save_reload_and_direct_execute_keep_temporal_semantics(schedule_service, message):
service, catalog, user_root = schedule_service
preview, public_profile = create_saved(service, catalog, message)
assert public_profile["schedule"] == preview["schedule"]
assert public_profile["behavior"] == "cyclic"
mode_id = public_profile["id"]
service.mode_profiles = ModeProfileRegistry(REPOSITORY_ROOT / "config" / "modes", user_root=user_root)
reloaded = service.mode_profiles.get_profile(mode_id, "denza_n7")
assert reloaded.schedule.to_dict() == preview["schedule"]
with patch("cognitive_agent.application.mode_schedule.Thread", side_effect=AssertionError("dry-run spawned worker")):
executed = service.execute_mode_profile_card(
mode_id, conversation_id="conv-schedule-execute", execution_mode="dry_run", vehicle_id="denza_n7",
)
assert executed["status"] == "succeeded", executed
assert executed["orchestration_route"] == "mode_schedule_execute"
assert executed["execution_mode"] == "dry_run"
assert executed["hardware_execution_allowed"] is False
assert "mode_profile_draft" not in executed
execution = executed["schedule_execution"]
assert execution["completed"] is True
assert execution["virtual_elapsed_ms"] == reloaded.schedule.total_duration_ms
assert len(execution["phase_results"]) == len(reloaded.schedule.phases) * reloaded.schedule.repeat_count
expected_phases = list(reloaded.schedule.phases) * reloaded.schedule.repeat_count
for expected, actual in zip(expected_phases, execution["phase_results"], strict=True):
phase_result = actual["result"]
assert phase_result["status"] == "succeeded"
assert phase_result["execution_mode"] == "dry_run"
actual_actions = phase_result["intent_ir"]["spec"]["actions"]
assert len(actual_actions) == len(expected.operations)
expected_params = sorted(json.dumps(op["parameters"], sort_keys=True) for op in expected.operations)
actual_params = sorted(json.dumps(op["parameters"], sort_keys=True) for op in actual_actions)
assert actual_params == expected_params
assert service.mode_schedule_runner.status("denza_n7")["status"] == "idle"
def test_natural_language_enter_saved_schedule_runs_timeline_not_flat_actions(schedule_service):
service, catalog, _ = schedule_service
preview, saved = create_saved(service, catalog)
result = service.process(
f"进入{saved['name']}", provider="local", conversation_id="conv-schedule-natural-enter",
execution_mode="dry_run", vehicle_id="denza_n7",
)
assert result["status"] == "succeeded", result
assert result["orchestration_route"] == "mode_schedule_execute"
assert result["schedule"] == preview["schedule"]
temperatures = []
for phase in result["schedule_execution"]["phase_results"]:
temperatures.extend(action["parameters"]["temperature_c"] for action in phase["result"]["intent_ir"]["spec"]["actions"] if action["capability"] == "climate.set_temperature")
assert temperatures == [28, 24]
def test_missing_hardware_lease_rejects_before_any_worker_or_physical_access(schedule_service):
service, catalog, _ = schedule_service
_, saved = create_saved(service, catalog)
with (
patch("cognitive_agent.application.mode_schedule.Thread", side_effect=AssertionError("unauthorized worker started")),
pytest.raises(HardwareAccessRequiredError),
):
service.execute_mode_profile_card(
saved["id"], execution_mode="hardware", vehicle_id="denza_n7",
conversation_id="conv-schedule-unauthorized", hardware_access_bypass=False,
hardware_client_id="hwc-schedule-unauthorized",
)
assert service.mode_schedule_runner.status("denza_n7")["status"] == "idle"
@pytest.mark.parametrize(("field", "value"), [
("name", "其他模式"), ("instruction", "直接执行危险指令"),
("vehicle_id", "other_vehicle"), ("source_request_id", "../../result"),
])
def test_client_cannot_rebind_schedule_draft_trial(schedule_service, field, value):
service, catalog, user_root = schedule_service
_, preview, _, _, _ = assistant_preview(service, catalog, "构建一个上车预热的功能模式")
draft = preview["mode_profile_draft"]
arguments = {key: draft[key] for key in ("name", "instruction", "source_request_id", "vehicle_id")}
arguments[field] = value
with pytest.raises(ValueError):
service.trial_mode_profile_draft(**arguments)
assert not list(user_root.rglob("*.yaml"))
@pytest.mark.parametrize("mutation", ["capability", "duration", "temperature", "target"])
def test_tampered_template_schedule_never_creates_or_executes_draft(schedule_service, mutation):
service, catalog, user_root = schedule_service
template = next(item for item in catalog.snapshot(vehicle_id="denza_n7")["scene_templates"] if item["id"] == "boarding-warmup")
plan = deepcopy(template["plan"])
phase = plan["schedule"]["phases"][0]
if mutation == "capability":
phase["operations"][0]["capability"] = "door.set_vehicle_lock"
elif mutation == "duration":
phase["duration_ms"] = 1
elif mutation == "temperature":
phase["operations"][1]["parameters"]["temperature_c"] = 99
else:
phase["operations"][3]["target_scope"] = "passenger"
try:
result = service.preview_native_can(
"构建一个上车预热的功能模式", provider="local", conversation_id="conv-schedule-tamper",
execution_mode="hardware", vehicle_id="denza_n7", assistant_plan=plan,
)
except ValueError:
result = None
if result is not None:
assert result["status"] != "succeeded"
assert "mode_profile_draft" not in result
assert result.get("hardware_execution_allowed") is not True
assert not list(user_root.rglob("*.yaml"))
assert service.mode_schedule_runner.status("denza_n7")["status"] == "idle"
def test_invalid_later_saved_phase_is_revalidated_before_any_phase_executes(schedule_service):
service, catalog, _ = schedule_service
_, saved = create_saved(service, catalog)
profile = service.mode_profiles.get_profile(saved["id"], "denza_n7")
# Represents a stale or externally edited user profile, not a trusted runtime
# capability change. Its structure remains valid, but the target is unsafe.
profile.schedule.phases[1].operations[0]["parameters"]["temperature_c"] = 99
with patch.object(service.mode_schedule_runner, "simulate", side_effect=AssertionError("invalid later phase reached executor")):
result = service.execute_mode_profile_card(
saved["id"], conversation_id="conv-schedule-stale-capability",
execution_mode="dry_run", vehicle_id="denza_n7",
)
assert result["status"] == "validation_failed", result
assert result["mode_creation_failure"]["code"] == "MODE_SCHEDULE_PHASE_INVALID"
assert "mode_profile_draft" not in result
assert service.mode_schedule_runner.status("denza_n7")["status"] == "idle"
def test_execution_failure_after_validated_first_phase_prevents_later_repeats(schedule_service):
service, catalog, _ = schedule_service
_, saved = create_saved(service, catalog, "构建一个主驾和副驾的座椅动态模式")
preview_method = service._preview_mode_schedule
virtual_method = service.native_can_runtime.execute_virtual
execution_started = False
executed_massage_modes = []
def validate_then_arm(*args, **kwargs):
nonlocal execution_started
result = preview_method(*args, **kwargs)
execution_started = True
return result
def fault_on_second_phase(command_plan, function_config):
if execution_started:
for op in function_config["spec"]["operations"]:
if op["capability"] == "seat.set_driver_massage_native":
executed_massage_modes.append(op["parameters"]["mode"])
if op["parameters"]["mode"] == "slack":
raise RuntimeError("injected lower-level actuator failure")
return virtual_method(command_plan, function_config)
with (
patch.object(service, "_preview_mode_schedule", side_effect=validate_then_arm),
patch.object(service.native_can_runtime, "execute_virtual", side_effect=fault_on_second_phase),
):
result = service.execute_mode_profile_card(
saved["id"], conversation_id="conv-schedule-injected-failure",
execution_mode="dry_run", vehicle_id="denza_n7",
)
execution = result["schedule_execution"]
assert result["status"] == "failed", result
assert execution["completed"] is False
assert execution["error"]
assert execution["executed_phases"] == 1
assert execution["virtual_elapsed_ms"] == 30000
assert execution["repeat_index"] == 0
assert len(execution["phase_results"]) == 2
assert executed_massage_modes == ["relax", "slack"]
assert result["hardware_execution_allowed"] is False
def structured_schedule():
return {
"requests": [], "ambiguities": [], "confidence": 0.96,
"schedule": {
"repeat_count": 2,
"phases": [
{"id": "warm", "name": "暖舱", "duration_ms": 30000,
"requests": [{"capability": "climate.set_temperature", "target_scope": "vehicle", "parameters": {"temperature_c": 28}}]},
{"id": "settle", "name": "恒温", "duration_ms": 60000,
"requests": [{"capability": "climate.set_temperature", "target_scope": "vehicle", "parameters": {"temperature_c": 24}}]},
],
},
}
def structured_client(output):
client = MagicMock()
client.available = True
client.status = "ready"
client.start_thread.return_value = "thread-schedule-test"
client.start_turn.side_effect = AssertionError("dynamic creation fell back to unstructured agent")
def start(thread_id, text, *, application_context, output_schema, timeout=None):
Draft202012Validator(output_schema).validate(output)
turn = ActiveCodexTurn(thread_id=thread_id, turn_id="turn-schedule-test", events=queue.Queue())
turn.events.put({"method": "item/completed", "params": {"item": {"id": "answer", "type": "agentMessage", "text": json.dumps(output, ensure_ascii=False)}}})
turn.events.put({"method": "turn/completed", "params": {"turn": {"id": turn.turn_id, "status": "completed", "items": [], "error": None}}})
return turn
client.start_structured_turn.side_effect = start
return client
@pytest.mark.parametrize("message", [
"构建一个星云动态模式,空调先28度持续30秒,然后24度持续60秒,循环2次",
"构建一个阅读模式,空调先28度持续30秒,然后24度持续60秒,循环2次",
"构建一个上车预热的功能模式,空调先28度持续30秒,然后24度持续60秒,循环2次",
])
def test_explicit_temporal_parameters_use_structured_composition_not_static_template(schedule_service, message):
service, catalog, user_root = schedule_service
output = structured_schedule()
client = structured_client(output)
response, preview, _, plans, _ = assistant_preview(service, catalog, message, client=client)
assert response["route"] == "scene_composer", response
assert response["success"] is True, (response, preview)
assert preview["mode_profile_draft"]["generation_source"] != "scene_template_copy"
client.start_structured_turn.assert_called_once()
client.start_turn.assert_not_called()
assert "schedule" in client.start_structured_turn.call_args.kwargs["output_schema"]["properties"]
assert preview["schedule"] == plans[0]["schedule"]
assert preview["schedule"]["repeat_count"] == 2
assert [phase["duration_ms"] for phase in preview["schedule"]["phases"]] == [30000, 60000]
assert [phase["operations"][0]["parameters"]["temperature_c"] for phase in preview["schedule"]["phases"]] == [28, 24]
assert ModeSchedule.from_dict(preview["schedule"]).total_duration_ms == 180000
assert not list(user_root.rglob("*.yaml"))
def test_dynamic_schema_normalization_preserves_complete_phase_contract(schedule_service):
_, catalog, _ = schedule_service
context = catalog.scene_composition_context(catalog.snapshot(vehicle_id="denza_n7"))
output = structured_schedule()
schema = build_scene_output_schema(context, dynamic=True)
Draft202012Validator(schema).validate(output)
plan = normalize_scene_output(output, context, dynamic=True)
assert plan["ambiguities"] == []
schedule = ModeSchedule.from_dict(plan["schedule"])
assert schedule.total_duration_ms == 180000
assert schedule.operation_count == 2
assert schedule.phases[0].operations[0]["domain"] == "climate"
assert schedule.phases[1].operations[0]["parameters"] == {"temperature_c": 24}
@pytest.mark.parametrize("mutation", [
"static-root", "unknown-root", "unknown-schedule", "unknown-phase", "unknown-capability",
"duplicate-phase", "duplicate-operation", "no-phases", "empty-phase", "bool-duration",
"bool-repeat", "long-duration", "long-total", "nan-parameter", "motion-capability",
])
def test_invalid_structured_dynamic_plans_fail_closed_during_normalization(schedule_service, mutation):
_, catalog, _ = schedule_service
context = catalog.scene_composition_context(catalog.snapshot(vehicle_id="denza_n7"))
output = structured_schedule()
schedule = output["schedule"]
phase = schedule["phases"][0]
if mutation == "static-root":
output["requests"] = deepcopy(phase["requests"])
elif mutation == "unknown-root":
output["execute_now"] = True
elif mutation == "unknown-schedule":
schedule["unbounded"] = True
elif mutation == "unknown-phase":
phase["skip_safety"] = True
elif mutation == "unknown-capability":
phase["requests"][0]["capability"] = "unknown.actuate"
elif mutation == "motion-capability":
phase["requests"][0] = {"capability": "seat.set_driver_axis_native", "target_scope": "driver", "parameters": {"duration_ms": 1000, "axis": "backrest", "direction": "backward"}}
elif mutation == "duplicate-phase":
schedule["phases"][1]["id"] = phase["id"]
elif mutation == "duplicate-operation":
phase["requests"].append(deepcopy(phase["requests"][0]))
elif mutation == "no-phases":
schedule["phases"] = []
elif mutation == "empty-phase":
phase["requests"] = []
elif mutation == "bool-duration":
phase["duration_ms"] = True
elif mutation == "bool-repeat":
schedule["repeat_count"] = True
elif mutation == "long-duration":
phase["duration_ms"] = 900001
elif mutation == "long-total":
schedule["repeat_count"] = 20
phase["duration_ms"] = 900000
elif mutation == "nan-parameter":
phase["requests"][0]["parameters"]["temperature_c"] = float("nan")
with pytest.raises(SceneCompositionError):
normalize_scene_output(output, context, dynamic=True)
def test_dynamic_ambiguity_produces_no_schedule_or_executable_actions(schedule_service):
_, catalog, _ = schedule_service
context = catalog.scene_composition_context(catalog.snapshot(vehicle_id="denza_n7"))
output = {"requests": [], "ambiguities": ["当前未开放该动态动作,请指定其他设备。"], "confidence": 0.2, "schedule": None}
Draft202012Validator(build_scene_output_schema(context, dynamic=True)).validate(output)
plan = normalize_scene_output(output, context, dynamic=True)
assert not plan["requests"]
assert "schedule" not in plan
assert plan["ambiguities"]
def test_model_schedule_with_unresolved_requirement_never_generates_successful_draft(schedule_service):
service, catalog, user_root = schedule_service
context = catalog.scene_composition_context(catalog.snapshot(vehicle_id="denza_n7"))
output = structured_schedule()
output["ambiguities"] = ["不能完成要求的主驾座椅绝对位置控制。"]
plan = normalize_scene_output(output, context, dynamic=True)
assert "metadata" not in plan
with pytest.raises(ValueError, match="未解决"):
service.preview_native_can(
"构建一个流光动态模式", provider="local", conversation_id="conv-schedule-ambiguous",
execution_mode="hardware", vehicle_id="denza_n7", assistant_plan=plan,
)
assert not list(user_root.rglob("*.yaml"))
assert service.mode_schedule_runner.status("denza_n7")["status"] == "idle"
@pytest.mark.parametrize(("seat", "allowed_targets"), [
("主驾", {"driver", "front_left"}), ("副驾", {"passenger", "front_right"}),
])
def test_single_seat_dynamic_execution_cannot_expand_to_both_seats(schedule_service, seat, allowed_targets):
service, catalog, _ = schedule_service
_, saved = create_saved(service, catalog, "构建一个主驾和副驾的座椅动态模式")
result = service.process(
f"只让{seat}进入{saved['name']}", provider="local", conversation_id="conv-schedule-single-seat",
execution_mode="dry_run", vehicle_id="denza_n7",
)
if result["status"] == "succeeded":
assert result["orchestration_route"] == "mode_schedule_execute"
execution = result["schedule_execution"]
for phase in execution["phase_results"]:
actions = phase["result"]["intent_ir"]["spec"]["actions"]
seat_targets = {action["target"].get("instance") or action["target"].get("zone") for action in actions if action["domain"] == "seat"}
assert seat_targets and seat_targets <= allowed_targets
else:
assert result["status"] in {"validation_failed", "needs_clarification", "needs_confirmation", "failed"}
assert not result.get("schedule_execution", {}).get("phase_results")
assert result.get("hardware_execution_allowed") is not True
def start_waiting_schedule(service):
"""Run only a no-device test callback, holding until the tested stop request."""
calls = []
entered = Event()
schedule = ModeSchedule.from_dict({
"schema": "cognitive-agent-mode-schedule/v1", "repeat_count": 1,
"phases": [
{"id": phase_id, "name": phase_id, "duration_ms": 900000,
"operations": [{"domain": "climate", "capability": "climate.set_temperature",
"target_scope": "vehicle", "operation": "set",
"parameters": {"temperature_c": temperature}, "reason": "test only"}]}
for phase_id, temperature in (("first", 28), ("second", 24))
],
})
def execute(phase):
calls.append(phase.id)
entered.set()
return {"status": "succeeded"}
service.mode_schedule_runner.start("denza_n7", schedule, execute, session_id="conv-stop-status")
assert entered.wait(2)
assert service.mode_schedule_runner.status("denza_n7")["status"] == "running"
return calls
def assistant_control(catalog, message, execute):
client = MagicMock()
client.available = False
assistant = CodexAssistantService(client)
snapshot = catalog.snapshot(vehicle_id="denza_n7")
events = []
response = assistant.stream(
message, conversation_id="conv-stop-status", principal="schedule-status-test", history=[],
emit=lambda event: events.append(event) or True,
execute_control=execute, capability_snapshot=snapshot,
capability_context=catalog.compact_context(snapshot),
scene_capability_context=catalog.scene_composition_context(snapshot),
control_terms=catalog.control_terms(snapshot),
)
client.start_thread.assert_not_called()
client.start_structured_turn.assert_not_called()
client.start_turn.assert_not_called()
return response, events
def test_dry_run_stop_does_not_cancel_a_running_vehicle_schedule(schedule_service):
service, _, _ = schedule_service
calls = start_waiting_schedule(service)
result = service.process(
"停止动态模式", provider="local", conversation_id="conv-stop-status",
execution_mode="dry_run", vehicle_id="denza_n7",
)
assert result["status"] == "succeeded"
assert result["schedule_execution"]["status"] == "simulated"
assert result["hardware_execution_allowed"] is False
assert service.mode_schedule_runner.status("denza_n7")["status"] == "running"
assert calls == ["first"]
persisted = json.loads((service.artifact_root / result["request_id"] / "result.json").read_text(encoding="utf-8"))
assert persisted["orchestration_route"] == "mode_schedule_stop"
assert persisted["trace_events"] == []
def test_hardware_stop_cancels_future_stages_and_persists_the_result(schedule_service):
service, _, _ = schedule_service
calls = start_waiting_schedule(service)
result = service.process(
"停止动态模式", provider="local", conversation_id="conv-stop-status",
execution_mode="hardware", vehicle_id="denza_n7",
)
assert result["status"] == "succeeded"
assert result["schedule_execution"]["status"] == "cancelled"
assert result["schedule_execution"]["completed"] is False
assert service.mode_schedule_runner.status("denza_n7")["status"] == "cancelled"
assert service.mode_schedule_runner.close(timeout=1)
assert calls == ["first"]
persisted = json.loads((service.artifact_root / result["request_id"] / "result.json").read_text(encoding="utf-8"))
assert persisted["orchestration_route"] == "mode_schedule_stop"
assert persisted["schedule_execution"]["status"] == "cancelled"
assert persisted["trace_events"] == []
@pytest.mark.parametrize(("execution_mode", "action_state", "runner_state"), [
("hardware", "cancelled", "cancelled"), ("dry_run", "simulated", "running"),
])
def test_assistant_stop_is_zero_model_success_and_preserves_execution_policy(
schedule_service, execution_mode, action_state, runner_state,
):
service, catalog, _ = schedule_service
calls = start_waiting_schedule(service)
def execute(message, progress):
return service.process(
message, provider="local", conversation_id="conv-stop-status",
execution_mode=execution_mode, vehicle_id="denza_n7",
)
response, events = assistant_control(catalog, "停止动态模式", execute)
assert response["success"] is True, response
assert response["route"] == "vehicle_control"
action = next(event for event in events if event["type"] == "action.completed")
assert action["success"] is True
assert action["state"] == action_state
assert action["payload"]["orchestration_route"] == "mode_schedule_stop"
assert service.mode_schedule_runner.status("denza_n7")["status"] == runner_state
assert calls == ["first"]
assert "已完成" not in response["answer"]
assert "未改变正在运行" in response["answer"] if execution_mode == "dry_run" else "已停止" in response["answer"]
def test_assistant_running_schedule_is_accepted_without_claiming_all_phases_completed(schedule_service):
service, catalog, _ = schedule_service
preview, profile = create_saved(service, catalog)
start_waiting_schedule(service)
state = service.mode_schedule_runner.status("denza_n7")
state["phase_results"] = [{"command_frames": ["sensitive-can-frame"]}]
def execute(message, progress):
return {
"status": "running", "execution_mode": "hardware", "hardware_execution_allowed": True,
"orchestration_route": "mode_schedule_execute", "schedule_execution": state,
"mode_profile": profile, "matched_modes": [profile], "schedule": preview["schedule"],
}
response, events = assistant_control(catalog, f"进入{profile['name']}", execute)
assert response["success"] is True, response
action = next(event for event in events if event["type"] == "action.completed")
assert action["success"] is True
assert action["state"] == "running"
assert action["payload"]["status"] == "running"
assert action["payload"]["schedule_execution"]["completed"] is False
assert "phase_results" not in action["payload"]["schedule_execution"]
assert "sensitive-can-frame" not in json.dumps(events, ensure_ascii=False)
assert "尚未完成所有阶段" in action["display_text"]
assert "尚未完成所有阶段" in response["answer"]
completed = next(event for event in events if event["type"] == "work.completed")
assert completed["state"] == "running"
assert "已完成" not in completed["display_text"]
def test_public_schedule_status_exposes_only_lifecycle_and_redacts_raw_error():
state = {
"run_id": "run-public", "vehicle_id": "denza_n7", "status": "failed",
"phase_index": 1, "repeat_index": 0, "completed": False,
"error": "secret-token /private/path/driver.dll CAN payload 123456",
"phase_results": [{"command_plan": {"data_hex": "deadbeef"}}],
"raw_error": {"token": "secret-token"}, "command_plans": {"internal": "data"},
"phase_count": {"nested": "unexpected"},
}
public = public_schedule_status(state)
assert public["status"] == "failed"
assert public["phase_index"] == 1
assert public["completed"] is False
assert public["error"] and public["error"] != state["error"]
assert set(public) == {"run_id", "vehicle_id", "status", "phase_index", "repeat_index", "completed", "error"}
for sensitive in ("secret-token", "driver.dll", "deadbeef", "123456"):
assert sensitive not in json.dumps(public, ensure_ascii=False)
@pytest.mark.parametrize("message", ["外循环通风模式", "内循环模式"])
def test_hvac_circulation_state_is_not_a_temporal_schedule(message):
assert needs_mode_schedule(message) is False
@@ -0,0 +1,278 @@
"""Natural-language edits cannot flatten or silently broaden dynamic phases."""
from __future__ import annotations
import hashlib
from copy import deepcopy
from pathlib import Path
from unittest.mock import patch
import pytest
from cognitive_agent.application.mode_profile import ModeProfileRegistry
from cognitive_agent.application.mode_schedule import ModeSchedule
from cognitive_agent.application.mode_schedule_edit import edit_schedule
from cognitive_agent.application.seat_service import REPOSITORY_ROOT, SeatDemoService
@pytest.fixture
def service(tmp_path):
with patch.dict(
"os.environ",
{
"COGNITIVE_AGENT_HARDWARE_EXECUTION_ENABLED": "false",
"COGNITIVE_AGENT_VEHICLE_CAN_ENABLED": "false",
"COGNITIVE_AGENT_WIRED_PASSENGER_CAN_ENABLED": "false",
"COGNITIVE_AGENT_VEHICLE_MQTT_ENABLED": "false",
},
):
instance = SeatDemoService(REPOSITORY_ROOT)
instance.artifact_root = tmp_path / "runs"
instance.mode_profiles = ModeProfileRegistry(
REPOSITORY_ROOT / "config" / "modes",
user_root=tmp_path / "user-modes",
)
try:
with patch.object(
instance.hardware_execution,
"reserve",
side_effect=AssertionError("schedule edit reached hardware"),
):
yield instance
finally:
instance.close()
def _build(service):
template = service.scene_templates.get_template("boarding-warmup")
result = service.preview_native_can(
"构建一个上车预热模式",
provider="local",
conversation_id="conv-schedule-edit",
execution_mode="hardware",
vehicle_id="denza_n7",
assistant_plan=template.plan(),
)
assert result["status"] == "succeeded", result.get("validation")
return result
def _edit(service, built, change):
draft = built["mode_profile_draft"]
reference = {key: draft[key] for key in ("name", "source_request_id", "vehicle_id")}
return service.preview_native_can(
f"把上车预热模式的{change}",
provider="local",
conversation_id="conv-schedule-edit",
execution_mode="hardware",
vehicle_id="denza_n7",
mode_profile_draft_context=reference,
)
def _parameters(schedule, index, capability, target="vehicle"):
return next(
op["parameters"]
for op in schedule["phases"][index]["operations"]
if op["capability"] == capability and op["target_scope"] == target
)
@pytest.mark.parametrize(
"change",
(
"第二阶段空调温度调整到26度",
"第2个阶段的空调温度变成26度",
"舒适恒温阶段空调温度改为26度",
),
)
def test_draft_temperature_edit_preserves_first_phase_and_timing(service, change):
built = _build(service)
original = deepcopy(built["schedule"])
revised = _edit(service, built, change)
assert revised["status"] == "succeeded", revised.get("mode_creation_failure")
expected = deepcopy(original)
_parameters(expected, 1, "climate.set_temperature")["temperature_c"] = 26
assert revised["schedule"] == expected
assert built["schedule"] == original
assert revised["mode_profile_draft"]["schedule"] == expected
assert revised["mode_profile_draft"]["draft_revision"] == 2
assert revised["mode_profile_draft"]["revises_source_request_id"] == built["request_id"]
assert revised["execution_mode"] == "dry_run"
assert revised["hardware_execution_allowed"] is False
@pytest.mark.parametrize(
("change", "expected_hold", "expected_repeat"),
(
("第二阶段持续时间调整为3分钟", 180000, 1),
("第二阶段保持30秒", 30000, 1),
("第二阶段时长改成2.5分钟", 150000, 1),
("循环次数改成3次", 300000, 3),
("重复两次", 300000, 2),
),
)
def test_draft_time_and_repeat_edits_keep_operations(
service, change, expected_hold, expected_repeat
):
built = _build(service)
revised = _edit(service, built, change)
assert revised["status"] == "succeeded", revised.get("mode_creation_failure")
assert revised["schedule"]["phases"][1]["duration_ms"] == expected_hold
assert revised["schedule"]["repeat_count"] == expected_repeat
for index in (0, 1):
assert (
revised["schedule"]["phases"][index]["operations"]
== built["schedule"]["phases"][index]["operations"]
)
def test_all_phases_temperature_edit_is_explicit_and_preserves_all_other_operations(service):
built = _build(service)
revised = _edit(service, built, "全部阶段空调温度调整到25度")
assert revised["status"] == "succeeded", revised.get("mode_creation_failure")
expected = deepcopy(built["schedule"])
for index in (0, 1):
_parameters(expected, index, "climate.set_temperature")["temperature_c"] = 25
assert revised["schedule"] == expected
@pytest.mark.parametrize(
"change",
(
"空调温度调整到25度",
"第三阶段空调温度调整到25度",
"第二阶段持续16分钟",
"循环21次",
"重复1.5次",
"持续30秒",
"第二阶段主驾靠背调整到90%",
"第二阶段空调温度调整到99度",
"第二阶段打开后备箱",
"第二阶段空调温度调整到26度并打开后备箱",
),
)
def test_ambiguous_unsafe_or_unbounded_edit_leaves_original_draft_unchanged(service, change):
built = _build(service)
original = deepcopy(built["schedule"])
revised = _edit(service, built, change)
assert revised["status"] in {"needs_confirmation", "validation_failed"}
assert not revised.get("mode_profile_draft")
assert revised["hardware_execution_allowed"] is False
source = service._load_mode_profile_draft_reference(
{
key: built["mode_profile_draft"][key]
for key in ("name", "source_request_id", "vehicle_id")
},
conversation_id="conv-schedule-edit",
mode_name="上车预热模式",
vehicle_id="denza_n7",
)[1]
assert source["schedule"] == original
def test_saved_schedule_edit_revalidates_and_persists_timeline_not_flat_actions(service):
built = _build(service)
draft = built["mode_profile_draft"]
saved = service.create_mode_profile_card(
draft["name"], draft["instruction"], draft["source_request_id"], draft["vehicle_id"]
)
mode_id = saved["mode_profile"]["id"]
profile = service.mode_profiles.get_profile(mode_id, "denza_n7")
path = Path(profile.source)
before = hashlib.sha256(path.read_bytes()).hexdigest()
rejected = service.preview_native_can(
"把上车预热模式的空调温度调整到26度",
provider="local",
conversation_id="conv-saved-schedule-edit",
execution_mode="hardware",
vehicle_id="denza_n7",
)
assert rejected["status"] == "needs_confirmation"
assert hashlib.sha256(path.read_bytes()).hexdigest() == before
revised = service.preview_native_can(
"把上车预热模式的第二阶段空调温度调整到26度",
provider="local",
conversation_id="conv-saved-schedule-edit",
execution_mode="hardware",
vehicle_id="denza_n7",
)
assert revised["status"] == "succeeded", revised.get("validation")
assert revised["orchestration_route"] == "mode_profile_update"
assert revised["hardware_execution_allowed"] is False
actual = service.mode_profiles.get_profile(mode_id, "denza_n7").schedule.to_dict()
expected = deepcopy(built["schedule"])
_parameters(expected, 1, "climate.set_temperature")["temperature_c"] = 26
assert actual == expected
assert revised["schedule"] == expected
assert revised["mode_profile"]["behavior"] == "cyclic"
def test_combined_phase_action_hold_then_repeat_revision_preserves_timeline(service):
built = _build(service)
revised = _edit(service, built, "第二阶段空调温度调整到26度,持续3分钟")
assert revised["status"] == "succeeded", revised.get("mode_creation_failure")
repeated = _edit(service, revised, "重复两次")
assert repeated["status"] == "succeeded", repeated.get("mode_creation_failure")
expected = deepcopy(built["schedule"])
_parameters(expected, 1, "climate.set_temperature")["temperature_c"] = 26
expected["phases"][1]["duration_ms"] = 180000
expected["repeat_count"] = 2
assert repeated["schedule"] == expected
assert repeated["mode_profile_draft"]["draft_revision"] == 3
draft = repeated["mode_profile_draft"]
saved = service.create_mode_profile_card(
draft["name"], draft["instruction"], draft["source_request_id"], draft["vehicle_id"]
)
assert (
service.mode_profiles.get_profile(
saved["mode_profile"]["id"], "denza_n7"
).schedule.to_dict()
== expected
)
def test_all_phases_is_not_mistaken_for_all_vehicle_zones(service):
source = service.scene_templates.get_template("boarding-warmup").plan()["schedule"]
for phase in source["phases"]:
for operation in phase["operations"]:
if operation["capability"] == "climate.set_temperature":
operation["target_scope"] = "passenger"
schedule = ModeSchedule.from_dict(source)
updated = edit_schedule(
schedule,
"全部阶段空调温度调整到25度",
parse_operations=lambda _: [
{
"domain": "climate",
"capability": "climate.set_temperature",
"target_scope": "vehicle",
"operation": "set",
"parameters": {"temperature_c": 25},
}
],
target_is_explicit=service._mode_update_target_is_explicit,
)
for index in (0, 1):
assert _parameters(updated.to_dict(), index, "climate.set_temperature", "passenger") == {
"temperature_c": 25
}
assert not any(
op["capability"] == "climate.set_temperature" and op["target_scope"] == "vehicle"
for op in updated.phases[index].operations
)
def test_phase_motor_duration_cannot_become_a_hold_duration(service):
built = _build(service)
result = _edit(service, built, "第二阶段副驾靠背向后移动3s")
assert result["status"] in {"needs_confirmation", "validation_failed"}
assert not result.get("mode_profile_draft")
def test_stage_selector_does_not_authorize_delayed_action_semantics(service):
built = _build(service)
result = _edit(service, built, "第二阶段30秒后空调温度调整到26度")
assert result["status"] == "needs_confirmation"
assert not result.get("mode_profile_draft")
@@ -273,7 +273,8 @@ def test_name_only_nap_request_reuses_the_verified_repository_profile(
draft = result["mode_profile_draft"]
assert isinstance(draft, dict)
assert draft["name"] == "小憩模式"
assert draft["operation_count"] == len(preset.operations) == 9
assert draft["operation_count"] == 9
assert len(preset.operations) == 10 # The registry retains both optional seat variants.
assert set(draft["domains"]) == {"seat", "climate", "roof", "ambient_light"}
assert draft["existing_mode_id"] == "nap"
assert isinstance(draft["existing_mode_revision"], str)
@@ -283,6 +284,7 @@ def test_name_only_nap_request_reuses_the_verified_repository_profile(
actions = _intent_actions(result)
assert {action["capability"] for action in actions} == {
operation.capability for operation in preset.operations
if operation.domain != "seat" or operation.target_scope in {"passenger", "front_right"}
} == EXPECTED_NAP_CAPABILITIES
expected_targets_and_parameters = {
operation.capability: (
@@ -292,6 +294,7 @@ def test_name_only_nap_request_reuses_the_verified_repository_profile(
operation.parameters,
)
for operation in preset.operations
if operation.domain != "seat" or operation.target_scope in {"passenger", "front_right"}
}
actual_targets_and_parameters = {
action["capability"]: (
@@ -0,0 +1,347 @@
"""Seat-aware built-in defaults must remain safe and preserve saved user intent."""
from __future__ import annotations
import hashlib
from collections.abc import Iterator
from pathlib import Path
from unittest.mock import patch
import pytest
from cognitive_agent.application.mode_profile import ModeProfileRegistry
from cognitive_agent.application.seat_service import REPOSITORY_ROOT, SeatDemoService
COMFORT_TEMPLATE_IDS = (
"reading",
"cinema",
"camping",
"quiet-night",
"reception",
"romantic",
"winter-warmth",
"summer-cooling",
"long-trip-comfort",
"relaxation",
"seat-comfort",
)
DRIVER_COMFORT_CAPABILITIES = {
"seat.set_driver_ventilation_native",
"seat.set_driver_heating_native",
"seat.set_driver_massage_native",
}
@pytest.fixture
def service(tmp_path: Path) -> Iterator[SeatDemoService]:
with patch.dict(
"os.environ",
{
"COGNITIVE_AGENT_HARDWARE_EXECUTION_ENABLED": "false",
"COGNITIVE_AGENT_VEHICLE_CAN_ENABLED": "false",
"COGNITIVE_AGENT_WIRED_PASSENGER_CAN_ENABLED": "false",
"COGNITIVE_AGENT_VEHICLE_MQTT_ENABLED": "false",
},
):
instance = SeatDemoService(REPOSITORY_ROOT)
instance.artifact_root = tmp_path / "runs"
instance.mode_profiles = ModeProfileRegistry(
REPOSITORY_ROOT / "config" / "modes",
user_root=tmp_path / "user-modes",
)
try:
with patch.object(
instance.hardware_execution,
"reserve",
side_effect=AssertionError("creating or previewing a mode reached real hardware"),
):
yield instance
finally:
instance.close()
def _preview(service: SeatDemoService, instruction: str, *, plan=None) -> dict:
result = service.preview_native_can(
instruction,
provider="local",
conversation_id="conv-registered-seat-coverage",
execution_mode="hardware",
vehicle_id="denza_n7",
assistant_plan=plan,
)
assert result["status"] == "succeeded", result.get("validation")
assert result["execution_mode"] == "dry_run"
assert result["hardware_execution_allowed"] is False
return result
def _action_map(result: dict) -> dict:
return {
(
action["capability"],
action["target"].get("instance") or action["target"].get("zone"),
): action["parameters"]
for action in result["intent_ir"]["spec"]["actions"]
}
@pytest.mark.parametrize("template_id", COMFORT_TEMPLATE_IDS)
@pytest.mark.parametrize(("seat_prefix", "targets"), (
("", {"passenger"}), ("主驾", {"driver"}),
("副驾", {"passenger"}), ("主驾和副驾", {"driver", "passenger"}),
))
def test_comfort_templates_select_only_requested_seat_without_mutating_seat_seeds(
service: SeatDemoService,
template_id: str,
seat_prefix: str,
targets: set[str],
) -> None:
template = service.scene_templates.get_template(template_id)
original_revision = template.revision
result = _preview(service, f"构建一个{seat_prefix}{template.name}", plan=template.plan())
actions = _action_map(result)
seat_keys = {key for key in actions if key[0].startswith("seat.")}
assert {target for _, target in seat_keys} == targets
assert {
operation.target_scope for operation in template.operations if operation.domain == "seat"
} == {"driver", "passenger"}
assert all(
operation.capability in DRIVER_COMFORT_CAPABILITIES
for operation in template.operations
if operation.domain == "seat" and operation.target_scope == "driver"
), "driver positional or timed movement must not enter static comfort defaults"
assert not any(
"massage" in capability for capability, target in seat_keys if target == "passenger"
)
assert actions == {
(operation.capability, operation.target_scope): operation.parameters
for operation in template.operations
if operation.domain != "seat" or operation.target_scope in targets
}
assert result["mode_profile_draft"]["generation_source"] == "scene_template_copy"
assert service.scene_templates.get_template(template_id).revision == original_revision
@pytest.mark.parametrize(
("mode_id", "mode_name"),
(
("office", "办公模式"),
("nap", "小憩模式"),
("meditation_space", "冥想空间模式"),
("leisure_lounger", "休闲躺椅模式"),
),
)
@pytest.mark.parametrize(("seat_prefix", "targets"), (
("", {"passenger"}), ("主驾", {"driver"}),
("副驾", {"passenger"}), ("主驾和副驾", {"driver", "passenger"}),
))
def test_registered_cabin_modes_select_only_requested_seat_on_creation(
service: SeatDemoService,
mode_id: str,
mode_name: str,
seat_prefix: str,
targets: set[str],
) -> None:
result = _preview(service, f"构建一个{seat_prefix}{mode_name}")
actions = _action_map(result)
assert {target for capability, target in actions if capability.startswith("seat.")} == targets
if "passenger" in targets:
assert ("seat.set_backrest_recline", "passenger") in actions
draft = result["mode_profile_draft"]
if seat_prefix:
assert "existing_mode_id" not in draft, "a selected-seat copy must not overwrite the original preset"
else:
assert draft["existing_mode_id"] == mode_id
assert draft["generation_source"] == "registered_mode_profile"
profile = service.mode_profiles.get_profile(mode_id, "denza_n7")
assert actions == {
(operation.capability, operation.target_scope): operation.parameters
for operation in profile.operations
if operation.domain != "seat" or operation.target_scope in targets
}
@pytest.mark.parametrize("template_id", ("fresh-air", "rain-defrost"))
def test_air_quality_and_visibility_templates_do_not_invent_seat_actions(
service: SeatDemoService,
template_id: str,
) -> None:
template = service.scene_templates.get_template(template_id)
result = _preview(service, f"构建一个{template.name}", plan=template.plan())
assert not any(capability.startswith("seat.") for capability, _ in _action_map(result))
@pytest.mark.parametrize(
"template_id",
(
"long-trip-comfort",
"summer-cooling",
"winter-warmth",
"reception",
"quiet-night",
"romantic",
),
)
def test_non_posture_templates_keep_both_seat_positions_unchanged(
service: SeatDemoService,
template_id: str,
) -> None:
template = service.scene_templates.get_template(template_id)
assert all(
"position_pct" not in operation.parameters and "duration_ms" not in operation.parameters
for operation in template.operations
if operation.domain == "seat"
)
def test_native_driver_and_passenger_thermal_levels_are_not_confused(
service: SeatDemoService,
) -> None:
for template_id in (
"reading",
"camping",
"quiet-night",
"reception",
"romantic",
"long-trip-comfort",
"seat-comfort",
):
template = service.scene_templates.get_template(template_id)
params = {(op.capability, op.target_scope): op.parameters for op in template.operations}
assert params[("seat.set_driver_ventilation_native", "driver")] == {
"state": "on",
"level": 2,
}
assert params[("seat.set_ventilation", "passenger")] == {"state": "on", "level": 1}
def test_upgraded_template_does_not_silently_add_seats_to_a_saved_custom_mode(
service: SeatDemoService,
tmp_path: Path,
) -> None:
# Simulate a previously saved user preference with the same name as a template.
original = _preview(service, "构建一个观影模式,空调温度调整到25度")
draft = original["mode_profile_draft"]
assert len(_action_map(original)) == 1
saved = service.create_mode_profile_card(
draft["name"],
draft["instruction"],
draft["source_request_id"],
draft["vehicle_id"],
)
assert saved["status"] == "succeeded"
paths = list((tmp_path / "user-modes").rglob("*.yaml"))
hashes = {path: hashlib.sha256(path.read_bytes()).hexdigest() for path in paths}
repeated = _preview(service, "构建一个观影模式")
assert repeated["mode_profile_draft"]["generation_source"] == "registered_mode_profile"
assert _action_map(repeated) == _action_map(original)
assert {path: hashlib.sha256(path.read_bytes()).hexdigest() for path in paths} == hashes
assert len(service.scene_templates.get_template("cinema").operations) == 11
def test_explicit_seat_only_requirement_is_not_expanded_to_other_seat_defaults(
service: SeatDemoService,
) -> None:
result = _preview(service, "构建一个阅读模式,副驾座椅通风关闭")
assert _action_map(result) == {("seat.set_ventilation", "passenger"): {"state": "off"}}
assert result["mode_profile_draft"]["generation_source"] == "explicit_scene_requirements"
@pytest.mark.parametrize("mode_id", ("office", "nap", "meditation_space", "leisure_lounger"))
@pytest.mark.parametrize(("seat", "spoken_seat"), (("driver", "主驾"), ("passenger", "副驾驶")))
def test_explicit_single_seat_invocation_selects_without_mutating_cabin_mode(
service: SeatDemoService,
mode_id: str,
seat: str,
spoken_seat: str,
) -> None:
profile = service.mode_profiles.get_profile(mode_id, "denza_n7")
original_revision = service.mode_profiles.profile_revision(profile)
expected = {
(operation.capability, operation.target_scope): operation.parameters
for operation in profile.operations
if operation.domain != "seat" or operation.target_scope == seat
}
result = service.preview_native_can(
f"请让{spoken_seat}进入{profile.name}",
provider="local",
conversation_id="conv-explicit-seat-mode-selection",
execution_mode="dry_run",
vehicle_id="denza_n7",
)
assert result["status"] == "succeeded", result.get("validation")
assert _action_map(result) == expected
assert (
service.mode_profiles.profile_revision(
service.mode_profiles.get_profile(mode_id, "denza_n7"),
)
== original_revision
)
assert any(
operation.target_scope != seat
for operation in service.mode_profiles.get_profile(mode_id, "denza_n7").operations
if operation.domain == "seat"
), "single-seat execution must not edit the persisted dual-seat profile"
@pytest.mark.parametrize(
("mode_id", "spoken_seat"),
[
(mode_id, seat)
for mode_id in ("zero_gravity", "easy_entry_exit", "rocking_horse", "office")
for seat in ("主驾", "后排", "所有座椅")
if (mode_id, seat) != ("office", "主驾")
],
)
def test_mode_invocation_still_rejects_genuinely_unsupported_seat_scopes(
service: SeatDemoService,
mode_id: str,
spoken_seat: str,
) -> None:
profile = service.mode_profiles.get_profile(mode_id, "denza_n7")
result = service.preview_native_can(
f"请让{spoken_seat}进入{profile.name}",
provider="local",
conversation_id="conv-explicit-seat-mode-rejection",
execution_mode="dry_run",
vehicle_id="denza_n7",
)
assert result["status"] in {"no_action", "needs_confirmation"}
assert _action_map(result) == {}
assert any(
issue.get("reason") == "mode_target_scope_mismatch"
for issue in result["intent_ir"]["spec"].get("ambiguities", [])
)
@pytest.mark.parametrize(
"message",
("创建一个主驾办公模式", "修改办公模式,只要主驾", "主驾办公模式"),
)
def test_mode_seat_selection_requires_an_explicit_invocation_not_configuration(
service: SeatDemoService,
message: str,
) -> None:
profile = service.mode_profiles.get_profile("office", "denza_n7")
assert service._explicit_mode_seat_selection(profile, message) is None
@pytest.mark.parametrize("mode_id", ("office", "nap", "meditation_space", "leisure_lounger"))
def test_unscoped_mode_invocation_keeps_registered_native_driver_actions(
service: SeatDemoService,
mode_id: str,
) -> None:
profile = service.mode_profiles.get_profile(mode_id, "denza_n7")
result = service.preview_native_can(
f"进入{profile.name}",
provider="local",
conversation_id="conv-mode-native-seat-preserved",
execution_mode="dry_run",
vehicle_id="denza_n7",
)
assert result["status"] == "succeeded"
assert _action_map(result) == {
(operation.capability, operation.target_scope): operation.parameters
for operation in profile.operations
}
@@ -0,0 +1,250 @@
from __future__ import annotations
from unittest.mock import patch
import pytest
from cognitive_agent.application.assistant_capabilities import AssistantCapabilityCatalog
from cognitive_agent.application.mode_profile import ModeProfileRegistry
from cognitive_agent.application.scene_composer import normalize_scene_output
from cognitive_agent.application.seat_service import REPOSITORY_ROOT, SeatDemoService
from cognitive_agent.application.semantic_ir import resolve_scene_seat_scopes
@pytest.fixture(scope="module")
def scene_service(tmp_path_factory):
service = SeatDemoService()
service.artifact_root = tmp_path_factory.mktemp("scene-explicit-seat-scope")
service.mode_profiles = ModeProfileRegistry(
REPOSITORY_ROOT / "config" / "modes",
user_root=tmp_path_factory.mktemp("scene-explicit-seat-user-modes"),
)
try:
yield service, AssistantCapabilityCatalog(service).scene_composition_context()
finally:
service.close()
def _plan(context, scopes):
requests = []
if "driver" in scopes:
requests.append({
"capability": "seat.set_driver_massage_native",
"target_scope": "driver",
"parameters": {"action": "mode", "mode": "relax"},
})
if "passenger" in scopes:
requests.append({
"capability": "seat.set_backrest_recline",
"target_scope": "passenger",
"parameters": {"position_pct": 45},
})
requests.append({
"capability": "climate.set_temperature",
"target_scope": "vehicle",
"parameters": {"temperature_c": 24},
})
return normalize_scene_output({
"requests": requests, "ambiguities": [], "confidence": 0.95,
}, context)
def _preview(
scene_service, message, scopes=("driver", "passenger"), *, execution_mode="hardware",
):
service, context = scene_service
with patch.object(
service.hardware_execution, "reserve",
side_effect=AssertionError("scene scope validation must not execute hardware"),
):
return service.preview_native_can(
message,
provider="local",
conversation_id="conv-explicit-scene-seat-scope",
execution_mode=execution_mode,
vehicle_id="denza_n7",
assistant_plan=_plan(context, scopes),
)
@pytest.mark.parametrize("message", (
"构建一个主驾座椅舒适模式",
"创建一个仅主驾舒适模式",
"创建一个只给驾驶员坐着放松的功能模式",
"构建一个副驾座椅舒适模式",
"构建一个仅副驾舒适模式",
"构建一个右前座椅舒适模式",
"构建一个后排座椅舒适模式",
"构建一个主驾座椅与副驾空调舒适模式",
"构建一个主驾阅读灯与副驾座椅舒适模式",
))
def test_explicit_scene_seat_scope_rejects_cross_seat_plan_atomically(scene_service, message):
result = _preview(scene_service, message)
assert result["status"] != "succeeded"
assert result.get("mode_profile_draft") is None
assert result["hardware_execution_allowed"] is False
assert result.get("command_plan") is None
assert result.get("command_plans") == {}
intent = result.get("intent_ir") or {}
assert (intent.get("spec") or {}).get("actions", []) == []
assert any(
"座位范围" in item["message"] or item.get("code") == "MODE_SEAT_SCOPE_UNAVAILABLE"
for item in result["validation"]
)
@pytest.mark.parametrize(("message", "scopes"), (
("构建一个主驾座椅舒适模式", ("driver",)),
("构建一个副驾座椅舒适模式", ("passenger",)),
("创建一个仅主驾舒适模式", ("driver",)),
("构建一个主驾和副驾座椅舒适模式", ("driver", "passenger")),
("构建一个主副驾座椅舒适模式", ("driver", "passenger")),
("构建一个云端座椅舒适模式", ("passenger",)),
("构建一个副驾空调与座椅舒适模式", ("passenger",)),
("构建一个座椅舒适与副驾阅读灯模式", ("passenger",)),
("构建一个主驾座椅与副驾空调舒适模式", ("driver",)),
("构建一个主驾阅读灯与副驾座椅舒适模式", ("passenger",)),
))
def test_scene_scope_preserves_supported_targets_without_borrowing_other_device_zones(
scene_service, message, scopes,
):
result = _preview(scene_service, message, scopes)
assert result["status"] == "succeeded", result.get("validation")
assert result["mode_profile_draft"]
assert result["execution_mode"] == "dry_run"
assert result["hardware_execution_allowed"] is False
actions = result["intent_ir"]["spec"]["actions"]
assert {
action["target"]["instance"]
for action in actions if action["domain"] == "seat"
} == set(scopes)
@pytest.mark.parametrize("message", (
"构建一个主驾舒适模式,副驾不要动",
"构建一个主驾舒适模式,不要动副驾",
))
def test_explicit_atomic_requirements_do_not_receive_scene_scope_relaxation(
scene_service, message,
):
result = _preview(scene_service, message, execution_mode="dry_run")
assert result["status"] != "succeeded"
assert result.get("mode_profile_draft") is None
assert result["hardware_execution_allowed"] is False
def test_unqualified_explicit_seat_requirement_defaults_to_passenger_without_model_additions(scene_service):
result = _preview(scene_service, "构建一个舒展模式,座椅靠背调整到45%", execution_mode="dry_run")
assert result["status"] == "succeeded", result.get("validation")
assert result["execution_mode"] == "dry_run"
assert result["hardware_execution_allowed"] is False
assert result["mode_profile_draft"]["operation_count"] == 1
actions = result["intent_ir"]["spec"]["actions"]
assert len(actions) == 1
assert actions[0]["capability"] == "seat.set_backrest_recline"
assert actions[0]["target"]["instance"] == "passenger"
assert actions[0]["parameters"] == {"position_pct": 45}
def test_explicit_atomic_requirements_override_label_without_adding_model_actions(scene_service):
result = _preview(scene_service, "构建一个主驾舒适模式,副驾靠背调整到45%")
assert result["status"] == "succeeded"
assert result["execution_mode"] == "dry_run"
actions = result["intent_ir"]["spec"]["actions"]
assert len(actions) == 1
assert actions[0]["capability"] == "seat.set_backrest_recline"
assert actions[0]["target"]["instance"] == "passenger"
assert actions[0]["parameters"]["position_pct"] == 45
def test_rear_window_scene_is_not_rejected_as_an_unsupported_seat(scene_service):
service, context = scene_service
plan = normalize_scene_output({
"requests": [{
"capability": "window.set_position",
"target_scope": "rear_right",
"parameters": {"position_pct": 45},
}],
"ambiguities": [], "confidence": 0.95,
}, context)
result = service.preview_native_can(
"构建一个后排换气模式", provider="local",
conversation_id="conv-rear-window-not-seat",
execution_mode="dry_run", vehicle_id="denza_n7", assistant_plan=plan,
)
assert result["status"] == "succeeded"
assert result["mode_profile_draft"]
@pytest.mark.parametrize(("text", "expected"), (
("进入主驾舒适模式", frozenset({"driver"})),
("进入仅副驾舒适模式", frozenset({"passenger"})),
("进入主副驾座椅舒适模式", frozenset({"driver", "passenger"})),
("进入主驾座椅与副驾的空调舒适模式", frozenset({"driver"})),
("进入副驾座椅与主驾的阅读灯模式", frozenset({"passenger"})),
("进入座椅舒适与右前车窗模式", None),
("进入座椅舒适与后排阅读灯模式", None),
("进入后排座椅舒适模式", frozenset({"unsupported"})),
))
def test_scene_scope_resolution_only_uses_seat_or_unqualified_scene_targets(text, expected):
assert resolve_scene_seat_scopes(text) == expected
def test_new_creation_cannot_borrow_draft_reference_to_bypass_explicit_scope(scene_service):
service, context = scene_service
built = _preview(scene_service, "构建一个主驾独享舒适模式", ("driver",))
reference = {key: built["mode_profile_draft"][key] for key in (
"name", "source_request_id", "vehicle_id",
)}
result = service.preview_native_can(
"构建一个主驾独享舒适模式", provider="local",
conversation_id="conv-explicit-scene-seat-scope", execution_mode="dry_run",
vehicle_id="denza_n7", assistant_plan=_plan(context, ("driver", "passenger")),
mode_profile_draft_context=reference,
)
assert result["status"] != "succeeded"
assert not result.get("mode_profile_draft")
@pytest.mark.parametrize(("initial_scope", "name", "change", "added_capability"), (
("driver", "主驾释压舒适模式", "副驾座椅通风调整为1档", "seat.set_ventilation"),
("passenger", "副驾释压舒适模式", "主驾座椅通风调整为低档", "seat.set_driver_ventilation_native"),
))
def test_explicit_revision_can_extend_old_label_scope_then_save_and_replay(
scene_service, initial_scope, name, change, added_capability,
):
service, _context = scene_service
built = _preview(scene_service, f"构建一个{name}", (initial_scope,))
source_draft = built["mode_profile_draft"]
reference = {key: source_draft[key] for key in (
"name", "source_request_id", "vehicle_id",
)}
revised = service.preview_native_can(
f"把{name}的{change}", provider="local",
conversation_id="conv-explicit-scene-seat-scope", execution_mode="dry_run",
vehicle_id="denza_n7", mode_profile_draft_context=reference,
)
assert revised["status"] == "succeeded", revised.get("validation")
draft = revised["mode_profile_draft"]
assert draft["generation_source"] == "draft_revision"
assert draft["operation_count"] == source_draft["operation_count"] + 1
actions = revised["intent_ir"]["spec"]["actions"]
assert added_capability in {action["capability"] for action in actions}
assert {
action["target"]["instance"] for action in actions if action["domain"] == "seat"
} == {"driver", "passenger"}
saved = service.create_mode_profile_card(
draft["name"], draft["instruction"], draft["source_request_id"], draft["vehicle_id"],
)
assert saved["status"] == "succeeded"
replayed = service.execute_mode_profile_card(
saved["mode_profile"]["id"], execution_mode="dry_run", vehicle_id="denza_n7",
)
assert replayed["status"] == "succeeded", replayed.get("validation")
assert {
(action["capability"], action["target"]["instance"])
for action in replayed["intent_ir"]["spec"]["actions"] if action["domain"] == "seat"
} == {
(action["capability"], action["target"]["instance"])
for action in actions if action["domain"] == "seat"
}
+139 -5
View File
@@ -12,6 +12,7 @@ import pytest
from cognitive_agent.application.assistant_capabilities import AssistantCapabilityCatalog
from cognitive_agent.application.codex_assistant import CodexAssistantService
from cognitive_agent.application.mode_profile import ModeProfileRegistry
from cognitive_agent.application.scene_composer import normalize_scene_output
from cognitive_agent.application.seat_service import REPOSITORY_ROOT, SeatDemoService
@@ -94,6 +95,7 @@ def _assistant_preview(
client.available = False
assistant = CodexAssistantService(client)
snapshot = catalog.snapshot(vehicle_id="denza_n7")
fast_answer = catalog.fast_answer(message, snapshot)
events: list[dict[str, object]] = []
raw_results: list[dict[str, object]] = []
passed_plans: list[dict[str, object] | None] = []
@@ -122,6 +124,7 @@ def _assistant_preview(
conversation_id=conversation_id,
principal="scene-template-hybrid-test",
history=[],
authoritative_answer=fast_answer.answer if fast_answer else None,
emit=lambda event: events.append(event) or True,
execute_control=execute_control,
capability_snapshot=snapshot,
@@ -141,7 +144,7 @@ def test_all_production_scene_templates_complete_the_full_local_dry_run(
templates = snapshot["scene_templates"]
assert isinstance(templates, list)
assert len(templates) == 12
assert len(templates) == 15
assert all(template["assistant_callable"] is True for template in templates)
with patch.object(
@@ -166,11 +169,30 @@ def test_all_production_scene_templates_complete_the_full_local_dry_run(
assert result["orchestration_route"] == "mode_profile_create_preview"
draft = result["mode_profile_draft"]
assert draft["generation_source"] == "scene_template_copy"
assert draft["operation_count"] == template["operation_count"]
assert draft["source_template_id"] == template["id"]
assert draft["source_template_revision"] == template["template_revision"]
assert "existing_mode_id" not in draft
expected_requests = plan["requests"]
if plan.get("schedule"):
expected_schedule = deepcopy(plan["schedule"])
for phase in expected_schedule["phases"]:
phase["operations"] = [
op for op in phase["operations"]
if op["domain"] != "seat" or op["target_scope"] == "passenger"
]
assert draft["schedule"] == expected_schedule
expected_requests = [
{**op, "parameters": [{"name": key, "value": value}
for key, value in op["parameters"].items()]}
for phase in expected_schedule["phases"] for op in phase["operations"]
]
else:
expected_requests = [
request for request in expected_requests
if request["domain"] != "seat" or request["target_scope"] == "passenger"
]
assert draft["operation_count"] == len(expected_requests)
expected = {
(
str(request["capability"]),
@@ -184,7 +206,7 @@ def test_all_production_scene_templates_complete_the_full_local_dry_run(
sort_keys=True,
),
)
for request in plan["requests"]
for request in expected_requests
}
actual = {
(
@@ -308,7 +330,10 @@ def test_purpose_descriptions_use_a_concise_name_and_zero_model_template_route(
assert draft["name"] == expected_name
assert draft["generation_source"] == "scene_template_copy"
assert draft["source_template_id"] == template_id
assert draft["operation_count"] == len(template.operations)
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 events[0]["engine"] == "n7web-scene-template"
assert not list(user_root.rglob("*.yaml"))
@@ -339,6 +364,115 @@ def test_explicit_settings_on_a_purpose_scene_bypass_template_defaults(
client.start_turn.assert_not_called()
@pytest.mark.parametrize(
("message", "expected_name", "template_id"),
(
("构建一个座椅舒适的功能模式", "座椅舒适模式", "seat-comfort"),
("构建一个让人坐着放松的功能模式", "坐着放松模式", "relaxation"),
("请帮我创建一个适合乘坐舒适的模式吧", "乘坐舒适模式", "seat-comfort"),
("帮我构建一个让我们坐着舒服的功能模式", "坐着舒服模式", "seat-comfort"),
("创建一个座椅放松模式并预览", "座椅放松模式", "relaxation"),
("构建一个长途舒适模式", "长途舒适模式", "long-trip-comfort"),
),
)
def test_seating_comfort_requests_preview_only_passenger_with_no_model_or_hardware_call(
hybrid_service: tuple[SeatDemoService, AssistantCapabilityCatalog, Path],
message: str,
expected_name: str,
template_id: str,
) -> None:
service, catalog, user_root = hybrid_service
with patch.object(
service.hardware_execution,
"reserve",
side_effect=AssertionError("seat comfort preview reached hardware execution"),
):
response, result, _events, client, _plans = _assistant_preview(
service, catalog, message, conversation_id="conv-seat-comfort-preview",
)
assert response["route"] == "scene_template"
assert response["success"] is True
assert result["status"] == "succeeded"
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
seat_actions = {
(capability, target): parameters
for (capability, target), parameters in _action_map(result).items()
if capability.startswith("seat.")
}
assert {target for _capability, target in seat_actions} == {"passenger"}
assert all(
"driver_axis" not in capability and "driver_position" not in capability
for capability, _target in seat_actions
)
assert ("seat.set_driver_massage_native", "passenger") not in seat_actions
if template_id in {"seat-comfort", "long-trip-comfort"}:
assert seat_actions["seat.set_ventilation", "passenger"] == {
"state": "on", "level": 1,
}
client.start_structured_turn.assert_not_called()
client.start_turn.assert_not_called()
assert not list(user_root.rglob("*.yaml"))
@pytest.mark.parametrize("name", ("云端座椅舒展模式", "晚间乘坐释压模式"))
def test_unregistered_seat_scene_keeps_both_explicit_targets_through_native_merge(
hybrid_service: tuple[SeatDemoService, AssistantCapabilityCatalog, Path],
name: str,
) -> None:
service, catalog, user_root = hybrid_service
plan = normalize_scene_output({
"requests": [
{"capability": "seat.set_driver_massage_native", "target_scope": "driver",
"parameters": {"action": "mode", "mode": "relax"}},
{"capability": "seat.set_driver_heating_native", "target_scope": "driver",
"parameters": {"state": "on", "level": 2}},
{"capability": "seat.set_backrest_recline", "target_scope": "passenger",
"parameters": {"position_pct": 45}},
{"capability": "seat.set_heating", "target_scope": "passenger",
"parameters": {"state": "on", "level": 1}},
],
"ambiguities": [], "confidence": 0.95,
}, catalog.scene_composition_context())
with patch.object(
service.hardware_execution, "reserve",
side_effect=AssertionError("scene composition reached hardware execution"),
):
result = service.preview_native_can(
f"构建一个主驾和副驾的{name}", provider="local", conversation_id="conv-seat-generated",
execution_mode="hardware", vehicle_id="denza_n7", assistant_plan=plan,
)
assert result["status"] == "succeeded"
assert result["execution_mode"] == "dry_run"
assert result["mode_profile_draft"]["generation_source"] == "codex_scene_composition"
assert set(_action_map(result)) == {
("seat.set_driver_massage_native", "driver"),
("seat.set_driver_heating_native", "driver"),
("seat.set_backrest_recline", "passenger"),
("seat.set_heating", "passenger"),
}
assert not list(user_root.rglob("*.yaml"))
def test_unscoped_seat_instruction_inside_mode_creation_defaults_to_passenger(
hybrid_service: tuple[SeatDemoService, AssistantCapabilityCatalog, Path],
) -> None:
service, _catalog, _user_root = hybrid_service
result = service.preview_native_can(
"构建一个舒展模式,座椅靠背调整到45%", provider="local",
conversation_id="conv-seat-explicit-target", execution_mode="hardware",
vehicle_id="denza_n7",
)
assert result["status"] == "succeeded"
assert _action_map(result) == {
("seat.set_backrest_recline", "passenger"): {"position_pct": 45},
}
assert result.get("mode_profile_draft")
assert result["hardware_execution_allowed"] is False
def test_template_metadata_cannot_authorize_modified_or_stale_template_actions(
hybrid_service: tuple[SeatDemoService, AssistantCapabilityCatalog, Path],
) -> None:
@@ -440,7 +574,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) == 7
assert len(built_actions) == len(shade_actions) == len(final_actions) == 10
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}