agentHost: run the agent host on the remote server - #329706
Draft
Connor Peet (connor4312) wants to merge 1 commit into
Draft
agentHost: run the agent host on the remote server#329706Connor Peet (connor4312) wants to merge 1 commit into
Connor Peet (connor4312) wants to merge 1 commit into
Conversation
The agent-host harness never appeared in the session-type picker when connected to a remote (Codespaces, Remote-SSH, Dev Containers, WSL). Two independent gaps caused this. 1. The REH server only spawned an agent host when launched with `--agent-host-port` / `--agent-host-path`, and only bridged renderers when `--agent-host-bridge-*` was set. The Rust CLI's `code tunnel` sidecar is the only caller that passes those flags, so every other remote registered `UnavailableAgentHostChannel`: the renderer's connect rejected, root state never arrived, and no agent chat session types were registered. The server now self-provisions an agent host when no flags are given, listening on a per-server random socket path guarded by a random connection token. It starts lazily on the first renderer connect, so servers where chat is never used pay nothing and remote auto-shutdown idle timers are unaffected. Startup only reports success once the agent host confirms its listener is bound, so the first connect can't race the socket into existence. 2. The agent host chat contributions were registered only from the desktop workbench, even though the web workbench already wired up `EditorRemoteAgentHostServiceClient` and enables the agent host whenever a remote authority is present. They now live in the browser layer and are shared by web and desktop, which is what makes the Codespaces browser client work. Gating is unchanged: the agent host is offered only when a real server-backed remote exists, so serverless web stays dark. Also stop forwarding the client-resolved default shell to a remote agent host. Sending a client's shell path to a server of a different platform is wrong; the agent host resolves its own shell when the key is absent. Fixes #326125 Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Contributor
There was a problem hiding this comment.
Pull request overview
Enables agent-host sessions on remote VS Code servers and web clients.
Changes:
- Lazily provisions and proxies a remote agent host.
- Shares agent-host contributions across desktop and web.
- Adds readiness, restart, endpoint, and remote-shell tests.
Show a summary per file
| File | Description |
|---|---|
src/vs/workbench/workbench.common.main.ts |
Loads shared agent-host registrations. |
src/vs/workbench/contrib/chat/test/browser/agentSessions/agentHostTerminalContribution.test.ts |
Tests remote shell exclusion. |
src/vs/workbench/contrib/chat/electron-browser/chat.contribution.ts |
Removes desktop-only registrations. |
src/vs/workbench/contrib/chat/browser/agentSessions/agentHost/agentHostTerminalContribution.ts |
Avoids forwarding client shells remotely. |
src/vs/workbench/contrib/chat/browser/agentSessions/agentHost/agentHost.contribution.ts |
Centralizes browser-safe registrations. |
src/vs/server/test/node/serverAgentHostManager.test.ts |
Covers lazy startup and restarts. |
src/vs/server/test/node/agentHostChannel.test.ts |
Covers deferred endpoint resolution. |
src/vs/server/node/serverServices.ts |
Provisions the default remote agent host. |
src/vs/server/node/serverAgentHostManager.ts |
Adds lazy startup and listener readiness. |
src/vs/server/node/agentHostChannel.ts |
Supports lazy endpoint resolution. |
src/vs/platform/agentHost/node/agentHostMain.ts |
Reports configured listener readiness. |
src/vs/platform/agentHost/common/agentService.ts |
Exposes the readiness method. |
Review details
Tip
Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.
- Files reviewed: 12/12 changed files
- Comments generated: 5
- Review effort level: Balanced
Comment on lines
+168
to
+169
| this._startPromise = undefined; | ||
| void this.ensureStarted().catch(() => undefined); |
Comment on lines
123
to
+125
| connection.store.dispose(); | ||
| this._start(); | ||
| this._startPromise = undefined; | ||
| void this.ensureStarted().catch(() => undefined); |
Comment on lines
+388
to
+396
| const endpointPromise = this._endpointPromise ??= Promise.resolve().then(() => endpoint()); | ||
| try { | ||
| return await endpointPromise; | ||
| } catch (error) { | ||
| if (this._endpointPromise === endpointPromise) { | ||
| this._endpointPromise = undefined; | ||
| } | ||
| throw error; | ||
| } |
| import './agentHostSettings.contribution.js'; | ||
| import './agentSessionSettings.contribution.js'; | ||
|
|
||
| registerWorkbenchContribution2(AgentHostContribution.ID, AgentHostContribution, WorkbenchPhase.AfterRestored); |
Comment on lines
+341
to
+345
| disposables.add(toDisposable(() => { | ||
| if (process.platform !== 'win32') { | ||
| void fs.promises.unlink(socketPath).catch(() => undefined); | ||
| } | ||
| })); |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Fixes #326125
The agent-host harness never appeared in the session-type picker when connected to a remote — Codespaces (both the web client and Desktop-connected), Remote-SSH, Dev Containers and WSL. Only the "Local" harness showed up.
Most of the plumbing already existed: the renderer picks
EditorRemoteAgentHostServiceClientwhen a remote authority is set, speaks AHP over the remote-agent IPC channel, the server has anagentHostProxychannel and aServerAgentHostManager, and the REH build already ships the agent host entrypoint and the@github/copilotruntime. Two independent gaps meant none of it was ever reachable.1. The server never provisioned an agent host
serverServices.tsonly spawned an agent host given--agent-host-port/--agent-host-path, and only registered a working bridge given--agent-host-bridge-*. The only caller that passes those flags is the Rust CLI'scode tunnelagent-host sidecar. Codespaces, Remote-SSH, Dev Containers and WSL all launchvscode-serverwithout them, so the server registeredUnavailableAgentHostChannel, the renderer'sconnect()rejected,rootStatenever arrived, andAgentHostContributionregistered zero chat session types.The wiring is now three branches. The two existing flag paths are behaviourally unchanged; a new default branch provisions an agent host itself:
createRandomIPCHandle()(handles Windows named pipes and the macOS/Linux socket path length limits), guarded by a random connection token, unlinked on shutdown.agentHostProxy, so servers where chat is never used pay no process or memory cost and remote auto-shutdown idle timers are unaffected. The existing--agent-host-portpath stays eager, because the CLI parses the readiness line from stdout at startup.ensureStarted()previously would have resolved as soon as the child-process IPC client was up, while the agent host binds its WebSocket listener asynchronously later — the first connect could hit an unbound socket and reproduce the exact symptom being fixed. Startup now waits for the agent host to confirm the listener is bound, and a failed listener startup rejects rather than hanging.Crash-restart, the restart budget, process telemetry and the
IServerLifetimeServicelifetime tokens behave as before in both modes.2. Web never registered the agent host chat contributions
AgentHostContributionand friends were registered only fromcontrib/chat/electron-browser/chat.contribution.ts, which only the desktop workbench imports — even thoughworkbench.web.main.tsalready registersEditorRemoteAgentHostServiceClientandWebAgentHostEnablementServiceexplicitly enables the agent host whenever a remote authority is present. So web had a fully wired client with no consumer, and fixing the server alone would not have fixed the Codespaces browser client.These registrations move to a new
agentSessions/agentHost/agentHost.contribution.tsin the browser layer, imported fromworkbench.common.main.tsso web and desktop share one path.Gating is unchanged. The agent host is offered only when a real server-backed remote exists:
WebAgentHostEnablementServicekeys offremoteAuthority, so serverless web (vscode.dev, Remote Repositories, virtual-filesystem workspaces) stays dark. When a remote is server-backed but can't provide an agent host — e.g. an older server — root state never arrives and nothing is registered, so the harness silently doesn't appear rather than showing a broken entry.Also
Stop forwarding the client-resolved default shell to a remote agent host. Forwarding a Windows client's shell path to a Linux server is wrong; with the key absent the agent host resolves its own shell server-side (and validates executability before falling back).
Validation
npm run typecheck-client✅npm run valid-layers-check✅scripts/test.shonagentHostChannel,serverAgentHostManager(21 passing),agentHostTerminalContribution(21 passing),agentHostChatContribution(291 passing) ✅New regression coverage: lazy start defers spawn, concurrent starts share one spawn, restart-after-crash, waiting for listener readiness, disposing mid-startup doesn't orphan the process, a fresh start is allowed after the crash budget is exhausted, and deferred endpoint resolution is shared/retryable.
Not yet exercised end-to-end against a live Codespace / Remote-SSH host — that's the main thing to verify before this comes out of draft.
Out of scope
#327339 / #325803 (Remote Repositories): a desktop window over a virtual filesystem has no remote authority, so it takes the local agent host path and the harness is shown but can't see
vscode-vfs://files. Different root cause, needs theSessionFsProviderwork.