import { z } from "zod"; import type { ToolDefinition } from "../types/index.js"; export interface SummonCallbacks { spawn(options: { task: string; tools: string[]; workingDirectory?: string }): Promise; getResult( agentId: string, ): Promise<{ status: "done"; result: string } | { status: "error"; error: string }>; } export function createSummonTool( defaultWorkingDirectory: string, callbacks: SummonCallbacks, ): ToolDefinition { return { name: "summon", description: [ "Spawn a new child agent to work on a task independently.", "", "By default, blocks until the child agent finishes and returns the result directly.", "Set background=true to return immediately with an agent_id instead — use retrieve to collect the result later.", "", "The child agent runs in its own tab visible to the user. Use the 'retrieve' tool with the returned agent_id to get the result when needed.", "", "Pattern for parallel work:", " 1. Call summon multiple times with background=true to start several agents", " 2. Do your own work or wait", " 3. Call retrieve for each agent_id to collect results", "", "The 'tools' parameter controls what the child can do. Available tool names:", " - read_file: Read file contents", " - list_files: List files and directories", " - write_file: Write/edit files", " - run_shell: Execute shell commands", " - todo: Track work items", " - summon: Spawn its own child agents (enables nesting)", " - retrieve: Collect results from its children (required if summon is given)", " - web_search: Search the web", " - youtube_transcribe: Fetch YouTube video transcripts", "", "If tools is omitted, the child gets read_file, list_files, and todo only (read-only by default).", ].join("\n"), parameters: z.object({ task: z .string() .describe( "Detailed instructions for the child agent. Be specific about what it should do and what it should return.", ), tools: z .array( z.enum([ "read_file", "list_files", "write_file", "run_shell", "todo", "summon", "retrieve", "web_search", "youtube_transcribe", ]), ) .optional() .describe( 'Tool names to give the child. Defaults to ["read_file", "list_files", "todo"]. Include "summon" and "retrieve" to allow nesting.', ), working_directory: z .string() .optional() .describe( "Absolute path for the child to work in. Defaults to the current working directory.", ), background: z .boolean() .optional() .describe( "If true, returns immediately with an agent_id for later retrieval. If false (default), blocks until the child agent finishes and returns the result directly.", ), }), execute: async (args: Record): Promise => { const task = args.task as string; const tools = (args.tools as string[] | undefined) ?? ["read_file", "list_files", "todo"]; const workingDirectory = (args.working_directory as string | undefined) ?? defaultWorkingDirectory; const background = (args.background as boolean | undefined) ?? false; try { const agentId = await callbacks.spawn({ task, tools, workingDirectory, }); if (!background) { // Block until the child agent completes const result = await callbacks.getResult(agentId); if (result.status === "done") { return result.result; } return `Error from child agent: ${result.error}`; } return [ `Agent spawned successfully.`, `agent_id: ${agentId}`, ``, `The child agent is now working on the task in its own tab.`, `Use the retrieve tool with this agent_id to get the result when ready.`, ].join("\n"); } catch (err) { return `Error spawning agent: ${err instanceof Error ? err.message : String(err)}`; } }, }; }