Skip to content

tui: streaming transcript entries appear out of order when tool calls interleave with text #70

Description

@li-xiu-qi

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

  1. Run step in TUI mode.
  2. Send a prompt that triggers a tool call (e.g. ask it to read a file).
  3. 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

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions