@@ -215,9 +215,14 @@ export async function* runSmartLoopStream(
215215 /* Phase 1: Initialization */
216216 /* ================================================================ */
217217
218+ const runStartTime = Date . now ( ) ;
219+ let finalResultYielded = false ;
220+
218221 // 1a. Create engine state from config (or checkpoint for resume)
219222 const state = createEngineState ( config , goal , checkpoint , resumeMessage ) ;
220223
224+ try {
225+
221226 // 1b. Build tool catalog (inline or deferred)
222227 const catalog = createToolCatalog ( state . tools , config . toolCatalog ) ;
223228 const allTools = catalog . discoverTool
@@ -399,7 +404,7 @@ export async function* runSmartLoopStream(
399404 status : "paused" ,
400405 checkpoint : buildCheckpoint ( state , "paused" ) ,
401406 } ;
402- yield { type : "run_end" as const , result : pausedResult , durationMs : Date . now ( ) - state . runStartTime , timestamp : Date . now ( ) } ;
407+ finalResultYielded = true ; yield { type : "run_end" as const , result : pausedResult , durationMs : Date . now ( ) - state . runStartTime , timestamp : Date . now ( ) } ;
403408 return pausedResult ;
404409 }
405410 if ( decision . action === "cancel" ) {
@@ -411,7 +416,7 @@ export async function* runSmartLoopStream(
411416 status : "canceled" ,
412417 checkpoint : buildCheckpoint ( state , "canceled" ) ,
413418 } ;
414- yield { type : "run_end" as const , result : canceledResult , durationMs : Date . now ( ) - state . runStartTime , timestamp : Date . now ( ) } ;
419+ finalResultYielded = true ; yield { type : "run_end" as const , result : canceledResult , durationMs : Date . now ( ) - state . runStartTime , timestamp : Date . now ( ) } ;
415420 return canceledResult ;
416421 }
417422 } catch {
@@ -485,7 +490,7 @@ export async function* runSmartLoopStream(
485490 error : `Primary LLM failed: ${ primaryMsg } . Fallback LLM also failed: ${ fallbackMsg } ` ,
486491 checkpoint : buildCheckpoint ( state ) ,
487492 } ;
488- yield { type : "run_end" as const , result : fatalResult , durationMs : Date . now ( ) - state . runStartTime , timestamp : Date . now ( ) } ;
493+ finalResultYielded = true ; yield { type : "run_end" as const , result : fatalResult , durationMs : Date . now ( ) - state . runStartTime , timestamp : Date . now ( ) } ;
489494 return fatalResult ;
490495 }
491496 // If we reach here, fallback succeeded — skip the context_limit path
@@ -562,7 +567,7 @@ export async function* runSmartLoopStream(
562567 error : "Context overflow unrecoverable: autocompact and reactive compact both exhausted" ,
563568 checkpoint : buildCheckpoint ( state ) ,
564569 } ;
565- yield { type : "run_end" as const , result : contextFatalResult , durationMs : Date . now ( ) - state . runStartTime , timestamp : Date . now ( ) } ;
570+ finalResultYielded = true ; yield { type : "run_end" as const , result : contextFatalResult , durationMs : Date . now ( ) - state . runStartTime , timestamp : Date . now ( ) } ;
566571 return contextFatalResult ;
567572 } else {
568573 // Non-context error with no fallback configured — use decideContinuation
@@ -583,7 +588,7 @@ export async function* runSmartLoopStream(
583588 error : errorMsg || contAction . error ,
584589 checkpoint : buildCheckpoint ( state ) ,
585590 } ;
586- yield { type : "run_end" as const , result : errFatalResult , durationMs : Date . now ( ) - state . runStartTime , timestamp : Date . now ( ) } ;
591+ finalResultYielded = true ; yield { type : "run_end" as const , result : errFatalResult , durationMs : Date . now ( ) - state . runStartTime , timestamp : Date . now ( ) } ;
587592 return errFatalResult ;
588593 }
589594 // Should not reach here for "error" finishReason (decideContinuation returns fatal)
@@ -645,7 +650,7 @@ export async function* runSmartLoopStream(
645650 status : "completed" ,
646651 checkpoint : buildCheckpoint ( state , "completed" ) ,
647652 } ;
648- yield { type : "run_end" as const , result : budgetResult , durationMs : Date . now ( ) - state . runStartTime , timestamp : Date . now ( ) } ;
653+ finalResultYielded = true ; yield { type : "run_end" as const , result : budgetResult , durationMs : Date . now ( ) - state . runStartTime , timestamp : Date . now ( ) } ;
649654 return budgetResult ;
650655 }
651656 }
@@ -671,15 +676,26 @@ export async function* runSmartLoopStream(
671676 pendingApproval : blockedApproval ,
672677 checkpoint : cp ,
673678 } ;
674- yield { type : "run_end" as const , result : approvalResult , durationMs : Date . now ( ) - state . runStartTime , timestamp : Date . now ( ) } ;
679+ finalResultYielded = true ; yield { type : "run_end" as const , result : approvalResult , durationMs : Date . now ( ) - state . runStartTime , timestamp : Date . now ( ) } ;
675680 return approvalResult ;
676681 }
677682
678683 // Tool calls were made
679684 if ( toolRecords . length > 0 ) {
685+ // Yield tool_call_start for each tool that was requested
686+ for ( const req of outcome . toolRequests ) {
687+ yield { type : "tool_call_start" as const , tool : req . name , args : req . args , iteration : state . iterations , timestamp : Date . now ( ) } ;
688+ }
689+
680690 // Yield tool_call_end events for each tool execution
681691 for ( const record of toolRecords ) {
682- yield { type : "tool_call_end" as const , tool : record . tool , args : record . args , result : record . result , status : ( record . status ?? "success" ) as "success" | "error" , durationMs : 0 , iteration : state . iterations , timestamp : Date . now ( ) } ;
692+ // Detect skill activation
693+ if ( record . tool === "activate_skill" && record . status !== "error" ) {
694+ const skillResult = record . result as Record < string , unknown > ;
695+ yield { type : "skill_activated" as const , skill : ( skillResult ?. skill as string ) ?? "unknown" , iteration : state . iterations , timestamp : Date . now ( ) } ;
696+ }
697+
698+ yield { type : "tool_call_end" as const , tool : record . tool , args : record . args , result : record . result , status : ( record . status ?? "success" ) as "success" | "error" , iteration : state . iterations , timestamp : Date . now ( ) } ;
683699 }
684700
685701 // Append assistant message
@@ -938,7 +954,7 @@ export async function* runSmartLoopStream(
938954 status : "completed" ,
939955 checkpoint : buildCheckpoint ( state , "completed" ) ,
940956 } ;
941- yield { type : "run_end" as const , result : completedResult , durationMs : Date . now ( ) - state . runStartTime , timestamp : Date . now ( ) } ;
957+ finalResultYielded = true ; yield { type : "run_end" as const , result : completedResult , durationMs : Date . now ( ) - state . runStartTime , timestamp : Date . now ( ) } ;
942958 return completedResult ;
943959 }
944960
@@ -956,7 +972,7 @@ export async function* runSmartLoopStream(
956972 status : "completed" ,
957973 checkpoint : buildCheckpoint ( state , "completed" ) ,
958974 } ;
959- yield { type : "run_end" as const , result : forceResult , durationMs : Date . now ( ) - state . runStartTime , timestamp : Date . now ( ) } ;
975+ finalResultYielded = true ; yield { type : "run_end" as const , result : forceResult , durationMs : Date . now ( ) - state . runStartTime , timestamp : Date . now ( ) } ;
960976 return forceResult ;
961977 }
962978
@@ -970,6 +986,12 @@ export async function* runSmartLoopStream(
970986 status : "max_iterations" ,
971987 checkpoint : buildCheckpoint ( state , "max_iterations" ) ,
972988 } ;
973- yield { type : "run_end" as const , result : maxIterResult , durationMs : Date . now ( ) - state . runStartTime , timestamp : Date . now ( ) } ;
989+ finalResultYielded = true ; yield { type : "run_end" as const , result : maxIterResult , durationMs : Date . now ( ) - state . runStartTime , timestamp : Date . now ( ) } ;
974990 return maxIterResult ;
991+
992+ } finally {
993+ if ( ! finalResultYielded ) {
994+ yield { type : "run_end" as const , result : { result : null , iterations : 0 , toolCalls : [ ] , taskCalls : [ ] , status : "fatal" as const , error : "Stream terminated early" } , durationMs : Date . now ( ) - runStartTime , timestamp : Date . now ( ) } ;
995+ }
996+ }
975997}
0 commit comments