Problem (one or two sentences)
While a task is running, every message I send is queued and only read later — sometimes minutes later, after the model has finished thinking in a direction my message already overrode. The only way to make the model stop right now is the Stop button, but Stop ends the task (status: "interrupted") and does not send any message.
Context (who is affected and when)
Anyone who steers a task mid-flight: "actually, skip that and do X", correcting a wrong turn, or answering what the model is currently reasoning about. Today the cost is the model's entire remaining generation — minutes on long thinking/tool chains — plus the tokens spent on work the user already decided against. The longer the model runs, the more this bites.
Desired behavior (conceptual, not technical)
A new action — call it Interrupt & send:
- While the model is generating, I type a message and trigger "Interrupt & send" (a button next to the queue button; a keyboard shortcut would be nice).
- The in-flight model request stops immediately, and what it has already produced stays in the conversation with its cost recorded (Stop does this part today).
- The task does NOT end: it stays active, the model immediately reads my message, and the same task continues based on it.
- The plain Stop button keeps working exactly as it does today.
- The action is only offered when it is safe to interrupt — e.g., while the model is just generating, not mid-way through writing a file or running a command.
Constraints / preferences
- No data loss: the partial output, its cost, and anything already queued must not be silently dropped.
- No behavior changes for what exists: queueing while streaming, the Stop button, and queue draining at prompts all stay as-is.
- Keyboard accessible, and must not slow down normal sending.
- Exact UI is open for design: dedicated button, modifier key (e.g., Alt+Enter), or a "send now" action on individual queued messages.
Request checklist
Acceptance criteria
- Given a task is streaming, when I "Interrupt & send" a message, then the in-flight request stops within about a second, the partial output remains in history with its cost and a cancel reason, my message is in the conversation, and the task continues with status active.
- But the task is not marked "interrupted" and I do not have to reopen it.
- Given messages were already queued, when I interrupt & send, then [OPEN — community decision] only my message / my message plus the existing queue in order is delivered, with stable ordering and no duplicates.
- Given the model is mid-tool (writing a file / running a command), when [per the safety decision below], then the action is either unavailable or handles the partial side effects safely.
- A plain Stop still ends the task exactly as today; existing queue behavior is unchanged.
Proposed approach (plain language)
Stop already has the machinery to abort an in-flight model request while preserving the partial response and its cost; today that machinery is coupled to "end the task and mark it interrupted". A variant that interrupts only the current request, hands my message to the model, and resumes the normal task loop would close the gap. The message queue already exists (edit/delete, drain at prompts), so after the interrupt the feature just needs to deliver my message (and possibly the queue) into the resumed loop.
Trade-offs / risks
- Interrupting mid-tool: partial side effects (a half-written file, a running command) may have already happened. Options: offer only between model turns (safe, simple), or offer anytime and rely on the existing partial-write revert machinery.
- Races: double-clicks, or the turn finishing at the same instant — the action must be idempotent, never send the message twice, and never lose it.
- The queue is in-memory today; an extension reload mid-interrupt would lose queued messages (existing behavior, likely out of scope).
- Scope: VS Code webview first; CLI parity can be a follow-up.
Open questions for the community
- Where is it "safe" to interrupt — only while the model is generating, or also between tool calls?
- After the interrupt, what gets delivered: only my message, or mine plus the existing queue (in order)?
- UI: dedicated button, modifier key, or per-queued-message action?
- Keep the interrupted partial turn in history (my vote: yes — it is transparent about what the model thought and how much it cost), or discard it?
- Should the CLI get the same feature in v1, or webview only?
Related work — and how this differs
Technical context (for maintainers — verified against current code, optional reading)
- Queue path: the webview routes sends into
queueMessage whenever the task is streaming/blocked (webview-ui/src/components/chat/ChatView.tsx:659–677); queueMessage → webviewMessageHandler.ts:3753 → Task.messageQueueService (src/core/message-queue/MessageQueueService.ts — in-memory, not persisted, cleared on task dispose; rendered by webview-ui/src/components/chat/QueuedMessages.tsx).
- The queue is drained today only at pending asks (
src/core/task/Task.ts:1299–1503, command_output excluded), after context condensing (Task.ts:1816), and via processQueuedMessages() (Task.ts:5105). Nothing drains it while the model is purely generating — that is the gap.
- Stop path to reuse:
ClineProvider.cancelTask() (src/core/webview/ClineProvider.ts:3497) calls Task.cancelCurrentRequest() (aborts the in-flight AbortController, Task.ts:2448) and Task.abortTask() (Task.ts:2467), which marks the task abandoned and persists history as "interrupted". The partial-turn finalization — finalizing the partial message, recording cost + cancelReason on the api_req_started message, saving to disk — lives in abortStream() (Task.ts:2960) and is already independent of ending the task.
- Sketch: a request-scoped interrupt that reuses
abortStream() finalization without the abandoned/interrupted side effects, then delivers the message via the existing submitUserMessage() (Task.ts:1641) / queue drain, and lets the main loop (Task.ts:2709) continue. The loop treats this.abort as the only stop condition and discriminates abort errors by message suffix (Task.ts:411–425), so an interrupted request must surface to the loop as a normal completed turn, not an error.
ClineApiReqCancelReason is "streaming_failed" | "user_cancelled" (packages/types/src/vscode-extension-host.ts:930); a third value would let telemetry/cost UI distinguish "interrupt & send" from Stop.
This issue was prepared with agentic AI assistance; every code reference was verified against the current codebase.
Problem (one or two sentences)
While a task is running, every message I send is queued and only read later — sometimes minutes later, after the model has finished thinking in a direction my message already overrode. The only way to make the model stop right now is the Stop button, but Stop ends the task (status: "interrupted") and does not send any message.
Context (who is affected and when)
Anyone who steers a task mid-flight: "actually, skip that and do X", correcting a wrong turn, or answering what the model is currently reasoning about. Today the cost is the model's entire remaining generation — minutes on long thinking/tool chains — plus the tokens spent on work the user already decided against. The longer the model runs, the more this bites.
Desired behavior (conceptual, not technical)
A new action — call it Interrupt & send:
Constraints / preferences
Request checklist
Acceptance criteria
Proposed approach (plain language)
Stop already has the machinery to abort an in-flight model request while preserving the partial response and its cost; today that machinery is coupled to "end the task and mark it interrupted". A variant that interrupts only the current request, hands my message to the model, and resumes the normal task loop would close the gap. The message queue already exists (edit/delete, drain at prompts), so after the interrupt the feature just needs to deliver my message (and possibly the queue) into the resumed loop.
Trade-offs / risks
Open questions for the community
Related work — and how this differs
interrupt) inside [ENHANCEMENT] Add queue and steer delivery modes for running conversations #133's design, or kept as a standalone feature building on the same queue infrastructure.Technical context (for maintainers — verified against current code, optional reading)
queueMessagewhenever the task is streaming/blocked (webview-ui/src/components/chat/ChatView.tsx:659–677);queueMessage→webviewMessageHandler.ts:3753→Task.messageQueueService(src/core/message-queue/MessageQueueService.ts— in-memory, not persisted, cleared on task dispose; rendered bywebview-ui/src/components/chat/QueuedMessages.tsx).src/core/task/Task.ts:1299–1503,command_outputexcluded), after context condensing (Task.ts:1816), and viaprocessQueuedMessages()(Task.ts:5105). Nothing drains it while the model is purely generating — that is the gap.ClineProvider.cancelTask()(src/core/webview/ClineProvider.ts:3497) callsTask.cancelCurrentRequest()(aborts the in-flightAbortController,Task.ts:2448) andTask.abortTask()(Task.ts:2467), which marks the task abandoned and persists history as "interrupted". The partial-turn finalization — finalizing the partial message, recording cost +cancelReasonon theapi_req_startedmessage, saving to disk — lives inabortStream()(Task.ts:2960) and is already independent of ending the task.abortStream()finalization without theabandoned/interruptedside effects, then delivers the message via the existingsubmitUserMessage()(Task.ts:1641) / queue drain, and lets the main loop (Task.ts:2709) continue. The loop treatsthis.abortas the only stop condition and discriminates abort errors by message suffix (Task.ts:411–425), so an interrupted request must surface to the loop as a normal completed turn, not an error.ClineApiReqCancelReasonis"streaming_failed" | "user_cancelled"(packages/types/src/vscode-extension-host.ts:930); a third value would let telemetry/cost UI distinguish "interrupt & send" from Stop.This issue was prepared with agentic AI assistance; every code reference was verified against the current codebase.