forked from EduCraft/curriculum-project-hub
feat(hub): live TodoWrite checklist on Feishu agent cards
Always expose Claude Agent SDK TodoWrite (todoFeatureEnabled) so multi-step runs can plan in the open. Parse TodoWrite payloads into a progress panel on the streaming Feishu card (completed/in_progress/pending) and filter raw TodoWrite noise out of the tool-use list.
This commit is contained in:
@@ -113,8 +113,10 @@ describe("runAgent", () => {
|
||||
permissionMode: "bypassPermissions",
|
||||
allowDangerouslySkipPermissions: true,
|
||||
settingSources: [],
|
||||
settings: { disableBundledSkills: true },
|
||||
settings: { disableBundledSkills: true, todoFeatureEnabled: true },
|
||||
skills: [],
|
||||
tools: expect.arrayContaining(["TodoWrite"]),
|
||||
allowedTools: expect.arrayContaining(["TodoWrite"]),
|
||||
strictMcpConfig: true,
|
||||
sandbox: expect.objectContaining({
|
||||
enabled: true,
|
||||
@@ -183,8 +185,9 @@ describe("runAgent", () => {
|
||||
|
||||
expect(queryMock.mock.calls[0]?.[0]).toMatchObject({
|
||||
options: {
|
||||
tools: ["Read", "Bash"],
|
||||
allowedTools: ["Read", "Bash", "mcp__cph_hub__send_file"],
|
||||
tools: ["Read", "Bash", "TodoWrite"],
|
||||
allowedTools: ["Read", "Bash", "mcp__cph_hub__send_file", "TodoWrite"],
|
||||
settings: expect.objectContaining({ todoFeatureEnabled: true }),
|
||||
},
|
||||
});
|
||||
});
|
||||
@@ -205,8 +208,9 @@ describe("runAgent", () => {
|
||||
|
||||
expect(queryMock.mock.calls[0]?.[0]).toMatchObject({
|
||||
options: {
|
||||
tools: [],
|
||||
allowedTools: [],
|
||||
tools: ["TodoWrite"],
|
||||
allowedTools: ["TodoWrite"],
|
||||
settings: expect.objectContaining({ todoFeatureEnabled: true }),
|
||||
},
|
||||
});
|
||||
});
|
||||
@@ -234,9 +238,10 @@ describe("runAgent", () => {
|
||||
|
||||
expect(queryMock.mock.calls[0]?.[0]).toMatchObject({
|
||||
options: {
|
||||
tools: ["Skill"],
|
||||
tools: ["TodoWrite", "Skill"],
|
||||
plugins: [expect.objectContaining({ type: "local", skipMcpDiscovery: true })],
|
||||
skills: ["cph-runtime:typst"],
|
||||
settings: expect.objectContaining({ todoFeatureEnabled: true }),
|
||||
},
|
||||
});
|
||||
});
|
||||
|
||||
@@ -0,0 +1,75 @@
|
||||
import { describe, expect, it } from "vitest";
|
||||
import {
|
||||
isTodoWriteTool,
|
||||
parseTodoWriteInput,
|
||||
todoProgressSummary,
|
||||
} from "../../src/agent/todoList.js";
|
||||
import { buildAgentCard } from "../../src/feishu/card/builder.js";
|
||||
|
||||
describe("todo list parse", () => {
|
||||
it("accepts TodoWrite payloads", () => {
|
||||
const todos = parseTodoWriteInput({
|
||||
todos: [
|
||||
{ content: "搜题", status: "completed", activeForm: "正在搜题" },
|
||||
{ content: "写报告", status: "in_progress", activeForm: "正在写报告" },
|
||||
{ content: "发卡片", status: "pending" },
|
||||
],
|
||||
});
|
||||
expect(todos).toEqual([
|
||||
{ content: "搜题", status: "completed", activeForm: "正在搜题" },
|
||||
{ content: "写报告", status: "in_progress", activeForm: "正在写报告" },
|
||||
{ content: "发卡片", status: "pending", activeForm: undefined },
|
||||
]);
|
||||
expect(todoProgressSummary(todos!)).toEqual({ completed: 1, total: 3, inProgress: 1 });
|
||||
});
|
||||
|
||||
it("rejects empty or invalid bodies", () => {
|
||||
expect(parseTodoWriteInput(null)).toBeNull();
|
||||
expect(parseTodoWriteInput({ todos: [] })).toBeNull();
|
||||
expect(parseTodoWriteInput({ todos: [{ status: "pending" }] })).toBeNull();
|
||||
});
|
||||
|
||||
it("recognizes TodoWrite tool names", () => {
|
||||
expect(isTodoWriteTool("TodoWrite")).toBe(true);
|
||||
expect(isTodoWriteTool("Bash")).toBe(false);
|
||||
});
|
||||
});
|
||||
|
||||
describe("agent card todo panel", () => {
|
||||
it("renders a progress checklist when todos are present", () => {
|
||||
const card = buildAgentCard({
|
||||
phase: "streaming",
|
||||
text: "",
|
||||
reasoningText: undefined,
|
||||
todos: [
|
||||
{ content: "A", status: "completed", activeForm: undefined },
|
||||
{ content: "B", status: "in_progress", activeForm: "Doing B" },
|
||||
{ content: "C", status: "pending", activeForm: undefined },
|
||||
],
|
||||
toolUseSteps: [
|
||||
{
|
||||
id: "1",
|
||||
seq: 1,
|
||||
toolName: "TodoWrite",
|
||||
toolUseId: "t1",
|
||||
input: {},
|
||||
result: undefined,
|
||||
error: undefined,
|
||||
status: "success",
|
||||
startedAt: 0,
|
||||
finishedAt: 1,
|
||||
durationMs: 1,
|
||||
},
|
||||
],
|
||||
toolUseElapsedMs: 10,
|
||||
isError: undefined,
|
||||
interrupted: undefined,
|
||||
runId: "run-1",
|
||||
});
|
||||
|
||||
const json = JSON.stringify(card);
|
||||
expect(json).toContain("任务进度 1/3");
|
||||
expect(json).toContain("Doing B");
|
||||
expect(json).toContain("collapsible_panel");
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user