@@ -156,32 +156,86 @@ function toToolData(tc: NonNullable<ContentBlock['toolCall']>): ToolCallData {
156156 */
157157function parseBlocks ( blocks : ContentBlock [ ] ) : MessageSegment [ ] {
158158 const segments : MessageSegment [ ] = [ ]
159- let group : AgentGroupSegment | null = null
160- const pushGroup = ( nextGroup : AgentGroupSegment , isOpen = false ) => {
161- segments . push ( { ...nextGroup , isOpen } )
159+ const groupsByKey = new Map < string , AgentGroupSegment > ( )
160+ let activeGroupKey : string | null = null
161+
162+ const groupKey = ( name : string , parentToolCallId : string | undefined ) =>
163+ parentToolCallId ? `${ name } :${ parentToolCallId } ` : `${ name } :legacy`
164+
165+ const resolveGroupKey = ( name : string , parentToolCallId : string | undefined ) => {
166+ if ( parentToolCallId ) return groupKey ( name , parentToolCallId )
167+ if ( activeGroupKey && groupsByKey . get ( activeGroupKey ) ?. agentName === name ) {
168+ return activeGroupKey
169+ }
170+ for ( const [ key , g ] of groupsByKey ) {
171+ if ( g . agentName === name && g . isOpen ) return key
172+ }
173+ return groupKey ( name , undefined )
174+ }
175+
176+ const ensureGroup = (
177+ name : string ,
178+ parentToolCallId : string | undefined
179+ ) : { group : AgentGroupSegment ; created : boolean } => {
180+ const key = resolveGroupKey ( name , parentToolCallId )
181+ const existing = groupsByKey . get ( key )
182+ if ( existing ) return { group : existing , created : false }
183+ const group : AgentGroupSegment = {
184+ type : 'agent_group' ,
185+ id : `agent-${ key } -${ segments . length } ` ,
186+ agentName : name ,
187+ agentLabel : resolveAgentLabel ( name ) ,
188+ items : [ ] ,
189+ isDelegating : false ,
190+ isOpen : false ,
191+ }
192+ segments . push ( group )
193+ groupsByKey . set ( key , group )
194+ return { group, created : true }
195+ }
196+
197+ const findGroupForSubagentChunk = (
198+ parentToolCallId : string | undefined
199+ ) : AgentGroupSegment | undefined => {
200+ if ( parentToolCallId ) {
201+ for ( const [ key , g ] of groupsByKey ) {
202+ if ( key . endsWith ( `:${ parentToolCallId } ` ) ) return g
203+ }
204+ return undefined
205+ }
206+ if ( activeGroupKey ) return groupsByKey . get ( activeGroupKey )
207+ return undefined
208+ }
209+
210+ const flushLanes = ( ) => {
211+ for ( const g of groupsByKey . values ( ) ) {
212+ g . isOpen = false
213+ g . isDelegating = false
214+ }
215+ groupsByKey . clear ( )
216+ activeGroupKey = null
162217 }
163218
164219 for ( let i = 0 ; i < blocks . length ; i ++ ) {
165220 const block = blocks [ i ]
166221
167222 if ( block . type === 'subagent_text' || block . type === 'subagent_thinking' ) {
168- if ( ! block . content || ! group ) continue
169- group . isDelegating = false
170- const lastItem = group . items [ group . items . length - 1 ]
223+ if ( ! block . content ) continue
224+ const g = findGroupForSubagentChunk ( block . parentToolCallId )
225+ if ( ! g ) continue
226+ g . isDelegating = false
227+ const lastItem = g . items [ g . items . length - 1 ]
171228 if ( lastItem ?. type === 'text' ) {
172229 lastItem . content += block . content
173230 } else {
174- group . items . push ( { type : 'text' , content : block . content } )
231+ g . items . push ( { type : 'text' , content : block . content } )
175232 }
176233 continue
177234 }
178235
179236 if ( block . type === 'thinking' ) {
180237 if ( ! block . content ?. trim ( ) ) continue
181- if ( group ) {
182- pushGroup ( group )
183- group = null
184- }
238+ flushLanes ( )
185239 const last = segments [ segments . length - 1 ]
186240 if ( last ?. type === 'thinking' && last . endedAt === undefined ) {
187241 last . content += block . content
@@ -201,21 +255,19 @@ function parseBlocks(blocks: ContentBlock[]): MessageSegment[] {
201255 if ( block . type === 'text' ) {
202256 if ( ! block . content ) continue
203257 if ( block . subagent ) {
204- if ( group && group . agentName === block . subagent ) {
205- group . isDelegating = false
206- const lastItem = group . items [ group . items . length - 1 ]
258+ const g = groupsByKey . get ( resolveGroupKey ( block . subagent , block . parentToolCallId ) )
259+ if ( g ) {
260+ g . isDelegating = false
261+ const lastItem = g . items [ g . items . length - 1 ]
207262 if ( lastItem ?. type === 'text' ) {
208263 lastItem . content += block . content
209264 } else {
210- group . items . push ( { type : 'text' , content : block . content } )
265+ g . items . push ( { type : 'text' , content : block . content } )
211266 }
212267 continue
213268 }
214269 }
215- if ( group ) {
216- pushGroup ( group )
217- group = null
218- }
270+ flushLanes ( )
219271 const last = segments [ segments . length - 1 ]
220272 if ( last ?. type === 'text' ) {
221273 last . content += block . content
@@ -228,34 +280,23 @@ function parseBlocks(blocks: ContentBlock[]): MessageSegment[] {
228280 if ( block . type === 'subagent' ) {
229281 if ( ! block . content ) continue
230282 const key = block . content
231- if ( group && group . agentName === key ) continue
232-
233- const dispatchToolName = SUBAGENT_DISPATCH_TOOLS [ key ]
234283 let inheritedDelegation = false
235- if ( group && dispatchToolName ) {
236- const last : AgentGroupItem | undefined = group . items [ group . items . length - 1 ]
237- if ( last ?. type === 'tool' && last . data . toolName === dispatchToolName ) {
238- inheritedDelegation = ! isToolDone ( last . data . status ) && Boolean ( last . data . streamingArgs )
239- group . items . pop ( )
240- }
241- if ( group . items . length > 0 ) {
242- pushGroup ( group )
284+ const dispatchToolName = SUBAGENT_DISPATCH_TOOLS [ key ]
285+ if ( dispatchToolName ) {
286+ const mship = groupsByKey . get ( groupKey ( 'mothership' , undefined ) )
287+ if ( mship ) {
288+ const last = mship . items [ mship . items . length - 1 ]
289+ if ( last ?. type === 'tool' && last . data . toolName === dispatchToolName ) {
290+ inheritedDelegation = ! isToolDone ( last . data . status ) && Boolean ( last . data . streamingArgs )
291+ mship . items . pop ( )
292+ }
243293 }
244- group = null
245- } else if ( group ) {
246- pushGroup ( group )
247- group = null
248- }
249-
250- group = {
251- type : 'agent_group' ,
252- id : `agent-${ key } -${ i } ` ,
253- agentName : key ,
254- agentLabel : resolveAgentLabel ( key ) ,
255- items : [ ] ,
256- isDelegating : inheritedDelegation ,
257- isOpen : false ,
258294 }
295+ groupsByKey . delete ( groupKey ( 'mothership' , undefined ) )
296+ const { group : g } = ensureGroup ( key , block . parentToolCallId )
297+ if ( inheritedDelegation ) g . isDelegating = true
298+ g . isOpen = true
299+ activeGroupKey = resolveGroupKey ( key , block . parentToolCallId )
259300 continue
260301 }
261302
@@ -267,95 +308,75 @@ function parseBlocks(blocks: ContentBlock[]): MessageSegment[] {
267308 const isDispatch = SUBAGENT_KEYS . has ( tc . name ) && ! tc . calledBy
268309
269310 if ( isDispatch ) {
270- if ( ! group || group . agentName !== tc . name ) {
271- if ( group ) {
272- pushGroup ( group )
273- group = null
274- }
275- group = {
276- type : 'agent_group' ,
277- id : `agent-${ tc . name } -${ i } ` ,
278- agentName : tc . name ,
279- agentLabel : resolveAgentLabel ( tc . name ) ,
280- items : [ ] ,
281- isDelegating : false ,
282- isOpen : false ,
283- }
284- }
285- group . isDelegating = isDelegatingTool ( tc )
311+ groupsByKey . delete ( groupKey ( 'mothership' , undefined ) )
312+ const { group : g } = ensureGroup ( tc . name , tc . id )
313+ g . isDelegating = isDelegatingTool ( tc )
314+ g . isOpen = g . isDelegating
286315 continue
287316 }
288317
289318 const tool = toToolData ( tc )
290319
291- if ( tc . calledBy && group && group . agentName === tc . calledBy ) {
292- group . isDelegating = false
293- group . items . push ( { type : 'tool' , data : tool } )
294- } else if ( tc . calledBy ) {
295- if ( group ) {
296- pushGroup ( group )
297- group = null
298- }
299- group = {
300- type : 'agent_group' ,
301- id : `agent-${ tc . calledBy } -${ i } ` ,
302- agentName : tc . calledBy ,
303- agentLabel : resolveAgentLabel ( tc . calledBy ) ,
304- items : [ { type : 'tool' , data : tool } ] ,
305- isDelegating : false ,
306- isOpen : false ,
307- }
320+ if ( tc . calledBy ) {
321+ const { group : g , created } = ensureGroup ( tc . calledBy , block . parentToolCallId )
322+ g . isDelegating = false
323+ if ( created && block . parentToolCallId ) g . isOpen = true
324+ g . items . push ( { type : 'tool' , data : tool } )
325+ activeGroupKey = resolveGroupKey ( tc . calledBy , block . parentToolCallId )
308326 } else {
309- if ( group && group . agentName === 'mothership' ) {
310- group . items . push ( { type : 'tool' , data : tool } )
311- } else {
312- if ( group ) {
313- pushGroup ( group )
314- group = null
315- }
316- group = {
317- type : 'agent_group' ,
318- id : `agent-mothership-${ i } ` ,
319- agentName : 'mothership' ,
320- agentLabel : 'Mothership' ,
321- items : [ { type : 'tool' , data : tool } ] ,
322- isDelegating : false ,
323- isOpen : false ,
324- }
325- }
327+ const { group : g } = ensureGroup ( 'mothership' , undefined )
328+ g . items . push ( { type : 'tool' , data : tool } )
326329 }
327330 continue
328331 }
329332
330333 if ( block . type === 'options' ) {
331334 if ( ! block . options ?. length ) continue
332- if ( group ) {
333- pushGroup ( group )
334- group = null
335- }
335+ flushLanes ( )
336336 segments . push ( { type : 'options' , items : block . options } )
337337 continue
338338 }
339339
340340 if ( block . type === 'subagent_end' ) {
341- if ( group ) {
342- pushGroup ( group )
343- group = null
341+ if ( block . parentToolCallId ) {
342+ for ( const [ key , g ] of groupsByKey ) {
343+ if ( key . endsWith ( `:${ block . parentToolCallId } ` ) ) {
344+ g . isOpen = false
345+ g . isDelegating = false
346+ }
347+ }
348+ if ( activeGroupKey ?. endsWith ( `:${ block . parentToolCallId } ` ) ) {
349+ activeGroupKey = null
350+ }
351+ } else {
352+ for ( const [ key , g ] of groupsByKey ) {
353+ if ( key . endsWith ( ':legacy' ) && g . agentName !== 'mothership' ) {
354+ g . isOpen = false
355+ g . isDelegating = false
356+ }
357+ }
358+ if ( activeGroupKey ?. endsWith ( ':legacy' ) ) {
359+ activeGroupKey = null
360+ }
344361 }
345362 continue
346363 }
347364
348365 if ( block . type === 'stopped' ) {
349- if ( group ) {
350- pushGroup ( group )
351- group = null
352- }
366+ flushLanes ( )
353367 segments . push ( { type : 'stopped' } )
354368 }
355369 }
356370
357- if ( group ) pushGroup ( group , true )
358- return segments
371+ const visibleSegments = segments . filter (
372+ ( segment ) =>
373+ segment . type !== 'agent_group' ||
374+ segment . items . length > 0 ||
375+ segment . isDelegating ||
376+ segment . isOpen
377+ )
378+
379+ return visibleSegments
359380}
360381
361382/**
@@ -428,12 +449,6 @@ export function MessageContent({
428449 isStreaming &&
429450 ! hasTrailingContent &&
430451 ( lastSegment . type === 'thinking' || hasSubagentEnded || allLastGroupToolsDone )
431- const lastOpenSubagentGroupId = [ ...segments ]
432- . reverse ( )
433- . find (
434- ( segment ) : segment is AgentGroupSegment =>
435- segment . type === 'agent_group' && segment . agentName !== 'mothership' && segment . isOpen
436- ) ?. id
437452
438453 return (
439454 < div className = 'space-y-[10px]' >
@@ -488,8 +503,8 @@ export function MessageContent({
488503 items = { segment . items }
489504 isDelegating = { segment . isDelegating }
490505 isStreaming = { isStreaming }
491- autoCollapse = { allToolsDone && hasFollowingText }
492- defaultExpanded = { segment . id === lastOpenSubagentGroupId }
506+ autoCollapse = { ! segment . isOpen && allToolsDone && hasFollowingText }
507+ defaultExpanded = { segment . isOpen }
493508 />
494509 </ div >
495510 )
0 commit comments