@@ -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" ;
1114import { logger } from "../util/logger.js" ;
1215
1316import { Tool } from "./types.js" ;
1417
18+ interface SubagentTask {
19+ description : string ;
20+ prompt : string ;
21+ subagent_name : string ;
22+ }
23+
1524export 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