fix(cli): stop refilling input with prior prompt on cancel#3208
Conversation
When ESC (or Ctrl+C) cancelled an in-progress model response, the input buffer was being repopulated with the just-submitted prompt. The handler unconditionally read userMessages.at(-1) and called buffer.setText with it, surprising users who expected ESC to leave the input clean. The previous prompt is still recoverable via Up arrow / Ctrl+P history navigation. Queued follow-up messages still get moved into the buffer for editing on cancel, but now via the atomic popAllMessages helper, and any in-progress draft the user typed is preserved by prepending the queued text instead of clobbering it (matching the existing popQueueIntoInput convention in InputPrompt). Fixes #3204
E2E Verification ReportVerified via interactive TUI on
Scenario D was added during code review — Codex flagged that the Ctrl+C cancellation path (unlike ESC) does not pre-clear the buffer, so the cancel handler must handle the case where the user has typed a draft. The handler now mirrors the existing Unit tests
|
📋 Review SummaryThis PR fixes issue #3204 where pressing ESC/Ctrl+C to cancel a streaming response was incorrectly repopulating the input buffer with the previously submitted prompt. The implementation is clean, well-tested, and demonstrates excellent understanding of the existing codebase patterns. The fix properly distinguishes between user expectations for cancel behavior versus history navigation. 🔍 General Feedback
🎯 Specific Feedback🔵 Low
✅ Highlights
|
Code Coverage Summary
CLI Package - Full Text ReportCore Package - Full Text ReportFor detailed HTML reports, please see the 'coverage-reports-22.x-ubuntu-latest' artifact from the main CI run. |
When ESC (or Ctrl+C) cancelled an in-progress model response, the input buffer was being repopulated with the just-submitted prompt. The handler unconditionally read userMessages.at(-1) and called buffer.setText with it, surprising users who expected ESC to leave the input clean. The previous prompt is still recoverable via Up arrow / Ctrl+P history navigation. Queued follow-up messages still get moved into the buffer for editing on cancel, but now via the atomic popAllMessages helper, and any in-progress draft the user typed is preserved by prepending the queued text instead of clobbering it (matching the existing popQueueIntoInput convention in InputPrompt). Fixes QwenLM#3204
TLDR
Pressing ESC (or Ctrl+C) to cancel a streaming response was repopulating the input box with the prompt the user had just submitted. The handler unconditionally called
buffer.setText(userMessages.at(-1)), so users who expected ESC to leave the input clean kept seeing their previous prompt re-appear. This PR removes the prior-prompt restoration. The previous prompt is still recoverable via Up arrow / Ctrl+P history navigation.While doing this, also harden the queued-message restoration path so it (a) uses the atomic
popAllMessageshelper instead of the state-backedgetQueuedMessagesText+clearQueuepair, and (b) preserves any in-progress draft the user typed (mirroring the existingpopQueueIntoInputconvention inInputPrompt.tsx).Screenshots / Video Demo
N/A — text-only TUI behavior change. See the verification report posted as a follow-up comment for the before/after results across four scenarios.
Dive Deeper
The cancel handler in
AppContainer.tsxhad three branches:lastUserMessage = userMessages.at(-1), optionally append queued text, and callbuffer.setTextwith the result.clearQueue().Branch 2 is the bug.
userMessagesis the deduplicated history of all user messages (used for Up-arrow recall), soat(-1)is the just-submitted prompt. The behavior was inherited from a Gemini-CLI upstream sync, but issue #3204 makes clear it conflicts with what users expect from ESC.The new handler:
popAllMessages()atomically drains the queue. If anything was queued, prepend it to whatever is currently in the buffer (so a Ctrl+C cancel that fires while the user has an in-progress draft does not lose that draft). If the queue was empty, leave the buffer alone — there is nothing to restore, andhandleSubmitAndClearalready cleared the buffer at submission time.Why
popAllMessagesinstead ofgetQueuedMessagesText+clearQueue: the queue hook already exposes a synchronous-ref-backed atomic drain (popAllMessagesanddrainQueue).useGeminiStreamitself relies on the atomic helpers for mid-turn drains, so using the same primitive in the cancel handler keeps the "tools just finished, queue not yet React-flushed" transition obviously safe.Note that the
userMessagesstate is still maintained for history navigation inInputPrompt— only the cancel handler stops reading it.Reviewer Test Plan
npm run build && npm run bundleand startnode dist/cli.jsin any directory.write a long explanation of how sorting algorithms work) and press ESC while the spinner is visible. Buffer should be empty.<queued>\n<draft>— the queued message prepended to the draft, neither lost.Also:
cd packages/cli && npx vitest run src/ui/AppContainer.test.tsx— three new regression tests cover the scenarios above.Testing Matrix
Linked issues / bugs
Fixes #3204