Skip to content

fix(tui): restore remote resume and fork history#14930

Merged
etraut-openai merged 12 commits intoopenai:mainfrom
fcoury:fcoury/fix/remote-resume-fork-history
Mar 17, 2026
Merged

fix(tui): restore remote resume and fork history#14930
etraut-openai merged 12 commits intoopenai:mainfrom
fcoury:fcoury/fix/remote-resume-fork-history

Conversation

@fcoury
Copy link
Copy Markdown
Contributor

@fcoury fcoury commented Mar 17, 2026

Problem

When the TUI connects to a remote app-server (via WebSocket), resume and fork operations lost all conversation history. AppServerStartedThread carried only the SessionConfigured event, not the full Thread snapshot. After resume or fork, the chat transcript was empty — prior turns were silently discarded.

A secondary issue: primary_session_configured was not cleared on reset, causing stale session state after reconnection.

Approach: TUI-side only, zero app-server changes

The app-server already returns the full Thread object (with populated turns: Vec<Turn>) in its ThreadStartResponse, ThreadResumeResponse, and ThreadForkResponse. The data was always there — the TUI was simply throwing it away. The old AppServerStartedThread struct only kept the SessionConfiguredEvent, discarding the rich turn history that the server had already provided.

This PR fixes the problem entirely within tui_app_server (3 files changed, 0 changes to app-server, app-server-protocol, or any other crate). Rather than modifying the server to send history in a different format or adding a new endpoint, the fix preserves the existing Thread snapshot and replays it through the TUI's standard event pipeline — making restored sessions indistinguishable from live ones.

Solution

Add a thread snapshot replay path. When the server hands back a Thread object (on start, resume, or fork), restore_started_app_server_thread converts its historical turns into the same core Event sequence the TUI already processes for live interactions, then replays them into the event store so the chat widget renders them.

Key changes:

  • AppServerStartedThread now carries the full Threadstarted_thread_from_{start,resume,fork}_response clone the thread into the struct alongside the existing SessionConfiguredEvent.
  • thread_snapshot_events() walks the thread's turns and items, producing TurnStartedItemCompleted* → TurnComplete/TurnAborted event sequences that the TUI already knows how to render.
  • restore_started_app_server_thread() pushes the session event + history events into the thread channel's store, activates the channel, and replays the snapshot — used for initial startup, resume, and fork.
  • primary_session_configured cleared on reset to prevent stale session state after reconnection.

Tradeoffs

  • Thread is cloned into AppServerStartedThread: The full thread snapshot (including all historical turns) is cloned at startup. For long-lived threads this could be large, but it's a one-time cost and avoids lifetime gymnastics with the response.

Tests

  • restore_started_app_server_thread_replays_remote_history — end-to-end: constructs a Thread with one completed turn, restores it, and asserts user/agent messages appear in the transcript.
  • bridges_thread_snapshot_turns_for_resume_restore — unit: verifies thread_snapshot_events produces the correct event sequence for completed and interrupted turns.

Test plan

  • Verify cargo check -p codex-tui-app-server passes
  • Verify cargo test -p codex-tui-app-server passes
  • Manual: connect to a remote app-server, resume an existing thread, confirm history renders in the chat widget
  • Manual: fork a thread via remote, confirm prior turns appear

fcoury added 10 commits March 17, 2026 10:01
Preserve the app-server `Thread` snapshot returned during start,
resume, and fork, then seed the primary thread store from its
`turns` before replaying the rebuilt transcript.

This makes `tui_app_server` restore remote session history without
relying on historical websocket notifications being replayed after
`thread/resume` or `thread/fork`.
Build the first replay snapshot for resumed and forked app-server threads
from a temporary lossless `ThreadEventStore` before updating the real
bounded channel store. This keeps early history visible on first load
even when the restored thread is larger than the live event buffer.

Add a regression test for over-capacity restores and document why the
`TurnStatus::InProgress` snapshot arm intentionally emits no completion
event during replay.
Share terminal turn event mapping between the live app-server bridge
and snapshot replay so interrupted and failed turns hydrate with the
same `EventMsg` sequence in both paths.

Add coverage for interrupted and failed `TurnCompleted` notifications
to lock in parity for reconnect and restore flows.
Add rustdoc to the new types and functions introduced by the remote
resume/fork history fix so reviewers can understand intent without
reading the full diff.
Borrow app-server thread items while bridging replay events and move
`response.thread` out of start, resume, and fork responses instead of
cloning potentially large restored histories.

This keeps the replay behavior unchanged while removing avoidable
allocation churn on restore paths and response mapping.
Keep `WebSearchAction::Other` when bridging app-server thread items so
restore replay matches the session mapping path instead of dropping the
entire web search item.

This preserves history parity for restored threads without changing the
handling of supported web search actions.
Keep failed-turn error synthesis for snapshot replay, but suppress it
when bridging live `TurnCompleted` notifications because the app-server
already emits a separate `ServerNotification::Error` for failed turns.

Add coverage so live failed turns emit a single completion event while
restored failed turns still replay their error state.
Stop resume and fork startup mapping from populating
`SessionConfiguredEvent.initial_messages` now that restored thread
history is replayed through the snapshot restore path.

Remove the unused initial-message conversion helpers and update the
session test to lock in snapshot-only replay for restored history.
Replay restored non-message turn items using their legacy event shapes so
resume and fork snapshots keep reasoning, web search, image generation,
and context compaction cells visible in the transcript.

Keep `ItemCompleted` replay for user, plan, and agent-message items,
which still depend on committed-turn semantics in the TUI. Add adapter
tests to pin the mixed replay contract.
Carry `show_raw_agent_reasoning` through the app-server start/resume
state so snapshot replay keeps raw reasoning history when users enable
that transcript mode.

Update snapshot replay tests in the adapter and app restore path to pin
that resumed and forked threads retain the raw reasoning events instead
of dropping them during hydration.
Copilot AI review requested due to automatic review settings March 17, 2026 14:14
@fcoury
Copy link
Copy Markdown
Contributor Author

fcoury commented Mar 17, 2026

@codex review

Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR fixes missing transcript history when the TUI connects to a remote app-server and the user resumes or forks a thread. It preserves and replays the server-provided Thread snapshot (including historical turns/items) through the TUI’s existing event pipeline, and also clears stale session state on reset.

Changes:

  • Extend AppServerStartedThread to carry the full Thread snapshot (plus show_raw_agent_reasoning) for start/resume/fork.
  • Add snapshot-to-event replay (thread_snapshot_events / restore_started_app_server_thread) so remote resumed/forked sessions render prior turns.
  • Clear primary_session_configured during reset to prevent stale session state after reconnection.

Reviewed changes

Copilot reviewed 3 out of 3 changed files in this pull request and generated 2 comments.

File Description
codex-rs/tui_app_server/src/app_server_session.rs Preserves the full Thread in start/resume/fork results; removes legacy “initial_messages from turns” path.
codex-rs/tui_app_server/src/app/app_server_adapter.rs Adds thread_snapshot_events + turn terminal-event bridging to generate replayable Event sequences from a Thread snapshot.
codex-rs/tui_app_server/src/app.rs Introduces restore_started_app_server_thread to seed the event store and replay history; clears primary_session_configured on reset; updates startup/resume/fork flow to use the new restore path.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

You can also share your feedback on Copilot code review. Take the survey.

Comment on lines +2132 to +2134
/// 2. Builds a **lossless** replay snapshot from a temporary store so that
/// the initial render sees all history even when the thread has more
/// turns than the bounded channel capacity.
Comment on lines +512 to +517
/// produce identical `EventMsg` sequences for the same turn status.
///
/// - `Completed` → `TurnComplete`
/// - `Interrupted` → `TurnAborted { reason: Interrupted }`
/// - `Failed` → `Error` (if present) then `TurnComplete`
/// - `InProgress` → no events (the turn is still running)
Copy link
Copy Markdown
Contributor

@chatgpt-codex-connector chatgpt-codex-connector bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: cfe71650e9

ℹ️ 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".

fcoury added 2 commits March 17, 2026 11:32
Defer `initial_user_message` submission while a restored thread snapshot is
replayed so resume and fork startup prompts stay after hydrated history in
the transcript.

Add a regression test that restores remote history with a startup prompt and
asserts the restored transcript renders before the prompt submission.
@fcoury
Copy link
Copy Markdown
Contributor Author

fcoury commented Mar 17, 2026

@codex review

Copy link
Copy Markdown
Contributor

@chatgpt-codex-connector chatgpt-codex-connector bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 6151d8a9ea

ℹ️ 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".

@fcoury
Copy link
Copy Markdown
Contributor Author

fcoury commented Mar 17, 2026

@codex review

@chatgpt-codex-connector
Copy link
Copy Markdown
Contributor

Codex Review: Didn't find any major issues. Can't wait for the next one!

ℹ️ 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".

@etraut-openai etraut-openai merged commit 78e8ee4 into openai:main Mar 17, 2026
29 of 33 checks passed
@github-actions github-actions bot locked and limited conversation to collaborators Mar 17, 2026
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants