Path: blob/main/src/vs/platform/agentHost/electron-main/electronAgentHostStarter.ts
13394 views
/*---------------------------------------------------------------------------------------------1* Copyright (c) Microsoft Corporation. All rights reserved.2* Licensed under the MIT License. See License.txt in the project root for license information.3*--------------------------------------------------------------------------------------------*/45import { Disposable, DisposableStore, toDisposable } from '../../../base/common/lifecycle.js';6import { DeferredPromise } from '../../../base/common/async.js';7import { Emitter } from '../../../base/common/event.js';8import { IpcMainEvent } from 'electron';9import { validatedIpcMain } from '../../../base/parts/ipc/electron-main/ipcMain.js';10import { Client as MessagePortClient } from '../../../base/parts/ipc/electron-main/ipc.mp.js';11import { IConfigurationService } from '../../configuration/common/configuration.js';12import { IEnvironmentMainService } from '../../environment/electron-main/environmentMainService.js';13import { parseAgentHostDebugPort } from '../../environment/node/environmentService.js';14import { ILifecycleMainService } from '../../lifecycle/electron-main/lifecycleMainService.js';15import { ILogService } from '../../log/common/log.js';16import { Schemas } from '../../../base/common/network.js';17import { getResolvedShellEnv } from '../../shell/node/shellEnv.js';18import { NullTelemetryService } from '../../telemetry/common/telemetryUtils.js';19import { UtilityProcess } from '../../utilityProcess/electron-main/utilityProcess.js';20import { IAgentHostConnection, IAgentHostStarter } from '../common/agent.js';21import { deepClone } from '../../../base/common/objects.js';2223export class ElectronAgentHostStarter extends Disposable implements IAgentHostStarter {2425private utilityProcess: UtilityProcess | undefined = undefined;26private utilityProcessStarted: DeferredPromise<void> | undefined = undefined;2728private readonly _onRequestConnection = this._register(new Emitter<void>());29readonly onRequestConnection = this._onRequestConnection.event;30private readonly _onWillShutdown = this._register(new Emitter<void>());31readonly onWillShutdown = this._onWillShutdown.event;3233constructor(34@IConfigurationService private readonly _configurationService: IConfigurationService,35@IEnvironmentMainService private readonly _environmentMainService: IEnvironmentMainService,36@ILifecycleMainService private readonly _lifecycleMainService: ILifecycleMainService,37@ILogService private readonly _logService: ILogService,38) {39super();4041this._register(this._lifecycleMainService.onWillShutdown(() => this._onWillShutdown.fire()));4243// Listen for new windows to establish a direct MessagePort connection to the agent host44const onWindowConnection = (e: IpcMainEvent, nonce: string) => this._onWindowConnection(e, nonce);45validatedIpcMain.on('vscode:createAgentHostMessageChannel', onWindowConnection);46this._register(toDisposable(() => {47validatedIpcMain.removeListener('vscode:createAgentHostMessageChannel', onWindowConnection);48}));49}5051async start(): Promise<IAgentHostConnection> {52this.utilityProcess = new UtilityProcess(this._logService, NullTelemetryService, this._lifecycleMainService);53this.utilityProcessStarted = new DeferredPromise<void>();5455const inspectParams = parseAgentHostDebugPort(this._environmentMainService.args, this._environmentMainService.isBuilt);56const execArgv = inspectParams.port ? [57'--nolazy',58`--inspect${inspectParams.break ? '-brk' : ''}=${inspectParams.port}`59] : undefined;6061// Resolve user shell environment so spawned tools/terminals inherit62// PATH and other vars from the user's login shell (macOS/Linux GUI launches).63const shellEnv = await this._resolveShellEnv();6465this.utilityProcess.start({66type: 'agentHost',67name: 'agent-host',68entryPoint: 'vs/platform/agentHost/node/agentHostMain',69execArgv,70args: ['--logsPath', this._environmentMainService.logsHome.with({ scheme: Schemas.file }).fsPath],71env: {72...deepClone(process.env),73...shellEnv,74VSCODE_ESM_ENTRYPOINT: 'vs/platform/agentHost/node/agentHostMain',75VSCODE_PIPE_LOGGING: 'true',76VSCODE_VERBOSE_LOGGING: 'true',77}78});7980this.utilityProcessStarted.complete();8182const port = this.utilityProcess.connect();83const client = new MessagePortClient(port, 'agentHost');8485const store = new DisposableStore();86store.add(client);87store.add(this.utilityProcess.onStderr(data => {88if (this._isExpectedStderr(data)) {89return;90}91this._logService.error(`[AgentHost:stderr] ${data}`);92}));93store.add(toDisposable(() => {94this.utilityProcess?.kill();95this.utilityProcess?.dispose();96this.utilityProcess = undefined;97this.utilityProcessStarted = undefined;98}));99100return {101client,102store,103onDidProcessExit: this.utilityProcess.onExit,104};105}106107private async _resolveShellEnv(): Promise<typeof process.env> {108try {109return await getResolvedShellEnv(this._configurationService, this._logService, this._environmentMainService.args, process.env);110} catch (error) {111this._logService.error('AgentHostStarter was unable to resolve shell environment', error);112return {};113}114}115116private async _onWindowConnection(e: IpcMainEvent, nonce: string): Promise<void> {117this._onRequestConnection.fire();118119// Wait for utilityProcess.start() to actually run before calling connect(),120// otherwise the MessagePort posted via connect() is silently dropped.121await this.utilityProcessStarted?.p;122123if (!this.utilityProcess) {124this._logService.error('AgentHostStarter: cannot create window connection, agent host process is not running');125return;126}127128const port = this.utilityProcess.connect();129130if (e.sender.isDestroyed()) {131port.close();132return;133}134135e.sender.postMessage('vscode:createAgentHostMessageChannelResult', nonce, [port]);136}137138private static readonly _expectedStderrPatterns = [139'Most NODE_OPTIONs are not supported in packaged apps',140'Debugger listening on ws://',141'For help, see: https://nodejs.org/en/docs/inspector',142'ExperimentalWarning: SQLite is an experimental feature',143];144145private _isExpectedStderr(data: string): boolean {146return ElectronAgentHostStarter._expectedStderrPatterns.some(pattern => data.includes(pattern));147}148}149150151