forked from EduCraft/curriculum-project-hub
fix(hub): ship checklist via cph_hub todo_write MCP tool
Native Claude TodoWrite is not registered in headless agent mode even with --tools default. Add mcp__cph_hub__todo_write (always enabled), mirror the TodoWrite schema, instruct multi-step runs to use it, and keep the Feishu progress panel parsing both native and hub tool names.
This commit is contained in:
@@ -43,6 +43,7 @@ const TOOL_ICONS: Record<string, string> = {
|
||||
grep: "search-filled",
|
||||
edit: "edit-filled",
|
||||
todowrite: "todo-filled",
|
||||
todo_write: "todo-filled",
|
||||
send_file: "send-filled",
|
||||
request_approval: "thumb-up-filled",
|
||||
feishu_read_context: "search-filled",
|
||||
@@ -52,10 +53,20 @@ const TOOL_ICONS: Record<string, string> = {
|
||||
};
|
||||
|
||||
function toolIcon(toolName: string): string {
|
||||
const normalized = toolName.toLowerCase().replace(/^mcp_/, "");
|
||||
const normalized = toolName.toLowerCase().replace(/^mcp_/, "").replace(/^cph_hub__/, "");
|
||||
return TOOL_ICONS[normalized] ?? "tool-filled";
|
||||
}
|
||||
|
||||
function isTodoToolName(toolName: string): boolean {
|
||||
const lower = toolName.toLowerCase();
|
||||
return (
|
||||
lower === "todowrite" ||
|
||||
lower === "todo_write" ||
|
||||
lower.endsWith("__todo_write") ||
|
||||
lower.endsWith("__todowrite")
|
||||
);
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Card builder
|
||||
// ---------------------------------------------------------------------------
|
||||
@@ -80,8 +91,8 @@ export function buildAgentCard(params: {
|
||||
elements.push(buildTodoPanel(todos, phase !== "complete"));
|
||||
}
|
||||
|
||||
// Tool-use panel (exclude TodoWrite itself — already shown as checklist)
|
||||
const visibleToolSteps = toolUseSteps.filter((step) => step.toolName.toLowerCase() !== "todowrite");
|
||||
// Tool-use panel (exclude todo tools — already shown as checklist)
|
||||
const visibleToolSteps = toolUseSteps.filter((step) => !isTodoToolName(step.toolName));
|
||||
if (visibleToolSteps.length > 0) {
|
||||
elements.push(buildToolUsePanel(visibleToolSteps, toolUseElapsedMs, phase !== "complete"));
|
||||
} else if (
|
||||
|
||||
@@ -399,6 +399,50 @@ export function createFileDeliveryMcpServer(options: FileDeliveryToolOptions): M
|
||||
);
|
||||
}
|
||||
}
|
||||
if (enabledTools.has("todo_write")) {
|
||||
tools.push(
|
||||
tool(
|
||||
"todo_write",
|
||||
"Create or replace the shared task checklist for this run. Call this first when the user`s request has multiple steps, then again whenever progress changes. Each item needs content + status (pending|in_progress|completed). Optionally set activeForm (present-tense label) for the current in_progress item. Keep exactly one item in_progress when work is underway. Hub shows this list on the Feishu card so teachers can track progress.",
|
||||
{
|
||||
todos: z
|
||||
.array(
|
||||
z.object({
|
||||
content: z.string().min(1).describe("Imperative task description, e.g. Search PBank for derivatives."),
|
||||
status: z
|
||||
.enum(["pending", "in_progress", "completed"])
|
||||
.describe("pending | in_progress | completed"),
|
||||
activeForm: z
|
||||
.string()
|
||||
.optional()
|
||||
.describe("Present continuous label while in_progress, e.g. Searching PBank."),
|
||||
}),
|
||||
)
|
||||
.min(1)
|
||||
.max(32)
|
||||
.describe("Full replacement list for the checklist (not a patch)."),
|
||||
},
|
||||
async (args) => {
|
||||
const completed = args.todos.filter((t) => t.status === "completed").length;
|
||||
const inProgress = args.todos.filter((t) => t.status === "in_progress").length;
|
||||
const lines = args.todos.map((t, i) => {
|
||||
const mark = t.status === "completed" ? "x" : t.status === "in_progress" ? ">" : " ";
|
||||
const label = t.status === "in_progress" && t.activeForm ? t.activeForm : t.content;
|
||||
return `${i + 1}. [${mark}] ${label}`;
|
||||
});
|
||||
return {
|
||||
content: [
|
||||
{
|
||||
type: "text",
|
||||
text: `Checklist updated ${completed}/${args.todos.length} completed, ${inProgress} in progress.\n${lines.join("\n")}`,
|
||||
},
|
||||
],
|
||||
};
|
||||
},
|
||||
{ alwaysLoad: true },
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
const instructions = mcpInstructions(enabledTools);
|
||||
return createSdkMcpServer({
|
||||
@@ -507,6 +551,12 @@ function mcpInstructions(enabledTools: ReadonlySet<CphHubMcpToolId>): string {
|
||||
"If tools fail because no ACTIVE pbank capability connection exists, tell the user an org admin must configure 题库 on the admin capabilities page.",
|
||||
);
|
||||
}
|
||||
if (enabledTools.has("todo_write")) {
|
||||
instructions.push(
|
||||
"For multi-step work, call todo_write first with a full checklist, then update it as each step starts/finishes so the teacher sees live progress on the card.",
|
||||
"Prefer mcp__cph_hub__todo_write (todo_write) for progress tracking — do not skip it because built-in TodoWrite is absent.",
|
||||
);
|
||||
}
|
||||
instructions.push(
|
||||
"Role skill docs (when bound) are readable at .cph/runtime-skills/<skill-name>/SKILL.md or $CPH_RUNTIME_SKILLS_DIR/<skill-name>/SKILL.md. Prefer the Skill tool when available. Workspace .claude/ and .mcp.json are sandbox stubs — not skill or MCP source.",
|
||||
);
|
||||
|
||||
Reference in New Issue
Block a user