bfff1b515c
Expose safe NFC-bound infrared actions to semantic scene composition, require flashing-light actions for romantic and Mid-Autumn modes, and preserve mixed CAN/IR mode flows. Fix NFC device reassignment and retry extension/toy MQTT publication when an active card's binding catalog changes.
240 lines
8.7 KiB
Python
240 lines
8.7 KiB
Python
from __future__ import annotations
|
|
|
|
import sys
|
|
from pathlib import Path
|
|
from types import SimpleNamespace
|
|
from unittest.mock import patch
|
|
|
|
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.seat_service import REPOSITORY_ROOT, SeatDemoService
|
|
from cognitive_agent.application.self_extension import (
|
|
SelfExtensionService,
|
|
SimulationGateway,
|
|
)
|
|
from cognitive_agent.infrastructure.codex.app_server import CodexAppServerClient
|
|
from cognitive_agent.server import SeatDemoHandler, VehicleDisplaySync
|
|
|
|
FAKE_SERVER = REPOSITORY_ROOT / "tests" / "fixtures" / "fake_codex_app_server.py"
|
|
MCP_SERVER = (
|
|
REPOSITORY_ROOT / "src/cognitive_agent/infrastructure/codex/vehicle_mcp_server.py"
|
|
)
|
|
CALLBACK_URL = "http://127.0.0.1:8765/api/internal/codex/vehicle-control"
|
|
|
|
|
|
@pytest.mark.parametrize(
|
|
("message", "expected_name"),
|
|
(
|
|
("构建一个星夜模式", "星夜模式"),
|
|
("创建学习模式", "学习模式"),
|
|
("创建中秋欢快模式", "中秋欢快模式"),
|
|
),
|
|
)
|
|
def test_structured_scene_turn_reaches_the_complete_local_dry_run_pipeline(
|
|
tmp_path: Path,
|
|
message: str,
|
|
expected_name: str,
|
|
) -> None:
|
|
service = SeatDemoService(REPOSITORY_ROOT)
|
|
service.artifact_root = tmp_path / "runs"
|
|
service.mode_profiles = ModeProfileRegistry(
|
|
REPOSITORY_ROOT / "config" / "modes",
|
|
user_root=tmp_path / "user-modes",
|
|
)
|
|
client = CodexAppServerClient(
|
|
cwd=tmp_path / "codex-workspace",
|
|
codex_command=[sys.executable, "-u", str(FAKE_SERVER)],
|
|
model="gpt-test-fast",
|
|
reasoning_effort="low",
|
|
request_timeout=2,
|
|
startup_timeout=2,
|
|
environment={"OPENAI_BASE_URL": "https://api.openai.com/v1"},
|
|
mcp_server_path=MCP_SERVER,
|
|
)
|
|
assistant = CodexAssistantService(client)
|
|
catalog = AssistantCapabilityCatalog(service)
|
|
snapshot = catalog.snapshot()
|
|
events: list[dict[str, object]] = []
|
|
|
|
def execute_control(
|
|
instruction: str,
|
|
progress: object,
|
|
assistant_plan: object,
|
|
) -> dict[str, object]:
|
|
assert isinstance(assistant_plan, dict)
|
|
return service.preview_native_can(
|
|
instruction,
|
|
provider="local",
|
|
conversation_id="conv-scene-e2e-pipeline",
|
|
execution_mode="hardware",
|
|
vehicle_id="denza_n7",
|
|
on_commands_validated=progress if callable(progress) else None,
|
|
assistant_plan=assistant_plan,
|
|
)
|
|
|
|
try:
|
|
assistant.start(CALLBACK_URL)
|
|
with patch.object(
|
|
service.hardware_execution,
|
|
"reserve",
|
|
side_effect=AssertionError("scene preview must never reserve hardware"),
|
|
):
|
|
response = assistant.stream(
|
|
message,
|
|
conversation_id="conv-scene-e2e-pipeline",
|
|
principal="scene-e2e-user",
|
|
history=[],
|
|
emit=lambda event: events.append(event) or True,
|
|
execute_control=execute_control,
|
|
capability_snapshot=snapshot,
|
|
capability_context=catalog.compact_context(snapshot),
|
|
scene_capability_context=catalog.scene_composition_context(snapshot),
|
|
control_terms=catalog.control_terms(snapshot),
|
|
)
|
|
finally:
|
|
assistant.close()
|
|
|
|
result = response["result"]
|
|
draft = result["mode_profile_draft"]
|
|
assert response["route"] == "scene_composer"
|
|
assert response["success"] is True
|
|
assert result["status"] == "succeeded"
|
|
assert result["execution_mode"] == "dry_run"
|
|
assert result["hardware_execution_allowed"] is False
|
|
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
|
|
capabilities = {
|
|
action["capability"] for action in result["actions"]
|
|
}
|
|
assert capabilities == {
|
|
"climate.set_temperature",
|
|
"roof.set_shade_position",
|
|
}
|
|
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(
|
|
tmp_path: Path,
|
|
) -> None:
|
|
service = SeatDemoService(REPOSITORY_ROOT)
|
|
service.artifact_root = tmp_path / "runs"
|
|
service.mode_profiles = ModeProfileRegistry(
|
|
REPOSITORY_ROOT / "config" / "modes",
|
|
user_root=tmp_path / "user-modes",
|
|
)
|
|
extension = SelfExtensionService(
|
|
SimulationGateway(),
|
|
catalog_db_path=tmp_path / "infrared.sqlite3",
|
|
)
|
|
extension.sync_peripheral_catalog(
|
|
[
|
|
{
|
|
"id": "ir-slot:flashing-light:2",
|
|
"status": "learned",
|
|
"enabled": True,
|
|
"device_id": "flashing-light",
|
|
"device_name": "闪烁灯",
|
|
"object_names": ["闪烁灯", "闪光灯"],
|
|
"card_uid": "E113B203",
|
|
"action": "打开",
|
|
"action_names": ["打开", "开启", "启动"],
|
|
"risk": "low",
|
|
"slot": 2,
|
|
},
|
|
{
|
|
"id": "ir-slot:flashing-light:3",
|
|
"status": "learned",
|
|
"enabled": True,
|
|
"device_id": "flashing-light",
|
|
"device_name": "闪烁灯",
|
|
"object_names": ["闪烁灯", "闪光灯"],
|
|
"card_uid": "E113B203",
|
|
"action": "切换为红色",
|
|
"action_names": ["切换为红色", "设置为红色"],
|
|
"risk": "low",
|
|
"slot": 3,
|
|
},
|
|
]
|
|
)
|
|
handler = object.__new__(SeatDemoHandler)
|
|
handler.service = service
|
|
handler.extension = extension
|
|
handler.nfc = SimpleNamespace(snapshot=lambda: {"card": None})
|
|
handler.vehicle_display_sync = VehicleDisplaySync()
|
|
catalog = AssistantCapabilityCatalog(
|
|
service,
|
|
peripheral_provider=extension.list_peripheral_routing_capabilities,
|
|
)
|
|
snapshot = catalog.snapshot()
|
|
client = CodexAppServerClient(
|
|
cwd=tmp_path / "codex-workspace",
|
|
codex_command=[sys.executable, "-u", str(FAKE_SERVER)],
|
|
model="gpt-test-fast",
|
|
reasoning_effort="low",
|
|
request_timeout=2,
|
|
startup_timeout=2,
|
|
environment={"OPENAI_BASE_URL": "https://api.openai.com/v1"},
|
|
mcp_server_path=MCP_SERVER,
|
|
)
|
|
assistant = CodexAssistantService(client)
|
|
|
|
def execute_control(
|
|
instruction: str,
|
|
progress: object,
|
|
assistant_plan: object,
|
|
) -> dict[str, object]:
|
|
assert isinstance(assistant_plan, dict)
|
|
result = handler._execute_request_with_peripherals(
|
|
"/api/requests/preview",
|
|
{
|
|
"utterance": instruction,
|
|
"provider": "local",
|
|
"conversation_id": "conv-mid-autumn-flashing-light",
|
|
"execution_mode": "hardware",
|
|
"vehicle_id": "denza_n7",
|
|
"simulation_fault": "normal",
|
|
"hardware_access_bypass": True,
|
|
"control_transport": "wired_can",
|
|
"assistant_plan": assistant_plan,
|
|
},
|
|
on_commands_validated=progress,
|
|
)
|
|
assert result is not None
|
|
return result
|
|
|
|
try:
|
|
assistant.start(CALLBACK_URL)
|
|
with patch.object(
|
|
extension,
|
|
"execute_gateway",
|
|
side_effect=AssertionError("mode creation must not emit infrared"),
|
|
):
|
|
response = assistant.stream(
|
|
"创建中秋欢快模式",
|
|
conversation_id="conv-mid-autumn-flashing-light",
|
|
principal="mid-autumn-e2e-user",
|
|
history=[],
|
|
emit=lambda _event: True,
|
|
execute_control=execute_control,
|
|
capability_snapshot=snapshot,
|
|
capability_context=catalog.compact_context(snapshot),
|
|
scene_capability_context=catalog.scene_composition_context(snapshot),
|
|
control_terms=catalog.control_terms(snapshot),
|
|
)
|
|
finally:
|
|
assistant.close()
|
|
|
|
result = response["result"]
|
|
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 result["mode_profile_draft"]["domains"][-1] == "self_extension"
|