@@ -119,6 +119,7 @@ import type {
119119 MothershipResourceType ,
120120 QueuedMessage ,
121121} from '../types'
122+ import { ToolCallStatus } from '../types'
122123
123124const FILE_SUBAGENT_ID = 'file'
124125
@@ -610,6 +611,28 @@ function getToolUI(ui?: MothershipStreamV1ToolUI): StreamToolUI | undefined {
610611 }
611612}
612613
614+ function resolveLiveToolStatus (
615+ payload : Partial < {
616+ status : string
617+ success : boolean
618+ } >
619+ ) : ToolCallStatus {
620+ switch ( payload . status ) {
621+ case MothershipStreamV1ToolOutcome . success :
622+ return ToolCallStatus . success
623+ case MothershipStreamV1ToolOutcome . error :
624+ return ToolCallStatus . error
625+ case MothershipStreamV1ToolOutcome . cancelled :
626+ return ToolCallStatus . cancelled
627+ case MothershipStreamV1ToolOutcome . skipped :
628+ return ToolCallStatus . skipped
629+ case MothershipStreamV1ToolOutcome . rejected :
630+ return ToolCallStatus . rejected
631+ default :
632+ return payload . success === true ? ToolCallStatus . success : ToolCallStatus . error
633+ }
634+ }
635+
613636/** Adds a workflow to the React Query cache with a top-insertion sort order if it doesn't already exist. */
614637function ensureWorkflowInRegistry ( resourceId : string , title : string , workspaceId : string ) : boolean {
615638 const workflows = getWorkflows ( workspaceId )
@@ -1421,6 +1444,7 @@ export function useChat(
14211444 let activeSubagent : string | undefined
14221445 let activeSubagentParentToolCallId : string | undefined
14231446 let activeCompactionId : string | undefined
1447+ const subagentByParentToolCallId = new Map < string , string > ( )
14241448
14251449 if ( preserveState ) {
14261450 for ( let i = blocks . length - 1 ; i >= 0 ; i -- ) {
@@ -1443,20 +1467,32 @@ export function useChat(
14431467 streamingBlocksRef . current = [ ]
14441468 }
14451469
1446- const ensureTextBlock = ( ) : ContentBlock => {
1470+ const ensureTextBlock = ( subagentName ?: string ) : ContentBlock => {
14471471 const last = blocks [ blocks . length - 1 ]
1448- if ( last ?. type === 'text' && last . subagent === activeSubagent ) return last
1472+ if ( last ?. type === 'text' && last . subagent === subagentName ) return last
14491473 const b : ContentBlock = { type : 'text' , content : '' }
1474+ if ( subagentName ) b . subagent = subagentName
14501475 blocks . push ( b )
14511476 return b
14521477 }
14531478
1454- const appendInlineErrorTag = ( tag : string ) => {
1479+ const resolveScopedSubagent = (
1480+ agentId : string | undefined ,
1481+ parentToolCallId : string | undefined
1482+ ) : string | undefined => {
1483+ if ( agentId ) return agentId
1484+ if ( parentToolCallId ) {
1485+ const scoped = subagentByParentToolCallId . get ( parentToolCallId )
1486+ if ( scoped ) return scoped
1487+ }
1488+ return activeSubagent
1489+ }
1490+
1491+ const appendInlineErrorTag = ( tag : string , subagentName ?: string ) => {
14551492 if ( runningText . includes ( tag ) ) return
1456- const tb = ensureTextBlock ( )
1493+ const tb = ensureTextBlock ( subagentName )
14571494 const prefix = runningText . length > 0 && ! runningText . endsWith ( '\n' ) ? '\n' : ''
14581495 tb . content = `${ tb . content ?? '' } ${ prefix } ${ tag } `
1459- if ( activeSubagent ) tb . subagent = activeSubagent
14601496 runningText += `${ prefix } ${ tag } `
14611497 streamingContentRef . current = runningText
14621498 flush ( )
@@ -1570,6 +1606,13 @@ export function useChat(
15701606 }
15711607
15721608 logger . debug ( 'SSE event received' , parsed )
1609+ const scopedParentToolCallId =
1610+ typeof parsed . scope ?. parentToolCallId === 'string'
1611+ ? parsed . scope . parentToolCallId
1612+ : undefined
1613+ const scopedAgentId =
1614+ typeof parsed . scope ?. agentId === 'string' ? parsed . scope . agentId : undefined
1615+ const scopedSubagent = resolveScopedSubagent ( scopedAgentId , scopedParentToolCallId )
15731616 switch ( parsed . type ) {
15741617 case MothershipStreamV1EventType . session : {
15751618 const payload = parsed . payload
@@ -1623,16 +1666,15 @@ export function useChat(
16231666 case MothershipStreamV1EventType . text : {
16241667 const chunk = parsed . payload . text
16251668 if ( chunk ) {
1626- const contentSource : 'main' | 'subagent' = activeSubagent ? 'subagent' : 'main'
1669+ const contentSource : 'main' | 'subagent' = scopedSubagent ? 'subagent' : 'main'
16271670 const needsBoundaryNewline =
16281671 lastContentSource !== null &&
16291672 lastContentSource !== contentSource &&
16301673 runningText . length > 0 &&
16311674 ! runningText . endsWith ( '\n' )
1632- const tb = ensureTextBlock ( )
1675+ const tb = ensureTextBlock ( scopedSubagent )
16331676 const normalizedChunk = needsBoundaryNewline ? `\n${ chunk } ` : chunk
16341677 tb . content = ( tb . content ?? '' ) + normalizedChunk
1635- if ( activeSubagent ) tb . subagent = activeSubagent
16361678 runningText += normalizedChunk
16371679 lastContentSource = contentSource
16381680 streamingContentRef . current = runningText
@@ -1823,22 +1865,24 @@ export function useChat(
18231865 }
18241866 const tc = blocks [ idx ] . toolCall !
18251867 const outputObj = asPayloadRecord ( payload . output )
1826- const success =
1827- payload . success ?? payload . status === MothershipStreamV1ToolOutcome . success
18281868 const isCancelled =
18291869 outputObj ?. reason === 'user_cancelled' ||
18301870 outputObj ?. cancelledByUser === true ||
18311871 payload . status === MothershipStreamV1ToolOutcome . cancelled
1872+ const status = isCancelled
1873+ ? ToolCallStatus . cancelled
1874+ : resolveLiveToolStatus ( payload )
1875+ const isSuccess = status === ToolCallStatus . success
18321876
1833- if ( isCancelled ) {
1834- tc . status = ' cancelled'
1877+ if ( status === ToolCallStatus . cancelled ) {
1878+ tc . status = ToolCallStatus . cancelled
18351879 tc . displayTitle = 'Stopped by user'
18361880 } else {
1837- tc . status = success ? 'success' : 'error'
1881+ tc . status = status
18381882 }
18391883 tc . streamingArgs = undefined
18401884 tc . result = {
1841- success : ! ! success ,
1885+ success : isSuccess ,
18421886 output : payload . output ,
18431887 error : typeof payload . error === 'string' ? payload . error : undefined ,
18441888 }
@@ -1925,7 +1969,7 @@ export function useChat(
19251969 } )
19261970 setActiveResourceId ( fileResource . id )
19271971 invalidateResourceQueries ( queryClient , workspaceId , 'file' , fileResource . id )
1928- } else if ( ! activeSubagent || activeSubagent !== FILE_SUBAGENT_ID ) {
1972+ } else if ( tc . calledBy !== FILE_SUBAGENT_ID ) {
19291973 setResources ( ( rs ) => rs . filter ( ( r ) => r . id !== 'streaming-file' ) )
19301974 }
19311975 }
@@ -1971,7 +2015,7 @@ export function useChat(
19712015 status : 'executing' ,
19722016 displayTitle,
19732017 params : args ,
1974- calledBy : activeSubagent ,
2018+ calledBy : scopedSubagent ,
19752019 } ,
19762020 } )
19772021 if ( name === ReadTool . id || isResourceToolName ( name ) ) {
@@ -2087,23 +2131,18 @@ export function useChat(
20872131 }
20882132 const spanData = asPayloadRecord ( payload . data )
20892133 const parentToolCallId =
2090- typeof parsed . scope ?. parentToolCallId === 'string'
2091- ? parsed . scope . parentToolCallId
2092- : typeof spanData ?. tool_call_id === 'string'
2093- ? spanData . tool_call_id
2094- : undefined
2134+ scopedParentToolCallId ??
2135+ ( typeof spanData ?. tool_call_id === 'string' ? spanData . tool_call_id : undefined )
20952136 const isPendingPause = spanData ?. pending === true
2096- const name =
2097- typeof payload . agent === 'string'
2098- ? payload . agent
2099- : typeof parsed . scope ?. agentId === 'string'
2100- ? parsed . scope . agentId
2101- : undefined
2137+ const name = typeof payload . agent === 'string' ? payload . agent : scopedAgentId
21022138 if ( payload . event === MothershipStreamV1SpanLifecycleEvent . start && name ) {
21032139 const isSameActiveSubagent =
21042140 activeSubagent === name &&
21052141 activeSubagentParentToolCallId &&
21062142 parentToolCallId === activeSubagentParentToolCallId
2143+ if ( parentToolCallId ) {
2144+ subagentByParentToolCallId . set ( parentToolCallId , name )
2145+ }
21072146 activeSubagent = name
21082147 activeSubagentParentToolCallId = parentToolCallId
21092148 if ( ! isSameActiveSubagent ) {
@@ -2127,6 +2166,9 @@ export function useChat(
21272166 if ( isPendingPause ) {
21282167 break
21292168 }
2169+ if ( parentToolCallId ) {
2170+ subagentByParentToolCallId . delete ( parentToolCallId )
2171+ }
21302172 if ( previewSessionRef . current && ! activePreviewSessionIdRef . current ) {
21312173 const lastFileResource = resourcesRef . current . find (
21322174 ( r ) => r . type === 'file' && r . id !== 'streaming-file'
@@ -2136,8 +2178,14 @@ export function useChat(
21362178 setActiveResourceId ( lastFileResource . id )
21372179 }
21382180 }
2139- activeSubagent = undefined
2140- activeSubagentParentToolCallId = undefined
2181+ if (
2182+ ! parentToolCallId ||
2183+ parentToolCallId === activeSubagentParentToolCallId ||
2184+ name === activeSubagent
2185+ ) {
2186+ activeSubagent = undefined
2187+ activeSubagentParentToolCallId = undefined
2188+ }
21412189 blocks . push ( { type : 'subagent_end' } )
21422190 flush ( )
21432191 }
@@ -2146,7 +2194,7 @@ export function useChat(
21462194 case MothershipStreamV1EventType . error : {
21472195 sawStreamError = true
21482196 setError ( parsed . payload . message || parsed . payload . error || 'An error occurred' )
2149- appendInlineErrorTag ( buildInlineErrorTag ( parsed . payload ) )
2197+ appendInlineErrorTag ( buildInlineErrorTag ( parsed . payload ) , scopedSubagent )
21502198 break
21512199 }
21522200 case MothershipStreamV1EventType . complete : {
0 commit comments