from __future__ import annotations import json import queue from collections.abc import Callable, Mapping import pytest from cognitive_agent.application.codex_assistant import ( ActiveCodexTurn, CodexAssistantService, ) from cognitive_agent.application.seat_service import SeatDemoService from cognitive_agent.infrastructure.codex import vehicle_mcp_server def _complete_turn(turn: ActiveCodexTurn, answer: str = "已安全处理。") -> None: item_id = f"answer-{turn.turn_id}" turn.events.put( { "method": "item/started", "params": { "item": { "id": item_id, "type": "agentMessage", "phase": "final_answer", } }, } ) turn.events.put( { "method": "item/agentMessage/delta", "params": {"itemId": item_id, "delta": answer}, } ) turn.events.put( { "method": "item/completed", "params": { "item": { "id": item_id, "type": "agentMessage", "phase": "final_answer", "text": answer, } }, } ) turn.events.put( { "method": "turn/completed", "params": {"turn": {"id": turn.turn_id, "status": "completed"}}, } ) class _SecurityProbeClient: available = True status = "ready" def __init__(self) -> None: self.on_turn: Callable[[str, ActiveCodexTurn], None] | None = None self.request_token = "" def start(self, _callback_url: str, _callback_token: str) -> None: return None def close(self) -> None: return None def start_thread( self, *, developer_instructions: str, ephemeral: bool = True, ) -> str: assert developer_instructions assert ephemeral is True return "thread-security-probe" def start_turn( self, thread_id: str, text: str, request_token: str, *, application_context: Mapping[str, object] | None = None, ) -> ActiveCodexTurn: del text, application_context self.request_token = request_token turn = ActiveCodexTurn(thread_id, "turn-security-probe", queue.Queue()) if self.on_turn is not None: self.on_turn(request_token, turn) _complete_turn(turn) return turn def finish_turn(self, _turn: ActiveCodexTurn) -> None: return None def interrupt(self, _turn: ActiveCodexTurn) -> None: return None def unsubscribe_thread(self, _thread_id: str) -> None: return None def _stream( assistant: CodexAssistantService, message: str, *, execute_control: Callable[..., dict[str, object]] | None = None, ) -> tuple[dict[str, object], list[dict[str, object]]]: events: list[dict[str, object]] = [] result = assistant.stream( message, conversation_id="conv-security-boundary", principal="security-probe", history=[], emit=lambda event: events.append(event), execute_control=execute_control or (lambda *_args: pytest.fail("unexpected control execution")), capability_snapshot={}, capability_context={}, control_terms=("空调",), query_capabilities=lambda _snapshot, _query: {}, ) return result, events def _plan(parameter_value: object) -> dict[str, object]: return { "detected_domains": ["roof"], "requests": [ { "domain": "roof", "capability": "roof.set_transmittance", "target_scope": "vehicle", "operation": "set", "duration_ms": None, "parameters": [{"name": "transmittance_pct", "value": parameter_value}], "reason": "用户明确指定透光率。", } ], "ambiguities": [], "decision_summary": ["设置天幕透光率。"], "confidence": 0.99, } def _seat_motion_plan( capability: str, operation: str, parameters: Mapping[str, object], *, duration_ms: int | None = None, ) -> dict[str, object]: return { "detected_domains": ["seat"], "requests": [ { "domain": "seat", "capability": capability, "target_scope": "passenger", "operation": operation, "duration_ms": duration_ms, "parameters": [ {"name": name, "value": value} for name, value in parameters.items() ], "reason": "仅规范化用户明确指定的座椅运动。", } ], "ambiguities": [], "decision_summary": ["执行用户明确指定的座椅运动。"], "confidence": 0.99, } def _prime_axis( service: SeatDemoService, conversation_id: str, axis: str, position_pct: int, ) -> None: service.conversations.apply_successful_actions( conversation_id, "test setup", [ { "action_type": "position", "target": "passenger", "axis": axis, "position_pct": position_pct, } ], ["test setup"], ) @pytest.mark.parametrize("non_finite", [float("nan"), float("inf"), float("-inf")]) def test_mcp_rejects_non_finite_plan_parameter_before_callback( non_finite: float, ) -> None: callback_called = False def callback( _tool: str, _arguments: Mapping[str, object], ) -> Mapping[str, object]: nonlocal callback_called callback_called = True return {"status": "succeeded"} response = vehicle_mcp_server.handle_message( { "jsonrpc": "2.0", "id": 1, "method": "tools/call", "params": { "name": "execute_cockpit_request", "arguments": { "request_token": "request-token-123", "plan": _plan(non_finite), }, }, }, callback, ) assert response is not None assert response["error"] == { "code": -32602, "message": "Invalid assistant plan", } assert callback_called is False def test_request_token_is_not_exposed_across_mcp_progress_events() -> None: client = _SecurityProbeClient() assistant = CodexAssistantService(client) def expose_split_token(request_token: str, turn: ActiveCodexTurn) -> None: split = len(request_token) // 2 for part in (request_token[:split], request_token[split:]): turn.events.put( { "method": "item/mcpToolCall/progress", "params": {"message": part}, } ) client.on_turn = expose_split_token _result, events = _stream(assistant, "解释一下空调的工作原理") serialized = json.dumps(events, ensure_ascii=False) split = len(client.request_token) // 2 assert client.request_token[:split] not in serialized assert client.request_token[split:] not in serialized def test_model_controlled_summary_cannot_echo_request_token_to_browser() -> None: client = _SecurityProbeClient() assistant = CodexAssistantService(client) def execute_tool(request_token: str, _turn: ActiveCodexTurn) -> None: assistant.execute_cockpit_request(request_token) def execute_control( instruction: str, _progress: Callable[[dict[str, object]], None], ) -> dict[str, object]: assert instruction == "打开空调" return { "status": "succeeded", "execution_mode": "dry_run", "decision_summary": [f"internal={client.request_token}"], "intent_ir": {"spec": {"actions": []}}, } client.on_turn = execute_tool _result, events = _stream( assistant, "打开空调", execute_control=execute_control, ) assert client.request_token not in json.dumps(events, ensure_ascii=False) def test_safe_vehicle_result_filters_sensitive_nested_action_parameters() -> None: safe = CodexAssistantService._safe_vehicle_result( { "status": "succeeded", "execution_mode": "dry_run", "intent_ir": { "spec": { "actions": [ { "domain": "climate", "capability": "climate.set_power", "target": {"zone": "vehicle"}, "parameters": { "state": "on", "raw_can": "41 01 02 03 04 05 06 07", "approval_token": "approval-secret", "internal_token": "internal-secret", }, } ] } }, } ) assert safe["actions"][0]["parameters"]["state"] == "on" serialized = json.dumps(safe, ensure_ascii=False) assert "raw_can" not in serialized assert "approval-secret" not in serialized assert "internal-secret" not in serialized @pytest.mark.parametrize( ( "text", "capability", "axis", "correct_operation", "reversed_operation", "parameters", "duration_ms", "initial_position", ), [ ( "副驾座椅向前移动20毫米", "seat.set_longitudinal_position", "longitudinal", "decrease", "increase", {"distance_mm": 20}, None, 50, ), ( "副驾座椅向后移动20毫米", "seat.set_longitudinal_position", "longitudinal", "increase", "decrease", {"distance_mm": 20}, None, 50, ), ( "把副驾坐垫前缘抬高2秒", "seat.set_cushion_tilt", "cushion_tilt", "decrease", "increase", {}, 2_000, 50, ), ( "把副驾坐垫前缘降低2秒", "seat.set_cushion_tilt", "cushion_tilt", "increase", "decrease", {}, 2_000, 50, ), ( "副驾靠背向后放倒10度", "seat.set_backrest_recline", "backrest_recline", "increase", "decrease", {"angle_delta_deg": 10}, None, 50, ), ( "副驾靠背向前调直10度", "seat.set_backrest_recline", "backrest_recline", "decrease", "increase", {"angle_delta_deg": 10}, None, 50, ), ( "请使副驾腿托角度向上调整10度", "seat.set_legrest_height", "legrest_height", "increase", "decrease", {"angle_delta_deg": 10}, None, 50, ), ( "请使副驾腿托角度向下调整10度", "seat.set_legrest_height", "legrest_height", "decrease", "increase", {"angle_delta_deg": 10}, None, 50, ), ( "请使副驾腿托伸长到65%", "seat.set_legrest_extension", "legrest_extension", "increase", "decrease", {"position_pct": 65}, None, 50, ), ( "请使副驾腿托缩回到35%", "seat.set_legrest_extension", "legrest_extension", "decrease", "increase", {"position_pct": 35}, None, 50, ), ], ) def test_each_seat_axis_binds_physical_words_to_its_normalized_direction( tmp_path, text: str, capability: str, axis: str, correct_operation: str, reversed_operation: str, parameters: Mapping[str, object], duration_ms: int | None, initial_position: int, ) -> None: service = SeatDemoService() service.artifact_root = tmp_path slug = capability.rsplit(".", 1)[-1].replace("_", "-") correct_conversation = f"conv-direction-correct-{slug}" reversed_conversation = f"conv-direction-reversed-{slug}" _prime_axis(service, correct_conversation, axis, initial_position) _prime_axis(service, reversed_conversation, axis, initial_position) try: reversed_result = service.preview_native_can( text, provider="local", execution_mode="dry_run", conversation_id=reversed_conversation, assistant_plan=_seat_motion_plan( capability, reversed_operation, parameters, duration_ms=duration_ms, ), ) correct_result = service.preview_native_can( text, provider="local", execution_mode="dry_run", conversation_id=correct_conversation, assistant_plan=_seat_motion_plan( capability, correct_operation, parameters, duration_ms=duration_ms, ), ) finally: service.close() assert reversed_result["status"] == "no_action" assert any("安全授权已过滤" in summary for summary in reversed_result["decision_summary"]) assert correct_result["status"] == "succeeded" assert correct_result["provider_used"] == "local_rules+codex_app_server" actions = correct_result["intent_ir"]["spec"]["actions"] assert len(actions) == 1 action = actions[0] assert action["capability"] == capability assert action["resolution"]["direction"] == correct_operation final_position = action["parameters"]["position_pct"] if correct_operation == "increase": assert final_position > initial_position else: assert final_position < initial_position