From 316552325e2a43a0bf03659811ca84019b7758ce Mon Sep 17 00:00:00 2001 From: Abhijeet Prasad Date: Wed, 21 Oct 2026 17:00:59 -0700 Subject: [PATCH 1/6] fix: update dependencies for Node.js October 2026 security releases Pin Node to the patched 24.22.1 release and bump undici past the October 15 advisories (cursor 7.30.1, pi 8.12.2 via a workspace override). Centralize tool versions in a root .tool-versions (node, pnpm, rust) that every workflow reads, pin Rust instead of floating on stable, and add a local-only mise.toml whose postinstall hook runs `make install-deps`. Co-Authored-By: Claude Opus 5.5 --- .github/actions/setup-node-pnpm/action.yml | 30 ++++++++ .github/actions/setup-rust/action.yml | 26 +++++++ .github/workflows/ci.yml | 76 ++++++++------------ .github/workflows/release-opencode.yml | 6 +- .github/workflows/release-pi.yml | 6 +- .github/workflows/release.yml | 2 +- .github/workflows/smoke-codex.yml | 2 +- .tool-versions | 3 + AGENTS.md | 11 +++ Makefile | 8 ++- mise.toml | 8 +++ src/plugins/cursor/content/package-lock.json | 6 +- src/plugins/opencode/content/.tool-versions | 3 - src/plugins/pi/content/.tool-versions | 3 - src/plugins/pi/content/pnpm-lock.yaml | 25 +++---- src/plugins/pi/content/pnpm-workspace.yaml | 1 + 16 files changed, 138 insertions(+), 78 deletions(-) create mode 100644 .github/actions/setup-node-pnpm/action.yml create mode 100644 .github/actions/setup-rust/action.yml create mode 100644 .tool-versions create mode 100644 mise.toml delete mode 100644 src/plugins/opencode/content/.tool-versions delete mode 100644 src/plugins/pi/content/.tool-versions diff --git a/.github/actions/setup-node-pnpm/action.yml b/.github/actions/setup-node-pnpm/action.yml new file mode 100644 index 00000000..ba3deed4 --- /dev/null +++ b/.github/actions/setup-node-pnpm/action.yml @@ -0,0 +1,30 @@ +name: Set up Node and pnpm +description: Install the Node and pnpm versions pinned in the root .tool-versions. +inputs: + cache-dependency-path: + description: pnpm lockfile(s) whose contents key the pnpm store cache. + required: true +runs: + using: composite + steps: + # pnpm/action-setup cannot read .tool-versions, so resolve the version here. + - name: Read pnpm version + id: versions + shell: bash + run: | + pnpm_version="$(awk '$1 == "pnpm" { print $2 }' "$GITHUB_WORKSPACE/.tool-versions")" + if [ -z "$pnpm_version" ]; then + echo "::error file=.tool-versions::missing pnpm entry" + exit 1 + fi + echo "pnpm=$pnpm_version" >> "$GITHUB_OUTPUT" + - name: Set up pnpm + uses: pnpm/action-setup@fc06bc1257f339d1d5d8b3a19a8cae5388b55320 # v5.0.0 + with: + version: ${{ steps.versions.outputs.pnpm }} + - name: Set up Node + uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6.4.0 + with: + node-version-file: .tool-versions + cache: pnpm + cache-dependency-path: ${{ inputs.cache-dependency-path }} diff --git a/.github/actions/setup-rust/action.yml b/.github/actions/setup-rust/action.yml new file mode 100644 index 00000000..fb120ba8 --- /dev/null +++ b/.github/actions/setup-rust/action.yml @@ -0,0 +1,26 @@ +name: Set up Rust +description: Install the Rust toolchain pinned in the root .tool-versions and make it the default. +inputs: + components: + description: Space-separated rustup components to add, such as "clippy rustfmt". + required: false + default: "" +runs: + using: composite + steps: + - name: Install Rust + shell: bash + env: + COMPONENTS: ${{ inputs.components }} + run: | + rust_version="$(awk '$1 == "rust" { print $2 }' "$GITHUB_WORKSPACE/.tool-versions")" + if [ -z "$rust_version" ]; then + echo "::error file=.tool-versions::missing rust entry" + exit 1 + fi + rustup toolchain install "$rust_version" --profile minimal + rustup default "$rust_version" + if [ -n "$COMPONENTS" ]; then + # shellcheck disable=SC2086 # split the component list into arguments + rustup component add $COMPONENTS + fi diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index cec29b0c..1bcabbec 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -23,15 +23,9 @@ jobs: timeout-minutes: 10 steps: - uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4.3.1 - - name: Set up pnpm - uses: pnpm/action-setup@fc06bc1257f339d1d5d8b3a19a8cae5388b55320 # v5.0.0 + - name: Set up Node and pnpm + uses: ./.github/actions/setup-node-pnpm with: - version: 11.21.0 - - name: Set up Node - uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6.4.0 - with: - node-version: 24 - cache: pnpm cache-dependency-path: | src/plugins/opencode/content/pnpm-lock.yaml src/plugins/pi/content/pnpm-lock.yaml @@ -47,6 +41,19 @@ jobs: echo "OpenCode still contains a Bun lockfile" >&2 exit 1 fi + - name: Check packageManager matches .tool-versions + shell: bash + run: | + expected="pnpm@$(awk '$1 == "pnpm" { print $2 }' .tool-versions)" + status=0 + for manifest in src/plugins/opencode/content/package.json src/plugins/pi/content/package.json; do + actual="$(node -p 'require(process.argv[1]).packageManager' "$PWD/$manifest")" + if [ "$actual" != "$expected" ]; then + echo "::error file=$manifest::packageManager is $actual; .tool-versions pins $expected" + status=1 + fi + done + exit "$status" - name: Build and validate plugins run: make test - name: Package Cursor extension @@ -67,15 +74,9 @@ jobs: timeout-minutes: 10 steps: - uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4.3.1 - - name: Set up pnpm - uses: pnpm/action-setup@fc06bc1257f339d1d5d8b3a19a8cae5388b55320 # v5.0.0 - with: - version: 11.21.0 - - name: Set up Node - uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6.4.0 + - name: Set up Node and pnpm + uses: ./.github/actions/setup-node-pnpm with: - node-version: 24 - cache: pnpm cache-dependency-path: | src/plugins/opencode/content/pnpm-lock.yaml src/plugins/pi/content/pnpm-lock.yaml @@ -120,7 +121,7 @@ jobs: run: New-Item -ItemType Directory -Force -Path $env:TEMP | Out-Null - name: Install Rust for native Windows console probes if: runner.os == 'Windows' - run: rustup default stable + uses: ./.github/actions/setup-rust - name: Verify JavaScript background launches have no Windows console if: runner.os == 'Windows' run: node scripts/tests/windows-background-process.mjs @@ -135,15 +136,9 @@ jobs: timeout-minutes: 10 steps: - uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4.3.1 - - name: Set up pnpm - uses: pnpm/action-setup@fc06bc1257f339d1d5d8b3a19a8cae5388b55320 # v5.0.0 + - name: Set up Node and pnpm + uses: ./.github/actions/setup-node-pnpm with: - version: 11.21.0 - - name: Set up Node - uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6.4.0 - with: - node-version: 24 - cache: pnpm cache-dependency-path: src/plugins/opencode/content/pnpm-lock.yaml # `plugins` validates the package once; this job only checks the # packed tarball against each supported OpenCode peer. @@ -191,15 +186,9 @@ jobs: timeout-minutes: 20 steps: - uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4.3.1 - - name: Set up pnpm - uses: pnpm/action-setup@fc06bc1257f339d1d5d8b3a19a8cae5388b55320 # v5.0.0 - with: - version: 11.21.0 - - name: Set up Node - uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6.4.0 + - name: Set up Node and pnpm + uses: ./.github/actions/setup-node-pnpm with: - node-version: 24 - cache: pnpm cache-dependency-path: src/plugins/pi/content/pnpm-lock.yaml # `plugins` validates the package once. This job runs a real Pi session # with the packed extension against each Pi release line, so a Pi @@ -218,9 +207,7 @@ jobs: '${{ matrix.pi-target.piCodingAgentSpec }}' "$RUNNER_TEMP/pi-install-test/node_modules/.bin/pi" --version - name: Install Rust - run: | - rustup toolchain install stable --profile minimal - rustup default stable + uses: ./.github/actions/setup-rust # One cache for every Pi target; pushes to main refresh it from the # latest target and PRs restore it. - name: Cache Rust build @@ -291,10 +278,9 @@ jobs: shell: pwsh run: New-Item -ItemType Directory -Force -Path $env:TEMP | Out-Null - name: Install Rust - run: | - rustup toolchain install stable --profile minimal - rustup default stable - rustup component add clippy rustfmt + uses: ./.github/actions/setup-rust + with: + components: clippy rustfmt # PRs restore main's cache; pushes to main and manual runs refresh it. - name: Cache Rust build uses: Swatinem/rust-cache@6323deb102c322ba6fcbdcafc7e3dddab59af2b6 # v2.9.2 @@ -338,15 +324,9 @@ jobs: fi sleep 10 done - - name: Set up pnpm - uses: pnpm/action-setup@fc06bc1257f339d1d5d8b3a19a8cae5388b55320 # v5.0.0 - with: - version: 11.21.0 - - name: Set up Node - uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6.4.0 + - name: Set up Node and pnpm + uses: ./.github/actions/setup-node-pnpm with: - node-version: 24 - cache: pnpm cache-dependency-path: | src/plugins/opencode/content/pnpm-lock.yaml src/plugins/pi/content/pnpm-lock.yaml diff --git a/.github/workflows/release-opencode.yml b/.github/workflows/release-opencode.yml index 148b7c56..2ac11753 100644 --- a/.github/workflows/release-opencode.yml +++ b/.github/workflows/release-opencode.yml @@ -1,5 +1,5 @@ # GENERATED by sdk-actions `bin/workflow` — update with: bin/workflow update -# sdk-actions: {"template":"release/js/turnkey","ref":"71731d4a726df3e3bf42e50c2da4a75842b038dd","version":"1.0.0","params":{"access":"public","allowed_channels":"latest,rc,next,beta","dry_run_environment":"npm-opencode-dry-run","emoji":":javascript:","node_version":"src/plugins/opencode/content/.tool-versions","package_name":"@braintrust/trace-opencode","provenance":"true","publish_environment":"npm-opencode","slack_channel_var":"SLACK_SDK_RELEASE_CHANNEL","slack_token_secret":"SLACK_BOT_TOKEN","workflow_name":"Release OpenCode","working_directory":"src/plugins/opencode/content"}} +# sdk-actions: {"template":"release/js/turnkey","ref":"71731d4a726df3e3bf42e50c2da4a75842b038dd","version":"1.0.0","params":{"access":"public","allowed_channels":"latest,rc,next,beta","dry_run_environment":"npm-opencode-dry-run","emoji":":javascript:","node_version":".tool-versions","package_name":"@braintrust/trace-opencode","provenance":"true","publish_environment":"npm-opencode","slack_channel_var":"SLACK_SDK_RELEASE_CHANNEL","slack_token_secret":"SLACK_BOT_TOKEN","workflow_name":"Release OpenCode","working_directory":"src/plugins/opencode/content"}} name: Release OpenCode @@ -79,7 +79,7 @@ jobs: sha: ${{ inputs.sha }} dry_run: ${{ inputs.dry_run }} working_directory: src/plugins/opencode/content - node_version: src/plugins/opencode/content/.tool-versions + node_version: .tool-versions release_tag: ${{ needs.configure.outputs.release_tag }} channel: ${{ needs.configure.outputs.channel }} allowed_channels: 'latest,rc,next,beta' @@ -121,7 +121,7 @@ jobs: with: sha: ${{ inputs.sha }} working_directory: src/plugins/opencode/content - node_version: src/plugins/opencode/content/.tool-versions + node_version: .tool-versions dry_run: ${{ inputs.dry_run }} release_tag: ${{ needs.configure.outputs.release_tag }} already_published: ${{ needs.configure.outputs.already_published }} diff --git a/.github/workflows/release-pi.yml b/.github/workflows/release-pi.yml index cc50e633..5296cd81 100644 --- a/.github/workflows/release-pi.yml +++ b/.github/workflows/release-pi.yml @@ -1,5 +1,5 @@ # GENERATED by sdk-actions `bin/workflow` — update with: bin/workflow update -# sdk-actions: {"template":"release/js/turnkey","ref":"71731d4a726df3e3bf42e50c2da4a75842b038dd","version":"1.0.0","params":{"access":"public","allowed_channels":"latest,rc,next,beta","dry_run_environment":"npm-pi-dry-run","emoji":":javascript:","node_version":"src/plugins/pi/content/.tool-versions","package_name":"@braintrust/pi-extension","provenance":"true","publish_environment":"npm-pi","slack_channel_var":"SLACK_SDK_RELEASE_CHANNEL","slack_token_secret":"SLACK_BOT_TOKEN","workflow_name":"Release Pi","working_directory":"src/plugins/pi/content"}} +# sdk-actions: {"template":"release/js/turnkey","ref":"71731d4a726df3e3bf42e50c2da4a75842b038dd","version":"1.0.0","params":{"access":"public","allowed_channels":"latest,rc,next,beta","dry_run_environment":"npm-pi-dry-run","emoji":":javascript:","node_version":".tool-versions","package_name":"@braintrust/pi-extension","provenance":"true","publish_environment":"npm-pi","slack_channel_var":"SLACK_SDK_RELEASE_CHANNEL","slack_token_secret":"SLACK_BOT_TOKEN","workflow_name":"Release Pi","working_directory":"src/plugins/pi/content"}} name: Release Pi @@ -79,7 +79,7 @@ jobs: sha: ${{ inputs.sha }} dry_run: ${{ inputs.dry_run }} working_directory: src/plugins/pi/content - node_version: src/plugins/pi/content/.tool-versions + node_version: .tool-versions release_tag: ${{ needs.configure.outputs.release_tag }} channel: ${{ needs.configure.outputs.channel }} allowed_channels: 'latest,rc,next,beta' @@ -121,7 +121,7 @@ jobs: with: sha: ${{ inputs.sha }} working_directory: src/plugins/pi/content - node_version: src/plugins/pi/content/.tool-versions + node_version: .tool-versions dry_run: ${{ inputs.dry_run }} release_tag: ${{ needs.configure.outputs.release_tag }} already_published: ${{ needs.configure.outputs.already_published }} diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index e623a195..12077288 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -271,7 +271,7 @@ jobs: - name: Set up Node uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6.4.0 with: - node-version: 24 + node-version-file: .tool-versions cache: npm cache-dependency-path: src/plugins/cursor/content/package-lock.json diff --git a/.github/workflows/smoke-codex.yml b/.github/workflows/smoke-codex.yml index bdc66ba1..936b8895 100644 --- a/.github/workflows/smoke-codex.yml +++ b/.github/workflows/smoke-codex.yml @@ -81,7 +81,7 @@ jobs: - name: Set up Node uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4 with: - node-version: 22 + node-version-file: .tool-versions - name: Install Codex and bt CLIs run: | diff --git a/.tool-versions b/.tool-versions new file mode 100644 index 00000000..76429086 --- /dev/null +++ b/.tool-versions @@ -0,0 +1,3 @@ +node 24.22.1 +pnpm 11.21.0 +rust 1.99.1 diff --git a/AGENTS.md b/AGENTS.md index e81f1b35..71acbe2a 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -37,7 +37,11 @@ limitation and preserve known parent totals rather than inventing attribution. ## Local development +Install the pinned tools with [mise](https://mise.jdx.dev). `mise install` +reads `.tool-versions` and `mise.toml`, then runs `make install-deps`: + ```bash +mise install make build make build-codex make test @@ -133,6 +137,13 @@ which trace a real CLI session through the daemon when `OPENAI_API_KEY` or `CURSOR_API_KEY` is available. Cursor's generated distribution is separate from the existing Braintrust MCP extension; users may install either or both. +The root `.tool-versions` pins Node, pnpm, and Rust for both local +development and CI. Workflows do not use mise: they read the file through +`actions/setup-node`, `.github/actions/setup-node-pnpm`, and +`.github/actions/setup-rust`. Bump versions there rather than in individual +workflows. CI requires each pnpm package's `packageManager` to match it. +`mise.toml` holds local-only tools and hooks. + CI builds and validates the plugin packages on Linux. It runs the JavaScript daemon client and the Pi and OpenCode unit tests, and builds, tests, and lints the Rust daemon, on Linux, macOS, and Windows. Pushes to `main` and manual runs diff --git a/Makefile b/Makefile index db0c13b1..1a2c3ce8 100644 --- a/Makefile +++ b/Makefile @@ -6,6 +6,7 @@ # publish.sh deploy it (push to dist repo, npm/vsce publish, ...) # # Targets fan out over every plugin dir: +# make install-deps install JavaScript dependencies for local development # make build build all plugins into dist/ # make build-codex build just one # make test build then validate all @@ -24,7 +25,12 @@ BUILD_RULES := $(addprefix build-,$(PLUGINS)) PUBLISH_RULES := $(addprefix publish-,$(PLUGINS)) VALIDATE_RULES := $(addprefix validate-,$(PLUGINS)) -.PHONY: build test publish clean $(BUILD_RULES) $(VALIDATE_RULES) $(PUBLISH_RULES) +.PHONY: install-deps build test publish clean $(BUILD_RULES) $(VALIDATE_RULES) $(PUBLISH_RULES) + +install-deps: + mise exec -- pnpm --dir src/plugins/opencode/content install --frozen-lockfile + mise exec -- pnpm --dir src/plugins/pi/content install --frozen-lockfile + mise exec -- npm --prefix src/plugins/cursor/content ci --no-audit --no-fund build: $(BUILD_RULES) diff --git a/mise.toml b/mise.toml new file mode 100644 index 00000000..e33b0009 --- /dev/null +++ b/mise.toml @@ -0,0 +1,8 @@ +# Mise will automatically read and use .tool-versions files as well as this file. +# CI reads .tool-versions directly and does not use mise. + +[tools] +"npm:vite-plus" = "0.2.9" + +[hooks] +postinstall = "make install-deps" diff --git a/src/plugins/cursor/content/package-lock.json b/src/plugins/cursor/content/package-lock.json index 8586fbbd..31b06f80 100644 --- a/src/plugins/cursor/content/package-lock.json +++ b/src/plugins/cursor/content/package-lock.json @@ -5094,9 +5094,9 @@ "license": "MIT" }, "node_modules/undici": { - "version": "7.30.0", - "resolved": "https://registry.npmjs.org/undici/-/undici-7.30.0.tgz", - "integrity": "sha512-dkrQXeHSaoamnItlYbmzG0wFYrM0ZwDxCIg0A7aKjTyyhh9svRzCNFEzV+Vm05/yehjCzjDZ31KXfGEjYSztDQ==", + "version": "7.30.1", + "resolved": "https://registry.npmjs.org/undici/-/undici-7.30.1.tgz", + "integrity": "sha512-03KrMbjIFDWQR5RNPrSw3bd1r12JpyFQl9/yxjUbJtqkjmllqC5lFjZh6ujeuJCKr8EOQvRW872Oy/az+nLYxg==", "dev": true, "license": "MIT", "engines": { diff --git a/src/plugins/opencode/content/.tool-versions b/src/plugins/opencode/content/.tool-versions deleted file mode 100644 index 013ce7fc..00000000 --- a/src/plugins/opencode/content/.tool-versions +++ /dev/null @@ -1,3 +0,0 @@ -node 24 -pnpm 11.21.0 -npm:vite-plus 0.2.1 diff --git a/src/plugins/pi/content/.tool-versions b/src/plugins/pi/content/.tool-versions deleted file mode 100644 index 013ce7fc..00000000 --- a/src/plugins/pi/content/.tool-versions +++ /dev/null @@ -1,3 +0,0 @@ -node 24 -pnpm 11.21.0 -npm:vite-plus 0.2.1 diff --git a/src/plugins/pi/content/pnpm-lock.yaml b/src/plugins/pi/content/pnpm-lock.yaml index 294c5bf3..6a31256d 100644 --- a/src/plugins/pi/content/pnpm-lock.yaml +++ b/src/plugins/pi/content/pnpm-lock.yaml @@ -10,6 +10,7 @@ overrides: postcss: 8.5.23 source-map-js: 1.2.2 tinypool: 2.1.2 + undici: 8.12.2 vite: 8.0.16 vitest: 4.1.11 @@ -1774,8 +1775,8 @@ packages: undici-types@8.9.0: resolution: {integrity: sha512-KTDyRTYX8sWmKXAikPHHSyc63CRPETMctyjKFupcC6OBLXT3xsN0e9aF7m+mIXutFWpUXuedtowG7iLOzp0kQg==} - undici@8.10.2: - resolution: {integrity: sha512-/y4/bH9YNU5hi9NIrpOuvGXFcxrj3CMrV+/AYpowAYTpHn8gX/XPFjNy766FPoYY0miQhdW977JFWKGNhBdwyQ==} + undici@8.12.2: + resolution: {integrity: sha512-XtuDWVdqTLr3NAxm2ly9/hU6kUsJOgGh1RASzMBbsw6aWCOaQzHdW+QQDfdERjfJ2V5vL5pRPa4RetO9UfivDg==} engines: {node: '>=22.19.0'} vite-plus@0.2.9: @@ -2122,9 +2123,9 @@ snapshots: dependencies: esbuild: 0.28.2 - '@earendil-works/pi-agent-core@1.0.0(@aws-sdk/credential-provider-node@3.972.84)(@smithy/signature-v4@5.7.4)(undici@8.10.2)(ws@8.22.0)(zod@4.4.3)': + '@earendil-works/pi-agent-core@1.0.0(@aws-sdk/credential-provider-node@3.972.84)(@smithy/signature-v4@5.7.4)(undici@8.12.2)(ws@8.22.0)(zod@4.4.3)': dependencies: - '@earendil-works/pi-ai': 1.0.0(@aws-sdk/credential-provider-node@3.972.84)(@smithy/signature-v4@5.7.4)(undici@8.10.2)(ws@8.22.0)(zod@4.4.3) + '@earendil-works/pi-ai': 1.0.0(@aws-sdk/credential-provider-node@3.972.84)(@smithy/signature-v4@5.7.4)(undici@8.12.2)(ws@8.22.0)(zod@4.4.3) typebox: 1.3.27 transitivePeerDependencies: - '@aws-sdk/credential-provider-node' @@ -2139,7 +2140,7 @@ snapshots: - ws - zod - '@earendil-works/pi-ai@1.0.0(@aws-sdk/credential-provider-node@3.972.84)(@smithy/signature-v4@5.7.4)(undici@8.10.2)(ws@8.22.0)(zod@4.4.3)': + '@earendil-works/pi-ai@1.0.0(@aws-sdk/credential-provider-node@3.972.84)(@smithy/signature-v4@5.7.4)(undici@8.12.2)(ws@8.22.0)(zod@4.4.3)': dependencies: '@anthropic-ai/sdk': 0.124.0(zod@4.4.3) '@aws-sdk/client-bedrock-runtime': 3.1127.0 @@ -2148,7 +2149,7 @@ snapshots: '@smithy/node-http-handler': 4.12.1 http-proxy-agent: 9.1.0 https-proxy-agent: 9.1.0 - openai: 7.19.0(@aws-sdk/credential-provider-node@3.972.84)(@smithy/signature-v4@5.7.4)(undici@8.10.2)(ws@8.22.0)(zod@4.4.3) + openai: 7.19.0(@aws-sdk/credential-provider-node@3.972.84)(@smithy/signature-v4@5.7.4)(undici@8.12.2)(ws@8.22.0)(zod@4.4.3) partial-json: 0.1.7 typebox: 1.3.27 transitivePeerDependencies: @@ -2171,8 +2172,8 @@ snapshots: '@earendil-works/pi-coding-agent@1.0.0(@aws-sdk/credential-provider-node@3.972.84)(@smithy/signature-v4@5.7.4)(ws@8.22.0)(zod@4.4.3)': dependencies: '@earendil-works/chord': 1.0.0 - '@earendil-works/pi-agent-core': 1.0.0(@aws-sdk/credential-provider-node@3.972.84)(@smithy/signature-v4@5.7.4)(undici@8.10.2)(ws@8.22.0)(zod@4.4.3) - '@earendil-works/pi-ai': 1.0.0(@aws-sdk/credential-provider-node@3.972.84)(@smithy/signature-v4@5.7.4)(undici@8.10.2)(ws@8.22.0)(zod@4.4.3) + '@earendil-works/pi-agent-core': 1.0.0(@aws-sdk/credential-provider-node@3.972.84)(@smithy/signature-v4@5.7.4)(undici@8.12.2)(ws@8.22.0)(zod@4.4.3) + '@earendil-works/pi-ai': 1.0.0(@aws-sdk/credential-provider-node@3.972.84)(@smithy/signature-v4@5.7.4)(undici@8.12.2)(ws@8.22.0)(zod@4.4.3) '@earendil-works/pi-codemode': 1.0.0 '@earendil-works/pi-mcp': 1.0.0 '@earendil-works/pi-tui': 1.0.0 @@ -2190,7 +2191,7 @@ snapshots: quickjs-wasi: 3.6.2 semver: 7.8.5 typebox: 1.3.27 - undici: 8.10.2 + undici: 8.12.2 yaml: 2.9.0 transitivePeerDependencies: - '@aws-sdk/credential-provider-node' @@ -3192,11 +3193,11 @@ snapshots: obug@2.2.1: {} - openai@7.19.0(@aws-sdk/credential-provider-node@3.972.84)(@smithy/signature-v4@5.7.4)(undici@8.10.2)(ws@8.22.0)(zod@4.4.3): + openai@7.19.0(@aws-sdk/credential-provider-node@3.972.84)(@smithy/signature-v4@5.7.4)(undici@8.12.2)(ws@8.22.0)(zod@4.4.3): optionalDependencies: '@aws-sdk/credential-provider-node': 3.972.84 '@smithy/signature-v4': 5.7.4 - undici: 8.10.2 + undici: 8.12.2 ws: 8.22.0 zod: 4.4.3 @@ -3405,7 +3406,7 @@ snapshots: undici-types@8.9.0: {} - undici@8.10.2: {} + undici@8.12.2: {} vite-plus@0.2.9(@opentelemetry/api@1.9.0)(@types/node@26.6.4)(esbuild@0.28.2)(jiti@2.7.0)(tsx@4.21.0)(typescript@6.0.3)(vite@8.0.16(@types/node@26.6.4)(esbuild@0.28.2)(jiti@2.7.0)(tsx@4.21.0)(yaml@2.9.0))(yaml@2.9.0): dependencies: diff --git a/src/plugins/pi/content/pnpm-workspace.yaml b/src/plugins/pi/content/pnpm-workspace.yaml index d52de7f5..cb1e52f3 100644 --- a/src/plugins/pi/content/pnpm-workspace.yaml +++ b/src/plugins/pi/content/pnpm-workspace.yaml @@ -9,6 +9,7 @@ overrides: postcss: 8.5.23 source-map-js: 1.2.2 tinypool: 2.1.2 + undici: 8.12.2 vite: 8.0.16 vitest: 4.1.11 minimumReleaseAgeExclude: From baad56f3cc5c093a40117c4522593c4108acf11a Mon Sep 17 00:00:00 2001 From: Abhijeet Prasad Date: Mon, 26 Oct 2026 12:03:10 -0400 Subject: [PATCH 2/6] chore: pin vite-plus in .tool-versions Co-Authored-By: Claude Opus 5.5 --- .tool-versions | 1 + AGENTS.md | 6 +++--- mise.toml | 3 --- 3 files changed, 4 insertions(+), 6 deletions(-) diff --git a/.tool-versions b/.tool-versions index 76429086..a4477451 100644 --- a/.tool-versions +++ b/.tool-versions @@ -1,3 +1,4 @@ node 24.22.1 pnpm 11.21.0 rust 1.99.1 +npm:vite-plus 0.2.9 diff --git a/AGENTS.md b/AGENTS.md index 71acbe2a..f7b51b9a 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -137,12 +137,12 @@ which trace a real CLI session through the daemon when `OPENAI_API_KEY` or `CURSOR_API_KEY` is available. Cursor's generated distribution is separate from the existing Braintrust MCP extension; users may install either or both. -The root `.tool-versions` pins Node, pnpm, and Rust for both local -development and CI. Workflows do not use mise: they read the file through +The root `.tool-versions` pins Node, pnpm, Rust, and vite-plus for local +development; CI reads its Node, pnpm, and Rust pins without mise through `actions/setup-node`, `.github/actions/setup-node-pnpm`, and `.github/actions/setup-rust`. Bump versions there rather than in individual workflows. CI requires each pnpm package's `packageManager` to match it. -`mise.toml` holds local-only tools and hooks. +`mise.toml` holds local-only mise hooks. CI builds and validates the plugin packages on Linux. It runs the JavaScript daemon client and the Pi and OpenCode unit tests, and builds, tests, and lints diff --git a/mise.toml b/mise.toml index e33b0009..02bfb79a 100644 --- a/mise.toml +++ b/mise.toml @@ -1,8 +1,5 @@ # Mise will automatically read and use .tool-versions files as well as this file. # CI reads .tool-versions directly and does not use mise. -[tools] -"npm:vite-plus" = "0.2.9" - [hooks] postinstall = "make install-deps" From a651372286d5ca3df6f854f27d3d1521b3be40a6 Mon Sep 17 00:00:00 2001 From: Abhijeet Prasad Date: Mon, 26 Oct 2026 12:04:00 -0400 Subject: [PATCH 3/6] ci: check packageManager inside setup-node-pnpm Co-Authored-By: Claude Opus 5.5 --- .github/actions/setup-node-pnpm/action.yml | 11 +++++++++++ .github/workflows/ci.yml | 13 ------------- 2 files changed, 11 insertions(+), 13 deletions(-) diff --git a/.github/actions/setup-node-pnpm/action.yml b/.github/actions/setup-node-pnpm/action.yml index ba3deed4..8e6b61f0 100644 --- a/.github/actions/setup-node-pnpm/action.yml +++ b/.github/actions/setup-node-pnpm/action.yml @@ -8,6 +8,8 @@ runs: using: composite steps: # pnpm/action-setup cannot read .tool-versions, so resolve the version here. + # pnpm also switches to each package's packageManager version, so require + # those to match the pin rather than silently running a different pnpm. - name: Read pnpm version id: versions shell: bash @@ -17,6 +19,15 @@ runs: echo "::error file=.tool-versions::missing pnpm entry" exit 1 fi + status=0 + for manifest in "$GITHUB_WORKSPACE"/src/plugins/*/content/package.json; do + actual="$(sed -n 's/^ *"packageManager": *"\(pnpm@[^"]*\)".*/\1/p' "$manifest")" + if [ -n "$actual" ] && [ "$actual" != "pnpm@$pnpm_version" ]; then + echo "::error file=${manifest#"$GITHUB_WORKSPACE"/}::packageManager is $actual; .tool-versions pins pnpm@$pnpm_version" + status=1 + fi + done + [ "$status" = 0 ] || exit 1 echo "pnpm=$pnpm_version" >> "$GITHUB_OUTPUT" - name: Set up pnpm uses: pnpm/action-setup@fc06bc1257f339d1d5d8b3a19a8cae5388b55320 # v5.0.0 diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 1bcabbec..4b3a66e0 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -41,19 +41,6 @@ jobs: echo "OpenCode still contains a Bun lockfile" >&2 exit 1 fi - - name: Check packageManager matches .tool-versions - shell: bash - run: | - expected="pnpm@$(awk '$1 == "pnpm" { print $2 }' .tool-versions)" - status=0 - for manifest in src/plugins/opencode/content/package.json src/plugins/pi/content/package.json; do - actual="$(node -p 'require(process.argv[1]).packageManager' "$PWD/$manifest")" - if [ "$actual" != "$expected" ]; then - echo "::error file=$manifest::packageManager is $actual; .tool-versions pins $expected" - status=1 - fi - done - exit "$status" - name: Build and validate plugins run: make test - name: Package Cursor extension From 101a062b324caa1f3c0d95b38814d52ba304ef7c Mon Sep 17 00:00:00 2001 From: Abhijeet Prasad Date: Mon, 26 Oct 2026 12:07:55 -0400 Subject: [PATCH 4/6] ci: simplify packageManager check Co-Authored-By: Claude Opus 5.5 --- .github/actions/setup-node-pnpm/action.yml | 28 +++++++++++++--------- 1 file changed, 17 insertions(+), 11 deletions(-) diff --git a/.github/actions/setup-node-pnpm/action.yml b/.github/actions/setup-node-pnpm/action.yml index 8e6b61f0..7cea3280 100644 --- a/.github/actions/setup-node-pnpm/action.yml +++ b/.github/actions/setup-node-pnpm/action.yml @@ -8,8 +8,6 @@ runs: using: composite steps: # pnpm/action-setup cannot read .tool-versions, so resolve the version here. - # pnpm also switches to each package's packageManager version, so require - # those to match the pin rather than silently running a different pnpm. - name: Read pnpm version id: versions shell: bash @@ -19,15 +17,6 @@ runs: echo "::error file=.tool-versions::missing pnpm entry" exit 1 fi - status=0 - for manifest in "$GITHUB_WORKSPACE"/src/plugins/*/content/package.json; do - actual="$(sed -n 's/^ *"packageManager": *"\(pnpm@[^"]*\)".*/\1/p' "$manifest")" - if [ -n "$actual" ] && [ "$actual" != "pnpm@$pnpm_version" ]; then - echo "::error file=${manifest#"$GITHUB_WORKSPACE"/}::packageManager is $actual; .tool-versions pins pnpm@$pnpm_version" - status=1 - fi - done - [ "$status" = 0 ] || exit 1 echo "pnpm=$pnpm_version" >> "$GITHUB_OUTPUT" - name: Set up pnpm uses: pnpm/action-setup@fc06bc1257f339d1d5d8b3a19a8cae5388b55320 # v5.0.0 @@ -39,3 +28,20 @@ runs: node-version-file: .tool-versions cache: pnpm cache-dependency-path: ${{ inputs.cache-dependency-path }} + # pnpm switches to each package's packageManager version, so keep those + # in sync with the pin. + - name: Check packageManager matches .tool-versions + shell: bash + working-directory: ${{ github.workspace }} + env: + EXPECTED: pnpm@${{ steps.versions.outputs.pnpm }} + run: | + status=0 + for manifest in src/plugins/opencode/content/package.json src/plugins/pi/content/package.json; do + actual="$(node -p 'require(process.argv[1]).packageManager' "$PWD/$manifest")" + if [ "$actual" != "$EXPECTED" ]; then + echo "::error file=$manifest::packageManager is $actual; .tool-versions pins $EXPECTED" + status=1 + fi + done + exit "$status" From ded31664b03aeb3ef761bdbf9eb7acc96b2850c3 Mon Sep 17 00:00:00 2001 From: Abhijeet Prasad Date: Mon, 26 Oct 2026 12:08:14 -0400 Subject: [PATCH 5/6] ci: drop tool version consistency checks Co-Authored-By: Claude Opus 5.5 --- .github/actions/setup-node-pnpm/action.yml | 24 +--------------------- .github/actions/setup-rust/action.yml | 4 ---- AGENTS.md | 3 +-- 3 files changed, 2 insertions(+), 29 deletions(-) diff --git a/.github/actions/setup-node-pnpm/action.yml b/.github/actions/setup-node-pnpm/action.yml index 7cea3280..fd8b7dbf 100644 --- a/.github/actions/setup-node-pnpm/action.yml +++ b/.github/actions/setup-node-pnpm/action.yml @@ -12,12 +12,7 @@ runs: id: versions shell: bash run: | - pnpm_version="$(awk '$1 == "pnpm" { print $2 }' "$GITHUB_WORKSPACE/.tool-versions")" - if [ -z "$pnpm_version" ]; then - echo "::error file=.tool-versions::missing pnpm entry" - exit 1 - fi - echo "pnpm=$pnpm_version" >> "$GITHUB_OUTPUT" + echo "pnpm=$(awk '$1 == "pnpm" { print $2 }' "$GITHUB_WORKSPACE/.tool-versions")" >> "$GITHUB_OUTPUT" - name: Set up pnpm uses: pnpm/action-setup@fc06bc1257f339d1d5d8b3a19a8cae5388b55320 # v5.0.0 with: @@ -28,20 +23,3 @@ runs: node-version-file: .tool-versions cache: pnpm cache-dependency-path: ${{ inputs.cache-dependency-path }} - # pnpm switches to each package's packageManager version, so keep those - # in sync with the pin. - - name: Check packageManager matches .tool-versions - shell: bash - working-directory: ${{ github.workspace }} - env: - EXPECTED: pnpm@${{ steps.versions.outputs.pnpm }} - run: | - status=0 - for manifest in src/plugins/opencode/content/package.json src/plugins/pi/content/package.json; do - actual="$(node -p 'require(process.argv[1]).packageManager' "$PWD/$manifest")" - if [ "$actual" != "$EXPECTED" ]; then - echo "::error file=$manifest::packageManager is $actual; .tool-versions pins $EXPECTED" - status=1 - fi - done - exit "$status" diff --git a/.github/actions/setup-rust/action.yml b/.github/actions/setup-rust/action.yml index fb120ba8..324fbb21 100644 --- a/.github/actions/setup-rust/action.yml +++ b/.github/actions/setup-rust/action.yml @@ -14,10 +14,6 @@ runs: COMPONENTS: ${{ inputs.components }} run: | rust_version="$(awk '$1 == "rust" { print $2 }' "$GITHUB_WORKSPACE/.tool-versions")" - if [ -z "$rust_version" ]; then - echo "::error file=.tool-versions::missing rust entry" - exit 1 - fi rustup toolchain install "$rust_version" --profile minimal rustup default "$rust_version" if [ -n "$COMPONENTS" ]; then diff --git a/AGENTS.md b/AGENTS.md index f7b51b9a..74f22f17 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -141,8 +141,7 @@ The root `.tool-versions` pins Node, pnpm, Rust, and vite-plus for local development; CI reads its Node, pnpm, and Rust pins without mise through `actions/setup-node`, `.github/actions/setup-node-pnpm`, and `.github/actions/setup-rust`. Bump versions there rather than in individual -workflows. CI requires each pnpm package's `packageManager` to match it. -`mise.toml` holds local-only mise hooks. +workflows. `mise.toml` holds local-only mise hooks. CI builds and validates the plugin packages on Linux. It runs the JavaScript daemon client and the Pi and OpenCode unit tests, and builds, tests, and lints From 5c8d720e6d7142da4dd9f5f534def2e7048c1664 Mon Sep 17 00:00:00 2001 From: Abhijeet Prasad Date: Mon, 26 Oct 2026 12:19:30 -0400 Subject: [PATCH 6/6] test: tolerate Claude Code retrying the failed inference request Claude Code 2.1.310 retries after the mock's deterministic 400, so the fourth request hit the mock's panic arm instead of the expected error. Co-Authored-By: Claude Opus 5.5 --- bt-daemon/tests/agent_integration.rs | 9 +++------ 1 file changed, 3 insertions(+), 6 deletions(-) diff --git a/bt-daemon/tests/agent_integration.rs b/bt-daemon/tests/agent_integration.rs index 7e3f460f..20de9b3b 100644 --- a/bt-daemon/tests/agent_integration.rs +++ b/bt-daemon/tests/agent_integration.rs @@ -201,7 +201,8 @@ async fn claude_session_emits_traces() { ); MockReply::response(AnthropicTurn::text("CLAUDE_MOCK_OK")) } - 2 => MockReply::http_error( + // Claude Code may retry the failed request, so keep failing it. + _ => MockReply::http_error( StatusCode::BAD_REQUEST, json!({ "type": "error", @@ -211,10 +212,6 @@ async fn claude_session_emits_traces() { } }), ), - index => panic!( - "unexpected Claude inference request {index}: {}", - request.body - ), }); let inference_server = TestServer::start(inference.router()).await; let world = AgentTestWorld::start().await; @@ -241,7 +238,7 @@ async fn claude_session_emits_traces() { .await; failed.assert_failure(); failed.assert_contains("deterministic Claude inference failure"); - assert_eq!(inference.requests().len(), 3); + assert!(inference.requests().len() >= 3); } let rows = world.wait_for_trace_delivery().await;