diff --git a/src/copilot/CopilotAgent.ts b/src/copilot/CopilotAgent.ts index 4bd00b7..8abb6b1 100644 --- a/src/copilot/CopilotAgent.ts +++ b/src/copilot/CopilotAgent.ts @@ -11,6 +11,7 @@ import Logger from "../helpers/Logger"; import Json from "../helpers/Json"; import Client, { CopilotResponse } from "./Client"; import File from "../helpers/File"; +import Node from "../helpers/Node"; import { GetCompletionsParams } from "@pierrad/ts-lsp-client"; import { InlineSuggestionEffect } from "../extensions/InlineSuggestionState"; @@ -51,10 +52,10 @@ class CopilotAgent implements SettingsObserver { public startAgent(): void { try { this.agent = spawn( - File.wrapFilePath(this.plugin.settings.nodePath), - [File.wrapFilePath(this.agentPath), "--stdio"], + // Spawn the executable directly to avoid shell parsing issues with spaces + Node.normalizePath(this.plugin.settings.nodePath), + [Node.normalizePath(this.agentPath), "--stdio"], { - shell: true, stdio: "pipe", ...(this.plugin.settings.proxy && { env: { diff --git a/src/helpers/Node.ts b/src/helpers/Node.ts index d4afb45..b577e01 100644 --- a/src/helpers/Node.ts +++ b/src/helpers/Node.ts @@ -10,6 +10,14 @@ class Node { return nodePath; } + // Trim surrounding whitespace and remove surrounding quotes if the user + // pasted a quoted path (common on Windows when copying paths). + nodePath = nodePath.trim(); + + if ((nodePath.startsWith('"') && nodePath.endsWith('"')) || (nodePath.startsWith("'") && nodePath.endsWith("'"))) { + nodePath = nodePath.slice(1, -1); + } + if (nodePath.startsWith("~")) { nodePath = nodePath.replace(/^~(?=$|\/|\\)/, os.homedir()); } @@ -74,11 +82,11 @@ class Node { } const result = await new Promise((resolve, reject) => { - let spawnOptions = {}; - - if (os.platform() === "win32") { - spawnOptions = { shell: true }; - } + // Do not run under a shell. Using a shell on Windows will split paths + // that contain spaces (eg. "C:\Program Files\nodejs\node.exe") and + // cause the command to fail. Spawn the executable directly so paths + // with spaces are handled correctly. + const spawnOptions = {}; const nodeProcess = child_process.spawn( normalizedPath,