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| {
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("