🤖 feat: continue interrupted stream by clicking the interrupted splitter#3634
🤖 feat: continue interrupted stream by clicking the interrupted splitter#3634ethanndickson wants to merge 4 commits into
Conversation
Clicking the 'interrupted' splitter now resumes the stream from where it stopped, identical to the RetryBarrier's Retry button and the backend auto-retry path. This is the only continue affordance for user-initiated (Esc) interrupts, where the RetryBarrier is intentionally suppressed. Extracts the manual-retry/resume flow (including the temporary auto-retry enable + rollback dance) into a shared useResumeStream hook so RetryBarrier and InterruptedBarrier share one implementation. BaseBarrier gains an optional onClick that turns only the centered label into a button with a hover underline + pointer cursor; the gradient lines and all other visuals are unchanged.
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: d7e6eaab08
ℹ️ 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".
P2-1: the interrupted divider opts out of the temporary auto-retry enable (autoRetryOnFailure:false). ChatPane unmounts the divider once auto-retry becomes active, so owning the rollback there would cancel an in-flight scheduled retry on unmount. A user-initiated Esc means 'continue once', and auto-retry-enabled users still get backend recovery (the backend consults the persisted preference on failure regardless), so this is both safe and the correct semantic. P2-2: only the divider on the resume target (history tail, lastRetryCandidateMessage) is clickable. resumeStream always continues the tail, so older partial dividers would otherwise resume the wrong turn; they now stay decorative.
|
Addressed both Codex P2 findings in d89eba2. P2-1 — Keep resume rollback outside the transient divider ( P2-2 — Limit clicks to the resumable interrupted turn ( |
|
@codex review Addressed both P2 findings (auto-retry rollback ownership + clickable-only-on-resume-target). Please take another look. |
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: d89eba2017
ℹ️ 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".
The divider is the only continue affordance for a user-aborted stream, so a resume failure before any stream-error (e.g. disconnected API or a SendMessageError from resumeStream) must be visible — otherwise the click looks like a no-op. Renders the hook's error inline below the resumable divider, mirroring RetryBarrier's error treatment. Idle/hover appearance is unchanged.
|
Addressed in 9364f85. P2 — Surface interrupted-resume failures ( |
|
@codex review Addressed the resume-failure feedback finding (errors now surface inline on the resumable divider). Please take another look. |
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 9364f85358
ℹ️ 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".
Transcript-only (archived, local checkout removed) workspaces are read-only: the composer is replaced and edit/send are disabled. Gate the divider's resumable flag on !transcriptOnly so archived interrupted transcripts render a decorative divider instead of a live continue button that would call resumeStream against a no-checkout workspace.
|
Addressed in c4d3d05. P2 — Disable resume in transcript-only chats ( |
|
@codex review Gated the divider's resume on !transcriptOnly so archived transcripts stay read-only. Please take another look. |
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: c4d3d054fb
ℹ️ 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".
| text="interrupted" | ||
| color="var(--color-interrupted)" | ||
| className={props.className} | ||
| onClick={props.resumable ? () => void resume() : undefined} |
There was a problem hiding this comment.
Add a shortcut for continuing interrupted responses
When props.resumable is true, this introduces a new continue/resume operation that is only exposed through the divider button. /workspace/mux/AGENTS.md requires that every operation have a keyboard shortcut, so keyboard-driven users have no direct shortcut/command-palette path to resume a user-aborted stream without tabbing to this small label. Please wire the same resume() path into the app keybind/command flow, with any visible hint hidden on mobile per the guideline.
Useful? React with 👍 / 👎.
Summary
Clicking the "interrupted" splitter now continues the stream from where it stopped, identical to the existing RetryBarrier "Retry" button and the backend auto-retry path. Previously the splitter was purely decorative, which left user-initiated (Esc) interrupts with no continue affordance at all — the warning RetryBarrier is intentionally suppressed for
reason: "user"aborts, so only the subtle "interrupted" line showed and there was no way to resume short of typing a new message.Background
When a stream is interrupted, the backend keeps the partial assistant turn in history. Both manual Retry and auto-retry converge on
resumeStream, which does no history shaping; the model simply continues its partial turn (plus an ephemeral[CONTINUE]sentinel injected at transform time, never persisted). The continue behavior already existed — it just wasn't reachable after a user-initiated Esc, because the RetryBarrier only mounts for system/error interrupts.Implementation
useResumeStream(workspaceId)hook. This includes the temporarysetAutoRetryEnabled({ persist: false })enable plus the rollback dance that restores the user's auto-retry preference once the resumed attempt reaches a terminal outcome (so a user who disabled auto-retry isn't silently re-opted-in).RetryBarriernow consumes the hook instead of owning that logic inline, so both entry points share one implementation and can't drift.BaseBarriergains an optionalonClick. When provided, only the centered label becomes a<button>with a hover underline and pointer cursor; the gradient lines and every other visual stay exactly as before. Barriers withoutonClick(e.g.EditCutoffBarrier) are unchanged.InterruptedBarrieracceptsworkspaceIdand wires its label toresume().resume()self-guards against re-entrancy, so the label stays mounted (no button↔div remount flicker).Visual contract
No visual change except: hovering the "interrupted" label underlines it and shows a pointer cursor. The gradient lines are inert and hovering them does nothing.
Risks
Low. The resume/rollback logic is moved verbatim into the hook (covered by the existing RetryBarrier tests, which still pass against the refactored component), and the new affordance reuses the same backend path. The only new surface is the conditional
<button>rendering inBaseBarrier, scoped to callers that passonClick.Follow-ups (not in scope)
RESUME_STREAMkeybind could call the same hook fromuseAIViewKeybinds.Generated with
mux• Model:anthropic:claude-opus-4-8• Thinking:xhigh• Cost:$5.84