@@ -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 )
@@ -650,7 +673,10 @@ function extractResourceFromReadResult(
650673) : MothershipResource | null {
651674 if ( ! path ) return null
652675
653- const segments = path . split ( '/' )
676+ const segments = path
677+ . split ( '/' )
678+ . map ( ( segment ) => segment . trim ( ) )
679+ . filter ( Boolean )
654680 const resourceType = VFS_DIR_TO_RESOURCE [ segments [ 0 ] ]
655681 if ( ! resourceType || ! segments [ 1 ] ) return null
656682
@@ -670,8 +696,22 @@ function extractResourceFromReadResult(
670696 }
671697 }
672698
699+ const fallbackTitle =
700+ resourceType === 'workflow'
701+ ? resolveLeafWorkflowPathSegment ( segments )
702+ : segments [ 1 ] || segments [ segments . length - 1 ]
703+
673704 if ( ! id ) return null
674- return { type : resourceType , id, title : name || segments [ 1 ] }
705+ return { type : resourceType , id, title : name || fallbackTitle || id }
706+ }
707+
708+ function resolveLeafWorkflowPathSegment ( segments : string [ ] ) : string | undefined {
709+ const lastSegment = segments [ segments . length - 1 ]
710+ if ( ! lastSegment ) return undefined
711+ if ( / \. [ ^ / . ] + $ / . test ( lastSegment ) && segments . length > 1 ) {
712+ return segments [ segments . length - 2 ]
713+ }
714+ return lastSegment
675715}
676716
677717export interface UseChatOptions {
@@ -1396,6 +1436,7 @@ export function useChat(
13961436 let activeSubagent : string | undefined
13971437 let activeSubagentParentToolCallId : string | undefined
13981438 let activeCompactionId : string | undefined
1439+ const subagentByParentToolCallId = new Map < string , string > ( )
13991440
14001441 if ( preserveState ) {
14011442 for ( let i = blocks . length - 1 ; i >= 0 ; i -- ) {
@@ -1418,20 +1459,32 @@ export function useChat(
14181459 streamingBlocksRef . current = [ ]
14191460 }
14201461
1421- const ensureTextBlock = ( ) : ContentBlock => {
1462+ const ensureTextBlock = ( subagentName ?: string ) : ContentBlock => {
14221463 const last = blocks [ blocks . length - 1 ]
1423- if ( last ?. type === 'text' && last . subagent === activeSubagent ) return last
1464+ if ( last ?. type === 'text' && last . subagent === subagentName ) return last
14241465 const b : ContentBlock = { type : 'text' , content : '' }
1466+ if ( subagentName ) b . subagent = subagentName
14251467 blocks . push ( b )
14261468 return b
14271469 }
14281470
1429- const appendInlineErrorTag = ( tag : string ) => {
1471+ const resolveScopedSubagent = (
1472+ agentId : string | undefined ,
1473+ parentToolCallId : string | undefined
1474+ ) : string | undefined => {
1475+ if ( agentId ) return agentId
1476+ if ( parentToolCallId ) {
1477+ const scoped = subagentByParentToolCallId . get ( parentToolCallId )
1478+ if ( scoped ) return scoped
1479+ }
1480+ return activeSubagent
1481+ }
1482+
1483+ const appendInlineErrorTag = ( tag : string , subagentName ?: string ) => {
14301484 if ( runningText . includes ( tag ) ) return
1431- const tb = ensureTextBlock ( )
1485+ const tb = ensureTextBlock ( subagentName )
14321486 const prefix = runningText . length > 0 && ! runningText . endsWith ( '\n' ) ? '\n' : ''
14331487 tb . content = `${ tb . content ?? '' } ${ prefix } ${ tag } `
1434- if ( activeSubagent ) tb . subagent = activeSubagent
14351488 runningText += `${ prefix } ${ tag } `
14361489 streamingContentRef . current = runningText
14371490 flush ( )
@@ -1545,6 +1598,13 @@ export function useChat(
15451598 }
15461599
15471600 logger . debug ( 'SSE event received' , parsed )
1601+ const scopedParentToolCallId =
1602+ typeof parsed . scope ?. parentToolCallId === 'string'
1603+ ? parsed . scope . parentToolCallId
1604+ : undefined
1605+ const scopedAgentId =
1606+ typeof parsed . scope ?. agentId === 'string' ? parsed . scope . agentId : undefined
1607+ const scopedSubagent = resolveScopedSubagent ( scopedAgentId , scopedParentToolCallId )
15481608 switch ( parsed . type ) {
15491609 case MothershipStreamV1EventType . session : {
15501610 const payload = parsed . payload
@@ -1600,16 +1660,15 @@ export function useChat(
16001660 case MothershipStreamV1EventType . text : {
16011661 const chunk = parsed . payload . text
16021662 if ( chunk ) {
1603- const contentSource : 'main' | 'subagent' = activeSubagent ? 'subagent' : 'main'
1663+ const contentSource : 'main' | 'subagent' = scopedSubagent ? 'subagent' : 'main'
16041664 const needsBoundaryNewline =
16051665 lastContentSource !== null &&
16061666 lastContentSource !== contentSource &&
16071667 runningText . length > 0 &&
16081668 ! runningText . endsWith ( '\n' )
1609- const tb = ensureTextBlock ( )
1669+ const tb = ensureTextBlock ( scopedSubagent )
16101670 const normalizedChunk = needsBoundaryNewline ? `\n${ chunk } ` : chunk
16111671 tb . content = ( tb . content ?? '' ) + normalizedChunk
1612- if ( activeSubagent ) tb . subagent = activeSubagent
16131672 runningText += normalizedChunk
16141673 lastContentSource = contentSource
16151674 streamingContentRef . current = runningText
@@ -1800,22 +1859,24 @@ export function useChat(
18001859 }
18011860 const tc = blocks [ idx ] . toolCall !
18021861 const outputObj = asPayloadRecord ( payload . output )
1803- const success =
1804- payload . success ?? payload . status === MothershipStreamV1ToolOutcome . success
18051862 const isCancelled =
18061863 outputObj ?. reason === 'user_cancelled' ||
18071864 outputObj ?. cancelledByUser === true ||
18081865 payload . status === MothershipStreamV1ToolOutcome . cancelled
1866+ const status = isCancelled
1867+ ? ToolCallStatus . cancelled
1868+ : resolveLiveToolStatus ( payload )
1869+ const isSuccess = status === ToolCallStatus . success
18091870
1810- if ( isCancelled ) {
1811- tc . status = ' cancelled'
1871+ if ( status === ToolCallStatus . cancelled ) {
1872+ tc . status = ToolCallStatus . cancelled
18121873 tc . displayTitle = 'Stopped by user'
18131874 } else {
1814- tc . status = success ? 'success' : 'error'
1875+ tc . status = status
18151876 }
18161877 tc . streamingArgs = undefined
18171878 tc . result = {
1818- success : ! ! success ,
1879+ success : isSuccess ,
18191880 output : payload . output ,
18201881 error : typeof payload . error === 'string' ? payload . error : undefined ,
18211882 }
@@ -1902,7 +1963,7 @@ export function useChat(
19021963 } )
19031964 setActiveResourceId ( fileResource . id )
19041965 invalidateResourceQueries ( queryClient , workspaceId , 'file' , fileResource . id )
1905- } else if ( ! activeSubagent || activeSubagent !== FILE_SUBAGENT_ID ) {
1966+ } else if ( tc . calledBy !== FILE_SUBAGENT_ID ) {
19061967 setResources ( ( rs ) => rs . filter ( ( r ) => r . id !== 'streaming-file' ) )
19071968 }
19081969 }
@@ -1948,7 +2009,7 @@ export function useChat(
19482009 status : 'executing' ,
19492010 displayTitle,
19502011 params : args ,
1951- calledBy : activeSubagent ,
2012+ calledBy : scopedSubagent ,
19522013 } ,
19532014 } )
19542015 if ( name === ReadTool . id || isResourceToolName ( name ) ) {
@@ -2064,23 +2125,18 @@ export function useChat(
20642125 }
20652126 const spanData = asPayloadRecord ( payload . data )
20662127 const parentToolCallId =
2067- typeof parsed . scope ?. parentToolCallId === 'string'
2068- ? parsed . scope . parentToolCallId
2069- : typeof spanData ?. tool_call_id === 'string'
2070- ? spanData . tool_call_id
2071- : undefined
2128+ scopedParentToolCallId ??
2129+ ( typeof spanData ?. tool_call_id === 'string' ? spanData . tool_call_id : undefined )
20722130 const isPendingPause = spanData ?. pending === true
2073- const name =
2074- typeof payload . agent === 'string'
2075- ? payload . agent
2076- : typeof parsed . scope ?. agentId === 'string'
2077- ? parsed . scope . agentId
2078- : undefined
2131+ const name = typeof payload . agent === 'string' ? payload . agent : scopedAgentId
20792132 if ( payload . event === MothershipStreamV1SpanLifecycleEvent . start && name ) {
20802133 const isSameActiveSubagent =
20812134 activeSubagent === name &&
20822135 activeSubagentParentToolCallId &&
20832136 parentToolCallId === activeSubagentParentToolCallId
2137+ if ( parentToolCallId ) {
2138+ subagentByParentToolCallId . set ( parentToolCallId , name )
2139+ }
20842140 activeSubagent = name
20852141 activeSubagentParentToolCallId = parentToolCallId
20862142 if ( ! isSameActiveSubagent ) {
@@ -2104,6 +2160,9 @@ export function useChat(
21042160 if ( isPendingPause ) {
21052161 break
21062162 }
2163+ if ( parentToolCallId ) {
2164+ subagentByParentToolCallId . delete ( parentToolCallId )
2165+ }
21072166 if ( previewSessionRef . current && ! activePreviewSessionIdRef . current ) {
21082167 const lastFileResource = resourcesRef . current . find (
21092168 ( r ) => r . type === 'file' && r . id !== 'streaming-file'
@@ -2113,8 +2172,14 @@ export function useChat(
21132172 setActiveResourceId ( lastFileResource . id )
21142173 }
21152174 }
2116- activeSubagent = undefined
2117- activeSubagentParentToolCallId = undefined
2175+ if (
2176+ ! parentToolCallId ||
2177+ parentToolCallId === activeSubagentParentToolCallId ||
2178+ name === activeSubagent
2179+ ) {
2180+ activeSubagent = undefined
2181+ activeSubagentParentToolCallId = undefined
2182+ }
21182183 blocks . push ( { type : 'subagent_end' } )
21192184 flush ( )
21202185 }
@@ -2123,7 +2188,7 @@ export function useChat(
21232188 case MothershipStreamV1EventType . error : {
21242189 sawStreamError = true
21252190 setError ( parsed . payload . message || parsed . payload . error || 'An error occurred' )
2126- appendInlineErrorTag ( buildInlineErrorTag ( parsed . payload ) )
2191+ appendInlineErrorTag ( buildInlineErrorTag ( parsed . payload ) , scopedSubagent )
21272192 break
21282193 }
21292194 case MothershipStreamV1EventType . complete : {
0 commit comments