Product or interface
CLI - interactive TUI
Use case and problem
When I submit a prompt and immediately realize it has a typo, targets the wrong file, or asks the wrong question, the only escape is Esc — which stops the turn and leaves the prompt stuck inside a cancelled turn in the transcript. I have to retype it or recall it with Up and edit from history, losing the "fix one word and resend" flow.
Today the queue flow already returns queued messages to the composer (Alt+Up), and crash recovery already restores interrupted submissions on relaunch — but the most common case, "I just sent it and regret it", has no path.
Desired behavior
State-gated restore, keyed on what the aborted turn actually produced — not a timer:
When the user aborts a running turn AND that turn has produced no user-visible output (no assistant text, no tool call, no steer message), the prompt text returns to the composer for editing. Thinking does not disqualify: it is internal model process and the most common regret moment. Turns with output keep today's stop-only behavior, so intentional stops are never second-guessed.
Interaction example:
type a prompt, press Enter
Esc (before any reply/tool output)
-> "Stopped · message restored to the Composer."
-> the prompt is back in the composer, editable
-> the aborted turn's transcript row is removed
type the correction (merges below the restored text), Enter to resend
Esc after the model started replying
-> turn stops as today; composer stays empty; transcript kept
Acceptance criteria:
- The trigger is the confirmed stop of the aborted turn on the existing abort funnel — delegated-only stops (root still running) never fire it; a stop the runtime does not confirm never fires it.
- Restore merges with text typed during the stop (never overwrites in-progress typing); restores drop the matching pending retry so a relaunch cannot duplicate the text; side conversations are skipped.
- Exiting the app after an abort still persists the restored draft (existing draft-recovery path), so the text survives to the next launch.
- A restore failure reports a warning without breaking Esc handling.
Why state-gated instead of a time window: a timer races the model's first token and behaves non-deterministically; gating on "no output yet" captures exactly the regret window with zero timing sensitivity. Why not unconditional: interrupting a turn that is already replying usually means "stop", not "give my words back" — unconditional restore would fight that intent.
Platform
Multiple platforms
Alternatives and additional context
What I tried / considered:
Up input-history recall — works, but loses cursor position and attachments and requires knowing the shortcut.
/history Edit on the committed message — a runtime-level rewind; far heavier than the regret-window case needs.
- Doing nothing — the cancelled turn keeps the text, but editing it means retyping.
I implemented this on my fork (per the collaborators-only PR policy, not opening a PR; feel free to review the fork commit and port it as a maintainer PR — partial adoption welcome):
- Branch
feat/abort-prompt-restore, single commit 2dfc60b on top of current main: kele98@2dfc60b
- TUI-only: 6 files (
packages/tui abort funnel + app wiring + a small draft-lifecycle helper + tests + the Esc shortcut row in README/README_ZH updated in both languages). No runtime, persistence, or protocol changes.
- Validation:
tui-app.test.ts 270/281 — the 11 failures are the pre-existing zh-CN locale baseline on this machine, reproduced identically with the patch stashed; new/adjusted cases cover no-output restore, thinking-only restore, after-output no-restore, unconfirmed-stop no-restore, delegated-only-stop no-restore, merge-with-typed-text, and a two-instance relaunch non-duplication test through real draft persistence. pnpm verify passes all gates up to test:capabilities (stops on the same 23 environment-baseline failures: 11 locale assertions + 12 Windows symlink EPERM); remaining gates pass individually (status-contract 9/9, smoke 16 pass/1 skip, byok 11/11); typecheck and build clean. Interactive smoke via a real pty against a local fixture provider (stream holds open until abort) verified both timings end-to-end. Not claimed: macOS/Linux manual acceptance, live-model runs, Desktop app.
Key semantics (trigger scope, thinking exemption, transcript-row removal) are all open to adjustment if maintainers prefer a different shape.
Product or interface
CLI - interactive TUI
Use case and problem
When I submit a prompt and immediately realize it has a typo, targets the wrong file, or asks the wrong question, the only escape is
Esc— which stops the turn and leaves the prompt stuck inside a cancelled turn in the transcript. I have to retype it or recall it withUpand edit from history, losing the "fix one word and resend" flow.Today the queue flow already returns queued messages to the composer (Alt+Up), and crash recovery already restores interrupted submissions on relaunch — but the most common case, "I just sent it and regret it", has no path.
Desired behavior
State-gated restore, keyed on what the aborted turn actually produced — not a timer:
Interaction example:
Acceptance criteria:
Why state-gated instead of a time window: a timer races the model's first token and behaves non-deterministically; gating on "no output yet" captures exactly the regret window with zero timing sensitivity. Why not unconditional: interrupting a turn that is already replying usually means "stop", not "give my words back" — unconditional restore would fight that intent.
Platform
Multiple platforms
Alternatives and additional context
What I tried / considered:
Upinput-history recall — works, but loses cursor position and attachments and requires knowing the shortcut./historyEdit on the committed message — a runtime-level rewind; far heavier than the regret-window case needs.I implemented this on my fork (per the collaborators-only PR policy, not opening a PR; feel free to review the fork commit and port it as a maintainer PR — partial adoption welcome):
feat/abort-prompt-restore, single commit2dfc60bon top of currentmain: kele98@2dfc60bpackages/tuiabort funnel + app wiring + a small draft-lifecycle helper + tests + theEscshortcut row in README/README_ZH updated in both languages). No runtime, persistence, or protocol changes.tui-app.test.ts270/281 — the 11 failures are the pre-existing zh-CN locale baseline on this machine, reproduced identically with the patch stashed; new/adjusted cases cover no-output restore, thinking-only restore, after-output no-restore, unconfirmed-stop no-restore, delegated-only-stop no-restore, merge-with-typed-text, and a two-instance relaunch non-duplication test through real draft persistence.pnpm verifypasses all gates up totest:capabilities(stops on the same 23 environment-baseline failures: 11 locale assertions + 12 Windows symlink EPERM); remaining gates pass individually (status-contract9/9,smoke16 pass/1 skip,byok11/11); typecheck and build clean. Interactive smoke via a real pty against a local fixture provider (stream holds open until abort) verified both timings end-to-end. Not claimed: macOS/Linux manual acceptance, live-model runs, Desktop app.Key semantics (trigger scope, thinking exemption, transcript-row removal) are all open to adjustment if maintainers prefer a different shape.