diff options
| author | Adam Malczewski <[email protected]> | 2026-05-22 00:19:14 +0900 |
|---|---|---|
| committer | Adam Malczewski <[email protected]> | 2026-05-22 00:19:14 +0900 |
| commit | fb97d4cb72d0a90dde102b7001603716ee6e4c3b (patch) | |
| tree | 55c6e8b56b3395008523ab94c16c4f526083d846 /packages/core/src/tools/summon.ts | |
| parent | 7884709e3b2adb1b65c1c086257e0300eed51cee (diff) | |
| download | dispatch-fb97d4cb72d0a90dde102b7001603716ee6e4c3b.tar.gz dispatch-fb97d4cb72d0a90dde102b7001603716ee6e4c3b.zip | |
feat: agent summoning system, todo improvements, security fixes, double-execution bug fix
- Add summon/retrieve tools for spawning child agents in new tabs
- summon: non-blocking, returns agent_id immediately
- retrieve: blocking, waits for child to finish, returns result
- Child tools are intersected with parent permissions (no privilege escalation)
- Working directory validated to stay within workspace
- Abort controller stops orphaned agents on tab close
- Rename task_list tool to todo with comprehensive usage guidance in system prompt
- Rename PermissionLog.svelte to ToolPermissions.svelte
- Add 'Summon agents' toggle to tool permissions UI
- Redesign TaskListPanel with DaisyUI checkboxes (indeterminate for in-progress)
- Remove 'blocked' status from task system
- Add tab-created WebSocket event for child agent tab visibility
- Add HMR cleanup for WebSocket connections (close stale connections on hot reload)
- Fix ensureAssistantMessage to not throw on closed tabs
- Fix double tool execution: remove execute from AI SDK tool() in registry.ts
(agent.ts already executes tools manually via executeToolWithStreaming)
- Fix all pre-existing test failures (missing mocks, stale API signatures)
- Add debug info to copy button (tab ID, injected skills, all tab IDs)
- Add tab ID and tools to conversation copy output
Diffstat (limited to 'packages/core/src/tools/summon.ts')
| -rw-r--r-- | packages/core/src/tools/summon.ts | 88 |
1 files changed, 88 insertions, 0 deletions
diff --git a/packages/core/src/tools/summon.ts b/packages/core/src/tools/summon.ts new file mode 100644 index 0000000..582b871 --- /dev/null +++ b/packages/core/src/tools/summon.ts @@ -0,0 +1,88 @@ +import { z } from "zod"; +import type { ToolDefinition } from "../types/index.js"; + +export interface SummonCallbacks { + spawn(options: { task: string; tools: string[]; workingDirectory?: string }): Promise<string>; +} + +export function createSummonTool( + defaultWorkingDirectory: string, + callbacks: SummonCallbacks, +): ToolDefinition { + return { + name: "summon", + description: [ + "Spawn a new child agent to work on a task independently. Returns immediately with an agent_id — does NOT wait for the child to finish.", + "", + "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 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)", + "", + "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", + ]), + ) + .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.", + ), + }), + execute: async (args: Record<string, unknown>): Promise<string> => { + 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; + + try { + const agentId = await callbacks.spawn({ + task, + tools, + workingDirectory, + }); + 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)}`; + } + }, + }; +} |
