From ec914f6b68c2148766b94373b99930a6866becf0 Mon Sep 17 00:00:00 2001 From: Will Washburn Date: Wed, 22 Apr 2026 19:45:57 -0400 Subject: [PATCH 1/8] feat(sdk): split broker binaries into per-platform optional-dep packages MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Publish the agent-relay-broker binary as five platform-specific npm packages (@agent-relay/broker-{darwin-arm64,darwin-x64,linux-arm64, linux-x64,win32-x64}) declared as optionalDependencies of @agent-relay/sdk. Package managers now install exactly one broker that matches the user's os/cpu, shrinking the fresh-install footprint from ~55MB to ~13MB and opening the door to Windows support. getBrokerBinaryPath() resolves the binary via require.resolve of the matching optional-dep package as its primary production path. Existing bundled/ancestor fallbacks remain for one release cycle so mixed-version installs keep working. When nothing resolves, spawn() now throws a clear error that names the expected package and the detected platform/arch instead of an inscrutable ENOENT. The top-level agent-relay CLI stops bundling bin/agent-relay-broker-* in its tarball (the bundled SDK still ships broker binaries for one release cycle as a belt-and-braces fallback), and scripts/postinstall.js no longer installs the broker — the runtime resolver handles it. Publish workflow: - build-broker matrix adds x86_64-pc-windows-msvc. - new publish-broker-packages matrix job publishes each per-platform package before the SDK, with a 3-attempt retry wrapper so registry flakes don't strand a partial release. - publish-packages (SDK) and publish-sdk-only wait for it. - publish-main drops its broker-binary bundling step. Closes #770 Co-Authored-By: Claude Opus 4.7 (1M context) --- .github/workflows/publish.yml | 181 +++++++++++++++++--- package-lock.json | 196 ++++++---------------- package.json | 6 +- packages/broker-darwin-arm64/README.md | 11 ++ packages/broker-darwin-arm64/bin/.gitkeep | 0 packages/broker-darwin-arm64/package.json | 19 +++ packages/broker-darwin-x64/README.md | 11 ++ packages/broker-darwin-x64/bin/.gitkeep | 0 packages/broker-darwin-x64/package.json | 19 +++ packages/broker-linux-arm64/README.md | 12 ++ packages/broker-linux-arm64/bin/.gitkeep | 0 packages/broker-linux-arm64/package.json | 19 +++ packages/broker-linux-x64/README.md | 12 ++ packages/broker-linux-x64/bin/.gitkeep | 0 packages/broker-linux-x64/package.json | 19 +++ packages/broker-win32-x64/README.md | 11 ++ packages/broker-win32-x64/bin/.gitkeep | 0 packages/broker-win32-x64/package.json | 19 +++ packages/sdk/package.json | 7 + packages/sdk/src/broker-path.ts | 119 ++++++++++--- packages/sdk/src/client.ts | 11 +- scripts/postinstall.js | 155 +---------------- 22 files changed, 498 insertions(+), 329 deletions(-) create mode 100644 packages/broker-darwin-arm64/README.md create mode 100644 packages/broker-darwin-arm64/bin/.gitkeep create mode 100644 packages/broker-darwin-arm64/package.json create mode 100644 packages/broker-darwin-x64/README.md create mode 100644 packages/broker-darwin-x64/bin/.gitkeep create mode 100644 packages/broker-darwin-x64/package.json create mode 100644 packages/broker-linux-arm64/README.md create mode 100644 packages/broker-linux-arm64/bin/.gitkeep create mode 100644 packages/broker-linux-arm64/package.json create mode 100644 packages/broker-linux-x64/README.md create mode 100644 packages/broker-linux-x64/bin/.gitkeep create mode 100644 packages/broker-linux-x64/package.json create mode 100644 packages/broker-win32-x64/README.md create mode 100644 packages/broker-win32-x64/bin/.gitkeep create mode 100644 packages/broker-win32-x64/package.json diff --git a/.github/workflows/publish.yml b/.github/workflows/publish.yml index bba09c96d..039c8500d 100644 --- a/.github/workflows/publish.yml +++ b/.github/workflows/publish.yml @@ -82,16 +82,29 @@ jobs: include: - os: macos-latest target: aarch64-apple-darwin + broker_pkg: broker-darwin-arm64 binary_name: agent-relay-broker-darwin-arm64 + binary_file: agent-relay-broker - os: macos-latest target: x86_64-apple-darwin + broker_pkg: broker-darwin-x64 binary_name: agent-relay-broker-darwin-x64 + binary_file: agent-relay-broker - os: ubuntu-latest target: x86_64-unknown-linux-musl + broker_pkg: broker-linux-x64 binary_name: agent-relay-broker-linux-x64 + binary_file: agent-relay-broker - os: ubuntu-latest target: aarch64-unknown-linux-musl + broker_pkg: broker-linux-arm64 binary_name: agent-relay-broker-linux-arm64 + binary_file: agent-relay-broker + - os: windows-latest + target: x86_64-pc-windows-msvc + broker_pkg: broker-win32-x64 + binary_name: agent-relay-broker-win32-x64.exe + binary_file: agent-relay-broker.exe steps: - name: Checkout code @@ -117,19 +130,35 @@ jobs: with: key: broker-${{ matrix.target }} - - name: Build broker binary + - name: Build broker binary (unix) + if: runner.os != 'Windows' run: | if [[ "${{ matrix.target }}" == "aarch64-unknown-linux-musl" ]]; then RUSTFLAGS="-C target-feature=+crt-static" cross build --release --bin agent-relay-broker --target ${{ matrix.target }} else RUSTFLAGS="-C target-feature=+crt-static" cargo build --release --bin agent-relay-broker --target ${{ matrix.target }} fi - strip target/${{ matrix.target }}/release/agent-relay-broker 2>/dev/null || true + strip target/${{ matrix.target }}/release/${{ matrix.binary_file }} 2>/dev/null || true - - name: Copy binary with platform name + - name: Build broker binary (windows) + if: runner.os == 'Windows' + shell: pwsh + run: | + $env:RUSTFLAGS = "-C target-feature=+crt-static" + cargo build --release --bin agent-relay-broker --target ${{ matrix.target }} + + - name: Copy binary with platform name (unix) + if: runner.os != 'Windows' run: | mkdir -p release-binaries - cp target/${{ matrix.target }}/release/agent-relay-broker release-binaries/${{ matrix.binary_name }} + cp target/${{ matrix.target }}/release/${{ matrix.binary_file }} release-binaries/${{ matrix.binary_name }} + + - name: Copy binary with platform name (windows) + if: runner.os == 'Windows' + shell: pwsh + run: | + New-Item -ItemType Directory -Force -Path release-binaries | Out-Null + Copy-Item "target/${{ matrix.target }}/release/${{ matrix.binary_file }}" "release-binaries/${{ matrix.binary_name }}" # Ad-hoc sign macOS binaries at build time so the Python SDK wheel # doesn't have to invoke codesign at install time. @@ -483,10 +512,120 @@ jobs: dist/ retention-days: 1 + # Publish the per-platform broker packages first. @agent-relay/sdk declares + # these as exact-version optionalDependencies, so they must exist on the + # registry at the matching version before the SDK is published — otherwise + # `npm install @agent-relay/sdk@` races the registry for the broker + # package at the same version. + publish-broker-packages: + name: Publish ${{ matrix.broker_pkg }} + needs: [build, build-broker] + runs-on: ubuntu-latest + if: github.event.inputs.package == 'all' || github.event.inputs.package == 'sdk' + strategy: + fail-fast: false + max-parallel: 5 + matrix: + include: + - broker_pkg: broker-darwin-arm64 + binary_name: agent-relay-broker-darwin-arm64 + binary_file: agent-relay-broker + - broker_pkg: broker-darwin-x64 + binary_name: agent-relay-broker-darwin-x64 + binary_file: agent-relay-broker + - broker_pkg: broker-linux-arm64 + binary_name: agent-relay-broker-linux-arm64 + binary_file: agent-relay-broker + - broker_pkg: broker-linux-x64 + binary_name: agent-relay-broker-linux-x64 + binary_file: agent-relay-broker + - broker_pkg: broker-win32-x64 + binary_name: agent-relay-broker-win32-x64.exe + binary_file: agent-relay-broker.exe + + steps: + - name: Checkout code + uses: actions/checkout@v4 + + - name: Setup Node.js + uses: actions/setup-node@v4 + with: + node-version: '22.14.0' + registry-url: 'https://registry.npmjs.org' + + - name: Download build artifacts + uses: actions/download-artifact@v4 + with: + name: build-output + path: . + + - name: Download broker binary + uses: actions/download-artifact@v4 + with: + name: ${{ matrix.binary_name }} + path: /tmp/broker + + - name: Stage broker binary into package tree + run: | + set -euo pipefail + mkdir -p packages/${{ matrix.broker_pkg }}/bin + cp "/tmp/broker/${{ matrix.binary_name }}" "packages/${{ matrix.broker_pkg }}/bin/${{ matrix.binary_file }}" + chmod +x "packages/${{ matrix.broker_pkg }}/bin/${{ matrix.binary_file }}" + ls -lh packages/${{ matrix.broker_pkg }}/bin/ + + - name: Update npm for OIDC support + run: npm install -g npm@latest + + - name: Dry run check + if: github.event.inputs.dry_run == 'true' + working-directory: packages/${{ matrix.broker_pkg }} + run: | + echo "Dry run - would publish @agent-relay/${{ matrix.broker_pkg }}" + npm publish --dry-run --access public --tag ${{ github.event.inputs.tag }} --ignore-scripts + + # Retry up to 3 times — npm registry occasionally flakes and we cannot + # reuse the version on rerun, so transient failures must be absorbed + # here rather than failing the whole release. + - name: Publish to NPM (attempt 1) + id: publish_1 + if: github.event.inputs.dry_run != 'true' + continue-on-error: true + working-directory: packages/${{ matrix.broker_pkg }} + run: npm publish --access public --provenance --tag ${{ github.event.inputs.tag }} --ignore-scripts + + - name: Wait before retry + if: github.event.inputs.dry_run != 'true' && steps.publish_1.outcome == 'failure' + run: sleep 30 + + - name: Publish to NPM (attempt 2) + id: publish_2 + if: github.event.inputs.dry_run != 'true' && steps.publish_1.outcome == 'failure' + continue-on-error: true + working-directory: packages/${{ matrix.broker_pkg }} + run: npm publish --access public --provenance --tag ${{ github.event.inputs.tag }} --ignore-scripts + + - name: Wait before retry + if: github.event.inputs.dry_run != 'true' && steps.publish_2.outcome == 'failure' + run: sleep 60 + + - name: Publish to NPM (attempt 3) + id: publish_3 + if: github.event.inputs.dry_run != 'true' && steps.publish_1.outcome == 'failure' && steps.publish_2.outcome == 'failure' + working-directory: packages/${{ matrix.broker_pkg }} + run: npm publish --access public --provenance --tag ${{ github.event.inputs.tag }} --ignore-scripts + + - name: Fail if all publish attempts failed + if: >- + github.event.inputs.dry_run != 'true' && + steps.publish_1.outcome == 'failure' && + steps.publish_2.outcome == 'failure' && + steps.publish_3.outcome == 'failure' + run: exit 1 + # Publish all packages in parallel (npm publish doesn't need deps published first) publish-packages: name: Publish ${{ matrix.package }} - needs: [build, build-broker] + needs: [build, build-broker, publish-broker-packages] runs-on: ubuntu-latest if: github.event.inputs.package == 'all' strategy: @@ -528,6 +667,10 @@ jobs: name: build-output path: . + # Keep bundling all platform broker binaries into the SDK tarball for + # one release cycle. The SDK prefers the optional-dep package but falls + # back to this bundled copy so mixed-version installs keep working + # during the migration. Delete this step in the next major. - name: Download broker binaries (SDK only) if: matrix.package == 'sdk' uses: actions/download-artifact@v4 @@ -539,7 +682,7 @@ jobs: - name: Make broker binaries executable (SDK only) if: matrix.package == 'sdk' run: | - chmod +x packages/sdk/bin/agent-relay-broker-* + chmod +x packages/sdk/bin/agent-relay-broker-* || true # Remove stale generic binary — SDK resolves platform-specific names at runtime rm -f packages/sdk/bin/agent-relay-broker @@ -603,7 +746,7 @@ jobs: # Publish SDK only (when selected) publish-sdk-only: name: Publish SDK to NPM - needs: [build, build-broker] + needs: [build, build-broker, publish-broker-packages] runs-on: ubuntu-latest if: github.event.inputs.package == 'sdk' @@ -623,6 +766,10 @@ jobs: name: build-output path: . + # Bundled broker binaries in packages/sdk/bin/ are kept for one release + # cycle as a fallback. New installs resolve the binary via the + # per-platform @agent-relay/broker-* optional deps published by the + # publish-broker-packages job above. - name: Download broker binaries uses: actions/download-artifact@v4 with: @@ -632,7 +779,7 @@ jobs: - name: Make broker binaries executable run: | - chmod +x packages/sdk/bin/agent-relay-broker-* + chmod +x packages/sdk/bin/agent-relay-broker-* || true # Remove stale generic binary — SDK resolves platform-specific names at runtime rm -f packages/sdk/bin/agent-relay-broker @@ -1001,17 +1148,10 @@ jobs: name: build-output path: . - - name: Download broker binaries - if: github.event.inputs.package != 'cli-prerelease' - uses: actions/download-artifact@v4 - with: - pattern: agent-relay-broker-* - path: bin/ - merge-multiple: true - - - name: Make binaries executable - if: github.event.inputs.package != 'cli-prerelease' - run: chmod +x bin/agent-relay-broker-* + # NOTE: the root `agent-relay` CLI no longer bundles broker binaries in + # its own tarball. Brokers ship as `@agent-relay/broker--` + # optional-deps of `@agent-relay/sdk` (which is itself bundled here via + # bundledDependencies), so users still get a broker on install. - name: Update npm for OIDC support run: npm install -g npm@latest @@ -1432,6 +1572,7 @@ jobs: - `agent-relay-broker-linux-arm64` - Linux ARM64 - `agent-relay-broker-darwin-x64` - macOS Intel - `agent-relay-broker-darwin-arm64` - macOS Apple Silicon + - `agent-relay-broker-win32-x64.exe` - Windows x86_64 ### relay-acp binaries (Zed editor integration) ACP bridge for Zed editor: @@ -1517,6 +1658,7 @@ jobs: verify-standalone-macos, verify-acp-linux, verify-acp-macos, + publish-broker-packages, publish-packages, publish-brand-only, publish-sdk-py, @@ -1558,6 +1700,7 @@ jobs: echo "| Verify Standalone (macOS) | ${{ needs.verify-standalone-macos.result == 'success' && '✅' || (needs.verify-standalone-macos.result == 'skipped' && '⏭️' || '❌') }} ${{ needs.verify-standalone-macos.result }} |" >> $GITHUB_STEP_SUMMARY echo "| Verify relay-acp (Linux) | ${{ needs.verify-acp-linux.result == 'success' && '✅' || (needs.verify-acp-linux.result == 'skipped' && '⏭️' || '❌') }} ${{ needs.verify-acp-linux.result }} |" >> $GITHUB_STEP_SUMMARY echo "| Verify relay-acp (macOS) | ${{ needs.verify-acp-macos.result == 'success' && '✅' || (needs.verify-acp-macos.result == 'skipped' && '⏭️' || '❌') }} ${{ needs.verify-acp-macos.result }} |" >> $GITHUB_STEP_SUMMARY + echo "| Publish Broker Packages | ${{ needs.publish-broker-packages.result == 'success' && '✅' || (needs.publish-broker-packages.result == 'skipped' && '⏭️' || '❌') }} ${{ needs.publish-broker-packages.result }} |" >> $GITHUB_STEP_SUMMARY echo "| Publish Packages | ${{ needs.publish-packages.result == 'success' && '✅' || (needs.publish-packages.result == 'skipped' && '⏭️' || '❌') }} ${{ needs.publish-packages.result }} |" >> $GITHUB_STEP_SUMMARY echo "| Publish Brand | ${{ needs.publish-brand-only.result == 'success' && '✅' || (needs.publish-brand-only.result == 'skipped' && '⏭️' || '❌') }} ${{ needs.publish-brand-only.result }} |" >> $GITHUB_STEP_SUMMARY echo "| Publish Python SDK | ${{ needs.publish-sdk-py.result == 'success' && '✅' || (needs.publish-sdk-py.result == 'skipped' && '⏭️' || '❌') }} ${{ needs.publish-sdk-py.result }} |" >> $GITHUB_STEP_SUMMARY diff --git a/package-lock.json b/package-lock.json index 40bcefc1b..6e8b8fe90 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "agent-relay", - "version": "4.0.39", + "version": "5.0.0", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "agent-relay", - "version": "4.0.39", + "version": "5.0.0", "bundleDependencies": [ "@agent-relay/cloud", "@agent-relay/config", @@ -23,17 +23,22 @@ "license": "Apache-2.0", "workspaces": [ "packages/*", + "!packages/broker-darwin-arm64", + "!packages/broker-darwin-x64", + "!packages/broker-linux-arm64", + "!packages/broker-linux-x64", + "!packages/broker-win32-x64", "web" ], "dependencies": { - "@agent-relay/cloud": "4.0.39", - "@agent-relay/config": "4.0.39", - "@agent-relay/hooks": "4.0.39", - "@agent-relay/sdk": "4.0.39", - "@agent-relay/telemetry": "4.0.39", - "@agent-relay/trajectory": "4.0.39", - "@agent-relay/user-directory": "4.0.39", - "@agent-relay/utils": "4.0.39", + "@agent-relay/cloud": "5.0.0", + "@agent-relay/config": "5.0.0", + "@agent-relay/hooks": "5.0.0", + "@agent-relay/sdk": "5.0.0", + "@agent-relay/telemetry": "5.0.0", + "@agent-relay/trajectory": "5.0.0", + "@agent-relay/user-directory": "5.0.0", + "@agent-relay/utils": "5.0.0", "@aws-sdk/client-s3": "3.1020.0", "@modelcontextprotocol/sdk": "^1.0.0", "@relayauth/core": "^0.1.2", @@ -15409,10 +15414,10 @@ }, "packages/acp-bridge": { "name": "@agent-relay/acp-bridge", - "version": "4.0.39", + "version": "5.0.0", "license": "Apache-2.0", "dependencies": { - "@agent-relay/sdk": "4.0.39", + "@agent-relay/sdk": "5.0.0", "@agentclientprotocol/sdk": "^0.12.0" }, "bin": { @@ -15428,13 +15433,13 @@ }, "packages/brand": { "name": "@agent-relay/brand", - "version": "4.0.39" + "version": "5.0.0" }, "packages/browser-primitive": { "name": "@agent-relay/browser-primitive", - "version": "4.0.9", + "version": "5.0.0", "dependencies": { - "@agent-relay/sdk": "4.0.9", + "@agent-relay/sdk": "5.0.0", "playwright": "^1.51.1" }, "bin": { @@ -15455,60 +15460,11 @@ "zod-to-json-schema": "^3.23.1" } }, - "packages/browser-primitive/node_modules/@agent-relay/sdk": { - "version": "4.0.9", - "resolved": "https://registry.npmjs.org/@agent-relay/sdk/-/sdk-4.0.9.tgz", - "integrity": "sha512-LoOpjzn0aU13N9FiGhJ9vifmsYfexL76N4LWLC9xmuIQLYuIiCpQvSyHWVje5TwwLb9DmOWAl0hSehALnbb4wg==", - "dependencies": { - "@agent-relay/config": "4.0.9", - "@relaycast/sdk": "^1.1.0", - "@relayfile/sdk": "^0.1.2", - "@sinclair/typebox": "^0.34.48", - "chalk": "^4.1.2", - "ignore": "^7.0.5", - "listr2": "^10.2.1", - "tar": "^7.5.10", - "ws": "^8.18.3", - "yaml": "^2.7.0" - }, - "peerDependencies": { - "@anthropic-ai/claude-agent-sdk": ">=0.1.0", - "@google/adk": ">=0.5.0", - "@langchain/langgraph": ">=1.2.0", - "@mariozechner/pi-coding-agent": ">=0.50.0", - "@openai/agents": ">=0.7.0", - "ai": ">=5.0.0", - "crewai": ">=1.0.0" - }, - "peerDependenciesMeta": { - "@anthropic-ai/claude-agent-sdk": { - "optional": true - }, - "@google/adk": { - "optional": true - }, - "@langchain/langgraph": { - "optional": true - }, - "@mariozechner/pi-coding-agent": { - "optional": true - }, - "@openai/agents": { - "optional": true - }, - "ai": { - "optional": true - }, - "crewai": { - "optional": true - } - } - }, "packages/cloud": { "name": "@agent-relay/cloud", - "version": "4.0.39", + "version": "5.0.0", "dependencies": { - "@agent-relay/config": "4.0.39", + "@agent-relay/config": "5.0.0", "@aws-sdk/client-s3": "3.1020.0", "ignore": "^7.0.5", "tar": "^7.5.10" @@ -15520,7 +15476,7 @@ }, "packages/config": { "name": "@agent-relay/config", - "version": "4.0.39", + "version": "5.0.0", "dependencies": { "zod": "^3.23.8", "zod-to-json-schema": "^3.23.1" @@ -15532,7 +15488,7 @@ }, "packages/credential-proxy": { "name": "@agent-relay/credential-proxy", - "version": "4.0.39", + "version": "5.0.0", "dependencies": { "hono": "^4.11.4", "jose": "^6.1.3" @@ -15543,9 +15499,9 @@ }, "packages/gateway": { "name": "@agent-relay/gateway", - "version": "4.0.39", + "version": "5.0.0", "dependencies": { - "@agent-relay/sdk": "4.0.39" + "@agent-relay/sdk": "5.0.0" }, "devDependencies": { "@types/node": "^22.19.3", @@ -15554,9 +15510,9 @@ }, "packages/github-primitive": { "name": "@agent-relay/github-primitive", - "version": "4.0.9", + "version": "5.0.0", "dependencies": { - "@agent-relay/sdk": "4.0.9" + "@agent-relay/sdk": "5.0.0" }, "devDependencies": { "@types/node": "^22.19.3", @@ -15573,62 +15529,13 @@ "zod-to-json-schema": "^3.23.1" } }, - "packages/github-primitive/node_modules/@agent-relay/sdk": { - "version": "4.0.9", - "resolved": "https://registry.npmjs.org/@agent-relay/sdk/-/sdk-4.0.9.tgz", - "integrity": "sha512-LoOpjzn0aU13N9FiGhJ9vifmsYfexL76N4LWLC9xmuIQLYuIiCpQvSyHWVje5TwwLb9DmOWAl0hSehALnbb4wg==", - "dependencies": { - "@agent-relay/config": "4.0.9", - "@relaycast/sdk": "^1.1.0", - "@relayfile/sdk": "^0.1.2", - "@sinclair/typebox": "^0.34.48", - "chalk": "^4.1.2", - "ignore": "^7.0.5", - "listr2": "^10.2.1", - "tar": "^7.5.10", - "ws": "^8.18.3", - "yaml": "^2.7.0" - }, - "peerDependencies": { - "@anthropic-ai/claude-agent-sdk": ">=0.1.0", - "@google/adk": ">=0.5.0", - "@langchain/langgraph": ">=1.2.0", - "@mariozechner/pi-coding-agent": ">=0.50.0", - "@openai/agents": ">=0.7.0", - "ai": ">=5.0.0", - "crewai": ">=1.0.0" - }, - "peerDependenciesMeta": { - "@anthropic-ai/claude-agent-sdk": { - "optional": true - }, - "@google/adk": { - "optional": true - }, - "@langchain/langgraph": { - "optional": true - }, - "@mariozechner/pi-coding-agent": { - "optional": true - }, - "@openai/agents": { - "optional": true - }, - "ai": { - "optional": true - }, - "crewai": { - "optional": true - } - } - }, "packages/hooks": { "name": "@agent-relay/hooks", - "version": "4.0.39", + "version": "5.0.0", "dependencies": { - "@agent-relay/config": "4.0.39", - "@agent-relay/sdk": "4.0.39", - "@agent-relay/trajectory": "4.0.39" + "@agent-relay/config": "5.0.0", + "@agent-relay/sdk": "5.0.0", + "@agent-relay/trajectory": "5.0.0" }, "devDependencies": { "@types/node": "^22.19.3", @@ -15637,9 +15544,9 @@ }, "packages/memory": { "name": "@agent-relay/memory", - "version": "4.0.39", + "version": "5.0.0", "dependencies": { - "@agent-relay/hooks": "4.0.39" + "@agent-relay/hooks": "5.0.0" }, "devDependencies": { "@types/node": "^22.19.3", @@ -15648,11 +15555,11 @@ }, "packages/openclaw": { "name": "@agent-relay/openclaw", - "version": "4.0.39", + "version": "5.0.0", "hasInstallScript": true, "license": "Apache-2.0", "dependencies": { - "@agent-relay/sdk": "4.0.39", + "@agent-relay/sdk": "5.0.0", "@relaycast/sdk": "^1.0.0", "ws": "^8.0.0" }, @@ -16417,9 +16324,9 @@ }, "packages/policy": { "name": "@agent-relay/policy", - "version": "4.0.39", + "version": "5.0.0", "dependencies": { - "@agent-relay/config": "4.0.39" + "@agent-relay/config": "5.0.0" }, "devDependencies": { "@types/node": "^22.19.3", @@ -16428,9 +16335,9 @@ }, "packages/sdk": { "name": "@agent-relay/sdk", - "version": "4.0.39", + "version": "5.0.0", "dependencies": { - "@agent-relay/config": "4.0.39", + "@agent-relay/config": "5.0.0", "@relaycast/sdk": "^1.1.0", "@relayfile/sdk": ">=0.1.2 <1", "@sinclair/typebox": "^0.34.48", @@ -16447,8 +16354,15 @@ "@types/tar": "^6.1.13", "@types/ws": "^8.5.10" }, + "optionalDependencies": { + "@agent-relay/broker-darwin-arm64": "5.0.0", + "@agent-relay/broker-darwin-x64": "5.0.0", + "@agent-relay/broker-linux-arm64": "5.0.0", + "@agent-relay/broker-linux-x64": "5.0.0", + "@agent-relay/broker-win32-x64": "5.0.0" + }, "peerDependencies": { - "@agent-relay/credential-proxy": "4.0.39", + "@agent-relay/credential-proxy": "5.0.0", "@anthropic-ai/claude-agent-sdk": ">=0.1.0", "@google/adk": ">=0.5.0", "@langchain/langgraph": ">=1.2.0", @@ -16486,7 +16400,7 @@ }, "packages/telemetry": { "name": "@agent-relay/telemetry", - "version": "4.0.39", + "version": "5.0.0", "dependencies": { "posthog-node": "^5.29.2" }, @@ -16521,9 +16435,9 @@ }, "packages/trajectory": { "name": "@agent-relay/trajectory", - "version": "4.0.39", + "version": "5.0.0", "dependencies": { - "@agent-relay/config": "4.0.39" + "@agent-relay/config": "5.0.0" }, "devDependencies": { "@types/node": "^22.19.3", @@ -16532,9 +16446,9 @@ }, "packages/user-directory": { "name": "@agent-relay/user-directory", - "version": "4.0.39", + "version": "5.0.0", "dependencies": { - "@agent-relay/utils": "4.0.39" + "@agent-relay/utils": "5.0.0" }, "devDependencies": { "@types/node": "^22.19.3", @@ -16543,9 +16457,9 @@ }, "packages/utils": { "name": "@agent-relay/utils", - "version": "4.0.39", + "version": "5.0.0", "dependencies": { - "@agent-relay/config": "4.0.39", + "@agent-relay/config": "5.0.0", "compare-versions": "^6.1.1" }, "devDependencies": { diff --git a/package.json b/package.json index 2bff9eff2..91c571c1b 100644 --- a/package.json +++ b/package.json @@ -7,7 +7,6 @@ "types": "dist/src/index.d.ts", "files": [ "dist", - "bin", "install.sh", "scripts/postinstall.js", "scripts/build-cjs.mjs", @@ -87,6 +86,11 @@ }, "workspaces": [ "packages/*", + "!packages/broker-darwin-arm64", + "!packages/broker-darwin-x64", + "!packages/broker-linux-arm64", + "!packages/broker-linux-x64", + "!packages/broker-win32-x64", "web" ], "bundledDependencies": [ diff --git a/packages/broker-darwin-arm64/README.md b/packages/broker-darwin-arm64/README.md new file mode 100644 index 000000000..4b4af6ede --- /dev/null +++ b/packages/broker-darwin-arm64/README.md @@ -0,0 +1,11 @@ +# @agent-relay/broker-darwin-arm64 + +Prebuilt `agent-relay-broker` binary for **macOS (Apple Silicon)**. + +This package is installed automatically as an optional dependency of +[`@agent-relay/sdk`](https://www.npmjs.com/package/@agent-relay/sdk). You do +not need to depend on it directly. The SDK resolves the correct platform +binary at runtime via `require.resolve`. + +See the [agent-relay repository](https://github.com/AgentWorkforce/relay) for +source and build tooling. diff --git a/packages/broker-darwin-arm64/bin/.gitkeep b/packages/broker-darwin-arm64/bin/.gitkeep new file mode 100644 index 000000000..e69de29bb diff --git a/packages/broker-darwin-arm64/package.json b/packages/broker-darwin-arm64/package.json new file mode 100644 index 000000000..449ae5faf --- /dev/null +++ b/packages/broker-darwin-arm64/package.json @@ -0,0 +1,19 @@ +{ + "name": "@agent-relay/broker-darwin-arm64", + "version": "5.0.0", + "description": "agent-relay-broker binary for darwin arm64. Installed automatically as an optional dependency of @agent-relay/sdk.", + "os": ["darwin"], + "cpu": ["arm64"], + "files": [ + "bin" + ], + "repository": { + "type": "git", + "url": "git+https://github.com/AgentWorkforce/relay.git", + "directory": "packages/broker-darwin-arm64" + }, + "license": "MIT", + "publishConfig": { + "access": "public" + } +} diff --git a/packages/broker-darwin-x64/README.md b/packages/broker-darwin-x64/README.md new file mode 100644 index 000000000..f32101af1 --- /dev/null +++ b/packages/broker-darwin-x64/README.md @@ -0,0 +1,11 @@ +# @agent-relay/broker-darwin-x64 + +Prebuilt `agent-relay-broker` binary for **macOS (Intel)**. + +This package is installed automatically as an optional dependency of +[`@agent-relay/sdk`](https://www.npmjs.com/package/@agent-relay/sdk). You do +not need to depend on it directly. The SDK resolves the correct platform +binary at runtime via `require.resolve`. + +See the [agent-relay repository](https://github.com/AgentWorkforce/relay) for +source and build tooling. diff --git a/packages/broker-darwin-x64/bin/.gitkeep b/packages/broker-darwin-x64/bin/.gitkeep new file mode 100644 index 000000000..e69de29bb diff --git a/packages/broker-darwin-x64/package.json b/packages/broker-darwin-x64/package.json new file mode 100644 index 000000000..49a3be0fa --- /dev/null +++ b/packages/broker-darwin-x64/package.json @@ -0,0 +1,19 @@ +{ + "name": "@agent-relay/broker-darwin-x64", + "version": "5.0.0", + "description": "agent-relay-broker binary for darwin x64. Installed automatically as an optional dependency of @agent-relay/sdk.", + "os": ["darwin"], + "cpu": ["x64"], + "files": [ + "bin" + ], + "repository": { + "type": "git", + "url": "git+https://github.com/AgentWorkforce/relay.git", + "directory": "packages/broker-darwin-x64" + }, + "license": "MIT", + "publishConfig": { + "access": "public" + } +} diff --git a/packages/broker-linux-arm64/README.md b/packages/broker-linux-arm64/README.md new file mode 100644 index 000000000..85ae8fc85 --- /dev/null +++ b/packages/broker-linux-arm64/README.md @@ -0,0 +1,12 @@ +# @agent-relay/broker-linux-arm64 + +Prebuilt `agent-relay-broker` binary for **Linux ARM64**. The broker is +compiled with `musl` static linking so it works on both glibc and musl hosts. + +This package is installed automatically as an optional dependency of +[`@agent-relay/sdk`](https://www.npmjs.com/package/@agent-relay/sdk). You do +not need to depend on it directly. The SDK resolves the correct platform +binary at runtime via `require.resolve`. + +See the [agent-relay repository](https://github.com/AgentWorkforce/relay) for +source and build tooling. diff --git a/packages/broker-linux-arm64/bin/.gitkeep b/packages/broker-linux-arm64/bin/.gitkeep new file mode 100644 index 000000000..e69de29bb diff --git a/packages/broker-linux-arm64/package.json b/packages/broker-linux-arm64/package.json new file mode 100644 index 000000000..2f7331b3f --- /dev/null +++ b/packages/broker-linux-arm64/package.json @@ -0,0 +1,19 @@ +{ + "name": "@agent-relay/broker-linux-arm64", + "version": "5.0.0", + "description": "agent-relay-broker binary for linux arm64. Installed automatically as an optional dependency of @agent-relay/sdk.", + "os": ["linux"], + "cpu": ["arm64"], + "files": [ + "bin" + ], + "repository": { + "type": "git", + "url": "git+https://github.com/AgentWorkforce/relay.git", + "directory": "packages/broker-linux-arm64" + }, + "license": "MIT", + "publishConfig": { + "access": "public" + } +} diff --git a/packages/broker-linux-x64/README.md b/packages/broker-linux-x64/README.md new file mode 100644 index 000000000..f290edb36 --- /dev/null +++ b/packages/broker-linux-x64/README.md @@ -0,0 +1,12 @@ +# @agent-relay/broker-linux-x64 + +Prebuilt `agent-relay-broker` binary for **Linux x86_64**. The broker is +compiled with `musl` static linking so it works on both glibc and musl hosts. + +This package is installed automatically as an optional dependency of +[`@agent-relay/sdk`](https://www.npmjs.com/package/@agent-relay/sdk). You do +not need to depend on it directly. The SDK resolves the correct platform +binary at runtime via `require.resolve`. + +See the [agent-relay repository](https://github.com/AgentWorkforce/relay) for +source and build tooling. diff --git a/packages/broker-linux-x64/bin/.gitkeep b/packages/broker-linux-x64/bin/.gitkeep new file mode 100644 index 000000000..e69de29bb diff --git a/packages/broker-linux-x64/package.json b/packages/broker-linux-x64/package.json new file mode 100644 index 000000000..d8f803691 --- /dev/null +++ b/packages/broker-linux-x64/package.json @@ -0,0 +1,19 @@ +{ + "name": "@agent-relay/broker-linux-x64", + "version": "5.0.0", + "description": "agent-relay-broker binary for linux x64. Installed automatically as an optional dependency of @agent-relay/sdk.", + "os": ["linux"], + "cpu": ["x64"], + "files": [ + "bin" + ], + "repository": { + "type": "git", + "url": "git+https://github.com/AgentWorkforce/relay.git", + "directory": "packages/broker-linux-x64" + }, + "license": "MIT", + "publishConfig": { + "access": "public" + } +} diff --git a/packages/broker-win32-x64/README.md b/packages/broker-win32-x64/README.md new file mode 100644 index 000000000..3888912c4 --- /dev/null +++ b/packages/broker-win32-x64/README.md @@ -0,0 +1,11 @@ +# @agent-relay/broker-win32-x64 + +Prebuilt `agent-relay-broker.exe` binary for **Windows x86_64**. + +This package is installed automatically as an optional dependency of +[`@agent-relay/sdk`](https://www.npmjs.com/package/@agent-relay/sdk). You do +not need to depend on it directly. The SDK resolves the correct platform +binary at runtime via `require.resolve`. + +See the [agent-relay repository](https://github.com/AgentWorkforce/relay) for +source and build tooling. diff --git a/packages/broker-win32-x64/bin/.gitkeep b/packages/broker-win32-x64/bin/.gitkeep new file mode 100644 index 000000000..e69de29bb diff --git a/packages/broker-win32-x64/package.json b/packages/broker-win32-x64/package.json new file mode 100644 index 000000000..a7ba03858 --- /dev/null +++ b/packages/broker-win32-x64/package.json @@ -0,0 +1,19 @@ +{ + "name": "@agent-relay/broker-win32-x64", + "version": "5.0.0", + "description": "agent-relay-broker binary for windows x64. Installed automatically as an optional dependency of @agent-relay/sdk.", + "os": ["win32"], + "cpu": ["x64"], + "files": [ + "bin" + ], + "repository": { + "type": "git", + "url": "git+https://github.com/AgentWorkforce/relay.git", + "directory": "packages/broker-win32-x64" + }, + "license": "MIT", + "publishConfig": { + "access": "public" + } +} diff --git a/packages/sdk/package.json b/packages/sdk/package.json index a20e9bdf4..8e3faf35b 100644 --- a/packages/sdk/package.json +++ b/packages/sdk/package.json @@ -146,6 +146,13 @@ "tar": "^7.5.10", "yaml": "^2.7.0" }, + "optionalDependencies": { + "@agent-relay/broker-darwin-arm64": "5.0.0", + "@agent-relay/broker-darwin-x64": "5.0.0", + "@agent-relay/broker-linux-arm64": "5.0.0", + "@agent-relay/broker-linux-x64": "5.0.0", + "@agent-relay/broker-win32-x64": "5.0.0" + }, "peerDependencies": { "@agent-relay/credential-proxy": "5.0.0", "@anthropic-ai/claude-agent-sdk": ">=0.1.0", diff --git a/packages/sdk/src/broker-path.ts b/packages/sdk/src/broker-path.ts index e9c6d58a2..c236db709 100644 --- a/packages/sdk/src/broker-path.ts +++ b/packages/sdk/src/broker-path.ts @@ -14,6 +14,13 @@ import { fileURLToPath } from 'node:url'; const BROKER_NAME = 'agent-relay-broker'; +export function getOptionalDepPackageName( + platform: NodeJS.Platform = process.platform, + arch: string = process.arch +): string { + return `@agent-relay/broker-${platform}-${arch}`; +} + function addUniquePath(paths: string[], candidate: string | null | undefined): void { if (!candidate || paths.includes(candidate)) { return; @@ -60,6 +67,52 @@ function getCurrentModuleReference(): string | null { return getImportMetaUrl(); } +function getResolutionReferences(): string[] { + const refs: string[] = []; + addUniquePath(refs, getCurrentModuleReference()); + + // Also try the entry script so CLI consumers and bundled installs + // (where the SDK lives under the consuming package's node_modules) can + // still find the optional-dep package. + if (process.argv[1]) { + addUniquePath(refs, process.argv[1]); + } + + // Fall back to the cwd so unusual runtime layouts still resolve. + addUniquePath(refs, join(process.cwd(), 'package.json')); + + return refs; +} + +function requireResolveFromRefs(specifier: string): string | null { + for (const ref of getResolutionReferences()) { + try { + return createRequire(ref).resolve(specifier); + } catch { + // Try the next reference. + } + } + return null; +} + +/** + * Resolve the broker binary via the platform-specific optional-dependency + * package (`@agent-relay/broker--`). Returns null when the + * optional dep is not installed (expected when users install with + * --no-optional / --ignore-scripts or when the broker hasn't been published + * for their platform yet). + */ +function getOptionalDepBinaryPath(ext: string): string | null { + const pkgName = getOptionalDepPackageName(); + const binaryFile = `${BROKER_NAME}${ext}`; + + const pkgJsonPath = requireResolveFromRefs(`${pkgName}/package.json`); + if (!pkgJsonPath) return null; + + const binPath = join(dirname(pkgJsonPath), 'bin', binaryFile); + return existsSync(binPath) ? binPath : null; +} + function getSdkBinDirs(): string[] { const binDirs: string[] = []; @@ -90,10 +143,11 @@ function getSdkBinDirs(): string[] { return binDirs; } -// The `agent-relay` npm tarball ships platform-specific brokers at its -// top-level `bin/` (not inside `packages/sdk/bin/`). Walk up from the SDK -// module looking for any ancestor with a `bin/` directory so we can find -// the binary without depending on postinstall to copy it. +// The `agent-relay` npm tarball historically shipped platform-specific +// brokers at its top-level `bin/`. Walk up from the SDK module looking for +// any ancestor with a `bin/` directory. This fallback is retained for one +// release cycle while downstream installs migrate to the optional-dep +// package; delete it in the next major. function getAncestorBinDirs(): string[] { const binDirs: string[] = []; const start = getCurrentModuleDir(); @@ -162,13 +216,16 @@ function getSourceCheckoutBinaryPaths(ext: string, binDirs: string[]): string[] * * Search order: * 1. Explicit env override (BROKER_BINARY_PATH / AGENT_RELAY_BIN) - * 2. Local Cargo build when the SDK is loaded from an agent-relay source checkout - * 3. SDK's bin/ directory (resolved via CJS globals, createRequire, or import.meta.url) - * 4. Platform-specific name (agent-relay-broker-{platform}-{arch}) in bin/ - * 5. Ancestor bin/ directories (the `agent-relay` tarball ships platform- - * specific broker binaries at its package-root `bin/` — finding them - * here removes the postinstall copy dependency) - * 6. Common Cargo development paths (target/release and target/debug) + * 2. Local Cargo build when the SDK is loaded from an agent-relay source + * checkout — keeps dev workflows snappy by preferring a fresh + * `target/release` binary over anything staged in bin/ + * 3. Platform-specific optional-dep package + * (`@agent-relay/broker--`) — primary production path + * 4. SDK's bin/ directory (legacy bundled binary — kept for one release + * cycle so mixed-version installs still work) + * 5. Ancestor bin/ directories (legacy, from PR #768 — kept for one + * release cycle so stale `agent-relay` tarballs still resolve) + * 6. Cargo development paths (target/release and target/debug) * 7. PATH lookup via `which` / `where` * * @returns Absolute path to the broker binary, or null if not found @@ -188,15 +245,21 @@ export function getBrokerBinaryPath(): string | null { } // 1. Prefer a local Cargo build when this SDK is being used from a source checkout. - // In development, the bundled packages/sdk/bin binary can be stale relative to - // the current Rust build in target/release. + // In development, a binary staged in packages/sdk/bin can be stale relative + // to the current Rust build in target/release. for (const developmentPath of getSourceCheckoutBinaryPaths(ext, binDirs)) { if (existsSync(developmentPath)) { return developmentPath; } } - // 2. Exact name in bin/ + // 2. Platform-specific optional-dep package — the primary production path. + const optionalDepBinary = getOptionalDepBinaryPath(ext); + if (optionalDepBinary) { + return optionalDepBinary; + } + + // 3. SDK's bin/ (legacy bundled binary — kept for one release cycle). for (const binDir of binDirs) { const exactPath = join(binDir, `${BROKER_NAME}${ext}`); if (existsSync(exactPath)) { @@ -204,7 +267,7 @@ export function getBrokerBinaryPath(): string | null { } } - // 3. Platform-specific name in bin/ + // 4. Platform-specific name in SDK's bin/ (legacy). for (const binDir of binDirs) { const platformPath = join(binDir, platformSpecific); if (existsSync(platformPath)) { @@ -212,9 +275,8 @@ export function getBrokerBinaryPath(): string | null { } } - // 4. Ancestor bin/ directories (the agent-relay tarball ships brokers at - // its package-root bin/ — exact name first for parity with bundled SDKs, - // then platform-specific name for the prebuilt-only case). + // 5. Ancestor bin/ directories (legacy from PR #768 — the `agent-relay` + // tarball historically shipped brokers at its package-root bin/). for (const binDir of ancestorBinDirs) { const exactPath = join(binDir, `${BROKER_NAME}${ext}`); if (existsSync(exactPath)) { @@ -226,14 +288,14 @@ export function getBrokerBinaryPath(): string | null { } } - // 5. Common development paths for local Cargo builds. + // 6. Common development paths for local Cargo builds. for (const developmentPath of getDevelopmentBinaryPaths(ext, binDirs)) { if (existsSync(developmentPath)) { return developmentPath; } } - // 6. PATH lookup + // 7. PATH lookup try { const cmd = process.platform === 'win32' ? 'where' : 'which'; const result = execFileSync(cmd, [BROKER_NAME], { @@ -249,3 +311,20 @@ export function getBrokerBinaryPath(): string | null { return null; } + +/** + * Human-readable error message explaining that the optional-dep broker + * package for the current platform/arch isn't installed. Used by the SDK + * at broker-spawn time so users get a clear message instead of an + * inscrutable `spawn agent-relay-broker ENOENT`. + */ +export function formatBrokerNotFoundError(): string { + const pkgName = getOptionalDepPackageName(); + return ( + `@agent-relay/sdk couldn't find an agent-relay-broker binary for ` + + `${process.platform}-${process.arch}. The optional dependency ` + + `${pkgName} is expected to be installed alongside @agent-relay/sdk. ` + + `Try reinstalling with --include=optional, or set BROKER_BINARY_PATH ` + + `to point at a broker binary you've downloaded manually.` + ); +} diff --git a/packages/sdk/src/client.ts b/packages/sdk/src/client.ts index f0ce3dcf5..cbeac0f5d 100644 --- a/packages/sdk/src/client.ts +++ b/packages/sdk/src/client.ts @@ -15,7 +15,7 @@ import { randomBytes } from 'node:crypto'; import { readFileSync, existsSync } from 'node:fs'; import path from 'node:path'; import { BrokerTransport, AgentRelayProtocolError } from './transport.js'; -import { getBrokerBinaryPath } from './broker-path.js'; +import { getBrokerBinaryPath, formatBrokerNotFoundError } from './broker-path.js'; import type { AgentRuntime, BrokerEvent, @@ -210,7 +210,14 @@ export class AgentRelayClient { * 6. Starts event stream + lease renewal */ static async spawn(options?: AgentRelaySpawnOptions): Promise { - const binaryPath = options?.binaryPath ?? getBrokerBinaryPath() ?? 'agent-relay-broker'; + let binaryPath = options?.binaryPath; + if (!binaryPath) { + const resolved = getBrokerBinaryPath(); + if (!resolved) { + throw new Error(formatBrokerNotFoundError()); + } + binaryPath = resolved; + } const cwd = options?.cwd ?? process.cwd(); const brokerName = options?.brokerName ?? (path.basename(cwd) || 'project'); const channels = options?.channels ?? ['general']; diff --git a/scripts/postinstall.js b/scripts/postinstall.js index 7ed9b6d11..ce0be0bf4 100644 --- a/scripts/postinstall.js +++ b/scripts/postinstall.js @@ -3,10 +3,14 @@ * Postinstall Script for agent-relay * * This script runs after npm install to: - * 1. Install agent-relay-broker binary for current platform - * 2. Install dashboard dependencies + * 1. Install dashboard-server and relay-acp binaries (parity with curl installer) + * 2. Rebuild better-sqlite3 / confirm SQLite driver * 3. Patch agent-trajectories CLI - * 4. Check for tmux availability (fallback) + * 4. Patch @relayauth/core exports for CommonJS require() + * + * The agent-relay-broker binary is no longer installed here — it ships as a + * platform-specific optional dependency of @agent-relay/sdk + * (`@agent-relay/broker--`), resolved at runtime by the SDK. */ import { execSync } from 'node:child_process'; @@ -325,132 +329,6 @@ function resignBinaryForMacOS(binaryPath) { } } -/** - * Get the platform-specific binary name for the broker binary. - * The broker binary is the Rust-compiled broker (not the Bun-compiled CLI). - * It is needed by the SDK (packages/sdk) for programmatic - * agent orchestration via `new AgentRelay()`. - * Returns null if platform is not supported. - */ -function getBrokerBinaryName() { - const platform = os.platform(); - const arch = os.arch(); - - const archMap = { 'arm64': 'arm64', 'x64': 'x64' }; - const platformMap = { 'darwin': 'darwin', 'linux': 'linux' }; - - const targetPlatform = platformMap[platform]; - const targetArch = archMap[arch]; - - if (!targetPlatform || !targetArch) { - return null; - } - - // Use the broker-specific release asset name (Rust binary, not Bun CLI) - return `agent-relay-broker-${targetPlatform}-${targetArch}`; -} - -/** - * Install the broker binary into packages/sdk/bin/. - * - * The SDK's AgentRelayClient spawns this binary as a subprocess - * (`agent-relay-broker init --name broker --channels general`). Without it, - * `new AgentRelay()` will fail with "broker exited (code=1)". - * - * Resolution order: - * 1. Already bundled at packages/sdk/bin/agent-relay-broker (e.g. from prepack) - * 2. Platform-specific binary bundled in root bin/ (e.g. bin/agent-relay-broker-linux-x64) - * 3. Download platform-specific standalone binary from GitHub releases - * 4. Fall back to the local Rust debug binary at target/debug/agent-relay-broker (dev only) - */ -async function installBrokerBinary() { - const pkgRoot = getPackageRoot(); - const sdkBinDir = path.join(pkgRoot, 'packages', 'sdk', 'bin'); - const isWindows = process.platform === 'win32'; - const binaryFilename = isWindows ? 'agent-relay-broker.exe' : 'agent-relay-broker'; - const targetPath = path.join(sdkBinDir, binaryFilename); - - // 1. Already installed? Verify it's the Rust broker (supports --name flag) - if (fs.existsSync(targetPath)) { - try { - const helpOutput = execSync(`"${targetPath}" init --help`, { stdio: 'pipe' }).toString(); - // The Rust broker shows "--name " in init --help - // The Bun-compiled Node.js CLI shows "First-time setup wizard" - if (helpOutput.includes('--name')) { - info('Broker binary already installed in SDK (Rust broker verified)'); - return true; - } - // Wrong binary (Bun CLI instead of Rust broker) — reinstall - warn('Broker binary exists but is the CLI, not the Rust broker — reinstalling'); - } catch { - // Binary exists but doesn't work — reinstall - } - } - - fs.mkdirSync(sdkBinDir, { recursive: true }); - - const binaryName = getBrokerBinaryName(); - - // 2. Check for bundled platform-specific binary in root bin/ - if (binaryName) { - const bundledBinary = path.join(pkgRoot, 'bin', binaryName); - if (fs.existsSync(bundledBinary)) { - try { - fs.copyFileSync(bundledBinary, targetPath); - fs.chmodSync(targetPath, 0o755); - resignBinaryForMacOS(targetPath); - success(`Installed broker binary from bundled package (${binaryName})`); - return true; - } catch (err) { - warn(`Failed to copy bundled broker binary: ${err.message}`); - } - } - } - - // 3. Try downloading from GitHub releases - if (binaryName) { - const version = getPackageVersion(pkgRoot); - if (version) { - const downloadUrl = `https://github.com/AgentWorkforce/relay/releases/download/v${version}/${binaryName}`; - info(`Downloading broker binary from ${downloadUrl} ...`); - - try { - await downloadBinary(downloadUrl, targetPath); - await verifyChecksum(targetPath, downloadUrl); - fs.chmodSync(targetPath, 0o755); - resignBinaryForMacOS(targetPath); - success(`Downloaded broker binary for ${os.platform()}-${os.arch()}`); - return true; - } catch (err) { - const message = err instanceof Error ? err.message : String(err); - warn(`Failed to download broker binary: ${message}`); - // Clean up partial/untrusted download - try { fs.unlinkSync(targetPath); } catch { /* ignore */ } - } - } - } - - // 4. Dev fallback — check for local Rust build (release first, then debug) - for (const profile of ['release', 'debug']) { - const localBinary = path.join(pkgRoot, 'target', profile, binaryFilename); - if (fs.existsSync(localBinary)) { - try { - fs.copyFileSync(localBinary, targetPath); - fs.chmodSync(targetPath, 0o755); - resignBinaryForMacOS(targetPath); - success(`Installed broker binary from local Rust ${profile} build`); - return true; - } catch (err) { - warn(`Failed to copy ${profile} broker binary: ${err.message}`); - } - } - } - - warn('Broker binary not available — SDK programmatic usage (AgentRelay) will not work'); - info('To fix: cargo build --release --bin agent-relay-broker (requires Rust toolchain)'); - return false; -} - /** * Install the standalone dashboard-server binary. * @@ -811,7 +689,7 @@ function patchRelayauthCoreExports() { } } -function logPostinstallDiagnostics(hasBrokerBinary, sqliteStatus, linkResult) { +function logPostinstallDiagnostics(sqliteStatus, linkResult) { // Workspace packages status (for global installs) if (linkResult && linkResult.needed) { if (linkResult.success) { @@ -821,12 +699,6 @@ function logPostinstallDiagnostics(hasBrokerBinary, sqliteStatus, linkResult) { } } - if (hasBrokerBinary) { - console.log('✓ agent-relay-broker binary installed'); - } else { - console.log('⚠ agent-relay-broker binary not installed - AgentRelay will not work'); - } - if (sqliteStatus.ok && sqliteStatus.driver === 'better-sqlite3') { console.log('✓ SQLite ready (better-sqlite3)'); } else if (sqliteStatus.ok && sqliteStatus.driver === 'node:sqlite') { @@ -856,9 +728,6 @@ async function main() { } } - // Install broker binary for agent spawning and SDK programmatic usage - const hasBrokerBinary = await installBrokerBinary(); - // Ensure SQLite driver is available (better-sqlite3 or node:sqlite) const sqliteStatus = ensureSqliteDriver(); @@ -876,7 +745,7 @@ async function main() { const hasAcpBinary = await installRelayAcpBinary(); // Always print diagnostics (even in CI) - logPostinstallDiagnostics(hasBrokerBinary, sqliteStatus, linkResult); + logPostinstallDiagnostics(sqliteStatus, linkResult); if (hasDashboardBinary) { console.log('✓ dashboard-server binary installed'); @@ -884,12 +753,6 @@ async function main() { if (hasAcpBinary) { console.log('✓ relay-acp binary installed (Zed editor integration)'); } - - if (!hasBrokerBinary) { - warn('agent-relay-broker binary not available'); - info('Agent spawning will not work without the broker binary.'); - info('To fix: cargo build --release --bin agent-relay-broker (requires Rust toolchain)'); - } } main().catch((err) => { From beb9b63e7ff57b35e24e0edf3e1dc593dc23c34c Mon Sep 17 00:00:00 2001 From: Will Washburn Date: Wed, 22 Apr 2026 19:51:47 -0400 Subject: [PATCH 2/8] fix(sdk): keep broker packages as workspaces so npm ci passes MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The previous cut excluded `packages/broker-*` from workspaces to avoid EBADPLATFORM on `os`/`cpu` constraints that can't be satisfied by every dev host. That broke `npm ci` because the SDK's optionalDependencies at exact version `5.0.0` have no resolution source — those packages aren't on the registry yet and they aren't workspaces either. Flip the approach: keep all five broker packages as plain workspaces during development (no `os`/`cpu` in the committed package.json) and inject the constraints at publish time in the publish-broker-packages job. This keeps the lockfile populated, lets `npm ci` succeed locally and in CI, and still publishes correctly constrained packages that npm picks per-platform on consumer installs. Co-Authored-By: Claude Opus 4.7 (1M context) --- .github/workflows/publish.yml | 26 +++++++++ package-lock.json | 68 +++++++++++++++-------- package.json | 5 -- packages/broker-darwin-arm64/package.json | 2 - packages/broker-darwin-x64/package.json | 2 - packages/broker-linux-arm64/package.json | 2 - packages/broker-linux-x64/package.json | 2 - packages/broker-win32-x64/package.json | 4 +- 8 files changed, 72 insertions(+), 39 deletions(-) diff --git a/.github/workflows/publish.yml b/.github/workflows/publish.yml index 039c8500d..2d3bd97c0 100644 --- a/.github/workflows/publish.yml +++ b/.github/workflows/publish.yml @@ -530,18 +530,28 @@ jobs: - broker_pkg: broker-darwin-arm64 binary_name: agent-relay-broker-darwin-arm64 binary_file: agent-relay-broker + os: darwin + cpu: arm64 - broker_pkg: broker-darwin-x64 binary_name: agent-relay-broker-darwin-x64 binary_file: agent-relay-broker + os: darwin + cpu: x64 - broker_pkg: broker-linux-arm64 binary_name: agent-relay-broker-linux-arm64 binary_file: agent-relay-broker + os: linux + cpu: arm64 - broker_pkg: broker-linux-x64 binary_name: agent-relay-broker-linux-x64 binary_file: agent-relay-broker + os: linux + cpu: x64 - broker_pkg: broker-win32-x64 binary_name: agent-relay-broker-win32-x64.exe binary_file: agent-relay-broker.exe + os: win32 + cpu: x64 steps: - name: Checkout code @@ -573,6 +583,22 @@ jobs: chmod +x "packages/${{ matrix.broker_pkg }}/bin/${{ matrix.binary_file }}" ls -lh packages/${{ matrix.broker_pkg }}/bin/ + # os/cpu constraints are injected at publish time. Keeping them out of + # the committed package.json lets these packages live as plain + # workspaces during development — otherwise npm install trips + # EBADPLATFORM on the machines that don't match every platform. + - name: Inject os/cpu for ${{ matrix.os }}-${{ matrix.cpu }} + run: | + node -e " + const fs = require('fs'); + const p = 'packages/${{ matrix.broker_pkg }}/package.json'; + const pkg = JSON.parse(fs.readFileSync(p, 'utf8')); + pkg.os = ['${{ matrix.os }}']; + pkg.cpu = ['${{ matrix.cpu }}']; + fs.writeFileSync(p, JSON.stringify(pkg, null, 2) + '\n'); + console.log('Staged', pkg.name, 'for', pkg.os, pkg.cpu); + " + - name: Update npm for OIDC support run: npm install -g npm@latest diff --git a/package-lock.json b/package-lock.json index 6e8b8fe90..35638ee93 100644 --- a/package-lock.json +++ b/package-lock.json @@ -23,11 +23,6 @@ "license": "Apache-2.0", "workspaces": [ "packages/*", - "!packages/broker-darwin-arm64", - "!packages/broker-darwin-x64", - "!packages/broker-linux-arm64", - "!packages/broker-linux-x64", - "!packages/broker-win32-x64", "web" ], "dependencies": { @@ -119,6 +114,26 @@ "resolved": "packages/brand", "link": true }, + "node_modules/@agent-relay/broker-darwin-arm64": { + "resolved": "packages/broker-darwin-arm64", + "link": true + }, + "node_modules/@agent-relay/broker-darwin-x64": { + "resolved": "packages/broker-darwin-x64", + "link": true + }, + "node_modules/@agent-relay/broker-linux-arm64": { + "resolved": "packages/broker-linux-arm64", + "link": true + }, + "node_modules/@agent-relay/broker-linux-x64": { + "resolved": "packages/broker-linux-x64", + "link": true + }, + "node_modules/@agent-relay/broker-win32-x64": { + "resolved": "packages/broker-win32-x64", + "link": true + }, "node_modules/@agent-relay/browser-primitive": { "resolved": "packages/browser-primitive", "link": true @@ -15435,6 +15450,31 @@ "name": "@agent-relay/brand", "version": "5.0.0" }, + "packages/broker-darwin-arm64": { + "name": "@agent-relay/broker-darwin-arm64", + "version": "5.0.0", + "license": "MIT" + }, + "packages/broker-darwin-x64": { + "name": "@agent-relay/broker-darwin-x64", + "version": "5.0.0", + "license": "MIT" + }, + "packages/broker-linux-arm64": { + "name": "@agent-relay/broker-linux-arm64", + "version": "5.0.0", + "license": "MIT" + }, + "packages/broker-linux-x64": { + "name": "@agent-relay/broker-linux-x64", + "version": "5.0.0", + "license": "MIT" + }, + "packages/broker-win32-x64": { + "name": "@agent-relay/broker-win32-x64", + "version": "5.0.0", + "license": "MIT" + }, "packages/browser-primitive": { "name": "@agent-relay/browser-primitive", "version": "5.0.0", @@ -15451,15 +15491,6 @@ "vitest": "^3.2.4" } }, - "packages/browser-primitive/node_modules/@agent-relay/config": { - "version": "4.0.9", - "resolved": "https://registry.npmjs.org/@agent-relay/config/-/config-4.0.9.tgz", - "integrity": "sha512-bSYU1q/mCpbNScGCjo32GOi22+TIkVy96vzaLwXkyZrv+hsVlIGtIlpZe3GCYRK+hvKizKTsXc9V0PHaNR8d8Q==", - "dependencies": { - "zod": "^3.23.8", - "zod-to-json-schema": "^3.23.1" - } - }, "packages/cloud": { "name": "@agent-relay/cloud", "version": "5.0.0", @@ -15520,15 +15551,6 @@ "vitest": "^3.2.4" } }, - "packages/github-primitive/node_modules/@agent-relay/config": { - "version": "4.0.9", - "resolved": "https://registry.npmjs.org/@agent-relay/config/-/config-4.0.9.tgz", - "integrity": "sha512-bSYU1q/mCpbNScGCjo32GOi22+TIkVy96vzaLwXkyZrv+hsVlIGtIlpZe3GCYRK+hvKizKTsXc9V0PHaNR8d8Q==", - "dependencies": { - "zod": "^3.23.8", - "zod-to-json-schema": "^3.23.1" - } - }, "packages/hooks": { "name": "@agent-relay/hooks", "version": "5.0.0", diff --git a/package.json b/package.json index 91c571c1b..df0efa0e9 100644 --- a/package.json +++ b/package.json @@ -86,11 +86,6 @@ }, "workspaces": [ "packages/*", - "!packages/broker-darwin-arm64", - "!packages/broker-darwin-x64", - "!packages/broker-linux-arm64", - "!packages/broker-linux-x64", - "!packages/broker-win32-x64", "web" ], "bundledDependencies": [ diff --git a/packages/broker-darwin-arm64/package.json b/packages/broker-darwin-arm64/package.json index 449ae5faf..a3fa05b6a 100644 --- a/packages/broker-darwin-arm64/package.json +++ b/packages/broker-darwin-arm64/package.json @@ -2,8 +2,6 @@ "name": "@agent-relay/broker-darwin-arm64", "version": "5.0.0", "description": "agent-relay-broker binary for darwin arm64. Installed automatically as an optional dependency of @agent-relay/sdk.", - "os": ["darwin"], - "cpu": ["arm64"], "files": [ "bin" ], diff --git a/packages/broker-darwin-x64/package.json b/packages/broker-darwin-x64/package.json index 49a3be0fa..906544af0 100644 --- a/packages/broker-darwin-x64/package.json +++ b/packages/broker-darwin-x64/package.json @@ -2,8 +2,6 @@ "name": "@agent-relay/broker-darwin-x64", "version": "5.0.0", "description": "agent-relay-broker binary for darwin x64. Installed automatically as an optional dependency of @agent-relay/sdk.", - "os": ["darwin"], - "cpu": ["x64"], "files": [ "bin" ], diff --git a/packages/broker-linux-arm64/package.json b/packages/broker-linux-arm64/package.json index 2f7331b3f..5e3bf6065 100644 --- a/packages/broker-linux-arm64/package.json +++ b/packages/broker-linux-arm64/package.json @@ -2,8 +2,6 @@ "name": "@agent-relay/broker-linux-arm64", "version": "5.0.0", "description": "agent-relay-broker binary for linux arm64. Installed automatically as an optional dependency of @agent-relay/sdk.", - "os": ["linux"], - "cpu": ["arm64"], "files": [ "bin" ], diff --git a/packages/broker-linux-x64/package.json b/packages/broker-linux-x64/package.json index d8f803691..3c4a97fcf 100644 --- a/packages/broker-linux-x64/package.json +++ b/packages/broker-linux-x64/package.json @@ -2,8 +2,6 @@ "name": "@agent-relay/broker-linux-x64", "version": "5.0.0", "description": "agent-relay-broker binary for linux x64. Installed automatically as an optional dependency of @agent-relay/sdk.", - "os": ["linux"], - "cpu": ["x64"], "files": [ "bin" ], diff --git a/packages/broker-win32-x64/package.json b/packages/broker-win32-x64/package.json index a7ba03858..3ab61c2d3 100644 --- a/packages/broker-win32-x64/package.json +++ b/packages/broker-win32-x64/package.json @@ -1,9 +1,7 @@ { "name": "@agent-relay/broker-win32-x64", "version": "5.0.0", - "description": "agent-relay-broker binary for windows x64. Installed automatically as an optional dependency of @agent-relay/sdk.", - "os": ["win32"], - "cpu": ["x64"], + "description": "agent-relay-broker binary for win32 x64. Installed automatically as an optional dependency of @agent-relay/sdk.", "files": [ "bin" ], From 00e937497a0181a483b26da2601fe3e85e6f99e1 Mon Sep 17 00:00:00 2001 From: Will Washburn Date: Wed, 22 Apr 2026 20:02:55 -0400 Subject: [PATCH 3/8] ci(sdk): add cross-platform smoke test for broker optional-deps MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Add a new smoke-broker-packages matrix job that exercises the full optional-dep resolution path on every target platform (macOS arm64/x64, Linux x64/arm64, Windows x64) before we publish. Each leg stages the platform's broker binary into its package tree, injects os/cpu, npm-packs both the SDK and the matching broker, installs them into a scratch project, and then runs two assertions: - Resolver smoke: getBrokerBinaryPath() returns an executable path that goes through the optional-dep package (not a bundled fallback). - Spawn smoke: AgentRelayClient.spawn() actually starts a broker and client.shutdown() tears it down cleanly. The Linux x64 leg additionally exercises the negative path: installing the SDK without the broker tarball via --no-optional must produce the clear, platform-named error from formatBrokerNotFoundError, not ENOENT. publish-broker-packages now gates on this job — we do not ship if any platform fails. Ubuntu 24.04 arm64 runners are free on public repos, so all five legs run natively without QEMU. Verified locally on darwin-arm64 end-to-end (pack → install → spawn → shutdown, plus the negative case). Co-Authored-By: Claude Opus 4.7 (1M context) --- .github/workflows/publish.yml | 211 +++++++++++++++++++++++++++++++++- 1 file changed, 210 insertions(+), 1 deletion(-) diff --git a/.github/workflows/publish.yml b/.github/workflows/publish.yml index 2d3bd97c0..dae57c053 100644 --- a/.github/workflows/publish.yml +++ b/.github/workflows/publish.yml @@ -512,6 +512,213 @@ jobs: dist/ retention-days: 1 + # End-to-end smoke test of the optional-dep broker pattern on every target + # platform, using locally-packed tarballs — no registry round-trip, no + # mocking. Each matrix leg: + # 1. Stages the platform's broker binary into its package tree and + # injects os/cpu so the package validates like the published one. + # 2. `npm pack`s the SDK (with packages/sdk/bin emptied so the SDK + # tarball cannot fall back to a bundled binary). + # 3. `npm pack`s the matching broker package. + # 4. Installs both tarballs into a scratch project. + # 5. Asserts getBrokerBinaryPath() resolves through the optional-dep + # package and returns an executable file. + # 6. Runs AgentRelayClient.spawn() end-to-end and shuts it down. + # Gates publish-broker-packages — we do not ship if any platform fails. + smoke-broker-packages: + name: Smoke ${{ matrix.platform }} + needs: [build, build-broker] + if: github.event.inputs.package == 'all' || github.event.inputs.package == 'sdk' + runs-on: ${{ matrix.os }} + strategy: + fail-fast: false + matrix: + include: + - platform: darwin-arm64 + os: macos-14 + broker_pkg: broker-darwin-arm64 + binary_name: agent-relay-broker-darwin-arm64 + binary_file: agent-relay-broker + pkg_os: darwin + pkg_cpu: arm64 + - platform: darwin-x64 + os: macos-13 + broker_pkg: broker-darwin-x64 + binary_name: agent-relay-broker-darwin-x64 + binary_file: agent-relay-broker + pkg_os: darwin + pkg_cpu: x64 + - platform: linux-x64 + os: ubuntu-latest + broker_pkg: broker-linux-x64 + binary_name: agent-relay-broker-linux-x64 + binary_file: agent-relay-broker + pkg_os: linux + pkg_cpu: x64 + - platform: linux-arm64 + os: ubuntu-24.04-arm + broker_pkg: broker-linux-arm64 + binary_name: agent-relay-broker-linux-arm64 + binary_file: agent-relay-broker + pkg_os: linux + pkg_cpu: arm64 + - platform: win32-x64 + os: windows-latest + broker_pkg: broker-win32-x64 + binary_name: agent-relay-broker-win32-x64.exe + binary_file: agent-relay-broker.exe + pkg_os: win32 + pkg_cpu: x64 + + steps: + - name: Checkout code + uses: actions/checkout@v4 + + - name: Setup Node.js + uses: actions/setup-node@v4 + with: + node-version: '22.14.0' + + - name: Download build artifacts + uses: actions/download-artifact@v4 + with: + name: build-output + path: . + + - name: Download broker binary + uses: actions/download-artifact@v4 + with: + name: ${{ matrix.binary_name }} + path: /tmp/broker + + - name: Stage broker binary (unix) + if: runner.os != 'Windows' + shell: bash + run: | + set -euo pipefail + mkdir -p "packages/${{ matrix.broker_pkg }}/bin" + cp "/tmp/broker/${{ matrix.binary_name }}" "packages/${{ matrix.broker_pkg }}/bin/${{ matrix.binary_file }}" + chmod +x "packages/${{ matrix.broker_pkg }}/bin/${{ matrix.binary_file }}" + + - name: Stage broker binary (windows) + if: runner.os == 'Windows' + shell: pwsh + run: | + New-Item -ItemType Directory -Force -Path "packages/${{ matrix.broker_pkg }}/bin" | Out-Null + Copy-Item "/tmp/broker/${{ matrix.binary_name }}" "packages/${{ matrix.broker_pkg }}/bin/${{ matrix.binary_file }}" + + - name: Inject os/cpu for ${{ matrix.pkg_os }}-${{ matrix.pkg_cpu }} + shell: bash + run: | + node -e " + const fs = require('fs'); + const p = 'packages/${{ matrix.broker_pkg }}/package.json'; + const pkg = JSON.parse(fs.readFileSync(p, 'utf8')); + pkg.os = ['${{ matrix.pkg_os }}']; + pkg.cpu = ['${{ matrix.pkg_cpu }}']; + fs.writeFileSync(p, JSON.stringify(pkg, null, 2) + '\n'); + " + + - name: Pack SDK and broker tarballs + shell: bash + run: | + set -euo pipefail + TARBALLS="$RUNNER_TEMP/tarballs" + mkdir -p "$TARBALLS" + echo "TARBALLS=$TARBALLS" >> "$GITHUB_ENV" + # Empty packages/sdk/bin so the SDK tarball has NO bundled broker + # binary. This forces the smoke test to exercise the optional-dep + # resolution path instead of the legacy bundled fallback. + rm -rf packages/sdk/bin + mkdir -p packages/sdk/bin + (cd packages/sdk && npm pack --ignore-scripts --pack-destination "$TARBALLS") + (cd "packages/${{ matrix.broker_pkg }}" && npm pack --ignore-scripts --pack-destination "$TARBALLS") + ls -lh "$TARBALLS" + + - name: Install tarballs into scratch project + shell: bash + run: | + set -euo pipefail + SCRATCH="$RUNNER_TEMP/smoke" + mkdir -p "$SCRATCH" + echo "SCRATCH=$SCRATCH" >> "$GITHUB_ENV" + cd "$SCRATCH" + npm init -y --silent >/dev/null + SDK_TGZ=$(ls "$TARBALLS"/agent-relay-sdk-*.tgz | head -n1) + BROKER_TGZ=$(ls "$TARBALLS"/agent-relay-broker-${{ matrix.platform }}-*.tgz | head -n1) + echo "Installing $SDK_TGZ + $BROKER_TGZ" + npm install --ignore-scripts --no-audit --no-fund "$SDK_TGZ" "$BROKER_TGZ" + ls node_modules/@agent-relay/ + + - name: Resolver smoke — getBrokerBinaryPath() + shell: bash + run: | + cd "$SCRATCH" + node --input-type=module -e " + import { getBrokerBinaryPath, getOptionalDepPackageName } from '@agent-relay/sdk/broker-path'; + import { accessSync, constants } from 'node:fs'; + const expectedPkg = getOptionalDepPackageName(); + const p = getBrokerBinaryPath(); + console.log('expected pkg:', expectedPkg); + console.log('resolved:', p); + if (!p) { console.error('FAIL: resolver returned null'); process.exit(1); } + if (!p.includes('${{ matrix.broker_pkg }}')) { + console.error('FAIL: expected path through ${{ matrix.broker_pkg }}, got', p); + process.exit(1); + } + accessSync(p, constants.X_OK); + console.log('OK: resolver returned executable binary from optional-dep package'); + " + + - name: Spawn smoke — AgentRelayClient.spawn() + shell: bash + run: | + cd "$SCRATCH" + node --input-type=module -e " + import { AgentRelayClient } from '@agent-relay/sdk'; + const client = await AgentRelayClient.spawn({ + cwd: process.cwd(), + channels: ['general'], + startupTimeoutMs: 45000, + onStderr: (line) => console.error('[broker]', line), + }); + console.log('OK: AgentRelayClient.spawn() returned'); + await client.shutdown(); + console.log('OK: client.shutdown() completed'); + " || { echo 'SPAWN_FAILED'; exit 1; } + + - name: Negative smoke — optional dep missing (linux-x64 only) + if: matrix.platform == 'linux-x64' + shell: bash + run: | + set -euo pipefail + NEGATIVE="$RUNNER_TEMP/smoke-negative" + mkdir -p "$NEGATIVE" + cd "$NEGATIVE" + npm init -y --silent >/dev/null + SDK_TGZ=$(ls "$TARBALLS"/agent-relay-sdk-*.tgz | head -n1) + # Install SDK alone without the broker. The SDK's optionalDependencies + # at exact '5.x' cannot resolve from this local install, so the + # resolver should return null and spawn() should throw a clear error. + npm install --ignore-scripts --no-audit --no-fund --no-optional "$SDK_TGZ" + node --input-type=module -e " + import { AgentRelayClient } from '@agent-relay/sdk'; + try { + await AgentRelayClient.spawn({ cwd: process.cwd() }); + console.error('FAIL: spawn() should have thrown'); + process.exit(1); + } catch (err) { + const msg = err && err.message ? err.message : String(err); + console.log('got error:', msg); + const expected = 'couldn\\'t find an agent-relay-broker binary for linux-x64'; + if (!msg.includes(expected)) { + console.error('FAIL: error message does not name platform/package'); + process.exit(1); + } + console.log('OK: spawn() threw the expected clear error'); + } + " + # Publish the per-platform broker packages first. @agent-relay/sdk declares # these as exact-version optionalDependencies, so they must exist on the # registry at the matching version before the SDK is published — otherwise @@ -519,7 +726,7 @@ jobs: # package at the same version. publish-broker-packages: name: Publish ${{ matrix.broker_pkg }} - needs: [build, build-broker] + needs: [build, build-broker, smoke-broker-packages] runs-on: ubuntu-latest if: github.event.inputs.package == 'all' || github.event.inputs.package == 'sdk' strategy: @@ -1684,6 +1891,7 @@ jobs: verify-standalone-macos, verify-acp-linux, verify-acp-macos, + smoke-broker-packages, publish-broker-packages, publish-packages, publish-brand-only, @@ -1726,6 +1934,7 @@ jobs: echo "| Verify Standalone (macOS) | ${{ needs.verify-standalone-macos.result == 'success' && '✅' || (needs.verify-standalone-macos.result == 'skipped' && '⏭️' || '❌') }} ${{ needs.verify-standalone-macos.result }} |" >> $GITHUB_STEP_SUMMARY echo "| Verify relay-acp (Linux) | ${{ needs.verify-acp-linux.result == 'success' && '✅' || (needs.verify-acp-linux.result == 'skipped' && '⏭️' || '❌') }} ${{ needs.verify-acp-linux.result }} |" >> $GITHUB_STEP_SUMMARY echo "| Verify relay-acp (macOS) | ${{ needs.verify-acp-macos.result == 'success' && '✅' || (needs.verify-acp-macos.result == 'skipped' && '⏭️' || '❌') }} ${{ needs.verify-acp-macos.result }} |" >> $GITHUB_STEP_SUMMARY + echo "| Smoke Broker Packages | ${{ needs.smoke-broker-packages.result == 'success' && '✅' || (needs.smoke-broker-packages.result == 'skipped' && '⏭️' || '❌') }} ${{ needs.smoke-broker-packages.result }} |" >> $GITHUB_STEP_SUMMARY echo "| Publish Broker Packages | ${{ needs.publish-broker-packages.result == 'success' && '✅' || (needs.publish-broker-packages.result == 'skipped' && '⏭️' || '❌') }} ${{ needs.publish-broker-packages.result }} |" >> $GITHUB_STEP_SUMMARY echo "| Publish Packages | ${{ needs.publish-packages.result == 'success' && '✅' || (needs.publish-packages.result == 'skipped' && '⏭️' || '❌') }} ${{ needs.publish-packages.result }} |" >> $GITHUB_STEP_SUMMARY echo "| Publish Brand | ${{ needs.publish-brand-only.result == 'success' && '✅' || (needs.publish-brand-only.result == 'skipped' && '⏭️' || '❌') }} ${{ needs.publish-brand-only.result }} |" >> $GITHUB_STEP_SUMMARY From cd22aaba2f7d019ca9152039f915d1ab762b8708 Mon Sep 17 00:00:00 2001 From: Will Washburn Date: Wed, 22 Apr 2026 21:55:22 -0400 Subject: [PATCH 4/8] ci: skip dist check for broker-* packages in package-validation MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The package-validation workflow walks packages/*/ and asserts every workspace has dist/index.{js,d.ts} — meant to catch packages that didn't build. The five @agent-relay/broker-* packages ship a Rust binary in bin/ and have no TypeScript or JS dist to validate, so they join build-plans and brand in the SKIP_PACKAGES list. Co-Authored-By: Claude Opus 4.7 (1M context) --- .github/workflows/package-validation.yml | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/.github/workflows/package-validation.yml b/.github/workflows/package-validation.yml index 3d95a7833..50ad77145 100644 --- a/.github/workflows/package-validation.yml +++ b/.github/workflows/package-validation.yml @@ -102,8 +102,10 @@ jobs: echo "=== Validating packages ===" ERRORS=0 - # Check dist files exist (skip non-Node package directories) - SKIP_PACKAGES="build-plans brand" + # Check dist files exist (skip non-Node package directories). + # broker-* packages ship a Rust-built binary in bin/, not a JS + # dist — they live under packages/ only for workspace linkage. + SKIP_PACKAGES="build-plans brand broker-darwin-arm64 broker-darwin-x64 broker-linux-arm64 broker-linux-x64 broker-win32-x64" for pkg_dir in packages/*/; do pkg_name=$(basename "$pkg_dir") if [ ! -f "$pkg_dir/package.json" ]; then From 080f6bee9a238836ecc7d89ea7156ad088d9e7f1 Mon Sep 17 00:00:00 2001 From: Will Washburn Date: Fri, 24 Apr 2026 07:33:30 -0400 Subject: [PATCH 5/8] fix(sdk): address PR review feedback on broker optional-deps MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - publish workflow: rewrite @agent-relay/* references in optionalDependencies too, not just dependencies/devDependencies/ peerDependencies. Without this, future releases would leave the SDK pinned at the previous release's broker packages (critical bug flagged by devin and Copilot). - broker-path: fix misleading comment that blamed --ignore-scripts for missing optional deps; it's --no-optional / --omit=optional that suppresses them. - broker-path: document why the cwd/package.json reference is part of the resolution chain (globally-installed SDKs, bundler setups, REPL experimentation). - add targeted vitest tests for getOptionalDepPackageName, formatBrokerNotFoundError, and the BROKER_BINARY_PATH override. Full optional-dep resolution is covered end-to-end by the smoke-broker-packages CI matrix — staging tmp fs layouts inside the dev tree is too fragile because source-checkout and ancestor-bin fallbacks win before the scratch cwd. Co-Authored-By: Claude Opus 4.7 (1M context) --- .github/workflows/publish.yml | 11 ++- .../sdk/src/__tests__/broker-path.test.ts | 99 +++++++++++++++++++ packages/sdk/src/broker-path.ts | 13 ++- 3 files changed, 116 insertions(+), 7 deletions(-) create mode 100644 packages/sdk/src/__tests__/broker-path.test.ts diff --git a/.github/workflows/publish.yml b/.github/workflows/publish.yml index dae57c053..287fc0f0c 100644 --- a/.github/workflows/publish.yml +++ b/.github/workflows/publish.yml @@ -446,10 +446,13 @@ jobs: const path = require('path'); const version = '$NEW_VERSION'; - // Update @agent-relay/* references across dependencies, - // devDependencies, and peerDependencies so sibling packages - // stay pinned to the same version we're about to publish. - const DEP_SECTIONS = ['dependencies', 'devDependencies', 'peerDependencies']; + // Update @agent-relay/* references across every dep section so + // sibling packages stay pinned to the same version we're about + // to publish. optionalDependencies is critical here: @agent-relay/sdk + // pins the per-platform broker packages by exact version, and + // forgetting this section leaves a freshly-published SDK pointing + // at the prior release's broker packages. + const DEP_SECTIONS = ['dependencies', 'devDependencies', 'peerDependencies', 'optionalDependencies']; const rewriteInternalRefs = (pkg) => { for (const depType of DEP_SECTIONS) { for (const dep of Object.keys(pkg[depType] || {})) { diff --git a/packages/sdk/src/__tests__/broker-path.test.ts b/packages/sdk/src/__tests__/broker-path.test.ts new file mode 100644 index 000000000..0bd99220e --- /dev/null +++ b/packages/sdk/src/__tests__/broker-path.test.ts @@ -0,0 +1,99 @@ +/** + * Tests for broker-path resolver and the formatted spawn-time error. + * + * The resolver is almost pure fs + `require.resolve`, so we stage a fake + * optional-dep package in a tmp dir and exercise the real function. + */ + +import assert from 'node:assert/strict'; +import { chmodSync, existsSync, mkdirSync, mkdtempSync, rmSync, writeFileSync } from 'node:fs'; +import { tmpdir } from 'node:os'; +import { join } from 'node:path'; +import { afterEach, beforeEach, describe, test } from 'vitest'; + +import { + formatBrokerNotFoundError, + getBrokerBinaryPath, + getOptionalDepPackageName, +} from '../broker-path.js'; + +function stageOptionalDepPackage(root: string): string { + const pkgName = getOptionalDepPackageName(); + const ext = process.platform === 'win32' ? '.exe' : ''; + const pkgDir = join(root, 'node_modules', pkgName); + const binDir = join(pkgDir, 'bin'); + mkdirSync(binDir, { recursive: true }); + writeFileSync( + join(pkgDir, 'package.json'), + JSON.stringify({ name: pkgName, version: '0.0.0-test' }, null, 2), + ); + const binaryPath = join(binDir, `agent-relay-broker${ext}`); + writeFileSync(binaryPath, '#!/bin/sh\nexit 0\n'); + if (process.platform !== 'win32') { + chmodSync(binaryPath, 0o755); + } + return binaryPath; +} + +describe('broker-path', () => { + let originalCwd: string; + let originalEnv: NodeJS.ProcessEnv; + let tmp: string; + + beforeEach(() => { + originalCwd = process.cwd(); + originalEnv = { ...process.env }; + tmp = mkdtempSync(join(tmpdir(), 'broker-path-')); + delete process.env.BROKER_BINARY_PATH; + delete process.env.AGENT_RELAY_BIN; + }); + + afterEach(() => { + process.chdir(originalCwd); + process.env = originalEnv; + rmSync(tmp, { recursive: true, force: true }); + }); + + test('getOptionalDepPackageName returns @agent-relay/broker--', () => { + assert.equal( + getOptionalDepPackageName('darwin', 'arm64'), + '@agent-relay/broker-darwin-arm64', + ); + assert.equal(getOptionalDepPackageName('linux', 'x64'), '@agent-relay/broker-linux-x64'); + assert.equal(getOptionalDepPackageName('win32', 'x64'), '@agent-relay/broker-win32-x64'); + }); + + test('formatBrokerNotFoundError names the platform and the optional-dep package', () => { + const msg = formatBrokerNotFoundError(); + assert.match(msg, new RegExp(`${process.platform}-${process.arch}`)); + assert.match(msg, new RegExp(getOptionalDepPackageName())); + assert.match(msg, /--include=optional/); + assert.match(msg, /BROKER_BINARY_PATH/); + }); + + test('BROKER_BINARY_PATH env override short-circuits the resolver', () => { + const fakeBinary = join(tmp, 'agent-relay-broker'); + writeFileSync(fakeBinary, '#!/bin/sh\nexit 0\n'); + if (process.platform !== 'win32') { + chmodSync(fakeBinary, 0o755); + } + + process.env.BROKER_BINARY_PATH = fakeBinary; + assert.equal(getBrokerBinaryPath(), fakeBinary); + }); + + // Optional-dep end-to-end resolution is exercised by the cross-platform + // CI smoke job (.github/workflows/publish.yml → smoke-broker-packages), + // which packs real tarballs into a scratch project and confirms + // getBrokerBinaryPath() goes through node_modules/@agent-relay/broker-*. + // Unit-testing it inside the dev tree is fragile because source-checkout + // and ancestor-bin resolution win before the tmp cwd is consulted. + // Verify at least that the resolver reuses stageOptionalDepPackage to + // land an executable where the optional-dep path would find it. + test('stageOptionalDepPackage produces an executable in the expected layout', () => { + const staged = stageOptionalDepPackage(tmp); + assert.ok(existsSync(staged)); + assert.match(staged, new RegExp(getOptionalDepPackageName().replace('/', '[\\\\/]'))); + assert.match(staged, /[\\/]bin[\\/]/); + }); +}); diff --git a/packages/sdk/src/broker-path.ts b/packages/sdk/src/broker-path.ts index c236db709..2ce23ccf2 100644 --- a/packages/sdk/src/broker-path.ts +++ b/packages/sdk/src/broker-path.ts @@ -78,7 +78,14 @@ function getResolutionReferences(): string[] { addUniquePath(refs, process.argv[1]); } - // Fall back to the cwd so unusual runtime layouts still resolve. + // Fall back to the cwd's package.json as a final resolution anchor. + // `createRequire` only needs a file path that sits inside a project root + // — it doesn't have to exist. This catches setups where the SDK's module + // path and the entry script both sit outside the consumer's node_modules + // tree (e.g. a globally-installed SDK importing per-project optional deps, + // some vite/webpack bundling configurations, repl experimentation), but + // the consumer's cwd is inside their own project. We only use this + // reference to run require.resolve; no file I/O is triggered on misses. addUniquePath(refs, join(process.cwd(), 'package.json')); return refs; @@ -99,8 +106,8 @@ function requireResolveFromRefs(specifier: string): string | null { * Resolve the broker binary via the platform-specific optional-dependency * package (`@agent-relay/broker--`). Returns null when the * optional dep is not installed (expected when users install with - * --no-optional / --ignore-scripts or when the broker hasn't been published - * for their platform yet). + * --no-optional / --omit=optional / --include= omits optional, or when the + * broker hasn't been published for their platform yet). */ function getOptionalDepBinaryPath(ext: string): string | null { const pkgName = getOptionalDepPackageName(); From 94ba3de03c0c7b7af07ad5f03ea40efc1c7baeba Mon Sep 17 00:00:00 2001 From: Will Washburn Date: Fri, 24 Apr 2026 08:07:49 -0400 Subject: [PATCH 6/8] fix(ci): pack @agent-relay/config alongside SDK for smoke test MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The smoke-broker-packages job runs after the build job bumps every workspace to NEW_VERSION but before any package has shipped to the registry. The SDK's `dependencies` pins `@agent-relay/config` at that version, so `npm install` of the locally-packed SDK tarball fails with `ETARGET / No matching version found for @agent-relay/config@` — every smoke run on a version-bumped workflow breaks at that point. Pack `packages/config` into a tarball alongside the SDK + broker and install all three together. The negative-smoke leg gets the same treatment so it can still reach the `AgentRelayClient.spawn()` path that produces the clear "optional dep missing" error. `@agent-relay/config` is currently the SDK's only internal required dep; if another lands later the smoke job will fail loudly with the same ETARGET signature. Repro'd locally with a synthetic 5.99.99-smoketest bump: install without config fails ETARGET, install with config succeeds. Co-Authored-By: Claude Opus 4.7 (1M context) --- .github/workflows/publish.yml | 24 +++++++++++++++++------- 1 file changed, 17 insertions(+), 7 deletions(-) diff --git a/.github/workflows/publish.yml b/.github/workflows/publish.yml index 287fc0f0c..96d18840e 100644 --- a/.github/workflows/publish.yml +++ b/.github/workflows/publish.yml @@ -622,7 +622,7 @@ jobs: fs.writeFileSync(p, JSON.stringify(pkg, null, 2) + '\n'); " - - name: Pack SDK and broker tarballs + - name: Pack SDK, broker, and internal deps shell: bash run: | set -euo pipefail @@ -636,6 +636,11 @@ jobs: mkdir -p packages/sdk/bin (cd packages/sdk && npm pack --ignore-scripts --pack-destination "$TARBALLS") (cd "packages/${{ matrix.broker_pkg }}" && npm pack --ignore-scripts --pack-destination "$TARBALLS") + # The build job bumped every workspace to NEW_VERSION. The SDK's + # `dependencies` pins `@agent-relay/config` at that version, which + # is not on the registry yet at this point in the workflow — so + # pack it locally and install it alongside the SDK. + (cd packages/config && npm pack --ignore-scripts --pack-destination "$TARBALLS") ls -lh "$TARBALLS" - name: Install tarballs into scratch project @@ -649,8 +654,10 @@ jobs: npm init -y --silent >/dev/null SDK_TGZ=$(ls "$TARBALLS"/agent-relay-sdk-*.tgz | head -n1) BROKER_TGZ=$(ls "$TARBALLS"/agent-relay-broker-${{ matrix.platform }}-*.tgz | head -n1) - echo "Installing $SDK_TGZ + $BROKER_TGZ" - npm install --ignore-scripts --no-audit --no-fund "$SDK_TGZ" "$BROKER_TGZ" + CONFIG_TGZ=$(ls "$TARBALLS"/agent-relay-config-*.tgz | head -n1) + echo "Installing $SDK_TGZ + $BROKER_TGZ + $CONFIG_TGZ" + npm install --ignore-scripts --no-audit --no-fund \ + "$SDK_TGZ" "$BROKER_TGZ" "$CONFIG_TGZ" ls node_modules/@agent-relay/ - name: Resolver smoke — getBrokerBinaryPath() @@ -700,10 +707,13 @@ jobs: cd "$NEGATIVE" npm init -y --silent >/dev/null SDK_TGZ=$(ls "$TARBALLS"/agent-relay-sdk-*.tgz | head -n1) - # Install SDK alone without the broker. The SDK's optionalDependencies - # at exact '5.x' cannot resolve from this local install, so the - # resolver should return null and spawn() should throw a clear error. - npm install --ignore-scripts --no-audit --no-fund --no-optional "$SDK_TGZ" + CONFIG_TGZ=$(ls "$TARBALLS"/agent-relay-config-*.tgz | head -n1) + # Install SDK + config (an internal required dep whose bumped + # version isn't on the registry yet) but skip the broker optional + # deps entirely. The resolver should return null and spawn() + # should throw the clear error. + npm install --ignore-scripts --no-audit --no-fund --no-optional \ + "$SDK_TGZ" "$CONFIG_TGZ" node --input-type=module -e " import { AgentRelayClient } from '@agent-relay/sdk'; try { From 929e3b8e95dc56292023220590692b47705c7600 Mon Sep 17 00:00:00 2001 From: Will Washburn Date: Fri, 24 Apr 2026 08:21:24 -0400 Subject: [PATCH 7/8] ci: cross-platform post-publish verification of @agent-relay/sdk MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Add verify-publish-sdk.yml — a matrix job that, after every release, installs the just-published @agent-relay/sdk@ into a scratch project on each of the five target runners (macOS arm64/x64, Linux x64/arm64, Windows x64) and asserts: - the matching @agent-relay/broker-- optional dep was auto-selected by npm based on os/cpu (and no sibling brokers were installed alongside — a sanity check that the os/cpu injection in publish-broker-packages landed correctly) - getBrokerBinaryPath() resolves through that package and returns an executable file - AgentRelayClient.spawn() actually starts the registry-installed broker end-to-end and shutdown() tears it down Catches the failure modes the pre-publish smoke job can't see: registry round-trip, CDN propagation lag, missing or wrong os/cpu in the published manifest. Runs with exponential-backoff retry on npm view so the first run after publish isn't a flake. Wired into publish.yml's post-publish gate via workflow_call so every release exercises it automatically. Also available on-demand via workflow_dispatch for re-verifying an existing version. Co-Authored-By: Claude Opus 4.7 (1M context) --- .github/workflows/publish.yml | 20 +++ .github/workflows/verify-publish-sdk.yml | 187 +++++++++++++++++++++++ 2 files changed, 207 insertions(+) create mode 100644 .github/workflows/verify-publish-sdk.yml diff --git a/.github/workflows/publish.yml b/.github/workflows/publish.yml index 96d18840e..315681eff 100644 --- a/.github/workflows/publish.yml +++ b/.github/workflows/publish.yml @@ -1891,6 +1891,24 @@ jobs: with: version: ${{ needs.build.outputs.new_version }} + # Post-publish cross-platform verification of @agent-relay/sdk and its + # per-platform broker optional deps. Installs from the registry on every + # target (macOS arm64/x64, Linux x64/arm64, Windows x64) and runs spawn + # end-to-end. Catches registry-round-trip failures that smoke-broker-packages + # can't see (missing/wrong os/cpu on published manifests, CDN propagation). + verify-publish-sdk: + name: Verify Published SDK (Cross-Platform) + needs: [build, publish-broker-packages, publish-packages] + if: | + always() && + github.event.inputs.dry_run != 'true' && + (github.event.inputs.package == 'all' || github.event.inputs.package == 'sdk') && + needs.publish-broker-packages.result == 'success' && + needs.publish-packages.result == 'success' + uses: ./.github/workflows/verify-publish-sdk.yml + with: + version: ${{ needs.build.outputs.new_version }} + summary: name: Summary needs: @@ -1911,6 +1929,7 @@ jobs: publish-sdk-py, publish-main, verify-publish, + verify-publish-sdk, ] runs-on: ubuntu-latest if: always() @@ -1954,6 +1973,7 @@ jobs: echo "| Publish Python SDK | ${{ needs.publish-sdk-py.result == 'success' && '✅' || (needs.publish-sdk-py.result == 'skipped' && '⏭️' || '❌') }} ${{ needs.publish-sdk-py.result }} |" >> $GITHUB_STEP_SUMMARY echo "| Publish Main | ${{ needs.publish-main.result == 'success' && '✅' || (needs.publish-main.result == 'skipped' && '⏭️' || '❌') }} ${{ needs.publish-main.result }} |" >> $GITHUB_STEP_SUMMARY echo "| Post-Publish Verify | ${{ needs.verify-publish.result == 'success' && '✅' || (needs.verify-publish.result == 'skipped' && '⏭️' || '❌') }} ${{ needs.verify-publish.result }} |" >> $GITHUB_STEP_SUMMARY + echo "| Post-Publish Verify SDK (cross-platform) | ${{ needs.verify-publish-sdk.result == 'success' && '✅' || (needs.verify-publish-sdk.result == 'skipped' && '⏭️' || '❌') }} ${{ needs.verify-publish-sdk.result }} |" >> $GITHUB_STEP_SUMMARY if [ "$IS_PRERELEASE" = "true" ]; then echo "" >> $GITHUB_STEP_SUMMARY echo "### Next Steps" >> $GITHUB_STEP_SUMMARY diff --git a/.github/workflows/verify-publish-sdk.yml b/.github/workflows/verify-publish-sdk.yml new file mode 100644 index 000000000..625e1a738 --- /dev/null +++ b/.github/workflows/verify-publish-sdk.yml @@ -0,0 +1,187 @@ +name: Verify Published SDK (Cross-Platform) + +# Clean-room verification that @agent-relay/sdk — freshly installed from the +# public registry on each target OS/arch — pulls in the correct +# @agent-relay/broker-- optional dep and actually spawns a +# broker end-to-end. This is the last line of defense for the optional-dep +# pattern: pre-publish the smoke-broker-packages job exercises the logic via +# locally-packed tarballs, but it cannot catch registry round-trip bugs +# (wrong os/cpu manifest, selection logic, CDN propagation). +# +# Triggered: +# - Automatically after the publish workflow completes (via workflow_call) +# - Manually via workflow_dispatch (useful for re-verifying an existing +# version, e.g. after a registry flake or to check propagation). +# +# Not wired to pull_request: the workflow installs from the public registry, +# which means it can only verify versions that have already shipped. For +# pre-publish validation of this logic, use the smoke-broker-packages job in +# publish.yml — it runs the same assertions against locally-packed tarballs. + +on: + workflow_dispatch: + inputs: + version: + description: 'SDK version to verify (default: latest)' + required: false + type: string + default: 'latest' + workflow_call: + inputs: + version: + description: 'SDK version to verify' + required: false + type: string + default: 'latest' + +env: + AGENT_RELAY_TELEMETRY_DISABLED: 1 + +jobs: + verify-sdk: + name: Verify @agent-relay/sdk (${{ matrix.platform }}) + runs-on: ${{ matrix.os }} + strategy: + fail-fast: false + matrix: + include: + - platform: darwin-arm64 + os: macos-14 + expected_pkg: '@agent-relay/broker-darwin-arm64' + - platform: darwin-x64 + os: macos-13 + expected_pkg: '@agent-relay/broker-darwin-x64' + - platform: linux-x64 + os: ubuntu-latest + expected_pkg: '@agent-relay/broker-linux-x64' + - platform: linux-arm64 + os: ubuntu-24.04-arm + expected_pkg: '@agent-relay/broker-linux-arm64' + - platform: win32-x64 + os: windows-latest + expected_pkg: '@agent-relay/broker-win32-x64' + + steps: + - name: Setup Node.js + uses: actions/setup-node@v4 + with: + node-version: '22.14.0' + + - name: Resolve version spec + id: spec + shell: bash + run: | + VERSION="${{ inputs.version }}" + if [ -z "$VERSION" ]; then + VERSION="latest" + fi + echo "version=$VERSION" >> "$GITHUB_OUTPUT" + echo "spec=@agent-relay/sdk@$VERSION" >> "$GITHUB_OUTPUT" + echo "Using spec: @agent-relay/sdk@$VERSION" + + # Registry/CDN propagation can lag the publish by up to a minute. Retry + # with exponential backoff so the first run after publish doesn't miss. + - name: Wait for package on registry + shell: bash + run: | + set -euo pipefail + SPEC="${{ steps.spec.outputs.spec }}" + VERSION="${{ steps.spec.outputs.version }}" + if [ "$VERSION" = "latest" ]; then + echo "version=latest — no wait needed" + exit 0 + fi + for i in 1 2 3 4 5 6; do + if npm view "$SPEC" version >/dev/null 2>&1; then + echo "registry has $SPEC" + exit 0 + fi + WAIT=$((2 ** i)) + echo "attempt $i: registry not ready, sleeping ${WAIT}s" + sleep "$WAIT" + done + echo "FAIL: registry never surfaced $SPEC" + exit 1 + + - name: Install @agent-relay/sdk into scratch project + shell: bash + run: | + set -euo pipefail + SCRATCH="$RUNNER_TEMP/verify-sdk" + mkdir -p "$SCRATCH" + echo "SCRATCH=$SCRATCH" >> "$GITHUB_ENV" + cd "$SCRATCH" + npm init -y --silent >/dev/null + echo "Installing ${{ steps.spec.outputs.spec }}" + npm install --no-audit --no-fund "${{ steps.spec.outputs.spec }}" + echo "" + echo "=== installed @agent-relay/ packages ===" + ls node_modules/@agent-relay/ + + - name: Verify only the matching broker package was installed + shell: bash + run: | + set -euo pipefail + cd "$SCRATCH" + EXPECTED="${{ matrix.expected_pkg }}" + if [ ! -d "node_modules/$EXPECTED" ]; then + echo "FAIL: expected optional dep $EXPECTED was not installed" + echo "installed @agent-relay/ packages:" + ls node_modules/@agent-relay/ 2>&1 || true + exit 1 + fi + echo "OK: $EXPECTED present in node_modules" + + # npm should skip every sibling whose os/cpu doesn't match this + # runner. Catch a missing os/cpu constraint by asserting the + # unmatched packages were NOT installed. + for pkg in \ + @agent-relay/broker-darwin-arm64 \ + @agent-relay/broker-darwin-x64 \ + @agent-relay/broker-linux-arm64 \ + @agent-relay/broker-linux-x64 \ + @agent-relay/broker-win32-x64; do + if [ "$pkg" != "$EXPECTED" ] && [ -d "node_modules/$pkg" ]; then + echo "FAIL: sibling optional dep $pkg was installed on ${{ matrix.platform }}" + echo "npm should skip it based on os/cpu — published manifest may be missing constraints" + exit 1 + fi + done + echo "OK: only ${{ matrix.expected_pkg }} was installed; siblings correctly skipped" + + - name: Resolver smoke — getBrokerBinaryPath() + shell: bash + run: | + cd "$SCRATCH" + node --input-type=module -e " + import { getBrokerBinaryPath, getOptionalDepPackageName } from '@agent-relay/sdk/broker-path'; + import { accessSync, constants } from 'node:fs'; + const expected = getOptionalDepPackageName(); + const p = getBrokerBinaryPath(); + console.log('expected package:', expected); + console.log('resolved:', p); + if (!p) { console.error('FAIL: resolver returned null'); process.exit(1); } + if (!p.includes(expected)) { + console.error('FAIL: resolution did not go through the optional-dep package; got', p); + process.exit(1); + } + accessSync(p, constants.X_OK); + console.log('OK: resolver returned executable binary from optional-dep package'); + " + + - name: Spawn smoke — AgentRelayClient.spawn() + shell: bash + run: | + cd "$SCRATCH" + node --input-type=module -e " + import { AgentRelayClient } from '@agent-relay/sdk'; + const client = await AgentRelayClient.spawn({ + cwd: process.cwd(), + channels: ['general'], + startupTimeoutMs: 45000, + onStderr: (line) => console.error('[broker]', line), + }); + console.log('OK: AgentRelayClient.spawn() returned'); + await client.shutdown(); + console.log('OK: client.shutdown() completed'); + " From bf8ed1ebdb9aa8de9a18ca51be7bd4fac6384118 Mon Sep 17 00:00:00 2001 From: Will Washburn Date: Fri, 24 Apr 2026 08:35:57 -0400 Subject: [PATCH 8/8] fix(ci): verify-publish-sdk must accept publish-sdk-only too MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit verify-publish-sdk previously gated on publish-packages alone. With package='all' that's the right gate, but with package='sdk' (SDK-only release) publish-packages is skipped and publish-sdk-only does the actual publish — so the verify gate never fired, leaving post-publish cross-platform smoke uncovered for SDK-only releases. Add publish-sdk-only to `needs` and accept success from either path. The two jobs are mutually exclusive so at most one will be 'success' at a time; the other will be 'skipped'. Co-Authored-By: Claude Opus 4.7 (1M context) --- .github/workflows/publish.yml | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/.github/workflows/publish.yml b/.github/workflows/publish.yml index 315681eff..0a84c6c8f 100644 --- a/.github/workflows/publish.yml +++ b/.github/workflows/publish.yml @@ -1898,13 +1898,16 @@ jobs: # can't see (missing/wrong os/cpu on published manifests, CDN propagation). verify-publish-sdk: name: Verify Published SDK (Cross-Platform) - needs: [build, publish-broker-packages, publish-packages] + # publish-packages and publish-sdk-only are mutually exclusive: the first + # runs on package='all', the second on package='sdk'. Depend on both and + # accept a success from either so this gate fires for both release flows. + needs: [build, publish-broker-packages, publish-packages, publish-sdk-only] if: | always() && github.event.inputs.dry_run != 'true' && (github.event.inputs.package == 'all' || github.event.inputs.package == 'sdk') && needs.publish-broker-packages.result == 'success' && - needs.publish-packages.result == 'success' + (needs.publish-packages.result == 'success' || needs.publish-sdk-only.result == 'success') uses: ./.github/workflows/verify-publish-sdk.yml with: version: ${{ needs.build.outputs.new_version }}