From f36773e081722287ff75e257d9ab217ea0a27503 Mon Sep 17 00:00:00 2001 From: Noitidart Date: Fri, 15 May 2026 00:11:44 -0700 Subject: [PATCH] fix(opencode): fix file reference feature of VS Code extension MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The VS Code extension requires the terminal title to be "opencode" in order for the Cmd+Option+K file reference hotkey to prefill the prompt. VS Code names terminal tabs after the foreground process detected via the OS process table (kern_proc on macOS, /proc on Linux). It reads the actual binary filename — not process.title, argv[0], or ANSI escape sequences. The old bin/opencode was a Node.js wrapper that used child_process.spawn() to launch the compiled binary, keeping the node process alive as a signal forwarder. This created a three-deep process tree: zsh → node (wrapper) → opencode (compiled binary) VS Code sees "node" as the direct child of zsh and never recurses into grandchildren, so the terminal tab stays "zsh". The VS Code extension identifies opencode terminals by checking terminal.name === "opencode", which means Cmd+Option+K (file reference insertion) silently does nothing. The fix replaces the Node.js wrapper with a POSIX shell script that exec's directly into the compiled binary. On Unix, exec replaces the calling process with the target binary in-place, so the OS process table shows "opencode" as the direct child of zsh. The platform detection logic (AVX2, musl, architecture) moves to a separate bin/opencode-resolve Node.js script that outputs the binary path to stdout. A bin/opencode.cmd fallback is added for Windows. Fixes #10993 Fixes #27678 --- packages/opencode/bin/opencode | 227 +++---------------------- packages/opencode/bin/opencode-resolve | 160 +++++++++++++++++ packages/opencode/bin/opencode.cmd | 19 +++ 3 files changed, 207 insertions(+), 199 deletions(-) create mode 100755 packages/opencode/bin/opencode-resolve create mode 100644 packages/opencode/bin/opencode.cmd diff --git a/packages/opencode/bin/opencode b/packages/opencode/bin/opencode index a7101f42b0fe..2c39d63d2a84 100755 --- a/packages/opencode/bin/opencode +++ b/packages/opencode/bin/opencode @@ -1,199 +1,28 @@ -#!/usr/bin/env node - -const childProcess = require("child_process") -const fs = require("fs") -const path = require("path") -const os = require("os") - -const forwardedSignals = ["SIGINT", "SIGTERM", "SIGHUP"] - -function run(target) { - const child = childProcess.spawn(target, process.argv.slice(2), { - stdio: "inherit", - }) - - child.on("error", (error) => { - console.error(error.message) - process.exit(1) - }) - - const forwarders = {} - for (const signal of forwardedSignals) { - forwarders[signal] = () => { - try { - child.kill(signal) - } catch { - // The child may have already exited. - } - } - process.on(signal, forwarders[signal]) - } - - child.on("exit", (code, signal) => { - for (const forwardedSignal of forwardedSignals) { - process.removeListener(forwardedSignal, forwarders[forwardedSignal]) - } - - if (signal) { - process.kill(process.pid, signal) - return - } - - process.exit(typeof code === "number" ? code : 0) - }) -} - -const envPath = process.env.OPENCODE_BIN_PATH - -const scriptPath = fs.realpathSync(__filename) -const scriptDir = path.dirname(scriptPath) - -// -const cached = path.join(scriptDir, ".opencode") - -const platformMap = { - darwin: "darwin", - linux: "linux", - win32: "windows", -} -const archMap = { - x64: "x64", - arm64: "arm64", - arm: "arm", -} - -let platform = platformMap[os.platform()] -if (!platform) { - platform = os.platform() -} -let arch = archMap[os.arch()] -if (!arch) { - arch = os.arch() -} -const base = "opencode-" + platform + "-" + arch -const binary = platform === "windows" ? "opencode.exe" : "opencode" - -function supportsAvx2() { - if (arch !== "x64") return false - - if (platform === "linux") { - try { - return /(^|\s)avx2(\s|$)/i.test(fs.readFileSync("/proc/cpuinfo", "utf8")) - } catch { - return false - } - } - - if (platform === "darwin") { - try { - const result = childProcess.spawnSync("sysctl", ["-n", "hw.optional.avx2_0"], { - encoding: "utf8", - timeout: 1500, - }) - if (result.status !== 0) return false - return (result.stdout || "").trim() === "1" - } catch { - return false - } - } - - if (platform === "windows") { - const cmd = - '(Add-Type -MemberDefinition "[DllImport(""kernel32.dll"")] public static extern bool IsProcessorFeaturePresent(int ProcessorFeature);" -Name Kernel32 -Namespace Win32 -PassThru)::IsProcessorFeaturePresent(40)' - - for (const exe of ["powershell.exe", "pwsh.exe", "pwsh", "powershell"]) { - try { - const result = childProcess.spawnSync(exe, ["-NoProfile", "-NonInteractive", "-Command", cmd], { - encoding: "utf8", - timeout: 3000, - windowsHide: true, - }) - if (result.status !== 0) continue - const out = (result.stdout || "").trim().toLowerCase() - if (out === "true" || out === "1") return true - if (out === "false" || out === "0") return false - } catch { - continue - } - } - - return false - } - - return false -} - -const names = (() => { - const avx2 = supportsAvx2() - const baseline = arch === "x64" && !avx2 - - if (platform === "linux") { - const musl = (() => { - try { - if (fs.existsSync("/etc/alpine-release")) return true - } catch { - // ignore - } - - try { - const result = childProcess.spawnSync("ldd", ["--version"], { encoding: "utf8" }) - const text = ((result.stdout || "") + (result.stderr || "")).toLowerCase() - if (text.includes("musl")) return true - } catch { - // ignore - } - - return false - })() - - if (musl) { - if (arch === "x64") { - if (baseline) return [`${base}-baseline-musl`, `${base}-musl`, `${base}-baseline`, base] - return [`${base}-musl`, `${base}-baseline-musl`, base, `${base}-baseline`] - } - return [`${base}-musl`, base] - } - - if (arch === "x64") { - if (baseline) return [`${base}-baseline`, base, `${base}-baseline-musl`, `${base}-musl`] - return [base, `${base}-baseline`, `${base}-musl`, `${base}-baseline-musl`] - } - return [base, `${base}-musl`] - } - - if (arch === "x64") { - if (baseline) return [`${base}-baseline`, base] - return [base, `${base}-baseline`] - } - return [base] -})() - -function findBinary(startDir) { - let current = startDir - for (;;) { - const modules = path.join(current, "node_modules") - if (fs.existsSync(modules)) { - for (const name of names) { - const candidate = path.join(modules, name, "bin", binary) - if (fs.existsSync(candidate)) return candidate - } - } - const parent = path.dirname(current) - if (parent === current) { - return - } - current = parent - } -} - -const resolved = envPath || (fs.existsSync(cached) ? cached : findBinary(scriptDir)) -if (!resolved) { - console.error( - "It seems that your package manager failed to install the right version of the opencode CLI for your platform. You can try manually installing " + - names.map((n) => `\"${n}\"`).join(" or ") + - " package", - ) - process.exit(1) -} - -run(resolved) +#!/bin/sh +set -e + +# Resolve the directory containing this script, following symlinks. +prog="$0" +while [ -L "$prog" ]; do + dir="$(cd "$(dirname "$prog")" && pwd)" + prog="$(readlink "$prog")" + case "$prog" in + /*) ;; + *) prog="$dir/$prog" ;; + esac +done +dir="$(cd "$(dirname "$prog")" && pwd)" + +# Check for env var override. +if [ -n "$OPENCODE_BIN_PATH" ]; then + exec "$OPENCODE_BIN_PATH" "$@" +fi + +# Resolve the binary path via the Node.js resolver. +binary=$("$dir/opencode-resolve" 2>/dev/null) || true +if [ -n "$binary" ] && [ -x "$binary" ]; then + exec "$binary" "$@" +fi + +# Fallback: let the resolver show its error message. +exec node "$dir/opencode-resolve" diff --git a/packages/opencode/bin/opencode-resolve b/packages/opencode/bin/opencode-resolve new file mode 100755 index 000000000000..077e743b9b7e --- /dev/null +++ b/packages/opencode/bin/opencode-resolve @@ -0,0 +1,160 @@ +#!/usr/bin/env node + +const childProcess = require("child_process") +const fs = require("fs") +const path = require("path") +const os = require("os") + +const envPath = process.env.OPENCODE_BIN_PATH + +const scriptPath = fs.realpathSync(__filename) +const scriptDir = path.dirname(scriptPath) + +const cached = path.join(scriptDir, ".opencode") + +const platformMap = { + darwin: "darwin", + linux: "linux", + win32: "windows", +} +const archMap = { + x64: "x64", + arm64: "arm64", + arm: "arm", +} + +let platform = platformMap[os.platform()] +if (!platform) { + platform = os.platform() +} +let arch = archMap[os.arch()] +if (!arch) { + arch = os.arch() +} +const base = "opencode-" + platform + "-" + arch +const binary = platform === "windows" ? "opencode.exe" : "opencode" + +function supportsAvx2() { + if (arch !== "x64") return false + + if (platform === "linux") { + try { + return /(^|\s)avx2(\s|$)/i.test(fs.readFileSync("/proc/cpuinfo", "utf8")) + } catch { + return false + } + } + + if (platform === "darwin") { + try { + const result = childProcess.spawnSync("sysctl", ["-n", "hw.optional.avx2_0"], { + encoding: "utf8", + timeout: 1500, + }) + if (result.status !== 0) return false + return (result.stdout || "").trim() === "1" + } catch { + return false + } + } + + if (platform === "windows") { + const cmd = + '(Add-Type -MemberDefinition "[DllImport(""kernel32.dll"")] public static extern bool IsProcessorFeaturePresent(int ProcessorFeature);" -Name Kernel32 -Namespace Win32 -PassThru)::IsProcessorFeaturePresent(40)' + + for (const exe of ["powershell.exe", "pwsh.exe", "pwsh", "powershell"]) { + try { + const result = childProcess.spawnSync(exe, ["-NoProfile", "-NonInteractive", "-Command", cmd], { + encoding: "utf8", + timeout: 3000, + windowsHide: true, + }) + if (result.status !== 0) continue + const out = (result.stdout || "").trim().toLowerCase() + if (out === "true" || out === "1") return true + if (out === "false" || out === "0") return false + } catch { + continue + } + } + + return false + } + + return false +} + +const names = (() => { + const avx2 = supportsAvx2() + const baseline = arch === "x64" && !avx2 + + if (platform === "linux") { + const musl = (() => { + try { + if (fs.existsSync("/etc/alpine-release")) return true + } catch { + // ignore + } + + try { + const result = childProcess.spawnSync("ldd", ["--version"], { encoding: "utf8" }) + const text = ((result.stdout || "") + (result.stderr || "")).toLowerCase() + if (text.includes("musl")) return true + } catch { + // ignore + } + + return false + })() + + if (musl) { + if (arch === "x64") { + if (baseline) return [`${base}-baseline-musl`, `${base}-musl`, `${base}-baseline`, base] + return [`${base}-musl`, `${base}-baseline-musl`, base, `${base}-baseline`] + } + return [`${base}-musl`, base] + } + + if (arch === "x64") { + if (baseline) return [`${base}-baseline`, base, `${base}-baseline-musl`, `${base}-musl`] + return [base, `${base}-baseline`, `${base}-musl`, `${base}-baseline-musl`] + } + return [base, `${base}-musl`] + } + + if (arch === "x64") { + if (baseline) return [`${base}-baseline`, base] + return [base, `${base}-baseline`] + } + return [base] +})() + +function findBinary(startDir) { + let current = startDir + for (;;) { + const modules = path.join(current, "node_modules") + if (fs.existsSync(modules)) { + for (const name of names) { + const candidate = path.join(modules, name, "bin", binary) + if (fs.existsSync(candidate)) return candidate + } + } + const parent = path.dirname(current) + if (parent === current) { + return + } + current = parent + } +} + +const resolved = envPath || (fs.existsSync(cached) ? cached : findBinary(scriptDir)) +if (!resolved) { + console.error( + "It seems that your package manager failed to install the right version of the opencode CLI for your platform. You can try manually installing " + + names.map((n) => `"${n}"`).join(" or ") + + " package", + ) + process.exit(1) +} + +process.stdout.write(resolved) diff --git a/packages/opencode/bin/opencode.cmd b/packages/opencode/bin/opencode.cmd new file mode 100644 index 000000000000..b27c0537be7d --- /dev/null +++ b/packages/opencode/bin/opencode.cmd @@ -0,0 +1,19 @@ +@echo off +setlocal + +REM Check for env var override. +if defined OPENCODE_BIN_PATH ( + "%OPENCODE_BIN_PATH%" %* + exit /b %ERRORLEVEL% +) + +REM Resolve the binary path via the Node.js resolver. +for /f "delims=" %%i in ('node "%~dp0\opencode-resolve"') do set BINARY=%%i + +if not defined BINARY ( + exit /b 1 +) + +REM Run the binary. +"%BINARY%" %* +exit /b %ERRORLEVEL%