@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.
Files changed (336) hide show
  1. package/CHANGELOG.md +1334 -275
  2. package/LICENSE +21 -0
  3. package/README.md +136 -31
  4. package/README.zh.md +136 -31
  5. package/dist/agent-creator.d.ts +11 -0
  6. package/dist/agent-creator.d.ts.map +1 -1
  7. package/dist/agent-creator.js +50 -20
  8. package/dist/agent-creator.js.map +1 -1
  9. package/dist/agent-switch.d.ts +61 -0
  10. package/dist/agent-switch.d.ts.map +1 -0
  11. package/dist/agent-switch.js +83 -0
  12. package/dist/agent-switch.js.map +1 -0
  13. package/dist/agents-store.d.ts +6 -1
  14. package/dist/agents-store.d.ts.map +1 -1
  15. package/dist/agents-store.js +46 -4
  16. package/dist/agents-store.js.map +1 -1
  17. package/dist/auto-migrate-confirm.d.ts +43 -0
  18. package/dist/auto-migrate-confirm.d.ts.map +1 -0
  19. package/dist/auto-migrate-confirm.js +47 -0
  20. package/dist/auto-migrate-confirm.js.map +1 -0
  21. package/dist/clipboard.d.ts +68 -0
  22. package/dist/clipboard.d.ts.map +1 -0
  23. package/dist/clipboard.js +177 -0
  24. package/dist/clipboard.js.map +1 -0
  25. package/dist/config-editor.d.ts +42 -5
  26. package/dist/config-editor.d.ts.map +1 -1
  27. package/dist/config-editor.js +80 -27
  28. package/dist/config-editor.js.map +1 -1
  29. package/dist/config.d.ts +76 -17
  30. package/dist/config.d.ts.map +1 -1
  31. package/dist/config.js +143 -39
  32. package/dist/config.js.map +1 -1
  33. package/dist/conversation-commands/registry.d.ts +127 -4
  34. package/dist/conversation-commands/registry.d.ts.map +1 -1
  35. package/dist/conversation-commands/registry.js +93 -14
  36. package/dist/conversation-commands/registry.js.map +1 -1
  37. package/dist/conversation-persist-plugin.d.ts +0 -1
  38. package/dist/conversation-persist-plugin.d.ts.map +1 -1
  39. package/dist/conversation-persist-plugin.js +7 -16
  40. package/dist/conversation-persist-plugin.js.map +1 -1
  41. package/dist/conversation-repository.d.ts +79 -18
  42. package/dist/conversation-repository.d.ts.map +1 -1
  43. package/dist/conversation-repository.js +96 -30
  44. package/dist/conversation-repository.js.map +1 -1
  45. package/dist/cwd-tracker-plugin.d.ts.map +1 -1
  46. package/dist/cwd-tracker-plugin.js +22 -7
  47. package/dist/cwd-tracker-plugin.js.map +1 -1
  48. package/dist/format-time.d.ts +17 -6
  49. package/dist/format-time.d.ts.map +1 -1
  50. package/dist/format-time.js +32 -14
  51. package/dist/format-time.js.map +1 -1
  52. package/dist/handoff-service.d.ts +19 -0
  53. package/dist/handoff-service.d.ts.map +1 -0
  54. package/dist/handoff-service.js +85 -0
  55. package/dist/handoff-service.js.map +1 -0
  56. package/dist/hook.d.ts.map +1 -1
  57. package/dist/hook.js +69 -15
  58. package/dist/hook.js.map +1 -1
  59. package/dist/i18n/en-wizard.d.ts +8 -0
  60. package/dist/i18n/en-wizard.d.ts.map +1 -0
  61. package/dist/i18n/en-wizard.js +303 -0
  62. package/dist/i18n/en-wizard.js.map +1 -0
  63. package/dist/i18n/en.d.ts +7 -0
  64. package/dist/i18n/en.d.ts.map +1 -0
  65. package/dist/i18n/en.js +245 -0
  66. package/dist/i18n/en.js.map +1 -0
  67. package/dist/i18n/index.d.ts +57 -0
  68. package/dist/i18n/index.d.ts.map +1 -0
  69. package/dist/i18n/index.js +109 -0
  70. package/dist/i18n/index.js.map +1 -0
  71. package/dist/i18n/preferences.d.ts +37 -0
  72. package/dist/i18n/preferences.d.ts.map +1 -0
  73. package/dist/i18n/preferences.js +73 -0
  74. package/dist/i18n/preferences.js.map +1 -0
  75. package/dist/i18n/zh-wizard.d.ts +11 -0
  76. package/dist/i18n/zh-wizard.d.ts.map +1 -0
  77. package/dist/i18n/zh-wizard.js +306 -0
  78. package/dist/i18n/zh-wizard.js.map +1 -0
  79. package/dist/i18n/zh.d.ts +10 -0
  80. package/dist/i18n/zh.d.ts.map +1 -0
  81. package/dist/i18n/zh.js +249 -0
  82. package/dist/i18n/zh.js.map +1 -0
  83. package/dist/index.js +52 -23
  84. package/dist/index.js.map +1 -1
  85. package/dist/mcp-command.d.ts +32 -0
  86. package/dist/mcp-command.d.ts.map +1 -0
  87. package/dist/mcp-command.js +37 -0
  88. package/dist/mcp-command.js.map +1 -0
  89. package/dist/mode.d.ts +31 -4
  90. package/dist/mode.d.ts.map +1 -1
  91. package/dist/mode.js +42 -7
  92. package/dist/mode.js.map +1 -1
  93. package/dist/model-switch.d.ts +58 -0
  94. package/dist/model-switch.d.ts.map +1 -0
  95. package/dist/model-switch.js +62 -0
  96. package/dist/model-switch.js.map +1 -0
  97. package/dist/reasoning-effort.d.ts +96 -0
  98. package/dist/reasoning-effort.d.ts.map +1 -0
  99. package/dist/reasoning-effort.js +164 -0
  100. package/dist/reasoning-effort.js.map +1 -0
  101. package/dist/reasoning-presets.d.ts +34 -0
  102. package/dist/reasoning-presets.d.ts.map +1 -0
  103. package/dist/reasoning-presets.js +85 -0
  104. package/dist/reasoning-presets.js.map +1 -0
  105. package/dist/sdk-log.d.ts +84 -0
  106. package/dist/sdk-log.d.ts.map +1 -0
  107. package/dist/sdk-log.js +167 -0
  108. package/dist/sdk-log.js.map +1 -0
  109. package/dist/session-pointer.d.ts.map +1 -1
  110. package/dist/session-pointer.js +10 -4
  111. package/dist/session-pointer.js.map +1 -1
  112. package/dist/stdio-runner.d.ts.map +1 -1
  113. package/dist/stdio-runner.js +32 -30
  114. package/dist/stdio-runner.js.map +1 -1
  115. package/dist/sub-agent-guard-plugin.d.ts +10 -7
  116. package/dist/sub-agent-guard-plugin.d.ts.map +1 -1
  117. package/dist/sub-agent-guard-plugin.js +12 -9
  118. package/dist/sub-agent-guard-plugin.js.map +1 -1
  119. package/dist/tui/chat-history.d.ts +29 -0
  120. package/dist/tui/chat-history.d.ts.map +1 -0
  121. package/dist/tui/chat-history.js +55 -0
  122. package/dist/tui/chat-history.js.map +1 -0
  123. package/dist/tui/chat-session.d.ts +144 -13
  124. package/dist/tui/chat-session.d.ts.map +1 -1
  125. package/dist/tui/chat-session.js +312 -73
  126. package/dist/tui/chat-session.js.map +1 -1
  127. package/dist/tui/components.d.ts +109 -6
  128. package/dist/tui/components.d.ts.map +1 -1
  129. package/dist/tui/components.js +177 -12
  130. package/dist/tui/components.js.map +1 -1
  131. package/dist/tui/config-wizard/detail.d.ts +18 -10
  132. package/dist/tui/config-wizard/detail.d.ts.map +1 -1
  133. package/dist/tui/config-wizard/detail.js +53 -46
  134. package/dist/tui/config-wizard/detail.js.map +1 -1
  135. package/dist/tui/config-wizard/engine.d.ts +2 -2
  136. package/dist/tui/config-wizard/engine.d.ts.map +1 -1
  137. package/dist/tui/config-wizard/engine.js +48 -26
  138. package/dist/tui/config-wizard/engine.js.map +1 -1
  139. package/dist/tui/config-wizard/fields.d.ts +3 -1
  140. package/dist/tui/config-wizard/fields.d.ts.map +1 -1
  141. package/dist/tui/config-wizard/fields.js +202 -167
  142. package/dist/tui/config-wizard/fields.js.map +1 -1
  143. package/dist/tui/config-wizard/frame.js +1 -1
  144. package/dist/tui/config-wizard/kv-editor.d.ts.map +1 -1
  145. package/dist/tui/config-wizard/kv-editor.js +13 -5
  146. package/dist/tui/config-wizard/kv-editor.js.map +1 -1
  147. package/dist/tui/config-wizard/list.d.ts +3 -1
  148. package/dist/tui/config-wizard/list.d.ts.map +1 -1
  149. package/dist/tui/config-wizard/list.js +4 -4
  150. package/dist/tui/config-wizard/list.js.map +1 -1
  151. package/dist/tui/config-wizard/mcp-helpers.d.ts.map +1 -1
  152. package/dist/tui/config-wizard/mcp-helpers.js +4 -3
  153. package/dist/tui/config-wizard/mcp-helpers.js.map +1 -1
  154. package/dist/tui/config-wizard/menu.d.ts.map +1 -1
  155. package/dist/tui/config-wizard/menu.js +33 -21
  156. package/dist/tui/config-wizard/menu.js.map +1 -1
  157. package/dist/tui/config-wizard/prompt-editor.d.ts +8 -1
  158. package/dist/tui/config-wizard/prompt-editor.d.ts.map +1 -1
  159. package/dist/tui/config-wizard/prompt-editor.js +11 -7
  160. package/dist/tui/config-wizard/prompt-editor.js.map +1 -1
  161. package/dist/tui/config-wizard/prompt.d.ts.map +1 -1
  162. package/dist/tui/config-wizard/prompt.js +8 -7
  163. package/dist/tui/config-wizard/prompt.js.map +1 -1
  164. package/dist/tui/config-wizard/step-agents.d.ts.map +1 -1
  165. package/dist/tui/config-wizard/step-agents.js +50 -29
  166. package/dist/tui/config-wizard/step-agents.js.map +1 -1
  167. package/dist/tui/config-wizard/step-defaults.d.ts.map +1 -1
  168. package/dist/tui/config-wizard/step-defaults.js +77 -27
  169. package/dist/tui/config-wizard/step-defaults.js.map +1 -1
  170. package/dist/tui/config-wizard/step-endpoints.d.ts.map +1 -1
  171. package/dist/tui/config-wizard/step-endpoints.js +27 -14
  172. package/dist/tui/config-wizard/step-endpoints.js.map +1 -1
  173. package/dist/tui/config-wizard/step-mcp.d.ts.map +1 -1
  174. package/dist/tui/config-wizard/step-mcp.js +29 -25
  175. package/dist/tui/config-wizard/step-mcp.js.map +1 -1
  176. package/dist/tui/config-wizard/step-models.d.ts.map +1 -1
  177. package/dist/tui/config-wizard/step-models.js +98 -43
  178. package/dist/tui/config-wizard/step-models.js.map +1 -1
  179. package/dist/tui/config-wizard/step-new-endpoint.d.ts.map +1 -1
  180. package/dist/tui/config-wizard/step-new-endpoint.js +16 -12
  181. package/dist/tui/config-wizard/step-new-endpoint.js.map +1 -1
  182. package/dist/tui/config-wizard/step-simple.d.ts.map +1 -1
  183. package/dist/tui/config-wizard/step-simple.js +18 -17
  184. package/dist/tui/config-wizard/step-simple.js.map +1 -1
  185. package/dist/tui/config-wizard/types.d.ts +31 -3
  186. package/dist/tui/config-wizard/types.d.ts.map +1 -1
  187. package/dist/tui/config-wizard/types.js.map +1 -1
  188. package/dist/tui/file-index.d.ts +38 -0
  189. package/dist/tui/file-index.d.ts.map +1 -0
  190. package/dist/tui/file-index.js +117 -0
  191. package/dist/tui/file-index.js.map +1 -0
  192. package/dist/tui/index.d.ts +17 -1
  193. package/dist/tui/index.d.ts.map +1 -1
  194. package/dist/tui/index.js +105 -49
  195. package/dist/tui/index.js.map +1 -1
  196. package/dist/tui/input-editing.d.ts +52 -0
  197. package/dist/tui/input-editing.d.ts.map +1 -0
  198. package/dist/tui/input-editing.js +91 -0
  199. package/dist/tui/input-editing.js.map +1 -0
  200. package/dist/tui/key-debug.d.ts +62 -0
  201. package/dist/tui/key-debug.d.ts.map +1 -0
  202. package/dist/tui/key-debug.js +92 -0
  203. package/dist/tui/key-debug.js.map +1 -0
  204. package/dist/tui/mention.d.ts +47 -0
  205. package/dist/tui/mention.d.ts.map +1 -0
  206. package/dist/tui/mention.js +152 -0
  207. package/dist/tui/mention.js.map +1 -0
  208. package/dist/tui/mouse.d.ts +55 -0
  209. package/dist/tui/mouse.d.ts.map +1 -0
  210. package/dist/tui/mouse.js +74 -0
  211. package/dist/tui/mouse.js.map +1 -0
  212. package/dist/tui/phase.d.ts +64 -0
  213. package/dist/tui/phase.d.ts.map +1 -0
  214. package/dist/tui/phase.js +112 -0
  215. package/dist/tui/phase.js.map +1 -0
  216. package/dist/tui/screens/agent-tabs.d.ts +53 -0
  217. package/dist/tui/screens/agent-tabs.d.ts.map +1 -0
  218. package/dist/tui/screens/agent-tabs.js +81 -0
  219. package/dist/tui/screens/agent-tabs.js.map +1 -0
  220. package/dist/tui/screens/chat-commands.d.ts +78 -1
  221. package/dist/tui/screens/chat-commands.d.ts.map +1 -1
  222. package/dist/tui/screens/chat-commands.js +688 -157
  223. package/dist/tui/screens/chat-commands.js.map +1 -1
  224. package/dist/tui/screens/chat.d.ts +57 -1
  225. package/dist/tui/screens/chat.d.ts.map +1 -1
  226. package/dist/tui/screens/chat.js +708 -261
  227. package/dist/tui/screens/chat.js.map +1 -1
  228. package/dist/tui/screens/common-keys.d.ts +36 -0
  229. package/dist/tui/screens/common-keys.d.ts.map +1 -0
  230. package/dist/tui/screens/common-keys.js +67 -0
  231. package/dist/tui/screens/common-keys.js.map +1 -0
  232. package/dist/tui/screens/frame-buffer.d.ts.map +1 -1
  233. package/dist/tui/screens/frame-buffer.js.map +1 -1
  234. package/dist/tui/screens/handoff-choice.d.ts +37 -0
  235. package/dist/tui/screens/handoff-choice.d.ts.map +1 -0
  236. package/dist/tui/screens/handoff-choice.js +42 -0
  237. package/dist/tui/screens/handoff-choice.js.map +1 -0
  238. package/dist/tui/screens/index.d.ts +7 -5
  239. package/dist/tui/screens/index.d.ts.map +1 -1
  240. package/dist/tui/screens/index.js +6 -5
  241. package/dist/tui/screens/index.js.map +1 -1
  242. package/dist/tui/screens/input-row.d.ts +64 -0
  243. package/dist/tui/screens/input-row.d.ts.map +1 -0
  244. package/dist/tui/screens/input-row.js +67 -0
  245. package/dist/tui/screens/input-row.js.map +1 -0
  246. package/dist/tui/screens/input.d.ts +77 -1
  247. package/dist/tui/screens/input.d.ts.map +1 -1
  248. package/dist/tui/screens/input.js +125 -8
  249. package/dist/tui/screens/input.js.map +1 -1
  250. package/dist/tui/screens/layout.d.ts +49 -0
  251. package/dist/tui/screens/layout.d.ts.map +1 -1
  252. package/dist/tui/screens/layout.js +45 -7
  253. package/dist/tui/screens/layout.js.map +1 -1
  254. package/dist/tui/screens/lines.d.ts +7 -0
  255. package/dist/tui/screens/lines.d.ts.map +1 -1
  256. package/dist/tui/screens/lines.js +6 -6
  257. package/dist/tui/screens/lines.js.map +1 -1
  258. package/dist/tui/screens/messages-view.d.ts +20 -1
  259. package/dist/tui/screens/messages-view.d.ts.map +1 -1
  260. package/dist/tui/screens/messages-view.js +55 -8
  261. package/dist/tui/screens/messages-view.js.map +1 -1
  262. package/dist/tui/screens/notice-bar.d.ts +5 -0
  263. package/dist/tui/screens/notice-bar.d.ts.map +1 -0
  264. package/dist/tui/screens/notice-bar.js +21 -0
  265. package/dist/tui/screens/notice-bar.js.map +1 -0
  266. package/dist/tui/screens/notice.d.ts +44 -10
  267. package/dist/tui/screens/notice.d.ts.map +1 -1
  268. package/dist/tui/screens/notice.js +75 -15
  269. package/dist/tui/screens/notice.js.map +1 -1
  270. package/dist/tui/screens/overlays.d.ts.map +1 -1
  271. package/dist/tui/screens/overlays.js +4 -3
  272. package/dist/tui/screens/overlays.js.map +1 -1
  273. package/dist/tui/screens/render.d.ts +1 -1
  274. package/dist/tui/screens/render.d.ts.map +1 -1
  275. package/dist/tui/screens/render.js +18 -11
  276. package/dist/tui/screens/render.js.map +1 -1
  277. package/dist/tui/screens/use-chat-viewport.d.ts +22 -0
  278. package/dist/tui/screens/use-chat-viewport.d.ts.map +1 -0
  279. package/dist/tui/screens/use-chat-viewport.js +50 -0
  280. package/dist/tui/screens/use-chat-viewport.js.map +1 -0
  281. package/dist/tui/screens/use-input-editing.d.ts +69 -0
  282. package/dist/tui/screens/use-input-editing.d.ts.map +1 -0
  283. package/dist/tui/screens/use-input-editing.js +220 -0
  284. package/dist/tui/screens/use-input-editing.js.map +1 -0
  285. package/dist/tui/screens/use-overlays.d.ts +25 -0
  286. package/dist/tui/screens/use-overlays.d.ts.map +1 -0
  287. package/dist/tui/screens/use-overlays.js +32 -0
  288. package/dist/tui/screens/use-overlays.js.map +1 -0
  289. package/dist/tui/screens/use-wizard-host.d.ts +46 -0
  290. package/dist/tui/screens/use-wizard-host.d.ts.map +1 -0
  291. package/dist/tui/screens/use-wizard-host.js +123 -0
  292. package/dist/tui/screens/use-wizard-host.js.map +1 -0
  293. package/dist/tui/screens/welcome.d.ts +12 -0
  294. package/dist/tui/screens/welcome.d.ts.map +1 -0
  295. package/dist/tui/screens/welcome.js +36 -0
  296. package/dist/tui/screens/welcome.js.map +1 -0
  297. package/dist/tui/status-segments.d.ts +37 -0
  298. package/dist/tui/status-segments.d.ts.map +1 -0
  299. package/dist/tui/status-segments.js +166 -0
  300. package/dist/tui/status-segments.js.map +1 -0
  301. package/dist/tui/text.d.ts +22 -7
  302. package/dist/tui/text.d.ts.map +1 -1
  303. package/dist/tui/text.js +110 -39
  304. package/dist/tui/text.js.map +1 -1
  305. package/dist/tui/theme.d.ts +2 -0
  306. package/dist/tui/theme.d.ts.map +1 -1
  307. package/dist/tui/theme.js +2 -0
  308. package/dist/tui/theme.js.map +1 -1
  309. package/dist/tui/token-rate.d.ts +83 -0
  310. package/dist/tui/token-rate.d.ts.map +1 -0
  311. package/dist/tui/token-rate.js +195 -0
  312. package/dist/tui/token-rate.js.map +1 -0
  313. package/dist/types.d.ts +7 -8
  314. package/dist/types.d.ts.map +1 -1
  315. package/dist/user-content.d.ts +41 -0
  316. package/dist/user-content.d.ts.map +1 -0
  317. package/dist/user-content.js +37 -0
  318. package/dist/user-content.js.map +1 -0
  319. package/dist/version-compare.d.ts +37 -0
  320. package/dist/version-compare.d.ts.map +1 -0
  321. package/dist/version-compare.js +105 -0
  322. package/dist/version-compare.js.map +1 -0
  323. package/dist/version.js.map +1 -1
  324. package/package.json +7 -5
  325. package/dist/auto-migrate-confirm-plugin.d.ts +0 -58
  326. package/dist/auto-migrate-confirm-plugin.d.ts.map +0 -1
  327. package/dist/auto-migrate-confirm-plugin.js +0 -61
  328. package/dist/auto-migrate-confirm-plugin.js.map +0 -1
  329. package/dist/migration-service.d.ts +0 -15
  330. package/dist/migration-service.d.ts.map +0 -1
  331. package/dist/migration-service.js +0 -66
  332. package/dist/migration-service.js.map +0 -1
  333. package/dist/tui/screens/splash.d.ts +0 -5
  334. package/dist/tui/screens/splash.d.ts.map +0 -1
  335. package/dist/tui/screens/splash.js +0 -51
  336. 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
- - **自动迁移与 CLI 的交互适配(两处)**:
6
- - **迁移期间输入框未真正拦住** —— 主输入 `useInput` 的 `isActive` 此前只挡「确认框 / 选择器 / 配置向导」,未挡「生成中」态。自动迁移在 `send()` 内 `await onAfterSend` 执行(`busy` 为真、输入框已被 Spinner 顶替),但按键仍会被静默吞进隐藏的输入缓冲、待本轮结束才浮现。现令主输入在 `busy` 时整体失活,另设「忙碌期」处理器仅放行 `Ctrl+C` 中止 / `Ctrl+D` 退出 / `PgUp`·`Shift+↑↓` 回看;`/migrate` 手动迁移同样纳入 `busy`。
7
- - **迁移完成后仍被总上下文护栏拦截** —— 迁移(prune)会物理重置上下文,却遗留 `agent.lastUsage`(迁移前那一次请求的 `prompt_tokens`)。`ContextGuardPlugin`(`onInnerLoopStart`,硬上限 `maxTokens × 1.5`)据此误判,迁移后第一条消息即被 `ContextOverflowError` 中断。现于迁移后清空 `agent.lastUsage`,护栏首轮无据可查而放行,待下一次响应以真实用量回填。
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
- - 新增 `src/migration-service.test.ts`:断言迁移后 `agent.lastUsage` 被清空(移除修复后该用例确会失败)。全量单测 **17 文件 / 227 用例**通过,`tsc --noEmit` 与 `pnpm build` 零错误。
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
- - **修复长时间 TUI 会话内存无界增长导致的 OOM(崩溃)** —— CLI 入口此前从未设定 `NODE_ENV`,React 因而按 `process.env.NODE_ENV !== "production"` 载入 **development** 版 `react-reconciler`;该开发构建每次渲染都会调用 `performance.measure()`(User Timing API),而 Node 的 performance 条目缓冲**无上限、从不逐出**,长时间流式会话下条目持续累积,约 55 分钟即将堆顶到 `heap_size_limit`(本机 ~1GB)触发 OOM。现于 `src/index.ts` 顶部设 `process.env.NODE_ENV ||= "production"`;因 ESM 静态 `import` 会被提升到模块体之前执行,对 TUI 入口 `./tui/index.js` 改用动态 `import()`,确保 React/Ink 在 `NODE_ENV` 设定**之后**才被求值,从而加载 production 版 reconciler(该版不调用 `performance.measure`)。显式 `NODE_ENV=development` 仍保留开发构建,便于排查。
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
- - **端到端实证** —— 真实 ESM 下经入口加载 Ink 并连续重渲染 300 次:修复后 `performance.measure` 条目 **0 → 0**(production reconciler),显式 `NODE_ENV=development` 对照组 **0 → 905**(dev 埋点仍在)。全量单测 **16 文件 / 226 用例**通过,`tsc --noEmit` 与 `pnpm build` 零错误。
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
- - **状态栏 token 用量纳入文档** —— `docs/zh|en/index.md` 的「主要特性」补上「状态栏实时展示 模型 · Agent · 本轮/累计 token 用量 · 生成状态」(此前仅在 README 提及),累计口径为 `sumUsage(agent.messages)`(含所有层级的子 Agent)。
28
- - 修正 `TODO.md` 中已过时的路径引用(`src/tui/config-wizard.tsx` → 目录 `src/tui/config-wizard/`)。
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`、`@ai-zen/agents-sdk` `1.0.0-alpha.8 → 1.0.0-alpha.9`
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
- - **累计用量改用 SDK 的 `sumUsage`,移除 CLI 自建的内存累加器** —— 依赖 `@ai-zen/agents-sdk` `1.0.0-alpha.8` / `@ai-zen/agents-core` `4.5.0`:除助手消息的 `usage` 外,委派产生的用量现由 core 在委派边界钉到工具消息的 `subAgentUsage` 上。故 `ChatSession.totalUsage` 直接 `sumUsage(agent.messages)` 即可(含所有层级的子 Agent),删掉 `src/usage.ts` 与 `onSubAgentEnd` 内存回灌——子 Agent 开销随消息持久化,续接 / `/load` 亦不丢,且不再有双重计数风险。
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`、`@ai-zen/agents-core` `^4.4.0 → ^4.5.0`
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
- - 移除 `src/usage.test.ts`(逻辑下沉 core,已由 core 覆盖);`chat-session.test.ts` 的 `totalUsage` 用例改为覆盖「assistant 的 usage + 工具消息的 subAgentUsage」求和。全量单测 **16 文件 / 226 用例**通过,`tsc --noEmit` 与 `npm run build` 零错误。
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
- - **状态栏展示 token 累计消耗** —— 依托 `@ai-zen/agents-sdk` `1.0.0-alpha.7` / `@ai-zen/agents-core` `4.4.0` 为每条 assistant 消息钉上的 `usage`,新增 `src/usage.ts`(`addUsage` / `sumMessagesUsage` 纯函数)与 `ChatSession.totalUsage`:对本会话全部 assistant 消息的 `usage` 求和即计费口径的累计消耗;子 Agent 的 messages 不在父 Agent 中,故在 `onSubAgentEnd` 单独累加并入、`/new` 时归零。状态栏由「`{tokens} tok`」升级为「`本轮 x · 累计 y tok`」(本轮 = `lastUsage.total_tokens`,累计 = `totalUsage.total_tokens`)。
55
- - **依赖升级** —— `@ai-zen/agents-sdk` `1.0.0-alpha.6 → 1.0.0-alpha.7`、`@ai-zen/agents-core` `^4.3.2 → ^4.4.0`(后者新增 `AgentNS.Message.usage` 逐条 token 用量)。
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
- - 新增 `src/usage.test.ts`(4 例);`chat-session.test.ts` 增补 `totalUsage` 求和用例;`screens.test.tsx` 的 `StatusBar` 用例更新。全量单测 **17 文件 / 230 用例**通过,`tsc --noEmit` 与 `npm run build` 零错误。
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 渲染直接以 `agent.messages` 为唯一数据源** —— 移除 `ChatEvent` / `chatReducer` / `LiveAssistant` / `Block` / `messagesToBlocks` 等自定义中间模型。SDK 在流式期间会**实时更新 `agent.messages`**(`Agent.parseStreamData` 把每个 delta 增量写进当前那条 message),因此跨消息顺序(数组序)与单条 assistant 内部顺序(`思考 → 工具 → 正文`)天然保持,直接渲染即可,杜绝「第二套真相」的失真与维护负担。已完成消息的折行 / 高亮按「消息 + 列宽 + 内容签名」缓存(`cachedMessageToLines`),流式中那条靠内容签名自动失效。
66
- - **notice / error 改为独立栏位** —— 系统级提示(命令反馈 / 迁移进度 / 报错)不再混入对话转录,也**绝不写进 `agent.messages`**(那会被 `formatHistory()` 原样发给模型、污染上下文);改在输入框上方的独立一行渲染(`src/tui/screens/notice.ts`)。
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
- - **流式增量渲染**:Ink 开启 `incrementalRendering` —— 从「每帧 erase 整屏 + 重写整屏」(`createStandard`)切到「只重绘变化的行」(`createIncremental`),每帧输出量由 O(屏幕大小) 降到 O(变化量),SSH / 弱网下流式更稳。
71
- - **流式合帧**(`src/tui/screens/frame-buffer.ts`):内容变更先攒入缓冲,按 60ms(约 16fps,低于 Ink 默认 30fps 输出节流)批量触发重渲染,把上屏频率从「每个 delta 一次」降到固定帧率。
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
- - 新增 `frame-buffer.test.ts`、`notice.test.ts`;`screens.test.tsx` 重写为基于 `messageToLines` / `transcriptLines` / `computeChatLayout(transcript)`。
78
- - `tsc` 零错误;全量单测 **16 文件 / 225 用例**通过;`npm run build` 通过。
79
- - 真机(pty)验证:SSH / 弱网下流式稳定,长对话滚动流畅。
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
- - **内置工具改名(统一 `snake_case`:多词收敛、单词简化)** —— 随 `@ai-zen/agents-sdk` `1.0.0-alpha.6` 落地:SDK 将内置工具名统一为 `snake_case`,并把单词工具简化为单词 —— `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`;工具类名同步为 `ReadTool` / `WriteTool` / `SpawnTool`。**若你在 Agent 定义、权限白名单(`tools.allow` / `tools.deny`)或自定义提示词里引用了旧工具名,请同步更新为 `snake_case` 新名**(旧名仅保留在 CHANGELOG 历史条目中)。
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
- - **正文 / 思考按 Markdown 源码着色** —— AI 回答本身即 Markdown,现交给 `highlight.js` 的 `markdown` 词法着色:标题(`#`)、粗体、斜体、行内代码、列表、引用、链接各有配色;**围栏代码块保留围栏行**(开 / 闭围栏以低调暗灰渲染)并继续按标注语言做语法高亮。标记符(`#` / `**` / 反引号 等)**原样保留、只染色**(源码着色,非语义渲染)。正文用**空格优先折行**,避免把英文单词从中间切断。
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
- - **正文默认文本色改用终端默认前景色** —— 此前正文基础色写死近白 `#e2e8f0`,在**亮色主题终端**里会「白底白字」看不见。现正文(含未指定颜色的默认分段)一律回落**终端默认前景色**,绝对色只留给标题 / 代码等强调项;同步移除语法色板里两处近白 token 色(`variable` / `params`)。
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` 升至 `^4.3.2`、`@ai-zen/agents-sdk` 升至 `1.0.0-alpha.6`** ——
98
- - core `4.3.1`:**中断的并行工具轮次归一化** —— 一轮回复里并行调用的多个工具若被中断(`Ctrl+C`),未完成的工具结果会在历史里被补齐 / 归一,避免下一轮因 `tool_calls` 数量与实际工具结果数量不匹配而被服务端拒绝(默认 `fill`:保留 assistant 正文并补齐未完成结果)。
99
- - core `4.3.2` / sdk `1.0.0-alpha.4…6`:内置工具统一 `snake_case` + 单词简化、`exec_async` → `spawn`、类名对齐(见「破坏性变更」)。
100
- - CLI 侧仅同步工具名(文档 / 测试 fixture / 提示语),运行时行为全量委托 SDK,无需其它适配。
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`(`tsconfig.test.json`)零错误;全量单测 **15 文件 / 235 用例**通过
105
- - 修复 `config-wizard.test.tsx` 两个预存失败:为向导注入终端尺寸(新增可选 `terminalSize` prop),消除 `ink-testing-library` 假 stdout 缺 `rows` 时 `terminal-size` 探测本机行数、把吸底帧撑到 38 行的干扰
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` 升至 `1.0.0-alpha.3`** — 拉取 SDK 对内置工具 `generateImage` 参数描述的修正。原描述写着「配置中的图片模型可通过 `aiz config show` 查看」,等于**指路让模型去执行宿主 CLI 命令**;而**工具描述会随每次请求的 `tools` 字段进入每一层 agent 的上下文**——模型据此 `exec("aiz config show")`,CLI 又把这条命令解析为**纯 stdio 模式**、以 `config show` 为提示词**再拉起一个 agent**,其工具描述里又是同一句指引,遂形成**自指型递归**(每层都真连 API,越套越深)。SDK 现已把该描述改为「若指定的模型不存在,本工具会返回当前可用的模型列表」,与 `call()` 的实际行为一致,且不再引导模型去执行宿主 CLI。该版本**无破坏性变更**,CLI 源码无需适配(类型 / 默认配置全量委托 SDK,版本横幅由实际安装版本读取,工具清单由 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`:运行时依赖版本同步为 `@ai-zen/agents-sdk` `1.0.0-alpha.3`
117
- - `docs/manifest.json`:版本号同步为 `1.0.0-alpha.2`
118
- - 中英 README 启动横幅示例版本同步为 `v1.0.0-alpha.2`
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`(`tsconfig.test.json`)与 `npm run build` 零错误
123
- - 全量单测 13 文件 / 224 用例通过
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
- > 本版本汇总**相对上一发布版本 `0.9.0`** 的全部变化。
1186
+ > This release summarizes all changes **relative to the previous release `0.9.0`**.
128
1187
 
129
- ### 💥 破坏性变更
1188
+ ### 💥 Breaking changes
130
1189
 
131
- - **启动命令由 `aiz` / `zen` 改为 `ai`** —— `package.json` 的 `bin` 改为 `{ "ai", "aiz", "zen" }`;`aiz` / `zen` 作为**过渡别名**保留,未来版本移除。
132
- - **引入双模式运行,两种模式互不混合** —— 带位置参数、或 stdin/stdout 非 TTY(管道 / 重定向 / CI)时启用**纯 stdio 模式**:无任何额外交互,最终 assistant 文本以纯文本写入 `stdout`(零 ANSI / emoji 前缀 / spinner),日志、进度、工具过程与错误摘要一律走 `stderr`,成功退出码 `0`、失败非 `0`、默认不落盘;仅在**无参数且 stdin/stdout 均为 TTY** 时进入 **TUI 模式**。
133
- - **交互界面整体重写为 Ink(React)全屏 TUI** —— 取代原 inquirer 行式对话;随之**移除主菜单**(`/menu` 与 `src/menus/*`)以及 `inquirer` / `@types/inquirer` / `chalk` 依赖。
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 全屏交互界面(Ink + React)**:
138
- - 动画渐变 ASCII 启动界面,结束后直接进入对话(有上一轮会话则自动续接);
139
- - 对话区**吸底**(分隔线 / 输入行 / 状态栏固定底部,整帧高度恒为「终端行数 − 1」以避开 Ink 全屏分支);
140
- - 流式输出(思考 / 正文 / 工具调用分行)、历史按行裁剪自底滚动;
141
- - **多行输入**(`Enter` 发送、`Ctrl+N` / `Ctrl+J` 换行)与**光标编辑**(`←`/`→`/`Home`/`End`/`Backspace`/`Delete`,`Ctrl`/`Meta` + `←`/`→` 跨词,反显光标 + 硬件光标同步);
142
- - **应用内翻页**(`PgUp`/`PgDn` 翻页、`Shift`/`Alt` + `↑`/`↓` 单行滚动、发送自动回底);
143
- - 输入历史、内联 `/` 命令菜单、状态栏、确认框。
144
- - **语法高亮**:工具调用参数按 **JSON** 高亮、正文与思考里的**围栏代码块**按语言高亮。高亮**数据**委托 `highlight.js`(经 `lowlight` 产出 hast),CLI 仅把 `hljs-*` scope 映射为 Ink `<Text>` 颜色 —— 与同为 Ink/React 的 Google Gemini CLI 做法一致。
145
- - **对话内命令**:`/help`、`/load`、`/clear`、`/new`、`/save`、`/config`、`/key`、`/migrate`、`/back`、`/editor`、`/exit`(`registry.ts` 为命令清单唯一来源,`/help` 与输入补全自动同步)。
146
- - **首启凭据引导 + 配置中心(Ink 原生)**:端点缺 API Key 时先弹凭据设置屏;`/config` 配置中心管理端点、模型、图片模型、默认项(默认模型 / 图片模型 / Agent / 迁移模型)、工具输出上限、**MCP 服务器**(全局 / 项目作用域)与 **Agent 定义**(顶层 Agent + Sub-agent,含四维权限);编辑「出厂托管」条目自动转 `custom: true`,危险操作二次确认并做引用守卫。
147
- - **多行文本走系统编辑器**:Agent 提示词、Sub-agent 函数说明 / 参数 schema 等多行字段按 `Enter` 调起 `$EDITOR`(Windows 默认 `notepad`);参数 schema 以 `.json` 打开并即时校验。
148
- - **会话持久化改革:会话即文件 + 单指针** —— 每轮结束落盘、`cli/last-session.json` 记录上一轮会话 id、首条消息后才落盘、`/save` 幂等;继续 / 加载时按消息重建历史。
149
- - **纯 stdio 模式配套**:`ai --help` / `-h`、`ai --version` / `-v`、`ai --save`;SDK 日志与 `console.log` 统一改道 `stderr` 以保证 stdout 纯净。
150
- - **IME 候选框跟随光标**:用 Ink `useCursor()` 把硬件光标同步到编辑光标,中文输入法候选框正确定位。
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
- - **依赖**:新增 `ink` / `react`(运行时)、`ink-testing-library` / `@types/react`(开发);新增语法高亮 `highlight.js` / `lowlight`(运行时)与 `@types/hast`(开发);**移除** `inquirer` / `@types/inquirer` / `chalk`。
155
- - **升级 `@ai-zen/agents-sdk`**:`1.0.0-alpha.0` → `1.0.0-alpha.1` → `1.0.0-alpha.2`(出厂模型目录刷新、模型清单托管、默认 `mcp.json` 增补 `chrome-devtools` 等;CLI 侧代码零改动)。
156
- - **TUI 输出统一走 Ink**:迁移进度、会话落盘失败等提示不再 `console.log` / `console.warn` 直写终端(会撕裂 Ink 帧),改为经回调渲染为 notice 块;启动 Logo 渐变也改为 Ink 逐字符 `<Text color>`。
157
- - **配置向导 / 覆盖层统一「吸底」布局**,列表项与键位提示裁成**严格单行**,修复 VS Code 终端里内容被裁切的问题。
158
- - **终端状态安全与干净退出**:`SIGINT` / `SIGTERM` / 未捕获异常均卸载 Ink、恢复 raw mode 与光标;`Ctrl+C` 语义为「忙时取消本轮、空闲时退出」;退出显式暂停 stdin 并终止进程(`farewell()`)。
159
- - **移除旧行式链路(死代码)**:`src/conversation-runner.ts`、`src/slash-hint-prompt.ts`、`src/delta-renderer.ts`、inquirer 版 `src/config-wizard.ts`、`src/draft-*.ts` 等;新增 `src/mode.ts` / `src/stdio-runner.ts` / `src/session-pointer.ts` / `src/conversation-persist-plugin.ts` / `src/config-editor.ts` 与 `src/tui/*` 新实现。
160
- - 文档:中英 README 与 `docs/zh|en/*` 同步(运行模式、`ai` 命令名、配置中心、多行编辑器等),`docs/manifest.json` 同步。
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
- - 全量单测 **13 文件 / 224 例**,`tsc --noEmit` 与 `npm run build` 零错误;e2e **12 例**真连 DeepSeek API(覆盖双模式、stdio 纯净性、退出码、`--save` 落盘)。
165
- - 新增测试文件:`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`。
166
- - 真实 PTY 实测:启动 → 对话 → 流式 → `/load` → `/clear` → 退出交还 shell;首启凭据链路;配置中心各子屏。
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
- - **升级 `@ai-zen/agents-sdk` 到 `1.0.0-alpha.0`(Provider → Scope 架构重构)** — SDK 1.0.0-alpha.0 将全局入口对象 `Provider` 改名为 `Scope`,把四类能力来源疏散为 5 个可组合的 `ScopePlugin`,并移除 `Provider.create()`(改为显式装配)。CLI 侧同步适配 `src/agent-creator.ts`:
173
- - 导入 `Scope` / `allInOne` 取代 `Provider`
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
- - 单例/工厂改名:`getProvider` → `getScope`、`resetProvider` → `resetScope`;`createAgent` 内部改调 `sdkCreateAgent(scope, agentId)`
176
- - 依赖声明由 `^0.12.0` 改为精确 `1.0.0-alpha.0`(alpha 阶段锁定)
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
- - 中英 README 与 `docs/zh|en/{tools,mcp,skills}.md`:`Provider` / `getProvider()` 同步为 `Scope` / `getScope()`
181
- - `docs/zh|en/getting-started.md`:运行时依赖版本同步为 `@ai-zen/agents-sdk` `1.0.0-alpha.0`
182
- - `docs/manifest.json`:版本号同步为 `0.9.0`
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`(`tsconfig.test.json`)与 `npm run build` 零错误
187
- - 全量单测 9 文件 / 74 用例通过
188
- - e2e 6 用例通过(含真实 DeepSeek API 对话,验证 CLI → SDK `1.0.0-alpha.0` → API 全链路)
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` 升至 `^0.12.0`** — SDK 0.12.0 新增内置工具 `inspectFile`(内置工具类 19 → 20)、`readFile` 的 `range` 分批读取参数,新增配置项 `maxToolOutput`(缺省 32768 字符)与工具输出保护行为,并精简了 `batchEdit` 的输出。该版本**无破坏性变更**,CLI 源码无需适配:类型与默认配置全量委托 SDK,`src/version.ts` 由实际安装版本读取,工具清单由 SDK 静态注册表自动发现,故版本横幅随之显示 sdk `0.12.0`
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
- - 同步内置工具计数 19 → 20:中英 README、`docs/zh|en/tools.md`、`docs/zh|en/index.md` 与 `docs/manifest.json`
199
- - 工具表补充 `inspectFile`(勘察文件结构概况),并为 `readFile` 补注 `range` 参数
200
- - `docs/zh|en/tools.md` 新增「输出保护」说明:超限时 `exec` / `findText` / `glob` / `ls` 落盘并返回警告,`readFile` / `inspectFile` 仅警告不落盘
201
- - `docs/zh|en/getting-started.md` 的运行时依赖版本同步为 `@ai-zen/agents-sdk` `^0.12.0`
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
- - 沿用既有 9 文件 / 74 用例,全部通过;`tsc --noEmit` 与 `npm run build`(`0.8.2`)均通过
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 子 Agent 纳入子 Agent 上下文护栏与流式渲染** — 依托 SDK 0.11.0 将 `call_skill_sub_agent` 统一到 `AgentToolLazy`,技能子 Agent 自此与常规 SubAgent 共享同一套委派边界(`onSubAgentStart` / `onSubAgentEnd` 钩子与 `sub-agent-start` / `sub-agent-end` 事件)。故 0.8.0 引入的 `SubAgentGuardInstallerPlugin` 与子 Agent 流式渲染**自动覆盖**该路径,本次 CLI 无源码改动(0.8.0 CHANGELOG「覆盖范围」中「技能子 Agent 不在覆盖范围」的说明随之失效)
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` 升至 `^4.3.0`、`@ai-zen/agents-sdk` 升至 `^0.11.0`** — `src/version.ts` 由实际安装版本读取,版本横幅随之显示 core `4.3.0` / sdk `0.11.0`。SDK 0.11.0 的破坏性变更(`createCallSkillSubAgentTool` 移除第三参 `provider`)仅影响直接调用该工厂函数的代码;CLI 经 `Provider.instantiate` 间接使用,无需适配
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
- - 沿用既有 9 文件 / 74 用例,全部通过;`tsc --noEmit` 与 `npm run build`(`0.8.1`)均通过
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
- - **子 Agent 上下文护栏(`SubAgentGuardInstallerPlugin`)** — 补上「主 Agent 有护栏、子 Agent 无护栏」的缺口(新增 `src/sub-agent-guard-plugin.ts`):
226
- - **背景**:SDK 的子 Agent 由 `AgentTool` / `AgentToolLazy` 在委派时 `new Agent({...})` 构建,新建实例不携带任何插件(`Agent._plugins` 每实例私有,不向子 Agent 传播),且子 Agent 走 `run()` 而非 `send()`(`onBeforeSend` / `onAfterSend` 不会触发)——因此主 Agent 上的 `ContextGuardPlugin` 与自动迁移插件对子 Agent 完全无效,子 Agent 的内循环没有约束
227
- - **介入点**:core 4.2.0 的 `onSubAgentStart` 钩子在「子 Agent 已构建、尚未 `run()`」时由宿主 Agent 分发,载荷携带子 Agent 实例;`use()` 只是把插件推入 `_plugins`,而钩子分发每次都实时遍历该列表,故在钩子内安插对**本次 `run()` 立即生效**(无需 `init()`,`AgentTool` 也不调用子 Agent 的 `init()`)
228
- - **约束口径**:把主 Agent 同款的 `ContextGuardPlugin` 装到子 Agent 上——同一 `maxTokens`(当前模型 `maxContextTokens`)、同样不传 ratio(沿用 SDK 默认 1.5),使「上下文 token 超过 `maxTokens` × 1.5 即中断」对子 Agent 同样成立;**不设轮次上限、不设工具调用次数上限**(仅此一项限制)
229
- - **中断语义为「软中断」**:子 Agent 的 run 立即结束,异常经 `AgentTool` 的 `finally`(分发 `onSubAgentEnd`)上抛至父 Agent,而父 Agent 默认 `allowJsonParseError = true`,故被降级为一条工具结果文本(`执行工具 X 时出错: …`),父 Agent 继续下一轮——停掉烧钱的子 Agent,但不终止用户对话
230
- - **递归覆盖**:安装器在委派时把自身一并装给子 Agent,故子 Agent 再委派(孙级)同样受约束(默认 SubAgent 定义为 `subagents: deny`,该路径默认不触发,仅在放开递归时生效)
231
- - **覆盖范围**:仅 core 的 `AgentTool` / `AgentToolLazy` 两条委派路径(均分发 `onSubAgentStart`);SDK 的 `call_skill_sub_agent`(技能子 Agent)不分发该钩子,本次不覆盖
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` 升至 `^4.2.0`、`@ai-zen/agents-sdk` 升至 `^0.10.0`** — `src/version.ts` 由实际安装版本读取,版本横幅随之显示 core `4.2.0` / sdk `0.10.0`;SDK 0.10.0 新增的 `load_mcp` `include_manifest` 开关为向后兼容的可选参数(默认 `true`,行为与升级前逐字一致),CLI 无需适配
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
- - **子 Agent 事件对齐 core 4.2.0 的改名与载荷结构** — core 4.2.0 将事件 `sub-agent` 更名为 `sub-agent-start`,载荷由 `{ agent: 子 Agent, ctx }` 改为 `SubAgentContext = { agent: 主 Agent, subAgent: 子 Agent, toolCallContext }`。`src/conversation-runner.ts` 同步适配:事件监听与注销改用 `sub-agent-start`(此前监听旧名将不再触发,子 Agent 流式渲染会静默失效);子 Agent 实例改取自 `subAgent` 字段,工具名改取自 `toolCallContext.tool_call.function.name`(此前读取已不存在的 `ctx` 字段会在 `sub-agent-end` 回调中抛错)
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
- - 新增 `src/sub-agent-guard-plugin.test.ts`(6 个用例):安装器的装载清单与「不拒绝委派」、安插后对本次 `run()` 立即生效(无需 `init()`)、用量恰好等于硬上限时放行、首轮无 usage 数据时放行、阈值随传入 `maxTokens` 变化、孙级传染
244
- - `tsc --noEmit` 类型检查、全量单测与 `npm run build` 通过(74 passed / 9 files)
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
- - **自动迁移二次确认** — 上下文 token 超限触发的自动迁移不再静默执行,迁移前先向用户确认(新增 `src/auto-migrate-confirm-plugin.ts`):
251
- - `AutoMigrateConfirmPlugin` 继承 SDK 的 `AutoMigratePlugin`,**仅在「超阈值」与「调用迁移」之间插入一次确认**:阈值判断、迁移调用与错误处理仍复用基类,实际迁移仍委托共享的 `migrationService`(与 `/migrate` 同一条链路)——即 SDK 侧「只负责触发」的语义不变,确认是 CLI 层的增量
252
- - 确认框展示当前用量与阈值(如 `260000/250000 tokens`)并说明影响(生成交接文档 → 保存当前对话 → 开启新会话继续),**默认「是」**(回车即迁移,与 `/migrate` 默认值一致)
253
- - 选择「否」仅跳过**本次**迁移,不记录隐式状态:当前对话不受影响(历史未被剔除),可继续提问或随时 `/migrate`;仍处超限状态时下次发送后再次询问,并提示继续增长至严重超限会被 `ContextGuardPlugin` 中断
254
- - **非交互环境不询问**:仅在 stdin 与 stdout 均为 TTY 时弹出确认框;管道、重定向、e2e 脚本无确认通道,保持既有静默自动迁移行为(不阻塞等待输入、不额外输出,管道语义不变)
255
- - 对话装配处(`src/conversation-runner.ts`)改注册 `AutoMigrateConfirmPlugin`,其余插件(`ContextGuardPlugin` 护栏位置)不变
256
- - **对话内命令提示** — 对话输入行键入 `/` 即实时列出可用命令及说明,继续输入按前缀收敛候选,无匹配时提示「无匹配命令(输入 /help 查看全部)」:
257
- - 新增 `src/slash-hint-prompt.ts`:继承 inquirer 的 input 提示并覆写 `render`,将候选命令作为底栏(`bottomContent`)交由 inquirer 的 `ScreenManager` 渲染,复用其折行、终端缩放、擦除与光标归位处理,渲染表现与其它提示一致
258
- - **仅提示,不改变提交语义**:回车始终提交输入原文,命令仍由 `dispatchCommand` 按完整名称精确匹配分发(不做前缀补全、不做高亮选择)
259
- - 非 TTY 环境(stdin/stdout 重定向、e2e 脚本)自动不渲染提示,退化为普通输入行,既有输出行为不变
260
- - 普通聊天内容(非 `/` 开头)不产生任何提示
261
- - 对话主循环输入改由 `src/slash-hint-prompt.ts` 的 `askInput()` 提供(`src/conversation-runner.ts` 不再直接构造输入提示)
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
- - **对话命令清单收敛为唯一来源** — 新增 `src/conversation-commands/registry.ts` 集中声明命令名、别名与说明,由分发(`index.ts`)、帮助(`help.ts`)与输入提示三处共用:
266
- - **别名不再重复登记**:registry 声明的别名由 `index.ts` 自动展开为处理函数表的键;registry 声明了命令却未注册处理函数时于启动阶段即报错(fail-fast)
267
- - `/help` 输出改由 registry 渲染,命令列按最长命令串对齐,与输入提示列宽一致(命令与说明文案保持不变)
268
- - `getCommandNames()` 迁至 registry,`conversation-commands/index.ts` 原样再导出,调用方无需改动
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
- - **引用了 inquirer 内部模块** `inquirer/lib/prompts/input.js` — inquirer 9 未导出 BasePrompt/InputPrompt,实现命令提示底栏只能深层导入(inquirer@9 的 `package.json` 无 `exports` 字段,ESM 深层导入已在 Node 26 + inquirer 9.3.8 下实测可用)。类型声明见 `src/typings/inquirer-internals.d.ts`;**升级 inquirer 主版本时须回归验证该模块路径与 `render` 行为**
273
- - **未新增运行时依赖**,`pnpm-lock.yaml` 不变
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
- - 新增 `src/auto-migrate-confirm-plugin.test.ts`(阈值判断、确认与拒绝分支、非交互降级、TTY 判定与默认确认文案)
278
- - 新增 `src/slash-hint-prompt.test.ts`(提示文本生成 + 底栏渲染,覆盖 TTY / 非 TTY / 已提交 / 错误行分支)
279
- - 新增 `src/conversation-commands/registry.test.ts`(元数据、别名折叠、前缀匹配、命令分发与别名等价)
280
- - `tsc` 类型检查与全量单测通过(68 passed / 8 files),`npm run build` 成功
281
- - 构建产物在真实 TTY 下实测:输入 `/` 列出全部命令、`/b` 收敛为 `/back`、回车提交原文且提示行正确擦除;`/help` 输出格式与列宽正常
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
- - **修复迁移后旧消息既未标记 omit 也未物理删除的问题(依赖升级)** — 上游 `@ai-zen/agents-sdk` 0.9.3 修复 `createAgent` 直接引用 `definition.messages` 导致对话 `append` 反向污染 Agent 定义模板的缺陷;此前该缺陷使任务迁移 `prune`/`omit` 策略失效(旧消息不被清理、却能注入断点消息)
288
- - **恢复草稿/已保存对话时拷贝消息数组** — `src/agent-creator.ts` 由直接引用改为 `agent.messages = [...messages]`,避免后续 `append` 改写草稿/对话存档的内存数组
289
- - **依赖跟随升级** — `@ai-zen/agents-sdk` 至 `0.9.3`(借助 caret 范围自动解析)
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` 类型检查与全量单测通过(37 passed / 5 files)
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
- - **启动版本 banner 增强** — 新增 `src/version.ts`,统一从实际安装包读取版本号(而非依赖声明中的版本范围);主菜单版本行由仅打印 CLI 版本扩展为同时展示 CLI / SDK / Core 三版本,例如 `AI-Zen CLI v0.6.2 · SDK 0.9.2 · Core 4.1.0`
300
- - **适配 `@ai-zen/agents-core` 4.1.0 类型变更** — 上游将 `AgentNS.Message.id` 由可选改为**强制必填**(构造时自动生成)。`src/menus/agents.ts` 与 `src/conversation-commands/back.test.ts` 中直接构造的裸消息对象改用 `Message` 内置工厂方法创建(`Message.System` / `Message.User` / `Message.Assistant`,以及 `new Message(...)`),由 core 统一生成消息 `id`
301
- - **依赖跟随升级** — `@ai-zen/agents-sdk` 至 `0.9.2`、`@ai-zen/agents-core` 至 `4.1.0`(借助 caret 范围自动解析)
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
- - 依赖升级后 `tsc` 类型检查与全量单测通过(37 passed / 5 files),`npm run build` 成功
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
- - **升级 `@ai-zen/agents-sdk` 到 0.9.0** — 迁移服务新增 `strategy` 开关:
312
- - **`omit`(默认)**:历史消息标记 `omit: true` 保留可审计,追加「对话断点」消息为新上下文起点
313
- - **`prune`(CLI 采用)**:物理剔除历史,仅保留系统提示 + 交接断点(即先前替换行为)
314
- - CLI `migration-service.ts` 显式启用 `strategy: "prune"`,与端侧「迁移后开启新会话」的产品语义一致,**对外使用无差异**
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
- - 依赖升级后单测全量通过(37 passed),`tsc --noEmit` 零错误
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
- - **升级 `@ai-zen/agents-sdk` 到 0.8.0** — 任务迁移能力在 SDK 内完成重构(详见 SDK CHANGELOG):
325
- - **`TaskMigrationService` 变为实例化、自包含迁移服务** —— 构造仅接收 `{ onBeforeMigrate?, onMigrated?, logger? }`(钩子平铺,无 `hooks` 包装),`migrate({ agent })` 直接复用传入 `agent` 的 `client`/`model`/`modelConfig` 生成交接文档,无需 Provider、无需独立迁移 Agent;暴露结构化 `MigrationContext`
326
- - **`AutoMigratePlugin` 收敛为仅触发角色** —— 构造注入 `{ service, maxTokens }`,只检测 token 超限并委托 `service.migrate`
327
- - **CLI 移除 `createMigrationAgent`** —— 删除 `src/agent-creator.ts` 中依赖已删除 `TaskMigrationService.createAgentDefinition` 的迁移 Agent 构建;手动迁移命令与自动迁移回调统一改为使用 SDK 迁移服务实例,钩子平铺在服务构造上
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
- - **手动任务迁移命令 `/migrate`** — 对话中随时输入 `/migrate` 即可主动触发任务迁移,无需等待 token 超限:
332
- - 迁移动作:保存当前对话 → 生成交接文档 → 开启新会话(注入交接文档为上下文)→ 新对话落盘为草稿(`_current.json`)
333
- - 与自动迁移联动复用 SDK 的 `TaskMigrationService.migrate`,通过 `onMigrated` 钩子处理后保存
334
- - 非破坏性:旧对话已保存到 `conversations/`,生成失败时可重试或继续当前对话
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
- - **迁移服务单实例收敛** — 将 `TaskMigrationService` 实例挂载到 `ConversationContext.migrationService`,自动迁移(`AutoMigratePlugin` 触发)与手动迁移(`/migrate` 命令)共用**同一个实例**;迁移前后处理(保存旧对话 / 开启新会话 / 落盘草稿)统一收敛到该服务钩子,消除自动与手动两侧各自 `new TaskMigrationService` 及重复的后处理实现
339
- - **迁移服务抽离为独立模块** — 新增 `src/migration-service.ts`,由 `createMigrationService(ctx)` 统一构建迁移服务实例(含迁移前后钩子);`saveCurrentConversation` 提升为 `conversation-repository.ts` 的共享 `saveConversation`,供迁移钩子与错误保存共用
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 侧:`TaskMigrationService`(含 `migrate` / 序列化 / 钩子上下文)、`AutoMigratePlugin`(触发委托断言)全量通过(447 passed / 2 skipped)
344
- - CLI 侧:`pretest` 类型检查与全量单测通过(37 passed),`npm run build` 成功
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
- - **升级 `@ai-zen/agents-core` 到 4.0.0、`@ai-zen/agents-sdk` 到 0.7.0** — 随底层核心重构(官方 OpenAI SDK + 插件化 Agent 驱动层):
351
- - **`createModel()` 返回结构变化** — 从返回旧 `ChatCompletionModel` 实例改为 `{ client, model, modelConfig }`(openai SDK client + 模型名 + 模型参数);`createMigrationAgent` 相应解构后传入 `SdkAgent` 新构造签名
352
- - **`SdkAgent` 构造签名变化** — 由 `{ ..., model }`(模型对象)改为 `{ client, model, modelConfig }`
353
- - **CLI 其余部分完全兼容** — 流式事件(`open`/`chunk`/`error`/`sub-agent`/`sub-agent-end`)、插件(`AutoMigratePlugin`/`AutoRefreshToolsPlugin`/`ContextGuardPlugin`)、`AgentNS` 类型(`Delta`/`StreamResponseData`/`Message`)在 core 4.0.0 / sdk 0.7.0 中原样保留,`AgentPlugin`/`SendContext` 由 sdk 重新导出,无破坏
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
- - 依赖升级后单测全量通过(37 passed / 0 failed),`tsc --noEmit` 零错误
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` 前置构建** — e2e 测试运行 `dist/index.js`,此前若 `dist` 缺失或为旧构建会导致 e2e 失败(升级后曾因旧 `dist` 报 `AgentContext must have a client`)。现于 e2e 前自动 `npm run build`,干净环境(CI clone 后)可开箱运行
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
- - **升级 `@ai-zen/agents-core` 到 3.4.0、`@ai-zen/agents-sdk` 到 0.6.0** — 随底层升级带来开箱即用的增强能力:
368
- - **内置工具中断信号(abort)支持** — `sleep`/`exec`/`downloadFile`/`generateImage`/`glob`/`findText` 及 subAgent 体系监听 `abort` 信号,CLI 终止对话时超长耗时操作可及时中断,避免资源滞留(`exec` 可区分 `aborted` 与 `timeout` 终止原因)
369
- - **工具定义内聚重构** — `Tool` 基类瘦身(不再强制传 `type`/`function`),定义与实现同处一类,回调签名由 `this` 注入改为显式 `(parsed_args, ctx)` 传参,`SdkCallbackTool` 构造改为 `{env}` 容器并透传完整 `ToolCallContext`
370
- - **MCP 工具 signal 透传** — `call_mcp_tool` / `read_mcp_resource` 支持中断
371
- - cli 层未直接使用上述受影响 API,升级为无破坏性平滑升级,功能不受影响
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
- - 依赖升级后单测全量通过(37 passed / 0 failed)
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
- - **升级 `@ai-zen/agents-core` 到 3.3.1、`@ai-zen/agents-sdk` 到 0.5.7** — 跟随上游内部重构,开箱即用,CLI 无代码改动:
382
- - **`innerLoopTasks` 拆分为双集合语义** — Core 3.3.1 新增 `innerLoopsTasks`(整组任务记录)与 `innerLoopTasks`(当前轮活跃任务);`abort()` 只中止当前轮活跃任务,不再误标已完成的轮次
383
- - **内循环开头统一追加 Assistant 占位** — `send()` / `AgentTool` 不再手动追加 Assistant,由 `run()` 每次内循环开头统一处理,多轮工具调用同样收敛到该处
384
- - **SDK 0.5.7 同步升级** — 依赖 core 3.3.1,全部 426 个测试(含真实 DeepSeek e2e)通过,完全兼容
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
- - **升级 `@ai-zen/agents-core` 到 3.3.0、`@ai-zen/agents-sdk` 到 0.5.6** — 跟随上游统一重构,开箱即用,CLI 无代码改动:
391
- - **`ToolCallContext` 统一贯穿「拦截决策 → 执行」** — Core 3.3.0 将 `FunctionCallContext` 统一为 `ToolCallContext`:`onToolCall` 钩子与 `Tool.exec(ctx)` 收同一个实例;新增 `tool_call`(统一形状)、`tool`(匹配到的工具)、`signal`(中止信号)字段,`toolCall` 改名为 `tool_call`。保留 `@deprecated FunctionCallContext` 兼容别名,旧代码无需改动
392
- - **新增 `onToolCall` 拦截钩子** — Core 3.3.0 与 SDK 0.5.6 同时提供:每个工具调用执行前可**拒绝**(返回字符串 = 拒绝原因作为工具结果回给 LLM、工具不执行、继续下一轮);SDK 侧 `AgentPlugin` 支持该钩子(多插件按注册顺序调用,任一返回字符串即拒绝)
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
- - **依赖声明统一为版本范围** — `@ai-zen/agents-core` 与 `@ai-zen/agents-sdk` 由精确版本(`3.2.0` / `0.5.3`)改为 caret 范围(`^3.2.0` / `^0.5.3`),与 SDK 侧对 core 的 `workspace:^`(发布为 `^3.x`)语义对齐。允许各自主版本范围内的兼容升级,避免升级版本时反复改动依赖声明;实际解析版本保持 `3.2.0` / `0.5.3`(当前 latest),功能不受影响
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
- - **接入 `ContextGuardPlugin` 上下文安全护栏** — 在 `conversation-runner.ts` 的插件装配中注册 SDK 0.5.3 提供的 `ContextGuardPlugin`(置于 `AutoMigratePlugin` 之前,复用同一 `maxTokens`)。每次内循环发请求前检测用量,超过 `maxTokens×1.2`(超阈 20%)即抛出 `ContextOverflowError` 中断对话,防止读入超大文件等突发超限撑爆上下文;与迁移插件区间互补:正常超限走交接迁移,严重超限由护栏直接中断报错
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
- - **修正 `conversation-runner.ts` 插件注册注释编号** — 统一连续编号(1 cwdTracker / 2 autoRefreshTools / 3 draftPlugin / 4 contextGuard / 5 autoMigrate),消除此前重复编号
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
- - **升级 `@ai-zen/agents-sdk` 到 0.5.3** — 随 SDK 新能力开箱即用:
415
- - **初始化默认释放 socket-pty MCP 配置** — SDK `bootstrap()` 新增 `ensureDefaultMcpConfig()`,CLI 首启在 `~/.ai-zen/mcp.json` 自动写入含 `socket-pty` 终端的默认 MCP 配置(`npx -y @ai-zen/socket-pty mcp`),文件已存在则幂等不覆盖
416
- - **`load_mcp` 透传 server description** — 枚举各 MCP 服务器描述供 LLM 参考(对齐 `load_skill`)
417
- - **新增 `ContextGuardPlugin` 上下文安全护栏** — 在发请求前检测用量,超过 `maxTokens×1.2`(超阈 20%)时抛出 `ContextOverflowError` 中断对话,防止读入超大文件导致上下文失控;与 `AutoMigratePlugin` 职责分离、区间互补
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
- - **自动迁移后未立即保存新对话为草稿** — `AutoMigratePlugin` 的迁移发生在 `onAfterSend`(所有内循环已结束、`DraftPlugin` 的 `onInnerLoopEnd` 不再触发),迁移替换 `agent.messages` 后的新开场白未被及时落盘,用户在中途退出会丢失迁移后的开场白。现于 `onMigrated` 回调中迁移完成后立即将新消息写入草稿(`_current.json`),与「原对话先保存为 `conversations/`」形成完整闭环
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 配置结构统一为业界标准 `mcpServers`** — 以 SDK(`discoverMcpServers`)为准,CLI 的 `McpConfig` 顶层字段由 `servers` 改为 `mcpServers`、server 传输字段由 `transport` 改为 `type`。消除格式漂移,CLI 管理界面(`zen config`)与 SDK 默认释放的 mcp.json 完全对齐,socket-pty 默认配置可被正确显示与管理
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
- - 更新 `config.test.ts` 中 MCP 读写相关断言以匹配新的 `mcpServers` 结构与 `type` 字段
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
- - **会话/草稿持久化下放 CLI** — SDK 0.5.0 移除了会话/草稿产品层(`ConversationRepository` / `DraftRepository` / `AutoDraftPlugin`)。CLI 自建本地存储:
436
- - 新增 `src/conversation-repository.ts` — `conversationRepository`(复用 SDK `EntityRepository`)
437
- - 新增 `src/draft-repository.ts` — `DraftRepository` + `draftRepository` 单例(草稿无 id 字段,单独实现)
438
- - 新增 `src/draft-plugin.ts` — CLI 草稿插件 `DraftPlugin`(替代被移除的 `AutoDraftPlugin`,`onInnerLoopEnd` 自动保存)
439
- - **更新 `@ai-zen/agents-sdk` 到 0.5.0** — 内置工具类化(`SdkCallbackTool` + `ToolEnv` 注入)、`Provider` 支持 `cwd`/`env`、`generateImage` 类化为 `GenerateImageTool`
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
- - **依赖同步线上版本** — `@ai-zen/agents-core` `3.0.1`、`@ai-zen/agents-sdk` `0.5.0`(开发期曾使用本地 `link:` 调试,已还原为 npm 版本号)
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
- - **更新 `@ai-zen/agents-sdk` 到 0.4.0** — load_mcp 返回值改为结构化 JSON(含完整 inputSchema),日志 API 改为全局单例 `getLogger()`/`setLogger()`
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
- - **更新 `@ai-zen/agents-sdk` 到 0.3.4** — 包含 AutoMigratePlugin 钩子重命名(`onHandoff` → `onMigrated`)
456
- - **保存旧对话的时机修正** — 从 `onMigrated`(原 `onHandoff`)移至 `onBeforeMigrate`,确保保存的是完整的旧对话历史
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
- - **更新 `@ai-zen/agents-sdk` 到 0.3.3** — 包含默认 Agent/SubAgent 提示词优化(信息明确性要求、矛盾检测)
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
- - **同步 SDK 0.3.2 异步 API** — `@ai-zen/agents-sdk` 从 `0.3.1` → `0.3.2`
469
- - **`readConfig()` / `saveConfig()` 改为 async** — 因 SDK `ConfigManager` 全面异步化
470
- - **`createAgent()` 恢复为 async** — 因 SDK `createAgent()` 改回异步
471
- - **`installHook()` / `uninstallHook()` 改为 async** — Shell hook 安装/卸载使用 `fs.promises`
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
- - **全面消除同步文件 IO** — `config.ts`、`hook.ts`、`conversations.ts`、`config-display.ts` 中所有 `existsSync`、`readFileSync`、`writeFileSync`、`readdirSync`、`statSync`、`renameSync`、`appendFileSync`、`mkdirSync`、`unlinkSync` 替换为 `fs.promises` 异步 API
476
- - **`getConversationsList()` 改为 async** — 对话列表读取使用异步文件 API
477
- - **MCP 配置读写异步化** — `readMcpConfig()`、`writeMcpConfig()`、`readProjectMcpConfig()` 均改为 async
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
- - 同步更新 `config.test.ts` 为 `async`/`await`,全部 43 个测试通过(含 6 个 E2E)
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
- - **更新依赖**: `@ai-zen/agents-core` → `3.0.0-alpha.4`, `@ai-zen/agents-sdk` → `0.2.7`
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
- - **更新 `@ai-zen/agents-sdk` 到 0.2.6** — 包含默认 SubAgent 函数名和描述修复,LLM 现在能正确识别子 Agent
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
- - **更新 `@ai-zen/agents-sdk` 到 0.2.5** — 包含默认 SubAgent 权限修复,`general-assistant` 现在能正常使用工具
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