@@ -5,7 +5,10 @@ import {unifiedHooksExecutor} from './unifiedHooksExecutor.js';
55import { interpretHookResult } from './hookResultInterpreter.js' ;
66import { runningSubAgentTracker } from './runningSubAgentTracker.js' ;
77import { resolveAgent , filterAllowedTools } from './subAgentResolver.js' ;
8- import { injectBuiltinTools , buildInitialMessages } from './subAgentBuiltinTools.js' ;
8+ import {
9+ injectBuiltinTools ,
10+ buildInitialMessages ,
11+ } from './subAgentBuiltinTools.js' ;
912import {
1013 createApiStream ,
1114 processStreamEvents ,
@@ -57,6 +60,7 @@ export async function executeSubAgent(
5760 instanceId ?: string ,
5861 spawnDepth : number = 0 ,
5962) : Promise < SubAgentResult > {
63+ let ctx : SubAgentExecutionContext | undefined ;
6064 try {
6165 // 1. Resolve agent
6266 const { agent, error : resolveError } = await resolveAgent ( agentId ) ;
@@ -85,7 +89,7 @@ export async function executeSubAgent(
8589 ) ;
8690
8791 // 4. Build execution context
88- const ctx : SubAgentExecutionContext = {
92+ ctx = {
8993 agent,
9094 instanceId,
9195 messages,
@@ -139,8 +143,11 @@ export async function executeSubAgent(
139143
140144 // Process stream events
141145 ctx . latestTotalTokens = 0 ;
142- const { toolCalls, hasError, errorMessage} =
143- await processStreamEvents ( ctx , stream , config ) ;
146+ const { toolCalls, hasError, errorMessage} = await processStreamEvents (
147+ ctx ,
148+ stream ,
149+ config ,
150+ ) ;
144151
145152 if ( hasError ) {
146153 return {
@@ -184,19 +191,14 @@ export async function executeSubAgent(
184191 remaining ,
185192 executeSubAgent ,
186193 ) . remainingToolCalls ;
187- remaining = (
188- await interceptAskUser ( ctx , remaining )
189- ) . remainingToolCalls ;
194+ remaining = ( await interceptAskUser ( ctx , remaining ) ) . remainingToolCalls ;
190195 if ( remaining . length === 0 ) continue ;
191196
192197 // Approve + execute MCP tools
193198 const approval = await checkAndApproveTools ( ctx , remaining ) ;
194199 if ( approval . shouldContinue ) continue ;
195200
196- const execResult = await executeMcpTools (
197- ctx ,
198- approval . approvedToolCalls ,
199- ) ;
201+ const execResult = await executeMcpTools ( ctx , approval . approvedToolCalls ) ;
200202 if ( execResult . aborted && execResult . abortResult ) {
201203 return execResult . abortResult ;
202204 }
@@ -221,6 +223,17 @@ export async function executeSubAgent(
221223 result : '' ,
222224 error : error instanceof Error ? error . message : 'Unknown error' ,
223225 } ;
226+ } finally {
227+ // Always emit a final 'done' so the UI handler can clear stream entries.
228+ // handleDone is idempotent (clearStreamState only removes existing entries),
229+ // so emitting an extra 'done' on already-cleaned-up paths is safe.
230+ if ( ctx ) {
231+ try {
232+ emitSubAgentMessage ( ctx , { type : 'done' } ) ;
233+ } catch {
234+ /* noop */
235+ }
236+ }
224237 }
225238}
226239
@@ -244,8 +257,9 @@ function injectPendingMessages(ctx: SubAgentExecutionContext): void {
244257 } ) ;
245258 }
246259
247- const interAgentMessages =
248- runningSubAgentTracker . dequeueInterAgentMessages ( ctx . instanceId ) ;
260+ const interAgentMessages = runningSubAgentTracker . dequeueInterAgentMessages (
261+ ctx . instanceId ,
262+ ) ;
249263 for ( const iaMsg of interAgentMessages ) {
250264 ctx . messages . push ( {
251265 role : 'user' ,
@@ -302,10 +316,7 @@ async function handleSpawnedChildren(
302316 }
303317
304318 if ( runningChildren . length > 0 ) {
305- await runningSubAgentTracker . waitForSpawnedAgents (
306- 300_000 ,
307- ctx . abortSignal ,
308- ) ;
319+ await runningSubAgentTracker . waitForSpawnedAgents ( 300_000 , ctx . abortSignal ) ;
309320 }
310321
311322 const spawnedResults = runningSubAgentTracker . drainSpawnedResults ( ) ;
@@ -354,7 +365,10 @@ async function handleCompletionHooks(
354365 ) ;
355366 const interpreted = interpretHookResult ( 'onSubAgentComplete' , hookResult ) ;
356367
357- if ( interpreted . injectedMessages && interpreted . injectedMessages . length > 0 ) {
368+ if (
369+ interpreted . injectedMessages &&
370+ interpreted . injectedMessages . length > 0
371+ ) {
358372 for ( const injected of interpreted . injectedMessages ) {
359373 ctx . messages . push ( { role : injected . role , content : injected . content } ) ;
360374 }
0 commit comments