feat(tui): Esc interrupts running agent turn - #21707
Closed
rxdxxxx wants to merge 1 commit into
Closed
Conversation
rxdxxxx
force-pushed
the
feat/tui-esc-interrupt
branch
from
May 8, 2026 07:00
5b99437 to
09b4eb9
Compare
When the agent is busy (turn in flight), pressing Esc now fires interruptTurn() — the same path Ctrl+C takes — without the clear-draft or exit fallbacks that Ctrl+C carries. Guards: - isBlocked: overlays (approval, clarify, pager, sudo, etc.) already own their own Esc handlers in the isBlocked branch above; this branch is unreachable while any overlay is open, but the explicit check keeps intent readable. - cState.completions.length: autocomplete dropdown gets the first Esc (dismiss list), interrupt only fires if no completions are showing. Existing Esc behaviour is fully preserved in non-busy states: - queue-edit cancel (queueEditIdx !== null) - selection clear (terminal.hasSelection) - voice-record toggle chord (isVoiceToggleKey) Also: - hotkeys.ts: document Esc in the /hotkeys help table - appLayout.tsx: update busy placeholder to 'Esc / Ctrl+C to interrupt…' Closes NousResearch#16181 (partial — Esc as an alias for interrupt reduces Ctrl+C copy/interrupt conflict surface). Related: NousResearch#11352, NousResearch#11355, NousResearch#4903
rxdxxxx
force-pushed
the
feat/tui-esc-interrupt
branch
from
June 18, 2026 09:16
09b4eb9 to
70d7aae
Compare
teknium1
reviewed
Jul 13, 2026
teknium1
left a comment
Contributor
There was a problem hiding this comment.
Thanks for the focused TUI improvement. Current main still has Ctrl+C as the busy-turn interruption path at ui-tui/src/app/useInputHandlers.ts:520-528, so the premise is valid.
Problems
- The new
!cState.completions.lengthguard does not make Esc dismiss completions.useCompletion()ownssetCompletionsprivately (ui-tui/src/hooks/useCompletion.ts:42-51) and exposes no clear action (:112), so Esc skips the interrupt branch while completion results remain visible. - The PR adds no test for the new busy-Esc route or its stated completion/overlay precedence (
ui-tui/src/__tests__/useInputHandlers.test.ts:1-114currently covers helper functions only).
Suggested changes
- Expose a completion-dismiss action through the composer state, clear it on the first Esc, then let a subsequent Esc reach
turnController.interruptTurn(). - Add tests for busy interruption, completion-first behavior, and overlay precedence.
Automated hermes-sweeper review.
| // handled above in the `isBlocked` branch and never reach here anyway, but | ||
| // the explicit check keeps the intent readable. Also skip when completions | ||
| // are open so Esc can first dismiss the autocomplete list. | ||
| if (key.escape && live.busy && live.sid && !isBlocked && !cState.completions.length) { |
Contributor
There was a problem hiding this comment.
!cState.completions.length suppresses interruption, but no later branch clears completion results. useCompletion() keeps setCompletions private, so Esc can remain a no-op while the list is open; expose and invoke a completion-dismiss action before allowing the next Esc to interrupt.
rxdxxxx
added a commit
to rxdxxxx/hermes-agent
that referenced
this pull request
Aug 5, 2026
Addresses sweeper review on NousResearch#21707 and NousResearch#21720: PR NousResearch#21707 fixes: - Expose clearCompletions() from useCompletion hook - First Esc dismisses completions, second Esc interrupts the turn - Add Esc to /hotkeys table with full precedence description - Update busy placeholder to 'Esc / Ctrl+C to interrupt…' - Add interruptKey.test.ts with parseInterruptKey/isInterruptKey tests PR NousResearch#21720 fixes: - Add parseInterruptKey + isInterruptKey + formatInterruptKey to platform.ts - Add interrupt_key to ConfigDisplayConfig (gatewayTypes.ts) - Add interrupt_key default to hermes_cli/config_defaults.py - Thread interruptKey through useConfigSync → useMainApp → useInputHandlers - Add interruptKey to shouldPassThroughToGlobalHandler so custom keys (e.g. ctrl+g) are not consumed by the focused TextInput composer - Pass interruptKey prop to TextInput component - Dynamic placeholder renders the configured key label via formatInterruptKey - Add pass-through tests for custom interrupt key in textInputPassThrough.test Guards preserved (overlay > voice chord > queue-edit > selection > completions > interrupt): - Blocked overlays own their Esc handlers — interrupt unreachable while blocked - Voice-toggle chord (ctrl/alt+escape) wins before generic Esc handlers - Queue-edit cancel and selection-clear take precedence over interrupt - Completions dismiss on first Esc; interrupt fires only when list is empty - Ctrl+C behavior entirely unchanged
rxdxxxx
added a commit
to rxdxxxx/hermes-agent
that referenced
this pull request
Aug 5, 2026
Addresses sweeper review on NousResearch#21707 and NousResearch#21720: PR NousResearch#21707 fixes: - Expose clearCompletions() from useCompletion hook - First Esc dismisses completions, second Esc interrupts the turn - Add Esc to /hotkeys table with full precedence description - Update busy placeholder to 'Esc / Ctrl+C to interrupt…' - Add interruptKey.test.ts with parseInterruptKey/isInterruptKey tests PR NousResearch#21720 fixes: - Add parseInterruptKey + isInterruptKey + formatInterruptKey to platform.ts - Add interrupt_key to ConfigDisplayConfig (gatewayTypes.ts) - Add interrupt_key default to hermes_cli/config_defaults.py - Thread interruptKey through useConfigSync → useMainApp → useInputHandlers - Add interruptKey to shouldPassThroughToGlobalHandler so custom keys (e.g. ctrl+g) are not consumed by the focused TextInput composer - Pass interruptKey prop to TextInput component - Dynamic placeholder renders the configured key label via formatInterruptKey - Add pass-through tests for custom interrupt key in textInputPassThrough.test Guards preserved (overlay > voice chord > queue-edit > selection > completions > interrupt): - Blocked overlays own their Esc handlers — interrupt unreachable while blocked - Voice-toggle chord (ctrl/alt+escape) wins before generic Esc handlers - Queue-edit cancel and selection-clear take precedence over interrupt - Completions dismiss on first Esc; interrupt fires only when list is empty - Ctrl+C behavior entirely unchanged
Author
|
Superseded by #21720 which now includes the base Esc-interrupt feature plus all review fixes in a single PR. |
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
Pressing
Ctrl+Cin the TUI is the only way to interrupt a running agent turn.This creates a conflict on macOS where
Ctrl+Cis also the primary copy shortcut(#16181): users who want to copy an error mid-run accidentally interrupt the agent instead.
Escis the universally expected "cancel" key in terminal UIs (vim, less, fzf, Claude Code).Hermes TUI already uses
Escfor overlay dismissal and queue-edit cancellation;extending it to agent interruption is the natural next step.
Related: #16181, #11352, #11355, #4903
Solution
When the agent is busy,
EsccallsturnController.interruptTurn()— the samepath
Ctrl+Ctakes — without the clear-draft or exit fallbacks.Ctrl+Cbehavior is unchanged.Changes
useInputHandlers.ts— one guard block before the existing queue-edit handler:Guards:
live.busy(turn running),live.sid(session exists),!isBlocked(no overlay),!cState.completions.length(autocomplete gets Esc first).All existing non-busy Esc behavior preserved: voice-record chord, queue-edit cancel, selection clear.
hotkeys.ts— Esc added to/hotkeystable.appLayout.tsx— busy placeholder updated toEsc / Ctrl+C to interrupt….Testing
Build passes. Manual:
*[interrupted]*Notes
Diff scope: this branch also contains the follow-up commit from #21720.
The changes for this PR are the first commit only (
7785cf6).Configurable keybinding (
display.interrupt_key) is in #21720 (depends on this PR).