From 644ae33c1988406f0800f706f7a107d0855e9af5 Mon Sep 17 00:00:00 2001 From: claude-code-best Date: Wed, 15 Apr 2026 10:04:39 +0800 Subject: [PATCH] =?UTF-8?q?fix:=20=E4=BF=AE=E5=A4=8D=20Bun=20=E7=9A=84=20p?= =?UTF-8?q?olyfill=20=E9=97=AE=E9=A2=98?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- build.ts | 62 +++++++------------ bun.lock | 3 +- package.json | 17 ++--- .../computer-use-input/src/backends/darwin.ts | 38 ++++++------ 4 files changed, 52 insertions(+), 68 deletions(-) diff --git a/build.ts b/build.ts index 857aefe8e..11b859330 100644 --- a/build.ts +++ b/build.ts @@ -88,8 +88,27 @@ for (const file of files) { } } +// Also patch unguarded globalThis.Bun destructuring from third-party deps +// (e.g. @anthropic-ai/sandbox-runtime) so Node.js doesn't crash at import time. +let bunPatched = 0 +const BUN_DESTRUCTURE = /var \{([^}]+)\} = globalThis\.Bun;?/g +const BUN_DESTRUCTURE_SAFE = 'var {$1} = typeof globalThis.Bun !== "undefined" ? globalThis.Bun : {};' +for (const file of files) { + if (!file.endsWith('.js')) continue + const filePath = join(outdir, file) + const content = await readFile(filePath, 'utf-8') + if (BUN_DESTRUCTURE.test(content)) { + await writeFile( + filePath, + content.replace(BUN_DESTRUCTURE, BUN_DESTRUCTURE_SAFE), + ) + bunPatched++ + } +} +BUN_DESTRUCTURE.lastIndex = 0 + console.log( - `Bundled ${result.outputs.length} files to ${outdir}/ (patched ${patched} for Node.js compat)`, + `Bundled ${result.outputs.length} files to ${outdir}/ (patched ${patched} for import.meta.require, ${bunPatched} for Bun destructure)`, ) // Step 4: Copy native .node addon files (audio-capture) @@ -119,46 +138,7 @@ const cliNode = join(outdir, 'cli-node.js') await writeFile(cliBun, '#!/usr/bin/env bun\nimport "./cli.js"\n') -// Node.js entry needs a Bun API polyfill because Bun.build({ target: 'bun' }) -// emits globalThis.Bun references (e.g. Bun.$ shell tag in computer-use-input, -// Bun.which in chunk-ys6smqg9) that crash at import time under plain Node.js. -const NODE_BUN_POLYFILL = `#!/usr/bin/env node -// Bun API polyfill for Node.js runtime -if (typeof globalThis.Bun === "undefined") { - const { execFileSync } = await import("child_process"); - const { resolve, delimiter } = await import("path"); - const { accessSync, constants: { X_OK } } = await import("fs"); - function which(bin) { - const isWin = process.platform === "win32"; - const pathExt = isWin ? (process.env.PATHEXT || ".EXE").split(";") : [""]; - for (const dir of (process.env.PATH || "").split(delimiter)) { - for (const ext of pathExt) { - const candidate = resolve(dir, bin + ext); - try { accessSync(candidate, X_OK); return candidate; } catch {} - } - } - return null; - } - // Bun.$ is the shell template tag (e.g. $\`osascript ...\`). Only used by - // computer-use-input/darwin — stub it so the top-level destructuring - // \`var { $ } = globalThis.Bun\` doesn't crash. - function $(parts, ...args) { - throw new Error("Bun.$ shell API is not available in Node.js. Use Bun runtime for this feature."); - } - function hash(data, seed) { - let h = ((seed || 0) ^ 0x811c9dc5) >>> 0; - for (let i = 0; i < data.length; i++) { - h ^= data.charCodeAt(i); - h = Math.imul(h, 0x01000193) >>> 0; - } - return h; - } - globalThis.Bun = { which, $, hash }; -} -import "./cli.js" -` -await writeFile(cliNode, NODE_BUN_POLYFILL) -// NOTE: when new Bun-specific globals appear in bundled output, add them here. +await writeFile(cliNode, '#!/usr/bin/env node\nimport "./cli.js"\n') // Make both executable const { chmodSync } = await import('fs') diff --git a/bun.lock b/bun.lock index eaa1a6b9f..f6621ad24 100644 --- a/bun.lock +++ b/bun.lock @@ -58,10 +58,11 @@ "@sentry/node": "^10.47.0", "@smithy/core": "^3.23.13", "@smithy/node-http-handler": "^4.5.1", - "@types/bun": "^1.3.11", + "@types/bun": "^1.3.12", "@types/cacache": "^20.0.1", "@types/he": "^1.2.3", "@types/lodash-es": "^4.17.12", + "@types/node": "^25.6.0", "@types/picomatch": "^4.0.3", "@types/plist": "^3.0.5", "@types/proper-lockfile": "^4.1.4", diff --git a/package.json b/package.json index f5d6b1d4d..7b3374247 100644 --- a/package.json +++ b/package.json @@ -59,10 +59,6 @@ "@claude-code-best/mcp-chrome-bridge": "^2.0.7" }, "devDependencies": { - "@types/he": "^1.2.3", - "@langfuse/otel": "^5.1.0", - "@langfuse/tracing": "^5.1.0", - "@types/lodash-es": "^4.17.12", "@alcalzone/ansi-tokenize": "^0.3.0", "@ant/claude-for-chrome-mcp": "workspace:*", "@ant/computer-use-input": "workspace:*", @@ -76,9 +72,6 @@ "@anthropic-ai/sdk": "^0.80.0", "@anthropic-ai/vertex-sdk": "^0.14.4", "@anthropic/ink": "workspace:*", - "@claude-code-best/builtin-tools": "workspace:*", - "@claude-code-best/agent-tools": "workspace:*", - "@claude-code-best/mcp-client": "workspace:*", "@aws-sdk/client-bedrock": "^3.1020.0", "@aws-sdk/client-bedrock-runtime": "^3.1020.0", "@aws-sdk/client-sts": "^3.1020.0", @@ -86,8 +79,13 @@ "@aws-sdk/credential-providers": "^3.1020.0", "@azure/identity": "^4.13.1", "@biomejs/biome": "^2.4.10", + "@claude-code-best/agent-tools": "workspace:*", + "@claude-code-best/builtin-tools": "workspace:*", + "@claude-code-best/mcp-client": "workspace:*", "@commander-js/extra-typings": "^14.0.0", "@growthbook/growthbook": "^1.6.5", + "@langfuse/otel": "^5.1.0", + "@langfuse/tracing": "^5.1.0", "@modelcontextprotocol/sdk": "^1.29.0", "@opentelemetry/api": "^1.9.1", "@opentelemetry/api-logs": "^0.214.0", @@ -110,8 +108,11 @@ "@sentry/node": "^10.47.0", "@smithy/core": "^3.23.13", "@smithy/node-http-handler": "^4.5.1", - "@types/bun": "^1.3.11", + "@types/bun": "^1.3.12", "@types/cacache": "^20.0.1", + "@types/he": "^1.2.3", + "@types/lodash-es": "^4.17.12", + "@types/node": "^25.6.0", "@types/picomatch": "^4.0.3", "@types/plist": "^3.0.5", "@types/proper-lockfile": "^4.1.4", diff --git a/packages/@ant/computer-use-input/src/backends/darwin.ts b/packages/@ant/computer-use-input/src/backends/darwin.ts index 4f9569d2d..37af38cff 100644 --- a/packages/@ant/computer-use-input/src/backends/darwin.ts +++ b/packages/@ant/computer-use-input/src/backends/darwin.ts @@ -5,9 +5,12 @@ * mouse and keyboard via CoreGraphics events and System Events. */ -import { $ } from 'bun' +import { execFile, execFileSync } from 'child_process' +import { promisify } from 'util' import type { FrontmostAppInfo, InputBackend } from '../types.js' +const execFileAsync = promisify(execFile) + const KEY_MAP: Record = { return: 36, enter: 36, tab: 48, space: 49, delete: 51, backspace: 51, escape: 53, esc: 53, @@ -25,13 +28,17 @@ const MODIFIER_MAP: Record = { } async function osascript(script: string): Promise { - const result = await $`osascript -e ${script}`.quiet().nothrow().text() - return result.trim() + const { stdout } = await execFileAsync('osascript', ['-e', script], { + encoding: 'utf-8', + }) + return stdout.trim() } async function jxa(script: string): Promise { - const result = await $`osascript -l JavaScript -e ${script}`.quiet().nothrow().text() - return result.trim() + const { stdout } = await execFileAsync('osascript', ['-l', 'JavaScript', '-e', script], { + encoding: 'utf-8', + }) + return stdout.trim() } function buildMouseJxa(eventType: string, x: number, y: number, btn: number, clickState?: number): string { @@ -115,19 +122,14 @@ export const typeText: InputBackend['typeText'] = async (text) => { export const getFrontmostAppInfo: InputBackend['getFrontmostAppInfo'] = () => { try { - const result = Bun.spawnSync({ - cmd: ['osascript', '-e', ` - tell application "System Events" - set frontApp to first application process whose frontmost is true - set appName to name of frontApp - set bundleId to bundle identifier of frontApp - return bundleId & "|" & appName - end tell - `], - stdout: 'pipe', - stderr: 'pipe', - }) - const output = new TextDecoder().decode(result.stdout).trim() + const output = execFileSync('osascript', ['-e', ` + tell application "System Events" + set frontApp to first application process whose frontmost is true + set appName to name of frontApp + set bundleId to bundle identifier of frontApp + return bundleId & "|" & appName + end tell + `], { encoding: 'utf-8', stdio: ['pipe', 'pipe', 'ignore'] }).trim() if (!output || !output.includes('|')) return null const [bundleId, appName] = output.split('|', 2) return { bundleId: bundleId!, appName: appName! }