Skip to content

agentHost: run the agent host on the remote server - #329706

Draft
Connor Peet (connor4312) wants to merge 1 commit into
mainfrom
connor/remote-server-agent-host
Draft

agentHost: run the agent host on the remote server#329706
Connor Peet (connor4312) wants to merge 1 commit into
mainfrom
connor/remote-server-agent-host

Conversation

@connor4312

Copy link
Copy Markdown
Member

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 EditorRemoteAgentHostServiceClient when a remote authority is set, speaks AHP over the remote-agent IPC channel, the server has an agentHostProxy channel and a ServerAgentHostManager, and the REH build already ships the agent host entrypoint and the @github/copilot runtime. Two independent gaps meant none of it was ever reachable.

1. The server never provisioned an agent host

serverServices.ts only 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's code tunnel agent-host sidecar. Codespaces, Remote-SSH, Dev Containers and WSL all launch vscode-server without them, so the server registered UnavailableAgentHostChannel, the renderer's connect() rejected, rootState never arrived, and AgentHostContribution registered 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:

  • Socket-based. A per-server path from createRandomIPCHandle() (handles Windows named pipes and the macOS/Linux socket path length limits), guarded by a random connection token, unlinked on shutdown.
  • Lazy. Nothing spawns until the first renderer actually connects to 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-port path stays eager, because the CLI parses the readiness line from stdout at startup.
  • Race-free. 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 IServerLifetimeService lifetime tokens behave as before in both modes.

2. Web never registered the agent host chat contributions

AgentHostContribution and friends were registered only from contrib/chat/electron-browser/chat.contribution.ts, which only the desktop workbench imports — even though workbench.web.main.ts already registers EditorRemoteAgentHostServiceClient and WebAgentHostEnablementService explicitly 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.ts in the browser layer, imported from workbench.common.main.ts so web and desktop share one path.

Gating is unchanged. The agent host is offered only when a real server-backed remote exists: WebAgentHostEnablementService keys off remoteAuthority, 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.sh on agentHostChannel, 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 the SessionFsProvider work.

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>
Copilot AI balanced review requested due to automatic review settings August 7, 2026 22:02

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

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);
}
}));
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.

Copilot harness not available on github codespace

2 participants