forked from EduCraft/curriculum-project-hub
fix(hub): expose TodoWrite via SDK default toolset
Unrestricted roles were still passed an explicit --tools name list. The native Claude binary only reliably registers bundled tools like TodoWrite on --tools default. Treat role tools JSON null as unrestricted, use the claude_code preset (→ default) in that case, and keep TodoWrite on allowedTools.
This commit is contained in:
+20
-12
@@ -140,7 +140,10 @@ export async function runAgent(req: RunRequest): Promise<RunResult> {
|
||||
let cleanupSecurity = async (): Promise<void> => {};
|
||||
try {
|
||||
await persistAgentMessage(req, "user", req.prompt);
|
||||
const toolConfig = claudeSdkToolConfigForRole(req.tools);
|
||||
// Role tools JSON null means unrestricted (omit), not "deny all".
|
||||
const roleToolIds = req.tools === null ? undefined : req.tools;
|
||||
const unrestricted = roleToolIds === undefined;
|
||||
const toolConfig = claudeSdkToolConfigForRole(roleToolIds);
|
||||
const workspaceRoot = req.project.workspaceRoot?.trim();
|
||||
if (workspaceRoot === undefined || workspaceRoot === "") {
|
||||
throw new Error("Agent run requires the configured workspace root");
|
||||
@@ -154,20 +157,25 @@ export async function runAgent(req: RunRequest): Promise<RunResult> {
|
||||
});
|
||||
cleanupSecurity = security.cleanup;
|
||||
const hasSkills = security.skillIds.length > 0;
|
||||
|
||||
type QueryOptions = NonNullable<Parameters<typeof query>[0]["options"]>;
|
||||
// When unrestricted, pass the SDK default toolset (`--tools default`) instead of
|
||||
// an explicit subset. Native claude uses that path to register bundled tools like
|
||||
// TodoWrite; listing names alone can omit them from the model's function list.
|
||||
// allowedTools still carries explicit MCP names + TodoWrite for permission.
|
||||
const skillExtras = hasSkills ? (["Skill"] as const) : ([] as const);
|
||||
const toolsOption: QueryOptions["tools"] = unrestricted
|
||||
? { type: "preset", preset: "claude_code" }
|
||||
: uniqueTools([...toolConfig.tools, "TodoWrite", ...skillExtras]);
|
||||
const allowedToolsOption = uniqueTools([
|
||||
...toolConfig.allowedTools,
|
||||
"TodoWrite",
|
||||
...skillExtras,
|
||||
]);
|
||||
|
||||
const options: QueryOptions = {
|
||||
cwd: security.cwd,
|
||||
// `skills` controls discovery/allowlisting, but an explicit `tools`
|
||||
// list still has to expose the Skill dispatcher itself.
|
||||
// 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"]),
|
||||
tools: toolsOption,
|
||||
allowedTools: allowedToolsOption,
|
||||
maxTurns: cap,
|
||||
includePartialMessages: true,
|
||||
// ADR-0018: bypass interactive prompts (headless server); the sandbox
|
||||
|
||||
Reference in New Issue
Block a user