Skip to content

[ENHANCEMENT] Interrupt and send: stop the current model turn and read my message immediately #1518

Description

@easonLiangWorldedtech

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:

  1. 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).
  2. 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).
  3. The task does NOT end: it stays active, the model immediately reads my message, and the same task continues based on it.
  4. The plain Stop button keeps working exactly as it does today.
  5. 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

  • I've searched existing Issues and Discussions for duplicates
  • This describes a specific problem with clear context and impact

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

  1. Where is it "safe" to interrupt — only while the model is generating, or also between tool calls?
  2. After the interrupt, what gets delivered: only my message, or mine plus the existing queue (in order)?
  3. UI: dedicated button, modifier key, or per-queued-message action?
  4. 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?
  5. 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); queueMessagewebviewMessageHandler.ts:3753Task.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.

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions