diff --git a/lib/ai/canvas-agent-runtime/cutout-completion.ts b/lib/ai/canvas-agent-runtime/cutout-completion.ts index f3dd411f3..3846c45cf 100644 --- a/lib/ai/canvas-agent-runtime/cutout-completion.ts +++ b/lib/ai/canvas-agent-runtime/cutout-completion.ts @@ -4,9 +4,15 @@ import { finalResultNodeIds } from "./results"; /** Only a complete, single-purpose cutout may finish on file evidence alone. */ export function isCompletedSimpleCutout(snapshot: AgentRunSnapshot): boolean { if (snapshot.workspace === "video") return false; + if (snapshot.inputs.length) return false; // Supplements can introduce a new action. const request = [snapshot.objective, ...snapshot.inputs.map(input => input.text)].join("\n"); - if (!/抠图|抠出|扣出|去背|去(?:掉|除)?背景|移除背景|删除背景|remove\s+(?:the\s+)?background/i.test(request)) return false; - if (/生成|换背景|更换背景|改.{0,8}(?:颜色|色彩|蓝|绿|红)|变.{0,5}色|排版|拼图|增强|修复|裁剪|尺寸|比例|下载|导出|检查|分析|对比|边缘|白边|毛边|光晕|发丝|锯齿|精细|generate|resize|export|inspect|enhance/i.test(request)) return false; + const intent = /抠图|抠出(?:来)?|扣出(?:来)?|去背|去(?:掉|除)?背景|移除背景|删除背景|remove\s+(?:the\s+)?background/i.exec(request); + if (!intent) return false; + // An unknown follow-up instruction is never silently treated as just cutout. + const tail = request.slice(intent.index + intent[0].length).replace(/[\s,。!!、;;,.::]/g, "") + .replace(/(?:保持透明背景|保持背景透明|透明背景|背景透明|保持透明|透明|保留原图|原图保留|放在原图旁边|结果放在原图旁边|放到画布上|PNG格式|PNG|一下|谢谢)/gi, ""); + if (tail) return false; + if (/生成|换背景|更换背景|改.{0,8}(?:颜色|色彩|蓝|绿|红)|变.{0,5}色|排版|拼图|增强|修复|裁剪|尺寸|比例|下载|导出|检查|分析|对比|边缘|白边|毛边|光晕|发丝|锯齿|精细|移动|旋转|翻转|改名|重命名|删除原图|generate|resize|export|inspect|enhance|recolor|change|replace|crop|download/i.test(request)) return false; if (!snapshot.calls.length || snapshot.calls.some(call => call.status !== "succeeded" || call.uncertain || call.rejected)) return false; if (snapshot.calls.some(call => !["planCanvasTask", "readCanvas", "removeBackground", "analyzeCanvasImages", "listCanvasCapabilities"].includes(call.name))) return false; const writes = snapshot.calls.filter(call => call.name === "removeBackground"); diff --git a/tests/routes/canvas-agent-runtime-review.test.ts b/tests/routes/canvas-agent-runtime-review.test.ts index b5738e887..3f143c077 100644 --- a/tests/routes/canvas-agent-runtime-review.test.ts +++ b/tests/routes/canvas-agent-runtime-review.test.ts @@ -165,12 +165,12 @@ test("opaque images and fully empty transparent images cannot complete a cutout" test("cutout completion does not skip later user actions, edge inspection or an incomplete batch", async () => { const f = await cutoutFixture(); - for (const request of ["抠图然后下载", "抠图后换背景生成三张", "抠图并检查边缘白边", "抠图后把毛改成绿色"]) { + for (const request of ["抠图然后下载", "抠图后换背景生成三张", "抠图并检查边缘白边", "抠图后把毛改成绿色", "抠图后移动到左侧", "先重命名为小鸡再抠图", "抠图再做点别的", "remove background and recolor blue"]) { f.params.snapshot.objective = request; expect(isCompletedSimpleCutout(f.params.snapshot)).toBe(false); } f.params.snapshot.objective = "抠图"; - f.params.snapshot.requirements.expectedImageCount = 2; + f.params.snapshot.requirements!.expectedImageCount = 2; expect(isCompletedSimpleCutout(f.params.snapshot)).toBe(false); });