Conversation
Co-authored-by: Copilot <[email protected]>
Co-authored-by: Copilot <[email protected]>
Co-authored-by: Copilot <[email protected]>
Co-authored-by: Copilot <[email protected]>
Co-authored-by: Copilot <[email protected]>
Co-authored-by: Copilot <[email protected]>
Co-authored-by: Copilot <[email protected]>
Co-authored-by: Copilot <[email protected]>
There was a problem hiding this comment.
Pull request overview
This PR introduces a proposed chat sessions API for extensions to inject system-initiated “notification-style” turns into chat sessions they own, and wires it up for Copilot CLI sessions so async SDK/terminal completions can surface as new chat bubbles.
Changes:
- Add
vscode.chat.sendSystemInitiatedRequest(...)proposed API + options type. - Implement ext host ↔ main thread plumbing to enqueue a steering, system-initiated chat request with a label.
- Update Copilot CLI chat sessions to forward SDK
system.notificationevents into the chat panel and keep SDK sessions alive long enough for async completions to arrive.
Show a summary per file
| File | Description |
|---|---|
| src/vscode-dts/vscode.proposed.chatSessionsProvider.d.ts | Adds proposed API surface for system-initiated chat requests. |
| src/vs/workbench/api/common/extHostChatSessions.ts | Validates ownership + forwards requests to main thread via RPC. |
| src/vs/workbench/api/common/extHost.protocol.ts | Extends main-thread chat sessions shape with $sendSystemInitiatedRequest. |
| src/vs/workbench/api/common/extHost.api.impl.ts | Exposes chat.sendSystemInitiatedRequest to extensions under proposed API gate. |
| src/vs/workbench/api/browser/mainThreadChatSessions.ts | Receives RPC and enqueues a steering system-initiated request in IChatService. |
| extensions/copilot/src/extension/chatSessions/vscode-node/copilotCLIChatSessions.ts | Forwards CLI SDK system notifications into chat via the new API; passes through isSystemInitiated. |
| extensions/copilot/src/extension/chatSessions/copilotcli/node/test/testHelpers.ts | Updates mock SDK session helper with an on(...) method to match usage. |
| extensions/copilot/src/extension/chatSessions/copilotcli/node/test/copilotcliSession.spec.ts | Extends test mock with getBackgroundTasks() to satisfy new logging/inspection. |
| extensions/copilot/src/extension/chatSessions/copilotcli/node/copilotcliSessionService.ts | Emits onDidReceiveSystemNotification and adds a post-turn keep-alive ref window. |
| extensions/copilot/src/extension/chatSessions/copilotcli/node/copilotcliSession.ts | Translates SDK system.notification to a typed event; adds system-initiated request handling path. |
Copilot's findings
Comments suppressed due to low confidence (1)
extensions/copilot/src/extension/chatSessions/copilotcli/node/copilotcliSession.ts:357
- There is new behavior for
request.isSystemInitiatedthat changes how requests are handled (skipping_sdkSession.send()and waiting for the nextassistant.turn_end). The existing unit tests forCopilotCLISessiondon't cover theisSystemInitiatedpath; please add a test that asserts the SDKsend()is not called and that the handler resolves onassistant.turn_end/ cancellation.
}
if (isAlreadyBusyWithAnotherRequest) {
return this._handleRequestSteering(input, attachments, model, previousRequestSnapshot, token);
} else {
return this._handleRequestImpl(request, input, attachments, model, token, /*isSystemInitiated*/ false);
}
});
- Files reviewed: 9/10 changed files
- Comments generated: 5
|
@DonJayamanne @rebornix @mattbierner Don't think vscode/src/vscode-dts/vscode.proposed.chatSessionsProvider.d.ts Lines 446 to 453 in ad4cb84 I think the biggest thing against VS. (This PR with new proposed api): The approach from the PR we'd have no stalling shimmer, but would have new compact "system" bubble appear for async output such as
|
Co-authored-by: Copilot <[email protected]>
Co-authored-by: Copilot <[email protected]>
…path Co-authored-by: Copilot <[email protected]>
|
@roblourens @TylerLeonhardt Screen.Recording.2026-04-23.at.2.22.09.PM.movHere is equivalent in Copilot CLI. We could improve to better distinguish the async terminal output but it seems like CLI themselves are not doing explicit labeling either. Maybe the color of the decorations are meant to signify something? /cc @justschen For chat ui |
Co-authored-by: Copilot <[email protected]>
| // task list only includes *detached* shells and background agents, not | ||
| // plain `mode: "async"` shells (they're tracked internally by | ||
| // `shellContext.currentExecutions`). A status-based window matches the | ||
| // existing `sessionTerminators` 5-minute lifetime and covers every |
There was a problem hiding this comment.
@DonJayamanne @roblourens I'm not so sure about this 5-minute lifetime thing. I can see it was existing previously but don't have context over what specific thing it was meant for, maybe we can do something better here for async terminals and keeping session alive.
Co-authored-by: Copilot <[email protected]>
| this.sessionId = _sdkSession.sessionId; | ||
| this.add(toDisposable(() => this._todoSqlQuery.dispose())); | ||
|
|
||
| // Forward SDK system notifications (async/detached shell completions, |
There was a problem hiding this comment.
I'll probably delete this before final review but thought it would be helpful for earlier people who read, understand more carefully.
| const disposables = new DisposableStore(); | ||
| try { | ||
| await new Promise<void>(resolve => { | ||
| const off = this._sdkSession.on('assistant.turn_end', () => resolve()); |
There was a problem hiding this comment.
what happens if the next turn also starts an async terminal call?
| try { | ||
| const notification = this._buildSystemNotification(event); | ||
| if (notification) { | ||
| this._onDidReceiveSystemNotification.fire(notification); |
There was a problem hiding this comment.
What happens if the async terminal completes within the current turn?
I think with this approach we will always end up going out of there and sending a message to core and then re-enter as another request handler when it could've been handled in a single turn.
| session.add(toDisposable(releaseKeepAlive)); | ||
| session.add(session.onDidChangeStatus(() => { | ||
| const status = session.status; | ||
| if (session.permissionRequested) { |
There was a problem hiding this comment.
Please do not use permissionRequested, this will be removed.
| keepAliveTimer.clear(); | ||
| return; | ||
| } | ||
| if (status === undefined || status === ChatSessionStatus.Completed || status === ChatSessionStatus.Failed) { |
There was a problem hiding this comment.
Doesn't this mean that ALL sessions will always keep a ref to this object and never release it?
| * Rendered as a compact system-notification chip in the chat transcript | ||
| * (e.g. ``"`sleep 10` completed"``). | ||
| */ | ||
| readonly systemInitiatedLabel: string; |
There was a problem hiding this comment.
perhaps prompt is a better property
If its a request thats being sent, then i would like to think the message thats sent as part of the request would be a prompt or command, & this case it should be a prompt.

Resolves: #309290
Both route with/without controller api should work.