diff --git a/components/canvas/CanvasChatPanel.tsx b/components/canvas/CanvasChatPanel.tsx index a3b489d51..3120eb0d8 100644 --- a/components/canvas/CanvasChatPanel.tsx +++ b/components/canvas/CanvasChatPanel.tsx @@ -4,6 +4,7 @@ import { useChat } from "@ai-sdk/react"; import { DefaultChatTransport } from "ai"; import { shouldUseCanvasAgentRuntime } from "@/lib/ai/canvas-agent-runtime/routing"; import { CanvasAgentProgressMessage } from "@/components/canvas/chat-panel/canvas-agent-progress"; +import { canvasHistoryForChat } from "@/lib/client/canvas-history-response"; import { useCanvasAgentRun } from "@/components/canvas/chat-panel/use-canvas-agent-run"; import { canvasAgentProgress, type CanvasAgentProgress } from "@/lib/ai/canvas-agent-runtime/progress"; import { useEffect, useRef, useState, useCallback, useMemo, lazy, Suspense, type ClipboardEvent as ReactClipboardEvent, type ComponentProps, type MouseEvent as ReactMouseEvent, type PointerEvent as ReactPointerEvent } from "react"; @@ -4515,8 +4516,7 @@ export function CanvasChatPanel({ dedupingInterval: 10000, } ); - const historyData = - fetchedHistoryData?.chatId === rawChatId ? fetchedHistoryData : undefined; + const historyData = canvasHistoryForChat(fetchedHistoryData, rawChatId); const initialCanvasMessageCount = activeChatId === chatId ? initialMessages.length : 0; const voteSWRKey = diff --git a/lib/client/canvas-history-response.ts b/lib/client/canvas-history-response.ts new file mode 100644 index 000000000..f46a0352c --- /dev/null +++ b/lib/client/canvas-history-response.ts @@ -0,0 +1,9 @@ +/** Accept an exact-key SWR payload from older consumers without weakening tagged chat isolation. */ +export function canvasHistoryForChat( + payload: { chatId?: string; messages: T[] } | undefined, + chatId: string, +): { messages: T[] } | undefined { + if (!payload || !chatId || !Array.isArray(payload.messages)) return undefined; + if (payload.chatId !== undefined && payload.chatId !== chatId) return undefined; + return payload; +} diff --git a/tests/routes/canvas-agent-runtime-progress.test.tsx b/tests/routes/canvas-agent-runtime-progress.test.tsx index dc79d291b..90c6ea86e 100644 --- a/tests/routes/canvas-agent-runtime-progress.test.tsx +++ b/tests/routes/canvas-agent-runtime-progress.test.tsx @@ -1,4 +1,5 @@ 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"; @@ -12,6 +13,13 @@ function fixture(): AgentRunView { 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);