Skip to content

chat: true mid-turn steering for cloud sessions#325368

Merged
osortega merged 3 commits into
mainfrom
osortega/fix/cloud-session-steering-queue-flush
Jul 14, 2026
Merged

chat: true mid-turn steering for cloud sessions#325368
osortega merged 3 commits into
mainfrom
osortega/fix/cloud-session-steering-queue-flush

Conversation

@osortega

@osortega osortega commented Jul 10, 2026

Copy link
Copy Markdown
Contributor

Problem

When steering a Copilot cloud chat session mid-turn, the message was stranded: it showed a "STEERING" pending bubble that only sent after the turn finished (queue behavior), rather than injecting into the running turn the way github.com/copilot/agents does.

Scope

This PR now delivers true mid-turn steering for cloud sessions, in three layers:

  1. Queue-flush fix — a message queued during a streamed (activeResponseCallback) turn is flushed when the turn completes (was previously stranded forever).
  2. Immediate mid-turn dispatch (core) — a Steering message is dispatched to the participant immediately instead of waiting for completion, so the provider can POST the steer server-side while the turn runs.
  3. Provider injection + continuous rendering — the cloud provider POSTs /tasks/{id}/steer and the running stream renders the injected result, matching github.com/copilot/agents.

Core (chatServiceImpl)

For non-server-managed streamed sessions:

  • Dispatch a queued Steering message immediately, but only when the in-flight pending request is the synthetic streamed-turn tracker (identified via an explicit WeakSet, since real requests are inserted into _pendingRequests before their requestId is assigned) — never a real in-flight request.
  • Restore in-progress tracking deterministically when the dispatched request settles (via responseCompletePromise), so requestInProgress stays true for the still-active stream.
  • Non-steering Queued messages are unaffected and still flush on completion.

Extension (copilotCloudSessionsProvider)

  • Track live TaskTurnStreamer task ids; when a stream is already active, handleTaskFollowUp only POSTs the steer (no second streamer).
  • The follow-up stream is yield-aware (mirrors the Copilot CLI provider): it polls context.yieldRequested and cancels only its local streamer on yield, so the chat service flushes the next steer immediately.
  • Render mode is chosen from the pre-steer state: a steer into a genuinely active turn (both task and latest turn active) renders mode:'current' (the injection produces no new task.sessions[] row, so mode:'next' would time out); a steer onto a settled task renders mode:'next'.

Testing

Unit tests in chatService.test.ts:

  • Immediate dispatch — a steering message on a streamed session is dispatched exactly once, mid-turn (stream not complete), preserves in-progress tracking (a plain send is rejected), and is not re-dispatched on completion.
  • Completion flush — a non-steering Queued message waits during the turn and is flushed when it completes.

Verified end-to-end against a live cloud task: the steer POSTs within ~100ms and the injected message + continued output render inline. typecheck-client and the copilot extension typecheck are clean.

Co-authored-by: Copilot 223556219+Copilot@users.noreply.github.com

Copilot AI review requested due to automatic review settings July 10, 2026 21:30

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Pull request overview

Fixes queued steering messages becoming stranded after streamed contributed-session turns complete.

Changes:

  • Flushes pending requests after streamed-response completion.
  • Adds regression coverage for queued steering messages.
Show a summary per file
File Description
chatServiceImpl.ts Processes queued requests after stream completion.
chatService.test.ts Tests steering-message delivery after completion.

Review details

  • Files reviewed: 2/2 changed files
  • Comments generated: 3
  • Review effort level: Medium

Comment thread src/vs/workbench/contrib/chat/common/chatService/chatServiceImpl.ts Outdated
Comment thread src/vs/workbench/contrib/chat/test/common/chatService/chatService.test.ts Outdated
Comment thread src/vs/workbench/contrib/chat/test/common/chatService/chatService.test.ts Outdated
@osortega
osortega force-pushed the osortega/fix/cloud-session-steering-queue-flush branch from 5c7b4c6 to 23d50e5 Compare July 10, 2026 21:39
…pletes

Contributed chat sessions whose in-progress turn is streamed via
activeResponseCallback (progressObs) — e.g. Copilot cloud sessions —
register a synthetic pending request while streaming. A message sent
mid-turn is therefore queued as a steering/queued message. The
streaming-completion branch in loadRemoteSession deleted that pending
request without flushing the queue, so the queued message was stranded
in the model forever and never sent (steering 'limbo').

Mirror the normal request-completion path by calling
processPendingRequests when the streamed turn completes. It is a no-op
while another request is in flight and returns early for server-managed
(agent-host) queues, so it is safe on every completion tick.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
@osortega
osortega force-pushed the osortega/fix/cloud-session-steering-queue-flush branch from 23d50e5 to a556676 Compare July 10, 2026 21:51
Builds on the queued-message flush fix to inject a steer into a live
cloud turn instead of only sending it after the turn settles, matching
github.com/copilot/agents.

Core (chatServiceImpl): for non-server-managed streamed
(activeResponseCallback) sessions, dispatch a queued Steering message
immediately when the in-flight pending request is the synthetic
streamed-turn tracker (no requestId) — never over a real in-flight
request. Re-establish tracking on the next progress tick so the turn
stays in-progress.

Extension (copilotCloudSessionsProvider):
- Track live TaskTurnStreamer taskIds; when a stream is already active,
  handleTaskFollowUp only POSTs /steer and lets the running stream
  render the injected result (no second streamer).
- Make the follow-up stream yield-aware (mirrors the Copilot CLI
  provider): poll context.yieldRequested and return on yield so the
  chat service flushes the next steer immediately.
- Choose the render mode from the pre-steer task state: a steer into an
  active turn renders mode:'current' (the injection has no new
  task.sessions[] row, so mode:'next' would time out); a steer onto a
  settled task renders mode:'next'.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
@osortega

Copy link
Copy Markdown
Contributor Author

Added a second commit that builds on the flush fix to implement true mid-turn steering for cloud sessions (previously the steer only sent after the turn settled — queue behavior — rather than injecting into the running turn like github.com/copilot/agents does).

Core (chatServiceImpl) — for non-server-managed streamed (activeResponseCallback) sessions, dispatch a queued Steering message immediately, but only when the in-flight pending request is the synthetic streamed-turn tracker (requestId === undefined), never over a real in-flight request (which it would dispose). Tracking is re-established on the next progress tick so the turn stays in-progress.

Extension (copilotCloudSessionsProvider):

  • Track live TaskTurnStreamer task ids; when a stream is already active, handleTaskFollowUp only POSTs /steer and lets the running stream render the injection (no second streamer).
  • Make the follow-up stream yield-aware, mirroring the Copilot CLI provider: poll context.yieldRequested and return on yield so the chat service flushes the next steer immediately (near-instant chaining instead of waiting ~60s for the turn to settle).
  • Choose the render mode from the pre-steer task state: a steer into an active turn renders mode:'current' (the injection produces no new task.sessions[] row, so mode:'next' would time out); a steer onto a settled task renders mode:'next'.

Verified end-to-end against a live cloud task: the steer POSTs within ~100ms of submitting, and the injected message + continued output render inline. typecheck-client and the copilot extension typecheck are clean.

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Review details

  • Files reviewed: 3/3 changed files
  • Comments generated: 12
  • Review effort level: Medium

Comment thread src/vs/workbench/contrib/chat/common/chatService/chatServiceImpl.ts Outdated
Comment thread src/vs/workbench/contrib/chat/common/chatService/chatServiceImpl.ts Outdated
Comment thread src/vs/workbench/contrib/chat/common/chatService/chatServiceImpl.ts Outdated
Comment thread src/vs/workbench/contrib/chat/common/chatService/chatServiceImpl.ts
Comment thread src/vs/workbench/contrib/chat/common/chatService/chatServiceImpl.ts Outdated
Comment thread src/vs/workbench/contrib/chat/common/chatService/chatServiceImpl.ts Outdated
…stale task state

- Identify the synthetic streamed-turn pending request via an explicit
  WeakSet instead of `requestId === undefined`: real requests are inserted
  into _pendingRequests before their requestId is assigned, so the old guard
  could delete a live request and start a concurrent invocation.
- Restore in-progress tracking deterministically when the immediate-steer
  dispatch settles (hook responseCompletePromise) instead of relying on a
  future progress tick that may never fire.
- Cloud provider: treat a follow-up as mid-turn (render mode:'current')
  only when BOTH the task and its latest turn are active, so a terminal
  task with a stale in_progress latest turn doesn't pick a streamer that
  exits before the steered turn appears.
- Rework the regression tests: the steering test now asserts immediate
  dispatch, no duplicate dispatch on completion, and preserved in-progress
  tracking; a separate test covers completion-flush of a non-steering
  queued message.
- Condense multi-line inline comments.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
@osortega osortega changed the title chat: flush queued/steering messages when a streamed session turn completes chat: true mid-turn steering for cloud sessions Jul 14, 2026
@osortega
osortega marked this pull request as ready for review July 14, 2026 00:56
@osortega
osortega enabled auto-merge (squash) July 14, 2026 00:59
@osortega
osortega merged commit 3eb10e1 into main Jul 14, 2026
29 checks passed
@osortega
osortega deleted the osortega/fix/cloud-session-steering-queue-flush branch July 14, 2026 06:04
@vs-code-engineering vs-code-engineering Bot added this to the 1.130.0 milestone Jul 14, 2026
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.

3 participants