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:
2026-07-23 22:42:02 +08:00
parent 4a03bc1f4a
commit 2f79b7743f
8 changed files with 263 additions and 18 deletions
+20 -3
View File
@@ -160,8 +160,14 @@ export async function runAgent(req: RunRequest): Promise<RunResult> {
cwd: security.cwd,
// `skills` controls discovery/allowlisting, but an explicit `tools`
// list still has to expose the Skill dispatcher itself.
tools: [...toolConfig.tools, ...(hasSkills ? ["Skill"] : [])],
allowedTools: [...toolConfig.allowedTools],
// TodoWrite is always available so multi-step runs can surface a live checklist
// on the Feishu card (Manus-style progress), even when a role whitelists tools.
tools: uniqueTools([
...toolConfig.tools,
"TodoWrite",
...(hasSkills ? ["Skill"] : []),
]),
allowedTools: uniqueTools([...toolConfig.allowedTools, "TodoWrite"]),
maxTurns: cap,
includePartialMessages: true,
// ADR-0018: bypass interactive prompts (headless server); the sandbox
@@ -178,7 +184,10 @@ export async function runAgent(req: RunRequest): Promise<RunResult> {
// The project workspace is untrusted input. Do not load user/project
// settings that could widen tools, hooks, MCP servers, or sandbox paths.
settingSources: [],
settings: { disableBundledSkills: true },
settings: {
disableBundledSkills: true,
todoFeatureEnabled: true,
},
...(hasSkills && security.skillPluginRoot !== undefined
? { plugins: [{ type: "local" as const, path: security.skillPluginRoot, skipMcpDiscovery: true }] }
: {}),
@@ -405,3 +414,11 @@ function extractToolResultText(content: unknown): string {
}
return parts.join("\n");
}
function uniqueTools(tools: readonly string[]): string[] {
const out: string[] = [];
for (const tool of tools) {
if (!out.includes(tool)) out.push(tool);
}
return out;
}