@@ -378,12 +378,18 @@ async function _runQuery(requestId, prompt, projectPath, model, signal, locale)
378378 let accumulatedText = "" ;
379379 let lastStreamTime = 0 ;
380380
381- // Tool input tracking
381+ // Tool input tracking (parent-level)
382382 let activeToolName = null ;
383383 let activeToolIndex = null ;
384384 let activeToolInputJson = "" ;
385385 let lastToolStreamTime = 0 ;
386386
387+ // Sub-agent tool tracking
388+ let subagentToolName = null ;
389+ let subagentToolIndex = null ;
390+ let subagentToolInputJson = "" ;
391+ let lastSubagentToolStreamTime = 0 ;
392+
387393 // Trace counters (logged at tool/query completion, not per-delta)
388394 let toolDeltaCount = 0 ;
389395 let toolStreamSendCount = 0 ;
@@ -406,94 +412,184 @@ async function _runQuery(requestId, prompt, projectPath, model, signal, locale)
406412 // Handle streaming events
407413 if ( message . type === "stream_event" ) {
408414 const event = message . event ;
415+ const isSubagent = ! ! message . parent_tool_use_id ;
416+
417+ if ( isSubagent ) {
418+ // --- Sub-agent events ---
419+
420+ // Sub-agent tool use start
421+ if ( event . type === "content_block_start" &&
422+ event . content_block ?. type === "tool_use" ) {
423+ subagentToolName = event . content_block . name ;
424+ subagentToolIndex = event . index ;
425+ subagentToolInputJson = "" ;
426+ toolCounter ++ ;
427+ lastSubagentToolStreamTime = 0 ;
428+ _log ( "Subagent tool start:" , subagentToolName , "#" + toolCounter ) ;
429+ nodeConnector . triggerPeer ( "aiProgress" , {
430+ requestId : requestId ,
431+ toolName : subagentToolName ,
432+ toolId : toolCounter ,
433+ phase : "tool_use"
434+ } ) ;
435+ }
409436
410- // Tool use start — send initial indicator
411- if ( event . type === "content_block_start" &&
412- event . content_block ?. type === "tool_use" ) {
413- activeToolName = event . content_block . name ;
414- activeToolIndex = event . index ;
415- activeToolInputJson = "" ;
416- toolCounter ++ ;
417- toolDeltaCount = 0 ;
418- toolStreamSendCount = 0 ;
419- lastToolStreamTime = 0 ;
420- _log ( "Tool start:" , activeToolName , "#" + toolCounter ) ;
421- nodeConnector . triggerPeer ( "aiProgress" , {
422- requestId : requestId ,
423- toolName : activeToolName ,
424- toolId : toolCounter ,
425- phase : "tool_use"
426- } ) ;
427- }
437+ // Sub-agent tool input streaming
438+ if ( event . type === "content_block_delta" &&
439+ event . delta ?. type === "input_json_delta" &&
440+ event . index === subagentToolIndex ) {
441+ subagentToolInputJson += event . delta . partial_json ;
442+ const now = Date . now ( ) ;
443+ if ( subagentToolInputJson &&
444+ now - lastSubagentToolStreamTime >= TEXT_STREAM_THROTTLE_MS ) {
445+ lastSubagentToolStreamTime = now ;
446+ nodeConnector . triggerPeer ( "aiToolStream" , {
447+ requestId : requestId ,
448+ toolId : toolCounter ,
449+ toolName : subagentToolName ,
450+ partialJson : subagentToolInputJson
451+ } ) ;
452+ }
453+ }
428454
429- // Accumulate tool input JSON and stream preview
430- if ( event . type === "content_block_delta" &&
431- event . delta ?. type === "input_json_delta" &&
432- event . index === activeToolIndex ) {
433- activeToolInputJson += event . delta . partial_json ;
434- toolDeltaCount ++ ;
435- const now = Date . now ( ) ;
436- if ( activeToolInputJson &&
437- now - lastToolStreamTime >= TEXT_STREAM_THROTTLE_MS ) {
438- lastToolStreamTime = now ;
439- toolStreamSendCount ++ ;
440- nodeConnector . triggerPeer ( "aiToolStream" , {
455+ // Sub-agent tool block complete
456+ if ( event . type === "content_block_stop" &&
457+ event . index === subagentToolIndex &&
458+ subagentToolName ) {
459+ if ( subagentToolInputJson ) {
460+ nodeConnector . triggerPeer ( "aiToolStream" , {
461+ requestId : requestId ,
462+ toolId : toolCounter ,
463+ toolName : subagentToolName ,
464+ partialJson : subagentToolInputJson
465+ } ) ;
466+ }
467+ let toolInput = { } ;
468+ try {
469+ toolInput = JSON . parse ( subagentToolInputJson ) ;
470+ } catch ( e ) {
471+ // ignore parse errors
472+ }
473+ _log ( "Subagent tool done:" , subagentToolName , "#" + toolCounter ,
474+ "json=" + subagentToolInputJson . length + "ch" ) ;
475+ nodeConnector . triggerPeer ( "aiToolInfo" , {
441476 requestId : requestId ,
477+ toolName : subagentToolName ,
442478 toolId : toolCounter ,
443- toolName : activeToolName ,
444- partialJson : activeToolInputJson
479+ toolInput : toolInput
445480 } ) ;
481+ subagentToolName = null ;
482+ subagentToolIndex = null ;
483+ subagentToolInputJson = "" ;
446484 }
447- }
448485
449- // Tool block complete — flush final stream preview and send details
450- if ( event . type === "content_block_stop" &&
451- event . index === activeToolIndex &&
452- activeToolName ) {
453- // Final flush of tool stream (bypasses throttle)
454- if ( activeToolInputJson ) {
455- toolStreamSendCount ++ ;
456- nodeConnector . triggerPeer ( "aiToolStream" , {
486+ // Sub-agent text deltas — stream as regular text
487+ if ( event . type === "content_block_delta" &&
488+ event . delta ?. type === "text_delta" ) {
489+ accumulatedText += event . delta . text ;
490+ textDeltaCount ++ ;
491+ const now = Date . now ( ) ;
492+ if ( now - lastStreamTime >= TEXT_STREAM_THROTTLE_MS ) {
493+ lastStreamTime = now ;
494+ textStreamSendCount ++ ;
495+ nodeConnector . triggerPeer ( "aiTextStream" , {
496+ requestId : requestId ,
497+ text : accumulatedText
498+ } ) ;
499+ accumulatedText = "" ;
500+ }
501+ }
502+ } else {
503+ // --- Parent-level events (unchanged) ---
504+
505+ // Tool use start — send initial indicator
506+ if ( event . type === "content_block_start" &&
507+ event . content_block ?. type === "tool_use" ) {
508+ activeToolName = event . content_block . name ;
509+ activeToolIndex = event . index ;
510+ activeToolInputJson = "" ;
511+ toolCounter ++ ;
512+ toolDeltaCount = 0 ;
513+ toolStreamSendCount = 0 ;
514+ lastToolStreamTime = 0 ;
515+ _log ( "Tool start:" , activeToolName , "#" + toolCounter ) ;
516+ nodeConnector . triggerPeer ( "aiProgress" , {
457517 requestId : requestId ,
458- toolId : toolCounter ,
459518 toolName : activeToolName ,
460- partialJson : activeToolInputJson
519+ toolId : toolCounter ,
520+ phase : "tool_use"
461521 } ) ;
462522 }
463- let toolInput = { } ;
464- try {
465- toolInput = JSON . parse ( activeToolInputJson ) ;
466- } catch ( e ) {
467- // ignore parse errors
523+
524+ // Accumulate tool input JSON and stream preview
525+ if ( event . type === "content_block_delta" &&
526+ event . delta ?. type === "input_json_delta" &&
527+ event . index === activeToolIndex ) {
528+ activeToolInputJson += event . delta . partial_json ;
529+ toolDeltaCount ++ ;
530+ const now = Date . now ( ) ;
531+ if ( activeToolInputJson &&
532+ now - lastToolStreamTime >= TEXT_STREAM_THROTTLE_MS ) {
533+ lastToolStreamTime = now ;
534+ toolStreamSendCount ++ ;
535+ nodeConnector . triggerPeer ( "aiToolStream" , {
536+ requestId : requestId ,
537+ toolId : toolCounter ,
538+ toolName : activeToolName ,
539+ partialJson : activeToolInputJson
540+ } ) ;
541+ }
468542 }
469- _log ( "Tool done:" , activeToolName , "#" + toolCounter ,
470- "deltas=" + toolDeltaCount , "sent=" + toolStreamSendCount ,
471- "json=" + activeToolInputJson . length + "ch" ) ;
472- nodeConnector . triggerPeer ( "aiToolInfo" , {
473- requestId : requestId ,
474- toolName : activeToolName ,
475- toolId : toolCounter ,
476- toolInput : toolInput
477- } ) ;
478- activeToolName = null ;
479- activeToolIndex = null ;
480- activeToolInputJson = "" ;
481- }
482543
483- // Stream text deltas (throttled)
484- if ( event . type === "content_block_delta" &&
485- event . delta ?. type === "text_delta" ) {
486- accumulatedText += event . delta . text ;
487- textDeltaCount ++ ;
488- const now = Date . now ( ) ;
489- if ( now - lastStreamTime >= TEXT_STREAM_THROTTLE_MS ) {
490- lastStreamTime = now ;
491- textStreamSendCount ++ ;
492- nodeConnector . triggerPeer ( "aiTextStream" , {
544+ // Tool block complete — flush final stream preview and send details
545+ if ( event . type === "content_block_stop" &&
546+ event . index === activeToolIndex &&
547+ activeToolName ) {
548+ // Final flush of tool stream (bypasses throttle)
549+ if ( activeToolInputJson ) {
550+ toolStreamSendCount ++ ;
551+ nodeConnector . triggerPeer ( "aiToolStream" , {
552+ requestId : requestId ,
553+ toolId : toolCounter ,
554+ toolName : activeToolName ,
555+ partialJson : activeToolInputJson
556+ } ) ;
557+ }
558+ let toolInput = { } ;
559+ try {
560+ toolInput = JSON . parse ( activeToolInputJson ) ;
561+ } catch ( e ) {
562+ // ignore parse errors
563+ }
564+ _log ( "Tool done:" , activeToolName , "#" + toolCounter ,
565+ "deltas=" + toolDeltaCount , "sent=" + toolStreamSendCount ,
566+ "json=" + activeToolInputJson . length + "ch" ) ;
567+ nodeConnector . triggerPeer ( "aiToolInfo" , {
493568 requestId : requestId ,
494- text : accumulatedText
569+ toolName : activeToolName ,
570+ toolId : toolCounter ,
571+ toolInput : toolInput
495572 } ) ;
496- accumulatedText = "" ;
573+ activeToolName = null ;
574+ activeToolIndex = null ;
575+ activeToolInputJson = "" ;
576+ }
577+
578+ // Stream text deltas (throttled)
579+ if ( event . type === "content_block_delta" &&
580+ event . delta ?. type === "text_delta" ) {
581+ accumulatedText += event . delta . text ;
582+ textDeltaCount ++ ;
583+ const now = Date . now ( ) ;
584+ if ( now - lastStreamTime >= TEXT_STREAM_THROTTLE_MS ) {
585+ lastStreamTime = now ;
586+ textStreamSendCount ++ ;
587+ nodeConnector . triggerPeer ( "aiTextStream" , {
588+ requestId : requestId ,
589+ text : accumulatedText
590+ } ) ;
591+ accumulatedText = "" ;
592+ }
497593 }
498594 }
499595 }
0 commit comments