From e7b83d54552fa9cb419c63fb24bde66f70addb60 Mon Sep 17 00:00:00 2001 From: Robo Date: Fri, 10 Jul 2026 13:24:25 +0900 Subject: [PATCH] fix: remote server node_modules lookup (#325233) --- .../platform/agentHost/node/appNodeModules.ts | 27 +++++++++++++++++++ .../agentHost/node/commandAutoApprover.ts | 14 +++++----- .../node/copilot/agentHostSandboxEngine.ts | 10 ++++--- .../agentHost/node/copilot/copilotAgent.ts | 12 ++++----- 4 files changed, 46 insertions(+), 17 deletions(-) create mode 100644 src/vs/platform/agentHost/node/appNodeModules.ts diff --git a/src/vs/platform/agentHost/node/appNodeModules.ts b/src/vs/platform/agentHost/node/appNodeModules.ts new file mode 100644 index 00000000000..edc7c8caced --- /dev/null +++ b/src/vs/platform/agentHost/node/appNodeModules.ts @@ -0,0 +1,27 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import { AppResourcePath, nodeModulesAsarUnpackedPath, nodeModulesPath } from '../../../base/common/network.js'; +import product from '../../product/common/product.js'; + +function hasUnpackedNodeModulesArchive(): boolean { + return !!process.versions['electron'] && !!product.commit && !process.env['VSCODE_DEV']; +} + +/** + * The {@link AppResourcePath} of the `node_modules` root that actually holds VS + * Code's bundled modules, suitable for passing to `FileAccess.asFileUri`. + */ +export function getAppNodeModulesPath(): AppResourcePath { + return hasUnpackedNodeModulesArchive() ? nodeModulesAsarUnpackedPath : nodeModulesPath; +} + +/** + * The bare directory name (`node_modules` or `node_modules.asar.unpacked`) of the + * resolved root, for callers that build paths from an app root themselves. + */ +export function getAppNodeModulesDirName(): 'node_modules' | 'node_modules.asar.unpacked' { + return hasUnpackedNodeModulesArchive() ? 'node_modules.asar.unpacked' : 'node_modules'; +} diff --git a/src/vs/platform/agentHost/node/commandAutoApprover.ts b/src/vs/platform/agentHost/node/commandAutoApprover.ts index 19a6b64e301..37c953a4d2f 100644 --- a/src/vs/platform/agentHost/node/commandAutoApprover.ts +++ b/src/vs/platform/agentHost/node/commandAutoApprover.ts @@ -6,10 +6,10 @@ import type { Language, Parser, Query, QueryCapture } from '@vscode/tree-sitter-wasm'; import * as fs from 'fs'; import { Disposable, toDisposable } from '../../../base/common/lifecycle.js'; -import { FileAccess, nodeModulesAsarUnpackedPath, nodeModulesPath } from '../../../base/common/network.js'; +import { FileAccess } from '../../../base/common/network.js'; import { escapeRegExpCharacters, regExpLeadsToEndlessLoop } from '../../../base/common/strings.js'; import { URI } from '../../../base/common/uri.js'; -import product from '../../product/common/product.js'; +import { getAppNodeModulesPath } from './appNodeModules.js'; import { ILogService } from '../../log/common/log.js'; import type { AgentHostTerminalAutoApproveRuleValue, AgentHostTerminalAutoApproveRules } from '../common/agentHostSchema.js'; @@ -299,11 +299,11 @@ export class CommandAutoApprover extends Disposable { return; } - // Resolve WASM files from node_modules. In a built app the `.wasm` files - // are unpacked next to the ASAR archive (`node_modules.asar.unpacked`), - // while in dev they live in `node_modules`. - const moduleRootPath = product.commit ? nodeModulesAsarUnpackedPath : nodeModulesPath; - const moduleRoot = URI.joinPath(FileAccess.asFileUri(moduleRootPath), '@vscode', 'tree-sitter-wasm', 'wasm'); + // Resolve WASM files from node_modules. In the desktop app the `.wasm` + // files are unpacked next to the ASAR archive (`node_modules.asar.unpacked`), + // while in dev and on the server (which has no ASAR) they live in a plain + // `node_modules`. + const moduleRoot = URI.joinPath(FileAccess.asFileUri(getAppNodeModulesPath()), '@vscode', 'tree-sitter-wasm', 'wasm'); const wasmPath = URI.joinPath(moduleRoot, 'tree-sitter.wasm').fsPath; await TreeSitter.Parser.init({ diff --git a/src/vs/platform/agentHost/node/copilot/agentHostSandboxEngine.ts b/src/vs/platform/agentHost/node/copilot/agentHostSandboxEngine.ts index 06af63977f8..662e8fe698d 100644 --- a/src/vs/platform/agentHost/node/copilot/agentHostSandboxEngine.ts +++ b/src/vs/platform/agentHost/node/copilot/agentHostSandboxEngine.ts @@ -15,6 +15,7 @@ import { IProductService } from '../../../product/common/productService.js'; import { ISandboxHelperService, type ISandboxDependencyStatus, type IWindowsMxcPolicyContainment, type IWindowsMxcSandboxPolicy } from '../../../sandbox/common/sandboxHelperService.js'; import { ITerminalSandboxEngineHost, ITerminalSandboxRuntimeInfo, TerminalSandboxEngine } from '../../../sandbox/common/terminalSandboxEngine.js'; import { IAgentConfigurationService } from '../agentConfigurationService.js'; +import { getAppNodeModulesDirName } from '../appNodeModules.js'; import { AgentHostSandboxConfigKey, sandboxConfigSchema, sandboxSettingIdToAgentHostKey } from '../../common/sandboxConfigSchema.js'; /** Subdirectory under the user home + product data folder where the engine creates its temp dir. */ @@ -49,10 +50,11 @@ class AgentHostTerminalSandboxHost implements ITerminalSandboxEngineHost { async getRuntimeInfo(): Promise { const appRoot = dirname(FileAccess.asFileUri('').path); const runAsNode = !!process.versions['electron']; - // In a packaged build the native binaries (ripgrep-universal, mxc-sdk) are - // unpacked from the archive into `node_modules.asar.unpacked`; in dev they - // remain in plain `node_modules`. - const nativeModulesDir = this._environmentService.isBuilt ? 'node_modules.asar.unpacked' : 'node_modules'; + // In the desktop app the native binaries (ripgrep-universal, mxc-sdk) are + // unpacked from the ASAR archive into `node_modules.asar.unpacked`; in dev + // and on the server (which has no ASAR) they remain in a plain + // `node_modules`. + const nativeModulesDir = getAppNodeModulesDirName(); return { appRoot, execPath: process.execPath, runAsNode, nativeModulesDir }; } diff --git a/src/vs/platform/agentHost/node/copilot/copilotAgent.ts b/src/vs/platform/agentHost/node/copilot/copilotAgent.ts index 9cecdf5ecba..3949bb0e17e 100644 --- a/src/vs/platform/agentHost/node/copilot/copilotAgent.ts +++ b/src/vs/platform/agentHost/node/copilot/copilotAgent.ts @@ -13,7 +13,7 @@ import { Emitter, Event } from '../../../../base/common/event.js'; import { appendEscapedMarkdownInlineCode } from '../../../../base/common/htmlContent.js'; import { combinedDisposable, Disposable, DisposableMap, MutableDisposable } from '../../../../base/common/lifecycle.js'; import { ResourceMap } from '../../../../base/common/map.js'; -import { FileAccess, nodeModulesAsarUnpackedPath, nodeModulesPath, Schemas } from '../../../../base/common/network.js'; +import { FileAccess, Schemas } from '../../../../base/common/network.js'; import { formatTokenCount } from '../../../../base/common/numbers.js'; import { equals } from '../../../../base/common/objects.js'; import { autorun, observableValue, type ISettableObservable } from '../../../../base/common/observable.js'; @@ -69,7 +69,7 @@ import { ICopilotApiService } from '../shared/copilotApiService.js'; import { CopilotSlashCommandCompletionProvider } from './copilotSlashCommandCompletionProvider.js'; import { DiscoveredType, SessionCustomizationDiscovery, areDiscoveredDirectoriesEqual, type IDiscoveredDirectory } from './sessionCustomizationDiscovery.js'; import { COPILOT_INTEGRATION_ID } from '../../../endpoint/common/licenseAgreement.js'; -import product from '../../../product/common/product.js'; +import { getAppNodeModulesPath } from '../appNodeModules.js'; const RUNTIME_SLASH_COMMAND_COMPLETION_WAIT_MS = 300; const COPILOT_CAPI_URL = 'https://api.githubcopilot.com'; @@ -1011,14 +1011,14 @@ export class CopilotAgent extends Disposable implements IAgent { } // Resolve the CLI entry point and native SDK binaries from node_modules. - // In a built app these live next to the ASAR archive in + // In the desktop app these live next to the ASAR archive in // `node_modules.asar.unpacked` (the `@github/copilot-` CLI and // the `@microsoft/mxc-sdk/bin` executables are unpacked so they can be - // spawned), while in dev they live in `node_modules`. + // spawned), while in dev and on the server (which has no ASAR) they live + // in a plain `node_modules`. // We can't use require.resolve() because @github/copilot's exports map // blocks direct subpath access. - const moduleRootPath = product.commit ? nodeModulesAsarUnpackedPath : nodeModulesPath; - const nodeModulesUri = FileAccess.asFileUri(moduleRootPath); + const nodeModulesUri = FileAccess.asFileUri(getAppNodeModulesPath()); const cliPath = await resolveCopilotCliPath(nodeModulesUri); // The SDK's sandbox auto-detection looks for `//wxc-exec.exe`