feat(server,web): reliably retract sent turns and restore editing - #11
Merged
Merged
Conversation
…stant output Pressing Esc between send and first assistant output interrupts the turn, reverts to the pre-turn checkpoint, and restores the message into the composer. Claude provider rollback was previously cosmetic: resumeSessionAt was never passed to the SDK and live sessions kept rolled-back history. Rollback now maintains per-turn assistant-uuid watermarks, clears the resume cursor at turn zero, and recycles the session before the next turn. Built with Claude Fable 5 via Claude Code, implementation by Codex (GPT-5.6-Sol).
…action intent Adds the client-dispatchable retract command, threadTurnRetraction capability, additive retraction metadata on turn-interrupt-requested/reverted payloads, a durable pending-retraction projection (migration 041) with startup scan, and authoritative decider eligibility incl. the no-assistant-output rule. Nothing consumes the intent yet; the retraction process manager follows. Built with Claude Fable 5 via Claude Code, implementation by Codex (GPT-5.6-Sol).
…d gating Retract can now cancel an unclaimed provider send before session spawn, and sends are atomically claimed after the pre-turn baseline checkpoint exists. Adds the dispatch-ownership ledger (migration 042) and restart classification. Built with Claude Fable 5 via Claude Code, implementation by Codex (GPT-5.6-Sol).
…ommand Bootstrap-created worktrees record provenance; thread.turn.retract.complete atomically emits reverted (+deleted for first-message retractions), and deletion cleanup removes only T3-owned, unshared worktrees. Built with Claude Fable 5 via Claude Code, implementation by Codex (GPT-5.6-Sol).
…ry draft handoff Esc now works with focus anywhere in the chat view (bubble phase; menus win), cancels sends locally before the start RPC dispatches, and capability-gated machinery hands first-message retractions back to a fresh seeded draft. Empty persisted threads render the project hero instead of the bare placeholder. Built with Claude Fable 5 via Claude Code, implementation by Codex (GPT-5.6-Sol).
Renumbered the managed-worktrees migration to 043 to resolve the parallel 042 collision with the dispatch-ownership ledger.
…rovider rollback TurnRetractionReactor drives requested retractions to completion or terminal failure from projected state, lifecycle events, and a startup scan. Adds rollbackConversationTo (verified absolute boundary) for Claude and Codex, late-event tombstone suppression, and new-turn blocking while pending. Built with Claude Fable 5 via Claude Code, implementation by Codex (GPT-5.6-Sol).
Retraction is now one correlated command with persistent commandId; the interrupt/settle/revert saga and checkpoint heuristics are deleted (ChatView net -112 LOC). First-message completions navigate to a seeded fresh draft; mid-thread completions restore into the thread composer; failures always preserve the user's text. Built with Claude Fable 5 via Claude Code, implementation by Codex (GPT-5.6-Sol).
… pending scan Adds a 30s pending-row re-enqueue so retryable retraction failures converge without lifecycle events, capability gating on the web dispatch seam, and coverage for commandId replay, duplicate completion, additive-metadata tolerance, and pre-retraction snapshot decode. Built with Claude Fable 5 via Claude Code, implementation by Codex (GPT-5.6-Sol).
The handoff watched a transient completion stream; cancelled-path completions land in milliseconds and beat the subscription, stranding the user on a deleted thread. The watcher now lives in the persistent chat layout and fires on correlated completion, thread deletion, shell absence, correlated failure, or a 60s staleness guard. Built with Claude Fable 5 via Claude Code, implementation by Codex (GPT-5.6-Sol).
…ctor Object.values inside the zustand selector returned a fresh array every snapshot, looping useSyncExternalStore from the persistent chat layout and crashing every route. Select the stable map and derive values outside. Built with Claude Fable 5 via Claude Code.
…lback arithmetic, correct client retraction correlation The retraction worker blocked forever awaiting a redundant turn/interrupt against a settled turn; interrupts are now once-per-request, bounded, and every convergence evaluation logs its outcome. Claude absolute rollback now offsets session-local turns by the resumed cursor's lifetime count. The client reducer maintains turnRetraction incrementally so pending rows suppress the staleness fallback, and failure/stale recovery never navigates. Built with Claude Fable 5 via Claude Code, implementation by Codex (GPT-5.6-Sol).
…h providers Claude's absolute rollback skipped cursor rewind and query recycle at zero delta, so the live SDK session kept interrupted prompts. Codex count arithmetic saw retained==reported and rolled back nothing while the interrupted turn remained live. Both paths now share finalization (Claude) or roll back from the concrete target turn with absence and retained-prefix verification (Codex). The wire repro gates on post-retract interrogation. Built with Claude Fable 5 via Claude Code, implementation by Codex (GPT-5.6-Sol).
…cting An interrupt issued while the session was still starting consumed the request's once-only budget, so long Claude turns ran to natural completion before rollback. The reactor now waits for the concrete turn and interrupts per turn id; the repro gains long-response scenarios asserting retraction beats natural completion on both providers. Built with Claude Fable 5 via Claude Code, implementation by Codex (GPT-5.6-Sol).
…cadence Interrupts landing before the SDK begins processing were silently swallowed; the adapter now replays a latched interrupt on system/status=requesting, and the reactor re-issues every 2s while the target turn stays projected-running. Double-pop gate: 8/8 Claude retractions <= 723ms (was up to 18.9s). Built with Claude Fable 5 via Claude Code, implementation by Codex (GPT-5.6-Sol).
The composer restores and the message row hides synchronously at keypress; completion reconciles without double-merges, failures un-hide and toast, first-message recovery navigates on command acceptance, and sends during a pending retraction hold up to 20s instead of erroring. Built with Claude Fable 5 via Claude Code, implementation by Codex (GPT-5.6-Sol).
While a client-known pending retraction exists, one effective-presentation derivation keeps the timeline working row, the composer stop button and the sidebar Working badge quiet, so the popped turn reads as never started. Suppression drops on terminal failure or when a different turn goes live (a held send dispatching), and shares the pending-recovery lifetime so a stuck retraction cannot masquerade as idle. Built with Claude Fable 5 via Claude Code, implementation by Claude Opus 5.
The post-rollback recycle replaces the SDK session over several hundred ms during which the thread looks idle, so a second send could race the recycle: both called startSession, and the losing session kept an already-opened, prompt-offered turn no live query could settle, pinning the session "running" forever. A per-thread semaphore makes recycle + turn-open + prompt-offer atomic. Adds interrupt-replay arm/fire logging and a wire-level pop-resend-follow repro scenario (fails pre-fix, passes N=3 post-fix). Built with Claude Fable 5 via Claude Code, implementation by Claude Opus 5.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
What Changed
Why
Previously, retracting a just-sent message could leave provider context, turn state, or the client out of sync—especially during early interrupts, session recycling, or rapid retract-and-resend flows. This change makes retraction durable and convergent: the concrete provider turn is rolled back to the correct checkpoint, the UI responds immediately, and restored messages can be edited and resent without wedging the thread or leaving stale model context behind.
UI Changes
The chat UI now optimistically removes a just-sent user message on Esc, restores it into the composer when retraction completes, and reconciles silently with durable server state. The change also updates related keybinding and message timeline behavior. Before/after screenshots and interaction video should be added for review.
Checklist