@ai-zen/cli 1.0.0-alpha.2 → 1.0.0-alpha.20

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 (181) hide show
  1. package/CHANGELOG.md +356 -0
  2. package/README.md +99 -28
  3. package/README.zh.md +99 -28
  4. package/dist/agent-creator.d.ts +11 -0
  5. package/dist/agent-creator.d.ts.map +1 -1
  6. package/dist/agent-creator.js +20 -11
  7. package/dist/agent-creator.js.map +1 -1
  8. package/dist/agents-store.d.ts.map +1 -1
  9. package/dist/agents-store.js +40 -3
  10. package/dist/agents-store.js.map +1 -1
  11. package/dist/auto-migrate-confirm.d.ts +43 -0
  12. package/dist/auto-migrate-confirm.d.ts.map +1 -0
  13. package/dist/auto-migrate-confirm.js +46 -0
  14. package/dist/auto-migrate-confirm.js.map +1 -0
  15. package/dist/config-editor.d.ts +4 -4
  16. package/dist/config-editor.d.ts.map +1 -1
  17. package/dist/config-editor.js +6 -8
  18. package/dist/config-editor.js.map +1 -1
  19. package/dist/config.d.ts +23 -2
  20. package/dist/config.d.ts.map +1 -1
  21. package/dist/config.js +26 -2
  22. package/dist/config.js.map +1 -1
  23. package/dist/conversation-commands/registry.d.ts.map +1 -1
  24. package/dist/conversation-commands/registry.js +4 -1
  25. package/dist/conversation-commands/registry.js.map +1 -1
  26. package/dist/conversation-persist-plugin.d.ts +0 -1
  27. package/dist/conversation-persist-plugin.d.ts.map +1 -1
  28. package/dist/conversation-persist-plugin.js +2 -3
  29. package/dist/conversation-persist-plugin.js.map +1 -1
  30. package/dist/conversation-repository.d.ts +5 -1
  31. package/dist/conversation-repository.d.ts.map +1 -1
  32. package/dist/conversation-repository.js +1 -2
  33. package/dist/conversation-repository.js.map +1 -1
  34. package/dist/handoff-service.d.ts +19 -0
  35. package/dist/handoff-service.d.ts.map +1 -0
  36. package/dist/handoff-service.js +86 -0
  37. package/dist/handoff-service.js.map +1 -0
  38. package/dist/index.js +21 -3
  39. package/dist/index.js.map +1 -1
  40. package/dist/model-switch.d.ts +58 -0
  41. package/dist/model-switch.d.ts.map +1 -0
  42. package/dist/model-switch.js +61 -0
  43. package/dist/model-switch.js.map +1 -0
  44. package/dist/reasoning-effort.d.ts +77 -0
  45. package/dist/reasoning-effort.d.ts.map +1 -0
  46. package/dist/reasoning-effort.js +137 -0
  47. package/dist/reasoning-effort.js.map +1 -0
  48. package/dist/reasoning-presets.d.ts +34 -0
  49. package/dist/reasoning-presets.d.ts.map +1 -0
  50. package/dist/reasoning-presets.js +85 -0
  51. package/dist/reasoning-presets.js.map +1 -0
  52. package/dist/sdk-log.d.ts +48 -0
  53. package/dist/sdk-log.d.ts.map +1 -0
  54. package/dist/sdk-log.js +67 -0
  55. package/dist/sdk-log.js.map +1 -0
  56. package/dist/session-pointer.d.ts +22 -9
  57. package/dist/session-pointer.d.ts.map +1 -1
  58. package/dist/session-pointer.js +55 -23
  59. package/dist/session-pointer.js.map +1 -1
  60. package/dist/stdio-runner.d.ts.map +1 -1
  61. package/dist/stdio-runner.js +15 -13
  62. package/dist/stdio-runner.js.map +1 -1
  63. package/dist/sub-agent-guard-plugin.d.ts +5 -5
  64. package/dist/sub-agent-guard-plugin.d.ts.map +1 -1
  65. package/dist/sub-agent-guard-plugin.js +7 -7
  66. package/dist/sub-agent-guard-plugin.js.map +1 -1
  67. package/dist/tui/chat-session.d.ts +83 -59
  68. package/dist/tui/chat-session.d.ts.map +1 -1
  69. package/dist/tui/chat-session.js +161 -105
  70. package/dist/tui/chat-session.js.map +1 -1
  71. package/dist/tui/components.d.ts +7 -2
  72. package/dist/tui/components.d.ts.map +1 -1
  73. package/dist/tui/components.js +3 -3
  74. package/dist/tui/components.js.map +1 -1
  75. package/dist/tui/config-wizard/engine.js +1 -1
  76. package/dist/tui/config-wizard/engine.js.map +1 -1
  77. package/dist/tui/config-wizard/fields.d.ts +3 -1
  78. package/dist/tui/config-wizard/fields.d.ts.map +1 -1
  79. package/dist/tui/config-wizard/fields.js +58 -6
  80. package/dist/tui/config-wizard/fields.js.map +1 -1
  81. package/dist/tui/config-wizard/menu.d.ts.map +1 -1
  82. package/dist/tui/config-wizard/menu.js +2 -3
  83. package/dist/tui/config-wizard/menu.js.map +1 -1
  84. package/dist/tui/config-wizard/step-defaults.d.ts.map +1 -1
  85. package/dist/tui/config-wizard/step-defaults.js +4 -11
  86. package/dist/tui/config-wizard/step-defaults.js.map +1 -1
  87. package/dist/tui/config-wizard/types.d.ts +18 -3
  88. package/dist/tui/config-wizard/types.d.ts.map +1 -1
  89. package/dist/tui/config-wizard/wizard.d.ts.map +1 -1
  90. package/dist/tui/config-wizard/wizard.js +4 -3
  91. package/dist/tui/config-wizard/wizard.js.map +1 -1
  92. package/dist/tui/highlight.d.ts +3 -2
  93. package/dist/tui/highlight.d.ts.map +1 -1
  94. package/dist/tui/highlight.js +3 -2
  95. package/dist/tui/highlight.js.map +1 -1
  96. package/dist/tui/index.d.ts +1 -0
  97. package/dist/tui/index.d.ts.map +1 -1
  98. package/dist/tui/index.js +87 -40
  99. package/dist/tui/index.js.map +1 -1
  100. package/dist/tui/input-editing.d.ts +49 -0
  101. package/dist/tui/input-editing.d.ts.map +1 -0
  102. package/dist/tui/input-editing.js +87 -0
  103. package/dist/tui/input-editing.js.map +1 -0
  104. package/dist/tui/key-debug.d.ts +62 -0
  105. package/dist/tui/key-debug.d.ts.map +1 -0
  106. package/dist/tui/key-debug.js +92 -0
  107. package/dist/tui/key-debug.js.map +1 -0
  108. package/dist/tui/mouse.d.ts +55 -0
  109. package/dist/tui/mouse.d.ts.map +1 -0
  110. package/dist/tui/mouse.js +74 -0
  111. package/dist/tui/mouse.js.map +1 -0
  112. package/dist/tui/screens/chat-commands.d.ts +12 -2
  113. package/dist/tui/screens/chat-commands.d.ts.map +1 -1
  114. package/dist/tui/screens/chat-commands.js +204 -17
  115. package/dist/tui/screens/chat-commands.js.map +1 -1
  116. package/dist/tui/screens/chat.d.ts +2 -5
  117. package/dist/tui/screens/chat.d.ts.map +1 -1
  118. package/dist/tui/screens/chat.js +460 -98
  119. package/dist/tui/screens/chat.js.map +1 -1
  120. package/dist/tui/screens/frame-buffer.d.ts +47 -0
  121. package/dist/tui/screens/frame-buffer.d.ts.map +1 -0
  122. package/dist/tui/screens/frame-buffer.js +78 -0
  123. package/dist/tui/screens/frame-buffer.js.map +1 -0
  124. package/dist/tui/screens/handoff-choice.d.ts +37 -0
  125. package/dist/tui/screens/handoff-choice.d.ts.map +1 -0
  126. package/dist/tui/screens/handoff-choice.js +41 -0
  127. package/dist/tui/screens/handoff-choice.js.map +1 -0
  128. package/dist/tui/screens/index.d.ts +8 -8
  129. package/dist/tui/screens/index.d.ts.map +1 -1
  130. package/dist/tui/screens/index.js +7 -7
  131. package/dist/tui/screens/index.js.map +1 -1
  132. package/dist/tui/screens/input.d.ts +35 -0
  133. package/dist/tui/screens/input.d.ts.map +1 -1
  134. package/dist/tui/screens/input.js +66 -0
  135. package/dist/tui/screens/input.js.map +1 -1
  136. package/dist/tui/screens/layout.d.ts +13 -3
  137. package/dist/tui/screens/layout.d.ts.map +1 -1
  138. package/dist/tui/screens/layout.js +15 -8
  139. package/dist/tui/screens/layout.js.map +1 -1
  140. package/dist/tui/screens/lines.d.ts +12 -41
  141. package/dist/tui/screens/lines.d.ts.map +1 -1
  142. package/dist/tui/screens/lines.js +87 -95
  143. package/dist/tui/screens/lines.js.map +1 -1
  144. package/dist/tui/screens/messages-view.d.ts +10 -0
  145. package/dist/tui/screens/messages-view.d.ts.map +1 -0
  146. package/dist/tui/screens/messages-view.js +115 -0
  147. package/dist/tui/screens/messages-view.js.map +1 -0
  148. package/dist/tui/screens/notice.d.ts +32 -0
  149. package/dist/tui/screens/notice.d.ts.map +1 -0
  150. package/dist/tui/screens/notice.js +46 -0
  151. package/dist/tui/screens/notice.js.map +1 -0
  152. package/dist/tui/screens/render.d.ts +2 -4
  153. package/dist/tui/screens/render.d.ts.map +1 -1
  154. package/dist/tui/screens/render.js +7 -43
  155. package/dist/tui/screens/render.js.map +1 -1
  156. package/dist/tui/screens/splash.d.ts.map +1 -1
  157. package/dist/tui/screens/splash.js +7 -1
  158. package/dist/tui/screens/splash.js.map +1 -1
  159. package/dist/tui/text.d.ts +12 -2
  160. package/dist/tui/text.d.ts.map +1 -1
  161. package/dist/tui/text.js +17 -3
  162. package/dist/tui/text.js.map +1 -1
  163. package/dist/tui/theme.d.ts +7 -1
  164. package/dist/tui/theme.d.ts.map +1 -1
  165. package/dist/tui/theme.js +15 -3
  166. package/dist/tui/theme.js.map +1 -1
  167. package/dist/types.d.ts +7 -5
  168. package/dist/types.d.ts.map +1 -1
  169. package/package.json +3 -3
  170. package/dist/auto-migrate-confirm-plugin.d.ts +0 -58
  171. package/dist/auto-migrate-confirm-plugin.d.ts.map +0 -1
  172. package/dist/auto-migrate-confirm-plugin.js +0 -61
  173. package/dist/auto-migrate-confirm-plugin.js.map +0 -1
  174. package/dist/migration-service.d.ts +0 -15
  175. package/dist/migration-service.d.ts.map +0 -1
  176. package/dist/migration-service.js +0 -59
  177. package/dist/migration-service.js.map +0 -1
  178. package/dist/tui/screens/state.d.ts +0 -13
  179. package/dist/tui/screens/state.d.ts.map +0 -1
  180. package/dist/tui/screens/state.js +0 -75
  181. package/dist/tui/screens/state.js.map +0 -1
package/CHANGELOG.md CHANGED
@@ -1,3 +1,359 @@
1
+ ## [1.0.0-alpha.20] - 2026-10-27
2
+
3
+ ### ✨ 新功能
4
+
5
+ - **对话屏支持鼠标滚轮滚动** —— 此前滚轮只能滚终端自己的回滚缓冲(`alternateScreen: false` 走内联渲染),滚上去看到的是历史帧快照,状态栏与输入框会直接滚出视野。
6
+ - 新增 `src/tui/mouse.ts`:挂载时下发 `DECSET 1000`(按钮事件,含滚轮)+ `1006`(SGR 扩展坐标);`mount()` 的退出、`farewell()`、信号与未捕获异常兜底**三处**都下发关闭序列,进程崩了也不会把 `^[[<64;…M` 喷进 shell。
7
+ - `parseWheel()` 剥掉 Shift(4) / Meta(8) / Ctrl(16) 修饰位后取滚轮码(`64` 上、`65` 下),横向滚轮 `66` / `67` 直接忽略;一格滚 3 行。
8
+ - 滚轮与 `PgUp`/`PgDn`、按行滚动共用同一套视口钳制 —— 抽出纯函数 `clampViewTop()`(`screens/layout.ts`),越界方向自然吃掉,返回 `null` 即贴底跟随最新内容。生成过程中滚动不会被拉回底部。
9
+ - **`isTerminalReply()` 纳入鼠标序列**:点击 / 拖拽 / 滚轮一律在源头丢弃。这一步是必须的 —— Ink 会把整条 SGR 序列当 `char` 交给 `useInput`(实测为 `"[<64;10;5M"`,所有键标志位 false),不拦就会落进输入框变成正文。
10
+ - 必须用 `1006`(SGR 坐标)而不是老的 X10 编码:后者被 Ink 的解析器拆成 `[M` + 三个普通字符两条事件,三个字符会直接被当正文插入(已实测)。
11
+ - 启动屏「任意键跳过」不再把鼠标事件当成按键。
12
+
13
+ ### 📝 文档
14
+
15
+ - README(中 / 英)键位表新增「鼠标滚轮」一行,并写明两个坑:开启上报后**拖选复制需按住 `Shift`**(回退到终端原生选择);**`tmux` 用户需 `set -g mouse off`**,否则滚轮会先被 copy-mode 拦下。
16
+ - `docs/manifest.json`、`docs/{zh,en}/getting-started.md`、README 中的版本号与运行时依赖版本对齐(`agents-core ^4.5.2`、`agents-sdk 1.0.0-alpha.18`)。
17
+
18
+ ### ⚠️ 已知取舍(终端机制使然,非缺陷)
19
+
20
+ - 鼠标上报是终端级开关:点击 / 拖拽不再交给终端做原生选择,复制需按住 `Shift`(iTerm2 / Windows Terminal / kitty 等均如此)。
21
+ - 应用内滚动只动视口,不产生终端 scrollback —— 想回看历史用滚轮 / `PgUp`,而不是终端的回滚条。
22
+ - 老版 Windows `conhost` 不支持 `1006`,滚轮在那种终端上表现为无反应。
23
+
24
+ ### ✅ 测试
25
+
26
+ - 全量 **25 文件 / 316 例通过**(新增 `src/tui/mouse.test.ts` 1 文件 11 例;`screens.test.tsx` 新增滚轮解析、`clampViewTop` 钳制、启动屏鼠标守卫三组用例)。
27
+ - 真机(pty)端到端验证:滚轮方向解析、修饰位剥离、点击丢弃、普通打字不受影响、退出时下发关闭序列。
28
+
29
+ ## [1.0.0-alpha.19] - 2026-10-22
30
+
31
+ ### 🐛 修复
32
+
33
+ - **修复升级后首次启动报「Agent「default」未绑定模型」** —— alpha.18 把模型真相源改成 `AgentDefinition.modelId`,并把「为历史 Agent 补齐 modelId」放在 `bootstrap()` 里。但 TUI 的**续接会话**路径(`resumeLastSession()` 命中上一轮存档)根本不会经过 `resolveTarget()`,也就不会读取配置,于是 `bootstrap()` 从未执行:Agent 还是没绑模型、`config.defaultModel` 也还在,启动直接失败。
34
+ - `runTui()` 开头显式 `await ensureConfigDir()`,配置初始化早于任何 Agent 读取;
35
+ - `modelOf()` 自身也先读一次配置(双重保险),即使调用方漏了也不会再撞到这个错误。
36
+ - 新增回归测试:老配置(有 `defaultModel`、Agent 无 `modelId`)读一次配置后,模型被搬到 Agent 上、`config.json` 里的旧字段被清除。
37
+
38
+ ### ✅ 测试
39
+
40
+ - 全量 **24 文件 / 299 例通过**。
41
+
42
+ ## [1.0.0-alpha.18] - 2026-10-22
43
+
44
+ ### 🐛 修复
45
+
46
+ - **修复「模型显示与实际请求不是同一个」导致的 401 死锁** —— 此前模型有三处来源:状态栏与会话存档里的 `modelId`、Agent 定义里的 `modelId`、以及配置里的 `defaultModel`。三者一旦分叉,就会:
47
+ - 状态栏写着 `deepseek-flash`,请求实际发给 GLM,而 bigmodel 的 key 无效 → 看到 401;
48
+ - 想切回 DeepSeek 时,`/model` 拿**显示层**的值判断,发现「已经是当前模型」直接返回,实际模型一个字都没改——于是怎么切都还是 401,只能删配置。
49
+
50
+ 现在模型**只存在 `AgentDefinition.modelId` 一处**,状态栏、凭据预检、上下文阈值与 SDK 实发请求全部读它。
51
+
52
+ ### 🔧 行为变更(不兼容)
53
+
54
+ - **升级 `@ai-zen/agents-sdk` 至 `1.0.0-alpha.17`**:配置层不再有 `defaultModel`,缺失 `modelId` 的 Agent 会直接报错(默认 Agent 出厂自带 `deepseek-flash`);历史配置升级时会把老的 `defaultModel` 搬进默认 Agent,不改变你正在用的模型。
55
+ - **会话存档不再记 `modelId`** —— 续接对话时模型完全由 Agent 定义决定;旧存档里的该字段读取时忽略。
56
+ - **`/model` 只写 Agent 定义** —— 不再有「Agent 未绑定就改全局默认模型」的分支。
57
+ - **切模型即重置思考强度** —— `AgentDefinition.modelParams` 在切模型时**直接清空**(不保留、不迁移、不逐键剔除),让目标模型走自己的 `defaultParams`。原因:思考参数是模型相关的,DeepSeek 关思考的 `thinking: {type:"disabled"}` 带到 GLM-5.3 上会被直接拒绝——此前会出现「一个不肯关思考的模型 + 一个说要关的参数」同时发出去。想调再用 `/think` 选一次。
58
+ - **配置中心移除「默认模型」项** —— 模型改在 Agent 详情页的「模型」字段里选(影响范围从「全局默认」变成「该 Agent」)。
59
+
60
+ ### ✅ 测试
61
+
62
+ - 全量 **24 文件 / 298 例通过**。
63
+ - 新增 `model-switch` 用例:切换即清空 `modelParams`(不是只剔除思考键)、原本没有也不多写空对象。
64
+
65
+ ## [1.0.0-alpha.17] - 2026-10-22
66
+
67
+ ### 🔧 行为变更
68
+
69
+ - **升级 `@ai-zen/agents-sdk` 至 `1.0.0-alpha.16`(内含 core `4.5.2`):模型参数不再能覆盖请求体的固定字段** —— core 之前把模型参数 spread 在请求体最外层,于是 `Model.defaultParams` / `AgentDefinition.modelParams` 里若出现 `model` / `messages` / `tools` / `stream` / `stream_options`,会静默覆盖框架自己填的值。最惨的是 `stream: false`:流式被关掉而调用方仍按异步流解析,整轮直接失败。现在固定字段永远胜出。
70
+ - 对 `/think` 与思考档位**没有影响**:写入的都是 `thinking` / `reasoning_effort` 这类非固定键,照常透传。
71
+ - 唯一需要注意的是别在 `modelParams` 里手写上面那五个键——写了也不会生效。
72
+
73
+ ## [1.0.0-alpha.16] - 2026-10-22
74
+
75
+ ### 🔧 行为变更(不兼容)
76
+
77
+ - **升级 `@ai-zen/agents-sdk` 至 `1.0.0-alpha.15`:思考档位不再「存档位名」,改存展开后的参数** —— 上一版把档位名写在 Agent 定义的 `reasoningEffort` 上,运行期由 SDK 查表翻译成请求参数。那等于把「什么叫 `max`」这件产品语义塞进了运行时。现在改成**配置即事实**:
78
+ - `/think` 把选中档位的参数补丁直接写进 `AgentDefinition.modelParams`(如 `{"thinking":{"type":"enabled"},"reasoning_effort":"high"}`),SDK 只做一次 `{...defaultParams, ...modelParams}` 浅合并;
79
+ - **旧 `reasoningEffort` 自动迁移**:读 Agent 定义时展开成 `modelParams` 并写回;模型没关联策略的只删旧字段(不会瞎猜参数)。
80
+ - **思考策略表的归属调整为「SDK 提供、CLI 使用」** —— 表的类型、出厂内容与读写放在 SDK(CLI 与桌面端共享同一份「智谱 5.3 只认 low/high/max」的知识),CLI 只负责**利用**它:限制 `/think` 的可选档位、把档位展开成参数写进 Agent。`Scope` 不再需要注入策略表。
81
+
82
+ ### 🚀 新功能
83
+
84
+ - **状态栏档位改为「从生效参数反查」** —— 显示的就是真正会发给 API 的那几个参数匹配到的档位(模型默认参数也算),不再依赖 Agent 上的档位声明。三种状态:命中某档 → 「低/中/高…」;有 Agent 覆盖但匹配不上任何档位(手改过参数)→「自定义」;完全匹配不上 →「默认」。
85
+ - **`/config` →「模型」详情页的「思考策略」字段**继续可用,显示该策略及其档位(如 `zhipu-5.3(low / high / max)`),写错名字当场拦下。
86
+
87
+ ### ✅ 测试
88
+
89
+ - `src/reasoning-effort.test.ts` 重写(19 例):写入的是展开参数而非档位名、`max` → `off` 不残留 `reasoning_effort`、保留 `temperature` 等无关参数、`null` 清空后连 `modelParams` 一起删、往返一致(写进去能反查回原档位)。
90
+ - 新增 `src/agents-store.test.ts`(4 例):旧 `reasoningEffort` 展开并写回磁盘、无策略时只删字段、无旧字段时不写盘、迁移后能反查回原档位。
91
+ - 全量 **24 文件 / 299 例通过**。
92
+
93
+ ## [1.0.0-alpha.15] - 2026-10-22
94
+
95
+ ### 🔧 行为变更(不兼容)
96
+
97
+ - **升级 `@ai-zen/agents-sdk` 至 `1.0.0-alpha.14`:思考能力改为声明式策略表** —— 上一版用 `Model.reasoning: { efforts, disable? }` 表达厂商差异,`disable` 一词双义、`off` 靠拼接、还用全局枚举假装各家档位等价。现在协议差异全部搬进一张表:
98
+ - 新增 `~/.ai-zen/reasoning-presets.json`(首次启动自动释放,出厂三张:`openai` / `zhipu-5.3` / `deepseek`);模型只记 `reasoningPreset` 这个名字;
99
+ - `/think` 的可选档位改为读策略表 —— 表里有 `off` 才能关思考,没有就不给这个选项;
100
+ - 模型没关联策略、或策略名在表里不存在 → 一律视为不可调档(状态栏 `—`),绝不把来路不明的参数发出去;
101
+ - 档位中文标签改为「已知档位查表 + 未知档位名原样显示」,用户自建的档位名(如 `turbo`)不会显示成空白。
102
+ - `src/stdio-runner.ts` / `src/tui/chat-session.ts` 的 `TokenThresholds` 改为直接从 SDK 导入(此前是绕开 alpha.13 缺导出的派生类型)。
103
+
104
+ ### 🚀 新功能
105
+
106
+ - **`/config` →「模型」详情页新增「思考策略」字段** —— 显示该模型当前引用的策略及其档位(如 `zhipu-5.3(low / high / max)`),可直接改成表里另一个策略名;写错名字当场拦下并列出可选策略,留空 = 不调档。自建模型从此能指向内置策略,不必手改 JSON。
107
+ - **策略表可自行编辑** —— 加一张表、调档位补丁都行;给某张表标 `"custom": true` 后 SDK 升级不再覆盖它,其余策略仍随 SDK 同步。
108
+
109
+ ### ✅ 测试
110
+
111
+ - `src/reasoning-effort.test.ts` 重写(14 例):可选档位 = 策略表的键、未知档位名原样显示、未关联策略/策略名不存在时抛错、状态栏在非法声明时回退「默认」。
112
+ - 配置向导新增 2 例:写入合法策略名、非法策略名被拦下且不写盘。
113
+ - 全量 **23 文件 / 290 例通过**。
114
+
115
+ ## [1.0.0-alpha.14] - 2026-10-22
116
+
117
+ ### 🚀 新功能
118
+
119
+ - **输入框键位对齐 Claude Code / readline** —— 编辑快捷键统一为业界通行的一套:
120
+ - 按词操作:`Alt+B` / `Alt+F` 跨词移动(macOS 上 `Option+←/→` 发的就是 `ESC b` / `ESC f`,此前完全不响应);`Alt+Backspace`、`Alt+D`、`Ctrl+Delete` 按词删除
121
+ - 整段操作:`Ctrl+K` 删到行尾、`Ctrl+U` 删到行首、`Ctrl+W` **删到前一空白**(标点不切分,`src/utils/foo.ts` 一次删完)
122
+ - kill ring:`Ctrl+Y` 取回刚删的文本,`Alt+Y` 循环取回更早的;`Ctrl+_` 撤销上一次输入编辑;`Ctrl+S` 暂存 / 恢复草稿
123
+ - 移动与历史:`Ctrl+A` / `Ctrl+E` 行首 / 行尾,`Ctrl+P` / `Ctrl+N` 历史上下
124
+ - 新增纯函数层 `src/tui/input-editing.ts`(`deleteToPrevWhitespace` / `killedText` / kill ring / 撤销栈),12 例单测
125
+ - **macOS 按键诊断** —— `AIZ_DEBUG_KEYS=1` 时把终端环境(`TERM` / `TERM_PROGRAM`)与每次按键(`input` + 修饰位 + `raw` 状态)写入 `/tmp/aiz-key-debug.log`(`AIZ_KEYS_FILE` 可改路径),用于定位「某个键在我机器上没反应」:没新行=键被系统截走,`flags` 不符=匹配逻辑问题,`raw=false`=按键被终端自己解释。
126
+ - **`/model` 切换当前会话的模型并写入配置** —— 无参弹模型选择器(标注当前),`/model <id>` 直切。写入位置按「会话模型真正的来源」决定:Agent 定义显式绑定了模型 → 改该 Agent 的 `modelId`;没绑定(走默认模型)→ 改 `config.json` 的 `defaultModel`。切换后重建 Scope 与会话(消息与上下文保留),状态栏模型名随之更新。判定逻辑抽为 `src/model-switch.ts`(`resolveModelWriteTarget` / `switchModel`,7 例单测)。
127
+ - **思考强度展示与切换(`/think`,别名 `/effort`)** —— 状态栏在模型名后展示当前强度(`关` / `极低` / `低` / `中` / `高` / `极高` / `最高`;模型可调但 Agent 未声明时显示`默认`,模型不可调时显示 `—`)。`/think` 无参弹选择器(标注当前)、`/think high` 直设、`/think default` 清除声明恢复跟随模型默认。
128
+ - **档位存在 Agent 定义上**(`AgentDefinition.reasoningEffort`),不在模型上:同一个模型可以给编程 / 小说 / 闲聊三个 Agent 配不同强度,而模型的 `defaultParams` 不动。
129
+ - **可选档位由模型能力决定**(`Model.reasoning.efforts`,SDK 按厂商官方文档声明):智谱 GLM-5.3 系只有 `low/high/max` 且不能关(传 `medium` 或 `disabled` 会 400),DeepSeek 是 `off/low/high/max`,OpenAI 是 `off/minimal/low/medium/high`;模型没声明就一个档位都不给,绝不推测。
130
+ - 读写与校验抽为 `src/reasoning-effort.ts`(`effortChoices` / `describeEffort` / `setAgentEffort`,9 例单测)。
131
+ - **SDK 日志接管** —— TUI 启动即 `setLogger()`,关掉 SDK 的 `console.log` 兜底(否则 `load_mcp` 的 manifest 会直写 stdout 撕裂界面);`AIZ_DEBUG_SDK=1` 时改为落盘 `/tmp/aiz-sdk.log`(`AIZ_SDK_LOG_FILE` 可改路径),6 例单测。
132
+
133
+ ### 🔧 行为变更(不兼容)
134
+
135
+ - **升级 `@ai-zen/agents-sdk` 至 `1.0.0-alpha.13`,上下文阈值改为结构体** —— `Model.maxContextTokens` 被 `Model.tokenThresholds: { handoff, guard }` 取代(`maxContextChars` 淘汰):
136
+ - `handoff`:交接软线,`prompt_tokens` 超过它触发任务交接;
137
+ - `guard`:强行阻断硬线,每次请求**前**超过它即中断本轮并报错。
138
+ - 两条线都是**绝对值**,`ContextGuardPlugin` 的 `ratio` / `suspendedRatio` / `isSuspended` 已从 SDK 删除——想调松就改数字。代价是交接回合不再有 2.0 倍额外放宽,出厂把 `guard` 配成 `handoff × 1.5`(375K)以留出缓冲。
139
+ - 旧配置**自动迁移**:SDK 的 `ConfigManager.read()` 读到即迁(`maxContextTokens` → `handoff`,`maxContextChars` 按 `/4` 折算,缺 `guard` 按 1.5 倍补一次,旧键删除),检测到差异才写回磁盘。用户无需手改 `config.json`。
140
+ - 配置中心里原来的「迁移阈值」一项拆成「交接阈值」与「阻断阈值」两行,并校验软线必须小于硬线。
141
+
142
+ - **`Ctrl+N` 由「换行」改为「历史下一条」** —— 换行改用 `Ctrl+J` / `Ctrl+Enter` / `Alt+Enter` / `Shift+Enter` / `\` + `Enter`
143
+ - **`Ctrl+V` 不再兼任「翻页」** —— 预留给 Claude Code 的「粘贴图片」;当前按下会提示暂未实现。翻页用 `PgUp` / `PgDn`,按行滚动用 `Shift+↑ ↓` / `Alt+↑ ↓`
144
+ - **`Ctrl+U` 由「清空整个输入框」改为「删到行首」** —— macOS 的 iTerm2 与 Terminal.app 会把 `⌘⌫` 映射成 `Ctrl+U`,原先的语义会让 Mac 用户一按 ⌘⌫ 就丢掉整段草稿;清空全部改由 `Esc` 负责
145
+ - **`Ctrl+C` 空闲时不再直接退出** —— 有草稿时先清空并提示,再按一次才退出
146
+ - **`Ctrl+D` 改为双击确认退出** —— 有草稿时改为 readline 语义(删光标后一个字符)
147
+ - **忙碌期(生成 / 迁移中)`Esc` 现在等于中断本轮**
148
+
149
+ ### ⏳ 暂未实现
150
+
151
+ - **粘贴图片(`Ctrl+V`)** —— 需要先解决「文件 → 内容块」的端点能力探测(Files API 上传 vs base64 内联),不能把上传端点硬编码成某个 id;随后的 SDK 改造再做,本轮已把该功能代码移除,避免半成品上线。
152
+
153
+ ### ✅ 测试
154
+
155
+ - 新增 `src/tui/input-editing.test.ts`(12 例)+ `src/tui/key-debug.test.ts`(4 例)+ `src/sdk-log.test.ts`(6 例)+ `src/model-switch.test.ts`(7 例)+ `src/reasoning-effort.test.ts`(9 例)+ 输入工具函数 8 例;全量 **23 文件 / 286 例** 通过,`tsc` 与 `build` 零错误。
156
+
157
+ ### 📄 文档
158
+
159
+ - `README{,.zh}.md` 重写「键盘与终端兼容」小节:改为业界通行路线「Option 当 Meta + readline 系 Ctrl 键」,附 Apple Terminal / iTerm2 / VS Code / Ghostty 的具体设置路径;说明 `⌘` 需 kitty 键盘协议才会上报、`⌘⌫` 被终端映射为 `Ctrl+U` 的原因;补充按键诊断用法。`README{,.zh}.md` 与 `docs/{zh,en}/getting-started.md` 的命令表新增 `/model`、`/think`(含「配置文件来源」对照),`docs/manifest.json` 版本号同步。
160
+
161
+ ## [1.0.0-alpha.13] - 2026-10-21
162
+
163
+ ### 🚀 新功能
164
+
165
+ - **交接二次确认统一为「三选一」** —— 自动迁移、手动 `/migrate`、手动 `/handoff` 三条链路此前交互不一致(自动是 2 项选择器 + Esc 跳过;`/migrate` 是 yes/no 确认;`/handoff` 不询问)。现统一为同一个「交接方式」选择器:
166
+ - **当前会话自己写交接文档**(`self`,保真 · 可核实真实状态)
167
+ - **单独 Agent 写交接文档**(`summary`,独立摘要 · 快且省,不核实文件)
168
+ - **不交接**(跳过本次;`Esc` 同效)
169
+
170
+ 命令后显式带 `self` / `summary`(如 `/migrate self`)时直接执行、不再询问。选择器抽为共用模块 `src/tui/screens/handoff-choice.ts`,三处复用同一份选项与文案。
171
+
172
+ ### 🔧 修复
173
+
174
+ - **自动「不交接」的确认提示被覆盖** —— `notice` 通道只保留最新一条,原先「已跳过本次任务迁移」与「可随时 /migrate…」分两条推送,后者覆盖前者;现合并为单条,跳过确认可见。
175
+
176
+ ### ✅ 测试
177
+
178
+ - 新增 `src/tui/screens/handoff-choice.test.ts`(5 例):三选一取值顺序、`self`/`summary` 原样返回、「不交接」/`Esc` → `null`、标题透传。全量 **18 文件 / 237 用例**通过,`tsc` 与 `build` 零错误。
179
+
180
+ ### 📄 文档
181
+
182
+ - `docs/{zh,en}/migration.md`「触发方式」同步为统一的三选一交互(手动命令均会先询问,除非显式指定执行者);`docs/manifest.json`、`README{,.zh}.md` 版本号同步。
183
+
184
+ ## [1.0.0-alpha.12] - 2026-10-20
185
+
186
+ ### 🔧 修复
187
+
188
+ - **自动迁移后旧会话被覆盖、历史丢失** —— `ChatSession` 曾把会话 id 存两份(`this.currentId` 与 `ctx.currentId`)。手动 `/migrate` 与 `/new` 会同步两份,但**自动迁移**由 SDK 的 `AutoMigratePlugin` 直接驱动 `HandoffService.migrate()`,**不经过 `ChatSession` 的任何方法**,新 id 只写进了 `ctx.currentId`。于是 `ConversationPersistPlugin.getId()` 仍返回旧 id,迁移后第一次落盘就把 `prune` 产生的「系统提示 + 交接断点」写进**旧会话文件**,把已保存的完整历史覆盖掉(用户表现为「交接之后历史全没了,只剩交接文档」)。
189
+ - 修法:`currentId` / `currentName` 改为**代理 `ctx` 的 getter/setter**,消灭双真相源——谁更新 `ctx` 都立刻生效。
190
+ - `ConversationContext.currentId` 由 `string | undefined` 收紧为 `string`;`reset()` 中两行冗余双写随之删除。
191
+ - **测试污染**:`chat-session.test.ts` 的端到端用例会把交接文档写进 `<cwd>/.ai-zen/handoffs`,现改指临时目录并自动清理。
192
+
193
+ ### ✅ 测试
194
+
195
+ - 新增 3 例:`ctx` 变更即时生效、赋值回写 `ctx`、端到端(真实 `handoffService.migrate()` 后 id 必须跟随;该用例在修复前会失败,已反向验证)。全量 **17 文件 / 232 用例**通过,`tsc`(含测试工程)与 `build` 零错误。
196
+
197
+ ### 📄 文档
198
+
199
+ - `docs/{zh,en}/migration.md`「迁移链路」补充「会话 id 只有一个真相源」的约束与失效表现;`docs/manifest.json`、`README{,.zh}.md` 版本号同步。
200
+ - 依赖仍为 `@ai-zen/agents-sdk` `1.0.0-alpha.11`(本次无 SDK 改动)。
201
+
202
+ ## [1.0.0-alpha.11] - 2026-10-20
203
+
204
+ ### ⚠️ 依赖
205
+
206
+ - `@ai-zen/agents-sdk` `1.0.0-alpha.10 → 1.0.0-alpha.11`(`createCallMcpTool` 新增 `env` 参数为破坏性变更;CLI 不直接调用该工厂,无代码改动)。
207
+
208
+ ### 🔧 变更(随 SDK)
209
+
210
+ - **`call_mcp_tool` 输出保护**:调用 MCP 工具时,文本输出超过 `maxToolOutput`(默认 32768 字符)会完整落盘为 `result.txt`,只返回警告 + 头尾预览,可用 `read` 分批读取——不再一次把上万字符灌进上下文。
211
+ - **图片 / 音频 / 二进制资源原样返回**:MCP 返回的 `image` / `audio` / `resource.blob` 转成 `image_url` / `file` 内容块透传(`resource.text` 并入正文、`resource_link` 转为可读线索),不再静默丢弃;媒体不受 `maxToolOutput` 约束。
212
+
213
+ ### 📄 文档
214
+
215
+ - `docs/{zh,en}/mcp.md` 说明「仅文本受输出保护、媒体不受上限约束」;`docs/{zh,en}/getting-started.md`、`docs/manifest.json`、`README{,.zh}.md` 版本同步。
216
+
217
+ ## [1.0.0-alpha.10] - 2026-10-20
218
+
219
+ ### 💥 变更
220
+
221
+ - **交接与迁移拆成两条命令**(语义唯一,命名与实现对齐):
222
+ - **`/handoff`** —— 原 agent 停手、核实状态、把交接文档写到 `<项目>/.ai-zen/handoffs/`,**会话照旧**(不弹确认,可反复执行;交接回合留在 `agent.messages` 里,可继续要求细化同一份文档);
223
+ - **`/migrate`**(别名 `/migration`)—— 同一次交接 + 清历史 + 开新会话(需确认)。
224
+ - 两条都支持 `self` / `summary` 参数(如 `/handoff summary`、`/migrate self`)跳过默认选择。
225
+ - **自动迁移改为「执行者三选一」选择器**:上下文超阈值时不再只问「是否迁移」,而是选择 **原 agent 自交接(默认高亮,保真)/ 独立摘要(便宜)/ 跳过本次**。选择器复用既有 picker 浮层(`setPickerState`)。非交互环境(管道、重定向、e2e)无选择通道,按默认执行者迁移,不阻塞、不额外输出。
226
+ - **交接文档目录**:新增 `PROJECT_HANDOFFS_DIR = <cwd>/.ai-zen/handoffs`,交接文档落在项目内,可随仓库共享/评审。
227
+
228
+ ### ♻️ 重构
229
+
230
+ - **`src/migration-service.ts` → `src/handoff-service.ts`**:`createMigrationService` → `createHandoffService`,钩子从 `onBeforeMigrate` / `onMigrated` 改为 `onBeforeHandoff` / `onHandoff` / `onMigrated`。存档旧会话移到 `onHandoff`(此时已包含交接回合,归档可审计);`/handoff` 不切换会话也因此不需要额外存档(`ConversationPersistPlugin` 每轮已落盘)。
231
+ - **`src/auto-migrate-confirm-plugin.ts` → `src/auto-migrate-confirm.ts`**:`AutoMigrateConfirmPlugin`(继承 + 重写 `onAfterSend`)退化为 `createAutoMigratePlugin()` 工厂——「何时迁移」仍在 SDK 插件里,「用谁交接」通过 SDK 新增的 `resolveMode` 回调注入,不再重复实现阈值判断。
232
+ - `src/agent-creator.ts` 抽出并导出 `listSkillDirs()`:Scope 装配与交接服务(读取 `handoff` Skill 正文)共用同一份目录优先级,避免两处各算一遍。
233
+ - `ChatSessionOptions.requestConfirm` → `requestMode`(返回 `HandoffMode | null`);`ChatSession.migrate()` / `handoff()` 返回文档路径;`ConversationContext.migrationService` → `handoffService`。
234
+ - `ContextGuardPlugin` 接线 `isSuspended: () => handoffService.busy`:交接回合护栏从 `× 1.5` 放宽到 `× 2`。
235
+ - 斜杠命令解析支持参数:命令名取首个 token(`/migrate summary` 不再被当成未知命令)。
236
+
237
+ ### ⚠️ 依赖
238
+
239
+ - `@ai-zen/agents-sdk` `1.0.0-alpha.9 → 1.0.0-alpha.10`(`TaskMigrationService` → `HandoffService` 为破坏性变更)。
240
+
241
+ ### ✅ 测试
242
+
243
+ - 新增 `src/handoff-service.test.ts`(迁移清 `lastUsage` / 新会话落盘 / `/handoff` 不切会话)、`src/auto-migrate-confirm.test.ts`(非交互默认值、选择透传、取消跳过、busy 让行)。全量 **17 文件 / 229 用例**通过,`tsc --noEmit`(含测试工程)与 `build` 零错误。
244
+
245
+ ### 📄 文档
246
+
247
+ - `docs/{en,zh}/migration.md` 重写为「任务交接与迁移」:两条命令的差异、两种执行者的取舍、自交接的代码级保障(守卫/放宽/防递归)、文档位置与命名、模板由 `handoff` Skill 承载(`metadata.custom: "true"` 退出同步、可项目级覆盖)、成本说明。网站镜像同步。
248
+ - `README.md` / `README.zh.md` 命令表与「任务交接与迁移」章节、`docs/{en,zh}/index.md` 特性与导航同步。
249
+
250
+ ## [1.0.0-alpha.9] - 2026-10-14
251
+
252
+ ### 🔧 修复
253
+
254
+ - **自动迁移与 CLI 的交互适配(两处)**:
255
+ - **迁移期间输入框未真正拦住** —— 主输入 `useInput` 的 `isActive` 此前只挡「确认框 / 选择器 / 配置向导」,未挡「生成中」态。自动迁移在 `send()` 内 `await onAfterSend` 执行(`busy` 为真、输入框已被 Spinner 顶替),但按键仍会被静默吞进隐藏的输入缓冲、待本轮结束才浮现。现令主输入在 `busy` 时整体失活,另设「忙碌期」处理器仅放行 `Ctrl+C` 中止 / `Ctrl+D` 退出 / `PgUp`·`Shift+↑↓` 回看;`/migrate` 手动迁移同样纳入 `busy`。
256
+ - **迁移完成后仍被总上下文护栏拦截** —— 迁移(prune)会物理重置上下文,却遗留 `agent.lastUsage`(迁移前那一次请求的 `prompt_tokens`)。`ContextGuardPlugin`(`onInnerLoopStart`,硬上限 `maxTokens × 1.5`)据此误判,迁移后第一条消息即被 `ContextOverflowError` 中断。现于迁移后清空 `agent.lastUsage`,护栏首轮无据可查而放行,待下一次响应以真实用量回填。
257
+
258
+ ### ✅ 测试
259
+
260
+ - 新增 `src/migration-service.test.ts`:断言迁移后 `agent.lastUsage` 被清空(移除修复后该用例确会失败)。全量单测 **17 文件 / 227 用例**通过,`tsc --noEmit` 与 `pnpm build` 零错误。
261
+
262
+ ## [1.0.0-alpha.8] - 2026-10-14
263
+
264
+ ### 🔧 修复
265
+
266
+ - **修复长时间 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` 仍保留开发构建,便于排查。
267
+
268
+ ### ✅ 测试
269
+
270
+ - **端到端实证** —— 真实 ESM 下经入口加载 Ink 并连续重渲染 300 次:修复后 `performance.measure` 条目 **0 → 0**(production reconciler),显式 `NODE_ENV=development` 对照组 **0 → 905**(dev 埋点仍在)。全量单测 **16 文件 / 226 用例**通过,`tsc --noEmit` 与 `pnpm build` 零错误。
271
+
272
+ ## [1.0.0-alpha.7] - 2026-10-14
273
+
274
+ ### 📄 文档
275
+
276
+ - **状态栏 token 用量纳入文档** —— `docs/zh|en/index.md` 的「主要特性」补上「状态栏实时展示 模型 · Agent · 本轮/累计 token 用量 · 生成状态」(此前仅在 README 提及),累计口径为 `sumUsage(agent.messages)`(含所有层级的子 Agent)。
277
+ - 修正 `TODO.md` 中已过时的路径引用(`src/tui/config-wizard.tsx` → 目录 `src/tui/config-wizard/`)。
278
+
279
+ ### ⚠️ 依赖
280
+
281
+ - `@ai-zen/agents-core` `^4.5.0 → ^4.5.1`、`@ai-zen/agents-sdk` `1.0.0-alpha.8 → 1.0.0-alpha.9`
282
+
283
+ > 纯文档修正,无代码变更。
284
+
285
+ ## [1.0.0-alpha.6] - 2026-10-14
286
+
287
+ ### ♻️ 重构
288
+
289
+ - **累计用量改用 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` 亦不丢,且不再有双重计数风险。
290
+
291
+ ### ⚠️ 依赖
292
+
293
+ - `@ai-zen/agents-sdk` `1.0.0-alpha.7 → 1.0.0-alpha.8`、`@ai-zen/agents-core` `^4.4.0 → ^4.5.0`
294
+
295
+ ### ✅ 测试
296
+
297
+ - 移除 `src/usage.test.ts`(逻辑下沉 core,已由 core 覆盖);`chat-session.test.ts` 的 `totalUsage` 用例改为覆盖「assistant 的 usage + 工具消息的 subAgentUsage」求和。全量单测 **16 文件 / 226 用例**通过,`tsc --noEmit` 与 `npm run build` 零错误。
298
+
299
+ ## [1.0.0-alpha.5] - 2026-10-14
300
+
301
+ ### 🚀 新功能
302
+
303
+ - **状态栏展示 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`)。
304
+ - **依赖升级** —— `@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 用量)。
305
+
306
+ ### ✅ 测试
307
+
308
+ - 新增 `src/usage.test.ts`(4 例);`chat-session.test.ts` 增补 `totalUsage` 求和用例;`screens.test.tsx` 的 `StatusBar` 用例更新。全量单测 **17 文件 / 230 用例**通过,`tsc --noEmit` 与 `npm run build` 零错误。
309
+
310
+ ## [1.0.0-alpha.4] - 2026-10-13
311
+
312
+ ### ♻️ 重构
313
+
314
+ - **TUI 渲染直接以 `agent.messages` 为唯一数据源** —— 移除 `ChatEvent` / `chatReducer` / `LiveAssistant` / `Block` / `messagesToBlocks` 等自定义中间模型。SDK 在流式期间会**实时更新 `agent.messages`**(`Agent.parseStreamData` 把每个 delta 增量写进当前那条 message),因此跨消息顺序(数组序)与单条 assistant 内部顺序(`思考 → 工具 → 正文`)天然保持,直接渲染即可,杜绝「第二套真相」的失真与维护负担。已完成消息的折行 / 高亮按「消息 + 列宽 + 内容签名」缓存(`cachedMessageToLines`),流式中那条靠内容签名自动失效。
315
+ - **notice / error 改为独立栏位** —— 系统级提示(命令反馈 / 迁移进度 / 报错)不再混入对话转录,也**绝不写进 `agent.messages`**(那会被 `formatHistory()` 原样发给模型、污染上下文);改在输入框上方的独立一行渲染(`src/tui/screens/notice.ts`)。
316
+
317
+ ### 🎯 优化
318
+
319
+ - **流式增量渲染**:Ink 开启 `incrementalRendering` —— 从「每帧 erase 整屏 + 重写整屏」(`createStandard`)切到「只重绘变化的行」(`createIncremental`),每帧输出量由 O(屏幕大小) 降到 O(变化量),SSH / 弱网下流式更稳。
320
+ - **流式合帧**(`src/tui/screens/frame-buffer.ts`):内容变更先攒入缓冲,按 60ms(约 16fps,低于 Ink 默认 30fps 输出节流)批量触发重渲染,把上屏频率从「每个 delta 一次」降到固定帧率。
321
+ - **渲染缓存**:长对话下历史消息的折行 / 高亮免于每帧重算,成本不再随对话增长而恶化。
322
+ - **思考与工具调用之间补一个空行**,排版更清晰。
323
+
324
+ ### ✅ 测试
325
+
326
+ - 新增 `frame-buffer.test.ts`、`notice.test.ts`;`screens.test.tsx` 重写为基于 `messageToLines` / `transcriptLines` / `computeChatLayout(transcript)`。
327
+ - `tsc` 零错误;全量单测 **16 文件 / 225 用例**通过;`npm run build` 通过。
328
+ - 真机(pty)验证:SSH / 弱网下流式稳定,长对话滚动流畅。
329
+
330
+ ## [1.0.0-alpha.3] - 2026-10-13
331
+
332
+ ### 💥 破坏性变更
333
+
334
+ - **内置工具改名(统一 `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 历史条目中)。
335
+
336
+ ### 🚀 新功能
337
+
338
+ - **正文 / 思考按 Markdown 源码着色** —— AI 回答本身即 Markdown,现交给 `highlight.js` 的 `markdown` 词法着色:标题(`#`)、粗体、斜体、行内代码、列表、引用、链接各有配色;**围栏代码块保留围栏行**(开 / 闭围栏以低调暗灰渲染)并继续按标注语言做语法高亮。标记符(`#` / `**` / 反引号 等)**原样保留、只染色**(源码着色,非语义渲染)。正文用**空格优先折行**,避免把英文单词从中间切断。
339
+
340
+ ### 🎯 优化
341
+
342
+ - **正文默认文本色改用终端默认前景色** —— 此前正文基础色写死近白 `#e2e8f0`,在**亮色主题终端**里会「白底白字」看不见。现正文(含未指定颜色的默认分段)一律回落**终端默认前景色**,绝对色只留给标题 / 代码等强调项;同步移除语法色板里两处近白 token 色(`variable` / `params`)。
343
+
344
+ ### ⚠️ 依赖升级
345
+
346
+ - **`@ai-zen/agents-core` 升至 `^4.3.2`、`@ai-zen/agents-sdk` 升至 `1.0.0-alpha.6`** ——
347
+ - core `4.3.1`:**中断的并行工具轮次归一化** —— 一轮回复里并行调用的多个工具若被中断(`Ctrl+C`),未完成的工具结果会在历史里被补齐 / 归一,避免下一轮因 `tool_calls` 数量与实际工具结果数量不匹配而被服务端拒绝(默认 `fill`:保留 assistant 正文并补齐未完成结果)。
348
+ - core `4.3.2` / sdk `1.0.0-alpha.4…6`:内置工具统一 `snake_case` + 单词简化、`exec_async` → `spawn`、类名对齐(见「破坏性变更」)。
349
+ - CLI 侧仅同步工具名(文档 / 测试 fixture / 提示语),运行时行为全量委托 SDK,无需其它适配。
350
+
351
+ ### ✅ 测试
352
+
353
+ - `tsc --noEmit`(`tsconfig.test.json`)零错误;全量单测 **15 文件 / 235 用例**通过
354
+ - 修复 `config-wizard.test.tsx` 两个预存失败:为向导注入终端尺寸(新增可选 `terminalSize` prop),消除 `ink-testing-library` 假 stdout 缺 `rows` 时 `terminal-size` 探测本机行数、把吸底帧撑到 38 行的干扰
355
+ - 渲染核验:标题 / 粗体 / 行内代码 / 列表 / 引用 / 链接与围栏代码块均按预期着色,普通正文无颜色(回落终端默认)
356
+
1
357
  ## [1.0.0-alpha.2] - 2026-10-03
2
358
 
3
359
  ### ⚠️ 依赖升级
package/README.md CHANGED
@@ -66,12 +66,79 @@ When run with no arguments on an interactive terminal, the CLI opens a full-scre
66
66
  ╚═╝ ╚═╝╚═╝ ╚══════╝╚══════╝╚═╝ ╚═══╝
67
67
 
68
68
  AI workbench in your terminal
69
- v1.0.0-alpha.2 · sdk x · core y
69
+ v1.0.0-alpha.20 · sdk x · core y
70
70
  ```
71
71
 
72
- After the splash, you land **directly in the chat screen** (resuming the last session if there is one, otherwise starting a new conversation). The chat screen is **bottom-pinned**: a divider, the input line, and the status bar (model · agent · token usage · generating) always sit at the bottom of the terminal, while the conversation scrolls above them. While generating, the spinner replaces the input line.
72
+ After the splash, you land **directly in the chat screen** (resuming the last session if there is one, otherwise starting a new conversation). The chat screen is **bottom-pinned**: a divider, the input line, and the status bar (model · agent · this-round / cumulative tokens · generating) always sit at the bottom of the terminal, while the conversation scrolls above them. While generating, the spinner replaces the input line.
73
73
 
74
- The chat screen provides a streaming output area (reasoning / answer / tool calls on separate lines), a multiline input (`Enter` sends; `Ctrl+N` inserts a newline; `← →` move the cursor, `Ctrl+← →` by word), and an inline `/` command menu (↑↓ to choose, `Tab` to complete). Tool calls render as `⚙ name(args)` with **JSON syntax highlighting**, and fenced code blocks in the answer / reasoning are **syntax-highlighted by language** (highlight data from `highlight.js` via `lowlight`). `Ctrl+C` cancels the current request while generating, and exits when idle.
74
+ The chat screen provides a streaming output area (reasoning / answer / tool calls on separate lines), a multiline input (`Enter` sends; `Ctrl+N` inserts a newline; `← →` move the cursor, `Ctrl+← →` by word; `Ctrl+W` / `Alt+Backspace` delete the previous word, `Alt+D` / `Ctrl+Delete` the next word; `Ctrl+K` kills to end of line; `Ctrl+U` kills to line start; `PgUp`/`PgDn` (or `Alt+V`/`Ctrl+V`) scroll the transcript, the mouse wheel scrolls by line; on macOS `⌘← →` moves by word, `⌘⌫`/`⌘⌦` deletes by word, `⌘[`/`⌘]` pages and `⌘;`/`⌘'` scrolls by line (depending on whether the terminal reports Cmd — see below)., and an inline `/` command menu (↑↓ to choose, `Tab` to complete). Tool calls render as `⚙ name(args)` with **JSON syntax highlighting**; the answer / reasoning is **Markdown-source-colored** (headings, bold, italic, inline code, lists, quotes, links), and fenced code blocks are **syntax-highlighted by language with the fence markers kept** (highlight data from `highlight.js` via `lowlight`). `Ctrl+C` cancels the current request while generating, and exits when idle.
75
+
76
+ ### Keyboard & terminal compatibility (macOS, etc.)
77
+
78
+ The convention for terminal TUIs on macOS is **Option-as-Meta plus readline-style Ctrl keys**: `Cmd` is not part of the terminal protocol (`super` is only reported under the [kitty keyboard protocol](https://sw.kovidgoyal.net/kitty/keyboard-protocol/)), so `⌘` is left to the terminal itself (copy / paste / search). This CLI follows that convention.
79
+
80
+ **Step one: make Option act as Meta.** macOS terminals do not send the Option modifier by default — until you enable it, every `Option+X` shortcut does nothing:
81
+
82
+ | Terminal | Setting |
83
+ |---|---|
84
+ | Apple Terminal | Settings → Profiles → Keyboard → check **Use Option as Meta Key** |
85
+ | iTerm2 | Settings → Profiles → Keys → General → Left/Right Option key = `Esc+` |
86
+ | VS Code / Cursor | add `"terminal.integrated.macOptionIsMeta": true` to settings |
87
+ | Ghostty / kitty / WezTerm | enable Option-as-Alt / Option-as-Meta in the config |
88
+
89
+ Key map (aligned with [Claude Code](https://docs.claude.com/en/docs/claude-code/interactive-mode)):
90
+
91
+ | Keys | Action |
92
+ |---|---|
93
+ | `Enter` | Send |
94
+ | `Ctrl+J`, `Ctrl+Enter`, `Alt+Enter`, `Shift+Enter` | Newline |
95
+ | `Ctrl+C` | While generating: interrupt; when idle: clear the draft, press again to exit |
96
+ | `Ctrl+D` | With a draft: delete the character after the cursor; empty input: press twice to exit |
97
+ | `Esc` | While generating: interrupt; otherwise clear the draft and jump to the bottom |
98
+ | `Ctrl+A` / `Ctrl+E` | Line start / end |
99
+ | `Ctrl+P` / `Ctrl+N` | Previous / next history entry (also moves the slash menu) |
100
+ | `Ctrl+K` / `Ctrl+U` | Kill to line end / line start |
101
+ | `Ctrl+W` | Kill back to the previous whitespace (punctuation does not split: `src/utils/foo.ts` goes in one press) |
102
+ | `Alt+B` / `Alt+F` | Move by word |
103
+ | `Alt+D`, `Alt+Backspace`, `Ctrl+Delete` | Delete by word |
104
+ | `Ctrl+Y` / `Alt+Y` | Yank the last killed text / cycle older entries |
105
+ | `Ctrl+_` | Undo the last input edit |
106
+ | `Ctrl+S` | Stash the draft / press again to restore |
107
+ | `Ctrl+V` | Reserved for image paste (**not implemented yet**; shows a notice) |
108
+ | `Ctrl+L` | Clear the screen (context kept) |
109
+ | `PgUp` / `PgDn` | Page through the transcript |
110
+ | Mouse wheel | Scroll by line (3 lines per notch). With mouse reporting on, text selection needs `Shift` (native terminal selection); `tmux` users need `set -g mouse off`, otherwise the wheel is swallowed by copy-mode |
111
+ | `Shift+↑ ↓`, `Alt+↑ ↓`, `⌘;` / `⌘'`* | Scroll by line |
112
+ | `\` + `Enter` | Newline (works in any terminal) |
113
+
114
+ Not implemented (no counterpart here): `Ctrl+O` transcript viewer, `Ctrl+R` reverse search, `Ctrl+B` background tasks, `Ctrl+T` task list, `Shift+Tab` permission modes.
115
+
116
+ `Ctrl+V` (Claude Code's "paste image") is **not implemented yet** — pressing it shows a notice. It first needs a proper "file → content block" capability probe (Files API upload vs base64 inline) instead of hard-coding a single endpoint. Note that macOS maps `⌘⌫` to `Ctrl+U`, which is why `Ctrl+U` kills to line start here; use `Esc` to clear the draft.
117
+
118
+ \* `⌘` combos only carry the `super` bit when the terminal **enables the kitty keyboard protocol** (and for iTerm2, when Cmd is mapped to Super). Terminals without it (e.g. Apple Terminal) can only map `⌘` to `ESC` sequences (e.g. `⌘←` → `ESC[1;9D`, equivalent to `Alt+←`), or you can use the Alt combos above.
119
+
120
+ > macOS maps `⌘⌫` to `Ctrl+U` in iTerm2 and Terminal.app, which is why `Ctrl+U` here follows readline semantics (delete to line start): making it "clear everything" would throw away the whole draft whenever a Mac user presses `⌘⌫`.
121
+
122
+ > On macOS `Ctrl+← →` is usually swallowed by the system: Mission Control binds it to "Move left/right a space". Uncheck it under System Settings → Keyboard → Keyboard Shortcuts → Mission Control.
123
+
124
+ If a key still does nothing, turn on the key diagnostics and press it once:
125
+
126
+ ```bash
127
+ AIZ_DEBUG_KEYS=1 aiz
128
+ # in another shell
129
+ tail -f /tmp/aiz-key-debug.log
130
+ ```
131
+
132
+ The first line records the terminal environment (`TERM`, `TERM_PROGRAM`, ...); every keypress then appends one line:
133
+
134
+ ```
135
+ key input="d" flags=[meta] raw=true
136
+ key input="" flags=[pageUp] raw=false
137
+ ```
138
+
139
+ - **No new line after pressing** → the key never reached the app (system shortcut / terminal key mapping)
140
+ - **Line present but `flags` unexpected** → the sequence arrived; the matching logic is at fault (paste that line in a report)
141
+ - **`raw=false`** → stdin was not in raw mode, so the terminal interpreted the key itself (e.g. scrolled its scrollback)
75
142
 
76
143
  ### First-run setup (API Key)
77
144
 
@@ -113,26 +180,27 @@ While in a conversation, all commands start with `/`. Typing `/` lists the avail
113
180
  | `/editor` | Open system editor for long-form input |
114
181
  | `/clear` | Clear the screen |
115
182
  | `/config` | Open the configuration center (endpoints / models / image models / MCP servers / agent definitions / defaults / tool-output cap) |
183
+ | `/model` | Switch the model of the current session and persist it (written to the Agent's `modelId`; thinking level is reset to the model default) |
184
+ | `/think` `/effort` | Switch reasoning effort (available levels come from the model's reasoning preset, e.g. `low/high/max`); persisted into the current Agent definition; `/think default` falls back to the model default |
116
185
  | `/key` | Set the API Key of the current endpoint (rebuilds the session on save) |
117
- | `/migrate` | Manually trigger task migration (generate handoff doc & start a new session) |
186
+ | `/handoff` | Write a handoff document only (session keeps running; no confirmation) |
187
+ | `/migrate` `/migration` | Handoff + start a new session (discards history; asks for confirmation) |
118
188
  | `/help` | Show available commands |
119
189
 
120
- ### Conversation Migration
190
+ ### Task Handoff & Migration
121
191
 
122
- When the API response's `usage.prompt_tokens` exceeds the model's `maxContextTokens`, the system first asks for a **second confirmation** (showing current usage and the threshold, defaulting to "yes"; choosing "no" skips this migration and leaves the current conversation untouched), then generates a **handover document** summarizing completed tasks, pending items, and key decisions. A new conversation session is created with this document as context, ensuring seamless continuation. The confirmation only appears when both stdin and stdout are TTYs; non-interactive scenarios such as pipes and redirections keep the previous automatic migration behavior.
192
+ Two related commands, one pipeline:
123
193
 
124
- You can also manually trigger a migration at any time by typing `/migrate` in the conversation — no need to wait for the token limit. Both automatic and manual migration delegate to the SDK's `TaskMigrationService`, so the handoff document is always produced consistently.
194
+ - **`/handoff`** — the original agent stops what it is doing and writes a handoff document to `<project>/.ai-zen/handoffs/<timestamp>-<conversation id>.md`, using its own tools to verify the real state (branch, uncommitted changes, tests, ports). The session keeps running, so you can keep refining the document.
195
+ - **`/migrate`** — the same handoff, then history is cleared and a new session starts from the document (the breakpoint message carries only the path; the new agent reads it).
125
196
 
126
- The same context guard also applies to **sub-agents**: at each delegation boundary (`onSubAgentStart`) the CLI installs the same `ContextGuardPlugin` (same `maxTokens`) onto the newly built sub-agent, so a delegated sub-agent (including the Skill sub-agent created by `call_skill_sub_agent`) is interrupted as soon as its context exceeds the hard limit. This is transparent — a normally-running sub-agent is unaffected.
197
+ Two executors: `self` (default — the original agent writes it in-session, highest fidelity) and `summary` (a single independent model call, cheap but lossy). `self` degrades to `summary` one-way on failure. Pass it explicitly with `/handoff summary` or `/migrate self`.
127
198
 
128
- The migration prompt template includes:
129
- - **Conversation Breakpoint** — Last user/AI exchange verbatim
130
- - **Completed Tasks** — Task titles and output paths
131
- - **Pending Tasks** — Description, progress, next steps
132
- - **Important Notes** — Technical preferences, lessons learned, architecture decisions
133
- - **File Index** — Key files with descriptions
134
- - **Handover Instructions** — SOP for the relay agent (read files first, verify state, then act)
199
+ When `usage.prompt_tokens` exceeds the model's `tokenThresholds.handoff`, an interactive terminal shows a three-way picker (self / summary / skip); non-interactive pipes migrate with the default executor without blocking.
135
200
 
201
+ The document template is not hard-coded: it comes from the `handoff` Skill (`~/.ai-zen/skills/handoff/SKILL.md`, released and synced by the SDK; mark `metadata.custom: "true"` to keep your own version, or override per project). See [Task Handoff & Migration](./docs/en/migration.md) for the full chain, cost notes, and the template sections.
202
+
203
+ The same context guard also applies to **sub-agents**: at each delegation boundary (`onSubAgentStart`) the CLI installs the same `ContextGuardPlugin` (same `maxTokens`) onto the newly built sub-agent, so a delegated sub-agent (including the Skill sub-agent created by `call_skill_sub_agent`) is interrupted as soon as its context exceeds the hard limit. This is transparent — a normally-running sub-agent is unaffected.
136
204
  ### Shell Fallback Hook
137
205
 
138
206
  When you type an unrecognized command in your terminal, it can be automatically forwarded to AI for processing:
@@ -162,7 +230,7 @@ Configuration is stored in `~/.ai-zen/config.json` (or `$AI_ZEN_DIR/config.json`
162
230
  | Endpoints / models / image models, MCP servers, agent definitions (incl. sub-agents), defaults, tool-output cap | `/config` (the configuration center — full management) |
163
231
  | Other advanced fields (skills / user tools, …) | Edit the files under `~/.ai-zen/` manually |
164
232
 
165
- The `maxContextTokens` field on each model sets the migration threshold (typically ~25% of the model's actual context window, e.g. 250,000 for a 1M-token model).
233
+ The `tokenThresholds` field on each model sets two usage lines: `handoff` (soft — a handoff is triggered above it) and `guard` (hard — the round is aborted above it, before the request). Both are absolute values; `guard ≈ handoff × 1.5` is recommended (typically ~25% of the model's actual context window for `handoff`, e.g. 250,000 for a 1M-token model).
166
234
 
167
235
  ```jsonc
168
236
  {
@@ -180,7 +248,8 @@ The `maxContextTokens` field on each model sets the migration threshold (typical
180
248
  "name": "GPT-6.1 Sol",
181
249
  "endpointId": "openai",
182
250
  "modelName": "gpt-6.1-sol",
183
- "maxContextTokens": 250000,
251
+ "tokenThresholds": { "handoff": 250000, "guard": 375000 },
252
+ "reasoningPreset": "openai",
184
253
  "custom": true
185
254
  }
186
255
  ],
@@ -193,7 +262,6 @@ The `maxContextTokens` field on each model sets the migration threshold (typical
193
262
  "defaultSize": "1024x1024"
194
263
  }
195
264
  ],
196
- "defaultModel": "deepseek-flash",
197
265
  "defaultImageModel": "cogview-4",
198
266
  "defaultAgent": "default",
199
267
  "defaultMigrationModel": "deepseek-flash"
@@ -201,10 +269,13 @@ The `maxContextTokens` field on each model sets the migration threshold (typical
201
269
  ```
202
270
 
203
271
  > Note: entries in `models` / `imageModels` **without `custom: true`** are **factory-managed** — on every launch they are reconciled with (and replaced by) the SDK factory catalog; add `"custom": true` for your own entries. `endpoints` are never managed and always keep your configuration.
272
+ >
273
+ > The model is **not** selected in `config.json`: each Agent binds one in its own definition file (`agents/<id>.json`, field `modelId`), which is the single source of truth at runtime. `/model` writes exactly that field.
204
274
 
205
275
  ### Environment Variable
206
276
 
207
277
  - `AI_ZEN_DIR` — Override the shared root directory (default: `~/.ai-zen`). CLI runtime data goes to `$AI_ZEN_DIR/cli/`, and shared resources (agents, skills, tools, MCP, etc.) go to `$AI_ZEN_DIR/`.
278
+ - `AIZ_DEBUG_KEYS=1` — Enables key diagnostics logging (off by default, zero overhead); `AIZ_KEYS_FILE=<path>` overrides the target file (default `/tmp/aiz-key-debug.log`).
208
279
 
209
280
  ## Filesystem Layout
210
281
 
@@ -262,25 +333,25 @@ The CLI provides 20 built-in tools, implemented by `@ai-zen/agents-sdk`. Tool av
262
333
  | Tool | Description |
263
334
  |------|-------------|
264
335
  | `cwd` | Get current working directory |
265
- | `readFile` | Read file contents (supports `range` for partial reads) |
266
- | `inspectFile` | Inspect file structure (lines, chars, column widths, line-ending style) without reading content |
267
- | `writeFile` | Write content to file |
336
+ | `read` | Read file contents (supports `range` for partial reads) |
337
+ | `inspect_file` | Inspect file structure (lines, chars, column widths, line-ending style) without reading content |
338
+ | `write` | Write content to file |
268
339
  | `edit` | Replace text in files (single replacement) |
269
- | `batchEdit` | Batch replace text in files |
340
+ | `batch_edit` | Batch replace text in files |
270
341
  | `exec` | Execute shell commands |
271
- | `exec_async` | Execute a shell command asynchronously (returns immediately) |
342
+ | `spawn` | Execute a shell command asynchronously (returns immediately) |
272
343
  | `mkdir` | Create directories |
273
344
  | `rm` | Delete files or directories |
274
345
  | `glob` | Scan files with glob patterns |
275
346
  | `ls` | List directory contents |
276
347
  | `exist` | Check if path exists |
277
- | `findText` | Search text in files |
278
- | `downloadFile` | Download file from URL |
348
+ | `find_text` | Search text in files |
349
+ | `download_file` | Download file from URL |
279
350
  | `rename` | Rename or move files |
280
351
  | `copy` | Copy files or directories |
281
352
  | `sleep` | Wait for a specified number of milliseconds |
282
- | `viewImage` | View/analyze an image — vision models only (`Model.vision`). A network URL is returned as an `image_url` content block; a local path is auto-uploaded via the Files API and returned as a `file` content block |
283
- | `generateImage` | Generate images from text — requires `defaultImageModel`; always returns a plain string (JSON with the image URLs plus `viewImage` / `downloadFile` hints) instead of forcing image content blocks |
353
+ | `view_image` | View/analyze an image — vision models only (`Model.vision`). A network URL is returned as an `image_url` content block; a local path is auto-uploaded via the Files API and returned as a `file` content block |
354
+ | `generate_image` | Generate images from text — requires `defaultImageModel`; always returns a plain string (JSON with the image URLs plus `view_image` / `download_file` hints) instead of forcing image content blocks |
284
355
 
285
356
  ### Dynamic Tools
286
357
 
@@ -299,7 +370,7 @@ In addition to built-in tools, the SDK provides 5 dynamic loading tools that are
299
370
  Tools are assembled in three phases by the SDK's `Scope` capability pipeline:
300
371
 
301
372
  1. **Discovery** — Scan filesystem for built-in tools, user tools, SubAgents, Skills, and MCP servers. All 20 built-in tools are discovered unconditionally (no filtering at this stage)
302
- 2. **Filtering** — Apply permissions (`allow`/`deny`), security exclusions (recursion protection), and each tool's self-declared `isAvailable(config, definition)` (availability is decided at build time when the model is known; e.g. `viewImage` only for vision models, `generateImage` requires `defaultImageModel`)
373
+ 2. **Filtering** — Apply permissions (`allow`/`deny`), security exclusions (recursion protection), and each tool's self-declared `isAvailable(config, definition)` (availability is decided at build time when the model is known; e.g. `view_image` only for vision models, `generate_image` requires `defaultImageModel`)
303
374
  3. **Instantiation** — Map filtered names to `Tool` instances and register dynamic loaders
304
375
 
305
376
  Each Agent has independent permissions — no inheritance between parent Agent and SubAgent. The only exception is the temporary Skill sub-agent (created by `call_skill_sub_agent`), which reuses the caller's tool set without a second permission pass, as a transient conversation proxy rather than an independent entity.