feat: add seat firmware workbench and OTA delivery

This commit is contained in:
vee
2026-09-16 17:55:30 +08:00
parent 3eeca2ea75
commit ab8e43050c
70 changed files with 22594 additions and 316 deletions
+59
View File
@@ -75,6 +75,65 @@ async function main() {
await page.locator('[data-execution-mode="dry_run"]').click();
assert.equal(await page.evaluate(() => window.CognitiveAgentConsole.getExecutionMode()), "dry_run");
async function submitDraft(text) {
const responsePromise = responseFor("/api/assistant/stream");
await page.locator("#utterance").fill(text);
await page.locator("#utterance").press("Enter");
const response = await responsePromise;
const body = responses.filter((item) => item.pathname === "/api/assistant/stream").at(-1).text;
assert.equal(response.status(), 200, `${text}: ${body}`);
const events = body.trim().split("\n").map((line) => JSON.parse(line));
await waitIdle();
const payload = events.find((event) => event.payload?.mode_profile_draft)?.payload;
assert.ok(payload, `Missing draft for ${text}: ${body}`);
assert.equal(payload.status, "succeeded");
assert.equal(payload.execution_mode, "dry_run");
const draft = payload.mode_profile_draft;
const card = page.locator(`[data-assistant-mode-draft="${draft.source_request_id}"]`);
await card.waitFor({ state: "visible" });
assert.equal(await card.locator(".assistant-mode-draft-row").count(), draft.operation_count);
return { payload, draft, card, request: posted.filter((item) => item.pathname === "/api/assistant/stream").at(-1).payload };
}
// The original failing two-turn request must revise the server-owned
// draft, not attempt immediate hardware execution or replace its seats.
const recline = await submitDraft("构建一个躺平模式");
const originalActions = recline.payload.actions;
await recline.card.scrollIntoViewIfNeeded();
await page.screenshot({ path: path.join(evidenceDir, "mode-device-addition-before.png") });
const expanded = await submitDraft("在躺平模式中加入空调、氛围灯、阅读灯、车窗等设备动作");
assert.deepEqual(expanded.request.mode_profile_draft_context, {
name: recline.draft.name,
source_request_id: recline.draft.source_request_id,
vehicle_id: recline.draft.vehicle_id,
});
assert.notEqual(expanded.draft.source_request_id, recline.draft.source_request_id);
assert.equal(expanded.draft.draft_revision, 2);
const actionKey = (action) => `${action.capability}:${action.target?.instance || action.target?.zone || ""}`;
const expandedActions = new Map(expanded.payload.actions.map((action) => [actionKey(action), action]));
for (const original of originalActions) {
assert.deepEqual(expandedActions.get(actionKey(original))?.parameters, original.parameters,
"adding devices must preserve all existing action values");
}
for (const capability of ["climate.set_temperature", "interior_light.set_reading_power", "window.set_position"]) {
assert.ok(expanded.payload.actions.some((action) => action.capability === capability), capability);
}
assert.ok(expanded.payload.actions.some((action) => action.domain === "ambient_light"));
assert.ok(expanded.payload.actions.every((action) => action.domain !== "seat"
|| (action.target?.instance || action.target?.zone) === "passenger"));
assert.ok(expanded.draft.operation_count > recline.draft.operation_count);
assert.equal(await recline.card.getAttribute("data-superseded"), "true");
assert.equal(await recline.card.locator(".assistant-mode-draft-save").isDisabled(), true);
assert.equal(posted.filter((item) => item.pathname === "/api/mode-profiles").length, 0);
await expanded.card.scrollIntoViewIfNeeded();
await page.screenshot({ path: path.join(evidenceDir, "mode-device-addition-after.png") });
const temperatureEdit = await submitDraft("把躺平模式的空调温度调整到26度");
assert.equal(temperatureEdit.request.mode_profile_draft_context.source_request_id, expanded.draft.source_request_id);
assert.equal(temperatureEdit.draft.draft_revision, 3);
assert.equal(temperatureEdit.draft.operation_count, expanded.draft.operation_count);
assert.ok(temperatureEdit.payload.actions.some((action) => action.capability === "climate.set_temperature"
&& action.parameters.temperature_c === 26));
// Exercise the same real assistant endpoint and rendered cards for both
// implicit passenger selection and an explicit driver request. These
// previews stay unsaved, so the timeline save/trial tests remain isolated.