Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
25 changes: 25 additions & 0 deletions .github/actions/setup-node-pnpm/action.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
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: |
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:
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 }}
22 changes: 22 additions & 0 deletions .github/actions/setup-rust/action.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
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")"
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
63 changes: 15 additions & 48 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -67,15 +61,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
Expand Down Expand Up @@ -120,7 +108,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
Expand All @@ -135,15 +123,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.
Expand Down Expand Up @@ -191,15 +173,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
Expand All @@ -218,9 +194,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
Expand Down Expand Up @@ -291,10 +265,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
Expand Down Expand Up @@ -338,15 +311,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
Expand Down
6 changes: 3 additions & 3 deletions .github/workflows/release-opencode.yml
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
# GENERATED by sdk-actions `bin/workflow` — update with: bin/workflow update <this file>
# 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

Expand Down Expand Up @@ -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'
Expand Down Expand Up @@ -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 }}
Expand Down
6 changes: 3 additions & 3 deletions .github/workflows/release-pi.yml
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
# GENERATED by sdk-actions `bin/workflow` — update with: bin/workflow update <this file>
# 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

Expand Down Expand Up @@ -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'
Expand Down Expand Up @@ -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 }}
Expand Down
2 changes: 1 addition & 1 deletion .github/workflows/release.yml
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down
2 changes: 1 addition & 1 deletion .github/workflows/smoke-codex.yml
Original file line number Diff line number Diff line change
Expand Up @@ -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: |
Expand Down
4 changes: 4 additions & 0 deletions .tool-versions
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
node 24.22.1
pnpm 11.21.0
rust 1.99.1
npm:vite-plus 0.2.9
10 changes: 10 additions & 0 deletions AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -133,6 +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, 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. `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
the Rust daemon, on Linux, macOS, and Windows. Pushes to `main` and manual runs
Expand Down
8 changes: 7 additions & 1 deletion Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -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/<agent>
# make build-codex build just one
# make test build then validate all
Expand 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)

Expand Down
9 changes: 3 additions & 6 deletions bt-daemon/tests/agent_integration.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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",
Expand All @@ -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;
Expand All @@ -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;
Expand Down
5 changes: 5 additions & 0 deletions mise.toml
Original file line number Diff line number Diff line change
@@ -0,0 +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.

[hooks]
postinstall = "make install-deps"
6 changes: 3 additions & 3 deletions src/plugins/cursor/content/package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

3 changes: 0 additions & 3 deletions src/plugins/opencode/content/.tool-versions

This file was deleted.

3 changes: 0 additions & 3 deletions src/plugins/pi/content/.tool-versions

This file was deleted.

Loading
Loading