@@ -219,8 +219,9 @@ export class StreamingMessageAggregator {
219219 }
220220
221221 // Then, reconstruct derived state from the most recent assistant message
222- // TODOs: only if there's an active stream (stream-scoped, only during reconnection)
223- // agentStatus: always (persists across sessions)
222+ // Use "streaming" context if there's an active stream (reconnection), otherwise "historical"
223+ const context = hasActiveStream ? "streaming" : "historical" ;
224+
224225 const sortedMessages = [ ...messages ] . sort (
225226 ( a , b ) => ( b . metadata ?. historySequence ?? 0 ) - ( a . metadata ?. historySequence ?? 0 )
226227 ) ;
@@ -229,16 +230,11 @@ export class StreamingMessageAggregator {
229230 const lastAssistantMessage = sortedMessages . find ( ( msg ) => msg . role === "assistant" ) ;
230231
231232 if ( lastAssistantMessage ) {
232- // Only process tool results from the most recent assistant message
233+ // Process all tool results from the most recent assistant message
234+ // processToolResult will decide what to do based on tool type and context
233235 for ( const part of lastAssistantMessage . parts ) {
234236 if ( isDynamicToolPart ( part ) && part . state === "output-available" ) {
235- // Reconstruct based on tool type and stream state
236- const shouldReconstructTodos = part . toolName === "todo_write" && hasActiveStream ;
237- const shouldReconstructStatus = part . toolName === "status_set" ;
238-
239- if ( shouldReconstructTodos || shouldReconstructStatus ) {
240- this . processToolResult ( part . toolName , part . input , part . output ) ;
241- }
237+ this . processToolResult ( part . toolName , part . input , part . output , context ) ;
242238 }
243239 }
244240 }
@@ -539,10 +535,21 @@ export class StreamingMessageAggregator {
539535 *
540536 * This is the single source of truth for updating state from tool results,
541537 * ensuring consistency whether processing live events or historical messages.
538+ *
539+ * @param toolName - Name of the tool that was called
540+ * @param input - Tool input arguments
541+ * @param output - Tool output result
542+ * @param context - Whether this is from live streaming or historical reload
542543 */
543- private processToolResult ( toolName : string , input : unknown , output : unknown ) : void {
544+ private processToolResult (
545+ toolName : string ,
546+ input : unknown ,
547+ output : unknown ,
548+ context : "streaming" | "historical"
549+ ) : void {
544550 // Update TODO state if this was a successful todo_write
545- if ( toolName === "todo_write" && hasSuccessResult ( output ) ) {
551+ // TODOs are stream-scoped: only update during live streaming, not on historical reload
552+ if ( toolName === "todo_write" && hasSuccessResult ( output ) && context === "streaming" ) {
546553 const args = input as { todos : TodoItem [ ] } ;
547554 // Only update if todos actually changed (prevents flickering from reference changes)
548555 if ( ! this . todosEqual ( this . currentTodos , args . todos ) ) {
@@ -551,6 +558,7 @@ export class StreamingMessageAggregator {
551558 }
552559
553560 // Update agent status if this was a successful status_set
561+ // agentStatus persists: update both during streaming and on historical reload
554562 // Use output instead of input to get the truncated message
555563 if ( toolName === "status_set" && hasSuccessResult ( output ) ) {
556564 const result = output as Extract < StatusSetToolResult , { success : true } > ;
@@ -584,7 +592,8 @@ export class StreamingMessageAggregator {
584592 ( toolPart as DynamicToolPartAvailable ) . output = data . result ;
585593
586594 // Process tool result to update derived state (todos, agentStatus, etc.)
587- this . processToolResult ( data . toolName , toolPart . input , data . result ) ;
595+ // This is from a live stream, so use "streaming" context
596+ this . processToolResult ( data . toolName , toolPart . input , data . result , "streaming" ) ;
588597 }
589598 this . invalidateCache ( ) ;
590599 }
0 commit comments