import { expect, test } from "bun:test"; // 自研(2026-09-17):Agent 运行中插话。 // // 这条功能的核心是**取消一个此前刻意加上的限制**:运行时输入框本来是禁用的 // (旧代码 `disabled={busy || running || …}`),策略提示词也据此写着「运行中用户无法打字」。 // 所以这里的断言要盯住两件事:入口真的打开了,以及旧前提没有残留在代码里。 test("运行中不再锁死输入框,发送走插话而不是新建轮次", async () => { // 面板拆分为主组件、展示组件与事件归约三个模块,源码约束覆盖整个模块组。 const panel = (await Promise.all(["canvas-cloud-agent-panel.tsx", "canvas-cloud-agent-panel-parts.tsx", "canvas-cloud-agent-events.ts"].map((file) => Bun.file(new URL(`../src/components/canvas/${file}`, import.meta.url)).text()))).join("\n"); // 运行中走插话分支(不建轮次、不走那套扣费幂等记账) expect(panel).toContain("await interject(value)"); expect(panel).toContain("sendAgentInterjection(activeRun.id, { text: value, messageId })"); // 乐观落进时间线时带插话标记,与「开新一轮」的消息在界面上区分开 expect(panel).toContain('interjection: "sent"'); // 输入框的禁用条件里不能再有 running —— 只保留「连不上事件流」「历史/待确认未水合」这类真不能发的状态 expect(panel).toContain('disabled={Boolean(run && connectionStatus !== "connected")'); const composer = panel.match(/ (current.trim() ? current : text))"); }); test("输入框运行中同时给出「发送(插话)」与「停止」", async () => { // 输入区已拆到 canvas-cloud-agent-composer.tsx,与消息渲染一起检查。 const composer = (await Promise.all(["canvas-cloud-agent-chat-ui.tsx", "canvas-cloud-agent-composer.tsx"].map((file) => Bun.file(new URL(`../src/components/canvas/${file}`, import.meta.url)).text()))).join("\n"); // 关键改动:canStop 不再等于 sending —— 否则运行中两个动作只能二选一 expect(composer).toContain("const canStop = Boolean(running && onStop);"); expect(composer).toContain("const canSubmit = !disabled && !sending && Boolean(prompt.trim() || attachments.length);"); expect(composer).not.toContain("const canStop = Boolean(sending && onStop);"); expect(composer).toContain('aria-label="停止本轮"'); // 插话/未送达要有可见标记 expect(composer).toContain('interjection?: "sent" | "undelivered";'); expect(composer).toContain('item.interjection === "undelivered" ? "插话未送达" : "插话"'); }); test("插话走独立接口,不碰轮次接口", async () => { const api = await Bun.file(new URL("../src/services/api/agent.ts", import.meta.url)).text(); expect(api).toContain("export function sendAgentInterjection("); expect(api).toContain("/interjections"); expect(api).toContain("messageId: string"); });