@@ -384,3 +384,96 @@ describe('isLiveAssistantMessageId', () => {
384384 expect ( isLiveAssistantMessageId ( '' ) ) . toBe ( false )
385385 } )
386386} )
387+
388+ describe ( 'tool ownership is call-frame authoritative' , ( ) => {
389+ const toolEvent = (
390+ seq : number ,
391+ phase : 'call' | 'result' ,
392+ toolCallId : string ,
393+ scope ?: Record < string , unknown >
394+ ) : StreamBatchEvent =>
395+ toBatchEvent ( seq , {
396+ v : 1 ,
397+ seq,
398+ ts : '2026-04-15T12:00:01.000Z' ,
399+ type : MothershipStreamV1EventType . tool ,
400+ stream : { streamId : 'stream-1' } ,
401+ payload :
402+ phase === 'call'
403+ ? { phase : 'call' , toolCallId, toolName : 'read' , arguments : { path : 'a.md' } }
404+ : { phase : 'result' , toolCallId, toolName : 'read' , success : true , output : 'ok' } ,
405+ ...( scope ? { scope } : { } ) ,
406+ // double-cast-allowed: synthetic test envelope; the reducer reads only the fields set here
407+ } as unknown as StreamBatchEvent [ 'event' ] )
408+
409+ const superagentScope = {
410+ lane : 'subagent' ,
411+ agentId : 'superagent' ,
412+ parentToolCallId : 'dispatch-1' ,
413+ spanId : 'S1' ,
414+ }
415+
416+ const ownership = ( result : ReturnType < typeof buildEffectiveChatTranscript > ) => {
417+ const blocks = ( result [ 1 ] . contentBlocks ?? [ ] ) as Array < Record < string , unknown > >
418+ const tool = blocks . find ( ( b ) => b . type === MothershipStreamV1EventType . tool )
419+ const tc = tool ?. toolCall as Record < string , unknown > | undefined
420+ return { calledBy : tc ?. calledBy , parentToolCallId : tool ?. parentToolCallId , spanId : tool ?. spanId }
421+ }
422+
423+ it ( 'an unscoped main call clears provisional subagent attribution' , ( ) => {
424+ // The observed dev bug: a mis-scoped replayed result seeded the main
425+ // read under Superagent, and nothing could ever move it back.
426+ const result = buildEffectiveChatTranscript ( {
427+ messages : [ buildUserMessage ( 'stream-1' , 'Hello' ) ] ,
428+ activeStreamId : 'stream-1' ,
429+ streamSnapshot : {
430+ events : [
431+ toolEvent ( 1 , 'result' , 'fc_1' , superagentScope ) ,
432+ toolEvent ( 2 , 'call' , 'fc_1' ) ,
433+ ] ,
434+ previewSessions : [ ] ,
435+ status : 'active' ,
436+ } ,
437+ } )
438+ const own = ownership ( result )
439+ expect ( own . calledBy ) . toBeUndefined ( )
440+ expect ( own . parentToolCallId ) . toBeUndefined ( )
441+ expect ( own . spanId ) . toBeUndefined ( )
442+ } )
443+
444+ it ( 'a later mis-scoped result cannot re-parent a settled main tool' , ( ) => {
445+ const result = buildEffectiveChatTranscript ( {
446+ messages : [ buildUserMessage ( 'stream-1' , 'Hello' ) ] ,
447+ activeStreamId : 'stream-1' ,
448+ streamSnapshot : {
449+ events : [
450+ toolEvent ( 1 , 'call' , 'fc_1' ) ,
451+ toolEvent ( 2 , 'result' , 'fc_1' , superagentScope ) ,
452+ ] ,
453+ previewSessions : [ ] ,
454+ status : 'active' ,
455+ } ,
456+ } )
457+ const own = ownership ( result )
458+ expect ( own . calledBy ) . toBeUndefined ( )
459+ expect ( own . parentToolCallId ) . toBeUndefined ( )
460+ } )
461+
462+ it ( 'a genuinely scoped subagent call keeps its ownership' , ( ) => {
463+ const result = buildEffectiveChatTranscript ( {
464+ messages : [ buildUserMessage ( 'stream-1' , 'Hello' ) ] ,
465+ activeStreamId : 'stream-1' ,
466+ streamSnapshot : {
467+ events : [
468+ toolEvent ( 1 , 'call' , 'fc_2' , superagentScope ) ,
469+ toolEvent ( 2 , 'result' , 'fc_2' ) ,
470+ ] ,
471+ previewSessions : [ ] ,
472+ status : 'active' ,
473+ } ,
474+ } )
475+ const own = ownership ( result )
476+ expect ( own . calledBy ) . toBe ( 'superagent' )
477+ expect ( own . parentToolCallId ) . toBe ( 'dispatch-1' )
478+ } )
479+ } )
0 commit comments