From cc7d9dba83c5bbd1507586b2da8cdf8d79149390 Mon Sep 17 00:00:00 2001 From: 0okay <45843383+0okay@users.noreply.github.com> Date: Wed, 14 Jan 2026 11:49:20 +0800 Subject: [PATCH 1/2] Refactor connection logic for Windows and hash calculationfix(cli): fix windows daemon startup and port calculation inconsistency --- cli/src/connection.rs | 15 +++++++-------- 1 file changed, 7 insertions(+), 8 deletions(-) diff --git a/cli/src/connection.rs b/cli/src/connection.rs index 5cd3a8dd..34480d37 100644 --- a/cli/src/connection.rs +++ b/cli/src/connection.rs @@ -104,7 +104,9 @@ fn get_port_for_session(session: &str) -> u16 { for c in session.chars() { hash = ((hash << 5).wrapping_sub(hash)).wrapping_add(c as i32); } - 49152 + ((hash.abs() as u16) % 16383) + // Correct logic: first take absolute modulo, then cast to u16 + // Using unsigned_abs() to safely handle i32::MIN + 49152 + ((hash.unsigned_abs() as u32 % 16383) as u16) } #[cfg(unix)] @@ -227,13 +229,10 @@ pub fn ensure_daemon( { use std::os::windows::process::CommandExt; - // On Windows, use cmd.exe to run node to ensure proper PATH resolution. - // This handles cases where node.exe isn't directly in PATH but node.cmd is. - // Pass the entire command as a single string to /c to handle paths with spaces. - let cmd_string = format!("node \"{}\"", daemon_path.display()); - let mut cmd = Command::new("cmd"); - cmd.arg("/c") - .arg(&cmd_string) + // On Windows, call node directly. Command::new handles PATH resolution (node.exe or node.cmd) + // and automatically quotes arguments containing spaces. + let mut cmd = Command::new("node"); + cmd.arg(daemon_path) .env("AGENT_BROWSER_DAEMON", "1") .env("AGENT_BROWSER_SESSION", session); From 307c06502abd164580a4f65ebec95b188b464aef Mon Sep 17 00:00:00 2001 From: 0okay <1170488531@qq.com> Date: Tue, 3 Feb 2026 19:59:37 +0800 Subject: [PATCH 2/2] fix: auto-start daemon on Windows to resolve startup failure --- bin/agent-browser.js | 152 +++++++++++++++++++++++++++++++++++++++++++ package.json | 2 +- 2 files changed, 153 insertions(+), 1 deletion(-) create mode 100644 bin/agent-browser.js diff --git a/bin/agent-browser.js b/bin/agent-browser.js new file mode 100644 index 00000000..370581fa --- /dev/null +++ b/bin/agent-browser.js @@ -0,0 +1,152 @@ +#!/usr/bin/env node + +/** + * Cross-platform CLI wrapper for agent-browser + * + * This wrapper enables npx support on Windows where shell scripts don't work. + * For global installs, postinstall.js patches the shims to invoke the native + * binary directly (zero overhead). + */ + +import { spawn } from 'child_process'; +import { existsSync, accessSync, chmodSync, constants } from 'fs'; +import { dirname, join } from 'path'; +import { fileURLToPath } from 'url'; +import { platform, arch, homedir } from 'os'; + +const __dirname = dirname(fileURLToPath(import.meta.url)); + +// [Fix] Auto-start daemon on Windows if missing +async function ensureDaemonRunning() { + if (platform() !== 'win32') return; + + const agentDir = join(homedir(), '.agent-browser'); + const portFile = join(agentDir, 'default.port'); + + if (existsSync(portFile)) return; + + // console.log('[Auto-Fix] Starting agent-browser daemon...'); + // The daemon is located in ../dist/daemon.js relative to this script + const daemonScript = join(__dirname, '../dist/daemon.js'); + + if (!existsSync(daemonScript)) { + // If dist doesn't exist (e.g. dev mode), try src (via tsx maybe?) + // but in production/installed package dist should exist. + return; + } + + try { + const child = spawn(process.execPath, [daemonScript], { + detached: true, + stdio: 'ignore', + windowsHide: true + }); + child.unref(); + + // Wait up to 3s for startup + const start = Date.now(); + while (Date.now() - start < 3000) { + if (existsSync(portFile)) break; + await new Promise(r => setTimeout(r, 100)); + } + } catch (e) { + // Ignore spawn errors, let the native binary try its own startup + } +} + +// Map Node.js platform/arch to binary naming convention +function getBinaryName() { + const os = platform(); + const cpuArch = arch(); + + let osKey; + switch (os) { + case 'darwin': + osKey = 'darwin'; + break; + case 'linux': + osKey = 'linux'; + break; + case 'win32': + osKey = 'win32'; + break; + default: + return null; + } + + let archKey; + switch (cpuArch) { + case 'x64': + case 'x86_64': + archKey = 'x64'; + break; + case 'arm64': + case 'aarch64': + archKey = 'arm64'; + break; + default: + return null; + } + + const ext = os === 'win32' ? '.exe' : ''; + return `agent-browser-${osKey}-${archKey}${ext}`; +} + +async function main() { + await ensureDaemonRunning(); + + const binaryName = getBinaryName(); + + if (!binaryName) { + console.error(`Error: Unsupported platform: ${platform()}-${arch()}`); + process.exit(1); + } + + const binaryPath = join(__dirname, binaryName); + + // If native binary is missing, we could try to run the Node.js version directly? + // But for now let's stick to the original logic which expects the binary. + + if (!existsSync(binaryPath)) { + console.error(`Error: No binary found for ${platform()}-${arch()}`); + console.error(`Expected: ${binaryPath}`); + console.error(''); + console.error('Run "npm run build:native" to build for your platform,'); + console.error('or reinstall the package to trigger the postinstall download.'); + process.exit(1); + } + + // Ensure binary is executable (fixes EACCES on macOS/Linux when postinstall didn't run, + // e.g., when using bun which blocks lifecycle scripts by default) + if (platform() !== 'win32') { + try { + accessSync(binaryPath, constants.X_OK); + } catch { + // Binary exists but isn't executable - fix it + try { + chmodSync(binaryPath, 0o755); + } catch (chmodErr) { + console.error(`Error: Cannot make binary executable: ${chmodErr.message}`); + console.error('Try running: chmod +x ' + binaryPath); + process.exit(1); + } + } + } + + // Spawn the native binary with inherited stdio + const child = spawn(binaryPath, process.argv.slice(2), { + stdio: 'inherit', + windowsHide: false, + }); + + child.on('error', (err) => { + console.error(`Error executing binary: ${err.message}`); + process.exit(1); + }); + + child.on('close', (code) => { + process.exit(code ?? 0); + }); +} + +main(); diff --git a/package.json b/package.json index 545f39d7..df92c549 100644 --- a/package.json +++ b/package.json @@ -11,7 +11,7 @@ "skills" ], "bin": { - "agent-browser": "./bin/agent-browser" + "agent-browser": "./bin/agent-browser.js" }, "scripts": { "prepare": "husky",