Skip to content

fix(vscode): queue or restore prompts bounced by a busy session - #2818

Open
matthiasgoergens wants to merge 2 commits into
MoonshotAI:mainfrom
matthiasgoergens:fix/vscode-busy-bounce-queue
Open

fix(vscode): queue or restore prompts bounced by a busy session#2818
matthiasgoergens wants to merge 2 commits into
MoonshotAI:mainfrom
matthiasgoergens:fix/vscode-busy-bounce-queue

Conversation

@matthiasgoergens

@matthiasgoergens matthiasgoergens commented Aug 11, 2026

Copy link
Copy Markdown

Related Issue

Resolve #2817

(Stacked on #2798 — shares the turn.agent_busy busy-rejection path.)

Problem

See linked issue. loadSession resets isStreaming: false even when the session has a live turn, so after a window reload/reattach the composer takes the send path for a session that is actually busy. The runtime rejects the send (turn.agent_busy, non-terminal), and the text is parked in pendingInput with no TurnBegin — invisible limbo until the other turn's terminal event finally restores it. The bridge result was ignored, so the webview couldn't distinguish "bounced, never started" from "turn ended".

What changed

  • SessionRuntime's busy rejection now returns PromptResult.reason: "busy", and streamChat maps it to { done, bounced: true } on the bridge.
  • The webview store tracks awaitingTurnBegin (set on send, cleared by the send's own TurnBegin) and now handles the bridge result:
    • bounced while a live turn owns the session → the message moves into the send queue and streaming state is kept, so it sends when the running turn's terminal event flushes the queue, and further input enqueues as usual;
    • bounced but the live turn's stream_complete landed first (the reply arrived after the terminal event cleared the parked input) → the session is free, so the prompt is simply sent again;
    • any other send that fails before its TurnBegin → the text rolls back into the composer immediately instead of parking;
    • bounce during an exclusive operation (terminal rejection surfaced via an inline error) → left to the existing error path.
  • Tests: bounce → enqueued with streaming state kept; bounce after the turn's terminal event → resent; non-busy failure → composer restored; started turn → untouched by the late bridge resolution. Busy-path assertions updated for the new reason field.

Checklist

  • I have read the CONTRIBUTING document.
  • I have linked a related issue, or explained the problem above.
  • I have added tests that prove my fix works.
  • Ran gen-changesets skill, or this PR needs no changeset. (changeset included: kimi-code patch)
  • Ran gen-docs skill, or this PR needs no doc update.

…l error

The busy rejections in SessionRuntime threw a plain Error, which
emitError maps to code "internal" and the webview renders as
"Internal error occurred." — alarming and wrong for what is simply
"a response is already being generated". Throw KimiError with the
existing turn.agent_busy code instead so the UI shows the mapped
"A message is being sent. Please wait." with the real detail attached.

Fixes MoonshotAI#2796
@changeset-bot

changeset-bot Bot commented Aug 11, 2026

Copy link
Copy Markdown

🦋 Changeset detected

Latest commit: dd09e7d

The changes in this PR will be included in the next version bump.

This PR includes changesets to release 1 package
Name Type
kimi-code Patch

Not sure what this means? Click here to learn what changesets are.

Click here if you're a maintainer who wants to add another changeset to this PR

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 1bdcf39ff8

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

if (event.type === "TurnBegin" || event.type === "StepBegin" || event.type === "ContentPart") {
clearHandshakeTimer();
set({ handshakeReceived: true });
set({ handshakeReceived: true, ...(event.type === "TurnBegin" ? { awaitingTurnBegin: false } : {}) });

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge Correlate TurnBegin before clearing the pending send

When the same session is open in two Webviews and both send nearly simultaneously, the winning request's TurnBegin is broadcast to every subscriber by SessionRuntime.emitStreamEvent. This unconditionally clears awaitingTurnBegin in the losing view even though its own request never began; when that request resolves with { bounced: true }, doSend returns early, and the winner's later stream_complete clears pendingInput, silently discarding the losing prompt instead of queueing it. Track the specific pending request/turn rather than treating every TurnBegin as belonging to this send.

Useful? React with 👍 / 👎.

A prompt sent while the session was busy with a turn the webview lost
track of (isStreaming resets to false on loadSession even when the
session has a live turn — e.g. after a window reload) was rejected with
a busy warning, and the text sat parked in pendingInput — invisible,
not queued — until the other turn's terminal event finally restored it.

The runtime now marks the busy rejection with reason "busy" and the
bridge result carries bounced: true, so the webview can tell "this send
never started a turn" apart from a normal turn end. On a bounce the
message moves into the send queue (flushed by the live turn's terminal
event) and the composer keeps streaming state; any other send that
fails before its TurnBegin rolls the text straight back into the
composer. A bounce during an exclusive operation (terminal rejection,
no later terminal event) is left to the error path as before.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[VSCode] Prompt sent during an untracked live turn vanishes until the turn ends

1 participant