TUI: stop button in the composer while a turn is running - #316
Merged
Conversation
Esc, Ctrl+C and /abort all stop the agent, but they are keyboard lore - nothing on screen says a running turn can be stopped at all. Put a clickable stop chip inside the input field, next to Send, rendered only while status is running; a press takes exactly the path Esc does (onAbort + abort_requested), so there is one abort path however it was asked for. The chip paints on the palette's error ground with a measured ink (readableOn), the same trick the composer buffer uses, so it stays legible across all eleven themes.
Aborting via the stop chip (or Esc / Ctrl+C / /abort) used to land a warn-styled 'Turn failed [cancelled]: This operation was aborted' in the chat — an error wall for something the operator did on purpose. Worse, the abort races the LLM stream, so it sometimes surfaced as 'Turn failed [model]: model returned empty content', dressing the user's own stop as a provider failure. The reducer now treats loop_failed as stopped-by-user when the category is 'cancelled' OR an abort_requested is on the books (state.aborting — every abort entry point sets it, finishRun clears it). It leaves a calm system notice — 'Agent stopped by user.' — that carries the aborted turn's prompt as retryText, and chat-log hangs the existing [try again] beside [copy] on exactly that notice, resending the user's prompt: a mistaken stop is one click to undo.
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.
Problem
While the agent is working there is no visible way to stop it. Esc, Ctrl+C and
/abortall abort the turn, but every one of them is keyboard lore — nothing on screen tells the operator a running turn can be stopped, or where. And once stopped, the chat renders an error wall (Turn failed [cancelled]: This operation was aborted— or, when the abort races the stream, evenTurn failed [model]: model returned empty content) for something the operator did on purpose.Change
1. A clickable stop chip inside the input field, next to
send →, rendered only whilestatus === "running":ComposerStopButton— a sibling ofComposerSendButtonwith the sameMouseTargetpattern and overlay layer. No disabled state: it only exists when there is a run to stop.errorground with a measured ink (readableOn), legible across all eleven themes.PromptShellgainsrunning/onStopprops;tui-appwiresonStopto exactly the pair of calls the Esc branch makes (onAbort()+abort_requested).2. A stopped turn says who stopped it, and offers a retry. The reducer treats
loop_failedas stopped-by-user when the category iscancelledor anabort_requestedis on the books (state.aborting— every abort entry point sets it,finishRunclears it), which also catches the abort/stream race that used to dress the user's own stop as a provider failure. Instead of the warn wall it leaves a calm system notice —Agent stopped by user.— carrying the aborted turn's prompt asretryText, and chat-log hangs the existing[try again]beside[copy]on exactly that notice, resending the user's prompt: a mistaken stop is one click to undo.Keyboard behaviour is unchanged; both pieces are additive.
Tests
onAbortexactly once, chip leaves when the run ends.loop_failed→ calm notice +retryText+cancelledoutcome;[model]failure during a requested abort → still stopped-by-user; the same failure with no abort on the books keeps the warn styling; noretryTextwhen there is no prompt to re-run.[try again]hangs under the stopped notice and only there.tsc --noEmitclean; components + reducer + key-binding/submit suites: 675 tests green. Verified live in the TUI against a real provider (stop mid-turn →Agent stopped by user.+[copy] [try again]).