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
28 changes: 13 additions & 15 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -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 <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.
- **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 <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. (#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)

Expand Down
Loading
Loading