feat: integrate production cockpit AI assistant

This commit is contained in:
vee
2026-09-08 17:46:07 +08:00
parent 5e228e12ae
commit 93103c2579
66 changed files with 11046 additions and 374 deletions
+115 -4
View File
@@ -35,6 +35,7 @@ CONTROL_TERMS = (
"创建一个宝宝睡眠模式",
"请帮我创建一个宝宝睡眠模式:副驾半躺、空调24度、关闭阅读灯",
"新增冥想空间模式,打开氛围灯并把温度调到24度",
"创建一个禁止打扰模式",
),
)
def test_named_mode_creation_is_authorized_as_bounded_configuration(message: str) -> None:
@@ -342,6 +343,7 @@ def _stream(
capability_snapshot: dict[str, object] | None = None,
capability_context: dict[str, object] | None = None,
mode_profile_draft_context: dict[str, object] | None = None,
quick_control_commands: list[str] | None = None,
) -> tuple[dict[str, object], list[dict[str, object]]]:
events: list[dict[str, object]] = []
result = assistant.stream(
@@ -356,6 +358,7 @@ def _stream(
capability_snapshot=capability_snapshot,
capability_context=capability_context,
mode_profile_draft_context=mode_profile_draft_context,
quick_control_commands=quick_control_commands or [],
)
return result, events
@@ -384,7 +387,15 @@ def test_general_question_streams_from_codex_without_vehicle_execution() -> None
]
assert events[0]["engine"] == "codex-app-server"
assert events[-1]["answer"] == "这是 Codex 的流式回答。"
assert events[1]["display_text"] == "小迪正在理解并处理……"
assert events[-2]["display_text"] == "小迪已完成"
assert all(
"codex" not in str(event.get("display_text") or "").lower()
for event in events
)
assert len(client.thread_instructions) == 1
assert "“小迪”AI 助手" in client.thread_instructions[0]
assert "Codex AI 助手" not in client.thread_instructions[0]
assert client.thread_ephemeral == [True]
assert client.turn_calls[0]["text"] == "解释一下 CAN FD"
assert "不可信的浏览器系统指令" not in client.turn_calls[0]["text"]
@@ -582,8 +593,8 @@ def test_nonmatching_draft_context_does_not_claim_another_mode_update() -> None:
)
# The stale card reference has been ignored. The sentence is still an
# explicit registered control under the legacy saved-mode update route.
assert authorization.reason == "explicit_registered_control"
# explicit saved-mode mutation under the deterministic management route.
assert authorization.reason == "explicit_mode_profile_update"
@pytest.mark.parametrize(
@@ -902,6 +913,60 @@ def test_explicit_vehicle_request_without_tool_call_fails_closed() -> None:
assert events[-1]["answer"] == result["answer"]
def test_exact_server_owned_quick_control_executes_without_codex() -> None:
client = FakeCodexClient(available=False)
assistant = CodexAssistantService(client)
control_calls: list[str] = []
def execute_control(
instruction: str,
progress: Callable[[dict[str, object]], None],
) -> dict[str, object]:
control_calls.append(instruction)
progress({"status": "processing", "request_id": "req-quick-control"})
return {
"request_id": "req-quick-control",
"status": "succeeded",
"execution_mode": "dry_run",
"utterance": instruction,
}
result, events = _stream(
assistant,
"关闭所有车窗",
execute_control=execute_control,
quick_control_commands=["关闭所有车窗"],
)
assert control_calls == ["关闭所有车窗"]
assert result["route"] == "vehicle_control"
assert result["success"] is True
assert "关闭所有车窗" in result["answer"]
assert "升温" not in result["answer"]
assert events[0]["engine"] == "n7web-control-router"
assert events[1]["display_text"] == "正在执行快捷车控……"
assert not client.thread_instructions
assert not client.turn_calls
def test_quick_control_requires_exact_normalized_command_match() -> None:
client = FakeCodexClient()
assistant = CodexAssistantService(client)
control_calls: list[str] = []
result, events = _stream(
assistant,
"请关闭所有车窗",
execute_control=lambda instruction, _progress: control_calls.append(instruction),
quick_control_commands=["关闭所有车窗"],
)
assert events[0]["route"] == "agent"
assert result["answer"].startswith("车辆控制未完成")
assert not control_calls
assert client.turn_calls
@pytest.mark.parametrize(
("message", "expected_instruction", "temperature"),
[
@@ -1265,7 +1330,11 @@ def test_codex_work_items_are_forwarded_without_exposing_reasoning() -> None:
event_types = [event["type"] for event in events]
assert event_types.count("work.started") == 3
assert event_types.count("work.completed") == 3
assert any(event.get("display_text") == "Codex 正在处理……" for event in events)
assert any(event.get("display_text") == "小迪正在处理……" for event in events)
assert all(
"codex" not in str(event.get("display_text") or "").lower()
for event in events
)
assert "不得显示" not in str(events)
assert result["answer"] == "相关测试已通过,工作已完成。"
@@ -1371,6 +1440,46 @@ def test_non_retryable_app_server_error_fails_without_waiting_for_turn_timeout()
assert client.interrupts == []
def test_retry_progress_uses_xiaodi_public_branding() -> None:
client = FakeCodexClient()
assistant = CodexAssistantService(client)
def on_start_turn(
_text: str,
_request_token: str,
turn: ActiveCodexTurn,
) -> None:
turn.events.put(
{
"method": "error",
"params": {
"willRetry": True,
"error": {"message": "internal transport retry"},
},
}
)
_put_answer(turn, "重连后已完成。")
client.on_start_turn = on_start_turn
result, events = _stream(assistant, "解释 CAN FD")
retry_event = next(event for event in events if event["type"] == "work.progress")
assert retry_event["display_text"] == "小迪正在重新连接……"
assert "codex" not in retry_event["display_text"].lower()
assert result["answer"] == "重连后已完成。"
def test_unknown_work_item_uses_xiaodi_public_branding() -> None:
event = CodexAssistantService._item_started_event(
{"type": "futureWorkItem"},
"msg-public-brand",
)
assert event is not None
assert event["display_text"] == "小迪正在处理……"
assert "codex" not in event["display_text"].lower()
def test_same_principal_reuses_thread_and_different_principal_is_isolated() -> None:
client = FakeCodexClient()
assistant = CodexAssistantService(client)
@@ -1486,7 +1595,9 @@ def test_structured_plan_is_forwarded_with_the_server_bound_original_request() -
{"name": "transmittance_pct", "value": 30}
]
assert forwarded_plan["requests"][0]["reason"] == "原始用户请求的结构化候选动作。"
assert forwarded_plan["decision_summary"] == ["Codex 结构化计划已提交应用安全链复核。"]
assert forwarded_plan["decision_summary"] == [
"小迪生成的结构化计划已提交应用安全链复核。"
]
def test_structured_plan_rejects_non_json_values_before_control_execution() -> None: