summaryrefslogtreecommitdiffhomepage
path: root/packages/core/src/tools/summon.ts
blob: 22ab35b34580f3e026343dea70b58367730eb131 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
import { z } from "zod";
import type { ToolDefinition } from "../types/index.js";

export interface SummonCallbacks {
	spawn(options: { task: string; tools: string[]; workingDirectory?: string }): Promise<string>;
	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<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;
			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)}`;
			}
		},
	};
}