fix: gate canvas agent view operations on client

This commit is contained in:
134 2026-06-23 19:09:07 +08:00
parent 2e386cb931
commit cb44b2237f
3 changed files with 37 additions and 0 deletions

View File

@ -139,6 +139,7 @@ import {
getCanvasMediaPayloadHandledItemIds,
getCanvasOperationHandledMediaItemIds,
type ImageGenerationStatusStreamPayload,
isCanvasAgentViewOperationClientEnabled,
shouldRetryCanvasViewOperationAfterRefresh,
shouldSkipCanvasMediaPayloadForHandledItems,
} from "@/components/canvas/chat-panel/canvas-agent-stream";
@ -482,6 +483,10 @@ const CANVAS_IMAGE_TASK_TIMEOUT_MS = 10 * 60 * 1000;
const CANVAS_GENERATION_NODE_GAP = 56;
const CANVAS_GENERATION_STACK_GAP = 28;
const CANVAS_GENERATION_COLLISION_GAP = 32;
const CANVAS_AGENT_VIEW_OPERATION_CLIENT_ENABLED =
isCanvasAgentViewOperationClientEnabled(
process.env.NEXT_PUBLIC_CANVAS_AGENT_VIEW_OPERATION_ENABLED
);
function hashCanvasChatValue(value: string) {
let hash = 0;
@ -1978,6 +1983,7 @@ export function CanvasChatPanel({
const applyCanvasViewOperation = useCallback(
(payload: CanvasViewOperationStreamPayload) => {
if (!CANVAS_AGENT_VIEW_OPERATION_CLIENT_ENABLED) return false;
if (!payload || typeof payload !== "object") return false;
const targetProjectId = String(payload.projectId || "").trim();
if (targetProjectId && targetProjectId !== canvasId) return false;

View File

@ -42,6 +42,22 @@ export function normalizeCanvasStreamIdList(value: unknown) {
: [];
}
export function normalizeCanvasAgentClientBoolean(
value: unknown,
fallback: boolean
) {
if (typeof value !== "string") return fallback;
const normalized = value.trim().toLowerCase();
if (!normalized) return fallback;
if (["1", "true", "yes", "on", "enabled"].includes(normalized)) return true;
if (["0", "false", "no", "off", "disabled"].includes(normalized)) return false;
return fallback;
}
export function isCanvasAgentViewOperationClientEnabled(value: unknown) {
return normalizeCanvasAgentClientBoolean(value, true);
}
export function getCanvasOperationHandledMediaItemIds(
payload: CanvasOperationStreamPayload
) {

View File

@ -9,6 +9,8 @@ import {
import {
getCanvasMediaPayloadHandledItemIds,
getCanvasOperationHandledMediaItemIds,
isCanvasAgentViewOperationClientEnabled,
normalizeCanvasAgentClientBoolean,
shouldRetryCanvasViewOperationAfterRefresh,
shouldSkipCanvasMediaPayloadForHandledItems,
} from "@/components/canvas/chat-panel/canvas-agent-stream";
@ -207,6 +209,19 @@ test.describe("canvas chat pure helpers", () => {
expect(isAgentModeScopedChatId(canvasChatId)).toBe(false);
});
test("normalizes client-side canvas agent feature flags", () => {
expect(normalizeCanvasAgentClientBoolean(undefined, true)).toBe(true);
expect(normalizeCanvasAgentClientBoolean("", false)).toBe(false);
expect(normalizeCanvasAgentClientBoolean("enabled", false)).toBe(true);
expect(normalizeCanvasAgentClientBoolean("off", true)).toBe(false);
expect(normalizeCanvasAgentClientBoolean("disabled", true)).toBe(false);
expect(normalizeCanvasAgentClientBoolean("unexpected", true)).toBe(true);
expect(isCanvasAgentViewOperationClientEnabled(undefined)).toBe(true);
expect(isCanvasAgentViewOperationClientEnabled("false")).toBe(false);
expect(isCanvasAgentViewOperationClientEnabled("1")).toBe(true);
});
test("rejects stale autosave bases without blocking fresh or missing revisions", () => {
const currentUpdatedAt = new Date("2026-06-22T00:00:05.500Z");