From 7f93b09a94536146abb0431b7a9c9d29ba05ccda Mon Sep 17 00:00:00 2001 From: Bulat Yapparov Date: Mon, 19 Oct 2026 12:54:35 +0100 Subject: [PATCH] chore(release): prepare @aictrl/cli 0.5.2 Bump packages/cli and its bun.lock workspace entry to 0.5.2, cut the CHANGELOG Unreleased section into 0.5.2 (2026-10-19) and replace the RELEASING.md 0.5.1 candidate notes with the 0.5.2 candidate and evidence. CHANGELOG: every entry now names its PR and issue as in 0.5.0, Security moves above Internal and includes #228's SDK entry, Internal is one section holding #229's tool-description trim and #222's test fixes, and three entries are corrected (subagent totals are a lower bound under nested delegation, session_start.permissions stays [] with --attach, and a full binary that set AICTRL_HEADLESS=true does see a prompt change). Co-Authored-By: Claude Opus 5.5 Claude-Session: https://claude.ai/code/session_015nwvfUHdM3bgMgD9Zpz7Mj --- CHANGELOG.md | 28 ++++++++---------- RELEASING.md | 62 +++++++++++++++++++++++++++++++-------- bun.lock | 2 +- packages/cli/package.json | 2 +- 4 files changed, 65 insertions(+), 29 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index c143293..25502c1 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,32 +1,30 @@ # Changelog -## Unreleased +## 0.5.2 (2026-10-19) ### Features -- **`user_message` NDJSON event** — `aictrl run --format json` now emits each primary-session user message once, with its `messageID`, `agent`, `model`, full `parts` as stored (including `synthetic` flags and `-f` file parts) and a `sequenceNum` on the per-session counter shared with `text`, `reasoning` and `tool_use`. It covers the prompt, the `--command` expansion and the user messages the loop creates (the compaction request, the compaction "continue" message and the subtask summary request), and always comes before the assistant events it triggers. Parts are not truncated, so consumers must accept large lines. The StructuredOutput corrective reminder is not stored, so it has no `user_message`. Subagent user messages are not emitted. The event is additive; `schemaVersion` stays `"1"`. (part of #223) +- **`user_message` NDJSON event** — `aictrl run --format json` now emits each primary-session user message once, with its `messageID`, `agent`, `model`, full `parts` as stored (including `synthetic` flags and `-f` file parts) and a `sequenceNum` on the per-session counter shared with `text`, `reasoning` and `tool_use`. It covers the prompt, the `--command` expansion and the user messages the loop creates (the compaction request, the compaction "continue" message and the subtask summary request), and always comes before the assistant events it triggers. Parts are not truncated, so consumers must accept large lines. The StructuredOutput corrective reminder is not stored, so it has no `user_message`. Subagent user messages are not emitted. The event is additive; `schemaVersion` stays `"1"`. It takes numbers from the shared `sequenceNum` counter, so the `text`, `reasoning` and `tool_use` numbers can have gaps; they stay monotonic. (#224, part of #223) ### Fixes -- **Messages written in the same millisecond keep their order** — Reading a session's messages now breaks `time_created` ties by message ID. Before, messages written in the same millisecond came back oldest first. A compaction writes its request, its summary and the continue message in quick succession, so the prompt loop could miss the finished summary and compact again, sending an extra model request. (#200) -- **Retry waits are bounded for every status** — No single wait between retries exceeds 5 minutes. A retryable error of any status whose `Retry-After` / `retry-after-ms` asks for longer is no longer retried: a 429 still fails as `usage_limit` (#174), and any other status (for example a 503 or 529 asking for `retry-after: 18000`, which used to sleep about 5 hours per attempt) fails at once with the provider error, `session_error.reason` from its status (`provider` for a 5xx) and exit 1, with no `retry` event. Exponential backoff without a retry hint now stops at 30 s even when the response has headers, as every AI SDK HTTP error does, so a hintless 429 or 5xx retries for about 3.5 minutes in all instead of about 34. A zero or negative `Retry-After` now counts as no hint and backs off, instead of retrying with no wait. With 10 retries the waits total at most 50 minutes. (closes #175) -- **`session_start.permissions` is complete** — it now lists the full ruleset tool calls are evaluated against (agent defaults, config `permission` rules, agent rules and session rules) in evaluation order; it was empty for the default agent and omitted session rules (#212). -- **Subagent token usage is reported** — `aictrl run --format json` emits a new `subagent_message_complete` event for each finished assistant turn of a `task` subagent, with the same usage fields as `message_complete` plus `subagentSessionID` and `parentSessionID`. Summing `message_complete` and `subagent_message_complete` gives the run's total; before, a run that delegated reported only the parent's turns. `message_complete` is unchanged and still covers the primary session only (#213). -- **Lite build sends the same system prompt as the full build** — `aictrl run` from the lite binary (`script/build.ts --lite`, entry `src/headless.ts`) now sends the environment block (working directory, git status, platform, date) and loads `AGENTS.md` from `AICTRL_CONFIG_DIR` and the global config directory, as the published full binary always did. The lite-only short prompt is gone. The no-op `AICTRL_HEADLESS` build define and the `AICTRL_HEADLESS` environment variable are removed; setting the variable has no effect. The full binary's prompt is unchanged. (closes #152) - -### Internal - -- **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 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. +- **Messages written in the same millisecond keep their order** — Reading a session's messages now breaks `time_created` ties by message ID. Before, messages written in the same millisecond came back in insertion order inside the newest-first read. A compaction writes its request, its summary and the continue message in quick succession, so the prompt loop could miss the finished summary and compact again, sending an extra model request. (#222, refs #200) +- **Retry waits are bounded for every status** — No single wait between retries exceeds 5 minutes. A retryable error of any status whose `Retry-After` / `retry-after-ms` asks for longer is no longer retried: a 429 still fails as `usage_limit` (#174), and any other status (for example a 503 or 529 asking for `retry-after: 18000`, which used to sleep about 5 hours per attempt) fails at once with the provider error, `session_error.reason` from its status (`provider` for a 5xx) and exit 1, with no `retry` event. Exponential backoff without a retry hint now stops at 30 s even when the response has headers, as every AI SDK HTTP error does, so a hintless 429 or 5xx retries for about 3.5 minutes in all instead of about 34. A zero or negative `Retry-After` now counts as no hint and backs off, instead of retrying with no wait. With 10 retries the waits total at most 50 minutes. (#217, closes #175) +- **`session_start.permissions` is complete** — it now lists the full ruleset tool calls are evaluated against (agent defaults, config `permission` rules, agent rules and session rules) in evaluation order; it was empty for the default agent and omitted session rules. With `--attach` it is still `[]`, because the attached server owns agents and config. (#218, closes #212) +- **Subagent token usage is reported** — `aictrl run --format json` emits a new `subagent_message_complete` event for each finished assistant turn of a `task` subagent, with the same usage fields as `message_complete` plus `subagentSessionID` and `parentSessionID`. Summing `message_complete` and `subagent_message_complete` gives the run's total when subagents do not delegate further (turns of nested `task` calls are not reported, so the sum is then a lower bound); before, a run that delegated reported only the parent's turns. `message_complete` is unchanged and still covers the primary session only. (#221, closes #213) +- **Lite build sends the same system prompt as the full build** — `aictrl run` from the lite binary (`script/build.ts --lite`, entry `src/headless.ts`) now sends the environment block (working directory, git status, platform, date) and loads `AGENTS.md` from `AICTRL_CONFIG_DIR` and the global config directory, as the published full binary always did. The lite-only short prompt is gone. The no-op `AICTRL_HEADLESS` build define and the `AICTRL_HEADLESS` environment variable are removed, with the README note that suggested setting it in CI; setting the variable now has no effect. The full binary's prompt is unchanged, except for runs that set `AICTRL_HEADLESS=true`: on 0.5.1 that also gave the full binary the one-line environment and skipped the `AICTRL_CONFIG_DIR` and global `AGENTS.md` files, and those runs now get the environment block and those files. (#219, closes #152) ### 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) -- **`@aictrl/sdk` `buildClientParams` prototype substitution** — the SDK generator `@hey-api/openapi-ts` moves from `0.90.10` to `0.97.3`, the first release patched for GHSA-hhx9-57xq-r5rw (CVE-2026-48819). The flaw is in the `buildClientParams` template copied into the generated client, so the v2 client is regenerated and the same fix is applied to the legacy client, which the build script does not regenerate: the `body`, `headers`, `path` and `query` slots are now null-prototype objects, so an unknown `$query___proto__` (or `$body_`, `$headers_`, `$path_`) key can no longer replace a slot's prototype. Both clients also keep an empty array body instead of dropping it, as the 0.97.3 template does. No generated endpoint calls `buildClientParams`; it is reachable only by code that imports it from the SDK. The regenerated v2 client also takes the upstream `@hey-api/client-fetch` changes from 0.91 to 0.97: request-building, interceptor and validator errors now go through the error interceptors and `throwOnError` like response errors, and `request` / `response` on an error result may be `undefined`. The endpoint and data types (`types.gen.ts`) are unchanged in both clients. The v2 client's runtime and error types change as described above. In the legacy client that the CLI uses for `aictrl run --attach` and plugins, only the hand-patched `buildClientParams` helper changes. (Dependabot #11) +- **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. (#220, closes #61) +- **`@aictrl/sdk` `buildClientParams` prototype substitution** — the SDK generator `@hey-api/openapi-ts` moves from `0.90.10` to `0.97.3`, the first release patched for GHSA-hhx9-57xq-r5rw (CVE-2026-48819). The flaw is in the `buildClientParams` template copied into the generated client, so the v2 client is regenerated and the same fix is applied to the legacy client, which the build script does not regenerate: the `body`, `headers`, `path` and `query` slots are now null-prototype objects, so an unknown `$query___proto__` (or `$body_`, `$headers_`, `$path_`) key can no longer replace a slot's prototype. Both clients also keep an empty array body instead of dropping it, as the 0.97.3 template does. No generated endpoint calls `buildClientParams`; it is reachable only by code that imports it from the SDK. The regenerated v2 client also takes the upstream `@hey-api/client-fetch` changes from 0.91 to 0.97: request-building, interceptor and validator errors now go through the error interceptors and `throwOnError` like response errors, and `request` / `response` on an error result may be `undefined`. The endpoint and data types (`types.gen.ts`) are unchanged in both clients. The v2 client's runtime and error types change as described above. In the legacy client that the CLI uses for `aictrl run --attach` and plugins, only the hand-patched `buildClientParams` helper changes. (#228, Dependabot #11) ### Internal -- **Shorter `bash` and `task` tool descriptions** — The two tool descriptions now state only what the tool does and how it behaves: for `bash`, its scope (terminal operations, with a one-line map from shell file commands to the dedicated tools), the default directory and `workdir`, the timeout, output truncation to a file, the `description` summary and parallel calls; for `task`, what a subagent is, the agent list, what comes back and resuming with `task_id`. The inherited directory-verification steps, quoting and `cd` examples, repeated guidance, and the Task tool's usage notes and examples are removed, and the `bash` `description` and `task` `task_id` parameter texts are shortened. The two tool definitions shrink from about 9.7k to 3.4k characters, so the first model request is about 1.5k tokens (22%) smaller. Tools, parameters and behaviour are unchanged. (refs #214) +- **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 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. (#225, 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. (#226) +- **Flaky tests fixed; `bun test --rerun-each` works** — The URL-caching instruction test serves on an ephemeral port, the unhandled-rejection fixture writes `e.message` instead of relying on Bun's stack header, the legacy-session fixture closes its SQLite handle, and the test preload keeps its data directory for the whole run, so `--rerun-each` passes no longer fail with `SQLITE_CANTOPEN`. No timeouts were changed and no tests were skipped. (#222, refs #200) +- **Smaller first request: shorter `bash` and `task` tool descriptions** — The two tool descriptions now state only what the tool does and how it behaves: for `bash`, its scope (terminal operations, with a one-line map from shell file commands to the dedicated tools), the default directory and `workdir`, the timeout, output truncation to a file, the `description` summary and parallel calls; for `task`, what a subagent is, the agent list, what comes back and resuming with `task_id`. The inherited directory-verification steps, quoting and `cd` examples, repeated guidance, and the Task tool's usage notes and examples are removed, and the `bash` `description` and `task` `task_id` parameter texts are shortened. The two tool definitions shrink from about 9.7k to 3.4k characters, so the first model request is about 1.5k tokens (21.7%) smaller, measured on `zai-coding-plan/glm-5.3`. Tools and parameters are unchanged; an A/B run of the 8-task benchmark suite on that model passed 24/24 on both versions with no extra steps and used 20.8% fewer tokens in total. (#229, refs #214) ## 0.5.1 (2026-10-18) diff --git a/RELEASING.md b/RELEASING.md index 5b63f23..59dbdc3 100644 --- a/RELEASING.md +++ b/RELEASING.md @@ -55,19 +55,57 @@ compare time to first response and RSS: run a one-turn `aictrl run` against a stub model in a fresh, CPU- and memory-limited container, several times each. Moving from Bun 1.3.10 to 1.4.3 alone changed them by about 85 ms and 85 MB. -## 0.5.1 candidate +## 0.5.2 candidate -- **Why a patch bump (0.5.1):** model-only support change. It restores the - built-in `google` provider (Google AI Studio, `@ai-sdk/google`) that 0.5.0 - left out of the static catalog, so `google/` works with only - `GOOGLE_GENERATIVE_AI_API_KEY`, `GEMINI_API_KEY` or `GOOGLE_API_KEY` set. -- The catalog gains one provider and 22 models (18 → 19 providers, 1354 → 1376 - models); every other provider's entry is byte-identical to 0.5.0. -- No CLI flags, config schema or NDJSON events change. One config behaviour - changes: a `provider.google` defined in `aictrl.json` on 0.5.0 now extends - the built-in, so its models without `npm` use `@ai-sdk/google` again (as up - to 0.4.8) and the built-in models are listed too. The CHANGELOG gives the - `npm` + `whitelist` opt-out. +- **Why a patch bump (0.5.2):** only additive NDJSON events, fixes and + internal changes. No CLI flag, command, config key or NDJSON event field is + removed, and `schemaVersion` stays `"1"`. Range `v0.5.1..d4d9696dbc` (11 PRs): + #222, #220, #217, #218, #224, #225, #221, #226, #219, #228, #229. +- **NDJSON additions:** `user_message` (#224) and `subagent_message_complete` + (#221). `session_start.permissions` keeps its shape and now carries the full + evaluated ruleset (#218). `user_message` takes numbers from the shared + `sequenceNum` counter, so `text`, `reasoning` and `tool_use` numbers can have + gaps; they stay monotonic. +- **Behaviour changes worth knowing:** + - a retryable error asking for more than 5 minutes fails at once, for any + status (#217); + - a second prompt for a busy session fails with `Session is busy` + instead of queuing; only parallel `task` calls resuming one `task_id` + reach it (#225); + - `AICTRL_HEADLESS` is ignored (#219). A full-binary run that set it now + gets the full environment block and the `AICTRL_CONFIG_DIR` / global + `AGENTS.md` files. The production executor does not set it. +- **Prompt:** #229 trims the `bash` and `task` tool descriptions (about + 9.7k → 3.4k characters, first request −21.7% tokens on glm-5.3). Tool names + and parameters are unchanged; its A/B passed 24/24 with no extra steps. +- **Database:** drizzle 1.0.0-beta.20 (#220) adds `name` and `applied_at` to + `__drizzle_migrations` on first start; a database upgraded by 0.5.2 still + opens with 0.5.1. +- **SDK:** #228 regenerates the v2 `@aictrl/sdk` client with + `@hey-api/openapi-ts` 0.97.3 (GHSA-hhx9-57xq-r5rw) and hand-patches the + legacy client's `buildClientParams`. The CLI uses only the legacy client + (`--attach`, plugins), where only that helper changes. `@aictrl/sdk` is not + bumped by this release. +- **Bun:** the first release built under the `packageManager` pin (#226). The + binary must embed `Bun v1.4.3`. +- Evidence, on the release commit with Bun 1.4.3: + - step-2 regressions: 88 pass, 0 fail (8 files); + - `bun test --timeout 30000` (packages/cli): 1760 pass, 6 skip, 0 fail + (1766 tests, 147 files); + - `bun turbo typecheck --force`: 6/6; + - `test/security/sdk-build-client-params.test.ts`: 12 pass, 0 fail; + - `AICTRL_VERSION=0.5.2 AICTRL_BUILD_SINGLE=true bun turbo build --force` + (CI's build): 3/3 tasks; `aictrl --version` prints `0.5.2` and the + binary embeds `Bun v1.4.3`; + - the five binary-capable `aictrl run` suites with `AICTRL_TEST_BINARY` set + to that binary: 21 pass, 0 fail; + - NDJSON schema tests (`run-schema-v1`, `events-command`, `run-user-message`, + `run-event-race`) and `bun-version-pin`: 27 pass, 0 fail; + - one-turn `aictrl run --format json` smoke runs of the binary against + a stub OpenAI-compatible endpoint: exit 0, one model request, every line + is JSON, `schemaVersion` `"1"`, `session_start.permissions` contains the + config rule, `user_message` precedes `text`, `message_complete.status` + `completed`, `invocation_complete.status` `completed`. Publication and executor promotion are separate gates. Preparing this candidate PR does not publish npm packages or deploy an executor image. diff --git a/bun.lock b/bun.lock index 3b60972..cf77e1d 100644 --- a/bun.lock +++ b/bun.lock @@ -24,7 +24,7 @@ }, "packages/cli": { "name": "@aictrl/cli", - "version": "0.5.1", + "version": "0.5.2", "bin": { "aictrl": "./bin/aictrl", }, diff --git a/packages/cli/package.json b/packages/cli/package.json index def87b3..2f77ac6 100644 --- a/packages/cli/package.json +++ b/packages/cli/package.json @@ -1,6 +1,6 @@ { "$schema": "https://json.schemastore.org/package.json", - "version": "0.5.1", + "version": "0.5.2", "name": "@aictrl/cli", "description": "Headless execution engine for AI agent skills", "type": "module",