Problem
While a session/prompt is in flight (the input is disabled, placeholder reads agent is processing…), there's no way to abort the turn from the TUI short of Ctrl-C, which quits the whole app and tears down the WS connection. Killing the connection is sufficient on the wire — acp-mux sees the WS close, sweeps the active turn — but it's a coarse hammer: you lose any other session state, any queued log scrollback, and you can't immediately reattach without re-doing initialize / session/new.
ACP defines a dedicated cancel channel for this: session/cancel is a notification (client → agent, no response expected) carrying just { sessionId }. The agent is expected to abort whatever it was doing and complete the turn with stopReason: "cancelled". acp-tui doesn't send this today.
Proposed solution
Bind Ctrl-X (or whatever key feels right — Ctrl-D conflicts with EOF, Ctrl-C is already taken by quit, Esc is consumed by the permission prompt) at the app level. When pressed:
- If there's no active turn, no-op (or log a hint "no turn in flight").
- If there's an active turn, send
cli.notify("session/cancel", {"sessionId": self.acp_session_id}) and log [bold yellow]cancel requested[/] in the log.
- Leave the input disabled — the agent's eventual
stopReason: "cancelled" response will re-enable it via the existing on_input_submitted finally block.
Optional: while we wait for the cancel to take effect (some agents are slow to honor it), update the placeholder to cancelling… so the user knows the keystroke registered.
Implementation notes
ACPClient.notify(method, params) already exists in src/acp_tui/acp_client.py:101-107. No new client code needed.
- The binding goes on
ACPApp.BINDINGS with priority=True so it works even when PromptInput has focus.
- An action method
action_cancel_turn is the handler. It needs to check self._prompt_in_flight (or whatever tracks "input disabled because a prompt is running") — currently the disabled state is the only signal. Either add an explicit flag on ACPApp or read prompt_input.disabled and assume the only thing that disables it is a turn-in-flight. The flag is cleaner.
Scope: ~20 lines plus a paragraph in the README.
Out of scope
- Sending
session/cancel while a permission prompt is pending — that's a different mode (the prompt is the in-flight thing, not the agent's turn), and the existing [esc] cancel already covers it.
- Cancel-during-turn that interrupts a tool call mid-execution. ACP delegates the actual cancellation semantics to the agent; the TUI's job is just to send the notification.
Problem
While a
session/promptis in flight (the input is disabled, placeholder readsagent is processing…), there's no way to abort the turn from the TUI short ofCtrl-C, which quits the whole app and tears down the WS connection. Killing the connection is sufficient on the wire — acp-mux sees the WS close, sweeps the active turn — but it's a coarse hammer: you lose any other session state, any queued log scrollback, and you can't immediately reattach without re-doinginitialize/session/new.ACP defines a dedicated cancel channel for this:
session/cancelis a notification (client → agent, no response expected) carrying just{ sessionId }. The agent is expected to abort whatever it was doing and complete the turn withstopReason: "cancelled". acp-tui doesn't send this today.Proposed solution
Bind
Ctrl-X(or whatever key feels right — Ctrl-D conflicts with EOF, Ctrl-C is already taken by quit, Esc is consumed by the permission prompt) at the app level. When pressed:cli.notify("session/cancel", {"sessionId": self.acp_session_id})and log[bold yellow]cancel requested[/]in the log.stopReason: "cancelled"response will re-enable it via the existingon_input_submittedfinallyblock.Optional: while we wait for the cancel to take effect (some agents are slow to honor it), update the placeholder to
cancelling…so the user knows the keystroke registered.Implementation notes
ACPClient.notify(method, params)already exists insrc/acp_tui/acp_client.py:101-107. No new client code needed.ACPApp.BINDINGSwithpriority=Trueso it works even whenPromptInputhas focus.action_cancel_turnis the handler. It needs to checkself._prompt_in_flight(or whatever tracks "input disabled because a prompt is running") — currently the disabled state is the only signal. Either add an explicit flag onACPAppor readprompt_input.disabledand assume the only thing that disables it is a turn-in-flight. The flag is cleaner.Scope: ~20 lines plus a paragraph in the README.
Out of scope
session/cancelwhile a permission prompt is pending — that's a different mode (the prompt is the in-flight thing, not the agent's turn), and the existing[esc] cancelalready covers it.