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
47 changes: 47 additions & 0 deletions .github/workflows/publish.yml
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,8 @@ jobs:
permissions:
contents: read
id-token: write
outputs:
platform_packages: ${{ steps.manifest.outputs.platform_packages }}
steps:
- uses: actions/checkout@d23441a48e516b6c34aea4fa41551a30e30af803 # v6
- uses: oven-sh/setup-bun@0c5077e51419868618aeaa5fe8019c62421857d6 # v2.2.0
Expand Down Expand Up @@ -111,8 +113,12 @@ jobs:
done

- name: Strip bundled deps and refresh platform binary optionalDependencies
id: manifest
working-directory: packages/cli
run: |
# The package.json `files` allowlist ships LICENSE, which lives at
# the repository root.
cp ../../LICENSE LICENSE
# All bundleable dependencies are compiled into the native binary, so
# the published package only needs:
# - platform binary packages (regenerated below from dist/ to match
Expand Down Expand Up @@ -163,6 +169,10 @@ jobs:
pkg.optionalDependencies = { ...preserved, ...platformDeps };
fs.writeFileSync('package.json', JSON.stringify(pkg, null, 2) + '\n');
console.log('optionalDependencies:', pkg.optionalDependencies);
// The smoke job waits until every one of them is visible on npm.
if (process.env.GITHUB_OUTPUT) {
fs.appendFileSync(process.env.GITHUB_OUTPUT, 'platform_packages=' + Object.keys(platformDeps).join(' ') + '\n');
}
"

- name: Publish @aictrl/cli
Expand All @@ -179,6 +189,43 @@ jobs:
with:
node-version: 22

- name: Wait for every platform package to be visible on npm
# npm can serve a new version of one package before another. When
# @aictrl/cli resolves before its platform package, npm skips the
# optional binary and the install below has no binary to run. The
# 0.5.1 install check failed on that lag. Ten attempts allow 8.5
# minutes of backoff, bounded by the step timeout.
timeout-minutes: 15
env:
RELEASE_TAG: ${{ github.event.release.tag_name }}
PLATFORM_PACKAGES: ${{ needs.publish.outputs.platform_packages }}
run: |
set -euo pipefail
AICTRL_VERSION="${RELEASE_TAG#v}"
read -r -a packages <<< "${PLATFORM_PACKAGES:-}"
if [ ${#packages[@]} -eq 0 ]; then
echo "::error::The publish job reported no platform packages to check"
exit 1
fi
for attempt in {1..10}; do
missing=()
for name in "${packages[@]}"; do
visible=$(npm view "${name}@${AICTRL_VERSION}" version --prefer-online 2>/dev/null || true)
if [ "$visible" != "$AICTRL_VERSION" ]; then missing+=("$name"); fi
done
if [ ${#missing[@]} -eq 0 ]; then
echo "::notice::All ${#packages[@]} platform packages are visible at ${AICTRL_VERSION} on attempt ${attempt}"
exit 0
fi
if [ "$attempt" -eq 10 ]; then break; fi
delay=60
if [ "$attempt" -eq 1 ]; then delay=30; fi
echo "::warning::Not yet visible at ${AICTRL_VERSION}: ${missing[*]}. Retrying in ${delay}s..."
sleep "$delay"
done
echo "::error::Platform packages not visible at ${AICTRL_VERSION} after 10 attempts: ${missing[*]}"
exit 1

- name: Smoke test - verify @aictrl/cli installs cleanly via npm
# Catches the v0.3.3-class bug where the published manifest carries
# Bun-only protocols (workspace:*, catalog:) that plain `npm install`
Expand Down
7 changes: 7 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,13 @@
- **A session runs one prompt at a time** — The prompt loop no longer queues a second prompt for a session that is already running one. The second prompt now fails with `Session <id> is busy: it already has a running prompt` before it writes a user message. A headless run has one user turn, so the only reachable case is two parallel `task` calls that resume the same `task_id`: the second call now returns that error to the model instead of waiting. The unused `!` shell command path (`SessionPrompt.shell`) and `Session.initialize` are removed. (Part of #223)
- **One Bun version for CI, releases and local builds** — The root `package.json` pins `packageManager` to `bun@1.4.3`, and the CI and publish workflows read it with `bun-version-file: package.json` instead of installing `latest`. 0.5.1 shipped Bun 1.4.3 while CI and local builds could use other versions; a local 1.3.10 build of the same commit was about 85 ms slower to first response and used about 85 MB more RSS. The pre-push hook and `@aictrl/script` (used by the build) check the local Bun against `packageManager`, so they now require Bun 1.4.3 or a later 1.x. The `bun-node` container image no longer has its own default `BUN_VERSION` and takes the `packageManager` version from its build script.

### Packaging

- **One platform package per OS, CPU and libc; the AVX2 x64 packages are no longer published** — This changes what is published. `@aictrl/cli-linux-x64`, `@aictrl/cli-linux-x64-musl`, `@aictrl/cli-darwin-x64` and `@aictrl/cli-windows-x64` get no new versions; x64 hosts use `@aictrl/cli-linux-x64-baseline`, `@aictrl/cli-linux-x64-baseline-musl`, `@aictrl/cli-darwin-x64-baseline` and `@aictrl/cli-windows-x64-baseline`. arm64 packages keep their names. Bun 1.4.3 ships the same binary for its baseline and AVX2 targets, and the 0.5.1 AVX2 and `-baseline` packages hold byte-identical binaries on all three x64 OSes, so no CPU loses a faster build. The linux packages now declare `libc` (`glibc` or `musl`), so npm installs one of them instead of every linux package for the CPU: `npm install -g @aictrl/cli` on linux-x64 drops from four platform packages and about 450 MB to one package of about 115 MB (glibc) or 110 MB (musl). A package manager that ignores `libc` still installs both libc packages, and the wrapper picks the host's. If you install a dropped package directly, switch to its `-baseline` name; its published versions up to 0.5.2 are unchanged.
- **`@aictrl/cli` publishes only the wrapper** — A `files` allowlist limits the package to `bin/aictrl`, `README.md`, `LICENSE` and `package.json`. `src/`, `test/`, migrations and build scripts are no longer published (369 files and 6.1 MB unpacked in 0.5.1, now 4 files and 18 kB). The platform packages ship `LICENSE` too.
- **The npm wrapper no longer probes the CPU or runs `ldd`** — `bin/aictrl` drops AVX2 detection. It reads the libc from `/usr/bin/ldd` and falls back to Node's diagnostic report (as npm does) instead of running `ldd --version`, which saves about 2.5 ms per start on glibc. When no binary of its version is installed, it no longer tries `bun run src/index.ts`, which failed with a misleading `Cannot find package` error; it exits 1 and names the platform package it expected.
- **The release waits for every platform package before the install check** — The publish workflow's smoke job checks that each platform package it published is visible on npm at the release version, retrying for up to 8.5 minutes, before it installs `@aictrl/cli`. 0.5.1's install check failed while a platform package was not yet visible.

### Security

- **drizzle-orm 1.0.0-beta.20** — `drizzle-orm` and `drizzle-kit` move from `1.0.0-beta.12-a5629fb` to `1.0.0-beta.20`, the first release patched for GHSA-gpj5-g38j-94v9 (SQL injection through improperly escaped SQL identifiers; the CLI never passed untrusted identifiers to drizzle). Existing databases open as before: on first start drizzle adds `name` and `applied_at` columns to `__drizzle_migrations` and fills in the names of the migrations already applied, without running them again. The upgraded database still opens with 0.5.1. (closes #61)
Expand Down
6 changes: 3 additions & 3 deletions CONTRIBUTING.md
Original file line number Diff line number Diff line change
Expand Up @@ -69,16 +69,16 @@ bun dev .
To compile a standalone executable:

```bash
./packages/aictrl/script/build.ts --single
./packages/cli/script/build.ts --single
```

Then run it with:

```bash
./packages/aictrl/dist/aictrl-<platform>/bin/aictrl
./packages/cli/dist/@aictrl/cli-<platform>/bin/aictrl
```

Replace `<platform>` with your platform (e.g., `darwin-arm64`, `linux-x64`).
Replace `<platform>` with your platform (e.g., `darwin-arm64`, `linux-x64-baseline`).

- Core pieces:
- `packages/aictrl`: Aictrl core business logic & server.
Expand Down
24 changes: 24 additions & 0 deletions RELEASING.md
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,30 @@
`sandbox` before `main`; verify a sandbox review persists MCP findings and
a stalled stream reports a timeout rather than waiting for the job limit.

## Platform packages

`packages/cli/script/build.ts` builds one binary per OS, CPU and libc, and the
publish workflow publishes each as a platform package. It then lists them in
the wrapper's `optionalDependencies`:

| Package | `os` | `cpu` | `libc` |
| --- | --- | --- | --- |
| `@aictrl/cli-linux-x64-baseline` | linux | x64 | glibc |
| `@aictrl/cli-linux-x64-baseline-musl` | linux | x64 | musl |
| `@aictrl/cli-linux-arm64` | linux | arm64 | glibc |
| `@aictrl/cli-linux-arm64-musl` | linux | arm64 | musl |
| `@aictrl/cli-darwin-x64-baseline` | darwin | x64 | |
| `@aictrl/cli-darwin-arm64` | darwin | arm64 | |
| `@aictrl/cli-windows-x64-baseline` | win32 | x64 | |

npm installs only the package that matches the host. Every x64 package is the
baseline (no AVX2) build: Bun ships the same binary for its baseline and AVX2
targets. The AVX2 names (`@aictrl/cli-linux-x64`, `-linux-x64-musl`,
`-darwin-x64`, `-windows-x64`) were last published with 0.5.2. Before adding
or renaming a platform package, update `bin/aictrl`'s `platformPackageNames`
and its tests. The smoke job waits until every package in this table is visible
on npm before it installs `@aictrl/cli`.

## Bun version

The compiled binary embeds the Bun runtime that built it, so the Bun version is
Expand Down
Loading
Loading