Problem
During an agent run (before it finishes), the TUI transcript sometimes displays entries in the wrong order — assistant text that should appear after a tool call instead gets appended to the assistant entry that sits before the tool call, or appears to jump/mutate when the final onAssistantMessage arrives.
The issue is most visible when watching the transcript mid-run: the ordering of assistant text and tool-call entries looks off, and text can visibly jump when the streamed text is replaced by the final message content.
Root cause
In src/runtime/local-opentui-bridge.ts, the onModelToolCall callback (line 457) appends a new tool entry to localEntries but does not reset this.currentAssistantEntryId:
onModelToolCall: ({ toolName, rawArgs }) => {
this.discardEmptyAssistantEntry(); // only discards if entry is EMPTY
// ... append tool entry ...
// ❌ currentAssistantEntryId is left pointing at the pre-tool assistant entry
},
discardEmptyAssistantEntry() (line 689) only removes the assistant entry when its content is blank. If the assistant has already streamed some text, the ID stays alive — so any subsequent onModelTextDelta in the same streaming response appends text to the pre-tool assistant entry instead of creating a new post-tool entry.
Secondary issue: streamed text → final text jump
commitAssistantMessage (line 564) replaces the streamed entry content with the final message.content:
if (this.currentAssistantEntryId) {
this.updateLocalEntry(this.currentAssistantEntryId, (entry) => ({
...entry,
content: message, // replace, not append
}));
If the streamed deltas and the final message differ even slightly (whitespace, formatting), the user sees a visible text jump.
Expected behavior
- When a tool call arrives mid-stream, subsequent text deltas should create a new assistant entry positioned after the tool entry — not append to the pre-tool entry.
- The transition from streamed text to final message content should be smooth (no visible jump/mutation).
- Entry ordering during streaming should match the ordering seen after reconciliation (post-run).
Reproduction
- Run
step in TUI mode.
- Send a prompt that triggers a tool call (e.g. ask it to read a file).
- Observe the transcript during the run — the assistant text and tool entries may appear out of order, or text may jump when the final message arrives.
Proposed fix
- In
onModelToolCall: after appending the tool entry, set this.currentAssistantEntryId = null (when the current assistant entry has content) so subsequent onModelTextDelta calls create a new entry after the tool entry.
- Ensure
commitAssistantMessage handles the case where currentAssistantEntryId was already reset (it should update the most recently created assistant entry for the current turn, or create a new one).
- Consider making the streamed→final text transition smoother (e.g. only update if content actually changed).
Scope
This is a pre-existing bug in local-opentui-bridge.ts — not introduced by the reasoning-collapsible (#69) or tool-call-collapsible (#65) PRs. Those PRs only touch the rendering layer and the reconciliation path; the streaming callbacks are unchanged.
Related
Problem
During an agent run (before it finishes), the TUI transcript sometimes displays entries in the wrong order — assistant text that should appear after a tool call instead gets appended to the assistant entry that sits before the tool call, or appears to jump/mutate when the final
onAssistantMessagearrives.The issue is most visible when watching the transcript mid-run: the ordering of assistant text and tool-call entries looks off, and text can visibly jump when the streamed text is replaced by the final message content.
Root cause
In
src/runtime/local-opentui-bridge.ts, theonModelToolCallcallback (line 457) appends a newtoolentry tolocalEntriesbut does not resetthis.currentAssistantEntryId:discardEmptyAssistantEntry()(line 689) only removes the assistant entry when its content is blank. If the assistant has already streamed some text, the ID stays alive — so any subsequentonModelTextDeltain the same streaming response appends text to the pre-tool assistant entry instead of creating a new post-tool entry.Secondary issue: streamed text → final text jump
commitAssistantMessage(line 564) replaces the streamed entry content with the finalmessage.content:If the streamed deltas and the final message differ even slightly (whitespace, formatting), the user sees a visible text jump.
Expected behavior
Reproduction
stepin TUI mode.Proposed fix
onModelToolCall: after appending the tool entry, setthis.currentAssistantEntryId = null(when the current assistant entry has content) so subsequentonModelTextDeltacalls create a new entry after the tool entry.commitAssistantMessagehandles the case wherecurrentAssistantEntryIdwas already reset (it should update the most recently created assistant entry for the current turn, or create a new one).Scope
This is a pre-existing bug in
local-opentui-bridge.ts— not introduced by the reasoning-collapsible (#69) or tool-call-collapsible (#65) PRs. Those PRs only touch the rendering layer and the reconciliation path; the streaming callbacks are unchanged.Related