While a turn runs the main thread is blocked in the conversation loop,
so nothing reads the keyboard. Add an EscapeInterruptMonitor that, for
the duration of a turn on an interactive terminal, switches stdin to a
minimal non-canonical mode (echo/line-buffering off; ISIG and output
processing untouched, unlike full raw mode) and polls for a lone ESC
byte. Escape sequences such as arrow keys are recognized and swallowed.
On Esc it trips the TurnInterruptSignal: the in-flight request is
aborted and control returns to the REPL prompt with the session intact.
Permission prompts read whole lines from stdin mid-turn, so stdin
ownership is coordinated through a StdinPromptGate: while a prompt holds
a lease the listener restores canonical mode and stops consuming bytes.
Also:
- spinner shows '(esc to interrupt)' and the banner/help mention Esc
- WorkerStatus/WorkerEventKind gain an 'interrupted' lifecycle state
- non-terminal stdin and non-unix platforms skip the listener; Ctrl+C
interruption still works there
Closes the Esc-to-interrupt feature request (#3196).
Wire the runtime's TurnInterruptSignal through the CLI turn lifecycle:
- AnthropicRuntimeClient races the streaming request against the
interrupt flag with tokio::select!; when the flag trips, the request
future is dropped, aborting the in-flight HTTP stream for every
provider variant.
- BuiltRuntime installs the shared signal into both the conversation
loop and the API client.
- The per-turn Ctrl+C monitor now interrupts the whole turn instead of
only aborting running hooks, so Ctrl+C mid-turn returns to the REPL
prompt with partial output instead of leaving the request running.
- The REPL spinner reports an interrupted turn distinctly from a
completed one.
Part of the Esc-to-interrupt work (#3196); the Esc key listener lands
separately.
Add a shared interrupt flag that the conversation loop polls at safe
points: before each model request, when a stream call fails, and before
each pending tool execution. When the flag is set the turn winds down
instead of failing: tools that have not run yet receive synthesized
error tool_results so every tool_use stays answered and the session
remains valid for the next request.
TurnSummary gains an interrupted field so callers can distinguish a
user-initiated stop from a completed turn, and the session tracer
records a turn_interrupted event (with iteration and phase) instead of
turn_failed / turn_completed.
Groundwork for Esc-to-interrupt (#3196): a follow-up wires this signal
into the streaming API client and the CLI input listener.