fix: execute mode cards through deterministic assistant route
This commit is contained in:
@@ -6443,6 +6443,8 @@ function applyModeCommand(command, modeProfileId = null) {
|
|||||||
delete input.dataset.speechUtteranceId;
|
delete input.dataset.speechUtteranceId;
|
||||||
delete input.dataset.speechValue;
|
delete input.dataset.speechValue;
|
||||||
input.dispatchEvent(new Event("input", { bubbles: true }));
|
input.dispatchEvent(new Event("input", { bubbles: true }));
|
||||||
|
state.pendingModeProfileId = modeProfileId;
|
||||||
|
if (modeProfileId) setModeExecutionState(modeProfileId, "running");
|
||||||
form.requestSubmit();
|
form.requestSubmit();
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -20,7 +20,7 @@
|
|||||||
<script src="/vendor/three/0.147.0/fflate.min.js" defer></script>
|
<script src="/vendor/three/0.147.0/fflate.min.js" defer></script>
|
||||||
<script src="/vendor/three/0.147.0/FBXLoader.js" defer></script>
|
<script src="/vendor/three/0.147.0/FBXLoader.js" defer></script>
|
||||||
<script src="/network.js?v=android-pad-2" defer></script>
|
<script src="/network.js?v=android-pad-2" defer></script>
|
||||||
<script src="/app.js?v=dual-seat-129" defer></script>
|
<script src="/app.js?v=dual-seat-130" defer></script>
|
||||||
<script src="/speech-input.js?v=sensevoice-13" defer></script>
|
<script src="/speech-input.js?v=sensevoice-13" defer></script>
|
||||||
<script type="module" src="/gesture-control.js?v=android-pad-9"></script>
|
<script type="module" src="/gesture-control.js?v=android-pad-9"></script>
|
||||||
<script src="/pwa.js?v=android-pad-3" defer></script>
|
<script src="/pwa.js?v=android-pad-3" defer></script>
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
const CACHE_VERSION = "cognitive-agent-pad-v276";
|
const CACHE_VERSION = "cognitive-agent-pad-v277";
|
||||||
const SHELL_CACHE = `${CACHE_VERSION}-shell`;
|
const SHELL_CACHE = `${CACHE_VERSION}-shell`;
|
||||||
const RUNTIME_CACHE = `${CACHE_VERSION}-runtime`;
|
const RUNTIME_CACHE = `${CACHE_VERSION}-runtime`;
|
||||||
const APP_SHELL = [
|
const APP_SHELL = [
|
||||||
@@ -6,7 +6,7 @@ const APP_SHELL = [
|
|||||||
"/index.html",
|
"/index.html",
|
||||||
"/styles.css?v=dual-seat-23",
|
"/styles.css?v=dual-seat-23",
|
||||||
"/network.js?v=android-pad-2",
|
"/network.js?v=android-pad-2",
|
||||||
"/app.js?v=dual-seat-129",
|
"/app.js?v=dual-seat-130",
|
||||||
"/gesture-control.js?v=android-pad-9",
|
"/gesture-control.js?v=android-pad-9",
|
||||||
"/pwa.js?v=android-pad-3",
|
"/pwa.js?v=android-pad-3",
|
||||||
"/console-v2.css?v=delivery-state-66",
|
"/console-v2.css?v=delivery-state-66",
|
||||||
|
|||||||
@@ -377,6 +377,65 @@ def _catalog_exact_mode_match_count(
|
|||||||
return len(matched_ids)
|
return len(matched_ids)
|
||||||
|
|
||||||
|
|
||||||
|
def _catalog_exact_mode_execution_id(
|
||||||
|
catalog: Mapping[str, object],
|
||||||
|
message: str,
|
||||||
|
) -> str | None:
|
||||||
|
"""Resolve an explicit saved-mode run without asking the model to route it."""
|
||||||
|
|
||||||
|
source_status = catalog.get("source_status")
|
||||||
|
if not isinstance(source_status, Mapping) or source_status.get("modes") != "ready":
|
||||||
|
return None
|
||||||
|
|
||||||
|
normalized = re.sub(r"[\s,。!?!?、;;::]", "", str(message).casefold())
|
||||||
|
if not normalized:
|
||||||
|
return None
|
||||||
|
prefixes = ("", "请", "请帮我", "帮我", "麻烦", "麻烦帮我", "现在", "立即", "马上")
|
||||||
|
verbs = ("进入", "启动", "执行", "开启", "打开", "切换到")
|
||||||
|
suffixes = ("", "吧", "一下", "现在", "立即")
|
||||||
|
matched_ids: set[str] = set()
|
||||||
|
raw_modes = catalog.get("modes", [])
|
||||||
|
if not isinstance(raw_modes, Sequence) or isinstance(
|
||||||
|
raw_modes,
|
||||||
|
(str, bytes, bytearray),
|
||||||
|
):
|
||||||
|
return None
|
||||||
|
for raw_mode in raw_modes:
|
||||||
|
if not isinstance(raw_mode, Mapping) or raw_mode.get("assistant_callable") is not True:
|
||||||
|
continue
|
||||||
|
mode_id = str(raw_mode.get("id") or "").strip()
|
||||||
|
if not mode_id:
|
||||||
|
continue
|
||||||
|
aliases = raw_mode.get("aliases", [])
|
||||||
|
candidates = [
|
||||||
|
raw_mode.get("name"),
|
||||||
|
*(
|
||||||
|
aliases
|
||||||
|
if isinstance(aliases, Sequence)
|
||||||
|
and not isinstance(aliases, (str, bytes, bytearray))
|
||||||
|
else []
|
||||||
|
),
|
||||||
|
]
|
||||||
|
for candidate in candidates:
|
||||||
|
mode_name = re.sub(
|
||||||
|
r"[\s,。!?!?、;;::]",
|
||||||
|
"",
|
||||||
|
str(candidate or "").casefold(),
|
||||||
|
)
|
||||||
|
if not mode_name:
|
||||||
|
continue
|
||||||
|
accepted = {
|
||||||
|
f"{prefix}{verb}{mode_name}{suffix}"
|
||||||
|
for prefix in prefixes
|
||||||
|
for verb in verbs
|
||||||
|
for suffix in suffixes
|
||||||
|
}
|
||||||
|
if normalized in accepted:
|
||||||
|
matched_ids.add(mode_id)
|
||||||
|
break
|
||||||
|
return next(iter(matched_ids)) if len(matched_ids) == 1 else None
|
||||||
|
|
||||||
|
|
||||||
def _catalog_exact_scene_template_plan(
|
def _catalog_exact_scene_template_plan(
|
||||||
catalog: Mapping[str, object],
|
catalog: Mapping[str, object],
|
||||||
requested_name: str,
|
requested_name: str,
|
||||||
@@ -822,6 +881,12 @@ class CodexAssistantService:
|
|||||||
control_terms,
|
control_terms,
|
||||||
draft_context,
|
draft_context,
|
||||||
)
|
)
|
||||||
|
saved_mode_execution_id = _catalog_exact_mode_execution_id(catalog, message)
|
||||||
|
if saved_mode_execution_id is not None:
|
||||||
|
authorization = _VehicleAuthorization(
|
||||||
|
message,
|
||||||
|
"explicit_saved_mode_execute",
|
||||||
|
)
|
||||||
fast_comfort_control = (
|
fast_comfort_control = (
|
||||||
self._fast_implicit_controls and authorization.reason in _FAST_COMFORT_REASONS
|
self._fast_implicit_controls and authorization.reason in _FAST_COMFORT_REASONS
|
||||||
)
|
)
|
||||||
@@ -877,6 +942,7 @@ class CodexAssistantService:
|
|||||||
direct_vehicle_control = (
|
direct_vehicle_control = (
|
||||||
fast_comfort_control
|
fast_comfort_control
|
||||||
or exact_quick_control
|
or exact_quick_control
|
||||||
|
or saved_mode_execution_id is not None
|
||||||
or direct_mode_profile_preview
|
or direct_mode_profile_preview
|
||||||
or authorization.reason == "explicit_mode_profile_draft_update"
|
or authorization.reason == "explicit_mode_profile_draft_update"
|
||||||
or saved_mode_management_route is not None
|
or saved_mode_management_route is not None
|
||||||
@@ -890,6 +956,8 @@ class CodexAssistantService:
|
|||||||
if scene_template_plan is not None
|
if scene_template_plan is not None
|
||||||
else saved_mode_management_route
|
else saved_mode_management_route
|
||||||
if saved_mode_management_route is not None
|
if saved_mode_management_route is not None
|
||||||
|
else "mode_profile_execute"
|
||||||
|
if saved_mode_execution_id is not None
|
||||||
else "vehicle_control"
|
else "vehicle_control"
|
||||||
if direct_vehicle_control
|
if direct_vehicle_control
|
||||||
else "scene_composer"
|
else "scene_composer"
|
||||||
@@ -905,6 +973,8 @@ class CodexAssistantService:
|
|||||||
if direct_mode_profile_preview or direct_mode_profile_update
|
if direct_mode_profile_preview or direct_mode_profile_update
|
||||||
else "n7web-mode-profile-manager"
|
else "n7web-mode-profile-manager"
|
||||||
if saved_mode_management_route is not None
|
if saved_mode_management_route is not None
|
||||||
|
else "n7web-mode-profile-runner"
|
||||||
|
if saved_mode_execution_id is not None
|
||||||
else "codex-app-server-scene-composer"
|
else "codex-app-server-scene-composer"
|
||||||
if scene_composition_preview
|
if scene_composition_preview
|
||||||
else "n7web-control-router"
|
else "n7web-control-router"
|
||||||
@@ -944,6 +1014,8 @@ class CodexAssistantService:
|
|||||||
if saved_mode_management_route == "mode_profile_update"
|
if saved_mode_management_route == "mode_profile_update"
|
||||||
else "正在删除自定义功能模式……"
|
else "正在删除自定义功能模式……"
|
||||||
if saved_mode_management_route == "mode_profile_delete"
|
if saved_mode_management_route == "mode_profile_delete"
|
||||||
|
else "正在载入并执行已保存的功能模式……"
|
||||||
|
if saved_mode_execution_id is not None
|
||||||
else "正在根据实时能力目录快速组合模式……"
|
else "正在根据实时能力目录快速组合模式……"
|
||||||
if scene_composition_preview
|
if scene_composition_preview
|
||||||
else "小迪正在理解场景并组合工程能力……"
|
else "小迪正在理解场景并组合工程能力……"
|
||||||
@@ -1039,6 +1111,8 @@ class CodexAssistantService:
|
|||||||
if scene_template_plan is not None
|
if scene_template_plan is not None
|
||||||
else saved_mode_management_route
|
else saved_mode_management_route
|
||||||
if saved_mode_management_route is not None
|
if saved_mode_management_route is not None
|
||||||
|
else "mode_profile_execute"
|
||||||
|
if saved_mode_execution_id is not None
|
||||||
else "vehicle_control"
|
else "vehicle_control"
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
@@ -1679,6 +1753,7 @@ class CodexAssistantService:
|
|||||||
authorization.reason == "explicit_mode_profile_draft_update"
|
authorization.reason == "explicit_mode_profile_draft_update"
|
||||||
)
|
)
|
||||||
exact_quick_control = authorization.reason == "exact_quick_control"
|
exact_quick_control = authorization.reason == "exact_quick_control"
|
||||||
|
saved_mode_execution = authorization.reason == "explicit_saved_mode_execute"
|
||||||
mode_profile_management_route = {
|
mode_profile_management_route = {
|
||||||
"explicit_mode_profile_update": "mode_profile_update",
|
"explicit_mode_profile_update": "mode_profile_update",
|
||||||
"explicit_mode_profile_delete": "mode_profile_delete",
|
"explicit_mode_profile_delete": "mode_profile_delete",
|
||||||
@@ -1697,6 +1772,8 @@ class CodexAssistantService:
|
|||||||
if mode_profile_management_route == "mode_profile_update"
|
if mode_profile_management_route == "mode_profile_update"
|
||||||
else "正在删除自定义功能模式……"
|
else "正在删除自定义功能模式……"
|
||||||
if mode_profile_management_route == "mode_profile_delete"
|
if mode_profile_management_route == "mode_profile_delete"
|
||||||
|
else "正在通过安全执行链进入功能模式……"
|
||||||
|
if saved_mode_execution
|
||||||
else "正在通过车辆安全链执行快捷控制……"
|
else "正在通过车辆安全链执行快捷控制……"
|
||||||
if exact_quick_control
|
if exact_quick_control
|
||||||
else "正在通过车辆安全链执行舒适性调节……"
|
else "正在通过车辆安全链执行舒适性调节……"
|
||||||
@@ -1721,6 +1798,8 @@ class CodexAssistantService:
|
|||||||
"display_text": (
|
"display_text": (
|
||||||
"模式动作已解析,正在进行安全校验……"
|
"模式动作已解析,正在进行安全校验……"
|
||||||
if mode_profile_preview
|
if mode_profile_preview
|
||||||
|
else "已保存模式动作已载入,正在校验并执行……"
|
||||||
|
if saved_mode_execution
|
||||||
else "功能模式存储请求已解析,正在校验……"
|
else "功能模式存储请求已解析,正在校验……"
|
||||||
if mode_profile_management_route is not None
|
if mode_profile_management_route is not None
|
||||||
else "控制指令已通过校验,正在执行……"
|
else "控制指令已通过校验,正在执行……"
|
||||||
@@ -1814,6 +1893,14 @@ class CodexAssistantService:
|
|||||||
if mode_profile_preview
|
if mode_profile_preview
|
||||||
else completion_text
|
else completion_text
|
||||||
if mode_profile_management_route is not None
|
if mode_profile_management_route is not None
|
||||||
|
else "功能模式已提交,等待车辆状态确认"
|
||||||
|
if saved_mode_execution and action_state == "submitted"
|
||||||
|
else "功能模式模拟编排已完成"
|
||||||
|
if saved_mode_execution and action_state == "simulated"
|
||||||
|
else "功能模式执行已完成"
|
||||||
|
if saved_mode_execution and successful
|
||||||
|
else "功能模式执行未完成"
|
||||||
|
if saved_mode_execution
|
||||||
else "快捷控制已提交,等待车辆状态确认"
|
else "快捷控制已提交,等待车辆状态确认"
|
||||||
if exact_quick_control and action_state == "submitted"
|
if exact_quick_control and action_state == "submitted"
|
||||||
else "快捷控制模拟编排已完成"
|
else "快捷控制模拟编排已完成"
|
||||||
@@ -2164,11 +2251,18 @@ class CodexAssistantService:
|
|||||||
result: Mapping[str, object],
|
result: Mapping[str, object],
|
||||||
successful: bool,
|
successful: bool,
|
||||||
) -> str:
|
) -> str:
|
||||||
if reason == "exact_quick_control":
|
if reason in {"exact_quick_control", "explicit_saved_mode_execute"}:
|
||||||
instruction = re.sub(
|
instruction = re.sub(
|
||||||
r"\s+",
|
r"\s+",
|
||||||
" ",
|
" ",
|
||||||
str(result.get("utterance") or "快捷控制指令"),
|
str(
|
||||||
|
result.get("utterance")
|
||||||
|
or (
|
||||||
|
"功能模式"
|
||||||
|
if reason == "explicit_saved_mode_execute"
|
||||||
|
else "快捷控制指令"
|
||||||
|
)
|
||||||
|
),
|
||||||
).strip()[:160]
|
).strip()[:160]
|
||||||
if not successful:
|
if not successful:
|
||||||
return f"未能完成“{instruction}”,请检查车辆状态后重试。"
|
return f"未能完成“{instruction}”,请检查车辆状态后重试。"
|
||||||
|
|||||||
@@ -757,6 +757,34 @@ class TestCodexAssistantWebApi:
|
|||||||
assert not FAKE_CODEX.turn_calls
|
assert not FAKE_CODEX.turn_calls
|
||||||
assert not FAKE_CODEX.bridge_results
|
assert not FAKE_CODEX.bridge_results
|
||||||
|
|
||||||
|
def test_saved_mode_execution_uses_deterministic_route_over_http(self) -> None:
|
||||||
|
message = "进入办公模式"
|
||||||
|
FAKE_CODEX.available = False
|
||||||
|
with patch.object(
|
||||||
|
AssistantApiHandler,
|
||||||
|
"assistant_capability_catalog",
|
||||||
|
StaticSavedModeCatalog(),
|
||||||
|
):
|
||||||
|
events = self.post_assistant(
|
||||||
|
{
|
||||||
|
"message": message,
|
||||||
|
"conversation_id": "conv-api-saved-mode-execute",
|
||||||
|
"execution_mode": "dry_run",
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
assert events[0]["route"] == "mode_profile_execute"
|
||||||
|
assert events[0]["engine"] == "n7web-mode-profile-runner"
|
||||||
|
action = next(event for event in events if event["type"] == "action.completed")
|
||||||
|
assert action["success"] is True
|
||||||
|
assert action["payload"]["execution_mode"] == "dry_run"
|
||||||
|
assert events[-1]["route"] == "mode_profile_execute"
|
||||||
|
assert "模拟编排" in events[-1]["answer"]
|
||||||
|
assert len(AssistantApiHandler.service.calls) == 1
|
||||||
|
assert AssistantApiHandler.service.calls[0]["utterance"] == message
|
||||||
|
assert not FAKE_CODEX.turn_calls
|
||||||
|
assert not FAKE_CODEX.bridge_results
|
||||||
|
|
||||||
def test_saved_mode_update_uses_deterministic_management_route_over_http(
|
def test_saved_mode_update_uses_deterministic_management_route_over_http(
|
||||||
self,
|
self,
|
||||||
) -> None:
|
) -> None:
|
||||||
|
|||||||
@@ -621,7 +621,7 @@ operations:
|
|||||||
def test_static_console_and_runtime_endpoint(self) -> None:
|
def test_static_console_and_runtime_endpoint(self) -> None:
|
||||||
with self.opener.open(f"{self.base_url}/", timeout=3) as response:
|
with self.opener.open(f"{self.base_url}/", timeout=3) as response:
|
||||||
html = response.read().decode("utf-8")
|
html = response.read().decode("utf-8")
|
||||||
with self.opener.open(f"{self.base_url}/app.js?v=dual-seat-129", timeout=3) as response:
|
with self.opener.open(f"{self.base_url}/app.js?v=dual-seat-130", timeout=3) as response:
|
||||||
app_script = response.read().decode("utf-8")
|
app_script = response.read().decode("utf-8")
|
||||||
with self.opener.open(
|
with self.opener.open(
|
||||||
f"{self.base_url}/gesture-control.js?v=android-pad-9", timeout=3
|
f"{self.base_url}/gesture-control.js?v=android-pad-9", timeout=3
|
||||||
@@ -697,7 +697,7 @@ operations:
|
|||||||
self.assertIn("CMD 0x431 · FB 0x41A · CH1", html)
|
self.assertIn("CMD 0x431 · FB 0x41A · CH1", html)
|
||||||
self.assertIn("CMD 0x431 · FB 0x388 · CH1", html)
|
self.assertIn("CMD 0x431 · FB 0x388 · CH1", html)
|
||||||
self.assertNotIn("透明车体 · 实体设备", html)
|
self.assertNotIn("透明车体 · 实体设备", html)
|
||||||
self.assertIn('src="/app.js?v=dual-seat-129"', html)
|
self.assertIn('src="/app.js?v=dual-seat-130"', html)
|
||||||
self.assertIn('href="/styles.css?v=dual-seat-23"', html)
|
self.assertIn('href="/styles.css?v=dual-seat-23"', html)
|
||||||
self.assertIn('src="/gesture-control.js?v=android-pad-9"', html)
|
self.assertIn('src="/gesture-control.js?v=android-pad-9"', html)
|
||||||
self.assertNotIn('class="vehicle-system-hud"', html)
|
self.assertNotIn('class="vehicle-system-hud"', html)
|
||||||
@@ -992,14 +992,14 @@ operations:
|
|||||||
self.assertIn("CAN 指令已提交,等待车辆状态确认", console_script)
|
self.assertIn("CAN 指令已提交,等待车辆状态确认", console_script)
|
||||||
self.assertIn('canvas.addEventListener("webglcontextlost"', app_script)
|
self.assertIn('canvas.addEventListener("webglcontextlost"', app_script)
|
||||||
self.assertIn('"cognitive-agent:module-change"', app_script)
|
self.assertIn('"cognitive-agent:module-change"', app_script)
|
||||||
self.assertIn('const CACHE_VERSION = "cognitive-agent-pad-v276"', service_worker)
|
self.assertIn('const CACHE_VERSION = "cognitive-agent-pad-v277"', service_worker)
|
||||||
self.assertIn('/console-v2.css?v=delivery-state-66', service_worker)
|
self.assertIn('/console-v2.css?v=delivery-state-66', service_worker)
|
||||||
self.assertIn('/console-v2.js?v=delivery-state-62', service_worker)
|
self.assertIn('/console-v2.js?v=delivery-state-62', service_worker)
|
||||||
self.assertIn('/assistant-chat.css?v=codex-assistant-26', service_worker)
|
self.assertIn('/assistant-chat.css?v=codex-assistant-26', service_worker)
|
||||||
self.assertIn('/assistant-chat.js?v=codex-assistant-26', service_worker)
|
self.assertIn('/assistant-chat.js?v=codex-assistant-26', service_worker)
|
||||||
self.assertIn('/energy-monitor.css?v=energy-monitor-4', service_worker)
|
self.assertIn('/energy-monitor.css?v=energy-monitor-4', service_worker)
|
||||||
self.assertIn('/energy-monitor.js?v=energy-monitor-4', service_worker)
|
self.assertIn('/energy-monitor.js?v=energy-monitor-4', service_worker)
|
||||||
self.assertIn('"/app.js?v=dual-seat-129"', service_worker)
|
self.assertIn('"/app.js?v=dual-seat-130"', service_worker)
|
||||||
self.assertIn('"/speech-input.js?v=sensevoice-13"', service_worker)
|
self.assertIn('"/speech-input.js?v=sensevoice-13"', service_worker)
|
||||||
self.assertIn('"/speech-input.css?v=sensevoice-7"', service_worker)
|
self.assertIn('"/speech-input.css?v=sensevoice-7"', service_worker)
|
||||||
self.assertIn('"/speech-audio-worklet.js?v=sensevoice-5"', service_worker)
|
self.assertIn('"/speech-audio-worklet.js?v=sensevoice-5"', service_worker)
|
||||||
@@ -1456,7 +1456,7 @@ operations:
|
|||||||
) as response:
|
) as response:
|
||||||
extension_script = response.read().decode("utf-8")
|
extension_script = response.read().decode("utf-8")
|
||||||
with self.opener.open(
|
with self.opener.open(
|
||||||
f"{self.base_url}/app.js?v=dual-seat-129", timeout=3
|
f"{self.base_url}/app.js?v=dual-seat-130", timeout=3
|
||||||
) as response:
|
) as response:
|
||||||
app_script = response.read().decode("utf-8")
|
app_script = response.read().decode("utf-8")
|
||||||
|
|
||||||
|
|||||||
@@ -645,7 +645,7 @@ def test_every_assistant_callable_capability_reaches_the_real_tool_boundary(
|
|||||||
assert action_event["success"] is True
|
assert action_event["success"] is True
|
||||||
|
|
||||||
|
|
||||||
def test_every_catalog_mode_can_execute_through_the_assistant_tool_boundary(
|
def test_every_catalog_mode_executes_through_the_deterministic_assistant_route(
|
||||||
vehicle_service: SeatDemoService,
|
vehicle_service: SeatDemoService,
|
||||||
catalog: AssistantCapabilityCatalog,
|
catalog: AssistantCapabilityCatalog,
|
||||||
) -> None:
|
) -> None:
|
||||||
@@ -690,7 +690,7 @@ def test_every_catalog_mode_can_execute_through_the_assistant_tool_boundary(
|
|||||||
return result
|
return result
|
||||||
|
|
||||||
client.on_turn = invoke_tool
|
client.on_turn = invoke_tool
|
||||||
_stream_with_catalog(
|
result, events = _stream_with_catalog(
|
||||||
assistant,
|
assistant,
|
||||||
catalog,
|
catalog,
|
||||||
message,
|
message,
|
||||||
@@ -700,9 +700,11 @@ def test_every_catalog_mode_can_execute_through_the_assistant_tool_boundary(
|
|||||||
|
|
||||||
assert raw_results[0]["status"] == "succeeded", mode["id"]
|
assert raw_results[0]["status"] == "succeeded", mode["id"]
|
||||||
assert raw_results[0]["matched_modes"][0]["id"] == mode["id"]
|
assert raw_results[0]["matched_modes"][0]["id"] == mode["id"]
|
||||||
assert tool_results[0]["verified"] is False
|
assert tool_results == []
|
||||||
assert tool_results[0]["verification_status"] == "simulated"
|
assert client.turn_calls == 0
|
||||||
assert tool_results[0]["actions"], mode["id"]
|
assert result["route"] == "mode_profile_execute"
|
||||||
|
assert result["success"] is True
|
||||||
|
assert events[0]["engine"] == "n7web-mode-profile-runner"
|
||||||
|
|
||||||
|
|
||||||
def test_every_catalog_mode_rejects_incompatible_explicit_seat_scopes(
|
def test_every_catalog_mode_rejects_incompatible_explicit_seat_scopes(
|
||||||
|
|||||||
@@ -19,7 +19,7 @@ def test_console_contains_ai_assistant_surface():
|
|||||||
|
|
||||||
def test_codex_assistant_assets_are_versioned_together_in_the_service_worker():
|
def test_codex_assistant_assets_are_versioned_together_in_the_service_worker():
|
||||||
service_worker = (ROOT / "apps/web-console/service-worker.js").read_text(encoding="utf-8")
|
service_worker = (ROOT / "apps/web-console/service-worker.js").read_text(encoding="utf-8")
|
||||||
assert 'const CACHE_VERSION = "cognitive-agent-pad-v276"' in service_worker
|
assert 'const CACHE_VERSION = "cognitive-agent-pad-v277"' in service_worker
|
||||||
assert '"/assistant-chat.css?v=codex-assistant-26"' in service_worker
|
assert '"/assistant-chat.css?v=codex-assistant-26"' in service_worker
|
||||||
assert '"/assistant-chat.js?v=codex-assistant-26"' in service_worker
|
assert '"/assistant-chat.js?v=codex-assistant-26"' in service_worker
|
||||||
|
|
||||||
|
|||||||
@@ -12,6 +12,7 @@ from cognitive_agent.application.codex_assistant import (
|
|||||||
CodexAssistantService,
|
CodexAssistantService,
|
||||||
CodexVehicleToolError,
|
CodexVehicleToolError,
|
||||||
_authorize_vehicle_request,
|
_authorize_vehicle_request,
|
||||||
|
_catalog_exact_mode_execution_id,
|
||||||
_catalog_exact_mode_match_count,
|
_catalog_exact_mode_match_count,
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -81,6 +82,60 @@ def test_catalog_mode_resolution_distinguishes_unknown_ambiguous_and_unavailable
|
|||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.parametrize(
|
||||||
|
"message",
|
||||||
|
(
|
||||||
|
"进入小憩模式",
|
||||||
|
"请进入小憩模式",
|
||||||
|
"帮我启动午休模式吧",
|
||||||
|
"麻烦帮我执行小憩模式一下",
|
||||||
|
"立即开启小憩模式",
|
||||||
|
"切换到午休模式",
|
||||||
|
),
|
||||||
|
)
|
||||||
|
def test_catalog_resolves_bounded_saved_mode_execution_phrasings(message: str) -> None:
|
||||||
|
catalog = {
|
||||||
|
"modes": [
|
||||||
|
{
|
||||||
|
"id": "nap",
|
||||||
|
"name": "小憩模式",
|
||||||
|
"aliases": ["午休模式"],
|
||||||
|
"assistant_callable": True,
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"source_status": {"modes": "ready"},
|
||||||
|
}
|
||||||
|
|
||||||
|
assert _catalog_exact_mode_execution_id(catalog, message) == "nap"
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.parametrize(
|
||||||
|
"message",
|
||||||
|
(
|
||||||
|
"不要进入小憩模式",
|
||||||
|
"小憩模式会执行什么",
|
||||||
|
"能否进入小憩模式?",
|
||||||
|
"进入不存在模式",
|
||||||
|
),
|
||||||
|
)
|
||||||
|
def test_catalog_saved_mode_execution_rejects_denials_questions_and_unknown_modes(
|
||||||
|
message: str,
|
||||||
|
) -> None:
|
||||||
|
catalog = {
|
||||||
|
"modes": [
|
||||||
|
{
|
||||||
|
"id": "nap",
|
||||||
|
"name": "小憩模式",
|
||||||
|
"aliases": ["午休模式"],
|
||||||
|
"assistant_callable": True,
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"source_status": {"modes": "ready"},
|
||||||
|
}
|
||||||
|
|
||||||
|
assert _catalog_exact_mode_execution_id(catalog, message) is None
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.parametrize(
|
@pytest.mark.parametrize(
|
||||||
"message",
|
"message",
|
||||||
(
|
(
|
||||||
@@ -402,6 +457,59 @@ def test_general_question_streams_from_codex_without_vehicle_execution() -> None
|
|||||||
assert client.finished[0].turn_id == "turn-1"
|
assert client.finished[0].turn_id == "turn-1"
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.parametrize(
|
||||||
|
"message",
|
||||||
|
(
|
||||||
|
"进入小憩模式",
|
||||||
|
"请帮我启动午休模式吧",
|
||||||
|
"麻烦执行小憩模式一下",
|
||||||
|
),
|
||||||
|
)
|
||||||
|
def test_registered_mode_execution_uses_fast_local_route_without_codex(
|
||||||
|
message: str,
|
||||||
|
) -> None:
|
||||||
|
client = FakeCodexClient(available=False)
|
||||||
|
assistant = CodexAssistantService(client)
|
||||||
|
calls: list[str] = []
|
||||||
|
catalog = {
|
||||||
|
"modes": [
|
||||||
|
{
|
||||||
|
"id": "nap",
|
||||||
|
"name": "小憩模式",
|
||||||
|
"aliases": ["午休模式"],
|
||||||
|
"assistant_callable": True,
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"source_status": {"modes": "ready"},
|
||||||
|
}
|
||||||
|
|
||||||
|
def execute(instruction: str, progress: Callable[[dict[str, object]], None]):
|
||||||
|
calls.append(instruction)
|
||||||
|
result = _sample_control_result(mode="dry_run")
|
||||||
|
result["utterance"] = instruction
|
||||||
|
progress(result)
|
||||||
|
return result
|
||||||
|
|
||||||
|
result, events = _stream(
|
||||||
|
assistant,
|
||||||
|
message,
|
||||||
|
execute_control=execute,
|
||||||
|
capability_snapshot=catalog,
|
||||||
|
)
|
||||||
|
|
||||||
|
assert result["route"] == "mode_profile_execute"
|
||||||
|
assert result["success"] is True
|
||||||
|
assert calls == [message]
|
||||||
|
assert client.turn_calls == []
|
||||||
|
assert events[0]["engine"] == "n7web-mode-profile-runner"
|
||||||
|
assert events[1]["display_text"] == "正在载入并执行已保存的功能模式……"
|
||||||
|
assert any(
|
||||||
|
event.get("display_text") == "功能模式模拟编排已完成"
|
||||||
|
for event in events
|
||||||
|
)
|
||||||
|
assert "模拟编排" in result["answer"]
|
||||||
|
|
||||||
|
|
||||||
def test_registered_name_only_mode_keeps_the_fast_local_route_without_codex() -> None:
|
def test_registered_name_only_mode_keeps_the_fast_local_route_without_codex() -> None:
|
||||||
client = FakeCodexClient(available=False)
|
client = FakeCodexClient(available=False)
|
||||||
assistant = CodexAssistantService(client)
|
assistant = CodexAssistantService(client)
|
||||||
|
|||||||
@@ -38,7 +38,7 @@ def test_energy_monitor_assets_are_versioned_and_cached() -> None:
|
|||||||
assert html.index("/assistant-chat.js?v=codex-assistant-26") < html.index(
|
assert html.index("/assistant-chat.js?v=codex-assistant-26") < html.index(
|
||||||
"/energy-monitor.js?v=energy-monitor-4"
|
"/energy-monitor.js?v=energy-monitor-4"
|
||||||
)
|
)
|
||||||
assert 'const CACHE_VERSION = "cognitive-agent-pad-v276"' in service_worker
|
assert 'const CACHE_VERSION = "cognitive-agent-pad-v277"' in service_worker
|
||||||
|
|
||||||
|
|
||||||
def test_energy_monitor_uses_the_fifth_shared_dock_icon_and_starts_closed() -> None:
|
def test_energy_monitor_uses_the_fifth_shared_dock_icon_and_starts_closed() -> None:
|
||||||
|
|||||||
@@ -47,7 +47,7 @@ def test_theme_asset_versions_are_consistent_with_the_pwa_shell() -> None:
|
|||||||
):
|
):
|
||||||
assert asset in html
|
assert asset in html
|
||||||
assert f'"{asset}"' in service_worker
|
assert f'"{asset}"' in service_worker
|
||||||
assert 'const CACHE_VERSION = "cognitive-agent-pad-v276"' in service_worker
|
assert 'const CACHE_VERSION = "cognitive-agent-pad-v277"' in service_worker
|
||||||
|
|
||||||
|
|
||||||
def test_vehicle_twin_icons_and_nested_overlays_use_semantic_tokens() -> None:
|
def test_vehicle_twin_icons_and_nested_overlays_use_semantic_tokens() -> None:
|
||||||
|
|||||||
@@ -271,6 +271,7 @@ const savedMode = {
|
|||||||
command_flow: { source_request_id: "saved-request" },
|
command_flow: { source_request_id: "saved-request" },
|
||||||
};
|
};
|
||||||
const context = {
|
const context = {
|
||||||
|
state: { pendingModeProfileId: null },
|
||||||
Event: class Event {
|
Event: class Event {
|
||||||
constructor(type, options = {}) { this.type = type; this.bubbles = options.bubbles; }
|
constructor(type, options = {}) { this.type = type; this.bubbles = options.bubbles; }
|
||||||
},
|
},
|
||||||
@@ -282,6 +283,7 @@ const context = {
|
|||||||
},
|
},
|
||||||
modeProfiles() { return [savedMode]; },
|
modeProfiles() { return [savedMode]; },
|
||||||
setModeRailSelection(mode) { calls.push(["select", mode?.id]); },
|
setModeRailSelection(mode) { calls.push(["select", mode?.id]); },
|
||||||
|
setModeExecutionState(modeId, status) { calls.push(["state", modeId, status]); },
|
||||||
closeModeGallery(options) { calls.push(["close", options.restoreFocus]); },
|
closeModeGallery(options) { calls.push(["close", options.restoreFocus]); },
|
||||||
showToast(message) { calls.push(["toast", message]); },
|
showToast(message) { calls.push(["toast", message]); },
|
||||||
$(selector) {
|
$(selector) {
|
||||||
@@ -295,6 +297,7 @@ console.log(JSON.stringify({
|
|||||||
calls,
|
calls,
|
||||||
value: input.value,
|
value: input.value,
|
||||||
speechDataset: input.dataset,
|
speechDataset: input.dataset,
|
||||||
|
pendingModeProfileId: context.state.pendingModeProfileId,
|
||||||
}));
|
}));
|
||||||
"""
|
"""
|
||||||
completed = subprocess.run(
|
completed = subprocess.run(
|
||||||
@@ -312,10 +315,12 @@ console.log(JSON.stringify({
|
|||||||
["close", False],
|
["close", False],
|
||||||
["open"],
|
["open"],
|
||||||
["input", "input", True, "进入阅读模式"],
|
["input", "input", True, "进入阅读模式"],
|
||||||
|
["state", "reading-user", "running"],
|
||||||
["submit", "进入阅读模式"],
|
["submit", "进入阅读模式"],
|
||||||
]
|
]
|
||||||
assert result["value"] == "进入阅读模式"
|
assert result["value"] == "进入阅读模式"
|
||||||
assert result["speechDataset"] == {}
|
assert result["speechDataset"] == {}
|
||||||
|
assert result["pendingModeProfileId"] == "reading-user"
|
||||||
assert "executeSavedModeProfile" not in snippet
|
assert "executeSavedModeProfile" not in snippet
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user