67 lines
5.1 KiB
TypeScript
67 lines
5.1 KiB
TypeScript
import { createCanvasSaveQueue } from "@/lib/client/canvas-save-queue";
|
|
import { canvasHistoryForChat } from "@/lib/client/canvas-history-response";
|
|
import { expect, test } from "@playwright/test";
|
|
import { createElement } from "react";
|
|
import { renderToStaticMarkup } from "react-dom/server";
|
|
import { canvasAgentProgress, canvasAgentProgressLabel, canvasAgentActivities } from "@/lib/ai/canvas-agent-runtime/progress";
|
|
import { newAgentRunSnapshot, publicAgentError, type AgentRunView } from "@/lib/ai/canvas-agent-runtime/protocol";
|
|
import { serverLoader } from "../helpers/canvas-download-server-loader";
|
|
const { CanvasAgentProgressMessage } = serverLoader({})("components/canvas/chat-panel/canvas-agent-progress.tsx");
|
|
function fixture(): AgentRunView {
|
|
return { id: "r", assistantMessageId: "assistant-1", chatId: "c", projectId: "p", status: "running",
|
|
message: "ZCode 正在规划并执行画布任务", steps: 3, toolCount: 1, resultNodeIds: [], inputs: [],
|
|
updatedAt: new Date().toISOString(), cancelRequested: false, resumable: false,
|
|
activities: [{ id: "step-1", title: "生成图片", status: "started" }] };
|
|
}
|
|
test("history accepts existing exact-key payloads and rejects another tagged chat", () => {
|
|
const messages = [{ id: "assistant-1", parts: [{ type: "text", text: "completed" }] }];
|
|
expect(canvasHistoryForChat({ messages }, "chat-1")?.messages).toBe(messages);
|
|
expect(canvasHistoryForChat({ chatId: "chat-1", messages }, "chat-1")?.messages).toBe(messages);
|
|
expect(canvasHistoryForChat({ chatId: "chat-2", messages }, "chat-1")).toBeUndefined();
|
|
expect(canvasHistoryForChat({ messages }, "")).toBeUndefined();
|
|
});
|
|
test("public status converts legacy engine and isolation messages without exposing diagnostics", () => {
|
|
for (const text of ["ZCode 正在规划并执行画布任务", "正在启动 ZCode 独立沙箱", "sandbox bwrap failed at /var/lib/agent"])
|
|
expect(canvasAgentProgressLabel(text)).not.toMatch(/zcode|沙箱|sandbox|bwrap|\/var\//i);
|
|
expect(canvasAgentProgressLabel("正在生成图片")).toBe("正在生成图片");
|
|
expect(publicAgentError(new Error("ZCode sandbox failed: /opt/agent/bin/node"))).not.toMatch(/zcode|sandbox|\/opt\//i);
|
|
});
|
|
test("public activity exposes real step titles but never tool arguments, output, engine or host paths", () => {
|
|
const snapshot = newAgentRunSnapshot("检查图片", []);
|
|
snapshot.calls.push({ id: "read", name: "readCanvas", input: { secret: "PRIVATE" }, output: { debug: "/var/lib/agent" }, status: "succeeded" });
|
|
expect(canvasAgentActivities(snapshot)).toEqual([{ id: "read", title: "查看画布", status: "succeeded" }]);
|
|
const view = fixture(); const progress = canvasAgentProgress({ ...view, runtime: { engine: "zcode", sandbox: "linux-bwrap" } } as any);
|
|
expect(Object.keys(progress).sort()).toEqual(["activities", "id", "message", "status", "toolCount"]);
|
|
expect(JSON.stringify(progress)).not.toMatch(/zcode|sandbox|PRIVATE|\/var\//i);
|
|
});
|
|
test("progress renders as inline assistant content with one stop action and no second input or mode badge", () => {
|
|
const run = fixture();const html = renderToStaticMarkup(createElement(CanvasAgentProgressMessage, {
|
|
progress: canvasAgentProgress(run), run, onCancel() {},
|
|
}));
|
|
expect(html).toContain('data-agent-message-id="assistant-1"');
|
|
expect(html).toContain("生成图片"); expect(html).toContain("停止后续步骤");
|
|
expect(html).not.toMatch(/ZCode|沙箱|sandbox|Auto Agent|<input|<form|已处理\s*3\s*轮/i);
|
|
});
|
|
test("completed execution details remain expandable in saved assistant history without active controls", () => {
|
|
const run = { ...fixture(), status: "completed" as const };
|
|
const part = JSON.parse(JSON.stringify({ type: "data-canvasAgentProgress", data: canvasAgentProgress(run) }));
|
|
const html = renderToStaticMarkup(createElement(CanvasAgentProgressMessage, { progress: part.data }));
|
|
expect(html).toContain("处理完成");expect(html).toContain("生成图片");
|
|
expect(html).not.toContain("停止后续步骤");expect(html).not.toContain("<button");
|
|
expect(html).not.toMatch(/<details[^>]+open/);
|
|
});
|
|
|
|
test("pre-Agent flush and autosave read successive revisions instead of racing stale snapshots", async () => {
|
|
const queue = createCanvasSaveQueue();let release!: () => void;let revision = 1;let title = "before";
|
|
const gate = new Promise<void>(resolve => { release = resolve; });const sent: Array<{revision:number;title:string}> = [];
|
|
const first = queue.enqueue(async () => { sent.push({revision,title});await gate;revision=2;return true; });
|
|
const second = queue.enqueue(async () => { sent.push({revision,title});revision=3;return true; });
|
|
await Promise.resolve();expect(sent).toEqual([{revision:1,title:"before"}]);title="latest local edit";release();await Promise.all([first,second]);
|
|
expect(sent).toEqual([{revision:1,title:"before"},{revision:2,title:"latest local edit"}]);
|
|
});
|
|
test("a failed local save does not deadlock later saves or replay a failed snapshot", async () => {
|
|
const queue = createCanvasSaveQueue();let calls=0;
|
|
await expect(queue.enqueue(async()=>{calls++;throw new Error("revision conflict");})).rejects.toThrow("revision conflict");
|
|
await expect(queue.enqueue(async()=>{calls++;return true;})).resolves.toBe(true);expect(calls).toBe(2);
|
|
});
|