Skip to content

fix(agent-core-v2): carry unmaterialized steer requests over to the next turn - #3445

Open
sailist wants to merge 1 commit into
MoonshotAI:mainfrom
sailist:bug-132-09-02-steer-abort-transfer
Open

fix(agent-core-v2): carry unmaterialized steer requests over to the next turn#3445
sailist wants to merge 1 commit into
MoonshotAI:mainfrom
sailist:bug-132-09-02-steer-abort-transfer

Conversation

@sailist

@sailist sailist commented Sep 2, 2026

Copy link
Copy Markdown
Collaborator

Related Issue

No linked issue — internal bug report (steer content silently lost when the turn is interrupted, with frame-by-frame video evidence).

Problem

When a prompt is steered (Ctrl+S) into the running turn, steer() returns success once the request is assigned to the turn's job queue, but the content only enters the model context at the next step boundary. If the turn is cancelled (ESC) or fails in that window, releaseActiveTurn cancels every queued step — including the not-yet-materialized steer request — and the content is silently lost, while the prompt record still settles as if delivered.

The code already marks steer requests as turnScoped: false, and the step loop's abortTurnScoped() deliberately spares non-turn-scoped requests — but releaseActiveTurn ignores that distinction and cancels everything, and there is no hand-off path out of a dead turn's queue.

What changed

  • releaseActiveTurn now honors turnScoped: before cancelling steps it drains the turn job queue and transfers pending non-turn-scoped requests to the standaloneStepQueue. The next turn picks them up through the existing moveStandaloneStepsTo channel and materializes them at its first step boundary with the usual logic.
  • cancelStep gains an abortRequest flag so a transferred step can be settled (cancelled, result resolved) without aborting its request, which stays pending for the next turn.
  • No contract changes: prompt completion behavior (timing and payload) is unchanged; the inject() path benefits from the same transfer for free.
  • Tests: three new loop cases — cancel transfers the request and materializes it in the next turn (driver before merged), turn-scoped requests still abort, turn failure also transfers.

Checklist

  • I have read the CONTRIBUTING document.
  • I have linked a related issue (external PRs: the issue must have a maintainer's /approve).
  • I have added tests that prove my feature works.
  • Ran gen-changesets skill, or this PR needs no changeset.
  • Ran gen-docs skill, or this PR needs no doc update.

@changeset-bot

changeset-bot Bot commented Sep 2, 2026

Copy link
Copy Markdown

🦋 Changeset detected

Latest commit: 01e18ed

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

This PR includes changesets to release 1 package
Name Type
@moonshot-ai/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

@pkg-pr-new

pkg-pr-new Bot commented Sep 2, 2026

Copy link
Copy Markdown
pnpm dlx https://pkg.pr.new/@moonshot-ai/kimi-code@01e18ed
npx https://pkg.pr.new/@moonshot-ai/kimi-code@01e18ed

commit: 01e18ed

@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: 01e18ed41a

ℹ️ About Codex in GitHub

Codex has been enabled to automatically 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 👍.

When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".

const transferred = new Map<string, StepRequest>();
for (const request of job.queue.drain()) {
if (request.state === 'pending' && !request.turnScoped) {
this.standaloneStepQueue.enqueue(request, 'tail');

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 Attach carryovers to an already queued next turn

When another newTurn request is already in pendingTurns, this moves the unmaterialized steer only to standaloneStepQueue; pumpTurns() then starts the pre-existing turn, whose queue was populated earlier in createPendingTurn(), without moving these standalone requests into it. If that queued turn is the last one, the Ctrl+S message remains pending indefinitely instead of reaching the next turn, and may be injected into an unrelated later prompt. Transfer these requests into the next pending job when one exists, or merge standalone requests when starting every turn.

Useful? React with 👍 / 👎.

Comment on lines +603 to +606
for (const request of job.queue.drain()) {
if (request.state === 'pending' && !request.turnScoped) {
this.standaloneStepQueue.enqueue(request, 'tail');
transferred.set(request.id, request);

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Abort carryovers when the loop is disposing

If dispose() is called while an active turn has an unmaterialized non-turn-scoped request, it aborts the turn and drains standaloneStepQueue before the asynchronous turn cleanup reaches this code. This cleanup then transfers the request into the already-drained queue even though disposing prevents any future turn, leaving the request permanently pending and making the disposed loop continue to report pending work. On the disposal path, abort the request rather than transferring it.

Useful? React with 👍 / 👎.

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.

1 participant