From 2f014f5c0cede614c2a7314b1747030c365140fe Mon Sep 17 00:00:00 2001 From: gsxdsm Date: Wed, 22 Jul 2026 13:37:44 -0700 Subject: [PATCH] fix(cli): install agent-browser across Windows, Linux, and macOS (#2405) ## Summary Fusion npm installs now expose a working `agent-browser` command on Windows, Linux, and macOS. Fusion publishes a top-level shim backed by the exact pinned native package, preserving the existing Browser Verification probe contract on every supported desktop OS. ## Validation - Added a packed consumer-install matrix for `ubuntu-latest`, `macos-latest`, and `windows-latest`. - Each platform executes npm's generated shim, verifies `agent-browser 0.26.0`, and asserts the matching native binary is installed. - Confirmed the packed Fusion manifest, installed dependency, and command output all retain the exact declared version pin. - Passed 101 focused CLI workflow/package tests, full workspace lint and typecheck, strict changeset validation, and a real macOS ARM64 packed-install smoke. ## Post-Deploy Monitoring & Validation - Search task logs for `agent-browser not found on PATH`, `Missing native executable`, and Browser Verification availability warnings. - Healthy signal: npm installs on Windows, Linux, and macOS resolve `agent-browser --version` without missing-shim or native-payload errors. - Failure signal: command resolution errors, version-pin mismatches, missing native payloads, or increased browser-verification fast-bails. - Validation window: first release cycle after publish; owner: Fusion maintainers. - Mitigation: revert the dependency and top-level shim if install compatibility regresses. --- [![Compound Engineering](https://img.shields.io/badge/Built_with-Compound_Engineering-6366f1)](https://github.com/EveryInc/compound-engineering-plugin) ## Summary by CodeRabbit * **New Features** * Added cross-platform `agent-browser` installation through Fusion CLI. * Exposed an `agent-browser` command for Windows, macOS, and Linux. * Pinned the included `agent-browser` version to ensure consistent installations. * **Bug Fixes** * Improved reliability of platform-specific executable selection and command setup across supported operating systems. --- .changeset/install-agent-browser-windows.md | 7 + .github/workflows/agent-browser-install.yml | 151 ++++++++++++++++++ packages/cli/agent-browser.mjs | 8 + packages/cli/package.json | 3 + .../cli/src/__tests__/ci-workflow.test.ts | 45 ++++++ .../cli/src/__tests__/package-config.test.ts | 17 ++ pnpm-lock.yaml | 9 ++ 7 files changed, 240 insertions(+) create mode 100644 .changeset/install-agent-browser-windows.md create mode 100644 .github/workflows/agent-browser-install.yml create mode 100755 packages/cli/agent-browser.mjs diff --git a/.changeset/install-agent-browser-windows.md b/.changeset/install-agent-browser-windows.md new file mode 100644 index 0000000000..789c05b354 --- /dev/null +++ b/.changeset/install-agent-browser-windows.md @@ -0,0 +1,7 @@ +--- +"@runfusion/fusion": patch +--- + +summary: Install the agent-browser binary with Fusion on Windows, Linux, and macOS. +category: fix +dev: Pins agent-browser and publishes a top-level bin shim that forwards to its native platform binary. diff --git a/.github/workflows/agent-browser-install.yml b/.github/workflows/agent-browser-install.yml new file mode 100644 index 0000000000..34b9f75612 --- /dev/null +++ b/.github/workflows/agent-browser-install.yml @@ -0,0 +1,151 @@ +name: Agent-browser install + +# FNXC:AgentBrowserPackaging 2026-07-22-13:25: +# Exercise the published npm boundary on every supported desktop OS because +# source-tree tests cannot prove the platform shim and native binary survive a +# packed consumer install. +on: + pull_request: + branches: [main] + paths: + - ".github/workflows/agent-browser-install.yml" + - "packages/cli/agent-browser.mjs" + - "packages/cli/package.json" + - "packages/cli/scripts/prepare-publish-manifest.mjs" + +concurrency: + group: agent-browser-install-${{ github.ref }} + cancel-in-progress: true + +permissions: + contents: read + +env: + FORCE_JAVASCRIPT_ACTIONS_TO_NODE24: "true" + +jobs: + pack-fixture: + name: Pack agent-browser install fixture + runs-on: ubuntu-latest + timeout-minutes: 5 + + steps: + - name: Checkout + uses: actions/checkout@v4 + with: + persist-credentials: false + + - name: Setup Node.js and pnpm + uses: ./.github/actions/setup-node-pnpm + with: + skip-install: "true" + + - name: Pack Fusion CLI and agent-browser + shell: bash + working-directory: packages/cli + run: | + pack_dir="$RUNNER_TEMP/fusion-agent-browser-pack" + mkdir -p "$pack_dir" + pnpm pack --pack-destination "$pack_dir" + agent_browser_version="$(node -p 'require("./package.json").dependencies["agent-browser"]')" + node -e "const version = process.argv[1]; if (!/^\\d+\\.\\d+\\.\\d+(?:-[0-9A-Za-z.-]+)?$/.test(version)) throw new Error('agent-browser must use an exact version pin')" "$agent_browser_version" + npm pack "agent-browser@$agent_browser_version" --ignore-scripts --pack-destination "$pack_dir" + node -e "require('node:fs').writeFileSync(process.argv[1], process.argv[2] + '\n')" "$pack_dir/agent-browser-version.txt" "$agent_browser_version" + + - name: Upload packed install fixture + uses: actions/upload-artifact@v4 + with: + name: agent-browser-install-pack + path: | + ${{ runner.temp }}/fusion-agent-browser-pack/*.tgz + ${{ runner.temp }}/fusion-agent-browser-pack/agent-browser-version.txt + retention-days: 1 + + install-smoke: + name: Agent-browser install (${{ matrix.os }}) + needs: pack-fixture + runs-on: ${{ matrix.os }} + timeout-minutes: 15 + strategy: + fail-fast: false + matrix: + os: [ubuntu-latest, macos-latest, windows-latest] + + steps: + - name: Setup Node.js + uses: actions/setup-node@v5 + with: + node-version: "24" + + - name: Download packed install fixture + uses: actions/download-artifact@v4 + with: + name: agent-browser-install-pack + path: ${{ runner.temp }}/fusion-agent-browser-pack + + - name: Install packed CLI and execute npm shim + shell: pwsh + run: | + $packDir = Join-Path $env:RUNNER_TEMP "fusion-agent-browser-pack" + $fixtureDir = Join-Path $env:RUNNER_TEMP "fusion-agent-browser-fixture" + $fixturePackDir = Join-Path $env:RUNNER_TEMP "fusion-agent-browser-fixture-pack" + $installDir = Join-Path $env:RUNNER_TEMP "fusion-agent-browser-consumer" + $tarball = Get-ChildItem $packDir -Filter "runfusion-fusion-*.tgz" | Select-Object -First 1 + if (!$tarball) { Write-Error "Packed Fusion CLI tarball was not created"; exit 1 } + $agentBrowserTarball = Get-ChildItem $packDir -Filter "agent-browser-*.tgz" | Select-Object -First 1 + if (!$agentBrowserTarball) { Write-Error "Packed agent-browser dependency was not created"; exit 1 } + $expectedVersion = (Get-Content (Join-Path $packDir "agent-browser-version.txt") -Raw).Trim() + + New-Item -ItemType Directory -Force -Path $fixtureDir | Out-Null + New-Item -ItemType Directory -Force -Path $fixturePackDir | Out-Null + New-Item -ItemType Directory -Force -Path $installDir | Out-Null + tar -xzf $tarball.FullName -C $fixtureDir + if ($LASTEXITCODE -ne 0) { exit $LASTEXITCODE } + + # FNXC:AgentBrowserPackaging 2026-07-22-13:25: + # Derive a minimal fixture from the real Fusion tarball to exercise its + # launcher and bin declaration without installing unrelated dependencies. + $fixturePackage = Join-Path $fixtureDir "package" + node -e "const { existsSync, readFileSync, writeFileSync } = require('node:fs'); const { join } = require('node:path'); const root = process.argv[1]; const dep = process.argv[2].replaceAll('\\', '/'); const expected = process.argv[3]; const file = join(root, 'package.json'); const pkg = JSON.parse(readFileSync(file, 'utf8')); if (pkg.dependencies?.['agent-browser'] !== expected) throw new Error('Packed Fusion manifest lost the exact agent-browser pin'); if (pkg.bin?.['agent-browser'] !== './agent-browser.mjs') throw new Error('Packed Fusion manifest lost the agent-browser bin'); if (!existsSync(join(root, 'agent-browser.mjs'))) throw new Error('Packed Fusion tarball omitted agent-browser.mjs'); pkg.dependencies = { 'agent-browser': 'file:' + dep }; delete pkg.devDependencies; delete pkg.peerDependencies; delete pkg.peerDependenciesMeta; delete pkg.scripts; writeFileSync(file, JSON.stringify(pkg, null, 2) + '\n');" $fixturePackage $agentBrowserTarball.FullName $expectedVersion + if ($LASTEXITCODE -ne 0) { exit $LASTEXITCODE } + npm pack $fixturePackage --ignore-scripts --pack-destination $fixturePackDir + if ($LASTEXITCODE -ne 0) { exit $LASTEXITCODE } + $fixtureTarball = Get-ChildItem $fixturePackDir -Filter "runfusion-fusion-*.tgz" | Select-Object -First 1 + if (!$fixtureTarball) { Write-Error "Minimal Fusion install fixture was not created"; exit 1 } + + Push-Location $installDir + try { + npm init --yes | Out-Null + npm install --ignore-scripts --no-audit --no-fund --install-strategy=nested $fixtureTarball.FullName + if ($LASTEXITCODE -ne 0) { exit $LASTEXITCODE } + + $shimName = if ($IsWindows) { "agent-browser.cmd" } else { "agent-browser" } + $shim = Join-Path $installDir "node_modules/.bin/$shimName" + if (!(Test-Path $shim)) { Write-Error "npm did not generate $shimName"; exit 1 } + if ($IsWindows) { + $normalizedShim = (Get-Content $shim -Raw).Replace("\", "/") + if (!$normalizedShim.Contains("@runfusion/fusion/agent-browser.mjs")) { + Write-Error "agent-browser.cmd does not target Fusion's packed launcher" + exit 1 + } + } else { + node -e "const { readlinkSync } = require('node:fs'); const target = readlinkSync(process.argv[1]).replaceAll('\\', '/'); if (target !== '../@runfusion/fusion/agent-browser.mjs') { console.error('agent-browser does not target Fusion packed launcher: ' + target); process.exit(1); }" $shim + if ($LASTEXITCODE -ne 0) { exit $LASTEXITCODE } + } + $version = (& $shim --version | Out-String).Trim() + if ($LASTEXITCODE -ne 0) { exit $LASTEXITCODE } + $installedVersion = (node -e "const { readFileSync } = require('node:fs'); const { createRequire } = require('node:module'); const fromFusion = createRequire(require.resolve('@runfusion/fusion/package.json')); console.log(JSON.parse(readFileSync(fromFusion.resolve('agent-browser/package.json'), 'utf8')).version)" | Out-String).Trim() + if ($installedVersion -ne $expectedVersion) { + Write-Error "Installed agent-browser $installedVersion does not match declared pin $expectedVersion" + exit 1 + } + if ($version -ne "agent-browser $expectedVersion") { + Write-Error "Unexpected agent-browser version: $version" + exit 1 + } + + node -e "const { existsSync } = require('node:fs'); const { createRequire } = require('node:module'); const { dirname, join } = require('node:path'); const fromFusion = createRequire(require.resolve('@runfusion/fusion/package.json')); const pkg = fromFusion.resolve('agent-browser/package.json'); const suffix = process.platform === 'win32' ? 'win32-x64.exe' : process.platform + '-' + process.arch; const native = join(dirname(pkg), 'bin', 'agent-browser-' + suffix); if (!existsSync(native)) { console.error('Missing native executable: ' + native); process.exit(1); } console.log(native);" + if ($LASTEXITCODE -ne 0) { exit $LASTEXITCODE } + } finally { + Pop-Location + } diff --git a/packages/cli/agent-browser.mjs b/packages/cli/agent-browser.mjs new file mode 100755 index 0000000000..09bccb9c84 --- /dev/null +++ b/packages/cli/agent-browser.mjs @@ -0,0 +1,8 @@ +#!/usr/bin/env node + +/* +FNXC:AgentBrowserPackaging 2026-07-22-12:19: +Publish this top-level bin shim with Fusion so npm can expose agent-browser and +delegate to the pinned dependency's platform-aware launcher. +*/ +await import("agent-browser/bin/agent-browser.js"); diff --git a/packages/cli/package.json b/packages/cli/package.json index 38ef57b3f8..d195733996 100644 --- a/packages/cli/package.json +++ b/packages/cli/package.json @@ -12,6 +12,7 @@ "pi-package" ], "bin": { + "agent-browser": "./agent-browser.mjs", "fn": "./bin.mjs", "fusion": "./bin.mjs" }, @@ -28,6 +29,7 @@ "access": "public" }, "files": [ + "agent-browser.mjs", "bin.mjs", "dist/**/*.js", "dist/**/*.d.ts", @@ -64,6 +66,7 @@ }, "dependencies": { "@agentclientprotocol/sdk": "0.24.0", + "agent-browser": "0.26.0", "@earendil-works/pi-ai": "0.81.1", "@earendil-works/pi-coding-agent": "0.81.1", "claude-code-cli-acp": "0.1.1", diff --git a/packages/cli/src/__tests__/ci-workflow.test.ts b/packages/cli/src/__tests__/ci-workflow.test.ts index bfa4c5b640..7c880aaaf8 100644 --- a/packages/cli/src/__tests__/ci-workflow.test.ts +++ b/packages/cli/src/__tests__/ci-workflow.test.ts @@ -635,6 +635,51 @@ describe("Test-release workflow (.github/workflows/test-release.yml)", () => { }); }); +describe("Cross-platform agent-browser install workflow", () => { + let workflow: any; + let content: string; + + beforeAll(() => { + const result = loadWorkflow("agent-browser-install.yml"); + workflow = result.parsed; + content = result.content; + }); + + it("runs packed consumer installs on Windows, Linux, and macOS for relevant pull requests", () => { + expect(workflow.on?.pull_request?.branches).toContain("main"); + expect(workflow.on?.pull_request?.paths).toContain(".github/workflows/agent-browser-install.yml"); + expect(workflow.on?.pull_request?.paths).toContain("packages/cli/agent-browser.mjs"); + expect(workflow.on?.pull_request?.paths).toContain("packages/cli/package.json"); + expect(workflow.on?.pull_request?.paths).toContain("packages/cli/scripts/prepare-publish-manifest.mjs"); + const packFixture = workflow.jobs?.["pack-fixture"]; + const installSmoke = workflow.jobs?.["install-smoke"]; + expect(packFixture?.["runs-on"]).toBe("ubuntu-latest"); + expect(findCompositeSetupStep(packFixture?.steps ?? [])?.with?.["skip-install"]).toBe("true"); + expect(installSmoke?.needs).toBe("pack-fixture"); + expect(installSmoke?.["runs-on"]).toBe("${{ matrix.os }}"); + expect(installSmoke?.strategy?.matrix?.os).toEqual(["ubuntu-latest", "macos-latest", "windows-latest"]); + expect(findCompositeSetupStep(installSmoke?.steps ?? [])).toBeUndefined(); + }); + + it("executes npm's generated platform shim and checks the matching native executable", () => { + expect(content).toContain("pnpm pack --pack-destination"); + expect(content).toContain('dependencies["agent-browser"]'); + expect(content).toContain("agent-browser-version.txt"); + expect(content).toContain("actions/upload-artifact@v4"); + expect(content).toContain("actions/download-artifact@v4"); + expect(content).toContain("Packed Fusion manifest lost the exact agent-browser pin"); + expect(content).toContain("Packed Fusion manifest lost the agent-browser bin"); + expect(content).toContain("Packed Fusion tarball omitted agent-browser.mjs"); + expect(content).toContain("npm install --ignore-scripts --no-audit --no-fund --install-strategy=nested"); + expect(content).toContain('$shimName = if ($IsWindows) { "agent-browser.cmd" } else { "agent-browser" }'); + expect(content).toContain('"node_modules/.bin/$shimName"'); + expect(content).toContain("& $shim --version"); + expect(content).toContain("does not match declared pin"); + expect(content).toContain("Missing native executable:"); + expect(content).toContain("process.platform + '-' + process.arch"); + }); +}); + describe("Code signing — Release workflow secrets", () => { let content: string; diff --git a/packages/cli/src/__tests__/package-config.test.ts b/packages/cli/src/__tests__/package-config.test.ts index e70af82575..a234c74094 100644 --- a/packages/cli/src/__tests__/package-config.test.ts +++ b/packages/cli/src/__tests__/package-config.test.ts @@ -158,6 +158,23 @@ describe("CLI package.json publishing config", () => { expect(deps).toContain("ioredis"); }); + /* + FNXC:AgentBrowserPackaging 2026-07-22-13:25: + Keep this unit test scoped to publish-manifest wiring. The cross-platform + agent-browser install workflow is authoritative for packed consumer installs, + npm-generated platform launchers, and native executable invariants. + */ + it("preserves agent-browser publish-manifest wiring", () => { + const publishedPkg = applyPrepackTransform(pkg); + + expect(pkg.dependencies?.["agent-browser"]).toBe("0.26.0"); + expect(publishedPkg.dependencies?.["agent-browser"]).toBe(pkg.dependencies["agent-browser"]); + expect(pkg.bin?.["agent-browser"]).toBe("./agent-browser.mjs"); + expect(publishedPkg.bin?.["agent-browser"]).toBe(pkg.bin["agent-browser"]); + expect(pkg.files).toContain("agent-browser.mjs"); + expect(publishedPkg.files).toContain("agent-browser.mjs"); + }); + /** * FNXC:Packaging 2026-06-13-16:36: * Standalone npm/pnpm installs may omit a package when the published manifest declares it as both a runtime dependency and an optional peer. Keep the pi runtime packages as plain dependencies so dist/bin.js and dist/extension.js can resolve their static imports outside the monorepo, while leaving typebox as the optional-peer control because Fusion does not import it at runtime. diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 2947beb70e..cab782a234 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -59,6 +59,9 @@ importers: '@earendil-works/pi-coding-agent': specifier: 0.81.1 version: 0.81.1(@modelcontextprotocol/sdk@1.28.0(zod@4.4.3))(ws@8.20.0)(zod@4.4.3) + agent-browser: + specifier: 0.26.0 + version: 0.26.0 claude-code-cli-acp: specifier: 0.1.1 version: 0.1.1 @@ -3778,6 +3781,10 @@ packages: resolution: {integrity: sha512-MnA+YT8fwfJPgBx3m60MNqakm30XOkyIoH1y6huTQvC0PwZG7ki8NacLBcrPbNoo8vEZy7Jpuk7+jMO+CUovTQ==} engines: {node: '>= 14'} + agent-browser@0.26.0: + resolution: {integrity: sha512-pdqSfjwbFSp+qnwlb2g23e9wXveIOfMi19xpPA9xZUbzEAUp6W4YBZj6Ybj8z4M7WkcbGDDYc+oDIHDt9R3EDQ==} + hasBin: true + ajv-formats@3.0.1: resolution: {integrity: sha512-8iUql50EUR+uUcdRQ3HDqa6EVyo3docL8g5WJ3FNcWmu62IbkGUue/pEyLBW8VGKKucTPgqeks4fIU1DA4yowQ==} peerDependencies: @@ -11137,6 +11144,8 @@ snapshots: agent-base@7.1.4: {} + agent-browser@0.26.0: {} + ajv-formats@3.0.1(ajv@8.18.0): optionalDependencies: ajv: 8.18.0