Remove outdated docs/instructions for remote agent host (#304341)

This commit is contained in:
Rob Lourens
2026-03-23 20:15:12 -07:00
committed by GitHub
parent c82bc515c2
commit efb9c8bab8
3 changed files with 0 additions and 1125 deletions

View File

@@ -1,35 +0,0 @@
---
description: Architecture documentation for remote agent host connections. Use when working in `src/vs/sessions/contrib/remoteAgentHost`
applyTo: src/vs/sessions/contrib/remoteAgentHost/**
---
# Remote Agent Host
The remote agent host feature connects the sessions app to agent host processes running on other machines over WebSocket.
## Key Files
- `ARCHITECTURE.md` - full architecture documentation (URI conventions, registration flow, data flow diagram)
- `REMOTE_AGENT_HOST_RECONNECTION.md` - reconnection lifecycle spec (15 numbered requirements)
- `browser/remoteAgentHost.contribution.ts` - central orchestrator
- `browser/agentHostFileSystemProvider.ts` - read-only FS provider for remote browsing
## Architecture Documentation
When making changes to this feature area, **review and update `ARCHITECTURE.md`** if your changes affect:
- Connection lifecycle (connect, disconnect, reconnect)
- Agent registration flow
- URI conventions or naming
- Session creation flow
- The data flow diagram
The doc lives at `src/vs/sessions/contrib/remoteAgentHost/ARCHITECTURE.md`.
## Related Code Outside This Folder
- `src/vs/platform/agentHost/common/remoteAgentHostService.ts` - service interface (`IRemoteAgentHostService`)
- `src/vs/platform/agentHost/electron-browser/remoteAgentHostServiceImpl.ts` - Electron implementation
- `src/vs/platform/agentHost/electron-browser/remoteAgentHostProtocolClient.ts` - WebSocket protocol client
- `src/vs/workbench/contrib/chat/browser/agentSessions/agentHost/agentHostSessionListController.ts` - session list sidebar
- `src/vs/workbench/contrib/chat/browser/agentSessions/agentHost/agentHostSessionHandler.ts` - session content provider

View File

@@ -1,762 +0,0 @@
# Remote Agent Host - Architecture Reference
This file describes the key types in the remote agent host system, from the
agent host process itself up through the sessions app integration layer.
The system has four layers:
1. **Agent host process** (`platform/agentHost/node/`)
The utility process that hosts agent backends (e.g. Copilot SDK).
Owns the authoritative state tree and dispatches to IAgent providers.
2. **Platform services** (`platform/agentHost/common/`, `electron-browser/`)
Service interfaces and IPC plumbing that expose the agent host to the
renderer. Local connections use MessagePort; remote ones use WebSocket.
3. **Workbench contributions** (`workbench/contrib/chat/browser/agentSessions/agentHost/`)
Shared UI adapters that bridge the agent host protocol with the chat UI:
session handlers, session list controllers, language model providers, and
state-to-progress adapters. Used by both local and remote agent hosts.
4. **Sessions app orchestrator** (`sessions/contrib/remoteAgentHost/`)
The contribution that discovers remote agent hosts, dynamically registers
them as chat session types, and provides the remote filesystem provider.
```
┌───────────────────────────────────────────────────────────────────────────┐
│ Sessions App (Layer 4) │
│ RemoteAgentHostContribution │
│ per-connection → SessionClientState + agent registrations │
│ AgentHostFileSystemProvider (agenthost:// scheme) │
├──────────────────────────────────────┬────────────────────────────────────┤
│ Workbench Contributions (3) │ Workbench Contributions (3) │
│ AgentHostContribution (local) │ (shared adapters) │
│ SessionClientState │ AgentHostSessionHandler │
│ per-agent registrations │ AgentHostSessionListController │
│ │ AgentHostLanguageModelProvider │
├──────────────────────────────────────┴────────────────────────────────────┤
│ Platform Services (Layer 2) │
│ IAgentHostService (local, MessagePort) │
│ IRemoteAgentHostService (remote, WebSocket) │
│ └─ both implement IAgentConnection │
├───────────────────────────────────────────────────────────────────────────┤
│ Agent Host Process (Layer 1) │
│ AgentService → SessionStateManager → IAgent (Copilot SDK) │
│ ProtocolServerHandler (WebSocket protocol bridge) │
│ AgentSideEffects (action dispatch + progress event routing) │
└───────────────────────────────────────────────────────────────────────────┘
```
```typescript
// =============================================================================
// LAYER 1: Agent Host Process (platform/agentHost/node/)
// =============================================================================
/**
* Implemented by each agent backend (e.g. the Copilot SDK wrapper).
* The agent host process can host multiple providers, though currently
* only `copilot` is supported.
*
* Registered with {@link AgentService.registerProvider}. Provider progress
* events are wired to the state manager through {@link AgentSideEffects}.
*
* File: `platform/agentHost/common/agentService.ts`
*/
interface IAgent {
/** Unique provider identifier (e.g. `'copilot'`). */
readonly id: AgentProvider;
/** Fires when the provider streams progress for a session. */
readonly onDidSessionProgress: Event<IAgentProgressEvent>;
createSession(config?: IAgentCreateSessionConfig): Promise<URI>;
sendMessage(session: URI, prompt: string, attachments?: IAgentAttachment[]): Promise<void>;
getSessionMessages(session: URI): Promise<IAgentProgressEvent[]>;
disposeSession(session: URI): Promise<void>;
abortSession(session: URI): Promise<void>;
changeModel(session: URI, model: string): Promise<void>;
respondToPermissionRequest(requestId: string, approved: boolean): void;
getDescriptor(): IAgentDescriptor;
listModels(): Promise<IAgentModelInfo[]>;
listSessions(): Promise<IAgentSessionMetadata[]>;
getProtectedResources(): IAuthorizationProtectedResourceMetadata[];
authenticate(resource: string, token: string): Promise<boolean>;
shutdown(): Promise<void>;
dispose(): void;
}
/**
* The agent service implementation that runs inside the agent host utility
* process. Dispatches to registered {@link IAgent} providers based on the
* provider identifier in the session URI scheme.
*
* Owns the {@link SessionStateManager} (authoritative state tree) and
* {@link AgentSideEffects} (action routing + progress event mapping).
*
* When `VSCODE_AGENT_HOST_PORT` is set, the process also starts a
* {@link ProtocolServerHandler} over WebSocket for external clients.
*
* File: `platform/agentHost/node/agentService.ts`
*/
interface AgentService extends IAgentService {
/** Exposes the state manager for co-hosting a WebSocket protocol server. */
readonly stateManager: SessionStateManager;
/** Register a new agent backend provider. */
registerProvider(provider: IAgent): void;
}
/**
* Server-side authoritative state manager for the sessions process protocol.
*
* Maintains the root state (agent list + active session count) and per-session
* state trees. Applies actions through pure reducers, assigns monotonic
* sequence numbers, and emits {@link IActionEnvelope}s for subscribed clients.
*
* Consumed by both the IPC proxy (for local clients) and
* {@link ProtocolServerHandler} (for WebSocket clients). Both paths share
* the same state, so local and remote clients see identical state.
*
* File: `platform/agentHost/node/sessionStateManager.ts`
*/
interface SessionStateManager {
readonly rootState: IRootState;
readonly serverSeq: number;
readonly onDidEmitEnvelope: Event<IActionEnvelope>;
readonly onDidEmitNotification: Event<INotification>;
getSessionState(session: string): ISessionState | undefined;
getSnapshot(resource: string): IStateSnapshot | undefined;
createSession(summary: ISessionSummary): ISessionState;
removeSession(session: string): void;
applyAction(action: IStateAction, origin: IActionOrigin): void;
}
/**
* Shared side-effect handler that routes client-dispatched actions to the
* correct {@link IAgent} backend, handles session create/dispose/list
* operations, and wires agent progress events to the state manager.
*
* Also implements {@link IProtocolSideEffectHandler} so the WebSocket
* {@link ProtocolServerHandler} can delegate side effects to the same logic.
*
* File: `platform/agentHost/node/agentSideEffects.ts`
*/
interface AgentSideEffects extends IProtocolSideEffectHandler {
/** Connects an IAgent's progress events to the state manager. */
registerProgressListener(provider: IAgent): IDisposable;
}
/**
* Server-side protocol handler for WebSocket clients. Routes JSON-RPC
* messages to the {@link SessionStateManager}, manages client subscriptions,
* and broadcasts action envelopes to subscribed clients.
*
* Handles the initialize/reconnect handshake, subscribe/unsubscribe,
* dispatchAction, createSession, disposeSession, and browseDirectory commands.
*
* Exposes {@link onDidChangeConnectionCount} so the server process can
* track how many external clients are connected (used by
* {@link ServerAgentHostManager} for lifetime management).
*
* File: `platform/agentHost/node/protocolServerHandler.ts`
*/
interface ProtocolServerHandler {
/** Fires with the current client count when a client connects or disconnects. */
readonly onDidChangeConnectionCount: Event<number>;
}
/**
* Side-effect handler interface for protocol commands that require
* business logic beyond pure state management. Implemented by
* {@link AgentSideEffects} and consumed by {@link ProtocolServerHandler}.
*
* File: `platform/agentHost/node/protocolServerHandler.ts`
*/
interface IProtocolSideEffectHandler {
handleAction(action: ISessionAction): void;
handleCreateSession(command: ICreateSessionParams): Promise<void>;
handleDisposeSession(session: string): void;
handleListSessions(): Promise<ISessionSummary[]>;
handleGetResourceMetadata(): IResourceMetadata;
handleAuthenticate(params: IAuthenticateParams): Promise<IAuthenticateResult>;
handleBrowseDirectory(uri: string): Promise<IBrowseDirectoryResult>;
getDefaultDirectory(): string;
}
/**
* Main-process service that manages the agent host utility process lifecycle:
* lazy start on first connection request, crash recovery (up to 5 restarts),
* and logger channel forwarding.
*
* The renderer communicates with the utility process directly via MessagePort;
* this class does not relay any agent service calls.
*
* File: `platform/agentHost/node/agentHostService.ts`
*/
interface AgentHostProcessManager {
// Internal lifecycle management - start, restart, logger forwarding.
}
/**
* Server-specific agent host manager. Eagerly starts the agent host process,
* handles crash recovery, and tracks both active agent sessions and connected
* WebSocket clients via {@link IServerLifetimeService} to keep the server
* alive while either signal is active.
*
* The lifetime token is held when:
* - there are active agent sessions (turns in progress), OR
* - there are WebSocket clients connected to the agent host
*
* The token is released (allowing server auto-shutdown) only when both
* active sessions = 0 AND connected clients = 0.
*
* Session count comes from `root/activeSessionsChanged` actions via
* {@link IAgentService.onDidAction}. Client connection count comes from
* a separate IPC channel ({@link AgentHostIpcChannels.ConnectionTracker})
* that is not part of the agent host protocol -- it is a server-only
* process-management concern.
*
* File: `server/node/serverAgentHostManager.ts`
*/
interface ServerAgentHostManager {
// Tracks _hasActiveSessions + _connectionCount, updates lifetime token
// when either changes.
}
/**
* Abstracts the utility process creation so the same lifecycle management
* works for both Electron utility processes and Node child processes.
*
* File: `platform/agentHost/common/agent.ts`
*/
interface IAgentHostStarter extends IDisposable {
readonly onRequestConnection?: Event<void>;
readonly onWillShutdown?: Event<void>;
/** Creates the agent host process and connects to it. */
start(): IAgentHostConnection;
}
/**
* The connection returned by {@link IAgentHostStarter.start}. Provides
* an IPC channel client and process exit events.
*
* File: `platform/agentHost/common/agent.ts`
*/
interface IAgentHostConnection {
readonly client: IChannelClient;
readonly store: DisposableStore;
readonly onDidProcessExit: Event<{ code: number; signal: string }>;
}
// =============================================================================
// LAYER 2: Platform Services (platform/agentHost/common/ & electron-browser/)
// =============================================================================
/**
* Core protocol surface for communicating with an agent host. Methods are
* proxied across MessagePort (local) or implemented over WebSocket (remote).
*
* State synchronization uses the subscribe/unsubscribe/dispatchAction pattern.
* Clients observe root state (discovered agents, models) and session state
* via subscriptions, and mutate state by dispatching actions (e.g.
* `session/turnStarted`, `session/turnCancelled`).
*
* File: `platform/agentHost/common/agentService.ts`
*/
interface IAgentService {
listAgents(): Promise<IAgentDescriptor[]>;
getResourceMetadata(): Promise<IResourceMetadata>;
authenticate(params: IAuthenticateParams): Promise<IAuthenticateResult>;
refreshModels(): Promise<void>;
listSessions(): Promise<IAgentSessionMetadata[]>;
createSession(config?: IAgentCreateSessionConfig): Promise<URI>;
disposeSession(session: URI): Promise<void>;
shutdown(): Promise<void>;
// ---- Protocol methods ----
subscribe(resource: URI): Promise<IStateSnapshot>;
unsubscribe(resource: URI): void;
readonly onDidAction: Event<IActionEnvelope>;
readonly onDidNotification: Event<INotification>;
dispatchAction(action: ISessionAction, clientId: string, clientSeq: number): void;
browseDirectory(uri: URI): Promise<IBrowseDirectoryResult>;
}
/**
* A concrete connection to an agent host - local utility process or remote
* WebSocket. Extends {@link IAgentService} with a `clientId` used for
* write-ahead reconciliation of optimistic actions.
*
* Both {@link IAgentHostService} (local) and per-connection objects from
* {@link IRemoteAgentHostService} (remote) satisfy this contract. The
* workbench contributions ({@link AgentHostSessionHandler}, etc.) program
* against this single interface.
*
* File: `platform/agentHost/common/agentService.ts`
*/
interface IAgentConnection extends IAgentService {
/** Unique client identifier, used as origin in action envelopes. */
readonly clientId: string;
}
/**
* The local agent host service - wraps the utility process connection and
* provides lifecycle events. The renderer talks to the utility process
* directly via MessagePort using ProxyChannel.
*
* Registered as a singleton service. Also implements {@link IAgentConnection}
* so it can be used interchangeably with remote connections.
*
* File: `platform/agentHost/common/agentService.ts` (interface)
* File: `platform/agentHost/electron-browser/agentHostService.ts` (implementation)
*/
interface IAgentHostService extends IAgentConnection {
readonly onAgentHostExit: Event<number>;
readonly onAgentHostStart: Event<void>;
restartAgentHost(): Promise<void>;
}
/**
* Manages connections to one or more remote agent host processes over
* WebSocket. Each connection is identified by its address string (from the
* `chat.remoteAgentHosts` setting) and exposed as an {@link IAgentConnection}.
*
* The implementation reads the setting, creates a
* {@link RemoteAgentHostProtocolClient} per address, reconnects when the
* setting changes, and fires `onDidChangeConnections` when connections are
* established or lost.
*
* File: `platform/agentHost/common/remoteAgentHostService.ts` (interface)
* File: `platform/agentHost/electron-browser/remoteAgentHostServiceImpl.ts` (implementation)
*/
interface IRemoteAgentHostService {
readonly onDidChangeConnections: Event<void>;
readonly connections: readonly IRemoteAgentHostConnectionInfo[];
getConnection(address: string): IAgentConnection | undefined;
}
/**
* Metadata about a single remote connection - address, friendly name,
* client ID from the handshake, and the remote machine's home directory.
*
* File: `platform/agentHost/common/remoteAgentHostService.ts`
*/
interface IRemoteAgentHostConnectionInfo {
readonly address: string;
readonly name: string;
readonly clientId: string;
readonly defaultDirectory?: string;
}
/**
* An entry in the `chat.remoteAgentHosts` setting.
*
* File: `platform/agentHost/common/remoteAgentHostService.ts`
*/
interface IRemoteAgentHostEntry {
readonly address: string;
readonly name: string;
readonly connectionToken?: string;
}
/**
* A protocol-level client for a single remote agent host connection.
* Manages the WebSocket transport, handshake (initialize command with
* protocol version exchange), subscriptions, action dispatch, and
* JSON-RPC request/response correlation.
*
* Implements {@link IAgentConnection} so consumers can program against
* a single interface regardless of whether the agent host is local or remote.
*
* File: `platform/agentHost/electron-browser/remoteAgentHostProtocolClient.ts`
*/
interface RemoteAgentHostProtocolClient extends IAgentConnection {
readonly defaultDirectory: string | undefined;
readonly onDidClose: Event<void>;
connect(): Promise<void>;
}
// =============================================================================
// LAYER 2: State Protocol Types (platform/agentHost/common/state/)
// =============================================================================
/**
* Root state: the top-level state tree subscribed to at `agenthost:/root`.
* Contains the list of discovered agent backends and active session count.
* Mutated by `root/agentsChanged` and `root/activeSessionsChanged` actions.
*
* File: `platform/agentHost/common/state/sessionState.ts` (re-exported from protocol)
*/
interface IRootState {
readonly agents: readonly IAgentInfo[];
readonly activeSessions: number;
}
/**
* Describes an agent backend discovered via root state subscription.
* Each agent exposes a provider name, display metadata, and available models.
*
* File: `platform/agentHost/common/state/sessionState.ts` (re-exported from protocol)
*/
interface IAgentInfo {
readonly provider: string;
readonly displayName: string;
readonly description: string;
readonly models: readonly ISessionModelInfo[];
}
/**
* Per-session state tree. Contains the session summary, lifecycle, completed
* turns, active turn (if any), and server tools. Mutated by session actions
* like `session/turnStarted`, `session/delta`, `session/toolCallStart`, etc.
*
* File: `platform/agentHost/common/state/sessionState.ts` (re-exported from protocol)
*/
interface ISessionState {
readonly summary: ISessionSummary;
readonly lifecycle: SessionLifecycle;
readonly turns: readonly ITurn[];
readonly activeTurn: IActiveTurn | undefined;
}
/**
* An envelope wrapping a state action with origin metadata and a monotonic
* server sequence number. Clients use the origin to distinguish their own
* echoed actions from concurrent actions from other clients/the server.
*
* File: `platform/agentHost/common/state/sessionActions.ts` (re-exported from protocol)
*/
interface IActionEnvelope {
readonly action: IStateAction;
readonly origin: IActionOrigin;
readonly serverSeq: number;
}
/**
* A state snapshot returned by the subscribe command. Contains the current
* state at the given resource URI and the server sequence number at
* snapshot time. The client should process subsequent envelopes with
* `serverSeq > fromSeq`.
*
* File: `platform/agentHost/common/state/sessionProtocol.ts` (re-exported from protocol)
*/
interface IStateSnapshot {
readonly resource: string;
readonly state: IRootState | ISessionState;
readonly fromSeq: number;
}
/**
* Client-side state manager with write-ahead reconciliation.
*
* Maintains confirmed state (last server-acknowledged), a pending action
* queue (optimistically applied), and reconciles when the server echoes
* actions back (possibly interleaved with actions from other sources).
* Operates on two kinds of subscribable state:
* - Root state (agents + models) - server-only mutations, no write-ahead.
* - Session state - mixed: client-sendable actions get write-ahead,
* server-only actions are applied directly.
*
* Usage:
* 1. `handleSnapshot()` - apply initial state from subscribe response.
* 2. `applyOptimistic()` - optimistically apply a client action.
* 3. `receiveEnvelope()` - process a server action envelope.
* 4. `receiveNotification()` - process an ephemeral notification.
*
* File: `platform/agentHost/common/state/sessionClientState.ts`
*/
interface SessionClientState {
readonly clientId: string;
readonly rootState: IRootState | undefined;
readonly onDidChangeRootState: Event<IRootState>;
readonly onDidChangeSessionState: Event<{ session: string; state: ISessionState }>;
readonly onDidReceiveNotification: Event<INotification>;
getSessionState(session: string): ISessionState | undefined;
handleSnapshot(resource: string, state: IRootState | ISessionState, fromSeq: number): void;
applyOptimistic(action: ISessionAction): number;
receiveEnvelope(envelope: IActionEnvelope): void;
receiveNotification(notification: INotification): void;
unsubscribe(resource: string): void;
}
/**
* A bidirectional transport for protocol messages (JSON-RPC 2.0 framing).
* Implementations handle serialization, framing, and connection management.
* Concrete implementations: MessagePort (ProxyChannel), WebSocket, stdio.
*
* File: `platform/agentHost/common/state/sessionTransport.ts`
*/
interface IProtocolTransport extends IDisposable {
readonly onMessage: Event<IProtocolMessage>;
readonly onClose: Event<void>;
send(message: IProtocolMessage): void;
}
/**
* Server-side transport that accepts multiple client connections.
* Each connected client gets its own {@link IProtocolTransport}.
*
* File: `platform/agentHost/common/state/sessionTransport.ts`
*/
interface IProtocolServer extends IDisposable {
readonly onConnection: Event<IProtocolTransport>;
readonly address: string | undefined;
}
// =============================================================================
// LAYER 3: Workbench Contributions (workbench/contrib/chat/browser/agentSessions/agentHost/)
// =============================================================================
/**
* Renderer-side handler for a single agent host chat session type.
* Bridges the protocol state layer with the chat UI:
*
* - Subscribes to session state via {@link IAgentConnection}
* - Derives `IChatProgress[]` from immutable state changes in
* {@link SessionClientState}
* - Dispatches client actions (`turnStarted`, `permissionResolved`,
* `turnCancelled`) back to the server
* - Registers a dynamic chat agent via {@link IChatAgentService}
*
* Works with both local and remote connections via the {@link IAgentConnection}
* interface passed in the config.
*
* File: `workbench/contrib/chat/browser/agentSessions/agentHost/agentHostSessionHandler.ts`
*/
interface AgentHostSessionHandler extends IChatSessionContentProvider {
provideChatSessionContent(sessionResource: URI, token: CancellationToken): Promise<IChatSession>;
}
/**
* Configuration for an {@link AgentHostSessionHandler} instance.
* Contains the agent identity, displayName, the connection to use,
* and optional callbacks for resolving working directories and
* interactive authentication.
*
* File: `workbench/contrib/chat/browser/agentSessions/agentHost/agentHostSessionHandler.ts`
*/
interface IAgentHostSessionHandlerConfig {
readonly provider: AgentProvider;
readonly agentId: string;
readonly sessionType: string;
readonly fullName: string;
readonly description: string;
readonly connection: IAgentConnection;
readonly extensionId?: string;
readonly extensionDisplayName?: string;
/** Resolve a working directory for a new session (e.g. from active session's repository URI). */
readonly resolveWorkingDirectory?: (resourceKey: string) => string | undefined;
/** Trigger interactive authentication when the server rejects with auth-required. */
readonly resolveAuthentication?: () => Promise<boolean>;
}
/**
* Provides session list items for the chat sessions sidebar by querying
* active sessions from an agent host connection. Listens to protocol
* notifications (`notify/sessionAdded`, `notify/sessionRemoved`) for
* incremental updates, and refreshes on `session/turnComplete` actions.
*
* Works with both local and remote agent host connections via
* {@link IAgentConnection}.
*
* File: `workbench/contrib/chat/browser/agentSessions/agentHost/agentHostSessionListController.ts`
*/
interface AgentHostSessionListController extends IChatSessionItemController {
readonly items: readonly IChatSessionItem[];
readonly onDidChangeChatSessionItems: Event<IChatSessionItemsDelta>;
refresh(token: CancellationToken): Promise<void>;
}
/**
* Exposes models available from the agent host process as selectable
* language models in the chat model picker. Models come from root state
* (via {@link IAgentInfo.models}) and are published with IDs prefixed
* by the session type (e.g. `remote-localhost__8081-copilot:claude-sonnet-4-20250514`).
*
* File: `workbench/contrib/chat/browser/agentSessions/agentHost/agentHostLanguageModelProvider.ts`
*/
interface AgentHostLanguageModelProvider extends ILanguageModelChatProvider {
/** Called when models change in root state to push updates to the model picker. */
updateModels(models: readonly ISessionModelInfo[]): void;
}
/**
* The local agent host contribution (for the workbench, not the sessions app).
* Discovers agents from the local agent host process and registers each one
* as a chat session type. Gated on the `chat.agentHost.enabled` setting.
*
* Uses the same shared adapters ({@link AgentHostSessionHandler}, etc.)
* but connects via {@link IAgentHostService} (MessagePort) instead of
* {@link IRemoteAgentHostService} (WebSocket).
*
* File: `workbench/contrib/chat/browser/agentSessions/agentHost/agentHostChatContribution.ts`
*/
interface AgentHostContribution extends IWorkbenchContribution {
// Registers per-agent: chat session contribution, session list controller,
// session handler, and language model provider - same 4 registrations
// as RemoteAgentHostContribution but for the local agent host.
}
// =============================================================================
// LAYER 4: Sessions App Orchestrator (sessions/contrib/remoteAgentHost/)
// =============================================================================
/**
* Central orchestrator for remote agent hosts in the sessions app.
*
* For each active remote connection:
* 1. Creates a {@link SessionClientState} for write-ahead reconciliation
* 2. Subscribes to `agenthost:/root` to discover available agents
* 3. For each discovered copilot agent, performs four registrations:
* - Chat session contribution (via {@link IChatSessionsService})
* - Session list controller ({@link AgentHostSessionListController})
* - Session content provider ({@link AgentHostSessionHandler})
* - Language model provider ({@link AgentHostLanguageModelProvider})
* 4. Registers authority→address mappings for the filesystem provider
* 5. Authenticates connections using RFC 9728 resource metadata
*
* Reconciles when connections change (added/removed/name changed)
* and when the default auth account or auth sessions change.
*
* File: `sessions/contrib/remoteAgentHost/browser/remoteAgentHost.contribution.ts`
*/
interface RemoteAgentHostContribution extends IWorkbenchContribution {
// Per-connection state tracked in a DisposableMap<address, ConnectionState>
}
/**
* Read-only {@link IFileSystemProvider} registered under the `agenthost`
* scheme. Proxies `stat` and `readdir` calls through the agent host
* protocol's `browseDirectory` RPC.
*
* The URI authority identifies the remote connection (sanitized address),
* the URI path is the remote filesystem path. Authority-to-address mappings
* are registered by {@link RemoteAgentHostContribution} via
* `registerAuthority(authority, address)`.
*
* File: `sessions/contrib/remoteAgentHost/browser/agentHostFileSystemProvider.ts`
*/
interface AgentHostFileSystemProvider extends IFileSystemProvider {
/** Register a mapping from sanitized URI authority to remote address. */
registerAuthority(authority: string, address: string): IDisposable;
}
// =============================================================================
// Naming Conventions & URI Schemes
// =============================================================================
/**
* Remote addresses are encoded into URI-safe authority strings:
* - `localhost:8081` → `localhost__8081`
* - `http://127.0.0.1:3000` → `b64-aHR0cDovLzEyNy4wLjAuMTozMDAw`
*
* | Context | Scheme | Example |
* |--------------------------|------------------|-------------------------------------------------|
* | Session resource (UI) | `<sessionType>` | `remote-localhost__8081-copilot:/untitled-abc` |
* | Backend session (server) | `<provider>` | `copilot:/abc-123` |
* | Root state subscription | (string literal) | `agenthost:/root` |
* | Remote filesystem | `agenthost` | `agenthost://localhost__8081/home/user/project` |
* | Language model ID | - | `remote-localhost__8081-copilot:claude-sonnet-4-20250514` |
*
* Session type naming: `remote-${authority}-${provider}` for remote,
* `agent-host-${provider}` for local.
*/
// =============================================================================
// IPC & Auth Data Types (platform/agentHost/common/agentService.ts)
// =============================================================================
/** Metadata describing an agent backend, discovered over IPC or root state. */
interface IAgentDescriptor {
readonly provider: AgentProvider;
readonly displayName: string;
readonly description: string;
/** @deprecated Use IResourceMetadata from getResourceMetadata() instead. */
readonly requiresAuth: boolean;
}
/** Serializable model information from the agent host. */
interface IAgentModelInfo {
readonly provider: AgentProvider;
readonly id: string;
readonly name: string;
readonly maxContextWindow: number;
readonly supportsVision: boolean;
readonly supportsReasoningEffort: boolean;
}
/** Configuration for creating a new session. */
interface IAgentCreateSessionConfig {
readonly provider?: AgentProvider;
readonly model?: string;
readonly session?: URI;
readonly workingDirectory?: string;
}
/** Metadata for an existing session (returned by listSessions). */
interface IAgentSessionMetadata {
readonly session: URI;
readonly startTime: number;
readonly modifiedTime: number;
readonly summary?: string;
readonly workingDirectory?: string;
}
/** Serializable attachment passed alongside a message to the agent host. */
interface IAgentAttachment {
readonly type: AttachmentType;
readonly path: string;
readonly displayName?: string;
readonly text?: string;
readonly selection?: {
readonly start: { readonly line: number; readonly character: number };
readonly end: { readonly line: number; readonly character: number };
};
}
/**
* Describes the agent host as an OAuth 2.0 protected resource (RFC 9728).
* Clients resolve tokens via the VS Code authentication service.
*/
interface IResourceMetadata {
readonly resources: readonly IAuthorizationProtectedResourceMetadata[];
}
/**
* Parameters for the `authenticate` command (RFC 6750 bearer token delivery).
*/
interface IAuthenticateParams {
readonly resource: string;
readonly token: string;
}
/**
* Result of the `authenticate` command.
*/
interface IAuthenticateResult {
readonly authenticated: boolean;
}
// =============================================================================
// Progress Events (platform/agentHost/common/agentService.ts)
// =============================================================================
/**
* Discriminated union of progress events streamed from the agent host.
* The state-to-progress adapter ({@link stateToProgressAdapter.ts})
* translates protocol state changes into `IChatProgress[]` for the chat UI,
* but these events are also used in the IPC path for the old event-based API.
*
* Types: `delta`, `message`, `idle`, `tool_start`, `tool_complete`,
* `title_changed`, `error`, `usage`, `permission_request`, `reasoning`.
*/
type IAgentProgressEvent =
| IAgentDeltaEvent
| IAgentMessageEvent
| IAgentIdleEvent
| IAgentToolStartEvent
| IAgentToolCompleteEvent
| IAgentTitleChangedEvent
| IAgentErrorEvent
| IAgentUsageEvent
| IAgentPermissionRequestEvent
| IAgentReasoningEvent;
```

View File

@@ -1,328 +0,0 @@
# Remote Agent Host Chat Agents - Architecture
This document describes how remote agent host chat agents are registered, how
sessions are created, and the URI/target conventions used throughout the system.
## Overview
A **remote agent host** is a VS Code agent host process running on another
machine, connected over WebSocket. The user configures remote addresses in the
`chat.remoteAgentHosts` setting. Each remote host may expose one or more agent
backends (currently only the `copilot` provider is supported). The system
discovers these agents, dynamically registers them as chat session types, and
creates sessions that stream turns via the agent host protocol.
```
┌─────────────┐ WebSocket ┌───────────────────┐
│ VS Code │ ◄──────────────► │ Remote Agent Host │
│ (client) │ AHP protocol │ (server) │
└─────────────┘ └───────────────────┘
```
## Connection Lifecycle
### 1. Configuration
Connections are configured via the `chat.remoteAgentHosts` setting:
```jsonc
"chat.remoteAgentHosts": [
{ "address": "http://192.168.1.10:3000", "name": "dev-box", "connectionToken": "..." }
]
```
Each entry is an `IRemoteAgentHostEntry` with `address`, `name`, and optional
`connectionToken`.
### 2. Service Layer
`IRemoteAgentHostService` (`src/vs/platform/agentHost/common/remoteAgentHostService.ts`)
manages WebSocket connections. The Electron implementation reads the setting,
creates `RemoteAgentHostProtocolClient` instances for each address, and fires
`onDidChangeConnections` when connections are established or lost.
Each connection satisfies the `IAgentConnection` interface (which extends
`IAgentService`), providing:
- `subscribe(resource)` / `unsubscribe(resource)` - state subscriptions
- `dispatchAction(action, clientId, seq)` - send client actions
- `onDidAction` / `onDidNotification` - receive server events
- `createSession(config)` - create a new backend session
- `browseDirectory(uri)` - list remote filesystem contents
- `clientId` - unique connection identifier for optimistic reconciliation
### 3. Connection Metadata
Each active connection exposes `IRemoteAgentHostConnectionInfo`:
```typescript
{
address: string; // e.g. "http://192.168.1.10:3000"
name: string; // e.g. "dev-box" (from setting)
clientId: string; // assigned during handshake
defaultDirectory?: string; // home directory on the remote machine
}
```
## Agent Discovery
### Root State Subscription
`RemoteAgentHostContribution` (`src/vs/sessions/contrib/remoteAgentHost/browser/remoteAgentHost.contribution.ts`)
is the central orchestrator. For each connection, it subscribes to `ROOT_STATE_URI`
(`agenthost:/root`) to discover available agents.
The root state (`IRootState`) contains:
```typescript
{
agents: IAgentInfo[]; // discovered agent backends
activeSessions?: number; // count of active sessions
}
```
Each `IAgentInfo` describes an agent:
```typescript
{
provider: string; // e.g. "copilot"
displayName: string; // e.g. "Copilot"
description: string;
models: ISessionModelInfo[]; // available language models
}
```
### Authority Encoding
Remote addresses are encoded into URI-safe authority strings via
`agentHostAuthority(address)`:
- Alphanumeric addresses pass through unchanged
- "Normal" addresses (`[a-zA-Z0-9.:-]`) get colons replaced with `__`
- Everything else is url-safe base64 encoded with a `b64-` prefix
Examples:
- `localhost:8081``localhost__8081`
- `192.168.1.1:8080``192.168.1.1__8080`
- `http://127.0.0.1:3000``b64-aHR0cDovLzEyNy4wLjAuMTozMDAw`
## Agent Registration
When `_registerAgent()` is called for a discovered copilot agent from address `X`:
### Naming Conventions
| Concept | Value | Example |
|---------|-------|---------|
| **Authority** | `agentHostAuthority(address)` | `localhost__8081` |
| **Session type** | `remote-${authority}-${provider}` | `remote-localhost__8081-copilot` |
| **Agent ID** | same as session type | `remote-localhost__8081-copilot` |
| **Vendor** | same as session type | `remote-localhost__8081-copilot` |
| **Display name** | `configuredName \|\| "${displayName} (${address})"` | `dev-box` |
### Four Registrations Per Agent
1. **Chat session contribution** - via `IChatSessionsService.registerChatSessionContribution()`:
```typescript
{ type: sessionType, name: agentId, displayName, canDelegate: true, requiresCustomModels: true }
```
2. **Session list controller** - `AgentHostSessionListController` handles the
sidebar session list. Lists sessions via `connection.listSessions()`, listens
for `notify/sessionAdded` and `notify/sessionRemoved` notifications.
3. **Session handler** - `AgentHostSessionHandler` implements
`IChatSessionContentProvider`, bridging the agent host protocol to chat UI
progress events. Also registers a _dynamic chat agent_ via
`IChatAgentService.registerDynamicAgent()`.
4. **Language model provider** - `AgentHostLanguageModelProvider` registers
models under the vendor descriptor. Model IDs are prefixed with the session
type (e.g., `remote-localhost__8081-copilot:claude-sonnet-4-20250514`).
## URI Conventions
| Context | Scheme | Format | Example |
|---------|--------|--------|---------|
| New session resource | `<sessionType>` | `<sessionType>:/untitled-<uuid>` | `remote-localhost__8081-copilot:/untitled-abc` |
| Existing session | `<sessionType>` | `<sessionType>:/<rawId>` | `remote-localhost__8081-copilot:/abc-123` |
| Backend session state | `<provider>` | `<provider>:/<rawId>` | `copilot:/abc-123` |
| Root state subscription | (string) | `agenthost:/root` | - |
| Remote filesystem | `agenthost` | `agenthost://<authority>/<path>` | `agenthost://localhost__8081/home/user/project` |
| Language model ID | - | `<sessionType>:<rawModelId>` | `remote-localhost__8081-copilot:claude-sonnet-4-20250514` |
### Key distinction: session resource vs backend session URI
- The **session resource** URI uses the session type as its scheme
(e.g., `remote-localhost__8081-copilot:/untitled-abc`). This is the URI visible to
the chat UI and session management.
- The **backend session** URI uses the provider as its scheme
(e.g., `copilot:/abc-123`). This is sent over the agent host protocol to the
server. The `AgentSession.uri(provider, rawId)` helper creates these.
The `AgentHostSessionHandler` translates between the two:
```typescript
private _resolveSessionUri(sessionResource: URI): URI {
const rawId = sessionResource.path.substring(1);
return AgentSession.uri(this._config.provider, rawId);
}
```
## Session Creation Flow
### 1. User Selects or Adds a Remote Workspace
In the `WorkspacePicker`, the user clicks **"Browse Remotes..."**. The picker
shows existing connected remotes and an **"Add Remote..."** action. When the
user chooses **"Add Remote..."**, they:
1. Paste a host, `host:port`, or WebSocket URL.
2. Enter a display name for that remote.
3. The parsed address is written into `chat.remoteAgentHosts`.
4. The client waits for the new remote connection to come up.
5. The user is immediately taken into the remote folder picker.
Supported address inputs include raw `host:port`, `ws://` / `wss://` URLs, and
log-line text such as `Listening on ws://127.0.0.1:8089`. If the input includes
`?tkn=...`, the token is extracted into `connectionToken` and the stored address
is normalized without that query parameter.
After choosing an existing remote or successfully adding a new one, the user
picks a folder on the remote filesystem. This produces a `SessionWorkspace`
with an `agenthost://` URI:
```
agenthost://localhost__8081/home/user/myproject
↑ authority ↑ remote filesystem path
```
### 2. Session Target Resolution
`NewChatWidget._createNewSession()` detects `project.isRemoteAgentHost` and
resolves the matching session type via `getRemoteAgentHostSessionTarget()`
(defined in `remoteAgentHost.contribution.ts`):
```typescript
// authority "localhost__8081" → find connection → "remote-localhost__8081-copilot"
const target = getRemoteAgentHostSessionTarget(connections, authority);
```
### 3. Resource URI Generation
`getResourceForNewChatSession()` creates the session resource:
```typescript
URI.from({ scheme: target, path: `/untitled-${generateUuid()}` })
// → remote-localhost__8081-copilot:/untitled-abc-123
```
### 4. Session Object Creation
`SessionsManagementService.createNewSessionForTarget()` creates an
`AgentHostNewSession` (when the `agentHost` option is set). This is a
lightweight `INewSession` that supports local model and mode pickers but
skips isolation mode, branch, and cloud option groups.
The project URI is set on the session, making it available as
`activeSessionItem.repository`.
### 5. Backend Session Creation (Deferred)
`AgentHostSessionHandler` defers backend session creation until the first turn
(for "untitled" sessions), so the user-selected model is available:
```typescript
const session = await connection.createSession({
model: rawModelId,
provider: 'copilot',
workingDirectory: '/home/user/myproject', // from activeSession.repository.path
});
```
### 6. Working Directory Resolution
The `resolveWorkingDirectory` callback in `RemoteAgentHostContribution` reads
the active session's repository URI path:
```typescript
const resolveWorkingDirectory = (resourceKey: string): string | undefined => {
const activeSessionItem = this._sessionsManagementService.getActiveSession();
if (activeSessionItem?.repository) {
return activeSessionItem.repository.path;
// For agenthost://authority/home/user/project → "/home/user/project"
}
return undefined;
};
```
## Turn Handling
When the user sends a message, `AgentHostSessionHandler._handleTurn()`:
1. Converts variable entries to `IAgentAttachment[]` (file, directory, selection)
2. Dispatches `session/modelChanged` if the model differs from current
3. Dispatches `session/turnStarted` with the user message + attachments
4. Listens to `SessionClientState.onDidChangeSessionState` and translates
the `activeTurn` state changes into `IChatProgress[]` events:
| Server State | Chat Progress |
|-------------|---------------|
| `streamingText` | `markdownContent` |
| `reasoning` | `thinking` |
| `toolCalls` (new) | `ChatToolInvocation` created |
| `toolCalls` (completed) | `ChatToolInvocation` finalized |
| `pendingPermissions` | `awaitConfirmation()` prompt |
5. On cancellation, dispatches `session/turnCancelled`
## Filesystem Provider
`AgentHostFileSystemProvider` is a read-only `IFileSystemProvider` registered
under the `agenthost` scheme. It proxies `stat` and `readdir` calls through
`connection.browseDirectory(uri)` RPC.
- The URI authority identifies the remote connection (sanitized address)
- The URI path is the remote filesystem path
- Authority-to-address mappings are registered by `RemoteAgentHostContribution`
via `registerAuthority(authority, address)`
## Data Flow Diagram
```
Settings (chat.remoteAgentHosts)
RemoteAgentHostService (WebSocket connections)
RemoteAgentHostContribution
├─► subscribe(ROOT_STATE_URI) → IRootState.agents
│ │
│ ▼
│ _registerAgent() for each copilot agent:
│ ├─► registerChatSessionContribution()
│ ├─► registerChatSessionItemController()
│ ├─► registerChatSessionContentProvider()
│ └─► registerLanguageModelProvider()
└─► registerProvider(AGENT_HOST_FS_SCHEME, fsProvider)
User picks remote workspace in WorkspacePicker
NewChatWidget._createNewSession(project)
│ target = getRemoteAgentHostSessionTarget(connections, authority)
SessionsManagementService.createNewSessionForTarget()
│ creates AgentHostNewSession
User sends message
AgentHostSessionHandler._handleTurn()
│ resolves working directory
│ creates backend session (if untitled)
│ dispatches session/turnStarted
connection ← streams state changes → IChatProgress[]
```