fix(tui): restore clean agent thread replay and streaming#15312
fix(tui): restore clean agent thread replay and streaming#15312fcoury wants to merge 9 commits intoopenai:mainfrom
Conversation
Prefer completed `AgentMessageItem` payloads during thread-snapshot replay so `/agent` views rebuild from the final assistant text instead of stale legacy delta state. Backport the snapshot-based streaming pipeline so live agent output replaces prior rendered content instead of appending invalidated line slices. This keeps agent thread views stable for long markdown-heavy messages.
Limit completed agent-message precedence to `ReplayKind::ThreadSnapshot` so resume replays continue using streamed deltas without duplicating the final assistant message. Preserve pending snapshot enqueue timestamps across replacements so catch-up mode still reflects the oldest unseen rewrite, and tighten the terminal-title regression to exercise the real project-title path.
Request a redraw when commit ticks replace the visible streamed cell so snapshot-based streaming updates paint immediately. Add a compatibility fallback for `ReplayKind::ThreadSnapshot` that uses `TurnComplete.last_agent_message` when older snapshots lack a completed `AgentMessageItem`, while still preferring the completed item when it exists.
Flush non-plan active cells before later plan deltas reclaim the active slot so tool and status cells are inserted into history instead of being overwritten by a mutable plan snapshot. Defer a second controller snapshot when a commit tick can only show one active cell. This keeps the pending update queued for the next tick instead of draining and discarding it.
Track the last replayed assistant item text during thread-snapshot replay and skip the `TurnComplete.last_agent_message` fallback when it would insert the same content again. This keeps compatibility for older snapshots that only preserve `TurnComplete`, while avoiding duplicate commentary cells when both a replayed item and echoed fallback text are present.
Flush non-stream active cells before a commit tick installs a new stream snapshot into the active slot. This preserves transient tool and status rows that would otherwise be overwritten by queued stream output. Add a regression for the queued-plan then web-search path so commit ticks keep the active search row in history before plan output reclaims the active slot.
Flush a non-plan active cell before a finalized streamed plan takes over the active slot. This keeps transient tool and status rows from being overwritten when plan completion arrives. Add a regression for the web-search plus plan-completion handoff so the search row lands in history before the finalized plan is emitted.
There was a problem hiding this comment.
Pull request overview
This PR fixes corruption in TUI agent-thread rendering by switching replay + streaming to prefer completed snapshots (instead of incremental/legacy deltas), ensuring markdown-heavy output doesn’t lose earlier text when later chunks rewrite wrapping/inline constructs.
Changes:
- Thread-snapshot replay now ignores
AgentMessageDeltaand rebuilds assistant message cells from completedAgentMessageItem(orTurnComplete.last_agent_messagefallback when needed). - Streaming markdown rendering now emits replaceable full committed snapshots; commit-tick plumbing is updated to surface a single
active_cellsnapshot instead of draining FIFO line queues. - Adds regressions covering snapshot replay precedence and streamed markdown rewrites (inline code closure + rewrap cases), plus commit-tick deferral behavior.
Reviewed changes
Copilot reviewed 7 out of 7 changed files in this pull request and generated 2 comments.
Show a summary per file
| File | Description |
|---|---|
codex-rs/tui/src/streaming/mod.rs |
Removes legacy StreamState queueing and updates module documentation/exports to reflect snapshot-based controllers. |
codex-rs/tui/src/streaming/controller.rs |
Reworks stream/plan controllers to stage and emit full rendered snapshots; adds controller-level regression tests. |
codex-rs/tui/src/streaming/commit_tick.rs |
Changes commit-tick output to a single active_cell snapshot with deferral between stream vs plan; adds unit tests for deferral/idle reporting. |
codex-rs/tui/src/markdown_stream.rs |
Collector now returns the latest full committed render snapshot; updates tests and adds new rewrite regressions. |
codex-rs/tui/src/chatwidget.rs |
Implements thread-snapshot replay rules (ignore deltas, prefer completed items), and updates active-cell/commit-tick integration. |
codex-rs/tui/src/chatwidget/tests.rs |
Adds replay/duplication coverage and new active-cell redraw/flush sequencing tests; updates call sites for signature changes. |
codex-rs/tui/src/app.rs |
Adds an integration-style test ensuring thread-snapshot replay prefers completed agent message items over deltas. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
|
@codex review |
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 95500fdd6b
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
Restore `ThreadSnapshot` replay for the trailing in-progress turn so switching to an active agent thread still shows buffered assistant deltas until a completed assistant payload arrives. Port the snapshot-based streaming collector and controller behavior to `tui_app_server` so both transcript paths share the same mutable active-cell semantics and avoid stale incremental markdown slices.
Handle live `AgentMessageContentDelta` events as the source of truth for assistant streaming so the transcript flushes the previous stream when a new assistant message item begins. This keeps live agent navigation aligned with replayed thread snapshots and prevents the initial corruption that only corrected itself after switching away and back. The app-server chat widget mirrors the same item-aware behavior.
Summary
This fixes two independent corruption paths in agent thread views:
AgentMessageItempayload instead of rebuilding
/agentviews from stale legacy deltasincrementally appending invalidated line slices
Together, these changes stop long markdown-heavy agent outputs from
losing prefixes, path segments, or punctuation while viewing active or
replayed subagent threads.
What changed
AgentMessageDeltaduringReplayKind::ThreadSnapshotreplay and rebuild the final agent message cell from the completed
item payload
MarkdownStreamCollectorreturn the latest full committed rendersnapshot instead of only newly committed lines
message/plan output lives in
active_celluntil finalizationfor streamed markdown rewrites that previously corrupted earlier rows
Validation
cargo test -p codex-tuijust fix -p codex-tuiNote: per repo workflow, tests were run before the final
just fixpass and were not rerun afterward.