Skip to content

Commit 142bc50

Browse files
committed
add subagent parallel tool
1 parent c2b2133 commit 142bc50

4 files changed

Lines changed: 173 additions & 3 deletions

File tree

extensions/cli/src/subagent/index.ts

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,3 +26,24 @@ export const SUBAGENT_TOOL_META: Tool = {
2626
},
2727
run: async () => "",
2828
};
29+
30+
export const SUBAGENT_PARALLEL_TOOL_META: Tool = {
31+
name: "SubagentParallel",
32+
displayName: "Subagent Parallel",
33+
description:
34+
"Invoke multiple subagents in parallel and wait for all to complete.",
35+
readonly: false,
36+
isBuiltIn: true,
37+
parameters: {
38+
type: "object",
39+
required: ["tasks"],
40+
properties: {
41+
tasks: {
42+
type: "array",
43+
description:
44+
"Array of tasks to execute in parallel. Each task specifies a subagent and its prompt.",
45+
},
46+
},
47+
},
48+
run: async () => "",
49+
};

extensions/cli/src/tools/allBuiltIns.ts

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,7 @@
1-
import { SUBAGENT_TOOL_META } from "../subagent/index.js";
1+
import {
2+
SUBAGENT_PARALLEL_TOOL_META,
3+
SUBAGENT_TOOL_META,
4+
} from "../subagent/index.js";
25

36
import { askQuestionTool } from "./askQuestion.js";
47
import { editTool } from "./edit.js";
@@ -31,6 +34,7 @@ export const ALL_BUILT_IN_TOOLS = [
3134
searchCodeTool,
3235
statusTool,
3336
SUBAGENT_TOOL_META,
37+
SUBAGENT_PARALLEL_TOOL_META,
3438
SKILLS_TOOL_META,
3539
uploadArtifactTool,
3640
viewDiffTool,

extensions/cli/src/tools/index.tsx

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,7 @@ import { reportFailureTool } from "./reportFailure.js";
3131
import { runTerminalCommandTool } from "./runTerminalCommand.js";
3232
import { checkIfRipgrepIsInstalled, searchCodeTool } from "./searchCode.js";
3333
import { skillsTool } from "./skills.js";
34-
import { subagentTool } from "./subagent.js";
34+
import { subagentParallelTool, subagentTool } from "./subagent.js";
3535
import { isBetaUploadArtifactToolEnabled } from "./toolsConfig.js";
3636
import {
3737
type Tool,
@@ -127,6 +127,7 @@ export async function getAllAvailableTools(
127127
}
128128

129129
tools.push(await subagentTool());
130+
tools.push(await subagentParallelTool());
130131

131132
tools.push(await skillsTool());
132133

extensions/cli/src/tools/subagent.ts

Lines changed: 145 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,11 +7,20 @@ import {
77
getSubagent,
88
getAgentNames as getSubagentNames,
99
} from "../subagent/get-agents.js";
10-
import { SUBAGENT_TOOL_META } from "../subagent/index.js";
10+
import {
11+
SUBAGENT_PARALLEL_TOOL_META,
12+
SUBAGENT_TOOL_META,
13+
} from "../subagent/index.js";
1114
import { logger } from "../util/logger.js";
1215

1316
import { Tool } from "./types.js";
1417

18+
interface SubagentTask {
19+
description: string;
20+
prompt: string;
21+
subagent_name: string;
22+
}
23+
1524
export const subagentTool = async (): Promise<Tool> => {
1625
const modelServiceState = await serviceContainer.get<ModelServiceState>(
1726
SERVICE_NAMES.MODEL,
@@ -99,3 +108,138 @@ export const subagentTool = async (): Promise<Tool> => {
99108
},
100109
};
101110
};
111+
112+
export const subagentParallelTool = async (): Promise<Tool> => {
113+
const modelServiceState = await serviceContainer.get<ModelServiceState>(
114+
SERVICE_NAMES.MODEL,
115+
);
116+
117+
const availableAgents = modelServiceState
118+
? getSubagentNames(modelServiceState).join(", ")
119+
: "";
120+
121+
return {
122+
...SUBAGENT_PARALLEL_TOOL_META,
123+
124+
description: `Invoke multiple subagents in parallel and wait for all to complete. Use this when you have multiple independent tasks that can be executed concurrently. Available agents: ${availableAgents}`,
125+
126+
parameters: {
127+
...SUBAGENT_PARALLEL_TOOL_META.parameters,
128+
properties: {
129+
tasks: {
130+
...SUBAGENT_PARALLEL_TOOL_META.parameters.properties.tasks,
131+
items: {
132+
type: "object",
133+
required: ["description", "prompt", "subagent_name"],
134+
properties: {
135+
...SUBAGENT_TOOL_META.parameters.properties,
136+
subagent_name: {
137+
type: "string",
138+
description: `The type of specialized agent to use. Available: ${availableAgents}`,
139+
},
140+
},
141+
},
142+
},
143+
},
144+
},
145+
146+
preprocess: async (args: any) => {
147+
const { tasks } = args as { tasks: SubagentTask[] };
148+
149+
if (!Array.isArray(tasks) || tasks.length === 0) {
150+
throw new Error("tasks must be a non-empty array");
151+
}
152+
153+
const previews: { type: string; content: string }[] = [];
154+
155+
for (const task of tasks) {
156+
const agent = getSubagent(modelServiceState, task.subagent_name);
157+
if (!agent) {
158+
throw new Error(
159+
`Unknown agent type: ${task.subagent_name}. Available agents: ${availableAgents}`,
160+
);
161+
}
162+
previews.push({
163+
type: "text",
164+
content: `Spawning ${agent.model.name} to: ${task.description}`,
165+
});
166+
}
167+
168+
return {
169+
args,
170+
preview: [
171+
{
172+
type: "text",
173+
content: `Spawning ${tasks.length} subagents in parallel:\n${previews.map((p) => ` - ${p.content}`).join("\n")}`,
174+
},
175+
],
176+
};
177+
},
178+
179+
run: async (args: any, context?: { toolCallId: string }) => {
180+
const { tasks } = args as { tasks: SubagentTask[] };
181+
182+
logger.debug("subagent_parallel args", { args, context });
183+
184+
const chatHistoryService = services.chatHistory;
185+
const parentSessionId = chatHistoryService.getSessionId();
186+
if (!parentSessionId) {
187+
throw new Error("No active session found");
188+
}
189+
190+
const executeTask = async (task: SubagentTask, index: number) => {
191+
const agent = getSubagent(modelServiceState, task.subagent_name);
192+
if (!agent) {
193+
return {
194+
index,
195+
description: task.description,
196+
success: false,
197+
response: `Unknown agent type: ${task.subagent_name}`,
198+
};
199+
}
200+
201+
try {
202+
const result = await subAgentService.executeSubAgent({
203+
agent,
204+
prompt: task.prompt,
205+
parentSessionId,
206+
abortController: new AbortController(),
207+
});
208+
209+
return {
210+
index,
211+
description: task.description,
212+
success: result.success,
213+
response: result.response,
214+
};
215+
} catch (error) {
216+
return {
217+
index,
218+
description: task.description,
219+
success: false,
220+
response: `Error: ${error instanceof Error ? error.message : String(error)}`,
221+
};
222+
}
223+
};
224+
225+
const results = await Promise.all(
226+
tasks.map((task, index) => executeTask(task, index)),
227+
);
228+
229+
logger.debug("subagent_parallel results", { results });
230+
231+
const outputParts = results.map((result) => {
232+
return [
233+
`<task index="${result.index}" description="${result.description}">`,
234+
result.response,
235+
`<task_metadata>`,
236+
`status: ${result.success ? "completed" : "failed"}`,
237+
`</task_metadata>`,
238+
`</task>`,
239+
].join("\n");
240+
});
241+
242+
return outputParts.join("\n\n");
243+
},
244+
};
245+
};

0 commit comments

Comments
 (0)