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", "expected_operation_count"), ( ("构建一个星夜模式", "星夜模式", 13), ("创建学习模式", "学习模式", 13), ("创建中秋欢快模式", "中秋欢快模式", 13), ), ) def test_structured_scene_turn_reaches_the_complete_local_dry_run_pipeline( tmp_path: Path, message: str, expected_name: str, expected_operation_count: int, ) -> None: service = SeatDemoService(REPOSITORY_ROOT) service.artifact_root = tmp_path / "runs" 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"] == expected_operation_count capabilities = { action["capability"] for action in result["actions"] } expected_capabilities = { "climate.set_power", "climate.set_temperature", "climate.set_fan_level", "climate.set_cycle_mode", "window.set_position", "roof.set_shade_position", "seat.set_ventilation", "seat.set_backrest_recline", "seat.set_legrest_height", "ambient_light.set_power", } assert capabilities == expected_capabilities window_actions = [ action for action in result["actions"] if action["capability"] == "window.set_position" ] assert { action["target"]["zone"] for action in window_actions } == {"front_left", "front_right", "rear_left", "rear_right"} assert all(action["parameters"] == {"position_pct": 0} for action in window_actions) assert not list((tmp_path / "user-modes").rglob("*.yaml")) assert any(event.get("type") == "action.completed" for event in events) assert all("request_token" not in str(event) for event in events) def test_mid_autumn_cloud_scene_requires_native_and_decorative_light_controls( 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, }, { "id": "ir-slot:light-strip:21", "status": "learned", "enabled": True, "device_id": "light-strip", "device_name": "灯带", "object_names": ["灯带", "LED 灯条"], "card_uid": "A1B2C3D4", "action": "打开", "action_names": ["打开", "开启"], "risk": "low", "slot": 21, }, { "id": "ir-slot:light-strip:23", "status": "learned", "enabled": True, "device_id": "light-strip", "device_name": "灯带", "object_names": ["灯带", "LED 灯条"], "card_uid": "A1B2C3D4", "action": "切换为红色", "action_names": ["切换为红色", "设置为红色"], "risk": "low", "slot": 23, }, { "id": "ir-slot:ambient-light:31", "status": "learned", "enabled": True, "device_id": "ambient-light", "device_name": "外设氛围灯", "object_names": ["外设氛围灯", "氛围彩灯玩具"], "card_uid": "B1C2D3E4", "action": "打开", "action_names": ["打开", "开启"], "risk": "low", "slot": 31, }, ] ) handler = object.__new__(SeatDemoHandler) 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, 31, 21, ] capabilities = { action["capability"] for action in result["actions"] } assert "climate.set_temperature" in capabilities assert "ambient_light.set_power" in capabilities assert "roof.set_shade_position" in capabilities assert { "climate.set_power", "climate.set_temperature", "climate.set_fan_level", "climate.set_cycle_mode", "window.set_position", "seat.set_ventilation", "seat.set_backrest_recline", "seat.set_legrest_height", "ambient_light.set_power", "infrared.emit", } <= capabilities assert result["mode_profile_draft"]["operation_count"] == 16 assert result["mode_profile_draft"]["domains"][-1] == "self_extension"