Files
ttstd-infinite-canvas/web/test/agent-interjection.test.ts

55 lines
3.6 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
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(/<AgentChatComposer[\s\S]*?disabled=\{([^\n]+)\}/)?.[1];
expect(composer).toBeDefined();
expect(composer).not.toMatch(/\bbusy\b|\brunning\b/);
// 停止按钮由 running 驱动,发送按钮由我们自己的请求驱动,两者不再互斥
expect(panel).toContain("sending={busy}");
expect(panel).toContain("running={running}");
// 两个 SSE 事件都要处理:回显(多标签页)与未送达退回
expect(panel).toContain('event.type === "user_interjection"');
expect(panel).toContain('event.type === "user_interjection_dropped"');
// 退回时要让文案能被再次发送
expect(panel).toContain("setPrompt?.((current) => (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");
});