fix: harden conversational mode draft revisions

This commit is contained in:
vee
2026-09-08 00:55:45 +08:00
parent f0a248ede3
commit 5e228e12ae
16 changed files with 1862 additions and 23 deletions
@@ -323,6 +323,86 @@ test("a follow-up turn carries only the active authoritative draft reference", a
assert.equal(Object.hasOwn(request.mode_profile_draft_context, "actions"), false);
});
test("the change-into predicate carries only a same-name opaque draft reference", async () => {
const events = modeDraftEvents("req-draft-change-into", 24);
const harness = createHarness(events);
harness.input.value = "构建一个午休模式";
await harness.sandbox.CognitiveAgentAssistant.submit();
events.splice(0, events.length,
{ type: "message.accepted", route: "agent", engine: "codex-app-server" },
{ type: "answer.completed", route: "agent", answer: "已处理" },
);
harness.input.value = "把午休模式的空调温度变成25度";
await harness.sandbox.CognitiveAgentAssistant.submit();
harness.input.value = "午休模式的空调温度变成25度了吗?";
await harness.sandbox.CognitiveAgentAssistant.submit();
harness.input.value = "把露营模式的空调温度变成25度";
await harness.sandbox.CognitiveAgentAssistant.submit();
const sameMode = JSON.parse(harness.requestCalls[1].options.body);
const question = JSON.parse(harness.requestCalls[2].options.body);
const anotherMode = JSON.parse(harness.requestCalls[3].options.body);
assert.deepEqual(
Object.keys(sameMode.mode_profile_draft_context).sort(),
["name", "source_request_id", "vehicle_id"],
);
assert.equal(sameMode.mode_profile_draft_context.name, "午休模式");
assert.equal(sameMode.mode_profile_draft_context.source_request_id, "req-draft-change-into");
assert.equal(sameMode.mode_profile_draft_context.vehicle_id, "demo-vehicle");
assert.equal(Object.hasOwn(question, "mode_profile_draft_context"), false);
assert.equal(Object.hasOwn(anotherMode, "mode_profile_draft_context"), false);
});
test("shade position rows explain the target opening at semantic boundaries", async () => {
const positions = [0, 30, 50, 100];
const events = [
{ type: "message.accepted", route: "vehicle_control", engine: "n7web-mode-builder" },
{
type: "action.completed",
success: true,
payload: {
status: "succeeded",
execution_mode: "dry_run",
orchestration_route: "mode_profile_create_preview",
mode_profile_draft: {
name: "遮阳测试模式",
instruction: "遮阳帘目标开度测试",
operation_count: positions.length,
source_request_id: "req-draft-shade-positions",
vehicle_id: "demo-vehicle",
},
actions: positions.map((position) => ({
domain: "roof",
capability: "roof.set_shade_position",
target: { zone: "vehicle" },
parameters: { position_pct: position },
})),
},
},
{ type: "answer.completed", route: "vehicle_control", answer: "模式草稿已生成" },
];
const harness = createHarness(events);
harness.input.value = "构建一个遮阳测试模式";
await harness.sandbox.CognitiveAgentAssistant.submit();
const rows = harness.messages.querySelectorAll(".assistant-mode-draft-row");
assert.equal(rows.length, 4);
assert.deepEqual(
rows.map((row) => ({
label: row.children[1].textContent,
value: row.children[2].textContent,
})),
[
{ label: "遮阳帘", value: "完全关闭(目标开度 0%)" },
{ label: "遮阳帘", value: "目标开度 30%" },
{ label: "遮阳帘", value: "半开(目标开度 50%)" },
{ label: "遮阳帘", value: "完全打开(目标开度 100%)" },
],
);
});
test("ordinary questions and updates for another mode do not carry the draft reference", async () => {
const events = modeDraftEvents("req-draft-scoped", 24);
const harness = createHarness(events);
@@ -13,15 +13,15 @@ def test_console_contains_ai_assistant_surface():
assert 'aria-label="AI助手对话"' in html
assert 'placeholder="向 AI助手提问或交代工作"' in html
assert 'aria-label="发送消息"' in html
assert 'href="/assistant-chat.css?v=codex-assistant-23"' in html
assert 'src="/assistant-chat.js?v=codex-assistant-23"' in html
assert 'href="/assistant-chat.css?v=codex-assistant-24"' in html
assert 'src="/assistant-chat.js?v=codex-assistant-24"' in html
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")
assert 'const CACHE_VERSION = "cognitive-agent-pad-v272"' in service_worker
assert '"/assistant-chat.css?v=codex-assistant-23"' in service_worker
assert '"/assistant-chat.js?v=codex-assistant-23"' in service_worker
assert '"/assistant-chat.css?v=codex-assistant-24"' in service_worker
assert '"/assistant-chat.js?v=codex-assistant-24"' in service_worker
def test_codex_frontend_uses_one_streaming_endpoint_and_agent_work_events():
@@ -349,6 +349,9 @@ class Can1SemanticWhitelistTests(unittest.TestCase):
("停止遮阳帘", ["shade stop"]),
("遮阳帘开到20%", ["shade position 20"]),
("遮阳帘打开30%", ["shade position 30"]),
("遮阳帘调整为40%", ["shade position 40"]),
("遮阳帘变成50%", ["shade position 50"]),
("把遮阳帘换成100%", ["shade position 100"]),
)
for index, (utterance, expected) in enumerate(cases):
with self.subTest(utterance=utterance):
@@ -468,6 +468,49 @@ def test_pending_mode_draft_update_uses_fast_configuration_route_without_codex()
assert "调整待保存" in result["answer"]
def test_pending_mode_draft_change_into_percentage_is_a_configuration_update() -> None:
context = {
"name": "观影模式",
"source_request_id": "req-viewing-preview",
"vehicle_id": "denza_n7",
}
update = _authorize_vehicle_request(
"把观影模式的遮阳帘变成100%",
CONTROL_TERMS,
context,
)
question = _authorize_vehicle_request(
"把观影模式的遮阳帘变成100%吗",
CONTROL_TERMS,
context,
)
assert update.reason == "explicit_mode_profile_draft_update"
assert question.reason == "mode_profile_query"
def test_pending_mode_draft_noop_answer_says_target_was_already_set() -> None:
result = _sample_mode_preview_result(
"观影模式",
generation_source="draft_revision",
)
draft = result["mode_profile_draft"]
assert isinstance(draft, dict)
draft.update(
{
"requested_operation_count": 1,
"changed_operation_count": 0,
}
)
answer = CodexAssistantService._mode_profile_preview_answer(result, True)
assert "该项原本已是目标状态" in answer
assert "已保持不变并重新校验" in answer
assert "已调整待保存" not in answer
def test_active_draft_context_does_not_hijack_an_ordinary_question() -> None:
client = FakeCodexClient()
assistant = CodexAssistantService(client)
@@ -1037,6 +1080,8 @@ def test_safe_vehicle_result_exposes_mode_preview_without_command_flow() -> None
"vehicle_id": "denza_n7",
"domains": ["roof", "interior_light", "ambient_light"],
"operation_count": 4,
"requested_operation_count": 1,
"changed_operation_count": 0,
"command_flow": {"raw_can": "must-not-cross-tool-boundary"},
},
"intent_ir": {
@@ -1065,6 +1110,8 @@ def test_safe_vehicle_result_exposes_mode_preview_without_command_flow() -> None
"vehicle_id": "denza_n7",
"domains": ["roof", "interior_light", "ambient_light"],
"operation_count": 4,
"requested_operation_count": 1,
"changed_operation_count": 0,
}
assert safe["actions"] == [
{
@@ -36,7 +36,7 @@ def test_energy_monitor_assets_are_versioned_and_cached() -> None:
):
assert asset in html
assert f'"{asset}"' in service_worker
assert html.index("/assistant-chat.js?v=codex-assistant-23") < html.index(
assert html.index("/assistant-chat.js?v=codex-assistant-24") < html.index(
"/energy-monitor.js?v=energy-monitor-4"
)
assert 'const CACHE_VERSION = "cognitive-agent-pad-v272"' in service_worker