summaryrefslogtreecommitdiffhomepage
path: root/packages/core/src/tools/summon.ts
diff options
context:
space:
mode:
authorAdam Malczewski <[email protected]>2026-05-22 00:19:14 +0900
committerAdam Malczewski <[email protected]>2026-05-22 00:19:14 +0900
commitfb97d4cb72d0a90dde102b7001603716ee6e4c3b (patch)
tree55c6e8b56b3395008523ab94c16c4f526083d846 /packages/core/src/tools/summon.ts
parent7884709e3b2adb1b65c1c086257e0300eed51cee (diff)
downloaddispatch-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.ts88
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)}`;
+ }
+ },
+ };
+}