feat(cli): double ESC discards the draft, even mid-stream

Ctrl+C interrupts a running turn and only clears the composer when idle,
so there was no way to discard a half-typed prompt while the agent was
streaming. Claude Code and Gemini CLI both bind that to double-Esc.

Appends the draft to history before clearing, so Up recalls it — the same
undo affordance Claude Code gives, which is what makes this safe on a key
people hit by reflex. Excluded when a modal prompt is up, since those bind
ESC eagerly and cancel should still win.

Co-authored-by: Brooklyn Nicholson <brooklyn.bb.nicholson@gmail.com>
This commit is contained in:
CK iRonin.IT
2026-07-30 04:20:41 -05:00
committed by Brooklyn Nicholson
parent 3f5ec8b45d
commit c1ec394160
+26
View File
@@ -15592,6 +15592,32 @@ class HermesCLI(CLIAgentSetupMixin, CLICommandsMixin, CLIBillingMixin):
event.app.invalidate()
return
@kb.add('escape', 'escape', filter=~_modal_prompt_active)
def handle_double_escape(event):
"""Double ESC: discard the current draft and any attached images.
Matches Claude Code / Gemini CLI, where double-Esc is the
clear-the-composer gesture. It works while the agent is
streaming, which is the gap Ctrl+C leaves: Ctrl+C interrupts a
running turn and only clears the draft when idle, so mid-stream
there was no way to discard a half-typed prompt.
The draft is appended to history first, so Up recalls it — the
same undo affordance Claude Code provides, and the reason this
is safe to bind to a key pressed by reflex.
Single ESC is the prefix for Alt sequences (escape+enter,
escape+g, escape+v), so prompt_toolkit's escape-timeout keeps
those distinct from the double press. Modal prompts bind ESC
eagerly and are excluded here so cancel still wins.
"""
buf = event.app.current_buffer
if not (buf.text or cli_ref._attached_images):
return
buf.reset(append_to_history=bool(buf.text))
cli_ref._attached_images.clear()
event.app.invalidate()
@kb.add('c-z')
def handle_ctrl_z(event):
"""Handle Ctrl+Z - suspend process to background (Unix only)."""