diff --git a/extensions/vscode-test-resolver/src/extension.ts b/extensions/vscode-test-resolver/src/extension.ts index 28e4971edbf..373c8809341 100644 --- a/extensions/vscode-test-resolver/src/extension.ts +++ b/extensions/vscode-test-resolver/src/extension.ts @@ -159,6 +159,21 @@ export function activate(context: vscode.ExtensionContext) { commandArgs.push('--connection-token', connectionToken); const agentHostPort = getConfiguration('agentHostPort'); + const agentHostBridgePort = getConfiguration('agentHostBridgePort'); + if ( + typeof agentHostPort === 'number' && agentHostPort > 0 && + typeof agentHostBridgePort === 'number' && agentHostBridgePort > 0 + ) { + // `agentHostPort` spawns an agent host inside the server; + // `agentHostBridgePort` bridges the renderer to an + // externally-running one. They contradict each other — + // reject up front so the user notices instead of getting + // two agent hosts and silently confusing failure modes. + throw new Error( + `testResolver: 'agentHostPort' and 'agentHostBridgePort' are mutually exclusive. ` + + `Configure at most one (got port=${agentHostPort}, bridge=${agentHostBridgePort}).` + ); + } if (typeof agentHostPort === 'number' && agentHostPort > 0) { commandArgs.push('--agent-host-port', String(agentHostPort)); } @@ -166,7 +181,6 @@ export function activate(context: vscode.ExtensionContext) { // Bridge to an externally-running agent host (e.g. one started // by `code agent host`). Mutually exclusive with `agentHostPort` // — that one spawns its own agent host. - const agentHostBridgePort = getConfiguration('agentHostBridgePort'); if (typeof agentHostBridgePort === 'number' && agentHostBridgePort > 0) { commandArgs.push('--agent-host-bridge-port', String(agentHostBridgePort)); } diff --git a/src/vs/server/node/agentHostChannel.ts b/src/vs/server/node/agentHostChannel.ts index 57881dcf654..c72ea7a3d68 100644 --- a/src/vs/server/node/agentHostChannel.ts +++ b/src/vs/server/node/agentHostChannel.ts @@ -256,6 +256,16 @@ export class AgentHostChannel extends Disposable implements IServerCha if (!conn) { conn = this._upstreamFactory(this._endpoint); this._perCtx.set(ctx, conn); + // If the upstream closes on its own (e.g. agent host restart or + // connection drop), evict it from the cache so the next + // `connect()` call creates a fresh upstream rather than + // returning the stuck-closed one. + const sub = conn.onClose(() => { + sub.dispose(); + if (this._perCtx.get(ctx) === conn) { + this._perCtx.delete(ctx); + } + }); } return conn; } diff --git a/src/vs/server/node/serverServices.ts b/src/vs/server/node/serverServices.ts index 8c10c5ae45f..0d82750e6f4 100644 --- a/src/vs/server/node/serverServices.ts +++ b/src/vs/server/node/serverServices.ts @@ -270,17 +270,25 @@ export async function setupServerServices(connectionToken: ServerConnectionToken disposables.add(instantiationService.createInstance(ServerAgentHostManager, agentHostStarter)); } - // The bridge requires an explicit upstream — we do NOT fall back to - // the spawn port. When `--agent-host-port=0` is used the OS picks a - // port at runtime that this server has no way of learning; in the - // `code tunnel` flow the CLI captures that port from the AH's - // readiness line and passes it back as `--agent-host-bridge-port` on - // the renderer-serving servers. The manager-spawned AH itself never - // serves renderer connections directly, so it doesn't need a bridge. - const bridgePort = args['agent-host-bridge-port']; - const bridgePath = args['agent-host-bridge-path']; + // The bridge upstream defaults to the agent host this server just + // spawned, but ONLY when that endpoint is dialable at configuration + // time — i.e. an explicit non-zero port or a socket path. When + // `--agent-host-port=0` is used the OS picks a port at runtime that + // this server has no way of learning, so we refuse to register a + // bridge against a placeholder `0`; in that case the caller (the CLI + // `code tunnel` flow) is expected to capture the bound port from the + // AH's readiness line and pass it back as `--agent-host-bridge-port` + // on the renderer-serving servers. Explicit `--agent-host-bridge-*` + // always wins over the spawn fallback. + const spawnPortNumber = spawnPort ? parseInt(spawnPort, 10) : NaN; + const hasUsableSpawnPort = Number.isFinite(spawnPortNumber) && spawnPortNumber > 0; + const bridgePort = args['agent-host-bridge-port'] ?? (hasUsableSpawnPort ? spawnPort : undefined); + const bridgePath = args['agent-host-bridge-path'] ?? spawnPath; const bridgeHost = args['agent-host-bridge-host'] ?? args.host ?? 'localhost'; - const bridgeToken = args['agent-host-bridge-connection-token']; + const bridgeToken = args['agent-host-bridge-connection-token'] + ?? ((bridgePort || bridgePath) && spawnAgentHost && connectionToken.type === ServerConnectionTokenType.Mandatory + ? connectionToken.value + : undefined); if (bridgePort || bridgePath) { const agentHostBridge = disposables.add(new AgentHostChannel( socketServer, diff --git a/src/vs/workbench/services/agentHost/browser/editorRemoteAgentHostServiceClient.ts b/src/vs/workbench/services/agentHost/browser/editorRemoteAgentHostServiceClient.ts index 6b906905c1e..de5fca1a9c6 100644 --- a/src/vs/workbench/services/agentHost/browser/editorRemoteAgentHostServiceClient.ts +++ b/src/vs/workbench/services/agentHost/browser/editorRemoteAgentHostServiceClient.ts @@ -67,10 +67,12 @@ export class EditorRemoteAgentHostServiceClient extends Disposable implements IA if (!enabled) { this._logService.info(`${LOG_PREFIX} Disabled via "${AgentHostEnabledSettingId}". Not connecting.`); + this.setAuthenticationPending(false); return; } if (!connection) { this._logService.warn(`${LOG_PREFIX} No remote agent connection available. Not connecting.`); + this.setAuthenticationPending(false); return; }