From b780f69fd915c36a256a1990999ca8e4cd4909df Mon Sep 17 00:00:00 2001 From: mynameistito Date: Mon, 19 Jan 2026 03:02:01 +1300 Subject: [PATCH] fix: implement shadow copying for native binaries on Windows --- bun.lock | 1 + src/plugin/pty/manager.ts | 14 +++++++-- src/plugin/pty/shadow.ts | 53 +++++++++++++++++++++++++++++++++++ src/plugin/pty/tools/spawn.ts | 2 +- 4 files changed, 67 insertions(+), 3 deletions(-) create mode 100644 src/plugin/pty/shadow.ts diff --git a/bun.lock b/bun.lock index 4b64200..9c6f61d 100644 --- a/bun.lock +++ b/bun.lock @@ -1,5 +1,6 @@ { "lockfileVersion": 1, + "configVersion": 0, "workspaces": { "": { "name": "opencode-gemini-auth", diff --git a/src/plugin/pty/manager.ts b/src/plugin/pty/manager.ts index 7e528b8..35a097a 100644 --- a/src/plugin/pty/manager.ts +++ b/src/plugin/pty/manager.ts @@ -1,8 +1,9 @@ -import { spawn, type IPty } from "bun-pty"; +import type { IPty } from "bun-pty"; import type { OpencodeClient } from "@opencode-ai/sdk"; import { RingBuffer } from "./buffer.ts"; import type { PTYSession, PTYSessionInfo, SpawnOptions, ReadResult, SearchResult } from "./types.ts"; import { createLogger } from "../logger.ts"; +import { prepareShadowPty } from "./shadow.ts"; const log = createLogger("manager"); @@ -21,8 +22,17 @@ function generateId(): string { class PTYManager { private sessions: Map = new Map(); + private ptyModule: typeof import("bun-pty") | null = null; - spawn(opts: SpawnOptions): PTYSessionInfo { + private async ensureLoaded() { + if (this.ptyModule) return this.ptyModule; + await prepareShadowPty(); + this.ptyModule = await import("bun-pty"); + return this.ptyModule; + } + + async spawn(opts: SpawnOptions): Promise { + const { spawn } = await this.ensureLoaded(); const id = generateId(); const args = opts.args ?? []; const workdir = opts.workdir ?? process.cwd(); diff --git a/src/plugin/pty/shadow.ts b/src/plugin/pty/shadow.ts new file mode 100644 index 0000000..af85996 --- /dev/null +++ b/src/plugin/pty/shadow.ts @@ -0,0 +1,53 @@ +import fs from "node:fs"; +import path from "node:path"; +import os from "node:os"; +import crypto from "node:crypto"; + +/** + * Prepares a shadow copy of the bun-pty native binary on Windows. + * This prevents file locking issues when multiple OpenCode instances are running. + */ +export async function prepareShadowPty() { + if (process.platform !== "win32") { + return; + } + + if (process.env.BUN_PTY_LIB) { + return; + } + + try { + // Resolve the bun-pty package location + const entryPath = await Bun.resolve("bun-pty", import.meta.dir); + const packageRoot = path.dirname(path.dirname(entryPath)); + + const dllPath = path.join(packageRoot, "rust-pty", "target", "release", "rust_pty.dll"); + + if (!fs.existsSync(dllPath)) { + return; + } + + const tempDir = path.join(os.tmpdir(), "opencode-pty", crypto.randomUUID()); + fs.mkdirSync(tempDir, { recursive: true }); + + const targetDllPath = path.join(tempDir, "rust_pty.dll"); + fs.copyFileSync(dllPath, targetDllPath); + + // Set the environment variable that bun-pty uses to find its native library + process.env.BUN_PTY_LIB = targetDllPath; + + // Ensure we clean up on exit + process.on("exit", () => { + try { + fs.rmSync(tempDir, { recursive: true, force: true }); + } catch { + // Ignore cleanup errors + } + }); + } catch (err) { + // Fail silently but log if in dev + if (process.env.NODE_ENV === "development") { + console.error("[opencode-pty] Failed to shadow copy native binary:", err); + } + } +} diff --git a/src/plugin/pty/tools/spawn.ts b/src/plugin/pty/tools/spawn.ts index f499ff7..c8957ff 100644 --- a/src/plugin/pty/tools/spawn.ts +++ b/src/plugin/pty/tools/spawn.ts @@ -22,7 +22,7 @@ export const ptySpawn = tool({ } const sessionId = ctx.sessionID; - const info = manager.spawn({ + const info = await manager.spawn({ command: args.command, args: args.args, workdir: args.workdir,