@ai-zen/cli 1.0.0-alpha.9 → 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +1334 -275
- package/LICENSE +21 -0
- package/README.md +136 -31
- package/README.zh.md +136 -31
- package/dist/agent-creator.d.ts +11 -0
- package/dist/agent-creator.d.ts.map +1 -1
- package/dist/agent-creator.js +50 -20
- package/dist/agent-creator.js.map +1 -1
- package/dist/agent-switch.d.ts +61 -0
- package/dist/agent-switch.d.ts.map +1 -0
- package/dist/agent-switch.js +83 -0
- package/dist/agent-switch.js.map +1 -0
- package/dist/agents-store.d.ts +6 -1
- package/dist/agents-store.d.ts.map +1 -1
- package/dist/agents-store.js +46 -4
- package/dist/agents-store.js.map +1 -1
- package/dist/auto-migrate-confirm.d.ts +43 -0
- package/dist/auto-migrate-confirm.d.ts.map +1 -0
- package/dist/auto-migrate-confirm.js +47 -0
- package/dist/auto-migrate-confirm.js.map +1 -0
- package/dist/clipboard.d.ts +68 -0
- package/dist/clipboard.d.ts.map +1 -0
- package/dist/clipboard.js +177 -0
- package/dist/clipboard.js.map +1 -0
- package/dist/config-editor.d.ts +42 -5
- package/dist/config-editor.d.ts.map +1 -1
- package/dist/config-editor.js +80 -27
- package/dist/config-editor.js.map +1 -1
- package/dist/config.d.ts +76 -17
- package/dist/config.d.ts.map +1 -1
- package/dist/config.js +143 -39
- package/dist/config.js.map +1 -1
- package/dist/conversation-commands/registry.d.ts +127 -4
- package/dist/conversation-commands/registry.d.ts.map +1 -1
- package/dist/conversation-commands/registry.js +93 -14
- package/dist/conversation-commands/registry.js.map +1 -1
- package/dist/conversation-persist-plugin.d.ts +0 -1
- package/dist/conversation-persist-plugin.d.ts.map +1 -1
- package/dist/conversation-persist-plugin.js +7 -16
- package/dist/conversation-persist-plugin.js.map +1 -1
- package/dist/conversation-repository.d.ts +79 -18
- package/dist/conversation-repository.d.ts.map +1 -1
- package/dist/conversation-repository.js +96 -30
- package/dist/conversation-repository.js.map +1 -1
- package/dist/cwd-tracker-plugin.d.ts.map +1 -1
- package/dist/cwd-tracker-plugin.js +22 -7
- package/dist/cwd-tracker-plugin.js.map +1 -1
- package/dist/format-time.d.ts +17 -6
- package/dist/format-time.d.ts.map +1 -1
- package/dist/format-time.js +32 -14
- package/dist/format-time.js.map +1 -1
- package/dist/handoff-service.d.ts +19 -0
- package/dist/handoff-service.d.ts.map +1 -0
- package/dist/handoff-service.js +85 -0
- package/dist/handoff-service.js.map +1 -0
- package/dist/hook.d.ts.map +1 -1
- package/dist/hook.js +69 -15
- package/dist/hook.js.map +1 -1
- package/dist/i18n/en-wizard.d.ts +8 -0
- package/dist/i18n/en-wizard.d.ts.map +1 -0
- package/dist/i18n/en-wizard.js +303 -0
- package/dist/i18n/en-wizard.js.map +1 -0
- package/dist/i18n/en.d.ts +7 -0
- package/dist/i18n/en.d.ts.map +1 -0
- package/dist/i18n/en.js +245 -0
- package/dist/i18n/en.js.map +1 -0
- package/dist/i18n/index.d.ts +57 -0
- package/dist/i18n/index.d.ts.map +1 -0
- package/dist/i18n/index.js +109 -0
- package/dist/i18n/index.js.map +1 -0
- package/dist/i18n/preferences.d.ts +37 -0
- package/dist/i18n/preferences.d.ts.map +1 -0
- package/dist/i18n/preferences.js +73 -0
- package/dist/i18n/preferences.js.map +1 -0
- package/dist/i18n/zh-wizard.d.ts +11 -0
- package/dist/i18n/zh-wizard.d.ts.map +1 -0
- package/dist/i18n/zh-wizard.js +306 -0
- package/dist/i18n/zh-wizard.js.map +1 -0
- package/dist/i18n/zh.d.ts +10 -0
- package/dist/i18n/zh.d.ts.map +1 -0
- package/dist/i18n/zh.js +249 -0
- package/dist/i18n/zh.js.map +1 -0
- package/dist/index.js +52 -23
- package/dist/index.js.map +1 -1
- package/dist/mcp-command.d.ts +32 -0
- package/dist/mcp-command.d.ts.map +1 -0
- package/dist/mcp-command.js +37 -0
- package/dist/mcp-command.js.map +1 -0
- package/dist/mode.d.ts +31 -4
- package/dist/mode.d.ts.map +1 -1
- package/dist/mode.js +42 -7
- package/dist/mode.js.map +1 -1
- package/dist/model-switch.d.ts +58 -0
- package/dist/model-switch.d.ts.map +1 -0
- package/dist/model-switch.js +62 -0
- package/dist/model-switch.js.map +1 -0
- package/dist/reasoning-effort.d.ts +96 -0
- package/dist/reasoning-effort.d.ts.map +1 -0
- package/dist/reasoning-effort.js +164 -0
- package/dist/reasoning-effort.js.map +1 -0
- package/dist/reasoning-presets.d.ts +34 -0
- package/dist/reasoning-presets.d.ts.map +1 -0
- package/dist/reasoning-presets.js +85 -0
- package/dist/reasoning-presets.js.map +1 -0
- package/dist/sdk-log.d.ts +84 -0
- package/dist/sdk-log.d.ts.map +1 -0
- package/dist/sdk-log.js +167 -0
- package/dist/sdk-log.js.map +1 -0
- package/dist/session-pointer.d.ts.map +1 -1
- package/dist/session-pointer.js +10 -4
- package/dist/session-pointer.js.map +1 -1
- package/dist/stdio-runner.d.ts.map +1 -1
- package/dist/stdio-runner.js +32 -30
- package/dist/stdio-runner.js.map +1 -1
- package/dist/sub-agent-guard-plugin.d.ts +10 -7
- package/dist/sub-agent-guard-plugin.d.ts.map +1 -1
- package/dist/sub-agent-guard-plugin.js +12 -9
- package/dist/sub-agent-guard-plugin.js.map +1 -1
- package/dist/tui/chat-history.d.ts +29 -0
- package/dist/tui/chat-history.d.ts.map +1 -0
- package/dist/tui/chat-history.js +55 -0
- package/dist/tui/chat-history.js.map +1 -0
- package/dist/tui/chat-session.d.ts +144 -13
- package/dist/tui/chat-session.d.ts.map +1 -1
- package/dist/tui/chat-session.js +312 -73
- package/dist/tui/chat-session.js.map +1 -1
- package/dist/tui/components.d.ts +109 -6
- package/dist/tui/components.d.ts.map +1 -1
- package/dist/tui/components.js +177 -12
- package/dist/tui/components.js.map +1 -1
- package/dist/tui/config-wizard/detail.d.ts +18 -10
- package/dist/tui/config-wizard/detail.d.ts.map +1 -1
- package/dist/tui/config-wizard/detail.js +53 -46
- package/dist/tui/config-wizard/detail.js.map +1 -1
- package/dist/tui/config-wizard/engine.d.ts +2 -2
- package/dist/tui/config-wizard/engine.d.ts.map +1 -1
- package/dist/tui/config-wizard/engine.js +48 -26
- package/dist/tui/config-wizard/engine.js.map +1 -1
- package/dist/tui/config-wizard/fields.d.ts +3 -1
- package/dist/tui/config-wizard/fields.d.ts.map +1 -1
- package/dist/tui/config-wizard/fields.js +202 -167
- package/dist/tui/config-wizard/fields.js.map +1 -1
- package/dist/tui/config-wizard/frame.js +1 -1
- package/dist/tui/config-wizard/kv-editor.d.ts.map +1 -1
- package/dist/tui/config-wizard/kv-editor.js +13 -5
- package/dist/tui/config-wizard/kv-editor.js.map +1 -1
- package/dist/tui/config-wizard/list.d.ts +3 -1
- package/dist/tui/config-wizard/list.d.ts.map +1 -1
- package/dist/tui/config-wizard/list.js +4 -4
- package/dist/tui/config-wizard/list.js.map +1 -1
- package/dist/tui/config-wizard/mcp-helpers.d.ts.map +1 -1
- package/dist/tui/config-wizard/mcp-helpers.js +4 -3
- package/dist/tui/config-wizard/mcp-helpers.js.map +1 -1
- package/dist/tui/config-wizard/menu.d.ts.map +1 -1
- package/dist/tui/config-wizard/menu.js +33 -21
- package/dist/tui/config-wizard/menu.js.map +1 -1
- package/dist/tui/config-wizard/prompt-editor.d.ts +8 -1
- package/dist/tui/config-wizard/prompt-editor.d.ts.map +1 -1
- package/dist/tui/config-wizard/prompt-editor.js +11 -7
- package/dist/tui/config-wizard/prompt-editor.js.map +1 -1
- package/dist/tui/config-wizard/prompt.d.ts.map +1 -1
- package/dist/tui/config-wizard/prompt.js +8 -7
- package/dist/tui/config-wizard/prompt.js.map +1 -1
- package/dist/tui/config-wizard/step-agents.d.ts.map +1 -1
- package/dist/tui/config-wizard/step-agents.js +50 -29
- package/dist/tui/config-wizard/step-agents.js.map +1 -1
- package/dist/tui/config-wizard/step-defaults.d.ts.map +1 -1
- package/dist/tui/config-wizard/step-defaults.js +77 -27
- package/dist/tui/config-wizard/step-defaults.js.map +1 -1
- package/dist/tui/config-wizard/step-endpoints.d.ts.map +1 -1
- package/dist/tui/config-wizard/step-endpoints.js +27 -14
- package/dist/tui/config-wizard/step-endpoints.js.map +1 -1
- package/dist/tui/config-wizard/step-mcp.d.ts.map +1 -1
- package/dist/tui/config-wizard/step-mcp.js +29 -25
- package/dist/tui/config-wizard/step-mcp.js.map +1 -1
- package/dist/tui/config-wizard/step-models.d.ts.map +1 -1
- package/dist/tui/config-wizard/step-models.js +98 -43
- package/dist/tui/config-wizard/step-models.js.map +1 -1
- package/dist/tui/config-wizard/step-new-endpoint.d.ts.map +1 -1
- package/dist/tui/config-wizard/step-new-endpoint.js +16 -12
- package/dist/tui/config-wizard/step-new-endpoint.js.map +1 -1
- package/dist/tui/config-wizard/step-simple.d.ts.map +1 -1
- package/dist/tui/config-wizard/step-simple.js +18 -17
- package/dist/tui/config-wizard/step-simple.js.map +1 -1
- package/dist/tui/config-wizard/types.d.ts +31 -3
- package/dist/tui/config-wizard/types.d.ts.map +1 -1
- package/dist/tui/config-wizard/types.js.map +1 -1
- package/dist/tui/file-index.d.ts +38 -0
- package/dist/tui/file-index.d.ts.map +1 -0
- package/dist/tui/file-index.js +117 -0
- package/dist/tui/file-index.js.map +1 -0
- package/dist/tui/index.d.ts +17 -1
- package/dist/tui/index.d.ts.map +1 -1
- package/dist/tui/index.js +105 -49
- package/dist/tui/index.js.map +1 -1
- package/dist/tui/input-editing.d.ts +52 -0
- package/dist/tui/input-editing.d.ts.map +1 -0
- package/dist/tui/input-editing.js +91 -0
- package/dist/tui/input-editing.js.map +1 -0
- package/dist/tui/key-debug.d.ts +62 -0
- package/dist/tui/key-debug.d.ts.map +1 -0
- package/dist/tui/key-debug.js +92 -0
- package/dist/tui/key-debug.js.map +1 -0
- package/dist/tui/mention.d.ts +47 -0
- package/dist/tui/mention.d.ts.map +1 -0
- package/dist/tui/mention.js +152 -0
- package/dist/tui/mention.js.map +1 -0
- package/dist/tui/mouse.d.ts +55 -0
- package/dist/tui/mouse.d.ts.map +1 -0
- package/dist/tui/mouse.js +74 -0
- package/dist/tui/mouse.js.map +1 -0
- package/dist/tui/phase.d.ts +64 -0
- package/dist/tui/phase.d.ts.map +1 -0
- package/dist/tui/phase.js +112 -0
- package/dist/tui/phase.js.map +1 -0
- package/dist/tui/screens/agent-tabs.d.ts +53 -0
- package/dist/tui/screens/agent-tabs.d.ts.map +1 -0
- package/dist/tui/screens/agent-tabs.js +81 -0
- package/dist/tui/screens/agent-tabs.js.map +1 -0
- package/dist/tui/screens/chat-commands.d.ts +78 -1
- package/dist/tui/screens/chat-commands.d.ts.map +1 -1
- package/dist/tui/screens/chat-commands.js +688 -157
- package/dist/tui/screens/chat-commands.js.map +1 -1
- package/dist/tui/screens/chat.d.ts +57 -1
- package/dist/tui/screens/chat.d.ts.map +1 -1
- package/dist/tui/screens/chat.js +708 -261
- package/dist/tui/screens/chat.js.map +1 -1
- package/dist/tui/screens/common-keys.d.ts +36 -0
- package/dist/tui/screens/common-keys.d.ts.map +1 -0
- package/dist/tui/screens/common-keys.js +67 -0
- package/dist/tui/screens/common-keys.js.map +1 -0
- package/dist/tui/screens/frame-buffer.d.ts.map +1 -1
- package/dist/tui/screens/frame-buffer.js.map +1 -1
- package/dist/tui/screens/handoff-choice.d.ts +37 -0
- package/dist/tui/screens/handoff-choice.d.ts.map +1 -0
- package/dist/tui/screens/handoff-choice.js +42 -0
- package/dist/tui/screens/handoff-choice.js.map +1 -0
- package/dist/tui/screens/index.d.ts +7 -5
- package/dist/tui/screens/index.d.ts.map +1 -1
- package/dist/tui/screens/index.js +6 -5
- package/dist/tui/screens/index.js.map +1 -1
- package/dist/tui/screens/input-row.d.ts +64 -0
- package/dist/tui/screens/input-row.d.ts.map +1 -0
- package/dist/tui/screens/input-row.js +67 -0
- package/dist/tui/screens/input-row.js.map +1 -0
- package/dist/tui/screens/input.d.ts +77 -1
- package/dist/tui/screens/input.d.ts.map +1 -1
- package/dist/tui/screens/input.js +125 -8
- package/dist/tui/screens/input.js.map +1 -1
- package/dist/tui/screens/layout.d.ts +49 -0
- package/dist/tui/screens/layout.d.ts.map +1 -1
- package/dist/tui/screens/layout.js +45 -7
- package/dist/tui/screens/layout.js.map +1 -1
- package/dist/tui/screens/lines.d.ts +7 -0
- package/dist/tui/screens/lines.d.ts.map +1 -1
- package/dist/tui/screens/lines.js +6 -6
- package/dist/tui/screens/lines.js.map +1 -1
- package/dist/tui/screens/messages-view.d.ts +20 -1
- package/dist/tui/screens/messages-view.d.ts.map +1 -1
- package/dist/tui/screens/messages-view.js +55 -8
- package/dist/tui/screens/messages-view.js.map +1 -1
- package/dist/tui/screens/notice-bar.d.ts +5 -0
- package/dist/tui/screens/notice-bar.d.ts.map +1 -0
- package/dist/tui/screens/notice-bar.js +21 -0
- package/dist/tui/screens/notice-bar.js.map +1 -0
- package/dist/tui/screens/notice.d.ts +44 -10
- package/dist/tui/screens/notice.d.ts.map +1 -1
- package/dist/tui/screens/notice.js +75 -15
- package/dist/tui/screens/notice.js.map +1 -1
- package/dist/tui/screens/overlays.d.ts.map +1 -1
- package/dist/tui/screens/overlays.js +4 -3
- package/dist/tui/screens/overlays.js.map +1 -1
- package/dist/tui/screens/render.d.ts +1 -1
- package/dist/tui/screens/render.d.ts.map +1 -1
- package/dist/tui/screens/render.js +18 -11
- package/dist/tui/screens/render.js.map +1 -1
- package/dist/tui/screens/use-chat-viewport.d.ts +22 -0
- package/dist/tui/screens/use-chat-viewport.d.ts.map +1 -0
- package/dist/tui/screens/use-chat-viewport.js +50 -0
- package/dist/tui/screens/use-chat-viewport.js.map +1 -0
- package/dist/tui/screens/use-input-editing.d.ts +69 -0
- package/dist/tui/screens/use-input-editing.d.ts.map +1 -0
- package/dist/tui/screens/use-input-editing.js +220 -0
- package/dist/tui/screens/use-input-editing.js.map +1 -0
- package/dist/tui/screens/use-overlays.d.ts +25 -0
- package/dist/tui/screens/use-overlays.d.ts.map +1 -0
- package/dist/tui/screens/use-overlays.js +32 -0
- package/dist/tui/screens/use-overlays.js.map +1 -0
- package/dist/tui/screens/use-wizard-host.d.ts +46 -0
- package/dist/tui/screens/use-wizard-host.d.ts.map +1 -0
- package/dist/tui/screens/use-wizard-host.js +123 -0
- package/dist/tui/screens/use-wizard-host.js.map +1 -0
- package/dist/tui/screens/welcome.d.ts +12 -0
- package/dist/tui/screens/welcome.d.ts.map +1 -0
- package/dist/tui/screens/welcome.js +36 -0
- package/dist/tui/screens/welcome.js.map +1 -0
- package/dist/tui/status-segments.d.ts +37 -0
- package/dist/tui/status-segments.d.ts.map +1 -0
- package/dist/tui/status-segments.js +166 -0
- package/dist/tui/status-segments.js.map +1 -0
- package/dist/tui/text.d.ts +22 -7
- package/dist/tui/text.d.ts.map +1 -1
- package/dist/tui/text.js +110 -39
- package/dist/tui/text.js.map +1 -1
- package/dist/tui/theme.d.ts +2 -0
- package/dist/tui/theme.d.ts.map +1 -1
- package/dist/tui/theme.js +2 -0
- package/dist/tui/theme.js.map +1 -1
- package/dist/tui/token-rate.d.ts +83 -0
- package/dist/tui/token-rate.d.ts.map +1 -0
- package/dist/tui/token-rate.js +195 -0
- package/dist/tui/token-rate.js.map +1 -0
- package/dist/types.d.ts +7 -8
- package/dist/types.d.ts.map +1 -1
- package/dist/user-content.d.ts +41 -0
- package/dist/user-content.d.ts.map +1 -0
- package/dist/user-content.js +37 -0
- package/dist/user-content.js.map +1 -0
- package/dist/version-compare.d.ts +37 -0
- package/dist/version-compare.d.ts.map +1 -0
- package/dist/version-compare.js +105 -0
- package/dist/version-compare.js.map +1 -0
- package/dist/version.js.map +1 -1
- package/package.json +7 -5
- package/dist/auto-migrate-confirm-plugin.d.ts +0 -58
- package/dist/auto-migrate-confirm-plugin.d.ts.map +0 -1
- package/dist/auto-migrate-confirm-plugin.js +0 -61
- package/dist/auto-migrate-confirm-plugin.js.map +0 -1
- package/dist/migration-service.d.ts +0 -15
- package/dist/migration-service.d.ts.map +0 -1
- package/dist/migration-service.js +0 -66
- package/dist/migration-service.js.map +0 -1
- package/dist/tui/screens/splash.d.ts +0 -5
- package/dist/tui/screens/splash.d.ts.map +0 -1
- package/dist/tui/screens/splash.js +0 -51
- package/dist/tui/screens/splash.js.map +0 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,499 +1,1558 @@
|
|
|
1
|
+
## [1.0.0] - 2026-11-03
|
|
2
|
+
|
|
3
|
+
> First stable release. Everything from the `1.0.0-alpha.x` line — `alpha.1` through
|
|
4
|
+
> `alpha.32` — is folded into this version: nothing was dropped, no alpha behaviour was
|
|
5
|
+
> reverted. The version number is a promise, not a code change: the documented surface (the
|
|
6
|
+
> `ai` command, both run modes, the chat screen and its keybindings, the `/` command set, the
|
|
7
|
+
> configuration center, the config file layout) is now frozen, so `^1.0.0` is safe to depend on.
|
|
8
|
+
>
|
|
9
|
+
> If you are coming from `0.9.0`, read `[1.0.0-alpha.1]` below first — that entry was written
|
|
10
|
+
> as a complete summary of the `0.9.0 → 1.0.0` delta (the `aiz` / `zen` → `ai` rename, dual-mode
|
|
11
|
+
> run, the Ink full-screen rewrite). Everything after it is listed release by release.
|
|
12
|
+
|
|
13
|
+
### 📌 What this release ships on
|
|
14
|
+
|
|
15
|
+
- `@ai-zen/agents-sdk@1.0.0` (exact) + `@ai-zen/agents-core@^4.5.4`. The SDK's 1.0.0 is the
|
|
16
|
+
release where its own public surface freezes, so this pair is the first combination whose
|
|
17
|
+
type surface is not expected to move underneath you.
|
|
18
|
+
|
|
19
|
+
### ✨ Where the alpha line landed
|
|
20
|
+
|
|
21
|
+
- **Bilingual UI** (zh / en), detected from the system locale on first run and changeable with
|
|
22
|
+
`/lang`; the whole TUI, `/help`, and the command output are covered. `README` ships a key
|
|
23
|
+
table for both.
|
|
24
|
+
- **Main / sub-agent tabs** — while sub-agents run, a bar appears below the transcript and
|
|
25
|
+
`←` / `→` switches whose conversation you are reading.
|
|
26
|
+
- **Handoff and migration** — an out-of-context round can hand off to a fresh agent (with a
|
|
27
|
+
picker for "self / separate summary / skip"), and a conversation can be migrated onto a
|
|
28
|
+
different model in the configuration center.
|
|
29
|
+
- **Configuration center, end to end** — endpoints, models, image models, defaults, tool
|
|
30
|
+
output limits, MCP servers (global / project), and Agent definitions with four dimensions of
|
|
31
|
+
permission. Factory-managed entries are hosted by the SDK; "restore factory defaults" puts
|
|
32
|
+
them back without hand-editing `custom`.
|
|
33
|
+
- **Composer upgrades** — multi-line editing with a hardware-cursor-synced IME, input history
|
|
34
|
+
that carries attached images, bottom-anchored `/` and `@` menus, `@` file-path completion,
|
|
35
|
+
`Ctrl+V` image paste (256KB text limit, over which it errors instead of truncating), and
|
|
36
|
+
in-app paging with the mouse wheel.
|
|
37
|
+
- **`/update`** reports progress step by step and restarts for real once confirmed.
|
|
38
|
+
- **Syntax highlighting** for JSON tool arguments and fenced code blocks, and a three-block
|
|
39
|
+
status bar (model · agent · effort | this-round / cumulative tokens · rate | key hints).
|
|
40
|
+
|
|
41
|
+
### 💥 The last behaviour change before the freeze
|
|
42
|
+
|
|
43
|
+
- **The startup splash is gone** (`alpha.32`). Startup used to block for about 1.8s on a reveal
|
|
44
|
+
+ gradient sweep before entering the chat; it now goes straight into the chat screen, and an
|
|
45
|
+
empty conversation shows the brand block (logo, tagline, CLI / SDK / core versions) in the
|
|
46
|
+
middle of the chat area, scrolling away with the conversation. The animated splash described
|
|
47
|
+
in `[1.0.0-alpha.1]` no longer exists.
|
|
48
|
+
|
|
49
|
+
### 🧪 Tests
|
|
50
|
+
|
|
51
|
+
- 806 unit cases in 58 files, plus e2e 9 passed / 3 skipped (the skipped ones need a real API
|
|
52
|
+
key). `tsc --noEmit` (production and test configs) and `npm run build` are clean.
|
|
53
|
+
- The two `↑`-history cases in `chat.test.tsx` that were flaky on `HEAD` are fixed in
|
|
54
|
+
`alpha.32`'s follow-up: they waited a fixed 20ms for a `busy` flip that needs a promise
|
|
55
|
+
callback plus a frame, so they now wait until the input actually echoes a probe keystroke.
|
|
56
|
+
|
|
57
|
+
## [1.0.0-alpha.32] - 2026-11-03
|
|
58
|
+
|
|
59
|
+
> This release does one thing: it removes the startup splash animation. Previously every `ai`
|
|
60
|
+
> startup waited about 1.8s for the reveal + sweep before entering the chat, and that pure
|
|
61
|
+
> blocking sat right in front of "read config → resume session → create session". Now it goes
|
|
62
|
+
> straight into the chat, and the branding moved into the chat area itself, landing in the
|
|
63
|
+
> vertical middle when the conversation is empty.
|
|
64
|
+
|
|
65
|
+
### 💥 Behavior changes
|
|
66
|
+
|
|
67
|
+
- **No splash animation on startup** — removed `splash.tsx` and the `await mount(<Splash />)`
|
|
68
|
+
in `runTui()`. The old implementation revealed line by line over 330ms + a 700ms diagonal
|
|
69
|
+
gradient sweep + a 1100ms hold and auto fade-out, about 1.8s in total; after removal the
|
|
70
|
+
remaining blank window measured about 66ms (`readConfig` three times + `AgentRepository.list`,
|
|
71
|
+
all local file reads), so initialization was not moved into the chat screen.
|
|
72
|
+
- **An empty conversation shows the brand block in the vertical middle of the chat area** — logo
|
|
73
|
+
(6 lines of ASCII on wide terminals, degrading to `◢ AI · ZEN ◣` on narrow ones) + tagline +
|
|
74
|
+
the three version numbers `CLI / sdk / core`, each line centered on its own. It is now
|
|
75
|
+
ordinary transcript content: it scrolls up naturally with messages and can be scrolled back
|
|
76
|
+
into view; it yields as soon as the first message is typed (including the optimistic echo).
|
|
77
|
+
The gradient is fixed at phase 0 and runs no timers.
|
|
78
|
+
- **The version numbers moved** — previously they appeared once on the splash, now they hang in
|
|
79
|
+
the brand block; the bottom status bar (model · Agent · reasoning effort | per-turn /
|
|
80
|
+
cumulative usage · rate | keybindings) is unchanged, character for character.
|
|
81
|
+
- **`computeChatLayout` gains `anchor: "bottom" | "center"`** (defaults to `bottom`, existing
|
|
82
|
+
behavior unchanged): an empty conversation passes `center`, splitting the remainder below one
|
|
83
|
+
screenful evenly between top and bottom. Pinning to the top leaves a big empty band in the
|
|
84
|
+
middle, pinning to the bottom presses right onto the input box, and neither looks good.
|
|
85
|
+
- Bottom-pinning semantics are unchanged: once there are messages, the whole remainder is still
|
|
86
|
+
added at the head, so the bottom edge of the latest line sits right against the divider.
|
|
87
|
+
|
|
88
|
+
### 🧪 Tests
|
|
89
|
+
|
|
90
|
+
- 806 cases all green, `tsc` (production / test configs) and `npm run build` clean.
|
|
91
|
+
- Added: 6 cases in `welcome.test.ts` (wide / narrow logo selection, tagline, three version
|
|
92
|
+
spots, per-character coloring, per-line centering), 2 chat screen cases (appears on an empty
|
|
93
|
+
conversation / disappears after the first message), 1 case for `anchor: center`; removed the
|
|
94
|
+
2 Splash cases.
|
|
95
|
+
- Known leftover: the two `↑` history cases in `chat.test.tsx` were already flaky in the
|
|
96
|
+
previous release (`flush(20)` colliding with the 60ms frame batching), unrelated to this
|
|
97
|
+
release, not yet addressed.
|
|
98
|
+
|
|
99
|
+
## [1.0.0-alpha.31] - 2026-11-03
|
|
100
|
+
|
|
101
|
+
> This release attaches the CLI to the re-reviewed SDK: `@ai-zen/agents-sdk` alpha.26 +
|
|
102
|
+
> `@ai-zen/agents-core` 4.5.4. The CLI's own changes are small (guardrails stand down by
|
|
103
|
+
> themselves during handoff, handoff document filenames drop the session id suffix), but all
|
|
104
|
+
> 24 SDK fixes reach users with this release — several of them are of the "one ordinary command
|
|
105
|
+
> is enough to wedge the process" or "the tool reported success but did nothing at all" kind.
|
|
106
|
+
>
|
|
107
|
+
> The stance is unchanged: the tool layer does not carry the safety boundary (the model is not
|
|
108
|
+
> an attacker); it only fixes things normal usage runs into.
|
|
109
|
+
|
|
110
|
+
### 💥 Behavior changes (from SDK alpha.26)
|
|
111
|
+
|
|
112
|
+
- **The `exec` exit code is no longer `null`** — a command that ends normally reports `0`, and
|
|
113
|
+
non-zero codes are returned truthfully; the model no longer misreads a successful command as
|
|
114
|
+
"did not end normally".
|
|
115
|
+
- **A timeout reclaims the whole process group** — previously only the shell was killed, so the
|
|
116
|
+
grandchildren of `cmd | cat`, `sh -c '… &'` were left as orphans (`sleep 45 &` could even
|
|
117
|
+
serve as a resident process, bypassing `spawn`). Now `detached` + `kill(-pid)` reclaims the
|
|
118
|
+
entire group.
|
|
119
|
+
- **`exec` output truncation is labeled truthfully** — `maxBuffer` is explicitly set to 16MB, a
|
|
120
|
+
real truncation reports `truncated`, and `ERR_CHILD_PROCESS_STDIO_MAXBUFFER` is no longer
|
|
121
|
+
taken as an exit code while falsely claiming "the full content was written to disk".
|
|
122
|
+
- **`download_file` gains a required `timeout` (milliseconds)** and now writes to disk as a
|
|
123
|
+
stream (a 200MB file no longer eats 650MB of heap); on interruption it no longer deletes a
|
|
124
|
+
file that already existed at the same path.
|
|
125
|
+
- **`spawn` no longer reports false success** — the returned field `success` becomes `started`,
|
|
126
|
+
and it states that "exit status is not tracked"; a missing command / syntax error / `exit 3`
|
|
127
|
+
no longer displays as success.
|
|
128
|
+
- **`glob` / `find_text` report an error for nonexistent directories**, no longer silently
|
|
129
|
+
returning `[]` and letting you believe the file does not exist.
|
|
130
|
+
- **`generate_image` supports endpoints that only return `b64_json`** — after writing to disk
|
|
131
|
+
it gives a local path, no more `success: true` with not a single image.
|
|
132
|
+
- **`edit` / `batch_edit` fix two file-destroying pitfalls** — an empty `oldText` puts the
|
|
133
|
+
process into a synchronous infinite loop (not even `Ctrl+C` helps); `$&` and `$$` in the
|
|
134
|
+
replacement text are taken as replacement patterns and silently corrupt the file.
|
|
135
|
+
- **`permissions` actually take effect** — a denied skill can no longer be loaded by naming it;
|
|
136
|
+
a server not allowed in `mcps` cannot be called even if another Agent has connected to it.
|
|
137
|
+
- **MCP connections are more stable** — quitting mid-connect no longer "resurrects" the
|
|
138
|
+
connection (stdio child processes are no longer left orphaned); a runtime disconnect marks the
|
|
139
|
+
status back to `disconnected` (`load_mcp` no longer returns a stale manifest); connecting has
|
|
140
|
+
a 60s timeout.
|
|
141
|
+
|
|
142
|
+
### 🛠️ CLI itself
|
|
143
|
+
|
|
144
|
+
- **Stand-down of the guardrails during handoff/migration is decided by the guardrails themselves**
|
|
145
|
+
(`isSuspended: () => handoffService.busy`). Previously `lastUsage` was zeroed right before
|
|
146
|
+
handoff — that was two copies of the same fact, and changing one left the other stale. Now
|
|
147
|
+
`lastUsage` is a read-only value derived from `messages`; replacing `messages` follows
|
|
148
|
+
automatically.
|
|
149
|
+
- **Handoff document filenames drop the session id suffix**: `<YYYYMMDD-HHmmss>.md`. Session
|
|
150
|
+
names are usually edited by hand, so carrying them only makes files harder to recognize;
|
|
151
|
+
concurrent handoffs within the same second should be avoided anyway, and no random suffix is
|
|
152
|
+
added for it.
|
|
153
|
+
|
|
154
|
+
### 🧪 Tests
|
|
155
|
+
|
|
156
|
+
- 799 cases all green (including the build and e2e gates), `tsc` clean on both configs
|
|
157
|
+
(production / test).
|
|
158
|
+
- Added: guardrail wiring cases (stand down during handoff, restore the hard line afterwards);
|
|
159
|
+
usage construction switched to an "assistant message with usage" (`lastUsage` is read-only
|
|
160
|
+
now).
|
|
161
|
+
|
|
162
|
+
## [1.0.0-alpha.30] - 2026-11-02
|
|
163
|
+
|
|
164
|
+
> This release closes out 22 review issues in one pass. Three of them change the feel directly:
|
|
165
|
+
> the completion menu is anchored to the bottom, `↑`/`↓` move the cursor first in multi-line
|
|
166
|
+
> input, and the input area is capped at 8 lines; the detail screen also no longer exposes
|
|
167
|
+
> `custom`, replaced by "restore factory defaults".
|
|
168
|
+
>
|
|
169
|
+
> Note: the `1.0.0-alpha.28` / `.29` entries had so far only existed locally (the registry's
|
|
170
|
+
> `alpha` tag stopped at alpha.27); this release carries them out as well.
|
|
171
|
+
|
|
172
|
+
### 💥 Behavior changes
|
|
173
|
+
|
|
174
|
+
- **The completion menu is anchored to the bottom** — `/` commands and `@` paths share one
|
|
175
|
+
`BottomMenu`: `items[0]` sits right above the input box, the rest grows upward and is dropped
|
|
176
|
+
from the top when it does not fit; the row cap is granted by the layout according to the
|
|
177
|
+
remaining space (no longer fixed at 8 rows), and rendering and the IME cursor read the same
|
|
178
|
+
`layout.hintRows`. Descriptions no longer squeeze the command name into a stub.
|
|
179
|
+
- **`↑` / `↓` move the cursor first** — in a multi-line draft they page through history only at
|
|
180
|
+
the first / last line (previously one press jumped to history).
|
|
181
|
+
- **The input area is capped at 8 lines**, and the window follows the cursor (previously it was
|
|
182
|
+
pinned to the end, so the cursor disappeared outside the box when it went back to the first
|
|
183
|
+
line).
|
|
184
|
+
- **`↑` history paging carries images** — the history data source is unified on `messages`, so
|
|
185
|
+
going back to a message with images restores the images as well; history images of type
|
|
186
|
+
`file_id` can only restore text (there is no local copy).
|
|
187
|
+
- **The `/load` list shows session filenames only** — no longer reading each session file to
|
|
188
|
+
parse a summary.
|
|
189
|
+
- **`/save <name>`** — save as a specified filename (the old filename disappears, the
|
|
190
|
+
last-session pointer follows); if the name is taken and is not the current session, a second
|
|
191
|
+
confirmation is asked first.
|
|
192
|
+
- **The detail screen no longer has a `custom` toggle, replaced by "restore factory defaults"**
|
|
193
|
+
— the factory model / image model are **replaced wholesale** with the factory definition
|
|
194
|
+
(`custom` disappears with it = managed by the SDK again); the default Agent only swaps the
|
|
195
|
+
prompt back to the factory one, keeping the name / description / model / permissions all
|
|
196
|
+
intact; entries with no matching id in the factory list (self-built) do not show the action.
|
|
197
|
+
- **256KB cap on pasted text** — exceeding it reports an error and does **not truncate** (half
|
|
198
|
+
a piece of code / half a JSON is worse than not pasting).
|
|
199
|
+
- **Writing sentences no longer pops up the `@` menu at random** — the token boundary adds
|
|
200
|
+
`。 、 ! ? ( ) 《 》 【 】 〔 〕 「 」 『 』 … — ~`.
|
|
201
|
+
- **No endpoint, no new model / image model** — previously an empty shell with
|
|
202
|
+
`endpointId: ""` could be created.
|
|
203
|
+
- **A broken `mcp.json` no longer passes itself off as empty config** — the MCP screen states
|
|
204
|
+
"cannot be parsed" and stays editable, and saving produces exactly a new, correctly formatted
|
|
205
|
+
file (that is a repair, not a data-losing overwrite).
|
|
206
|
+
|
|
207
|
+
### 🐛 Fixes
|
|
208
|
+
|
|
209
|
+
- The KV editor crashed on Enter after deleting the last item (the cursor is clamped to the new
|
|
210
|
+
last item).
|
|
211
|
+
- `SelectList` crashed on Enter after the list got shorter (both rendering and confirmation go
|
|
212
|
+
through `safeIndex`).
|
|
213
|
+
- Automatic save failures were reported in an info tone.
|
|
214
|
+
- `/back` did not save after reverting (the reverted messages stayed in the archive).
|
|
215
|
+
- Deleting the default Agent did not clear `config.defaultAgent`, and the next startup died on
|
|
216
|
+
"Agent has no model bound".
|
|
217
|
+
- An EPIPE stack trace was printed when stdout was closed early (now handled as a normal exit).
|
|
218
|
+
- The `/load` `/back` selector row budget under-counted by 2 rows, so the bottom-pinned layout
|
|
219
|
+
ate the last two entries.
|
|
220
|
+
- `truncate` truncates by grapheme cluster and no longer splits emoji into lone surrogates.
|
|
221
|
+
- The e2e assertion did not match reality (`ai hook` writes to stdout, exit code 0) and was
|
|
222
|
+
excluded by `vitest.config.ts`, so it could never go red — after correcting the assertion it
|
|
223
|
+
went from 5 red to all green.
|
|
224
|
+
|
|
225
|
+
### 🔒 Security
|
|
226
|
+
|
|
227
|
+
- **`@ai-zen/agents-sdk` bumped to `1.0.0-alpha.25`**: `EntityRepository.path()` now validates
|
|
228
|
+
that an entity id cannot escape the repository directory (`resolve` + `root + sep`; a bare
|
|
229
|
+
`startsWith` cannot stop `<root>-evil`). `/save ../x` now reports a clear error and can no
|
|
230
|
+
longer write outside the session directory. This release also carries the SDK's endpoint
|
|
231
|
+
`capabilities` migration backfill (old `deepseek` endpoints get `filesApi`, and images move
|
|
232
|
+
to the Files API).
|
|
233
|
+
|
|
234
|
+
### ✅ Tests
|
|
235
|
+
|
|
236
|
+
- Unit tests **57 files / 798 cases passing**, e2e **9 passed / 3 skipped** (3 need a real API
|
|
237
|
+
key), `tsc --noEmit` clean.
|
|
238
|
+
- The release gate `prepublishOnly` changed to `npm run test:all` (unit tests + build + e2e);
|
|
239
|
+
`npm test` keeps its semantics and still runs only types + unit tests.
|
|
240
|
+
- Test time 43.6s → ~8s: removed `isolate`, single fork, dependency injection replaced all
|
|
241
|
+
module-level `vi.mock`.
|
|
242
|
+
|
|
243
|
+
## [1.0.0-alpha.29] - 2026-10-31
|
|
244
|
+
|
|
245
|
+
> `Ctrl+V` goes from "pops up an unsupported notice" to **actually pasting**: if the clipboard
|
|
246
|
+
> holds an image it is attached to the message, otherwise it falls back to pasting text.
|
|
247
|
+
|
|
248
|
+
### ✨ New features
|
|
249
|
+
|
|
250
|
+
- **Pasting images** — `Ctrl+V` (or `⌘V` under the kitty protocol) first asks the system
|
|
251
|
+
clipboard for an image; if there is one it is attached to the message being composed, and
|
|
252
|
+
`📎 N images attached · Ctrl+V to add more · Esc to clear` appears above the input box; with
|
|
253
|
+
no image it counts as an ordinary paste, inserting the clipboard text at the cursor. Probed
|
|
254
|
+
per platform:
|
|
255
|
+
|
|
256
|
+
| Platform | Image | Text |
|
|
257
|
+
|---|---|---|
|
|
258
|
+
| macOS | `osascript -e "get the clipboard as «class PNGf»"` (stdout is `«data PNGf<HEX>»`) | `pbpaste` |
|
|
259
|
+
| Linux | `wl-paste --type image/png` (Wayland) → `xclip -t image/png -o` (X11) | `wl-paste --no-newline` → `xclip -o` |
|
|
260
|
+
| Windows | PowerShell `Get-Clipboard -Format Image` → PNG → base64 | `Get-Clipboard -Raw` |
|
|
261
|
+
|
|
262
|
+
- **Nothing ever touches the disk** — images are handed to the SDK's `fileToContentSection()`
|
|
263
|
+
as raw base64 (its `FileSource` already has a `{ base64, mime }` variant): whether to upload
|
|
264
|
+
to the Files API or inline base64, and how to degrade when the model does not support vision,
|
|
265
|
+
is entirely decided by the SDK per current endpoint — the CLI neither reimplements it nor
|
|
266
|
+
hardcodes endpoints.
|
|
267
|
+
- **At most 4 images per message** (`MAX_PASTED_IMAGES`): one screenshot is often 1–3MB once
|
|
268
|
+
inlined, and a few more in a single request easily reach tens of MB. Over the cap, an
|
|
269
|
+
oversized image (>20MB), an empty clipboard and a missing clipboard tool each have their own
|
|
270
|
+
message.
|
|
271
|
+
|
|
272
|
+
### 🎨 Interaction
|
|
273
|
+
|
|
274
|
+
- **Images first, text as the fallback**, and the order cannot be reversed: pasting the text
|
|
275
|
+
first leaves no room (there is already a chunk in the draft by then).
|
|
276
|
+
- **`Esc` with attachments clears the attachments first and keeps the draft** — a mistaken paste
|
|
277
|
+
always has a way back, and `Esc` does not have to become a random key that "clears everything".
|
|
278
|
+
- The attachment hint row is always one line, and its row count is counted both in
|
|
279
|
+
`computeChatLayout` (height) and in `computeInputCursorPosition` (IME cursor y) — one row off
|
|
280
|
+
and the whole input box shifts; this has happened before.
|
|
281
|
+
- The optimistic echo is built from "text segments + image placeholder blocks", matching the
|
|
282
|
+
structure actually sent (otherwise the image entries would flash and vanish before the real
|
|
283
|
+
message lands); inline images in the transcript show only `[image]`, so a data URL of hundreds
|
|
284
|
+
of thousands of characters is not painted on screen (only remote URLs carry an address).
|
|
285
|
+
|
|
286
|
+
### 🐛 Fixes
|
|
287
|
+
|
|
288
|
+
- **Messages with images lost the cwd injection** — `CwdTrackerPlugin` used to look only for
|
|
289
|
+
user messages whose `content` was a string, and skipped them outright once content became a
|
|
290
|
+
block array, so the model saw the working directory of the previous turn for that round. Now
|
|
291
|
+
it appends to the **last text segment**; when the array has no text segment, one is added.
|
|
292
|
+
- "`PgUp`/`PgDn` (or `Alt+V`/`Ctrl+V`) page through" in README / getting-started was wrong:
|
|
293
|
+
`Ctrl+V` never doubled as paging (`common-keys.ts` only accepts `Alt+V`), now corrected.
|
|
294
|
+
|
|
295
|
+
### ✅ Tests
|
|
296
|
+
|
|
297
|
+
- 33 cases added: `clipboard.test.ts` 15 (three-platform command selection / osascript hex
|
|
298
|
+
parsing / missing tool / empty output / over the limit / CRLF normalization),
|
|
299
|
+
`user-content.test.ts` 6 (text segment first / partial failure / total failure falls back to
|
|
300
|
+
plain text / warning passthrough), `cwd-tracker-plugin.test.ts` 4 (string / content blocks /
|
|
301
|
+
images only / no duplicate injection), `pasted-images.test.tsx` 3 (always one line / layout
|
|
302
|
+
takes one extra row / cursor y stays in sync), `chat.test.tsx` +5 (paste text without images /
|
|
303
|
+
attachment row with images / Esc clears attachments and keeps the draft / send with images /
|
|
304
|
+
empty clipboard notice).
|
|
305
|
+
- Full run **57 files / 741 cases passing**, `tsc --noEmit` 0 errors.
|
|
306
|
+
- Real-artifact verification (local Linux, no clipboard tool): `readClipboardImage() → { kind: "none" }`,
|
|
307
|
+
`readClipboardText() → null`, 11ms without throwing; running the real SDK conversion without
|
|
308
|
+
stubs, `vision: true` yields an `image_url` inline block and a missing `vision` yields a text
|
|
309
|
+
hint + warning.
|
|
310
|
+
|
|
311
|
+
## [1.0.0-alpha.28] - 2026-10-31
|
|
312
|
+
|
|
313
|
+
> Typing `@` in the input box brings up **file path completion** — what gets inserted is the
|
|
314
|
+
> relative path text, and the model reads the file itself with the `read` tool.
|
|
315
|
+
|
|
316
|
+
### ✨ New features
|
|
317
|
+
|
|
318
|
+
- **`@` file path completion** — typing `@` pops up in-project candidates, `↑`/`↓` to select,
|
|
319
|
+
`Tab` or `Enter` to insert into the draft, `Esc` to close the menu only (without touching the
|
|
320
|
+
draft). Three layers, consistent with the existing style of the repo:
|
|
321
|
+
- `tui/mention.ts` (pure logic): `extractMention()` decides whether the cursor is inside an
|
|
322
|
+
`@token`, `rankPaths()` fuzzy subsequence ranking, `menuWindow()` the viewport,
|
|
323
|
+
`mentionMenuRows()` the row count;
|
|
324
|
+
- `tui/file-index.ts` (IO): `git ls-files -co --exclude-standard` preferred (honors
|
|
325
|
+
`.gitignore`, includes untracked files), falling back to `fs.glob` outside a git directory
|
|
326
|
+
and excluding `node_modules` / `.git` / `dist`; 15s cache, 20000 entries capped, 5s timeout;
|
|
327
|
+
- `screens/chat.tsx`: the wiring from keybinding → candidates → `replaceRange()` insertion.
|
|
328
|
+
- **The criterion rests on character facts**: scan back from the cursor to whitespace / line
|
|
329
|
+
start, and an `@` encountered on the way counts as a mention only if it is preceded by line
|
|
330
|
+
start, whitespace or a separator. So `a@b.com` does not pop up the menu, while `@src/tui`,
|
|
331
|
+
`(@src/x)` and `"@src/x"` all do; when the cursor sits in the middle of a token
|
|
332
|
+
(`@src|/foo.ts`), the query ends at the cursor and `end` eats the whole token forward —
|
|
333
|
+
completion replaces the whole span and leaves no partial leftovers.
|
|
334
|
+
- **Ranking**: greedy subsequence + gap penalty + consecutive-hit bonus; **whole-span prefix >
|
|
335
|
+
filename prefix**, and on a tie the shallower / shorter one wins. Measured on this repo: `@`
|
|
336
|
+
takes 50 out of 182 indexed entries (1.1ms), `@chat` hits 19 (0.2ms), `@file-index` matches 2
|
|
337
|
+
exactly (0.1ms).
|
|
338
|
+
- **Directories can be drilled into level by level**: selecting a directory appends a trailing
|
|
339
|
+
slash (`@src/tui/`), and after insertion the menu refilters to that directory's contents, so
|
|
340
|
+
you can walk `@src/` → `@src/tui/` → the target file.
|
|
341
|
+
|
|
342
|
+
### 🎨 Interaction
|
|
343
|
+
|
|
344
|
+
- `Tab` / `Enter` = **complete without sending** (keep writing after picking, to avoid sending
|
|
345
|
+
half a message by mistake); `Esc` only closes the menu and does not clear the draft (the old
|
|
346
|
+
`Esc` semantic was to clear the draft, so pressing Esc halfway through an `@` took the body
|
|
347
|
+
with it).
|
|
348
|
+
- Candidates can number in the dozens; the menu **takes at most 8 rows** and scrolls with the
|
|
349
|
+
highlight; while the index is not ready, one row of "Scanning project files…" is shown first.
|
|
350
|
+
- The row count has a **single source of truth** `mentionMenuRows()`: `computeChatLayout`'s
|
|
351
|
+
`hintCount`, the IME cursor y coordinate and `MentionMenu`'s actual rendering draw from the
|
|
352
|
+
same source, with a case asserting `mentionMenuRows(n) === menuWindow(n)`.
|
|
353
|
+
- The placeholder changed to "Type a message, / for commands, @ for files…" so this entry point
|
|
354
|
+
can be seen.
|
|
355
|
+
|
|
356
|
+
### 🔧 Refactors
|
|
357
|
+
|
|
358
|
+
- `useInputEditing` gains `replaceRange(start, end, text)`: it replaces the span **and records
|
|
359
|
+
an undo snapshot**, so a wrong completion can be taken back with `Ctrl+_` (using
|
|
360
|
+
`setInputText` would bypass the undo stack).
|
|
361
|
+
|
|
362
|
+
### ✅ Tests
|
|
363
|
+
|
|
364
|
+
- 39 cases added: `mention.test.ts` 22 (extraction / insertion / ranking / viewport / row count
|
|
365
|
+
consistency), `file-index.test.ts` 9 (git listing normalization / cache TTL / cwd switch /
|
|
366
|
+
fallback / error convergence / real disk), `components-mention.test.tsx` 5 (8-row cap /
|
|
367
|
+
scrolling follows / directory trailing slash / scanning placeholder / no match takes no row),
|
|
368
|
+
`chat.test.tsx` +6 (menu pops up / Tab completes without sending / Enter completes / ↓ changes
|
|
369
|
+
the highlight / Esc only closes the menu / no match still sends), `use-input-editing.test.tsx`
|
|
370
|
+
+3 (range replacement / undoable / out-of-range clamping).
|
|
371
|
+
- Full run **53 files / 708 cases passing**, `tsc --noEmit` 0 errors.
|
|
372
|
+
|
|
373
|
+
## [1.0.0-alpha.27] - 2026-10-29
|
|
374
|
+
|
|
375
|
+
> SDK warnings and errors now go to the notice bar — a **generic channel**, not wired up
|
|
376
|
+
> separately for each feature.
|
|
377
|
+
|
|
378
|
+
### ✨ New features
|
|
379
|
+
|
|
380
|
+
- **SDK logs → notice bar** (`sdk-log.ts` goes from "write to disk only" to a tee) —
|
|
381
|
+
`installSdkLogger()` used to **take over SDK logs wholesale into a file** (otherwise
|
|
382
|
+
`console.log` would tear up Ink's frames), at the cost that a single word of "what the SDK
|
|
383
|
+
said" was invisible in the UI and could only be found afterwards in `~/.ai-zen/logs/sdk.log`.
|
|
384
|
+
Now `warn` / `error` are also forwarded to the notice bar besides being written to disk:
|
|
385
|
+
- **Late binding**: `installSdkLogger()` runs before Ink renders, while the notice bar lives
|
|
386
|
+
in `Chat`'s React state. `Chat` registers a sink on mount; the latest 5 messages are
|
|
387
|
+
buffered before registration and pushed on registration — startup problems are exactly what
|
|
388
|
+
most needs to be seen.
|
|
389
|
+
- **Dedup**: the same sentence repeating (for example failing once per turn) reaches the bar
|
|
390
|
+
only once.
|
|
391
|
+
- **Capped at 20 rows**: after the cap, one extra line `More SDK warnings suppressed — see <log path>`
|
|
392
|
+
is appended and nothing more is pushed. SDK log volume is counted per "request / refresh",
|
|
393
|
+
and without a cap the bar and the React state would be flooded together.
|
|
394
|
+
- `info` goes to the file only — the SDK logs info densely (mcp manifest, tool discovery), and
|
|
395
|
+
putting it on the bar is spam.
|
|
396
|
+
- From now on, any new SDK feature that logs `warn` / `error` is automatically visible in the
|
|
397
|
+
UI, **no callback needs to be passed for it**. This channel only uses the existing
|
|
398
|
+
`setLogger` and is decoupled from the SDK version.
|
|
399
|
+
- **The notice bar gains a `warn` tone** — `NoticeTone` expands from `info | error` to
|
|
400
|
+
`info | warn | error`, and `NoticeBar` colors it with `theme.warn`. A warning should neither be
|
|
401
|
+
downgraded to an ordinary notice (it would be missed) nor pass itself off as an error (it
|
|
402
|
+
would mislead).
|
|
403
|
+
|
|
404
|
+
### 🔧 Dependencies
|
|
405
|
+
|
|
406
|
+
- `@ai-zen/agents-sdk` `1.0.0-alpha.23` → `1.0.0-alpha.24`: loading of the `tools/` directory
|
|
407
|
+
became fail-safe — files without `export` are no longer evaluated, `process.exit` / `abort`
|
|
408
|
+
are suppressed during evaluation while `console` output is redirected to stderr, and importing
|
|
409
|
+
a single file now has a timeout.
|
|
410
|
+
|
|
411
|
+
This fixes a real incident: a dev script that called `process.exit(2)` when run without
|
|
412
|
+
arguments was placed in `.ai-zen/tools/`, the SDK `import()`-ed it midway through creating a
|
|
413
|
+
session, and so **every startup killed the CLI during initialization**, leaving only the last
|
|
414
|
+
frame on screen and looking like "stuck on startup"; several other scripts in the same
|
|
415
|
+
directory silently rewrote PNG assets in the project on every startup. Such files are now
|
|
416
|
+
skipped (or suppressed) and reported in the notice bar through the channel above.
|
|
417
|
+
|
|
418
|
+
### ✅ Tests
|
|
419
|
+
|
|
420
|
+
- 6 cases added in `src/sdk-log-notice.test.ts`: `warn`/`error` reach the bar, `info` does not,
|
|
421
|
+
buffered startup messages are pushed later, dedup, cap wrap-up, silent mode does not touch the
|
|
422
|
+
bar.
|
|
423
|
+
- Full run **50 files / 663 cases passing** (49 files / 657 cases at alpha.26), `tsc --noEmit`
|
|
424
|
+
0 errors.
|
|
425
|
+
|
|
426
|
+
## [1.0.0-alpha.26] - 2026-10-29
|
|
427
|
+
|
|
428
|
+
> `/update` changes from "one spinner + one result line" to **saying what it is doing at every
|
|
429
|
+
> step**, and fills in the previously nominal "takes effect after restart" — it now really
|
|
430
|
+
> restarts the process.
|
|
431
|
+
|
|
432
|
+
### ✨ New features
|
|
433
|
+
|
|
434
|
+
- **One key to restart after install** — a confirmation box pops up after install: "vX is
|
|
435
|
+
installed, it takes effect after a restart… restart now?", with "Yes" highlighted by default
|
|
436
|
+
(the same `Confirm` overlay used by `/migrate`, Enter goes ahead); after confirming, the
|
|
437
|
+
current conversation is saved first and then the process restarts, and the new process
|
|
438
|
+
**resumes the previous session** on a normal start. Declining blocks nothing and still gives
|
|
439
|
+
the "takes effect after restarting ai" hint.
|
|
440
|
+
- **Progress information is no longer missing** — it reports in order "Querying the latest
|
|
441
|
+
version…" → "Found vX (current vY), installing…" → "✅ Installed …"; before handing the
|
|
442
|
+
terminal over to npm, it prints a header on the real TTY: which package is being installed,
|
|
443
|
+
the current version, the install location from `npm root -g`, and the command actually
|
|
444
|
+
executed.
|
|
445
|
+
|
|
446
|
+
### 🐛 Fixes
|
|
447
|
+
|
|
448
|
+
- **"Takes effect after restart" was an empty promise** — the old implementation only `push`-ed
|
|
449
|
+
one "takes effect after restarting ai" line, and `/update` itself never restarted. And the
|
|
450
|
+
`exit({ restart: true })` path (used by `/model`, `/lang`) merely remounted `<Chat/>` in the
|
|
451
|
+
same process, so the in-memory module graph stayed old, which for an upgrade like "the code on
|
|
452
|
+
disk changed" amounts to not installing at all. It now **really re-execs itself**: `spawn` a
|
|
453
|
+
new process running the new dist, and the parent waits in a suspended state and exits after
|
|
454
|
+
the child exits.
|
|
455
|
+
- **The spinner during `/update` was "frozen"** — `readDistTags()` used `spawnSync`, blocking
|
|
456
|
+
the event loop synchronously, so Ink's animation timer stalled for 1–3 seconds. Changed to an
|
|
457
|
+
async `spawn` with a 20 second timeout (no more hanging indefinitely when offline).
|
|
458
|
+
- **The spinner label was "Processing…"** — that label comes from `phaseLabel(session.phase)` in
|
|
459
|
+
the generation phase, but `/update` has no generation turn at all, so `phase` was `null` and
|
|
460
|
+
the fallback became the generic "Processing…", having nothing to do with what was happening.
|
|
461
|
+
Added `busySpinnerLabel()`: with a generation phase it is still driven by the SDK phase, and
|
|
462
|
+
during blocking commands it shows `/update running…` instead.
|
|
463
|
+
|
|
464
|
+
### 🔧 Improvements
|
|
465
|
+
|
|
466
|
+
- **`--prefer-online` on both the query and the install** — npm caches the packument, so right
|
|
467
|
+
after a release `npm view` may still return old dist-tags and mistake "there is a new version"
|
|
468
|
+
for "already the latest" (this happened on release day); the install side likewise. Also added
|
|
469
|
+
`--no-fund` to cut noise.
|
|
470
|
+
|
|
471
|
+
### ⚠️ Two terminal gotchas (written in code comments)
|
|
472
|
+
|
|
473
|
+
- **The restart must exit inside the suspended state**: `beginSuspend()` in `suspendTerminal`
|
|
474
|
+
turns raw mode off, and `endSuspend()` turns it back on; if `process.exit` is called after
|
|
475
|
+
`suspendTerminal` returns, the terminal stays in raw mode and every key is garbage after
|
|
476
|
+
returning to the shell.
|
|
477
|
+
- **`disableMouseTracking()` must be called before the restart**: mouse reporting is toggled by
|
|
478
|
+
`mount()` per Ink instance, and the instance has not exited during suspension — without
|
|
479
|
+
turning it off, if the child process dies abnormally, the terminal keeps writing wheel events
|
|
480
|
+
as escape sequences.
|
|
481
|
+
|
|
482
|
+
### 📖 Docs
|
|
483
|
+
|
|
484
|
+
- `README.md` / `README.zh.md` / `docs/{zh,en}/getting-started.md` — the `/update` description is
|
|
485
|
+
corrected: the original said "installs the alpha channel globally", while it actually picks
|
|
486
|
+
the highest semver among **all** dist-tags (so alpha is installed when it is newer than
|
|
487
|
+
`latest`), and "shows progress step by step, one-key restart after install" was added.
|
|
488
|
+
|
|
489
|
+
### ✅ Tests
|
|
490
|
+
|
|
491
|
+
- Full run **49 files / 657 cases passing** (649 cases at alpha.25), `tsc --noEmit` 0 errors.
|
|
492
|
+
- `/update` cases went from 6 to 11: added "restart after confirmation + save before restart",
|
|
493
|
+
"declining the restart neither saves nor restarts", "a new session with no user messages is
|
|
494
|
+
not saved", "three progress notices reported in order", "the terminal header contains
|
|
495
|
+
version/location/command", "the query carries `--prefer-online`".
|
|
496
|
+
- 3 cases added for `busySpinnerLabel`, including a regression guard: the label **must not** fall
|
|
497
|
+
back to "Processing…" while a command is running.
|
|
498
|
+
|
|
499
|
+
## [1.0.0-alpha.25] - 2026-10-29
|
|
500
|
+
|
|
501
|
+
> Upgraded to SDK `1.0.0-alpha.23` (model-facing and user-facing copy is now fully in English),
|
|
502
|
+
> and added the missing `LICENSE` file.
|
|
503
|
+
|
|
504
|
+
### 🔧 Dependencies
|
|
505
|
+
|
|
506
|
+
- `@ai-zen/agents-sdk` `1.0.0-alpha.22` → `1.0.0-alpha.23`.
|
|
507
|
+
|
|
508
|
+
### 💬 Behavior changes (from SDK)
|
|
509
|
+
|
|
510
|
+
- **All built-in SDK copy is now English** — tool schema descriptions, the factory system prompt,
|
|
511
|
+
injected wording, tool return values, error messages, factory Skills and catalog data are all
|
|
512
|
+
fixed English; the SDK no longer contains any Chinese copy, and **no i18n is introduced**
|
|
513
|
+
(reason: `tokenThresholds` / `maxToolOutput` are absolute quantities, and changing the language
|
|
514
|
+
of tool schemas means a different set of token counts, which would remove the single variable
|
|
515
|
+
from performance benchmarks).
|
|
516
|
+
- **Reply language becomes "follow the user"** — "please reply in Chinese" in the factory system
|
|
517
|
+
prompt is replaced by `Always reply in the same language the user writes in.`, adapting per
|
|
518
|
+
message.
|
|
519
|
+
- **No breaking impact on the CLI** — the CLI ships its own `i18n` dictionary (`src/i18n`), and
|
|
520
|
+
all UI copy and notices are rendered by the CLI itself without parsing SDK copy strings; SDK
|
|
521
|
+
usage is limited to type imports and structural APIs such as `AgentRepository` /
|
|
522
|
+
`ConfigManager` / `setLogger` / `HandoffService`. The 49 files / 649 test cases are identical
|
|
523
|
+
item by item before and after the upgrade.
|
|
524
|
+
|
|
525
|
+
### 📄 License
|
|
526
|
+
|
|
527
|
+
- **Added `LICENSE`** — `package.json` declared `"license": "MIT"` before, but the repository
|
|
528
|
+
had no license text, and naturally the npm package did not either. The full MIT text is added
|
|
529
|
+
now (identical to `LICENSE` at the root of the `agents` repository,
|
|
530
|
+
`Copyright (c) 2024 LZQCN`). The `files` field is unchanged: npm includes `LICENSE` forcibly,
|
|
531
|
+
and `npm pack` confirmed the tarball contains the file.
|
|
532
|
+
|
|
533
|
+
### 📖 Docs
|
|
534
|
+
|
|
535
|
+
- `docs/{zh,en}/getting-started.md` — the SDK version in the runtime dependency list is corrected
|
|
536
|
+
from `1.0.0-alpha.18` to `1.0.0-alpha.23`, and `@ai-zen/agents-core` from `^4.5.2` to `^4.5.3`
|
|
537
|
+
(aligned with the actual `package.json`).
|
|
538
|
+
- `docs/{zh,en}/index.md` — the license section now points to the `LICENSE` file at the
|
|
539
|
+
repository root and states that the full text ships with the package.
|
|
540
|
+
- `README.md` / `README.zh.md` — the version number in the startup screen example is synced to
|
|
541
|
+
`1.0.0-alpha.25`.
|
|
542
|
+
|
|
543
|
+
### ✅ Tests
|
|
544
|
+
|
|
545
|
+
- Full run **49 files / 649 cases passing**, `tsc --noEmit` 0 errors, consistent with the alpha.24
|
|
546
|
+
baseline (this release only bumps dependencies, no source changes).
|
|
547
|
+
## [1.0.0-alpha.24] - 2026-10-28
|
|
548
|
+
|
|
549
|
+
> Three things: **invisible context** is now visible (messages that never enter the context are struck through),
|
|
550
|
+
> upgrades have `/update`, and the abort strategy went from a hardcoded constant to a configurable option.
|
|
551
|
+
|
|
552
|
+
### ✨ New features
|
|
553
|
+
|
|
554
|
+
- **Messages that never enter the context are struck through** — the predicate shares its source with core's
|
|
555
|
+
`formatHistory()` (`status == undefined || Completed` and not `omit`), while the strategy was already settled into the
|
|
556
|
+
field when `Agent.run()` wraps up (`keep`→`Completed`, `drop`→`Aborted`; tool rounds `fill`→`Completed`,
|
|
557
|
+
`drop`→`omit`), so the render layer does not re-derive the strategy.
|
|
558
|
+
- The streaming one (`Pending` / `Writing`) is not struck through: it is absent from `formatHistory` as well, but that
|
|
559
|
+
means "not yet" rather than "dropped".
|
|
560
|
+
- `hidden` still does not render at all; `omit` no longer makes messages vanish into thin air (previously `hidden || omit`
|
|
561
|
+
both `return []`, so after a user aborted a round of tool calls those messages evaporated silently).
|
|
562
|
+
- **`/update`** — installs the newest published version from the registry: reads
|
|
563
|
+
`npm view @ai-zen/cli dist-tags --json` and takes the one with **the highest semver across all tags**. Honoring only
|
|
564
|
+
`latest` would downgrade users running alpha builds (the actual published state is
|
|
565
|
+
`latest 0.9.0 < alpha 1.0.0-alpha.23`); if the target is not newer than the current process, nothing is installed
|
|
566
|
+
(a local dev build is not "upgraded" into a downgrade). Installation goes through `suspendTerminal` +
|
|
567
|
+
`stdio: inherit`, so npm progress and errors land directly on the real terminal; afterwards the version is read from
|
|
568
|
+
the `package.json` under `npm root -g` (`npm view` against the registry lags by a few minutes), and a restart is
|
|
569
|
+
suggested (old code is still in memory; no automatic restart, to avoid losing the session).
|
|
570
|
+
- **Configurable abort strategy** — SDK upgraded to `1.0.0-alpha.22`: `AppConfig` gains
|
|
571
|
+
`abortedMessageStrategy` (`keep` / `drop`, factory `keep`) and `abortedToolRoundStrategy`
|
|
572
|
+
(`fill` / `drop`, factory `fill`); `createAgent()` reads `scope.config` directly when no arguments are passed.
|
|
573
|
+
Two new items under the configuration center's "Defaults" are enough to switch them; they are read only **when an
|
|
574
|
+
Agent is created**, so closing the configuration center makes the host rebuild the session automatically
|
|
575
|
+
(`WizardCloseResult.sessionOptionsChanged`), with no need to type `/reload` by hand.
|
|
576
|
+
|
|
577
|
+
### ♻️ Refactors
|
|
578
|
+
|
|
579
|
+
- **CLI no longer hardcodes `abortedMessageStrategy: "keep"`** — hardcoding overrides the user's choice. Configuration
|
|
580
|
+
is now the source of truth: whatever `config.json` says is used, and `agent-creator` only creates the Agent.
|
|
581
|
+
- **Config migration backfills the strategy fields** (SDK side) — missing or invalid values are always written back as
|
|
582
|
+
the factory default and persisted once, so no client needs its own `?? "keep"` fallback.
|
|
583
|
+
- **New `src/version-compare.ts`** — semver comparison (core segments / prerelease identifiers / build ignored),
|
|
584
|
+
used by `/update` to pick the newest among multiple dist-tags; no `semver` dependency is pulled in for it.
|
|
585
|
+
|
|
586
|
+
### ✅ Tests
|
|
587
|
+
|
|
588
|
+
- Full run: **49 files / 649 cases passed** (626 cases at alpha.23).
|
|
589
|
+
- New: `version-compare.test.ts` 10 cases, `chat-commands-update.test.ts` 6 cases (tag selection, already newest so
|
|
590
|
+
no install, install failure, npm missing, dist-tags unavailable), `messageToLines` strikethrough 3 cases,
|
|
591
|
+
strategy setters / two wizard screens / host reload 4 cases.
|
|
592
|
+
- End-to-end check (temporary `AI_ZEN_DIR`): factory default `config: keep fill → agent: keep fill`; after switching
|
|
593
|
+
to drop and rebuilding, `config: drop drop → agent: drop drop`.
|
|
594
|
+
|
|
595
|
+
### 🔧 Dependencies
|
|
596
|
+
|
|
597
|
+
- `@ai-zen/agents-sdk` `1.0.0-alpha.21` → `1.0.0-alpha.22`.
|
|
598
|
+
|
|
599
|
+
## [1.0.0-alpha.23] - 2026-10-28
|
|
600
|
+
|
|
601
|
+
> Sub-agents went from "one line in the notice bar saying it is running" to a **switchable tab row**: main and
|
|
602
|
+
> sub-agent messages are shown in separate columns, with execution status inlined on the labels.
|
|
603
|
+
|
|
604
|
+
### ✨ New features
|
|
605
|
+
|
|
606
|
+
- **Main/sub-agent tab row** — when a sub-agent appears in the conversation, a tab row appears below the message
|
|
607
|
+
list, `MAIN AGENT ◈SUB AGENT1 ◈SUB AGENT2`, with the current item highlighted by background color; after
|
|
608
|
+
switching with `←` / `→`, the message list is replaced by that Agent's own conversation.
|
|
609
|
+
- **Data source**: core's `onSubAgentStart` hook hands over the sub-agent instance; `AgentTool` /
|
|
610
|
+
`AgentToolLazy` **do not destroy** the instance after delegation ends, so `messages` is always readable — keeping
|
|
611
|
+
a reference is enough to review the sub-agent's whole process (reasoning, tool calls, multi-round inference),
|
|
612
|
+
not just the final result.
|
|
613
|
+
- **Lifecycle aligned with the inner loop**: a sub-agent is a product of a tool call inside the inner loop; when
|
|
614
|
+
this round's inner loop ends entirely (`send` wraps up; exceptions / aborts go through `finally` and clear as
|
|
615
|
+
well) the whole tab row is emptied and the display index resets to zero.
|
|
616
|
+
- **Inline execution status**: `◈` + color (running blue, done green, failed red); the running blue dot blinks
|
|
617
|
+
between bright and dim every 500ms. It **no longer** pushes "sub-task running / done" into the notice bar —
|
|
618
|
+
that would add a row out of nowhere and make the whole frame height jump.
|
|
619
|
+
- **`←` / `→` routed by state**: switches tabs when the input line is empty (cursor at 0, where left/right have
|
|
620
|
+
nothing to do) or while generating (the input line has been replaced by the spinner); when the input line has
|
|
621
|
+
content the keys still move the cursor, sacrificing no editing capability.
|
|
622
|
+
- Sending a message switches back to the main Agent view automatically (otherwise the user cannot see their own
|
|
623
|
+
question).
|
|
624
|
+
|
|
625
|
+
### 🐛 Fixes
|
|
626
|
+
|
|
627
|
+
- **Sub-agents left over after `/new` and `/reload`** — those two operations only swap `agent` and do not rebuild
|
|
628
|
+
the `ChatSession` instance, so the sub-agent view does not disappear on its own; it is now cleared explicitly on
|
|
629
|
+
`reset` / `reload` / `migrate`.
|
|
630
|
+
- **View id and display index mixed up** — the index resets every round; if the id reset too, next round's `sub-1`
|
|
631
|
+
would share a name with the previous round's, and since selection is matched by id, a leftover selected id would
|
|
632
|
+
"latch onto" a newly appeared sub-agent. They are now two counters: the id increments forever, and only the
|
|
633
|
+
display index resets.
|
|
634
|
+
|
|
635
|
+
### ♻️ Refactors
|
|
636
|
+
|
|
637
|
+
- **Fixed the outdated comment in `sub-agent-guard-plugin`** — the original comment claimed `call_skill_sub_agent`
|
|
638
|
+
"does not dispatch onSubAgentStart, not covered yet", but after skill sub-agents were unified onto `AgentToolLazy`
|
|
639
|
+
it in fact is covered (core has only two delegation paths, both dispatch the hook). That wrong comment directly
|
|
640
|
+
misled the assessment of the covered surface early in this implementation.
|
|
641
|
+
|
|
642
|
+
### ✅ Tests
|
|
643
|
+
|
|
644
|
+
- Full run: **47 files / 626 cases passed** (604 cases when alpha.22 was released).
|
|
645
|
+
- New guards: tab entry order and 1-space padding, the `◈` symbol and the three-color mapping, still a single line
|
|
646
|
+
when over-wide, sequential numbering, switching to `done` at the end, switching to `failed` on error without being
|
|
647
|
+
overwritten by `end`, progress kept out of the notice bar, content present during execution but cleared after
|
|
648
|
+
wrapping up, cleared on `reset` / `reload` too, `←`/`→` switching the data source and not switching when the input
|
|
649
|
+
is non-empty, layout height and IME offset.
|
|
650
|
+
- Break checks: removing `agentTabLines` from `computeChatLayout` → layout case red
|
|
651
|
+
(`expected 4 to be 5`); making a command description longer → width guard red (`expected 62 to be <= 36`).
|
|
652
|
+
|
|
653
|
+
## [1.0.0-alpha.22] - 2026-10-28
|
|
654
|
+
|
|
655
|
+
> This release is the result of a **full code review plus root-cause fixes**: first a complete review of all
|
|
656
|
+
> non-test code in `src/` (30 items P0~P3, 3 of them verified by experiment to be non-issues and left unchanged),
|
|
657
|
+
> then each item landed on the principle "trace the root cause if you can, no defensive fallbacks / no swallowed exceptions / no redundant validation", and finally Ctrl+C abort for handoff was added.
|
|
658
|
+
|
|
659
|
+
### 🐛 Fixes
|
|
660
|
+
|
|
661
|
+
- **Line wrapping measures width by grapheme cluster, fixing emoji blowing up the whole frame layout (P0)** — `toCells` / `wrapSpans` / `clipSpans` / `toChars` / `segmentsWidth` used to walk the text by **code point**: a VS16 sequence (`✌️` = `U+270C U+FE0F`) was counted as 1 column although it occupies 2 → the line got too wide → Ink wrapped a second time while rendering → the actual frame was one row taller than `computeChatLayout` had budgeted → **the input line was pushed off screen**; ZWJ families (`👨👩👧`) were counted as 6 columns although they occupy 2 → a wrap every two clusters. All of it now goes through `graphemes()` + `clusterWidth()`, the same ruler Ink lays out with.
|
|
662
|
+
- **Threshold writes no longer replace the whole object** — `updateModel` is a shallow merge, and callers used to construct `{ handoff, guard }` directly, which wiped every other field in `tokenThresholds` (compression thresholds the SDK added later, and so on). New `mergeTokenThresholds()`; all threshold writes go through it.
|
|
663
|
+
- **Blank screen after `/migrate`** — migration uses the `prune` strategy to cut `messages` down to 1~2, but `transcriptFrom` stayed at its old value, so `messages.slice(transcriptFrom)` was always empty: the migration succeeded and the context had content, yet the screen showed nothing. The transcript display origin is now reset after a successful migration (the same approach as `/new`).
|
|
664
|
+
- **Notice bar kept only the last entry** — the subscriber side is a single-value state, so a batch of summaries / staged progress overwrote each other one by one. It is now a queue keeping the most recent 4 (`MAX_NOTICE_HISTORY`), and a batch of summaries is merged into **one multi-line** entry, so everything is shown while occupying a single slot; layout decisions (wrapping / yielding when there are several / truncation within an entry) moved into `notice.ts` as pure logic, plus a new independently renderable `NoticeBar`.
|
|
665
|
+
- **`ai hook --help` exits with a non-zero code** — "wanted to see usage" and "wrote the wrong sub-action" used to be conflated. Now `ai hook` / `ai hook --help` print normally and exit 0; `ai hook boom` reports an error and exits 1. The bare-word forms `ai help` / `ai version` are equivalent to the corresponding options, but **only when the word is the sole argument** (`ai help me refactor this` is still a question).
|
|
666
|
+
- **`ai hook install` failed to clean up duplicate blocks** — an rc file may hold several hook blocks (an old version and a new one each wrote once), and the old implementation looked only at the first, so "the first block happens to be the newest → judged installed → the old blocks behind it stay forever". It now takes all blocks and counts as installed only when there is exactly one block and its content matches.
|
|
667
|
+
- **Conversation persistence was not an atomic write** — the SDK's `EntityRepository.write()` overwrites directly, so a process killed mid-write (Ctrl+C / OOM / power loss) leaves half a JSON, `read()` fails to parse and returns null, and **the whole history is lost** (conversation files can reach several MB, so the write window is not small). It now writes `<id>.json.tmp` → `rename`, so any read at any moment sees either the old file or a complete new one.
|
|
668
|
+
- **Ctrl+C could not abort a session handoff** — two root causes: the standalone model request in `summary` mode had no cancellation point at all; `self` mode could be interrupted by `agent.abort()`, but abort does not throw, so after the abort it was misjudged as "the model did not write a document", and the code **retried one round, then downgraded to summary and ran another round** — the user thought they had cancelled while two more full-input rounds burned. Handoff now accepts an `AbortSignal`: cancellation throws `HandoffAbortedError`, **no downgrade and no retry**, the half-written document is cleaned up, and `migrate` leaves the conversation history untouched; in the TUI, Ctrl+C first goes through the handoff's cancellation path and reports "cancelled" instead of an error. (Depends on the same-named change in `@ai-zen/agents-sdk`.)
|
|
669
|
+
- **Notice bar text shifted right by 2 columns** — the indent was applied twice: one layer from `NoticeBar`'s `paddingX={1}`, another layer from `notice.ts` hard-concatenating two spaces, so the text sat 2 columns further right than the separator and the `❯` input row on the same screen; it was most glaring when a migration pushed 4 notices at once. Horizontal positioning is now owned solely by the container and the logic layer only wraps. The `· ` / `✖ ` prefixes were dropped along the way — tone is already expressed by `tone` coloring (error goes red), so the symbols were pure placeholders; every line of a multi-line notice now starts at column 0. The per-item summary of configuration updates is now indented by one CJK character width (2 columns) instead of carrying a bullet.
|
|
670
|
+
|
|
671
|
+
### ✨ New features
|
|
672
|
+
|
|
673
|
+
- **`/load` list shows conversation summaries** — a conversation id is a timestamp (`chat_2026_10_28_00_56_34`), so listing them directly as labels made it impossible to tell which was which. The first line of the first user message is now taken as the label. Implementation-wise it only `stat`s first, and after sorting parses content **only for the few entries about to be displayed**, so `/load` is not slowed down even when conversations pile up into the hundreds.
|
|
674
|
+
|
|
675
|
+
### ♻️ Refactors
|
|
676
|
+
|
|
677
|
+
- **`chat.tsx` split into five hooks: 1085 → 830 lines** — only conversation lifecycle and render assembly are left; the rest became 5 unit-testable hooks: `use-chat-viewport` (viewport and scroll arithmetic, every action going through `clampViewTop()`), `use-overlays` (resolve lifecycle of confirm boxes / selectors, with closing and settling done in the same functional update), `use-wizard-host` (wizard host, dependency injection), `use-input-editing` (text / cursor / history / kill ring / undo / stash), `common-keys` (keybindings shared by the two `useInput` handlers + double Ctrl+D to quit).
|
|
678
|
+
- **Slash commands are table-driven** — the 557-line `switch` became `HANDLERS: Record<CommandName, CommandHandler>` + 18 handlers: "added a command but forgot the handler" changed from silently falling through to "unknown command" at runtime into a **compile error**; command-name parsing lives only in the registry (`parseCommandInput` / `parseCommandArgs` share one tokenizer), and `blocksInput` became command metadata instead of a host whitelist.
|
|
679
|
+
- **Wizard field factory de-noised** — the `commit` / `toggle` signatures were widened to `string | null | Promise<string | null>`, removing 23 `Promise.resolve(...)` noise sites; `nameField()` / `endpointIdField()` / `customPatch()` and friends absorbed 8 duplicated chunks; `DetailFieldKind`'s `"map"` was renamed to `"open"` (the semantics were always "Enter opens a sub-screen").
|
|
680
|
+
- **Conversation persistence converged into a single entry point** — "read the old record for `createdAt` → write → update the pointer" used to be written out in 5 places and had already drifted (`saveConversation()` missing `cwd`, stdio's `--save` not updating the pointer, and a deprecated `modelId` still being written). Everything converges on `persistConversation()`, and the only remaining difference is one explicit switch: whether to update the pointer.
|
|
681
|
+
- **Initialization and singletons cache promises** — when `getScope()` / `ensureConfigDir()` cached instances, there was an `await` between `if (x) return x` and the assignment, so concurrent calls fell through together (two Scopes built → MCP connected twice, tool discovery done twice). Caching the promise lets concurrent calls share one initialization; failures are not cached.
|
|
682
|
+
- **Dead code and unreachable fallbacks removed** — the dead fields `ConversationContext.running|input|shouldSend`, the never-enabled i18n plural machinery (`pluralRules` + `key_one/_other`), the unreachable truncation fallback at the end of `noticeLines()` (84 combinations enumerated to prove it never runs), the `pick(direct, …)` parameter nobody ever passed, the zero-reference `scrollTo` export of `useChatViewport`, and `parseHandoffMode`, which every command ran to no effect; `cwd` / `previewLimit` went from "optional + internal fallback" to required, with fallbacks kept only at the real boundary of the SDK's optional fields.
|
|
683
|
+
- **Command descriptions trimmed** — the slash menu and `/help` are one line per command, and an over-long description fills the whole line. Rewritten on the principle "say what it does, not how it is implemented": internal concepts dropped (`scope`, writing config, rebuilding the session) along with redundant qualifiers, and parentheses keep only the consequences the user must expect (replaces the conversation / rebuilds / does not switch conversation). Longest Chinese 59 → **34**, English 73 → **49** (mean 19.3 / 33.8), and `/mcp` fell from the longest to mid-pack.
|
|
684
|
+
- **The four permission dimensions go on screen through dictionary entries** — `tools` / `skills` / `mcps` / `subagents` are keys in the configuration, and showing them directly would expose internal identifiers to the user, so they map to `perm.*` entries.
|
|
685
|
+
|
|
686
|
+
### ⚡ Performance
|
|
687
|
+
|
|
688
|
+
- **Fast path for `graphemes()`** — wrapping is a per-frame hot path, but for the vast majority of text (ASCII / CJK / code) every cluster is a single code point and `Intl.Segmenter` is unnecessary. One regex first decides "could multiple code points form one cluster" (astral / CRLF / ZWNJ·ZWJ / Hangul composing jamo / `\p{M}` combining marks), and only on a hit does it go to the segmenter. One frame of 60 lines of Chinese: **655 µs → 52 µs** (604 µs saved, 1% of the frame budget). The predicate prefers false positives over false negatives, and 6 new guard cases cover the various forms.
|
|
689
|
+
|
|
690
|
+
### 🔧 Dependencies
|
|
691
|
+
|
|
692
|
+
- Upgraded `@ai-zen/agents-sdk` to `1.0.0-alpha.21` (handoff cancellation).
|
|
693
|
+
|
|
694
|
+
### ✅ Tests
|
|
695
|
+
|
|
696
|
+
- Full run: **46 files / 604 cases passed** (36 files / 487 cases before the review; a net gain of 10 files / 117 guard cases).
|
|
697
|
+
- Notable additions: `use-chat-viewport` 7 cases, `use-overlays` 6 cases, `use-wizard-host` 9 cases, `use-input-editing` 13 cases, `common-keys` 10 cases, `chat.test.tsx` 7 integration guard cases, `lines.test.ts` 12 cases (grapheme-cluster wrapping), `notice-bar` 4 cases, `conversation-persist` 10 cases (including a spy on `fs.rename` pinning the atomic write).
|
|
698
|
+
- Break checks (each confirmed to turn the guard red): wrapping back to code points, `clampCursor` switched to `Array.from`, paging step switched to `budget`, the PgUp branch removed, `\p{M}` dropped, atomic write back to direct write, the history cursor reset removed from `clearDraft`.
|
|
699
|
+
|
|
700
|
+
## [1.0.0-alpha.21] - 2026-10-27
|
|
701
|
+
|
|
702
|
+
### ✨ New features
|
|
703
|
+
|
|
704
|
+
- **Status bar split into three information blocks, plus a generation rate** — the status bar used to be one priority-ordered stream of text, and when it wrapped on a narrow screen it cut units like `this round 1284 tok` in half while also making it impossible to tell which pieces of information belonged together.
|
|
705
|
+
- It is now three blocks: ① model · Agent · reasoning effort ② this round / cumulative token usage · generation rate ③ keybinding hints (at the bottom).
|
|
706
|
+
- **Wrapping is per block**: new `packSegments()` (`src/tui/status-segments.ts`) — if it fits, blocks sit side by side (joined by ` · `); if not, the **whole block** moves to the next line; only a single block longer than a whole line degrades to wrapping inside the block (prefer breaking at spaces, hard break for CJK, drop an orphan `·` at line start, trim trailing spaces). Line boundaries only ever fall on block boundaries.
|
|
707
|
+
- **Row count is deterministic**: `statusBarRows()` measures the actual row count in pure logic and hands it to `computeChatLayout({ statusBarLines })`; measurement and rendering are the same function, so a taller status bar on a narrow screen never pushes the input line off screen.
|
|
708
|
+
- Numbers and units are bound with NBSP (`this round␣1284␣tok`), so they are not split across two lines inside a block either.
|
|
709
|
+
- **Two measures for the generation rate, never mixed into one number**: while generating, `⚡ 47 tok/s` is an estimate (derived from output deltas, CJK 0.7 / character, other non-whitespace 0.25 / character, whitespace 0.1, over a sliding window of the last 2 seconds); after a round ends it switches to `⚡ avg 38 tok/s`, where the denominator is the first-char → last-char span and the numerator is the real `usage.completion_tokens` returned by the API (accumulated round by round for multi-round tool calls). A span < 400ms is not shown, so a burst on the first packet cannot inflate the rate into the hundreds.
|
|
710
|
+
- **New `/agent`: switch the main Agent quickly inside a conversation** — the selector lists every main Agent in `~/.ai-zen/agents/*.json`, marking the current one and the model it is bound to (switching Agent means switching the model with it).
|
|
711
|
+
- Supports `/agent <id>` for a direct switch; no argument opens the selector.
|
|
712
|
+
- A full-chain precheck runs before switching: Agent exists → bound to a model → the model is still in the configuration → the endpoint exists → the endpoint has a key. Ones that cannot run are marked "unavailable" with a reason, instead of switching and then being stuck on the startup error screen.
|
|
713
|
+
- Writes `config.defaultAgent` (the same field as `/config` → "Default Agent") and then rebuilds the session; **messages and context are kept**. The Agent definition itself is not modified (prompt / permissions / `modelParams` all untouched).
|
|
714
|
+
- **New `/mcp`: jump straight to the MCP management screen** — no separate editor is built; the existing MCP screen in the configuration center is opened directly (add / delete / edit / enable-disable / global⇄project scope switching all live on that screen). After the wizard closes it reuses the existing `mcpChanged` path to call `session.reload()` automatically, so new servers take effect on the spot.
|
|
715
|
+
- Default landing: if the project scope has servers, go to `project`, otherwise `global` (entering the project scope when it is empty shows only an empty list). `/mcp global`, `/mcp project` (abbreviable to `g` / `p`) can specify it.
|
|
716
|
+
- **New `/reload`: refresh the runtime environment** — `session.reload()` contains `resetScope()` (disconnect the old MCP connections, drop the config snapshot) → rebuild the Agent → rediscover tools / Skills / MCP. After hand-editing `mcp.json` / `skills/` / `tools/`, one command makes it take effect, **messages and context are kept**, and the CLI need not be restarted.
|
|
717
|
+
- **Keep the reasoning and content already output when aborting** — after stopping with `Ctrl+C` / `Esc`, the half sentence the model **already emitted no longer vanishes into thin air**. Previously abort only marked the in-flight message `Aborted`, while the history filter let only `completed` through, so the reasoning and answer still visible on screen disappeared entirely from the next request, and the model started over the next round as if it had amnesia.
|
|
718
|
+
- The root cause is upstream: core's `abort()` only sets a flag and `formatHistory()` only filters by status, and neither cares about "what has already been output". core `4.5.3` adds `abortedMessageStrategy` (`drop` / `keep`, default `drop`) and exports `reconcileAbortedMessages()`, which settles pure content messages **after** tool rounds are normalized.
|
|
719
|
+
- The CLI sets it explicitly to `keep`: an aborted assistant message that really produced output is restored to `completed` and enters the context. **Empty output is not kept** (to avoid empty messages polluting the context) and **messages with tool calls are not its business** (they still follow "whole round in, whole round out", otherwise the history would be illegal with "a call but no result").
|
|
720
|
+
- The SDK dependency is upgraded to `1.0.0-alpha.20` accordingly (`createAgent` gains a third parameter `CreateAgentOptions`, which also surfaces the previously unexposed `abortedToolRoundStrategy` / `abortedToolRoundReason`), and core goes to `4.5.3`.
|
|
721
|
+
- **UI internationalization (Chinese / English)** — the first launch detects the system language (`LC_ALL` → `LC_MESSAGES` → `LANG` → Node's `Intl` locale, **English as the fallback**), the result is written to `~/.ai-zen/cli/preferences.json`, and the preference wins from then on (changing the system language later does not let the UI language drift quietly). `/lang` switches at any time; `/lang zh` / `/lang en` set it directly.
|
|
722
|
+
- Dictionaries are compiled into the bundle (`src/i18n/{zh,en}.ts` plus the configuration center section `{zh,en}-wizard.ts`): flattened keys, `{n}` interpolation; switching language swaps one reference, with no disk re-read and no restart. A missing entry returns the key itself (no silent cross-language fallback, which would make Chinese pop up in the English UI). `t()` supports `_one` / `_other` plural variants (chosen by `Intl.PluralRules`), but `i18n/index.test.ts` requires the **Chinese and English key sets to be identical**, so existing entries use the `(s)` form uniformly and no variants are enabled.
|
|
723
|
+
- The language preference lives in the CLI's own `preferences.json` and is **not stuffed into the shared `config.json`** — that is the `AppConfig` contract shared by the CLI and Desktop, and anything stuffed in there would be dropped by the desktop app as an unknown field.
|
|
724
|
+
- Coverage is **the whole TUI**: the main conversation screen (three status bar blocks, input placeholder and hints, notices, reasoning effort labels, generating hints), the command layer (`/help`, the slash menu, selectors and feedback of every command), every screen of the configuration center (endpoints / models / image models / MCP / Agent definitions / permissions / defaults / tool output limits / creation wizard / KV and multi-line text editors), the handoff mode selector and confirm boxes, the startup screen and the startup error screen, plus the summaries returned by business modules (the switch results of `/model` `/agent` `/think`, handoff progress and downgrade hints).
|
|
725
|
+
- `COMMAND_REGISTRY` stores **i18n keys rather than display text** — it is a pure data module loaded before language initialization, so storing text would freeze "the language at load time"; rendering calls `getCommandHints()` to fetch it on the spot. The startup screen's `TAGLINE` constant became a `tagline()` function for the same reason.
|
|
726
|
+
- **Pipeline mode does not do i18n**: all output and errors of `ai <prompt>` / piped input are **English only** (it is for scripts and should not follow a person's UI language), so the stdio path **does not initialize the language** and its behavior is fully decoupled from `preferences.json`.
|
|
727
|
+
- **`ai --help` and `ai hook install / uninstall` go through i18n**: both print outside the TUI, so each branch calls `initLocale()` before fetching entries; the module-level `HELP_TEXT` constant became `helpText()` assembled on demand (a constant would freeze the language at load time).
|
|
728
|
+
- **Conversation id renamed**: the old localized `dialogue_<date and time with Chinese units>` form → `chat_2026_10_28_00_56_34` (English prefix + all-underscore timestamp, identical in Chinese and English, filename-safe). It is also the file name, and letting it follow the language would mix old and new history files, so the timestamp is produced by the new `formatFileStamp()`; **display** times, by contrast, now follow the language via `formatShortTime()`. Pipeline mode's `--save` no longer assembles a name itself and calls `generateConversationId()` instead, removing the duplicated assembly in both places.
|
|
729
|
+
- **The "working directory" note injected into history switched to English with an explicit marker**: `[system-injected: working directory changed — not written by the user]` — flipping through an archive or a handoff document makes it obvious at a glance which content the user did not type; `stripCwdNote()` recognizes both generations of the format, so old archives do not render this note on screen.
|
|
730
|
+
- The `task` parameter description written into the tool schema when creating a sub-agent also comes from the dictionary (Chinese `Complete task description` / English `Full task description`) — the only exception where "protocol content is translated too"; the cost is that the same definition created under different UI languages yields different schema text.
|
|
731
|
+
- The only injected content left untranslated is the working-directory note above; the three code-block marker strings `hook.ts` writes into the rc file (such as `# === ai hook (开始) ===`) also stay as they are — translated, the old block could never be found again.
|
|
732
|
+
- Time formats switch dayjs locale and pattern along with the language: Chinese renders a localized month-day form plus time with a "yesterday" prefix, English `Jul 1, 14:30` / `Yesterday 14:30`.
|
|
733
|
+
- Two hidden mines were cleared along the way: the status bar used to decide whether reasoning effort is dimmed by **display text** (`effort === "off"`), which never held in the English UI, and now uses `effortDimmed()` based on the level's semantics; the input area placeholder used to be hardcoded inside `inputLines()` (while the render layer went through `t()`), so in the English UI the measured input row height came out one line short and dragged the hardware cursor's y offset along — both now share one source.
|
|
734
|
+
|
|
735
|
+
### 🔧 Behavior changes
|
|
736
|
+
|
|
737
|
+
- **The message row's "thinking…" split into six states: connecting / queued / thinking / calling a tool / waiting for a tool result / streaming** — that row used to have only the single word "thinking…", but a model actually goes through several stages within one round, and summing them up with one word is **plain wrong** most of the time: it is not thinking while waiting for the first token, and while a tool runs the model is not running at all.
|
|
738
|
+
- The predicate uses only **the shape of events and deltas** (`src/tui/phase.ts`, pure logic and unit-testable): `connecting` = from request dispatch to response headers; `queued` = after `open` and before the first content chunk; `thinking` = `reasoning_content` received; `tool-calling` = `tool_calls` / `function_call` received (with a tool name, reusing the name from the first delta when later chunks carry only argument fragments); `tool-waiting` = `parsed` and this round has tool calls; `streaming` = `content` received. When several appear in one chunk the priority is tool_calls > content > reasoning.
|
|
739
|
+
- The "connecting" stage is provided by the new `PhasePlugin` (`chat-session.ts`): the SDK's `open` event is emitted **after the `await` of `chat.completions.create()` returns** and only means the response headers arrived; the DNS / TCP / TLS / server acceptance before that is entirely silent.
|
|
740
|
+
- **Sub-agent output does not advance the main stage**: during delegation the main Agent is waiting for a tool result, and counting the sub-agent's deltas into the main stage would make the UI jump to "streaming", covering exactly the sentence the user most needs to know. Sub-agent chunks still count toward the rate and still trigger a redraw.
|
|
741
|
+
- The spinner row got `truncate`: while busy the input area height is fixed at 1 row by `computeChatLayout` (`inputHeight = 1`), and a wrapped label would push the input line off screen.
|
|
742
|
+
- **"⏳ Generating" removed from the status bar** — what this round is doing is already explained stage by stage by the message row spinner, and another generic "generating" in the status bar only duplicates it and eats width (on a narrow screen it also squeezes the keybinding hints onto the next line).
|
|
743
|
+
|
|
744
|
+
- **Shell hook switched to entering the TUI (the earlier pipeline mode was an implementation error)** — the hook is installed on `command_not_found_handle` / `command_not_found_handler`, and the triggering situation is "a person sitting at an interactive terminal typed a command wrong", yet the function body was `ai "$@"`, while the rule in `decideMode()` is "positional arguments → stdio", so the hook session inevitably fell into pipeline mode: it spat some text to stdout and exited, **with no history and no follow-up questions**. The classification table at the top of `mode.ts` merged "script invocation" and "manually triggered by a person" into one predicate, so this was not something a patch could fix — the classification itself was wrong.
|
|
745
|
+
- The function body is now `ai --tui --new "$@"`: the new `--tui` flag explicitly overrides the "has arguments → stdio" decision, enters the TUI and **sends the original command automatically as the first message** (not pre-filled into the input line — the semantics of the hook are "this command cannot run, handle it for me", and making the user press Enter again is pointless).
|
|
746
|
+
- The first message is **forced to be sent as an ordinary user message** and skips slash-command parsing: the mistyped command could be something like `/opt/bin/foo` starting with `/`, which would otherwise be treated as a CLI command and reported as "unknown command". `runSubmit()` gained an `asMessage` option for this.
|
|
747
|
+
- In non-TTY environments (scripts / pipes) `--tui` reports an error and exits (exit code 2, English message) and does **not** degrade into pipeline mode — otherwise a whole screen of UI content would be written into the pipe.
|
|
748
|
+
- The first message is sent only on the first mount: remounts caused by switching models / `/lang` do not resend it.
|
|
749
|
+
- **Hook sessions always start a new conversation**: the function body has an extra `--new` (`ai --tui --new "$@"`). Otherwise one mistyped command would be appended to the long conversation that already existed in that directory, scrambling both the context and the conversation name. `--new` is a general flag and works the same when used manually: `ai --new` skips the `last-session.json` pointer and starts a new conversation; it **only affects the TUI path** and is silently ignored on the stdio path (a stdio invocation is a one-shot conversation anyway, so there is nothing to continue).
|
|
750
|
+
- **`ai hook install` now upgrades itself**: previously it printed "installed" and returned as soon as `MARKER_START` existed in the rc file, so after the function body changed (say, `--new` added this time) existing users could never upgrade. It now normalizes the installed blocks and the current version and compares them (blank lines removed, every line trimmed), overwrites in place when they differ, and reports "updated to the latest version" (new entry `hook.updated`).
|
|
751
|
+
|
|
752
|
+
- **`/migration` is no longer an alias of `/migrate`** — only `/migrate` remains, and `/migration` now reports "unknown command". The registry, the command executor, the `blocksInput` decision and the four documents were updated together.
|
|
753
|
+
- **SDK global logs go to `~/.ai-zen/logs/sdk.log` by default** — previously they were **discarded** by default, and only `AIZ_DEBUG_SDK=1` wrote them to `/tmp`. But the SDK's `warn` / `error` carry critical diagnostics such as "MCP cannot connect" and "Skill failed to parse", and discarding them by default meant failures left no trace. They are now written by default (rotated to `sdk.log.1` after a 2MB cap per file), `AIZ_DEBUG_SDK=0` turns it off completely, and `AIZ_SDK_LOG_FILE` changes the path. The UI still shows not a single character of it.
|
|
754
|
+
|
|
755
|
+
### 🐛 Fixes
|
|
756
|
+
|
|
757
|
+
- **Fixed the IME candidate window being "sometimes mispositioned"** — the hardware cursor position used to be computed by `displayWidth()`. But width is a trap: Ink lays out with `string-width`, and the hand-written table disagreed with it on several characters (`⚡` `✅` `✨` `⭐` were counted as 1 column against Ink's 2; the ZWJ emoji `👨👩👧` as 8 columns against 2; the keycap `1️⃣` as 3 against 2), so as soon as such a character appeared in the input, x drifted. It is now **measured after rendering**:
|
|
758
|
+
- The character at the cursor is wrapped in its own `<Box ref>` (inline nested `<Text>` fragments have no node in yoga and `measureElement()` returns `null` for them, verified by experiment), and Ink 7's `measureElement()` reads the absolute coordinates it is imprinted at within the frame — width is computed by the renderer itself and no longer passes through the hand-written model.
|
|
759
|
+
- After splitting rows, the previous whole-row `truncate` fallback would be lost: once the content is too wide (e.g. the width model counts `⚡` as 1 column) the row is pushed into two rows, the whole frame grows a row and the bottom is pushed off screen. The solution hands the overflow to the **truncatable adjacent segments** — `before` / `after` use `flexShrink={1} minWidth={0}` + `wrap="truncate"` and the cursor block uses `flexShrink={0}`; measured with a 40-column container and a 60-column `before`, it was still **one row**.
|
|
760
|
+
- Measurement can only happen after rendering (`useEffect`), while Ink pushes `setCursorPosition` only in the commit phase (`useInsertionEffect`), so there is a one-commit delay: the first render puts up one frame with the old hand-computed value, and the next frame is corrected by the measured value (one extra render pass, which Ink throttles itself).
|
|
761
|
+
- The placeholder row (nothing typed yet) does not go through measurement and still uses the fixed position after the prefix.
|
|
762
|
+
- **The placeholder state no longer draws a "fake cursor"** — when the input line is empty it shows the gray hint "Type a message, / for commands…", and a `▏` used to be drawn at the end as well. But the hardware cursor already sits at the start of the hint (`x = prefix width`), the terminal displays it on its own, and drawing another block means two cursors on screen — and positioned at the end of the hint, which is misleading. The placeholder state now keeps only the hint text.
|
|
763
|
+
- **Fixed "the inverse cursor disappears / the real cursor is misaligned after typing emoji"** — two root causes, both treating a "character" as a code point:
|
|
764
|
+
- **The cursor could stop in the middle of a grapheme cluster**: `✌️` is `U+270C U+FE0F`, **two code points, one grapheme cluster** (`👨👩👧` five, `🇨🇳` two regional indicators). When splitting by code point the cursor could stop between `✌` and `U+FE0F`, so the inverse display became a string of **variation selectors** — in Ink's output the inverse segment disappears entirely (measured: with `at=U+FE0F` the frame carries neither the inverse escape nor any character). Backspace also deleted only half an emoji and left an orphan `U+FE0F`. All cursor arithmetic (wrapping, moving, deleting, inserting, undoing) now uses `Intl.Segmenter`'s **grapheme clusters** as the unit (`graphemes()`, built into Node with no dependency).
|
|
765
|
+
- **The width table switched to Ink's own ruler**: `displayWidth()` no longer uses the hand-written `isWide` range table but calls **`string-width`** directly (that is what Ink lays out with, see `ink/build/output.js`); per-cluster widths are cached, so the wrapping hot path costs almost nothing. From now on wrapping, status bar packing, cursor column numbers and the renderer share one source — and the status bar's "reserve 1 extra column" degraded to a pure safety margin.
|
|
766
|
+
- **Cursor unit changed to grapheme clusters**: `✌️` is `U+270C U+FE0F`, **two code points, one grapheme cluster** (`👨👩👧` five, `🇨🇳` two regional indicators). When splitting by code point the cursor could stop between `✌` and `U+FE0F`, so the inverse display became a string of **variation selectors** — in Ink's output the inverse segment disappears entirely (measured: with `at=U+FE0F` the frame carries neither the inverse escape nor any character). Backspace also deleted only half an emoji and left an orphan `U+FE0F`. All cursor arithmetic (wrapping, moving, deleting, inserting, undoing) now uses `Intl.Segmenter`'s **grapheme clusters** as the unit (`graphemes()`, built into Node with no dependency).
|
|
767
|
+
- **New direct dependency** `string-width@^8.3.0` (pure ESM, already in Ink's dependency tree, no size change).
|
|
768
|
+
- **Notice bar rows were not counted in the cursor y** — the render order is "conversation area → notice bar → slash menu → separator → input row", and the y formula was missing the notice bar. So whenever a notice appeared the hardware cursor floated up by that many rows; `/help` pushes up to 8 rows of notices at once, and the candidate window could float into the conversation area. `noticeCount` is now included (kept as the fallback algorithm while the measurement is not ready).
|
|
769
|
+
- **Fixed SDK logs "taken over and still smearing the UI"** — when loading MCP, `[load_mcp]` along with the whole tools / resources listing smashed straight outside the render area. The reason was that 4 SDK modules run `const log = getLogger()` **at module top level**: the moment the module is evaluated the `console.log` function reference is frozen into a constant, and `setLogger()` only swaps an internal variable afterwards, unable to change the reference already captured; ESM static imports also guarantee those modules are evaluated before `runTui()` — so no matter how the CLI took over, it leaked.
|
|
770
|
+
- Fixed on the SDK side (not with a CLI-side workaround that lies to console): all 4 sites now **take the logger at runtime** (`getLogger().info(...)` and so on); the `skillTools` site was dead code that was never called and was removed along with its import. All 10 log calls inside the SDK now go through the single `getLogger()` exit; no console bypass remains.
|
|
771
|
+
|
|
772
|
+
### 📝 Docs
|
|
773
|
+
|
|
774
|
+
- README (zh / en) and `docs/{zh,en}/getting-started.md`: the command table gained three rows, `/agent`, `/reload`, `/mcp`.
|
|
775
|
+
- README (zh / en) and `docs/{zh,en}/index.md`: the status bar description changed from "one priority-ordered stream of text" to "three information blocks + per-block wrapping".
|
|
776
|
+
- `docs/{zh,en}/mcp.md`: the "Management" section gained the `/mcp` direct entry and the `/reload` refresh; `docs/{zh,en}/migration.md` dropped the `/migration` alias.
|
|
777
|
+
- README (zh / en) gained a "UI language" section: detection priority, `preferences.json` and `/lang`, coverage, and the three kinds of content deliberately left untranslated.
|
|
778
|
+
- Version numbers in `docs/manifest.json` and the READMEs updated to `1.0.0-alpha.21`.
|
|
779
|
+
- README (zh / en): "Environment variables" gained `AIZ_DEBUG_SDK=0` / `AIZ_SDK_LOG_FILE`, "Filesystem layout" gained `~/.ai-zen/logs/`, and a note was added under the keybinding table saying reasoning and content already output are kept after an abort.
|
|
780
|
+
- README (zh / en) and `docs/{zh,en}/getting-started.md`: the command table gained `/lang`; the directory trees in "Filesystem layout" and `docs/{zh,en}/configuration.md` gained `~/.ai-zen/cli/preferences.json` (CLI-local preferences).
|
|
781
|
+
|
|
782
|
+
### ⚠️ Known trade-offs
|
|
783
|
+
|
|
784
|
+
- **Emoji render width may still disagree with the terminal, and that is the terminal layer's business**: the code side now shares one source with the renderer (`displayWidth` = the `string-width` Ink uses, cursor position measured with `measureElement`), but **the terminal's own column-width judgment for emoji / East Asian Ambiguous characters** may still differ from the Unicode tables (some terminals render `✌️` as 1 column instead of 2, and offer no query interface). In that case the relative position of the whole line is already decided by the terminal, and the cursor can only follow Ink's model. The emoji problems at the input / delete / cursor-unit level are all fixed in this release.
|
|
785
|
+
- **The cursor is "terminal cursor + inverse character" side by side, deliberately**: the character at the cursor is still inverted into a block with `<Text inverse>`, and the source of truth for position is the measured hardware cursor. Visually the two stack into a slightly heavy block — accept the ghosting, because when either layer fails the other can still report the position (some terminals / multiplexers do not show the hardware cursor, or the inverse display is gone when Ink unmounts). Do not delete the inverse as duplicate rendering later.
|
|
786
|
+
- **Blocks are not split ⇒ row count varies with width**: at 80 columns the three blocks each take a row (3 rows), at 120 columns identity and usage can sit on one row (2 rows in total). The old "one stream" could be squeezed into 2 rows, at the cost of splitting `this round / cumulative` across two rows — information integrity wins.
|
|
787
|
+
- **The rate while generating is an estimate**: the SDK only provides `usage` in the **last chunk** of each request (`stream_options.include_usage`) and offers no per-token usage; mixing estimates and exact values into one number is the misleading option, hence the two forms "real-time estimate" and "round average".
|
|
788
|
+
- **`/mcp` only covers the configuration layer**: whether a server is currently reachable and how many tools it exposes is plugin-private state in the SDK (`McpScopePlugin`'s `mcpManager`) with no public API, so the UI does not show connection status.
|
|
789
|
+
- **`/reload` has a gap of a second or two**: it rebuilds the Agent and reconnects MCP, so with many servers there is a perceptible delay; messages and context are unaffected during it.
|
|
790
|
+
|
|
791
|
+
### ✅ Tests
|
|
792
|
+
|
|
793
|
+
- Full run: **33 files / 440 cases passed**, `npx tsc --noEmit -p tsconfig.test.json` clean.
|
|
794
|
+
- New `src/i18n/index.test.ts` (14 cases: language detection priority / `C`·`POSIX` skipped / English fallback / interpolation / missing entry falls back to the key / Chinese and English entries correspond one to one) and `src/i18n/preferences.test.ts` (9 cases: tolerance of a missing, corrupt or invalid file, atomic write, `initLocale` detecting only once and persisting); `reasoning-effort.test.ts` gained 5 cases for "dimming does not rely on display text".
|
|
795
|
+
- pty acceptance on a real terminal: `LANG=en_US.UTF-8` first launch → English, with `en` persisted; `/lang` to Chinese → status bar / input line / notices all follow, and **Chinese is kept after a restart** (the preference beats the system language); `/lang en` back to English works.
|
|
796
|
+
- pty acceptance (stage 2): in English the slash menu shows `❯ /help Show this help` and `/help` outputs an all-English list; after `/lang zh` the slash menu and `/help` switch back to Chinese as a whole.
|
|
797
|
+
- pty acceptance (stages 3 / 4, isolated `AI_ZEN_DIR` + `LANG=en_US.UTF-8`): first launch detected as English and persisted; the startup screen shows `The AI collaboration desk in your terminal`; the main screen shows `Type a message, / for commands…` and `thinking default · total 0 tok`; the configuration center shows `◆ Configuration` with `+ New model — marked as custom automatically` / `openai · factory-managed · vision`; the model detail screen shows `◆ Model · GPT-6 Astra` / `factory-managed (editing switches it to custom)` / `Delete model`; the new-model and new-endpoint wizard screens are entirely English. After setting `preferences.json` back to `zh`, **a restart under `LANG=en_US.UTF-8` still shows the Chinese UI** (the preference beats the system language), and the Chinese text in the configuration center and model list matches the pre-rework state.
|
|
798
|
+
- **Tests asserting Chinese always pin the language explicitly**: `setLocale("zh")` added in `screens.test.tsx`, `config-wizard.test.tsx`, `reasoning-effort.test.ts`, `registry.test.ts`, `agent-prompt.test.tsx`, `handoff-choice.test.ts`, `config-editor.test.ts`, `agent-switch.test.ts`, `model-switch.test.ts`, `handoff-service.test.ts`, `auto-migrate-confirm.test.ts` — the default language is `en`, and without pinning they would go red on CI for no apparent reason.
|
|
799
|
+
- `mode.test.ts` gained 6 cases for `--tui`: with arguments it enters the TUI and the arguments become the first message; without arguments it behaves the same as without `--tui`; a command starting with `/` is not taken as an option / sub-command; `--tui --help` gives help priority; non-TTY is rejected rather than degraded; a `--tui` after `--` is an ordinary argument.
|
|
800
|
+
- `mode.test.ts` gained 8 cases for `--new`: used alone it enters the TUI with `newSession: true`; `--tui --new <command>` carries both the first message and the new session; the order relative to `--tui` does not matter; with arguments it falls to stdio and is ignored; in non-TTY it falls to stdio and is ignored; `--tui --new` in non-TTY is still rejected rather than degraded; `--new --help` gives help priority; a `--new` after `--` is an ordinary argument.
|
|
801
|
+
- New `src/hook.test.ts` (9 cases, all run under a temporary HOME, **never touching the real `~/.bashrc`**): the written function body contains `ai --tui --new "$@"` and exactly one block, existing rc content is preserved, repeated installation is idempotent, **an old function body already installed (`ai --tui "$@"`) is upgraded in place**, historical aiz blocks are migrated, zsh uses `command_not_found_handler` and writes to `.zshrc`; uninstalling removes the block and keeps the rest, leaves the file alone when nothing is installed, and does not error when the rc file does not exist.
|
|
802
|
+
- pty acceptance (hook path): `ai --tui gti status` enters the TUI and automatically sends `gti status` as a user message (the UI shows `❯ you / gti status`), and after the model errors out (a deliberate local fake endpoint) the UI survives and further questions are possible; `ai --tui /opt/bin/nonexistent --flag` is likewise sent as an **ordinary message** and is not eaten by slash-command parsing; `ai --tui` in non-TTY exits with code 2; the function body written to `~/.bashrc` is `ai --tui --new "$@"` and passes `bash -n` syntax checking.
|
|
803
|
+
- pty acceptance (`--new`, isolated `AI_ZEN_DIR` + a preset `last-session.json` pointing at an old conversation): `ai` continues it and shows the old message `MARKER-OLD-QUESTION`; `ai --new` lands in an **empty conversation**; `ai --tui --new gti status` starts empty and sends the first message automatically; `--help` lists `--new` in both the Chinese and English UI.
|
|
804
|
+
- Full run after this change: **36 files / 487 cases passed**, `npx tsc --noEmit` and `-p tsconfig.test.json` both clean.
|
|
805
|
+
- New `src/tui/phase.test.ts` (15 cases: the progression of each of the six states, tool name reuse, a trailing chunk without deltas not changing the stage, content beating reasoning, `reset` returning to zero, the six Chinese and English strings all differing from each other); `chat-session.test.ts` gained 3 cases (progression following the SDK event order, sub-agent output not overriding the main stage, the stage returning to zero when `send` ends).
|
|
806
|
+
- pty acceptance (a local mock SSE server emitting the stream in stages): "connecting…" → "queued… (waiting for the first token)" → "thinking…" → "streaming…" → "calling tool no_such_tool…" → "waiting for sub_agent_default…" were all observed in order; the status bar carried only `⚡ 4.2 tok/s` and this round / cumulative tokens over the same period, with no "generating".
|
|
807
|
+
- New `src/agent-switch.test.ts` (10 cases), `src/mcp-command.test.ts` (6 cases), `src/tui/token-rate.test.ts` (20 cases), `src/tui/status-segments.test.ts` (17 cases), `src/tui/screens/input-row.test.tsx` (7 cases), `src/tui/text.test.ts` (18 cases); `input-editing.test.ts` gained 3 cases for "an emoji is never cut in half".
|
|
808
|
+
- `src/tui/screens.test.tsx` gained assertions for the order of the three status bar blocks, blocks not being split, side-by-side layout on wide screens, and "the row count Ink actually renders == the row count `statusBarRows` measures"; `computeInputCursorPosition` gained a case for "notice bar rows participate in y".
|
|
809
|
+
- The `⚡` case in `input-row.test.tsx` asserts both "hand-computation gives 8, measurement gives 9" — the reason for this change is in those two lines.
|
|
810
|
+
|
|
811
|
+
## [1.0.0-alpha.20] - 2026-10-27
|
|
812
|
+
|
|
813
|
+
### ✨ New features
|
|
814
|
+
|
|
815
|
+
- **The chat screen supports mouse wheel scrolling** — previously the wheel could only scroll the terminal's own scrollback buffer (`alternateScreen: false` uses inline rendering), and scrolling up showed snapshots of historical frames while the status bar and input line scrolled out of view entirely.
|
|
816
|
+
- New `src/tui/mouse.ts`: on mount it emits `DECSET 1000` (button events, wheel included) + `1006` (SGR extended coordinates); the close sequence is emitted in **three** places — `mount()`'s exit, `farewell()`, and the signal / uncaught exception fallback — so a crashing process does not spray `^[[<64;…M` into the shell.
|
|
817
|
+
- `parseWheel()` strips the Shift(4) / Meta(8) / Ctrl(16) modifier bits and takes the wheel code (`64` up, `65` down); horizontal wheel `66` / `67` is ignored outright; one notch scrolls 3 rows.
|
|
818
|
+
- The wheel shares one viewport clamp with `PgUp`/`PgDn` and line-wise scrolling — extracted into the pure function `clampViewTop()` (`screens/layout.ts`); scrolling against the boundary is absorbed naturally, and returning `null` means "glued to the bottom, following the newest content". Scrolling during generation is not snapped back to the bottom.
|
|
819
|
+
- **`isTerminalReply()` now covers mouse sequences**: clicks / drags / wheel events are dropped at the source. This step is mandatory — Ink hands the whole SGR sequence to `useInput` as `char` (measured as `"[<64;10;5M"`, all key flags false), and without filtering it lands in the input line as text.
|
|
820
|
+
- `1006` (SGR coordinates) must be used rather than the old X10 encoding: Ink's parser splits the latter into two events, `[M` plus three ordinary characters, and those three characters get inserted as text directly (verified by experiment).
|
|
821
|
+
- The startup screen's "press any key to skip" no longer treats mouse events as keys.
|
|
822
|
+
|
|
823
|
+
### 📝 Docs
|
|
824
|
+
|
|
825
|
+
- README (zh / en): the keybinding table gained a "mouse wheel" row, documenting two gotchas: with reporting enabled **drag-select copy requires holding `Shift`** (falling back to the terminal's native selection); **`tmux` users need `set -g mouse off`**, otherwise the wheel is intercepted by copy-mode first.
|
|
826
|
+
- Version numbers and runtime dependency versions in `docs/manifest.json`, `docs/{zh,en}/getting-started.md` and the READMEs aligned (`agents-core ^4.5.2`, `agents-sdk 1.0.0-alpha.18`).
|
|
827
|
+
|
|
828
|
+
### ⚠️ Known trade-offs (inherent to the terminal, not defects)
|
|
829
|
+
|
|
830
|
+
- Mouse reporting is a terminal-level switch: clicks / drags no longer go to the terminal for native selection, and copying requires holding `Shift` (true for iTerm2 / Windows Terminal / kitty and others).
|
|
831
|
+
- In-app scrolling only moves the viewport and produces no terminal scrollback — to review history use the wheel / `PgUp`, not the terminal scrollbar.
|
|
832
|
+
- Old Windows `conhost` does not support `1006`, so the wheel appears to do nothing in that terminal.
|
|
833
|
+
|
|
834
|
+
### ✅ Tests
|
|
835
|
+
|
|
836
|
+
- Full run: **25 files / 316 cases passed** (new `src/tui/mouse.test.ts`, 1 file, 11 cases; `screens.test.tsx` gained three groups: wheel parsing, `clampViewTop` clamping, startup screen mouse guard).
|
|
837
|
+
- End-to-end check on a real terminal (pty): wheel direction parsing, modifier stripping, clicks dropped, ordinary typing unaffected, close sequence emitted on exit.
|
|
838
|
+
|
|
839
|
+
## [1.0.0-alpha.19] - 2026-10-22
|
|
840
|
+
|
|
841
|
+
### 🐛 Fixes
|
|
842
|
+
|
|
843
|
+
- **Fixed the "Agent 'default' is not bound to a model" error on the first launch after upgrading** — alpha.18 moved the model source of truth to `AgentDefinition.modelId` and put "backfill modelId for historical Agents" inside `bootstrap()`. But the TUI's **session continuation** path (`resumeLastSession()` hitting the previous archive) never goes through `resolveTarget()` and therefore never reads the configuration, so `bootstrap()` never ran: the Agent still had no model, `config.defaultModel` was still there, and startup failed outright.
|
|
844
|
+
- `runTui()` now explicitly `await ensureConfigDir()` at the start, so configuration initialization precedes any Agent read;
|
|
845
|
+
- `modelOf()` also reads the configuration once itself (double insurance), so a missed caller can no longer hit this error.
|
|
846
|
+
- New regression test: with an old configuration (has `defaultModel`, Agent has no `modelId`), after one configuration read the model is moved onto the Agent and the old field in `config.json` is cleared.
|
|
847
|
+
|
|
848
|
+
### ✅ Tests
|
|
849
|
+
|
|
850
|
+
- Full run: **24 files / 299 cases passed**.
|
|
851
|
+
|
|
852
|
+
## [1.0.0-alpha.18] - 2026-10-22
|
|
853
|
+
|
|
854
|
+
### 🐛 Fixes
|
|
855
|
+
|
|
856
|
+
- **Fixed the 401 deadlock caused by "the displayed model is not the model actually requested"** — the model used to come from three places: the `modelId` in the status bar and the session archive, the `modelId` in the Agent definition, and the `defaultModel` in config. Once the three diverged:
|
|
857
|
+
- the status bar said `deepseek-flash`, the request actually went to GLM, and bigmodel's key was invalid → 401;
|
|
858
|
+
- when switching back to DeepSeek, `/model` judged by the **display layer** value, found that it was "already the current model" and returned immediately, without changing the actual model at all — so no amount of switching fixed the 401, and the only way out was deleting the config.
|
|
859
|
+
|
|
860
|
+
The model now **exists in exactly one place, `AgentDefinition.modelId`**, and the status bar, the credential pre-check, the context thresholds and the request the SDK actually sends all read it.
|
|
861
|
+
|
|
862
|
+
### 🔧 Behavior changes (incompatible)
|
|
863
|
+
|
|
864
|
+
- **Upgraded `@ai-zen/agents-sdk` to `1.0.0-alpha.17`**: the config layer no longer has `defaultModel`, and an Agent missing `modelId` errors out immediately (the default Agent ships with `deepseek-flash`); when upgrading historical configs, the old `defaultModel` is moved into the default Agent, so the model in use does not change.
|
|
865
|
+
- **Session archives no longer record `modelId`** — when a conversation is resumed, the model is determined entirely by the Agent definition; the field in old archives is ignored on read.
|
|
866
|
+
- **`/model` only writes the Agent definition** — the branch that changed the global default model when the Agent had no binding no longer exists.
|
|
867
|
+
- **Switching models resets reasoning effort** — `AgentDefinition.modelParams` is **cleared outright** when the model is switched (no retention, no migration, no per-key removal), so the target model uses its own `defaultParams`. Rationale: thinking parameters are model-specific, and DeepSeek's `thinking: {type:"disabled"}` for turning thinking off is rejected outright by GLM-5.3 — previously "a model unwilling to turn thinking off + a parameter saying to turn it off" could be sent together. To tune it again, select once more with `/think`.
|
|
868
|
+
- **Removed the "default model" entry from the configuration center** — the model is now chosen in the "model" field of the Agent detail screen (scope changes from "global default" to "that Agent").
|
|
869
|
+
|
|
870
|
+
### ✅ Tests
|
|
871
|
+
|
|
872
|
+
- Full run of **24 files / 298 cases passed**.
|
|
873
|
+
- New `model-switch` cases: switching clears `modelParams` (not merely removing the thinking keys), and no extra empty object is written when there was none.
|
|
874
|
+
|
|
875
|
+
## [1.0.0-alpha.17] - 2026-10-22
|
|
876
|
+
|
|
877
|
+
### 🔧 Behavior changes
|
|
878
|
+
|
|
879
|
+
- **Upgraded `@ai-zen/agents-sdk` to `1.0.0-alpha.16` (bundling core `4.5.2`): model parameters can no longer override the fixed fields of the request body** — core previously spread the model parameters at the outermost level of the request body, so if `model` / `messages` / `tools` / `stream` / `stream_options` appeared in `Model.defaultParams` / `AgentDefinition.modelParams`, they silently overrode the values the framework itself filled in. Worst of all was `stream: false`: streaming was turned off while the caller still parsed an async stream, failing the whole turn. Fixed fields now always win.
|
|
880
|
+
- This has **no effect** on `/think` and the effort levels: what gets written are non-fixed keys such as `thinking` / `reasoning_effort`, which pass through as before.
|
|
881
|
+
- The only thing to watch is not to hand-write those five keys in `modelParams` — writing them has no effect.
|
|
882
|
+
|
|
883
|
+
## [1.0.0-alpha.16] - 2026-10-22
|
|
884
|
+
|
|
885
|
+
### 🔧 Behavior changes (incompatible)
|
|
886
|
+
|
|
887
|
+
- **Upgraded `@ai-zen/agents-sdk` to `1.0.0-alpha.15`: effort levels no longer store the level name but the expanded parameters** — the previous version wrote the level name into the Agent definition's `reasoningEffort`, and at runtime the SDK looked it up in a table and translated it into request parameters. That pushed the product semantics of "what `max` means" into the runtime. It is now **configuration is the fact**:
|
|
888
|
+
- `/think` writes the selected level's parameter patch directly into `AgentDefinition.modelParams` (e.g. `{"thinking":{"type":"enabled"},"reasoning_effort":"high"}`), and the SDK only performs a single shallow merge of `{...defaultParams, ...modelParams}`;
|
|
889
|
+
- **the old `reasoningEffort` is migrated automatically**: when the Agent definition is read it is expanded into `modelParams` and written back; when the model has no associated preset, only the old field is deleted (no guessing at parameters).
|
|
890
|
+
- **Ownership of the reasoning preset table adjusted to "provided by the SDK, used by the CLI"** — the table's types, factory content and read/write live in the SDK (the CLI and the desktop share the same knowledge that "Zhipu 5.3 only accepts low/high/max"), and the CLI only **consumes** it: limiting the levels `/think` offers and expanding a level into parameters written into the Agent. `Scope` no longer needs the preset table injected.
|
|
891
|
+
|
|
892
|
+
### 🚀 New features
|
|
893
|
+
|
|
894
|
+
- **The status bar level is now looked up from the effective parameters** — it shows the level matched by the parameters that will actually be sent to the API (the model defaults included), no longer relying on the level declaration on the Agent. Three states: matches a level → "low / medium / high…"; there is an Agent override but it matches no level (parameters were edited by hand) → "custom"; no match at all → "default".
|
|
895
|
+
- **The "reasoning preset" field in `/config` → "model" detail screen** remains available, showing the preset and its levels (e.g. `zhipu-5.3(low / high / max)`), and a wrong name is rejected on the spot.
|
|
896
|
+
|
|
897
|
+
### ✅ Tests
|
|
898
|
+
|
|
899
|
+
- `src/reasoning-effort.test.ts` rewritten (19 cases): what is written is the expanded parameters rather than the level name; `max` → `off` leaves no residual `reasoning_effort`; unrelated parameters such as `temperature` are preserved; `null` clears and removes `modelParams` together; round trips are consistent (what was written can be looked up back to the original level).
|
|
900
|
+
- New `src/agents-store.test.ts` (4 cases): the old `reasoningEffort` is expanded and written back to disk; only the field is deleted when there is no preset; nothing is written to disk when the old field is absent; the original level can be looked up again after migration.
|
|
901
|
+
- Full run of **24 files / 299 cases passed**.
|
|
902
|
+
|
|
903
|
+
## [1.0.0-alpha.15] - 2026-10-22
|
|
904
|
+
|
|
905
|
+
### 🔧 Behavior changes (incompatible)
|
|
906
|
+
|
|
907
|
+
- **Upgraded `@ai-zen/agents-sdk` to `1.0.0-alpha.14`: reasoning capability changed to a declarative preset table** — the previous version expressed vendor differences with `Model.reasoning: { efforts, disable? }`, where `disable` carried two meanings, `off` relied on concatenation, and a global enum pretended that every vendor's levels were equivalent. All protocol differences are now moved into one table:
|
|
908
|
+
- new `~/.ai-zen/reasoning-presets.json` (released automatically on first start, three factory presets: `openai` / `zhipu-5.3` / `deepseek`); a model only records the name `reasoningPreset`;
|
|
909
|
+
- the levels `/think` offers now come from the preset table — thinking can only be turned off if the table has `off`, and that option is not offered otherwise;
|
|
910
|
+
- a model with no preset associated, or with a preset name absent from the table → always treated as not adjustable (`—` in the status bar), and parameters of unknown origin are never sent;
|
|
911
|
+
- level labels became "look up known levels in the table + display unknown level names as-is", so level names created by users (e.g. `turbo`) are no longer shown as blank.
|
|
912
|
+
- `TokenThresholds` in `src/stdio-runner.ts` / `src/tui/chat-session.ts` now imported directly from the SDK (previously a derived type that worked around the missing export in alpha.13).
|
|
913
|
+
|
|
914
|
+
### 🚀 New features
|
|
915
|
+
|
|
916
|
+
- **New "reasoning preset" field in `/config` → "model" detail screen** — shows the preset the model currently references and its levels (e.g. `zhipu-5.3(low / high / max)`), and can be changed directly to another preset name from the table; a wrong name is rejected on the spot with the available presets listed, and leaving it empty = no level adjustment. Custom models can now point at a built-in preset without hand-editing JSON.
|
|
917
|
+
- **The preset table can be edited** — adding a table or adjusting level patches is fine; once a table is marked `"custom": true`, SDK upgrades no longer overwrite it, while the remaining presets still sync with the SDK.
|
|
918
|
+
|
|
919
|
+
### ✅ Tests
|
|
920
|
+
|
|
921
|
+
- `src/reasoning-effort.test.ts` rewritten (14 cases): available levels = the keys of the preset table; unknown level names displayed as-is; an error is thrown when no preset is associated / the preset name does not exist; the status bar falls back to "default" on an illegal declaration.
|
|
922
|
+
- Configuration wizard: 2 new cases: writing a valid preset name; an invalid preset name is rejected and nothing is written to disk.
|
|
923
|
+
- Full run of **23 files / 290 cases passed**.
|
|
924
|
+
|
|
925
|
+
## [1.0.0-alpha.14] - 2026-10-22
|
|
926
|
+
|
|
927
|
+
### 🚀 New features
|
|
928
|
+
|
|
929
|
+
- **Input line keys aligned with Claude Code / readline** — editing shortcuts unified to the industry-standard set:
|
|
930
|
+
- word operations: `Alt+B` / `Alt+F` move by word (on macOS `Option+←/→` sends `ESC b` / `ESC f`, which previously did not respond at all); `Alt+Backspace`, `Alt+D`, `Ctrl+Delete` delete by word
|
|
931
|
+
- whole-segment operations: `Ctrl+K` deletes to end of line, `Ctrl+U` deletes to start of line, `Ctrl+W` **deletes to the previous whitespace** (punctuation does not split, so `src/utils/foo.ts` goes in one go)
|
|
932
|
+
- kill ring: `Ctrl+Y` retrieves the text just deleted, `Alt+Y` cycles through earlier ones; `Ctrl+_` undoes the last input edit; `Ctrl+S` stashes / restores the draft
|
|
933
|
+
- movement and history: `Ctrl+A` / `Ctrl+E` start / end of line, `Ctrl+P` / `Ctrl+N` previous / next history
|
|
934
|
+
- new pure-function layer `src/tui/input-editing.ts` (`deleteToPrevWhitespace` / `killedText` / kill ring / undo stack), 12 unit tests
|
|
935
|
+
- **macOS key diagnostics** — with `AIZ_DEBUG_KEYS=1` the terminal environment (`TERM` / `TERM_PROGRAM`) and every key press (`input` + modifier bits + `raw` state) are written to `/tmp/aiz-key-debug.log` (path configurable via `AIZ_KEYS_FILE`), for tracking down "a key does nothing on my machine": no new line = the key was intercepted by the system, `flags` mismatch = a matching logic problem, `raw=false` = the key was interpreted by the terminal itself.
|
|
936
|
+
- **`/model` switches the current session's model and writes the config** — with no argument it opens a model picker (annotating the current one), and `/model <id>` switches directly. The write target depends on "where the session model really comes from": model bound explicitly in the Agent definition → that Agent's `modelId` is changed; not bound (using the default model) → `defaultModel` in `config.json` is changed. After switching, the Scope and session are rebuilt (messages and context retained) and the model name in the status bar updates. The decision logic is extracted into `src/model-switch.ts` (`resolveModelWriteTarget` / `switchModel`, 7 unit tests).
|
|
937
|
+
- **Reasoning effort display and switching (`/think`, alias `/effort`)** — the status bar shows the current effort after the model name (`off` / `minimal` / `low` / `medium` / `high` / `xhigh` / `max`; `default` when the model is adjustable but the Agent has no declaration, `—` when the model is not adjustable). `/think` with no argument opens a picker (annotating the current one), `/think high` sets directly, `/think default` clears the declaration and follows the model default again.
|
|
938
|
+
- **The level lives on the Agent definition** (`AgentDefinition.reasoningEffort`), not on the model: the same model can have different efforts for the coding / novel / chat Agents, while the model's `defaultParams` stay untouched.
|
|
939
|
+
- **Available levels are decided by model capability** (`Model.reasoning.efforts`, declared by the SDK per the vendor's official docs): Zhipu GLM-5.3 only has `low/high/max` and cannot be turned off (passing `medium` or `disabled` returns 400), DeepSeek is `off/low/high/max`, OpenAI is `off/minimal/low/medium/high`; a model with no declaration offers no level at all — never guessed.
|
|
940
|
+
- Read/write and validation are extracted into `src/reasoning-effort.ts` (`effortChoices` / `describeEffort` / `setAgentEffort`, 9 unit tests).
|
|
941
|
+
- **SDK logging takeover** — the TUI calls `setLogger()` on startup, disabling the SDK's `console.log` fallback (otherwise the `load_mcp` manifest writes straight to stdout and tears the UI apart); with `AIZ_DEBUG_SDK=1` it writes to `/tmp/aiz-sdk.log` instead (path configurable via `AIZ_SDK_LOG_FILE`), 6 unit tests.
|
|
942
|
+
|
|
943
|
+
### 🔧 Behavior changes (incompatible)
|
|
944
|
+
|
|
945
|
+
- **Upgraded `@ai-zen/agents-sdk` to `1.0.0-alpha.13`, context thresholds changed to a struct** — `Model.maxContextTokens` is replaced by `Model.tokenThresholds: { handoff, guard }` (`maxContextChars` retired):
|
|
946
|
+
- `handoff`: the soft handoff line; `prompt_tokens` exceeding it triggers a task handoff;
|
|
947
|
+
- `guard`: the hard cutoff line; exceeding it **before** each request aborts the turn with an error.
|
|
948
|
+
- both lines are **absolute values**, and `ratio` / `suspendedRatio` / `isSuspended` of `ContextGuardPlugin` are removed from the SDK — loosen things by changing the numbers. The cost is that the handoff turn no longer gets the 2.0× extra allowance; by default `guard` is configured as `handoff × 1.5` (375K) to leave headroom.
|
|
949
|
+
- old configs are **migrated automatically**: the SDK's `ConfigManager.read()` migrates on read (`maxContextTokens` → `handoff`, `maxContextChars` converted by `/4`, a missing `guard` filled in once at 1.5×, old keys deleted), writing back to disk only when a difference is detected. Users do not need to hand-edit `config.json`.
|
|
950
|
+
- the former "migration threshold" entry in the configuration center is split into "handoff threshold" and "guard threshold" rows, and the soft line is validated to be less than the hard line.
|
|
951
|
+
|
|
952
|
+
- **`Ctrl+N` changed from "newline" to "next history entry"** — newline is now `Ctrl+J` / `Ctrl+Enter` / `Alt+Enter` / `Shift+Enter` / `\` + `Enter`
|
|
953
|
+
- **`Ctrl+V` no longer doubles as "page down"** — reserved for Claude Code's "paste image"; pressing it now shows a not-implemented-yet notice. Paging uses `PgUp` / `PgDn`, scrolling by line uses `Shift+↑ ↓` / `Alt+↑ ↓`
|
|
954
|
+
- **`Ctrl+U` changed from "clear the whole input box" to "delete to start of line"** — iTerm2 and Terminal.app on macOS map `⌘⌫` to `Ctrl+U`, and the old semantics made Mac users lose an entire draft with a single ⌘⌫; clearing everything is now handled by `Esc`
|
|
955
|
+
- **`Ctrl+C` no longer exits directly when idle** — with a draft, it clears the draft first with a notice, and a second press exits
|
|
956
|
+
- **`Ctrl+D` changed to double-press to confirm exit** — with a draft, readline semantics apply (delete one character after the cursor)
|
|
957
|
+
- **`Esc` during a busy period (generating / migrating) now aborts the current turn**
|
|
958
|
+
|
|
959
|
+
### ⏳ Not implemented yet
|
|
960
|
+
|
|
961
|
+
- **Pasting images (`Ctrl+V`)** — the endpoint capability probe for "file → content block" has to be solved first (Files API upload vs base64 inline), and the upload endpoint must not be hardcoded to a particular id; the subsequent SDK rework comes after, and the code for this feature was removed in this round to avoid shipping a half-finished feature.
|
|
962
|
+
|
|
963
|
+
### ✅ Tests
|
|
964
|
+
|
|
965
|
+
- New `src/tui/input-editing.test.ts` (12 cases) + `src/tui/key-debug.test.ts` (4 cases) + `src/sdk-log.test.ts` (6 cases) + `src/model-switch.test.ts` (7 cases) + `src/reasoning-effort.test.ts` (9 cases) + 8 cases for the input utility functions; full run of **23 files / 286 cases** passed, `tsc` and `build` with zero errors.
|
|
966
|
+
|
|
967
|
+
### 📄 Docs
|
|
968
|
+
|
|
969
|
+
- Rewrote the "keyboard and terminal compatibility" section of `README{,.zh}.md`: switched to the industry-standard route "Option as Meta + readline-style Ctrl keys", with the concrete settings paths for Apple Terminal / iTerm2 / VS Code / Ghostty; explained that `⌘` only reports with the kitty keyboard protocol, and why `⌘⌫` is mapped to `Ctrl+U` by the terminal; added the usage of key diagnostics. The command tables in `README{,.zh}.md` and `docs/{zh,en}/getting-started.md` gained `/model` and `/think` (with a "config file source" comparison), and the version in `docs/manifest.json` was synced.
|
|
970
|
+
|
|
971
|
+
## [1.0.0-alpha.13] - 2026-10-21
|
|
972
|
+
|
|
973
|
+
### 🚀 New features
|
|
974
|
+
|
|
975
|
+
- **Handoff confirmation unified to a three-way choice** — the three paths of automatic migration, manual `/migrate` and manual `/handoff` previously behaved inconsistently (automatic: a 2-item picker + Esc to skip; `/migrate`: a yes/no confirmation; `/handoff`: no question). They are now unified into the same "handoff mode" picker:
|
|
976
|
+
- **the current session writes the handoff doc itself** (`self`, faithful · the real state can be verified)
|
|
977
|
+
- **a separate Agent writes the handoff doc** (`summary`, independent summary · fast and cheap, files are not verified)
|
|
978
|
+
- **no handoff** (skip this time; `Esc` does the same)
|
|
979
|
+
|
|
980
|
+
When `self` / `summary` is given explicitly after the command (e.g. `/migrate self`), it executes directly without asking. The picker is extracted into the shared module `src/tui/screens/handoff-choice.ts`, with the same options and copy reused in all three places.
|
|
981
|
+
|
|
982
|
+
### 🔧 Fixes
|
|
983
|
+
|
|
984
|
+
- **The confirmation notice for an automatic "no handoff" was overwritten** — the `notice` channel keeps only the latest entry, and the previous "this task migration has been skipped" and "you can /migrate at any time…" were pushed as two entries, the latter overwriting the former; they are now merged into a single entry, so the skip confirmation is visible.
|
|
985
|
+
|
|
986
|
+
### ✅ Tests
|
|
987
|
+
|
|
988
|
+
- New `src/tui/screens/handoff-choice.test.ts` (5 cases): the value order of the three-way choice, `self`/`summary` returned as-is, "no handoff"/`Esc` → `null`, title passed through. Full run of **18 files / 237 cases** passed, `tsc` and `build` with zero errors.
|
|
989
|
+
|
|
990
|
+
### 📄 Docs
|
|
991
|
+
|
|
992
|
+
- The "trigger methods" section of `docs/{zh,en}/migration.md` synced to the unified three-way choice (manual commands always ask first unless the executor is specified explicitly); the versions in `docs/manifest.json` and `README{,.zh}.md` synced.
|
|
993
|
+
|
|
994
|
+
## [1.0.0-alpha.12] - 2026-10-20
|
|
995
|
+
|
|
996
|
+
### 🔧 Fixes
|
|
997
|
+
|
|
998
|
+
- **After automatic migration the old session was overwritten and history was lost** — `ChatSession` used to store the session id in two places (`this.currentId` and `ctx.currentId`). Manual `/migrate` and `/new` synced both, but **automatic migration** is driven by the SDK's `AutoMigratePlugin` calling `HandoffService.migrate()` directly and **goes through no method of `ChatSession`**, so the new id was written only into `ctx.currentId`. `ConversationPersistPlugin.getId()` therefore still returned the old id, and the first write after migration stored the "system prompt + handoff breakpoint" produced by `prune` into the **old session file**, overwriting the complete saved history (users saw "all history gone after the handoff, only the handoff doc left").
|
|
999
|
+
- Fix: `currentId` / `currentName` became **getters/setters proxying `ctx`**, eliminating the second source of truth — whoever updates `ctx` takes effect immediately.
|
|
1000
|
+
- `ConversationContext.currentId` narrowed from `string | undefined` to `string`; the two redundant double writes in `reset()` were removed accordingly.
|
|
1001
|
+
- **Test pollution**: the end-to-end case in `chat-session.test.ts` wrote handoff docs into `<cwd>/.ai-zen/handoffs`; it now points at a temp directory with automatic cleanup.
|
|
1002
|
+
|
|
1003
|
+
### ✅ Tests
|
|
1004
|
+
|
|
1005
|
+
- 3 new cases: `ctx` changes take effect immediately; assignment writes back to `ctx`; end to end (the id must follow after a real `handoffService.migrate()`; this case does fail before the fix, verified in reverse). Full run of **17 files / 232 cases** passed, `tsc` (including the test project) and `build` with zero errors.
|
|
1006
|
+
|
|
1007
|
+
### 📄 Docs
|
|
1008
|
+
|
|
1009
|
+
- The "migration chain" section of `docs/{zh,en}/migration.md` gained the constraint "the session id has a single source of truth" and its failure symptoms; the versions in `docs/manifest.json` and `README{,.zh}.md` synced.
|
|
1010
|
+
- The dependency is still `@ai-zen/agents-sdk` `1.0.0-alpha.11` (no SDK changes this time).
|
|
1011
|
+
|
|
1012
|
+
## [1.0.0-alpha.11] - 2026-10-20
|
|
1013
|
+
|
|
1014
|
+
### ⚠️ Dependencies
|
|
1015
|
+
|
|
1016
|
+
- `@ai-zen/agents-sdk` `1.0.0-alpha.10 → 1.0.0-alpha.11` (the new `env` parameter of `createCallMcpTool` is a breaking change; the CLI does not call that factory directly, so there are no code changes).
|
|
1017
|
+
|
|
1018
|
+
### 🔧 Changes (with SDK)
|
|
1019
|
+
|
|
1020
|
+
- **`call_mcp_tool` output protection**: when an MCP tool is called, text output exceeding `maxToolOutput` (32768 characters by default) is written in full to `result.txt`, and only a warning plus a head/tail preview is returned, readable in batches with `read` — no more pouring tens of thousands of characters into the context at once.
|
|
1021
|
+
- **Images / audio / binary resources returned as-is**: the `image` / `audio` / `resource.blob` returned by MCP are converted into `image_url` / `file` content blocks and passed through (`resource.text` merged into the body, `resource_link` turned into readable clues), no longer dropped silently; media is not subject to `maxToolOutput`.
|
|
1022
|
+
|
|
1023
|
+
### 📄 Docs
|
|
1024
|
+
|
|
1025
|
+
- `docs/{zh,en}/mcp.md` explains "only text is subject to output protection; media is not limited by the cap"; the versions in `docs/{zh,en}/getting-started.md`, `docs/manifest.json` and `README{,.zh}.md` synced.
|
|
1026
|
+
|
|
1027
|
+
## [1.0.0-alpha.10] - 2026-10-20
|
|
1028
|
+
|
|
1029
|
+
### 💥 Changes
|
|
1030
|
+
|
|
1031
|
+
- **Handoff and migration split into two commands** (single semantics, naming aligned with the implementation):
|
|
1032
|
+
- **`/handoff`** — the original agent stops, verifies the state and writes the handoff doc to `<project>/.ai-zen/handoffs/`, with the **session unchanged** (no confirmation, repeatable; the handoff turn stays in `agent.messages`, so you can keep asking to refine the same doc);
|
|
1033
|
+
- **`/migrate`** (alias `/migration`) — the same handoff + clear history + start a new session (confirmation required).
|
|
1034
|
+
- both support the `self` / `summary` argument (e.g. `/handoff summary`, `/migrate self`) to skip the default choice.
|
|
1035
|
+
- **Automatic migration changed to an "executor three-way choice" picker**: when the context exceeds the threshold it no longer only asks "whether to migrate", but offers **self-handoff by the original agent (highlighted by default, faithful) / independent summary (cheap) / skip this time**. The picker reuses the existing picker overlay (`setPickerState`). Non-interactive environments (pipes, redirection, e2e) have no choice channel and migrate with the default executor, without blocking or extra output.
|
|
1036
|
+
- **Handoff doc directory**: new `PROJECT_HANDOFFS_DIR = <cwd>/.ai-zen/handoffs`, so handoff docs live inside the project and can be shared / reviewed with the repository.
|
|
1037
|
+
|
|
1038
|
+
### ♻️ Refactors
|
|
1039
|
+
|
|
1040
|
+
- **`src/migration-service.ts` → `src/handoff-service.ts`**: `createMigrationService` → `createHandoffService`, and the hooks change from `onBeforeMigrate` / `onMigrated` to `onBeforeHandoff` / `onHandoff` / `onMigrated`. Archiving the old session moved to `onHandoff` (which now already contains the handoff turn, making the archive auditable); `/handoff` does not switch sessions and therefore needs no extra archiving (`ConversationPersistPlugin` already writes to disk every turn).
|
|
1041
|
+
- **`src/auto-migrate-confirm-plugin.ts` → `src/auto-migrate-confirm.ts`**: `AutoMigrateConfirmPlugin` (inheriting and overriding `onAfterSend`) degenerated into the `createAutoMigratePlugin()` factory — "when to migrate" stays in the SDK plugin, and "who does the handoff" is injected through the SDK's new `resolveMode` callback, with the threshold logic no longer reimplemented.
|
|
1042
|
+
- `src/agent-creator.ts` extracted and exported `listSkillDirs()`: Scope assembly and the handoff service (which reads the body of the `handoff` Skill) share the same directory priority, so it is not computed twice.
|
|
1043
|
+
- `ChatSessionOptions.requestConfirm` → `requestMode` (returning `HandoffMode | null`); `ChatSession.migrate()` / `handoff()` return the doc path; `ConversationContext.migrationService` → `handoffService`.
|
|
1044
|
+
- `ContextGuardPlugin` wired with `isSuspended: () => handoffService.busy`: the handoff-turn guard is relaxed from `× 1.5` to `× 2`.
|
|
1045
|
+
- Slash command parsing supports arguments: the command name is the first token (`/migrate summary` is no longer treated as an unknown command).
|
|
1046
|
+
|
|
1047
|
+
### ⚠️ Dependencies
|
|
1048
|
+
|
|
1049
|
+
- `@ai-zen/agents-sdk` `1.0.0-alpha.9 → 1.0.0-alpha.10` (`TaskMigrationService` → `HandoffService` is a breaking change).
|
|
1050
|
+
|
|
1051
|
+
### ✅ Tests
|
|
1052
|
+
|
|
1053
|
+
- New `src/handoff-service.test.ts` (migration clears `lastUsage` / the new session is written to disk / `/handoff` does not switch sessions), `src/auto-migrate-confirm.test.ts` (non-interactive default, choice passed through, cancel skips, busy yields). Full run of **17 files / 229 cases** passed, `tsc --noEmit` (including the test project) and `build` with zero errors.
|
|
1054
|
+
|
|
1055
|
+
### 📄 Docs
|
|
1056
|
+
|
|
1057
|
+
- `docs/{en,zh}/migration.md` rewritten as "task handoff and migration": the difference between the two commands, the trade-offs of the two executors, the code-level guarantees of self-handoff (guard / relaxation / recursion prevention), doc location and naming, the template carried by the `handoff` Skill (`metadata.custom: "true"` opts out of sync and can be overridden per project), and cost notes. The website mirror synced.
|
|
1058
|
+
- The command table and the "task handoff and migration" chapter of `README.md` / `README.zh.md`, and the features and navigation of `docs/{en,zh}/index.md`, synced.
|
|
1059
|
+
|
|
1
1060
|
## [1.0.0-alpha.9] - 2026-10-14
|
|
2
1061
|
|
|
3
|
-
### 🔧
|
|
1062
|
+
### 🔧 Fixes
|
|
4
1063
|
|
|
5
|
-
-
|
|
6
|
-
-
|
|
7
|
-
-
|
|
1064
|
+
- **Interaction adaptations between automatic migration and the CLI (two places)**:
|
|
1065
|
+
- **The input line did not really block during migration** — the `isActive` of the main `useInput` only blocked the "confirmation box / picker / configuration wizard", not the "generating" state. Automatic migration runs inside `send()` via `await onAfterSend` (`busy` is true and the input line has been replaced by the Spinner), yet keystrokes were still swallowed silently into the hidden input buffer and only surfaced once the turn ended. The main input is now fully deactivated while `busy`, with a separate "busy period" handler that only passes `Ctrl+C` to abort / `Ctrl+D` to exit / `PgUp`·`Shift+↑↓` to look back; manual `/migrate` is included in `busy` as well.
|
|
1066
|
+
- **Still blocked by the overall context guard after migration** — migration (prune) physically resets the context but left `agent.lastUsage` behind (the `prompt_tokens` of the request before migration). `ContextGuardPlugin` (`onInnerLoopStart`, hard cap `maxTokens × 1.5`) misjudged from it, and the first message after migration was aborted with `ContextOverflowError`. `agent.lastUsage` is now cleared after migration, so the guard has nothing to go on in the first round and lets it pass, until the next response fills in the real usage.
|
|
8
1067
|
|
|
9
|
-
### ✅
|
|
1068
|
+
### ✅ Tests
|
|
10
1069
|
|
|
11
|
-
-
|
|
1070
|
+
- New `src/migration-service.test.ts`: asserts that `agent.lastUsage` is cleared after migration (this case does indeed fail when the fix is removed). Full run of **17 files / 227 cases** passed, `tsc --noEmit` and `pnpm build` with zero errors.
|
|
12
1071
|
|
|
13
1072
|
## [1.0.0-alpha.8] - 2026-10-14
|
|
14
1073
|
|
|
15
|
-
### 🔧
|
|
1074
|
+
### 🔧 Fixes
|
|
16
1075
|
|
|
17
|
-
-
|
|
1076
|
+
- **Fixed the OOM (crash) caused by unbounded memory growth in long TUI sessions** — the CLI entry never set `NODE_ENV`, so React loaded the **development** build of `react-reconciler` per `process.env.NODE_ENV !== "production"`; that dev build calls `performance.measure()` (User Timing API) on every render, and Node's performance entry buffer is **unbounded and never evicted**, so entries kept accumulating during long streaming sessions and the heap approached `heap_size_limit` (about 1GB on this machine) after roughly 55 minutes, triggering OOM. `process.env.NODE_ENV ||= "production"` is now set at the top of `src/index.ts`; because ESM static `import`s are hoisted and evaluated before the module body, the TUI entry `./tui/index.js` now uses a dynamic `import()`, ensuring React/Ink is evaluated **after** `NODE_ENV` is set and thus loads the production reconciler (which does not call `performance.measure`). An explicit `NODE_ENV=development` still keeps the dev build for debugging.
|
|
18
1077
|
|
|
19
|
-
### ✅
|
|
1078
|
+
### ✅ Tests
|
|
20
1079
|
|
|
21
|
-
-
|
|
1080
|
+
- **End-to-end proof** — loading Ink through the entry point under real ESM and re-rendering 300 times in a row: after the fix, `performance.measure` entries went **0 → 0** (production reconciler), while the control group with an explicit `NODE_ENV=development` went **0 → 905** (dev instrumentation still in place). Full run of **16 files / 226 cases** passed, `tsc --noEmit` and `pnpm build` with zero errors.
|
|
22
1081
|
|
|
23
1082
|
## [1.0.0-alpha.7] - 2026-10-14
|
|
24
1083
|
|
|
25
|
-
### 📄
|
|
1084
|
+
### 📄 Docs
|
|
26
1085
|
|
|
27
|
-
-
|
|
28
|
-
-
|
|
1086
|
+
- **Status bar token usage added to the docs** — the "main features" section of `docs/zh|en/index.md` gained "the status bar shows in real time the model · Agent · current-turn/cumulative token usage · generation state" (previously mentioned only in the README), with cumulative usage defined as `sumUsage(agent.messages)` (including sub-agents at all levels).
|
|
1087
|
+
- Fixed the outdated path reference in `TODO.md` (`src/tui/config-wizard.tsx` → directory `src/tui/config-wizard/`).
|
|
29
1088
|
|
|
30
|
-
### ⚠️
|
|
1089
|
+
### ⚠️ Dependencies
|
|
31
1090
|
|
|
32
|
-
- `@ai-zen/agents-core` `^4.5.0 → ^4.5.1
|
|
1091
|
+
- `@ai-zen/agents-core` `^4.5.0 → ^4.5.1`, `@ai-zen/agents-sdk` `1.0.0-alpha.8 → 1.0.0-alpha.9`
|
|
33
1092
|
|
|
34
|
-
>
|
|
1093
|
+
> Docs-only correction, no code changes.
|
|
35
1094
|
|
|
36
1095
|
## [1.0.0-alpha.6] - 2026-10-14
|
|
37
1096
|
|
|
38
|
-
### ♻️
|
|
1097
|
+
### ♻️ Refactors
|
|
39
1098
|
|
|
40
|
-
-
|
|
1099
|
+
- **Cumulative usage now uses the SDK's `sumUsage`, and the CLI's own in-memory accumulator is removed** — depends on `@ai-zen/agents-sdk` `1.0.0-alpha.8` / `@ai-zen/agents-core` `4.5.0`: besides the `usage` of assistant messages, usage produced by delegation is now pinned by core onto the tool message's `subAgentUsage` at the delegation boundary. So `ChatSession.totalUsage` can simply be `sumUsage(agent.messages)` (covering sub-agents at every level), `src/usage.ts` and the `onSubAgentEnd` in-memory back-feed are deleted — sub-agent cost is persisted along with messages and is not lost on resume / `/load`, and the double-counting risk is gone.
|
|
41
1100
|
|
|
42
|
-
### ⚠️
|
|
1101
|
+
### ⚠️ Dependencies
|
|
43
1102
|
|
|
44
|
-
- `@ai-zen/agents-sdk` `1.0.0-alpha.7 → 1.0.0-alpha.8
|
|
1103
|
+
- `@ai-zen/agents-sdk` `1.0.0-alpha.7 → 1.0.0-alpha.8`, `@ai-zen/agents-core` `^4.4.0 → ^4.5.0`
|
|
45
1104
|
|
|
46
|
-
### ✅
|
|
1105
|
+
### ✅ Tests
|
|
47
1106
|
|
|
48
|
-
-
|
|
1107
|
+
- Removed `src/usage.test.ts` (the logic moved down into core and is already covered there); the `totalUsage` case in `chat-session.test.ts` now covers summing "the assistant's usage + the tool message's subAgentUsage". Full unit suite **16 files / 226 cases** pass, `tsc --noEmit` and `npm run build` report zero errors.
|
|
49
1108
|
|
|
50
1109
|
## [1.0.0-alpha.5] - 2026-10-14
|
|
51
1110
|
|
|
52
|
-
### 🚀
|
|
1111
|
+
### 🚀 New features
|
|
53
1112
|
|
|
54
|
-
-
|
|
55
|
-
-
|
|
1113
|
+
- **Status bar shows cumulative token consumption** — built on the `usage` that `@ai-zen/agents-sdk` `1.0.0-alpha.7` / `@ai-zen/agents-core` `4.4.0` pin onto each assistant message, added `src/usage.ts` (`addUsage` / `sumMessagesUsage` pure functions) and `ChatSession.totalUsage`: summing the `usage` of all assistant messages in the session gives the cumulative consumption on the billing basis; a sub-agent's messages are not in the parent agent, so they are accumulated separately in `onSubAgentEnd` and folded in, and reset to zero on `/new`. The status bar is upgraded from "`{tokens} tok`" to "`this round x · cumulative y tok`" (this round = `lastUsage.total_tokens`, cumulative = `totalUsage.total_tokens`).
|
|
1114
|
+
- **Dependency upgrade** — `@ai-zen/agents-sdk` `1.0.0-alpha.6 → 1.0.0-alpha.7`, `@ai-zen/agents-core` `^4.3.2 → ^4.4.0` (the latter adds `AgentNS.Message.usage`, per-message token usage).
|
|
56
1115
|
|
|
57
|
-
### ✅
|
|
1116
|
+
### ✅ Tests
|
|
58
1117
|
|
|
59
|
-
-
|
|
1118
|
+
- Added `src/usage.test.ts` (4 cases); `chat-session.test.ts` gained a summation case for `totalUsage`; the `StatusBar` cases in `screens.test.tsx` were updated. Full unit suite **17 files / 230 cases** pass, `tsc --noEmit` and `npm run build` report zero errors.
|
|
60
1119
|
|
|
61
1120
|
## [1.0.0-alpha.4] - 2026-10-13
|
|
62
1121
|
|
|
63
|
-
### ♻️
|
|
1122
|
+
### ♻️ Refactors
|
|
64
1123
|
|
|
65
|
-
- **TUI
|
|
66
|
-
- **notice / error
|
|
1124
|
+
- **TUI rendering uses `agent.messages` directly as the single source of truth** — removed custom intermediate models such as `ChatEvent` / `chatReducer` / `LiveAssistant` / `Block` / `messagesToBlocks`. During streaming the SDK **updates `agent.messages` in real time** (`Agent.parseStreamData` writes each delta incrementally into the current message), so the cross-message order (array order) and the order inside a single assistant message (`thinking → tool → text`) are preserved naturally and rendering directly is enough, which rules out the distortion and maintenance burden of a "second source of truth". Wrapping / highlighting of completed messages is cached by "message + column width + content signature" (`cachedMessageToLines`), and the in-flight message is invalidated automatically through its content signature.
|
|
1125
|
+
- **notice / error moved to a dedicated slot** — system-level notices (command feedback / migration progress / errors) no longer mix into the chat transcript, and are **never written into `agent.messages`** (that would be sent to the model verbatim by `formatHistory()` and pollute the context); they now render on a dedicated row above the input line (`src/tui/screens/notice.ts`).
|
|
67
1126
|
|
|
68
|
-
### 🎯
|
|
1127
|
+
### 🎯 Optimizations
|
|
69
1128
|
|
|
70
|
-
-
|
|
71
|
-
-
|
|
72
|
-
-
|
|
73
|
-
-
|
|
1129
|
+
- **Incremental streaming rendering**: Ink enables `incrementalRendering` — switching from "erase the whole screen and rewrite it every frame" (`createStandard`) to "redraw only the changed lines" (`createIncremental`), bringing per-frame output from O(screen size) down to O(change size), so streaming is steadier over SSH / weak networks.
|
|
1130
|
+
- **Streaming frame coalescing** (`src/tui/screens/frame-buffer.ts`): content changes are buffered first and re-rendering is flushed in batches every 60ms (about 16fps, below Ink's default 30fps output throttle), taking the paint rate from "once per delta" down to a fixed frame rate.
|
|
1131
|
+
- **Render cache**: in long conversations the wrapping / highlighting of historical messages no longer needs to be recomputed every frame, so the cost no longer degrades as the conversation grows.
|
|
1132
|
+
- **A blank line is added between thinking and tool calls**, for a clearer layout.
|
|
74
1133
|
|
|
75
|
-
### ✅
|
|
1134
|
+
### ✅ Tests
|
|
76
1135
|
|
|
77
|
-
-
|
|
78
|
-
- `tsc`
|
|
79
|
-
-
|
|
1136
|
+
- Added `frame-buffer.test.ts`, `notice.test.ts`; `screens.test.tsx` rewritten on top of `messageToLines` / `transcriptLines` / `computeChatLayout(transcript)`.
|
|
1137
|
+
- `tsc` reports zero errors; full unit suite **16 files / 225 cases** pass; `npm run build` passes.
|
|
1138
|
+
- Verified on a real terminal (pty): streaming is stable over SSH / weak networks, and scrolling in long conversations is smooth.
|
|
80
1139
|
|
|
81
1140
|
## [1.0.0-alpha.3] - 2026-10-13
|
|
82
1141
|
|
|
83
|
-
### 💥
|
|
1142
|
+
### 💥 Breaking changes
|
|
84
1143
|
|
|
85
|
-
-
|
|
1144
|
+
- **Built-in tools renamed (unified on `snake_case`: multi-word names collapsed, single-word names simplified)** — landed with `@ai-zen/agents-sdk` `1.0.0-alpha.6`: the SDK unifies built-in tool names on `snake_case` and simplifies single-word tools to one word — `readFile` → `read`, `writeFile` → `write`, `exec_async` → `spawn`, `findText` → `find_text`, `batchEdit` → `batch_edit`, `inspectFile` → `inspect_file`, `downloadFile` → `download_file`, `generateImage` → `generate_image`, `viewImage` → `view_image`, `indexedSearch` → `indexed_search`; tool class names are likewise now `ReadTool` / `WriteTool` / `SpawnTool`. **If your Agent definitions, permission allow lists (`tools.allow` / `tools.deny`) or custom prompts reference the old tool names, update them to the new `snake_case` names** (old names survive only in historical CHANGELOG entries).
|
|
86
1145
|
|
|
87
|
-
### 🚀
|
|
1146
|
+
### 🚀 New features
|
|
88
1147
|
|
|
89
|
-
-
|
|
1148
|
+
- **Text / thinking colored by Markdown source** — AI answers are Markdown already, so they are now colored by `highlight.js`'s `markdown` lexer: headings (`#`), bold, italic, inline code, lists, quotes and links each get their own color; **fenced code blocks keep their fence lines** (opening / closing fences rendered in subdued dark gray) and continue to be syntax-highlighted by the annotated language. Markers (`#` / `**` / backticks, etc.) **are kept as-is and only colored** (source coloring, not semantic rendering). Text wraps **preferring spaces**, so English words are not cut in the middle.
|
|
90
1149
|
|
|
91
|
-
### 🎯
|
|
1150
|
+
### 🎯 Optimizations
|
|
92
1151
|
|
|
93
|
-
-
|
|
1152
|
+
- **The default text color falls back to the terminal default foreground** — the base text color used to be hardcoded near-white `#e2e8f0`, which was invisible "white on white" in **light-theme terminals**. Text (including default segments with no color assigned) now always falls back to the **terminal default foreground**, with absolute colors kept only for emphasis such as headings / code; the two near-white token colors in the syntax palette (`variable` / `params`) are removed at the same time.
|
|
94
1153
|
|
|
95
|
-
### ⚠️
|
|
1154
|
+
### ⚠️ Dependency upgrades
|
|
96
1155
|
|
|
97
|
-
- **`@ai-zen/agents-core`
|
|
98
|
-
- core `4.3.1
|
|
99
|
-
- core `4.3.2` / sdk `1.0.0-alpha.4…6
|
|
100
|
-
- CLI
|
|
1156
|
+
- **`@ai-zen/agents-core` raised to `^4.3.2`, `@ai-zen/agents-sdk` raised to `1.0.0-alpha.6`** —
|
|
1157
|
+
- core `4.3.1`: **normalization of interrupted parallel tool rounds** — when several tools called in parallel within one reply are interrupted (`Ctrl+C`), unfinished tool results are filled in / normalized in the history, so the next round is not rejected by the server because the number of `tool_calls` does not match the number of actual tool results (default `fill`: keep the assistant text and fill in unfinished results).
|
|
1158
|
+
- core `4.3.2` / sdk `1.0.0-alpha.4…6`: built-in tools unified on `snake_case` + single-word simplification, `exec_async` → `spawn`, class names aligned (see "Breaking changes").
|
|
1159
|
+
- On the CLI side only tool names were synced (docs / test fixtures / prompts); runtime behavior is fully delegated to the SDK, with no other adaptation needed.
|
|
101
1160
|
|
|
102
|
-
### ✅
|
|
1161
|
+
### ✅ Tests
|
|
103
1162
|
|
|
104
|
-
- `tsc --noEmit
|
|
105
|
-
-
|
|
106
|
-
-
|
|
1163
|
+
- `tsc --noEmit` (`tsconfig.test.json`) reports zero errors; full unit suite **15 files / 235 cases** pass
|
|
1164
|
+
- Fixed two pre-existing failures in `config-wizard.test.tsx`: terminal size is now injected into the wizard (new optional `terminalSize` prop), removing the interference where `ink-testing-library`'s fake stdout lacked `rows`, so `terminal-size` probed the host machine's row count and stretched the bottom-anchored frame to 38 rows
|
|
1165
|
+
- Render verification: headings / bold / inline code / lists / quotes / links and fenced code blocks are colored as expected, and plain text has no color (terminal default)
|
|
107
1166
|
|
|
108
1167
|
## [1.0.0-alpha.2] - 2026-10-03
|
|
109
1168
|
|
|
110
|
-
### ⚠️
|
|
1169
|
+
### ⚠️ Dependency upgrades
|
|
111
1170
|
|
|
112
|
-
- **`@ai-zen/agents-sdk`
|
|
1171
|
+
- **`@ai-zen/agents-sdk` raised to `1.0.0-alpha.3`** — picks up the SDK's fix to the parameter description of the built-in tool `generateImage`. The old description said "the image model in the configuration can be viewed via `aiz config show`", which effectively **pointed the model at running a host CLI command**; and **tool descriptions enter every layer of agent context with the `tools` field of each request** — the model therefore ran `exec("aiz config show")`, the CLI parsed that command as **pure stdio mode** and launched **another agent** with `config show` as the prompt, whose tool description carried the same instruction again, forming a **self-referential recursion** (every layer really hit the API, nesting deeper and deeper). The SDK now rewrites the description to "if the specified model does not exist, this tool returns the list of currently available models", which matches `call()`'s actual behavior and no longer steers the model toward running the host CLI. This version has **no breaking changes**, and the CLI source needs no adaptation (types / default config are fully delegated to the SDK, the version banner reads the actually installed version, and the tool list is auto-discovered from the SDK's static registry).
|
|
113
1172
|
|
|
114
|
-
### 📝
|
|
1173
|
+
### 📝 Docs
|
|
115
1174
|
|
|
116
|
-
- `docs/zh|en/getting-started.md
|
|
117
|
-
- `docs/manifest.json
|
|
118
|
-
-
|
|
1175
|
+
- `docs/zh|en/getting-started.md`: runtime dependency version synced to `@ai-zen/agents-sdk` `1.0.0-alpha.3`
|
|
1176
|
+
- `docs/manifest.json`: version number synced to `1.0.0-alpha.2`
|
|
1177
|
+
- The example startup banner version in the Chinese and English READMEs synced to `v1.0.0-alpha.2`
|
|
119
1178
|
|
|
120
|
-
### ✅
|
|
1179
|
+
### ✅ Tests
|
|
121
1180
|
|
|
122
|
-
- `tsc --noEmit
|
|
123
|
-
-
|
|
1181
|
+
- `tsc --noEmit` (`tsconfig.test.json`) and `npm run build` report zero errors
|
|
1182
|
+
- Full unit suite: 13 files / 224 cases pass
|
|
124
1183
|
|
|
125
1184
|
## [1.0.0-alpha.1] - 2026-09-30
|
|
126
1185
|
|
|
127
|
-
>
|
|
1186
|
+
> This release summarizes all changes **relative to the previous release `0.9.0`**.
|
|
128
1187
|
|
|
129
|
-
### 💥
|
|
1188
|
+
### 💥 Breaking changes
|
|
130
1189
|
|
|
131
|
-
-
|
|
132
|
-
-
|
|
133
|
-
-
|
|
1190
|
+
- **The startup command changes from `aiz` / `zen` to `ai`** — `bin` in `package.json` becomes `{ "ai", "aiz", "zen" }`; `aiz` / `zen` are kept as **transitional aliases** and will be removed in a future version.
|
|
1191
|
+
- **Dual-mode operation introduced, the two modes never mix** — when positional arguments are present, or stdin/stdout are not a TTY (pipe / redirection / CI), **pure stdio mode** is used: no extra interaction at all, the final assistant text is written to `stdout` as plain text (zero ANSI / emoji prefixes / spinner), while logs, progress, tool activity and error summaries all go to `stderr`, with exit code `0` on success and non-`0` on failure, and nothing written to disk by default; **TUI mode** is entered only when there are **no arguments and both stdin/stdout are TTYs**.
|
|
1192
|
+
- **The whole interactive UI was rewritten as an Ink (React) full-screen TUI** — replacing the former inquirer line-based conversation; the **main menu is removed** (`/menu` and `src/menus/*`) along with the `inquirer` / `@types/inquirer` / `chalk` dependencies.
|
|
134
1193
|
|
|
135
|
-
### ✨
|
|
1194
|
+
### ✨ Added
|
|
136
1195
|
|
|
137
|
-
- **TUI
|
|
138
|
-
-
|
|
139
|
-
-
|
|
140
|
-
-
|
|
141
|
-
-
|
|
142
|
-
-
|
|
143
|
-
-
|
|
144
|
-
-
|
|
145
|
-
-
|
|
146
|
-
-
|
|
147
|
-
-
|
|
148
|
-
-
|
|
149
|
-
-
|
|
150
|
-
- **IME
|
|
1196
|
+
- **Full-screen TUI (Ink + React)**:
|
|
1197
|
+
- animated gradient ASCII splash screen, going straight into the conversation when it ends (auto-resuming the previous session if there is one);
|
|
1198
|
+
- the chat area is **bottom-anchored** (separator / input line / status bar fixed at the bottom, with the whole frame always "terminal rows − 1" tall to stay out of Ink's full-screen branch);
|
|
1199
|
+
- streaming output (thinking / text / tool calls on separate rows), with history trimmed by line and scrolling from the bottom;
|
|
1200
|
+
- **multi-line input** (`Enter` sends, `Ctrl+N` / `Ctrl+J` insert a newline) and **cursor editing** (`←`/`→`/`Home`/`End`/`Backspace`/`Delete`, `Ctrl`/`Meta` + `←`/`→` by word, inverse-video cursor + hardware cursor sync);
|
|
1201
|
+
- **in-app paging** (`PgUp`/`PgDn` page, `Shift`/`Alt` + `↑`/`↓` scroll one line, sending jumps back to the bottom);
|
|
1202
|
+
- input history, inline `/` command menu, status bar, confirmation dialogs.
|
|
1203
|
+
- **Syntax highlighting**: tool call arguments highlighted as **JSON**, and **fenced code blocks** in text and thinking highlighted by language. The highlighting **data** is delegated to `highlight.js` (via `lowlight` producing hast), and the CLI only maps `hljs-*` scopes to Ink `<Text>` colors — the same approach as the Ink/React-based Google Gemini CLI.
|
|
1204
|
+
- **In-conversation commands**: `/help`, `/load`, `/clear`, `/new`, `/save`, `/config`, `/key`, `/migrate`, `/back`, `/editor`, `/exit` (`registry.ts` is the single source of the command list, and `/help` and input autocomplete stay in sync automatically).
|
|
1205
|
+
- **First-run credential onboarding + configuration center (Ink native)**: when the endpoint has no API key, a credential setup screen is shown first; the `/config` configuration center manages endpoints, models, image models, defaults (default model / image model / Agent / migration model), tool output limits, **MCP servers** (global / project scope) and **Agent definitions** (top-level Agents + sub-agents, with four dimensions of permissions); editing a "factory-managed" entry automatically switches it to `custom: true`, and dangerous operations require a second confirmation and are guarded by reference checks.
|
|
1206
|
+
- **Multi-line text goes through the system editor**: multi-line fields such as agent prompts and sub-agent function descriptions / parameter schemas open `$EDITOR` on `Enter` (`notepad` by default on Windows); parameter schemas open as `.json` and are validated immediately.
|
|
1207
|
+
- **Session persistence overhaul: a session is a file + a single pointer** — written to disk at the end of every round, `cli/last-session.json` records the previous round's session id, nothing is written to disk until the first message, and `/save` is idempotent; resume / load rebuilds the history from messages.
|
|
1208
|
+
- **Extras for pure stdio mode**: `ai --help` / `-h`, `ai --version` / `-v`, `ai --save`; SDK logs and `console.log` are uniformly redirected to `stderr` to keep stdout clean.
|
|
1209
|
+
- **IME candidate window follows the cursor**: Ink's `useCursor()` syncs the hardware cursor to the editing cursor, so the Chinese IME candidate window is positioned correctly.
|
|
151
1210
|
|
|
152
|
-
### 🔧
|
|
1211
|
+
### 🔧 Changes
|
|
153
1212
|
|
|
154
|
-
-
|
|
155
|
-
-
|
|
156
|
-
- **TUI
|
|
157
|
-
-
|
|
158
|
-
-
|
|
159
|
-
-
|
|
160
|
-
-
|
|
1213
|
+
- **Dependencies**: added `ink` / `react` (runtime), `ink-testing-library` / `@types/react` (dev); added syntax highlighting `highlight.js` / `lowlight` (runtime) and `@types/hast` (dev); **removed** `inquirer` / `@types/inquirer` / `chalk`.
|
|
1214
|
+
- **Upgraded `@ai-zen/agents-sdk`**: `1.0.0-alpha.0` → `1.0.0-alpha.1` → `1.0.0-alpha.2` (factory model catalog refresh, model list hosting, `chrome-devtools` and others added to the default `mcp.json`; zero code changes on the CLI side).
|
|
1215
|
+
- **TUI output uniformly goes through Ink**: notices such as migration progress and session write failures no longer use `console.log` / `console.warn` to write directly to the terminal (which tears Ink frames) and are rendered as notice blocks via callbacks; the startup logo gradient also uses Ink per-character `<Text color>`.
|
|
1216
|
+
- **The config wizard / overlays are unified on a "bottom-anchored" layout**, with list items and key hints trimmed to a **strict single line**, fixing content clipping in the VS Code terminal.
|
|
1217
|
+
- **Terminal state safety and clean exit**: `SIGINT` / `SIGTERM` / uncaught exceptions all uninstall Ink and restore raw mode and the cursor; `Ctrl+C` means "cancel the current round when busy, exit when idle"; exiting explicitly pauses stdin and terminates the process (`farewell()`).
|
|
1218
|
+
- **Removed the old line-based pipeline (dead code)**: `src/conversation-runner.ts`, `src/slash-hint-prompt.ts`, `src/delta-renderer.ts`, the inquirer-based `src/config-wizard.ts`, `src/draft-*.ts` and so on; added the new implementations `src/mode.ts` / `src/stdio-runner.ts` / `src/session-pointer.ts` / `src/conversation-persist-plugin.ts` / `src/config-editor.ts` and `src/tui/*`.
|
|
1219
|
+
- Docs: the Chinese and English READMEs and `docs/zh|en/*` synced (run modes, the `ai` command name, the configuration center, the multi-line editor, etc.), and `docs/manifest.json` synced.
|
|
161
1220
|
|
|
162
|
-
### ✅
|
|
1221
|
+
### ✅ Tests
|
|
163
1222
|
|
|
164
|
-
-
|
|
165
|
-
-
|
|
166
|
-
-
|
|
1223
|
+
- Full unit suite **13 files / 224 cases**, `tsc --noEmit` and `npm run build` report zero errors; e2e **12 cases** really hitting the DeepSeek API (covering both modes, stdio purity, exit codes, `--save` persistence).
|
|
1224
|
+
- New test files: `mode.test.ts`, `stdio-runner.test.ts`, `theme.test.ts`, `screens.test.tsx`, `tui/config-wizard.test.tsx`, `tui/agent-prompt.test.tsx`, `tui/highlight.test.ts`, `sub-agent-guard-plugin.test.ts`, `auto-migrate-confirm-plugin.test.ts`, `conversation-commands/registry.test.ts`.
|
|
1225
|
+
- Verified on a real PTY: startup → conversation → streaming → `/load` → `/clear` → exit and hand the shell back; the first-run credential flow; each sub-screen of the configuration center.
|
|
167
1226
|
|
|
168
1227
|
## [0.9.0] - 2026-09-30
|
|
169
1228
|
|
|
170
|
-
### 💥
|
|
1229
|
+
### 💥 Breaking changes
|
|
171
1230
|
|
|
172
|
-
-
|
|
173
|
-
-
|
|
1231
|
+
- **Upgraded `@ai-zen/agents-sdk` to `1.0.0-alpha.0` (Provider → Scope architecture refactor)** — SDK 1.0.0-alpha.0 renames the global entry object `Provider` to `Scope`, spreads the four capability sources into 5 composable `ScopePlugin`s, and removes `Provider.create()` (replaced by explicit assembly). The CLI side adapted `src/agent-creator.ts` accordingly:
|
|
1232
|
+
- import `Scope` / `allInOne` instead of `Provider`
|
|
174
1233
|
- `Provider.create({ config, agentsDir, subAgentsPaths, skillsPaths, toolsPaths, mcpPaths })` → `new Scope({ config, agentsDir }).use(...allInOne({ subAgentsPaths, skillsPaths, toolsPaths, mcpPaths }))` + `await scope.init()`
|
|
175
|
-
-
|
|
176
|
-
-
|
|
1234
|
+
- singleton / factory renames: `getProvider` → `getScope`, `resetProvider` → `resetScope`; `createAgent` now calls `sdkCreateAgent(scope, agentId)` internally
|
|
1235
|
+
- the dependency declaration changed from `^0.12.0` to the exact `1.0.0-alpha.0` (pinned during the alpha phase)
|
|
177
1236
|
|
|
178
|
-
### 📝
|
|
1237
|
+
### 📝 Docs
|
|
179
1238
|
|
|
180
|
-
-
|
|
181
|
-
- `docs/zh|en/getting-started.md
|
|
182
|
-
- `docs/manifest.json
|
|
1239
|
+
- Chinese and English READMEs and `docs/zh|en/{tools,mcp,skills}.md`: `Provider` / `getProvider()` synced to `Scope` / `getScope()`
|
|
1240
|
+
- `docs/zh|en/getting-started.md`: runtime dependency version synced to `@ai-zen/agents-sdk` `1.0.0-alpha.0`
|
|
1241
|
+
- `docs/manifest.json`: version number synced to `0.9.0`
|
|
183
1242
|
|
|
184
|
-
### ✅
|
|
1243
|
+
### ✅ Tests
|
|
185
1244
|
|
|
186
|
-
- `tsc --noEmit
|
|
187
|
-
-
|
|
188
|
-
- e2e
|
|
1245
|
+
- `tsc --noEmit` (`tsconfig.test.json`) and `npm run build` report zero errors
|
|
1246
|
+
- Full unit suite: 9 files / 74 cases pass
|
|
1247
|
+
- 6 e2e cases pass (including a real DeepSeek API conversation, verifying the whole CLI → SDK `1.0.0-alpha.0` → API chain)
|
|
189
1248
|
|
|
190
1249
|
## [0.8.2] - 2026-09-29
|
|
191
1250
|
|
|
192
|
-
### ⚠️
|
|
1251
|
+
### ⚠️ Dependency upgrades
|
|
193
1252
|
|
|
194
|
-
- **`@ai-zen/agents-sdk`
|
|
1253
|
+
- **`@ai-zen/agents-sdk` raised to `^0.12.0`** — SDK 0.12.0 adds the built-in tool `inspectFile` (built-in tool classes 19 → 20) and the `range` batched-read parameter of `readFile`, adds the config option `maxToolOutput` (32768 characters by default) with tool output protection behavior, and trims the output of `batchEdit`. This version has **no breaking changes**, and the CLI source needs no adaptation: types and default config are fully delegated to the SDK, `src/version.ts` reads the actually installed version, and the tool list is auto-discovered from the SDK's static registry, so the version banner now shows sdk `0.12.0`
|
|
195
1254
|
|
|
196
|
-
### 📝
|
|
1255
|
+
### 📝 Docs
|
|
197
1256
|
|
|
198
|
-
-
|
|
199
|
-
-
|
|
200
|
-
- `docs/zh|en/tools.md`
|
|
201
|
-
- `docs/zh|en/getting-started.md`
|
|
1257
|
+
- Synced the built-in tool count 19 → 20: the Chinese and English READMEs, `docs/zh|en/tools.md`, `docs/zh|en/index.md` and `docs/manifest.json`
|
|
1258
|
+
- Added `inspectFile` (surveying a file's structural overview) to the tool table, and documented the `range` parameter for `readFile`
|
|
1259
|
+
- `docs/zh|en/tools.md` gained an "output protection" section: when a limit is exceeded, `exec` / `findText` / `glob` / `ls` write to disk and return a warning, while `readFile` / `inspectFile` only warn without writing to disk
|
|
1260
|
+
- The runtime dependency version in `docs/zh|en/getting-started.md` synced to `@ai-zen/agents-sdk` `^0.12.0`
|
|
202
1261
|
|
|
203
|
-
### ✅
|
|
1262
|
+
### ✅ Tests
|
|
204
1263
|
|
|
205
|
-
-
|
|
1264
|
+
- Reuses the existing 9 files / 74 cases, all passing; `tsc --noEmit` and `npm run build` (`0.8.2`) both pass
|
|
206
1265
|
|
|
207
1266
|
## [0.8.1] - 2026-09-28
|
|
208
1267
|
|
|
209
|
-
### 🚀
|
|
1268
|
+
### 🚀 New features
|
|
210
1269
|
|
|
211
|
-
- **Skill
|
|
1270
|
+
- **Skill sub-agents are brought under the sub-agent context guard and streaming rendering** — relying on SDK 0.11.0 unifying `call_skill_sub_agent` under `AgentToolLazy`, skill sub-agents now share the same delegation boundary with regular SubAgents (`onSubAgentStart` / `onSubAgentEnd` hooks and `sub-agent-start` / `sub-agent-end` events). So the `SubAgentGuardInstallerPlugin` and the sub-agent streaming rendering introduced in 0.8.0 **automatically cover** that path, and this time the CLI has no source changes (the note in the 0.8.0 CHANGELOG's "coverage" saying "skill sub-agents are not covered" no longer applies)
|
|
212
1271
|
|
|
213
|
-
### ⚠️
|
|
1272
|
+
### ⚠️ Dependency upgrades
|
|
214
1273
|
|
|
215
|
-
- **`@ai-zen/agents-core`
|
|
1274
|
+
- **`@ai-zen/agents-core` raised to `^4.3.0`, `@ai-zen/agents-sdk` raised to `^0.11.0`** — `src/version.ts` reads the actually installed version, so the version banner now shows core `4.3.0` / sdk `0.11.0`. SDK 0.11.0's breaking change (removal of `createCallSkillSubAgentTool`'s third parameter `provider`) only affects code that calls that factory function directly; the CLI uses it indirectly through `Provider.instantiate` and needs no adaptation
|
|
216
1275
|
|
|
217
|
-
### ✅
|
|
1276
|
+
### ✅ Tests
|
|
218
1277
|
|
|
219
|
-
-
|
|
1278
|
+
- Reuses the existing 9 files / 74 cases, all passing; `tsc --noEmit` and `npm run build` (`0.8.1`) both pass
|
|
220
1279
|
|
|
221
1280
|
## [0.8.0] - 2026-09-28
|
|
222
1281
|
|
|
223
|
-
### 🚀
|
|
1282
|
+
### 🚀 New features
|
|
224
1283
|
|
|
225
|
-
-
|
|
226
|
-
-
|
|
227
|
-
-
|
|
228
|
-
-
|
|
229
|
-
-
|
|
230
|
-
-
|
|
231
|
-
-
|
|
1284
|
+
- **Sub-agent context guard (`SubAgentGuardInstallerPlugin`)** — closes the gap where "the main Agent has a guard while sub-agents have none" (added `src/sub-agent-guard-plugin.ts`):
|
|
1285
|
+
- **Background**: SDK sub-agents are built by `AgentTool` / `AgentToolLazy` via `new Agent({...})` at delegation time; a newly built instance carries no plugins (`Agent._plugins` is per-instance and does not propagate to sub-agents), and sub-agents go through `run()` rather than `send()` (`onBeforeSend` / `onAfterSend` never fire) — therefore the `ContextGuardPlugin` and the auto-migration plugin on the main Agent have no effect at all on sub-agents, and a sub-agent's inner loop is unconstrained
|
|
1286
|
+
- **Intervention point**: core 4.2.0's `onSubAgentStart` hook is dispatched by the host Agent at the moment "the sub-agent has been built and `run()` has not been called yet", with a payload carrying the sub-agent instance; `use()` merely pushes a plugin into `_plugins`, and the hook dispatch iterates that list live every time, so installing into the sub-agent inside the hook takes effect **for the current `run()` immediately** (no `init()` needed, and `AgentTool` does not call a sub-agent's `init()` either)
|
|
1287
|
+
- **Constraint policy**: install the same `ContextGuardPlugin` the main Agent uses onto the sub-agent — the same `maxTokens` (the current model's `maxContextTokens`), likewise without a ratio (keeping the SDK default 1.5), so that "context tokens exceeding `maxTokens` × 1.5 aborts" holds for sub-agents as well; **no round limit and no tool-call-count limit are set** (this is the only constraint)
|
|
1288
|
+
- **Interruption semantics are a "soft abort"**: the sub-agent's run ends immediately, the exception is thrown up to the parent Agent through `AgentTool`'s `finally` (which dispatches `onSubAgentEnd`), and since the parent Agent has `allowJsonParseError = true` by default, it is downgraded to a single tool result text (`error while executing tool X: …`) and the parent Agent proceeds to the next round — the money-burning sub-agent is stopped without terminating the user's conversation
|
|
1289
|
+
- **Recursive coverage**: the installer installs itself into the sub-agent at delegation time, so further delegation by the sub-agent (grandchild level) is constrained as well (the default SubAgent definition is `subagents: deny`, so that path does not trigger by default and only takes effect when recursion is opened up)
|
|
1290
|
+
- **Coverage**: only core's two delegation paths `AgentTool` / `AgentToolLazy` (both dispatch `onSubAgentStart`); the SDK's `call_skill_sub_agent` (skill sub-agents) does not dispatch that hook and is not covered here
|
|
232
1291
|
|
|
233
|
-
### ⚠️
|
|
1292
|
+
### ⚠️ Dependency upgrades
|
|
234
1293
|
|
|
235
|
-
- **`@ai-zen/agents-core`
|
|
1294
|
+
- **`@ai-zen/agents-core` raised to `^4.2.0`, `@ai-zen/agents-sdk` raised to `^0.10.0`** — `src/version.ts` reads the actually installed version, so the version banner now shows core `4.2.0` / sdk `0.10.0`; the `include_manifest` switch of `load_mcp` added in SDK 0.10.0 is a backward-compatible optional parameter (default `true`, behavior byte-for-byte identical to before the upgrade), so the CLI needs no adaptation
|
|
236
1295
|
|
|
237
|
-
### 🔧
|
|
1296
|
+
### 🔧 Fixes
|
|
238
1297
|
|
|
239
|
-
-
|
|
1298
|
+
- **Sub-agent events aligned with core 4.2.0's renames and payload structure** — core 4.2.0 renames the event `sub-agent` to `sub-agent-start` and changes the payload from `{ agent: sub-agent, ctx }` to `SubAgentContext = { agent: main agent, subAgent: sub-agent, toolCallContext }`. `src/conversation-runner.ts` adapted accordingly: event listening and unsubscription now use `sub-agent-start` (listening to the old name no longer fires, which would silently disable sub-agent streaming rendering); the sub-agent instance is now read from the `subAgent` field, and the tool name from `toolCallContext.tool_call.function.name` (previously reading the now-nonexistent `ctx` field threw inside the `sub-agent-end` callback)
|
|
240
1299
|
|
|
241
|
-
### ✅
|
|
1300
|
+
### ✅ Tests
|
|
242
1301
|
|
|
243
|
-
-
|
|
244
|
-
- `tsc --noEmit`
|
|
1302
|
+
- Added `src/sub-agent-guard-plugin.test.ts` (6 cases): the installer's plugin list and "does not reject delegation", immediate effect on the current `run()` after installation (no `init()` needed), pass-through when usage exactly equals the hard limit, pass-through on the first round with no usage data, the threshold following the passed `maxTokens`, and grandchild-level propagation
|
|
1303
|
+
- `tsc --noEmit` type checking, the full unit suite and `npm run build` pass (74 passed / 9 files)
|
|
245
1304
|
|
|
246
1305
|
## [0.7.0] - 2026-09-20
|
|
247
1306
|
|
|
248
|
-
### 🚀
|
|
1307
|
+
### 🚀 New features
|
|
249
1308
|
|
|
250
|
-
-
|
|
251
|
-
- `AutoMigrateConfirmPlugin`
|
|
252
|
-
-
|
|
253
|
-
-
|
|
254
|
-
-
|
|
255
|
-
-
|
|
256
|
-
-
|
|
257
|
-
-
|
|
258
|
-
-
|
|
259
|
-
-
|
|
260
|
-
-
|
|
261
|
-
-
|
|
1309
|
+
- **Second confirmation for auto-migration** — auto-migration triggered by exceeding the context token limit no longer runs silently; the user is asked to confirm before migrating (added `src/auto-migrate-confirm-plugin.ts`):
|
|
1310
|
+
- `AutoMigrateConfirmPlugin` extends the SDK's `AutoMigratePlugin` and **only inserts one confirmation between "threshold exceeded" and "migration invoked"**: threshold checking, migration invocation and error handling still reuse the base class, and the actual migration is still delegated to the shared `migrationService` (the same chain as `/migrate`) — i.e. the SDK-side semantics of "only responsible for triggering" are unchanged, and the confirmation is an increment on the CLI layer
|
|
1311
|
+
- The confirmation dialog shows the current usage and the threshold (e.g. `260000/250000 tokens`) and explains the impact (generate a handoff document → save the current conversation → start a new session and continue), and **defaults to "yes"** (Enter migrates, consistent with `/migrate`'s default)
|
|
1312
|
+
- Choosing "no" only skips **this** migration and records no implicit state: the current conversation is unaffected (the history is not trimmed), and the user can keep asking or run `/migrate` at any time; when still over the limit, the prompt appears again after the next send, noting that continued growth into severe overflow will be aborted by `ContextGuardPlugin`
|
|
1313
|
+
- **No prompt in non-interactive environments**: the confirmation dialog is only shown when both stdin and stdout are TTYs; pipes, redirection and e2e scripts have no confirmation channel, so the existing silent auto-migration behavior is kept (no blocking on input, no extra output, pipe semantics unchanged)
|
|
1314
|
+
- The conversation wiring (`src/conversation-runner.ts`) now registers `AutoMigrateConfirmPlugin`, while the other plugins (including the `ContextGuardPlugin` guard position) are unchanged
|
|
1315
|
+
- **In-conversation command hints** — typing `/` on the chat input line immediately lists the available commands and their descriptions; further input narrows the candidates by prefix, and when nothing matches it shows "no matching command (type /help to see all)":
|
|
1316
|
+
- Added `src/slash-hint-prompt.ts`: it extends inquirer's input prompt and overrides `render`, handing the candidate commands to inquirer's `ScreenManager` as the bottom bar (`bottomContent`), so its wrapping, terminal resize, erasing and cursor restoration handling is reused and rendering matches the other prompts
|
|
1317
|
+
- **Hints only, submission semantics unchanged**: Enter always submits the input verbatim, and commands are still dispatched by `dispatchCommand` using exact matching on the full name (no prefix completion, no highlighted selection)
|
|
1318
|
+
- In non-TTY environments (stdin/stdout redirected, e2e scripts) the hint is simply not rendered and it degrades to a plain input line, with the existing output behavior unchanged
|
|
1319
|
+
- Ordinary chat content (not starting with `/`) produces no hint at all
|
|
1320
|
+
- The main conversation loop now gets its input from `askInput()` in `src/slash-hint-prompt.ts` (`src/conversation-runner.ts` no longer constructs the input prompt directly)
|
|
262
1321
|
|
|
263
|
-
### 🎯
|
|
1322
|
+
### 🎯 Optimizations
|
|
264
1323
|
|
|
265
|
-
-
|
|
266
|
-
-
|
|
267
|
-
- `/help`
|
|
268
|
-
- `getCommandNames()`
|
|
1324
|
+
- **The conversation command list is collapsed into a single source** — added `src/conversation-commands/registry.ts`, which declares command names, aliases and descriptions in one place, shared by dispatch (`index.ts`), help (`help.ts`) and input hints:
|
|
1325
|
+
- **Aliases are no longer registered twice**: aliases declared by the registry are automatically expanded by `index.ts` into keys of the handler table; if the registry declares a command with no registered handler, an error is raised at startup (fail-fast)
|
|
1326
|
+
- `/help` output is now rendered by the registry, with the command column aligned to the longest command string and consistent with the input hint's column width (the command and description texts are unchanged)
|
|
1327
|
+
- `getCommandNames()` moved to the registry, and `conversation-commands/index.ts` re-exports it as before, so callers need no changes
|
|
269
1328
|
|
|
270
|
-
### ⚠️
|
|
1329
|
+
### ⚠️ Dependency constraints
|
|
271
1330
|
|
|
272
|
-
-
|
|
273
|
-
-
|
|
1331
|
+
- **References the inquirer internal module** `inquirer/lib/prompts/input.js` — inquirer 9 does not export BasePrompt/InputPrompt, so implementing the command-hint bottom bar requires a deep import (inquirer@9's `package.json` has no `exports` field, and ESM deep imports were verified working on Node 26 + inquirer 9.3.8). Type declarations live in `src/typings/inquirer-internals.d.ts`; **when upgrading inquirer's major version, that module path and the `render` behavior must be re-verified**
|
|
1332
|
+
- **No new runtime dependency**, `pnpm-lock.yaml` unchanged
|
|
274
1333
|
|
|
275
|
-
### ✅
|
|
1334
|
+
### ✅ Tests
|
|
276
1335
|
|
|
277
|
-
-
|
|
278
|
-
-
|
|
279
|
-
-
|
|
280
|
-
- `tsc`
|
|
281
|
-
-
|
|
1336
|
+
- Added `src/auto-migrate-confirm-plugin.test.ts` (threshold checks, the confirm and decline branches, non-interactive degradation, TTY detection and the default confirmation text)
|
|
1337
|
+
- Added `src/slash-hint-prompt.test.ts` (hint text generation + bottom bar rendering, covering the TTY / non-TTY / submitted / error-line branches)
|
|
1338
|
+
- Added `src/conversation-commands/registry.test.ts` (metadata, alias folding, prefix matching, command dispatch and alias equivalence)
|
|
1339
|
+
- `tsc` type checking and the full unit suite pass (68 passed / 8 files), and `npm run build` succeeds
|
|
1340
|
+
- The build output was verified on a real TTY: typing `/` lists all commands, `/b` narrows to `/back`, Enter submits the raw text and the hint line is erased correctly; `/help` output format and column widths are normal
|
|
282
1341
|
|
|
283
1342
|
## [0.6.3] - 2026-09-01
|
|
284
1343
|
|
|
285
|
-
### 🔧
|
|
1344
|
+
### 🔧 Fixes
|
|
286
1345
|
|
|
287
|
-
-
|
|
288
|
-
-
|
|
289
|
-
-
|
|
1346
|
+
- **Fixed old messages being neither marked omit nor physically deleted after migration (dependency upgrade)** — upstream `@ai-zen/agents-sdk` 0.9.3 fixes the defect where `createAgent` referenced `definition.messages` directly, causing conversation `append` to pollute the Agent definition template in reverse; that defect had made task migration's `prune`/`omit` strategies ineffective (old messages were not cleaned up, yet breakpoint messages could be injected)
|
|
1347
|
+
- **Copy the message array when restoring a draft / saved conversation** — `src/agent-creator.ts` changed from a direct reference to `agent.messages = [...messages]`, so later `append` calls do not rewrite the in-memory array of a draft / conversation archive
|
|
1348
|
+
- **Dependencies upgraded along** — `@ai-zen/agents-sdk` to `0.9.3` (resolved automatically through the caret range)
|
|
290
1349
|
|
|
291
|
-
### ✅
|
|
1350
|
+
### ✅ Tests
|
|
292
1351
|
|
|
293
|
-
- `tsc`
|
|
1352
|
+
- `tsc` type checking and the full unit suite pass (37 passed / 5 files)
|
|
294
1353
|
|
|
295
1354
|
## [0.6.2] - 2026-08-31
|
|
296
1355
|
|
|
297
|
-
### 🎯
|
|
1356
|
+
### 🎯 Optimizations
|
|
298
1357
|
|
|
299
|
-
-
|
|
300
|
-
-
|
|
301
|
-
-
|
|
1358
|
+
- **Startup version banner enhanced** — added `src/version.ts`, which uniformly reads version numbers from the actually installed packages (rather than the version ranges in the dependency declarations); the main menu version row is expanded from printing only the CLI version to showing the CLI / SDK / Core versions together, e.g. `AI-Zen CLI v0.6.2 · SDK 0.9.2 · Core 4.1.0`
|
|
1359
|
+
- **Adapted to `@ai-zen/agents-core` 4.1.0 type changes** — upstream changed `AgentNS.Message.id` from optional to **mandatory** (auto-generated on construction). Bare message objects constructed directly in `src/menus/agents.ts` and `src/conversation-commands/back.test.ts` now use `Message`'s built-in factory methods (`Message.System` / `Message.User` / `Message.Assistant`, and `new Message(...)`), letting core generate the message `id` uniformly
|
|
1360
|
+
- **Dependencies upgraded along** — `@ai-zen/agents-sdk` to `0.9.2`, `@ai-zen/agents-core` to `4.1.0` (resolved automatically through the caret ranges)
|
|
302
1361
|
|
|
303
|
-
### ✅
|
|
1362
|
+
### ✅ Tests
|
|
304
1363
|
|
|
305
|
-
-
|
|
1364
|
+
- After the dependency upgrade, `tsc` type checking and the full unit suite pass (37 passed / 5 files), and `npm run build` succeeds
|
|
306
1365
|
|
|
307
1366
|
## [0.6.1] - 2026-08-28
|
|
308
1367
|
|
|
309
|
-
### 🎯
|
|
1368
|
+
### 🎯 Optimizations
|
|
310
1369
|
|
|
311
|
-
-
|
|
312
|
-
- **`omit
|
|
313
|
-
- **`prune
|
|
314
|
-
- CLI `migration-service.ts`
|
|
1370
|
+
- **Upgraded `@ai-zen/agents-sdk` to 0.9.0** — the migration service gains a `strategy` switch:
|
|
1371
|
+
- **`omit` (default)**: historical messages are marked `omit: true` and kept for auditability, and a "conversation breakpoint" message is appended as the starting point of the new context
|
|
1372
|
+
- **`prune` (used by the CLI)**: history is physically removed, keeping only the system prompt + handoff breakpoint (i.e. the previous replacement behavior)
|
|
1373
|
+
- CLI `migration-service.ts` explicitly enables `strategy: "prune"`, consistent with the client-side product semantics of "start a new session after migration", with **no difference in external usage**
|
|
315
1374
|
|
|
316
|
-
### ✅
|
|
1375
|
+
### ✅ Tests
|
|
317
1376
|
|
|
318
|
-
-
|
|
1377
|
+
- After the dependency upgrade the full unit suite passes (37 passed), and `tsc --noEmit` reports zero errors
|
|
319
1378
|
|
|
320
1379
|
## [0.6.0] - 2026-08-24
|
|
321
1380
|
|
|
322
|
-
### 💥
|
|
1381
|
+
### 💥 Breaking changes
|
|
323
1382
|
|
|
324
|
-
-
|
|
325
|
-
- **`TaskMigrationService`
|
|
326
|
-
- **`AutoMigratePlugin`
|
|
327
|
-
- **CLI
|
|
1383
|
+
- **Upgraded `@ai-zen/agents-sdk` to 0.8.0** — the task migration capability was refactored inside the SDK (see the SDK CHANGELOG for details):
|
|
1384
|
+
- **`TaskMigrationService` becomes an instantiable, self-contained migration service** — the constructor takes only `{ onBeforeMigrate?, onMigrated?, logger? }` (hooks flat, no `hooks` wrapper), and `migrate({ agent })` directly reuses the passed `agent`'s `client`/`model`/`modelConfig` to generate the handoff document, with no Provider and no dedicated migration Agent needed; it exposes a structured `MigrationContext`
|
|
1385
|
+
- **`AutoMigratePlugin` narrows to a trigger-only role** — the constructor injects `{ service, maxTokens }`, and it only detects token overflow and delegates to `service.migrate`
|
|
1386
|
+
- **The CLI removes `createMigrationAgent`** — the migration Agent construction in `src/agent-creator.ts` that depended on the deleted `TaskMigrationService.createAgentDefinition` is removed; the manual migration command and the auto-migration callback both now use the SDK migration service instance, with hooks flat on the service constructor
|
|
328
1387
|
|
|
329
|
-
### 🚀
|
|
1388
|
+
### 🚀 New features
|
|
330
1389
|
|
|
331
|
-
-
|
|
332
|
-
-
|
|
333
|
-
-
|
|
334
|
-
-
|
|
1390
|
+
- **Manual task migration command `/migrate`** — typing `/migrate` in a conversation triggers task migration at any time, without waiting for the token limit to be exceeded:
|
|
1391
|
+
- Migration steps: save the current conversation → generate a handoff document → start a new session (injecting the handoff document as context) → write the new conversation to disk as a draft (`_current.json`)
|
|
1392
|
+
- Reuses the SDK's `TaskMigrationService.migrate` together with auto-migration, handling the post-migration save through the `onMigrated` hook
|
|
1393
|
+
- Non-destructive: the old conversation is already saved under `conversations/`, so on failure the user can retry or continue the current conversation
|
|
335
1394
|
|
|
336
|
-
### 🎯
|
|
1395
|
+
### 🎯 Optimizations
|
|
337
1396
|
|
|
338
|
-
-
|
|
339
|
-
-
|
|
1397
|
+
- **The migration service is collapsed to a single instance** — the `TaskMigrationService` instance is mounted on `ConversationContext.migrationService`, shared by auto-migration (triggered by `AutoMigratePlugin`) and manual migration (the `/migrate` command) as **the same instance**; pre/post-migration handling (saving the old conversation / starting a new session / writing the draft to disk) is unified into that service's hooks, eliminating the separate `new TaskMigrationService` on both the automatic and the manual side and the duplicated post-processing implementations
|
|
1398
|
+
- **The migration service is extracted into a standalone module** — added `src/migration-service.ts`, where `createMigrationService(ctx)` builds the migration service instance uniformly (including the pre/post-migration hooks); `saveCurrentConversation` is promoted to the shared `saveConversation` in `conversation-repository.ts`, used by both the migration hooks and error saving
|
|
340
1399
|
|
|
341
|
-
### ✅
|
|
1400
|
+
### ✅ Tests
|
|
342
1401
|
|
|
343
|
-
- SDK
|
|
344
|
-
- CLI
|
|
1402
|
+
- SDK side: `TaskMigrationService` (including `migrate` / serialization / hook context) and `AutoMigratePlugin` (trigger delegation assertions) all pass (447 passed / 2 skipped)
|
|
1403
|
+
- CLI side: `pretest` type checking and the full unit suite pass (37 passed), and `npm run build` succeeds
|
|
345
1404
|
|
|
346
1405
|
## [0.5.0] - 2026-08-23
|
|
347
1406
|
|
|
348
|
-
### 💥
|
|
1407
|
+
### 💥 Breaking changes
|
|
349
1408
|
|
|
350
|
-
-
|
|
351
|
-
- **`createModel()`
|
|
352
|
-
- **`SdkAgent`
|
|
353
|
-
- **CLI
|
|
1409
|
+
- **Upgraded `@ai-zen/agents-core` to 4.0.0 and `@ai-zen/agents-sdk` to 0.7.0** — along with the underlying core refactor (official OpenAI SDK + plugin-based Agent driver layer):
|
|
1410
|
+
- **`createModel()` return structure changed** — instead of returning an old `ChatCompletionModel` instance it now returns `{ client, model, modelConfig }` (openai SDK client + model name + model parameters); `createMigrationAgent` destructures it accordingly and passes it to `SdkAgent`'s new constructor signature
|
|
1411
|
+
- **`SdkAgent` constructor signature changed** — from `{ ..., model }` (a model object) to `{ client, model, modelConfig }`
|
|
1412
|
+
- **The rest of the CLI is fully compatible** — stream events (`open`/`chunk`/`error`/`sub-agent`/`sub-agent-end`), plugins (`AutoMigratePlugin`/`AutoRefreshToolsPlugin`/`ContextGuardPlugin`) and `AgentNS` types (`Delta`/`StreamResponseData`/`Message`) are kept as-is in core 4.0.0 / sdk 0.7.0, and `AgentPlugin`/`SendContext` are re-exported by the sdk, with no breakage
|
|
354
1413
|
|
|
355
|
-
### ✅
|
|
1414
|
+
### ✅ Tests
|
|
356
1415
|
|
|
357
|
-
-
|
|
1416
|
+
- After the dependency upgrade the full unit suite passes (37 passed / 0 failed), and `tsc --noEmit` reports zero errors
|
|
358
1417
|
|
|
359
|
-
### 🎯
|
|
1418
|
+
### 🎯 Optimizations
|
|
360
1419
|
|
|
361
|
-
- **`test:all` / `test:e2e`
|
|
1420
|
+
- **`test:all` / `test:e2e` pre-build** — e2e tests run `dist/index.js`, and previously a missing or stale `dist` caused e2e failures (after an upgrade it once reported `AgentContext must have a client` because of a stale `dist`). `npm run build` now runs automatically before e2e, so a clean environment (a fresh CI clone) works out of the box
|
|
362
1421
|
|
|
363
1422
|
## [0.4.0] - 2026-08-05
|
|
364
1423
|
|
|
365
|
-
### 🚀
|
|
1424
|
+
### 🚀 New features
|
|
366
1425
|
|
|
367
|
-
-
|
|
368
|
-
-
|
|
369
|
-
-
|
|
370
|
-
- **MCP
|
|
371
|
-
-
|
|
1426
|
+
- **Upgraded `@ai-zen/agents-core` to 3.4.0 and `@ai-zen/agents-sdk` to 0.6.0** — the underlying upgrade brings out-of-the-box enhancements:
|
|
1427
|
+
- **Abort signal support for built-in tools** — `sleep`/`exec`/`downloadFile`/`generateImage`/`glob`/`findText` and the subAgent system listen to the `abort` signal, so long-running operations can be interrupted promptly when the CLI terminates a conversation, avoiding stranded resources (`exec` can distinguish `aborted` from `timeout` termination reasons)
|
|
1428
|
+
- **Tool definition cohesion refactor** — the `Tool` base class is slimmed down (no longer requires passing `type`/`function`), definition and implementation live in the same class, the callback signature changes from `this` injection to explicit `(parsed_args, ctx)` parameters, and the `SdkCallbackTool` constructor becomes an `{env}` container passing through the full `ToolCallContext`
|
|
1429
|
+
- **MCP tool signal pass-through** — `call_mcp_tool` / `read_mcp_resource` support interruption
|
|
1430
|
+
- The CLI layer does not use the affected APIs directly, so this is a smooth non-breaking upgrade with no functional impact
|
|
372
1431
|
|
|
373
|
-
### ✅
|
|
1432
|
+
### ✅ Tests
|
|
374
1433
|
|
|
375
|
-
-
|
|
1434
|
+
- After the dependency upgrade the full unit suite passes (37 passed / 0 failed)
|
|
376
1435
|
|
|
377
1436
|
## [0.3.5] - 2026-08-13
|
|
378
1437
|
|
|
379
|
-
### 🎯
|
|
1438
|
+
### 🎯 Optimizations
|
|
380
1439
|
|
|
381
|
-
-
|
|
382
|
-
- **`innerLoopTasks`
|
|
383
|
-
-
|
|
384
|
-
- **SDK 0.5.7
|
|
1440
|
+
- **Upgraded `@ai-zen/agents-core` to 3.3.1 and `@ai-zen/agents-sdk` to 0.5.7** — following upstream internal refactors, out of the box, with no CLI code changes:
|
|
1441
|
+
- **`innerLoopTasks` split into two set semantics** — Core 3.3.1 adds `innerLoopsTasks` (whole-group task records) and `innerLoopTasks` (tasks active in the current round); `abort()` now only aborts tasks active in the current round and no longer mislabels completed rounds
|
|
1442
|
+
- **An Assistant placeholder is appended uniformly at the start of the inner loop** — `send()` / `AgentTool` no longer append the Assistant manually; `run()` handles it uniformly at the start of every inner loop, and multi-round tool calls converge there as well
|
|
1443
|
+
- **SDK 0.5.7 upgraded in sync** — depends on core 3.3.1, all 426 tests (including real DeepSeek e2e) pass, fully compatible
|
|
385
1444
|
|
|
386
1445
|
## [0.3.4] - 2026-08-14
|
|
387
1446
|
|
|
388
|
-
### 🎯
|
|
1447
|
+
### 🎯 Optimizations
|
|
389
1448
|
|
|
390
|
-
-
|
|
391
|
-
- **`ToolCallContext`
|
|
392
|
-
-
|
|
1449
|
+
- **Upgraded `@ai-zen/agents-core` to 3.3.0 and `@ai-zen/agents-sdk` to 0.5.6** — following the upstream unified refactor, out of the box, with no CLI code changes:
|
|
1450
|
+
- **`ToolCallContext` runs through "interception decision → execution"** — Core 3.3.0 unifies `FunctionCallContext` into `ToolCallContext`: the `onToolCall` hook and `Tool.exec(ctx)` receive the same instance; the fields `tool_call` (unified shape), `tool` (the matched tool) and `signal` (abort signal) are added, and `toolCall` is renamed to `tool_call`. A `@deprecated FunctionCallContext` compatibility alias is kept, so old code needs no changes
|
|
1451
|
+
- **New `onToolCall` interception hook** — provided by both Core 3.3.0 and SDK 0.5.6: before each tool call executes it can **reject** it (returning a string = the rejection reason returned to the LLM as the tool result, the tool does not execute, and the next round continues); on the SDK side `AgentPlugin` supports the hook (multiple plugins are called in registration order, and the first string returned rejects)
|
|
393
1452
|
|
|
394
1453
|
## [0.3.3] - 2026-08-05
|
|
395
1454
|
|
|
396
|
-
### 🎯
|
|
1455
|
+
### 🎯 Optimizations
|
|
397
1456
|
|
|
398
|
-
-
|
|
1457
|
+
- **Dependency declarations unified as version ranges** — `@ai-zen/agents-core` and `@ai-zen/agents-sdk` changed from exact versions (`3.2.0` / `0.5.3`) to caret ranges (`^3.2.0` / `^0.5.3`), aligned with the SDK side's `workspace:^` for core (published as `^3.x`). This allows compatible upgrades within each major version and avoids repeatedly editing dependency declarations when upgrading; the resolved versions remain `3.2.0` / `0.5.3` (current latest), with no functional impact
|
|
399
1458
|
|
|
400
1459
|
## [0.3.2] - 2026-08-05
|
|
401
1460
|
|
|
402
|
-
### 🚀
|
|
1461
|
+
### 🚀 New features
|
|
403
1462
|
|
|
404
|
-
-
|
|
1463
|
+
- **Wired in the `ContextGuardPlugin` context guard** — registers the `ContextGuardPlugin` provided by SDK 0.5.3 in `conversation-runner.ts`'s plugin assembly (placed before `AutoMigratePlugin`, reusing the same `maxTokens`). Before each inner-loop request it checks usage and, once `maxTokens×1.2` (20% over the threshold) is exceeded, throws `ContextOverflowError` to abort the conversation, preventing sudden overflow (such as reading a huge file) from blowing up the context; it complements the migration plugin's range: normal overflow goes through handoff migration, while severe overflow is aborted directly by the guard with an error
|
|
405
1464
|
|
|
406
|
-
### ✨
|
|
1465
|
+
### ✨ Optimizations
|
|
407
1466
|
|
|
408
|
-
-
|
|
1467
|
+
- **Fixed the plugin registration comment numbering in `conversation-runner.ts`** — unified consecutive numbering (1 cwdTracker / 2 autoRefreshTools / 3 draftPlugin / 4 contextGuard / 5 autoMigrate), removing the previous duplicate numbering
|
|
409
1468
|
|
|
410
1469
|
## [0.3.1] - 2026-08-05
|
|
411
1470
|
|
|
412
|
-
### 🚀
|
|
1471
|
+
### 🚀 New features
|
|
413
1472
|
|
|
414
|
-
-
|
|
415
|
-
-
|
|
416
|
-
- **`load_mcp`
|
|
417
|
-
-
|
|
1473
|
+
- **Upgraded `@ai-zen/agents-sdk` to 0.5.3** — new SDK capabilities available out of the box:
|
|
1474
|
+
- **A default socket-pty MCP config is released on initialization** — SDK `bootstrap()` adds `ensureDefaultMcpConfig()`; on first start the CLI automatically writes a default MCP config containing the `socket-pty` terminal (`npx -y @ai-zen/socket-pty mcp`) to `~/.ai-zen/mcp.json`, idempotently and without overwriting an existing file
|
|
1475
|
+
- **`load_mcp` passes through the server description** — enumerates each MCP server's description for the LLM to reference (aligned with `load_skill`)
|
|
1476
|
+
- **New `ContextGuardPlugin` context guard** — checks usage before sending a request and, once `maxTokens×1.2` (20% over the threshold) is exceeded, throws `ContextOverflowError` to abort the conversation, preventing a huge file read from letting the context run away; responsibilities are separated from `AutoMigratePlugin` and the ranges complement each other
|
|
418
1477
|
|
|
419
|
-
### 🔧
|
|
1478
|
+
### 🔧 Fixes
|
|
420
1479
|
|
|
421
|
-
-
|
|
1480
|
+
- **The new conversation was not saved as a draft immediately after auto-migration** — `AutoMigratePlugin` migrates in `onAfterSend` (all inner loops have ended, so `DraftPlugin`'s `onInnerLoopEnd` no longer fires), so the new opening message produced after migration replaced `agent.messages` was not written to disk in time, and a user exiting midway lost the post-migration opening. The new messages are now written to the draft (`_current.json`) immediately after migration completes in the `onMigrated` callback, closing the loop together with "the original conversation is saved to `conversations/` first"
|
|
422
1481
|
|
|
423
|
-
### 🎯
|
|
1482
|
+
### 🎯 Optimizations
|
|
424
1483
|
|
|
425
|
-
- **MCP
|
|
1484
|
+
- **The MCP config structure is unified on the industry-standard `mcpServers`** — following the SDK (`discoverMcpServers`), the CLI's `McpConfig` top-level field changes from `servers` to `mcpServers`, and the server transport field from `transport` to `type`. This eliminates format drift, fully aligning the CLI management UI (`zen config`) with the mcp.json released by the SDK by default, so the default socket-pty config can be displayed and managed correctly
|
|
426
1485
|
|
|
427
|
-
### ✅
|
|
1486
|
+
### ✅ Tests
|
|
428
1487
|
|
|
429
|
-
-
|
|
1488
|
+
- Updated the MCP read/write assertions in `config.test.ts` to match the new `mcpServers` structure and `type` field
|
|
430
1489
|
|
|
431
1490
|
## [0.3.0] - 2026-08-01
|
|
432
1491
|
|
|
433
|
-
### 💥
|
|
1492
|
+
### 💥 Breaking changes
|
|
434
1493
|
|
|
435
|
-
-
|
|
436
|
-
-
|
|
437
|
-
-
|
|
438
|
-
-
|
|
439
|
-
-
|
|
1494
|
+
- **Session/draft persistence moved down into the CLI** — SDK 0.5.0 removed the session/draft product layer (`ConversationRepository` / `DraftRepository` / `AutoDraftPlugin`). The CLI builds its own local storage:
|
|
1495
|
+
- Added `src/conversation-repository.ts` — `conversationRepository` (reusing the SDK's `EntityRepository`)
|
|
1496
|
+
- Added `src/draft-repository.ts` — `DraftRepository` + the `draftRepository` singleton (drafts have no id field, so it is implemented separately)
|
|
1497
|
+
- Added `src/draft-plugin.ts` — the CLI draft plugin `DraftPlugin` (replacing the removed `AutoDraftPlugin`, auto-saving in `onInnerLoopEnd`)
|
|
1498
|
+
- **Updated `@ai-zen/agents-sdk` to 0.5.0** — built-in tools turned into classes (`SdkCallbackTool` + `ToolEnv` injection), `Provider` supports `cwd`/`env`, `generateImage` turned into the `GenerateImageTool` class
|
|
440
1499
|
|
|
441
|
-
### 🎯
|
|
1500
|
+
### 🎯 Optimizations
|
|
442
1501
|
|
|
443
|
-
-
|
|
1502
|
+
- **Dependencies synced to the published versions** — `@ai-zen/agents-core` `3.0.1`, `@ai-zen/agents-sdk` `0.5.0` (a local `link:` was used for debugging during development and has been restored to npm version numbers)
|
|
444
1503
|
|
|
445
1504
|
## [0.2.3] - 2026-07-29
|
|
446
1505
|
|
|
447
|
-
### 🎯
|
|
1506
|
+
### 🎯 Optimizations
|
|
448
1507
|
|
|
449
|
-
-
|
|
1508
|
+
- **Updated `@ai-zen/agents-sdk` to 0.4.0** — the load_mcp return value changed to structured JSON (including the full inputSchema), and the logging API changed to the global singleton `getLogger()`/`setLogger()`
|
|
450
1509
|
|
|
451
1510
|
## [0.2.2] - 2026-07-27
|
|
452
1511
|
|
|
453
|
-
### 🎯
|
|
1512
|
+
### 🎯 Optimizations
|
|
454
1513
|
|
|
455
|
-
-
|
|
456
|
-
-
|
|
1514
|
+
- **Updated `@ai-zen/agents-sdk` to 0.3.4** — includes the AutoMigratePlugin hook rename (`onHandoff` → `onMigrated`)
|
|
1515
|
+
- **Fixed the timing of saving the old conversation** — moved from `onMigrated` (formerly `onHandoff`) to `onBeforeMigrate`, ensuring that what is saved is the complete old conversation history
|
|
457
1516
|
|
|
458
1517
|
## [0.2.1] - 2026-07-27
|
|
459
1518
|
|
|
460
|
-
### 🎯
|
|
1519
|
+
### 🎯 Optimizations
|
|
461
1520
|
|
|
462
|
-
-
|
|
1521
|
+
- **Updated `@ai-zen/agents-sdk` to 0.3.3** — includes default Agent/SubAgent prompt improvements (information explicitness requirements, contradiction detection)
|
|
463
1522
|
|
|
464
1523
|
## [0.2.0] - 2026-07-26
|
|
465
1524
|
|
|
466
|
-
### 💥
|
|
1525
|
+
### 💥 Breaking changes
|
|
467
1526
|
|
|
468
|
-
-
|
|
469
|
-
- **`readConfig()` / `saveConfig()`
|
|
470
|
-
- **`createAgent()`
|
|
471
|
-
- **`installHook()` / `uninstallHook()`
|
|
1527
|
+
- **Synced to SDK 0.3.2's async API** — `@ai-zen/agents-sdk` from `0.3.1` → `0.3.2`
|
|
1528
|
+
- **`readConfig()` / `saveConfig()` changed to async** — because the SDK's `ConfigManager` is fully async
|
|
1529
|
+
- **`createAgent()` restored to async** — because the SDK's `createAgent()` is async again
|
|
1530
|
+
- **`installHook()` / `uninstallHook()` changed to async** — shell hook install/uninstall uses `fs.promises`
|
|
472
1531
|
|
|
473
|
-
### 🎯
|
|
1532
|
+
### 🎯 Optimizations
|
|
474
1533
|
|
|
475
|
-
-
|
|
476
|
-
- **`getConversationsList()`
|
|
477
|
-
- **MCP
|
|
1534
|
+
- **Synchronous file IO fully eliminated** — every `existsSync`, `readFileSync`, `writeFileSync`, `readdirSync`, `statSync`, `renameSync`, `appendFileSync`, `mkdirSync` and `unlinkSync` in `config.ts`, `hook.ts`, `conversations.ts` and `config-display.ts` is replaced with the async `fs.promises` API
|
|
1535
|
+
- **`getConversationsList()` changed to async** — the conversation list is read through async file APIs
|
|
1536
|
+
- **MCP config read/write made async** — `readMcpConfig()`, `writeMcpConfig()` and `readProjectMcpConfig()` are all async now
|
|
478
1537
|
|
|
479
|
-
### ✅
|
|
1538
|
+
### ✅ Tests
|
|
480
1539
|
|
|
481
|
-
-
|
|
1540
|
+
- `config.test.ts` updated in sync to `async`/`await`, all 43 tests pass (including 6 E2E)
|
|
482
1541
|
|
|
483
1542
|
## [0.1.7] - 2026-07-21
|
|
484
1543
|
|
|
485
|
-
### 🔧
|
|
1544
|
+
### 🔧 Fixes
|
|
486
1545
|
|
|
487
|
-
-
|
|
1546
|
+
- **Updated dependencies**: `@ai-zen/agents-core` → `3.0.0-alpha.4`, `@ai-zen/agents-sdk` → `0.2.7`
|
|
488
1547
|
|
|
489
1548
|
## [0.1.6] - 2026-07-20
|
|
490
1549
|
|
|
491
|
-
### 🔧
|
|
1550
|
+
### 🔧 Fixes
|
|
492
1551
|
|
|
493
|
-
-
|
|
1552
|
+
- **Updated `@ai-zen/agents-sdk` to 0.2.6** — includes fixes to the default SubAgent function name and description, so the LLM can now identify sub-agents correctly
|
|
494
1553
|
|
|
495
1554
|
## [0.1.5] - 2026-07-20
|
|
496
1555
|
|
|
497
|
-
### 🔧
|
|
1556
|
+
### 🔧 Fixes
|
|
498
1557
|
|
|
499
|
-
-
|
|
1558
|
+
- **Updated `@ai-zen/agents-sdk` to 0.2.5** — includes a fix to default SubAgent permissions, so `general-assistant` can now use tools normally
|