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

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 (311) hide show
  1. package/CHANGELOG.md +514 -0
  2. package/README.md +136 -39
  3. package/README.zh.md +136 -39
  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 +52 -21
  7. package/dist/agent-creator.js.map +1 -1
  8. package/dist/agent-switch.d.ts +61 -0
  9. package/dist/agent-switch.d.ts.map +1 -0
  10. package/dist/agent-switch.js +83 -0
  11. package/dist/agent-switch.js.map +1 -0
  12. package/dist/agents-store.d.ts +6 -1
  13. package/dist/agents-store.d.ts.map +1 -1
  14. package/dist/agents-store.js +46 -4
  15. package/dist/agents-store.js.map +1 -1
  16. package/dist/auto-migrate-confirm.d.ts +43 -0
  17. package/dist/auto-migrate-confirm.d.ts.map +1 -0
  18. package/dist/auto-migrate-confirm.js +47 -0
  19. package/dist/auto-migrate-confirm.js.map +1 -0
  20. package/dist/config-editor.d.ts +13 -5
  21. package/dist/config-editor.d.ts.map +1 -1
  22. package/dist/config-editor.js +36 -27
  23. package/dist/config-editor.js.map +1 -1
  24. package/dist/config.d.ts +24 -3
  25. package/dist/config.d.ts.map +1 -1
  26. package/dist/config.js +55 -15
  27. package/dist/config.js.map +1 -1
  28. package/dist/conversation-commands/registry.d.ts +123 -4
  29. package/dist/conversation-commands/registry.d.ts.map +1 -1
  30. package/dist/conversation-commands/registry.js +92 -14
  31. package/dist/conversation-commands/registry.js.map +1 -1
  32. package/dist/conversation-persist-plugin.d.ts +0 -1
  33. package/dist/conversation-persist-plugin.d.ts.map +1 -1
  34. package/dist/conversation-persist-plugin.js +7 -16
  35. package/dist/conversation-persist-plugin.js.map +1 -1
  36. package/dist/conversation-repository.d.ts +71 -13
  37. package/dist/conversation-repository.d.ts.map +1 -1
  38. package/dist/conversation-repository.js +81 -24
  39. package/dist/conversation-repository.js.map +1 -1
  40. package/dist/cwd-tracker-plugin.d.ts.map +1 -1
  41. package/dist/cwd-tracker-plugin.js +6 -2
  42. package/dist/cwd-tracker-plugin.js.map +1 -1
  43. package/dist/format-time.d.ts +17 -6
  44. package/dist/format-time.d.ts.map +1 -1
  45. package/dist/format-time.js +32 -14
  46. package/dist/format-time.js.map +1 -1
  47. package/dist/handoff-service.d.ts +19 -0
  48. package/dist/handoff-service.d.ts.map +1 -0
  49. package/dist/handoff-service.js +89 -0
  50. package/dist/handoff-service.js.map +1 -0
  51. package/dist/hook.d.ts.map +1 -1
  52. package/dist/hook.js +69 -15
  53. package/dist/hook.js.map +1 -1
  54. package/dist/i18n/en-wizard.d.ts +8 -0
  55. package/dist/i18n/en-wizard.d.ts.map +1 -0
  56. package/dist/i18n/en-wizard.js +290 -0
  57. package/dist/i18n/en-wizard.js.map +1 -0
  58. package/dist/i18n/en.d.ts +7 -0
  59. package/dist/i18n/en.d.ts.map +1 -0
  60. package/dist/i18n/en.js +224 -0
  61. package/dist/i18n/en.js.map +1 -0
  62. package/dist/i18n/index.d.ts +57 -0
  63. package/dist/i18n/index.d.ts.map +1 -0
  64. package/dist/i18n/index.js +109 -0
  65. package/dist/i18n/index.js.map +1 -0
  66. package/dist/i18n/preferences.d.ts +34 -0
  67. package/dist/i18n/preferences.d.ts.map +1 -0
  68. package/dist/i18n/preferences.js +69 -0
  69. package/dist/i18n/preferences.js.map +1 -0
  70. package/dist/i18n/zh-wizard.d.ts +11 -0
  71. package/dist/i18n/zh-wizard.d.ts.map +1 -0
  72. package/dist/i18n/zh-wizard.js +293 -0
  73. package/dist/i18n/zh-wizard.js.map +1 -0
  74. package/dist/i18n/zh.d.ts +10 -0
  75. package/dist/i18n/zh.d.ts.map +1 -0
  76. package/dist/i18n/zh.js +228 -0
  77. package/dist/i18n/zh.js.map +1 -0
  78. package/dist/index.js +61 -25
  79. package/dist/index.js.map +1 -1
  80. package/dist/mcp-command.d.ts +32 -0
  81. package/dist/mcp-command.d.ts.map +1 -0
  82. package/dist/mcp-command.js +37 -0
  83. package/dist/mcp-command.js.map +1 -0
  84. package/dist/mode.d.ts +31 -4
  85. package/dist/mode.d.ts.map +1 -1
  86. package/dist/mode.js +42 -7
  87. package/dist/mode.js.map +1 -1
  88. package/dist/model-switch.d.ts +58 -0
  89. package/dist/model-switch.d.ts.map +1 -0
  90. package/dist/model-switch.js +62 -0
  91. package/dist/model-switch.js.map +1 -0
  92. package/dist/reasoning-effort.d.ts +96 -0
  93. package/dist/reasoning-effort.d.ts.map +1 -0
  94. package/dist/reasoning-effort.js +164 -0
  95. package/dist/reasoning-effort.js.map +1 -0
  96. package/dist/reasoning-presets.d.ts +34 -0
  97. package/dist/reasoning-presets.d.ts.map +1 -0
  98. package/dist/reasoning-presets.js +85 -0
  99. package/dist/reasoning-presets.js.map +1 -0
  100. package/dist/sdk-log.d.ts +55 -0
  101. package/dist/sdk-log.d.ts.map +1 -0
  102. package/dist/sdk-log.js +91 -0
  103. package/dist/sdk-log.js.map +1 -0
  104. package/dist/session-pointer.d.ts +22 -9
  105. package/dist/session-pointer.d.ts.map +1 -1
  106. package/dist/session-pointer.js +55 -23
  107. package/dist/session-pointer.js.map +1 -1
  108. package/dist/stdio-runner.d.ts.map +1 -1
  109. package/dist/stdio-runner.js +32 -30
  110. package/dist/stdio-runner.js.map +1 -1
  111. package/dist/sub-agent-guard-plugin.d.ts +5 -5
  112. package/dist/sub-agent-guard-plugin.d.ts.map +1 -1
  113. package/dist/sub-agent-guard-plugin.js +7 -7
  114. package/dist/sub-agent-guard-plugin.js.map +1 -1
  115. package/dist/tui/chat-session.d.ts +110 -59
  116. package/dist/tui/chat-session.d.ts.map +1 -1
  117. package/dist/tui/chat-session.js +268 -120
  118. package/dist/tui/chat-session.js.map +1 -1
  119. package/dist/tui/components.d.ts +48 -2
  120. package/dist/tui/components.d.ts.map +1 -1
  121. package/dist/tui/components.js +96 -3
  122. package/dist/tui/components.js.map +1 -1
  123. package/dist/tui/config-wizard/detail.d.ts +12 -6
  124. package/dist/tui/config-wizard/detail.d.ts.map +1 -1
  125. package/dist/tui/config-wizard/detail.js +33 -32
  126. package/dist/tui/config-wizard/detail.js.map +1 -1
  127. package/dist/tui/config-wizard/engine.d.ts.map +1 -1
  128. package/dist/tui/config-wizard/engine.js +27 -21
  129. package/dist/tui/config-wizard/engine.js.map +1 -1
  130. package/dist/tui/config-wizard/fields.d.ts +3 -1
  131. package/dist/tui/config-wizard/fields.d.ts.map +1 -1
  132. package/dist/tui/config-wizard/fields.js +216 -152
  133. package/dist/tui/config-wizard/fields.js.map +1 -1
  134. package/dist/tui/config-wizard/kv-editor.d.ts.map +1 -1
  135. package/dist/tui/config-wizard/kv-editor.js +9 -5
  136. package/dist/tui/config-wizard/kv-editor.js.map +1 -1
  137. package/dist/tui/config-wizard/list.d.ts.map +1 -1
  138. package/dist/tui/config-wizard/list.js +2 -1
  139. package/dist/tui/config-wizard/list.js.map +1 -1
  140. package/dist/tui/config-wizard/mcp-helpers.d.ts.map +1 -1
  141. package/dist/tui/config-wizard/mcp-helpers.js +4 -3
  142. package/dist/tui/config-wizard/mcp-helpers.js.map +1 -1
  143. package/dist/tui/config-wizard/menu.d.ts.map +1 -1
  144. package/dist/tui/config-wizard/menu.js +33 -21
  145. package/dist/tui/config-wizard/menu.js.map +1 -1
  146. package/dist/tui/config-wizard/prompt-editor.d.ts.map +1 -1
  147. package/dist/tui/config-wizard/prompt-editor.js +4 -3
  148. package/dist/tui/config-wizard/prompt-editor.js.map +1 -1
  149. package/dist/tui/config-wizard/prompt.d.ts.map +1 -1
  150. package/dist/tui/config-wizard/prompt.js +8 -7
  151. package/dist/tui/config-wizard/prompt.js.map +1 -1
  152. package/dist/tui/config-wizard/step-agents.d.ts.map +1 -1
  153. package/dist/tui/config-wizard/step-agents.js +29 -20
  154. package/dist/tui/config-wizard/step-agents.js.map +1 -1
  155. package/dist/tui/config-wizard/step-defaults.d.ts.map +1 -1
  156. package/dist/tui/config-wizard/step-defaults.js +24 -27
  157. package/dist/tui/config-wizard/step-defaults.js.map +1 -1
  158. package/dist/tui/config-wizard/step-endpoints.d.ts.map +1 -1
  159. package/dist/tui/config-wizard/step-endpoints.js +16 -6
  160. package/dist/tui/config-wizard/step-endpoints.js.map +1 -1
  161. package/dist/tui/config-wizard/step-mcp.d.ts.map +1 -1
  162. package/dist/tui/config-wizard/step-mcp.js +17 -19
  163. package/dist/tui/config-wizard/step-mcp.js.map +1 -1
  164. package/dist/tui/config-wizard/step-models.d.ts.map +1 -1
  165. package/dist/tui/config-wizard/step-models.js +41 -26
  166. package/dist/tui/config-wizard/step-models.js.map +1 -1
  167. package/dist/tui/config-wizard/step-new-endpoint.d.ts.map +1 -1
  168. package/dist/tui/config-wizard/step-new-endpoint.js +16 -12
  169. package/dist/tui/config-wizard/step-new-endpoint.js.map +1 -1
  170. package/dist/tui/config-wizard/step-simple.d.ts.map +1 -1
  171. package/dist/tui/config-wizard/step-simple.js +18 -17
  172. package/dist/tui/config-wizard/step-simple.js.map +1 -1
  173. package/dist/tui/config-wizard/types.d.ts +18 -3
  174. package/dist/tui/config-wizard/types.d.ts.map +1 -1
  175. package/dist/tui/config-wizard/wizard.d.ts.map +1 -1
  176. package/dist/tui/config-wizard/wizard.js +4 -3
  177. package/dist/tui/config-wizard/wizard.js.map +1 -1
  178. package/dist/tui/highlight.d.ts +3 -2
  179. package/dist/tui/highlight.d.ts.map +1 -1
  180. package/dist/tui/highlight.js +3 -2
  181. package/dist/tui/highlight.js.map +1 -1
  182. package/dist/tui/index.d.ts +18 -1
  183. package/dist/tui/index.d.ts.map +1 -1
  184. package/dist/tui/index.js +107 -49
  185. package/dist/tui/index.js.map +1 -1
  186. package/dist/tui/input-editing.d.ts +52 -0
  187. package/dist/tui/input-editing.d.ts.map +1 -0
  188. package/dist/tui/input-editing.js +91 -0
  189. package/dist/tui/input-editing.js.map +1 -0
  190. package/dist/tui/key-debug.d.ts +62 -0
  191. package/dist/tui/key-debug.d.ts.map +1 -0
  192. package/dist/tui/key-debug.js +92 -0
  193. package/dist/tui/key-debug.js.map +1 -0
  194. package/dist/tui/mouse.d.ts +55 -0
  195. package/dist/tui/mouse.d.ts.map +1 -0
  196. package/dist/tui/mouse.js +74 -0
  197. package/dist/tui/mouse.js.map +1 -0
  198. package/dist/tui/phase.d.ts +64 -0
  199. package/dist/tui/phase.d.ts.map +1 -0
  200. package/dist/tui/phase.js +112 -0
  201. package/dist/tui/phase.js.map +1 -0
  202. package/dist/tui/screens/chat-commands.d.ts +64 -3
  203. package/dist/tui/screens/chat-commands.d.ts.map +1 -1
  204. package/dist/tui/screens/chat-commands.js +483 -157
  205. package/dist/tui/screens/chat-commands.js.map +1 -1
  206. package/dist/tui/screens/chat.d.ts +10 -5
  207. package/dist/tui/screens/chat.d.ts.map +1 -1
  208. package/dist/tui/screens/chat.js +464 -301
  209. package/dist/tui/screens/chat.js.map +1 -1
  210. package/dist/tui/screens/common-keys.d.ts +36 -0
  211. package/dist/tui/screens/common-keys.d.ts.map +1 -0
  212. package/dist/tui/screens/common-keys.js +67 -0
  213. package/dist/tui/screens/common-keys.js.map +1 -0
  214. package/dist/tui/screens/frame-buffer.d.ts +47 -0
  215. package/dist/tui/screens/frame-buffer.d.ts.map +1 -0
  216. package/dist/tui/screens/frame-buffer.js +78 -0
  217. package/dist/tui/screens/frame-buffer.js.map +1 -0
  218. package/dist/tui/screens/handoff-choice.d.ts +37 -0
  219. package/dist/tui/screens/handoff-choice.d.ts.map +1 -0
  220. package/dist/tui/screens/handoff-choice.js +42 -0
  221. package/dist/tui/screens/handoff-choice.js.map +1 -0
  222. package/dist/tui/screens/index.d.ts +8 -8
  223. package/dist/tui/screens/index.d.ts.map +1 -1
  224. package/dist/tui/screens/index.js +7 -7
  225. package/dist/tui/screens/index.js.map +1 -1
  226. package/dist/tui/screens/input-row.d.ts +64 -0
  227. package/dist/tui/screens/input-row.d.ts.map +1 -0
  228. package/dist/tui/screens/input-row.js +67 -0
  229. package/dist/tui/screens/input-row.js.map +1 -0
  230. package/dist/tui/screens/input.d.ts +46 -1
  231. package/dist/tui/screens/input.d.ts.map +1 -1
  232. package/dist/tui/screens/input.js +83 -8
  233. package/dist/tui/screens/input.js.map +1 -1
  234. package/dist/tui/screens/layout.d.ts +21 -3
  235. package/dist/tui/screens/layout.d.ts.map +1 -1
  236. package/dist/tui/screens/layout.js +20 -9
  237. package/dist/tui/screens/layout.js.map +1 -1
  238. package/dist/tui/screens/lines.d.ts +12 -41
  239. package/dist/tui/screens/lines.d.ts.map +1 -1
  240. package/dist/tui/screens/lines.js +90 -98
  241. package/dist/tui/screens/lines.js.map +1 -1
  242. package/dist/tui/screens/messages-view.d.ts +10 -0
  243. package/dist/tui/screens/messages-view.d.ts.map +1 -0
  244. package/dist/tui/screens/messages-view.js +116 -0
  245. package/dist/tui/screens/messages-view.js.map +1 -0
  246. package/dist/tui/screens/notice-bar.d.ts +5 -0
  247. package/dist/tui/screens/notice-bar.d.ts.map +1 -0
  248. package/dist/tui/screens/notice-bar.js +15 -0
  249. package/dist/tui/screens/notice-bar.js.map +1 -0
  250. package/dist/tui/screens/notice.d.ts +64 -0
  251. package/dist/tui/screens/notice.d.ts.map +1 -0
  252. package/dist/tui/screens/notice.js +106 -0
  253. package/dist/tui/screens/notice.js.map +1 -0
  254. package/dist/tui/screens/overlays.d.ts.map +1 -1
  255. package/dist/tui/screens/overlays.js +3 -2
  256. package/dist/tui/screens/overlays.js.map +1 -1
  257. package/dist/tui/screens/render.d.ts +2 -4
  258. package/dist/tui/screens/render.d.ts.map +1 -1
  259. package/dist/tui/screens/render.js +7 -43
  260. package/dist/tui/screens/render.js.map +1 -1
  261. package/dist/tui/screens/splash.d.ts +1 -1
  262. package/dist/tui/screens/splash.d.ts.map +1 -1
  263. package/dist/tui/screens/splash.js +12 -3
  264. package/dist/tui/screens/splash.js.map +1 -1
  265. package/dist/tui/screens/use-chat-viewport.d.ts +22 -0
  266. package/dist/tui/screens/use-chat-viewport.d.ts.map +1 -0
  267. package/dist/tui/screens/use-chat-viewport.js +50 -0
  268. package/dist/tui/screens/use-chat-viewport.js.map +1 -0
  269. package/dist/tui/screens/use-input-editing.d.ts +44 -0
  270. package/dist/tui/screens/use-input-editing.d.ts.map +1 -0
  271. package/dist/tui/screens/use-input-editing.js +192 -0
  272. package/dist/tui/screens/use-input-editing.js.map +1 -0
  273. package/dist/tui/screens/use-overlays.d.ts +25 -0
  274. package/dist/tui/screens/use-overlays.d.ts.map +1 -0
  275. package/dist/tui/screens/use-overlays.js +32 -0
  276. package/dist/tui/screens/use-overlays.js.map +1 -0
  277. package/dist/tui/screens/use-wizard-host.d.ts +35 -0
  278. package/dist/tui/screens/use-wizard-host.d.ts.map +1 -0
  279. package/dist/tui/screens/use-wizard-host.js +103 -0
  280. package/dist/tui/screens/use-wizard-host.js.map +1 -0
  281. package/dist/tui/status-segments.d.ts +37 -0
  282. package/dist/tui/status-segments.d.ts.map +1 -0
  283. package/dist/tui/status-segments.js +166 -0
  284. package/dist/tui/status-segments.js.map +1 -0
  285. package/dist/tui/text.d.ts +20 -7
  286. package/dist/tui/text.d.ts.map +1 -1
  287. package/dist/tui/text.js +111 -37
  288. package/dist/tui/text.js.map +1 -1
  289. package/dist/tui/theme.d.ts +7 -1
  290. package/dist/tui/theme.d.ts.map +1 -1
  291. package/dist/tui/theme.js +15 -3
  292. package/dist/tui/theme.js.map +1 -1
  293. package/dist/tui/token-rate.d.ts +83 -0
  294. package/dist/tui/token-rate.d.ts.map +1 -0
  295. package/dist/tui/token-rate.js +195 -0
  296. package/dist/tui/token-rate.js.map +1 -0
  297. package/dist/types.d.ts +7 -8
  298. package/dist/types.d.ts.map +1 -1
  299. package/package.json +4 -3
  300. package/dist/auto-migrate-confirm-plugin.d.ts +0 -58
  301. package/dist/auto-migrate-confirm-plugin.d.ts.map +0 -1
  302. package/dist/auto-migrate-confirm-plugin.js +0 -61
  303. package/dist/auto-migrate-confirm-plugin.js.map +0 -1
  304. package/dist/migration-service.d.ts +0 -15
  305. package/dist/migration-service.d.ts.map +0 -1
  306. package/dist/migration-service.js +0 -59
  307. package/dist/migration-service.js.map +0 -1
  308. package/dist/tui/screens/state.d.ts +0 -13
  309. package/dist/tui/screens/state.d.ts.map +0 -1
  310. package/dist/tui/screens/state.js +0 -75
  311. package/dist/tui/screens/state.js.map +0 -1
@@ -0,0 +1,192 @@
1
+ // 输入编辑 —— 草稿文本与光标、历史、kill ring、撤销栈、暂存区的唯一实现
2
+ //
3
+ // 从 `chat.tsx` 抽出来的理由:这一整块(`input` / `cursor` / `history` / kill ring /
4
+ // undo / stash + 20 个编辑动作)是**纯编辑器语义**,与「对话屏」毫无关系,却和会话、
5
+ // 向导、渲染挤在同一个组件里 —— 于是既不能单测,也每次都被误认为「对话的一部分」。
6
+ //
7
+ // 两条贯穿全篇的约定:
8
+ //
9
+ // 1. **按字素簇编辑**:所有位置计算都走 `graphemes()`。`❤️` 是 2 个 code point、
10
+ // `👨‍👩‍👧` 是 5 个,但它们都是**一个**字符 —— 按 code point 删会切出半个字素簇。
11
+ // 2. **纯函数算、hook 只套用**:具体「删哪个词、删到哪」在 `input.ts` /
12
+ // `input-editing.ts` 里(有独立测试),这里只负责「拿到结果 → 更新状态 + 记撤销」。
13
+ import { useRef, useState } from "react";
14
+ import { deleteToLineEnd, deleteToLineStart, deleteWordLeft, deleteWordRight, wordLeft, wordRight, } from "./input.js";
15
+ import { deleteToPrevWhitespace, killedText, killAt, popUndo, pushKill, pushUndo, } from "../input-editing.js";
16
+ import { graphemes } from "../text.js";
17
+ import { t } from "../../i18n/index.js";
18
+ export function useInputEditing({ initialText, notify }) {
19
+ const [input, setInput] = useState(initialText ?? "");
20
+ const [cursor, setCursor] = useState(graphemes(initialText ?? "").length);
21
+ const [history, setHistory] = useState([]);
22
+ /** 历史游标:-1 = 不在浏览历史(当前是草稿) */
23
+ const historyIndex = useRef(-1);
24
+ /** kill ring:Ctrl+W/U/K 删掉的文本,Ctrl+Y 取回、Alt+Y 循环 */
25
+ const killRingRef = useRef([]);
26
+ const yankIndexRef = useRef(-1);
27
+ /** 编辑撤销栈(Ctrl+_) */
28
+ const undoStackRef = useRef([]);
29
+ /** 暂存区(Ctrl+S) */
30
+ const stashRef = useRef(null);
31
+ const setInputText = (text) => {
32
+ setInput(text);
33
+ setCursor(graphemes(text).length);
34
+ };
35
+ /**
36
+ * 取「当前文本 + 钳制后的光标」,所有编辑动作的统一起点。
37
+ *
38
+ * 钳制防的**不是**「cursor 越界」——`cursor` 的每个写入点都已保证落在 `[0, len]`;
39
+ * 防的是**文本与光标不同步**:`input` 与 `cursor` 是两个 state,任何一处只改其一
40
+ * (或将来新增编辑动作忘了同步)都会让 `cp > chars.length` 成立,那时 `slice`
41
+ * 会静默切少、`cp - 1` 会变负。钳一下把后果限制在「本次编辑落点不对」。
42
+ */
43
+ const clampCursor = () => {
44
+ const chars = graphemes(input);
45
+ return { chars, cp: Math.max(0, Math.min(cursor, chars.length)) };
46
+ };
47
+ const insertAtCursor = (text) => {
48
+ const { chars, cp } = clampCursor();
49
+ setInput(chars.slice(0, cp).join("") + text + chars.slice(cp).join(""));
50
+ setCursor(cp + graphemes(text).length);
51
+ };
52
+ const deleteBeforeCursor = () => {
53
+ const { chars, cp } = clampCursor();
54
+ if (cp === 0)
55
+ return;
56
+ setInput(chars.slice(0, cp - 1).join("") + chars.slice(cp).join(""));
57
+ setCursor(cp - 1);
58
+ };
59
+ const deleteAfterCursor = () => {
60
+ const { chars, cp } = clampCursor();
61
+ if (cp >= chars.length)
62
+ return;
63
+ setInput(chars.slice(0, cp).join("") + chars.slice(cp + 1).join(""));
64
+ };
65
+ /** 套用纯函数算好的「文本 + 光标」结果(无变化则不触发重渲染,也不进撤销栈) */
66
+ const applyEdit = (r) => {
67
+ if (r.text === input && r.cursor === cursor)
68
+ return;
69
+ undoStackRef.current = pushUndo(undoStackRef.current, { text: input, cursor });
70
+ setInput(r.text);
71
+ setCursor(r.cursor);
72
+ };
73
+ /** 删除类编辑:额外把删掉的文本放进 kill ring(Ctrl+Y 可取回) */
74
+ const applyKill = (r) => {
75
+ if (r.text === input && r.cursor === cursor)
76
+ return;
77
+ killRingRef.current = pushKill(killRingRef.current, killedText(input, r.text));
78
+ yankIndexRef.current = -1;
79
+ applyEdit(r);
80
+ };
81
+ const yank = (advance) => {
82
+ const ring = killRingRef.current;
83
+ if (ring.length === 0)
84
+ return;
85
+ const next = advance ? yankIndexRef.current + 1 : 0;
86
+ // 只用 `killAt` 的 undefined 做越界守卫(环已到头),不再先判一遍 `next >= ring.length`
87
+ const text = killAt(ring, next);
88
+ if (text === undefined)
89
+ return;
90
+ yankIndexRef.current = next;
91
+ insertAtCursor(text);
92
+ };
93
+ const undoEdit = () => {
94
+ const popped = popUndo(undoStackRef.current);
95
+ if (!popped)
96
+ return;
97
+ undoStackRef.current = popped.rest;
98
+ setInput(popped.state.text);
99
+ setCursor(popped.state.cursor);
100
+ };
101
+ const toggleStash = () => {
102
+ if (input.length > 0) {
103
+ stashRef.current = { text: input, cursor };
104
+ setInputText("");
105
+ notify(t("notice.draftSaved"));
106
+ return;
107
+ }
108
+ const stashed = stashRef.current;
109
+ if (!stashed)
110
+ return;
111
+ stashRef.current = null;
112
+ setInput(stashed.text);
113
+ setCursor(stashed.cursor);
114
+ notify(t("notice.draftRestored"));
115
+ };
116
+ const moveCursor = (delta) => {
117
+ const len = graphemes(input).length;
118
+ setCursor((c) => Math.max(0, Math.min(len, c + delta)));
119
+ };
120
+ const moveCursorByWord = (dir) => {
121
+ setCursor(dir === -1 ? wordLeft(input, cursor) : wordRight(input, cursor));
122
+ };
123
+ const moveCursorToLineStart = () => {
124
+ const { chars, cp } = clampCursor();
125
+ let i = cp;
126
+ while (i > 0 && chars[i - 1] !== "\n")
127
+ i -= 1;
128
+ setCursor(i);
129
+ };
130
+ const moveCursorToLineEnd = () => {
131
+ const { chars, cp } = clampCursor();
132
+ let i = cp;
133
+ while (i < chars.length && chars[i] !== "\n")
134
+ i += 1;
135
+ setCursor(i);
136
+ };
137
+ // 历史浏览:「上一条」从最新开始往回走,「下一条」走到底就回到空草稿。
138
+ // 上下两处按键(Ctrl+P/N 与 ↑/↓)共用这一对,不再各写一遍。
139
+ const historyPrev = () => {
140
+ if (history.length === 0)
141
+ return;
142
+ const idx = historyIndex.current < 0 ? history.length - 1 : Math.max(0, historyIndex.current - 1);
143
+ historyIndex.current = idx;
144
+ setInputText(history[idx]);
145
+ };
146
+ const historyNext = () => {
147
+ if (historyIndex.current < 0)
148
+ return;
149
+ const idx = historyIndex.current + 1;
150
+ if (idx >= history.length) {
151
+ historyIndex.current = -1;
152
+ setInputText("");
153
+ return;
154
+ }
155
+ historyIndex.current = idx;
156
+ setInputText(history[idx]);
157
+ };
158
+ const clearDraft = () => {
159
+ setInput("");
160
+ setCursor(0);
161
+ historyIndex.current = -1;
162
+ };
163
+ const rememberSubmitted = (text) => {
164
+ setHistory((h) => [...h, text]);
165
+ historyIndex.current = -1;
166
+ };
167
+ return {
168
+ input,
169
+ cursor,
170
+ setInputText,
171
+ insertAtCursor,
172
+ deleteBeforeCursor,
173
+ deleteAfterCursor,
174
+ deleteWordBeforeCursor: () => applyKill(deleteWordLeft(input, cursor)),
175
+ deleteWordAfterCursor: () => applyKill(deleteWordRight(input, cursor)),
176
+ deleteToStartOfLine: () => applyKill(deleteToLineStart(input, cursor)),
177
+ deleteToEndOfLine: () => applyKill(deleteToLineEnd(input, cursor)),
178
+ deletePrevWhitespace: () => applyKill(deleteToPrevWhitespace(input, cursor)),
179
+ yank,
180
+ undoEdit,
181
+ toggleStash,
182
+ moveCursor,
183
+ moveCursorToLineStart,
184
+ moveCursorToLineEnd,
185
+ moveCursorByWord,
186
+ historyPrev,
187
+ historyNext,
188
+ clearDraft,
189
+ rememberSubmitted,
190
+ };
191
+ }
192
+ //# sourceMappingURL=use-input-editing.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"use-input-editing.js","sourceRoot":"","sources":["../../../src/tui/screens/use-input-editing.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,25 @@
1
+ import { type Dispatch, type SetStateAction } from "react";
2
+ import type { SelectItem } from "../components.js";
3
+ /** 确认框请求:`question` 上屏,用户作答后由 `resolve` 结算 */
4
+ export interface ConfirmState {
5
+ question: string;
6
+ resolve: (value: boolean) => void;
7
+ }
8
+ /** 选择器请求:`items` 上屏,用户选中 / 取消后由 `resolve` 结算(取消为 `null`) */
9
+ export interface PickerState {
10
+ title: string;
11
+ items: SelectItem<string>[];
12
+ resolve: (value: string | null) => void;
13
+ }
14
+ export interface ChatOverlays {
15
+ confirmState: ConfirmState | null;
16
+ pickerState: PickerState | null;
17
+ /** 命令层推送覆盖层用(`runCommand` / `chooseHandoffMode` 的入参类型) */
18
+ setConfirmState: Dispatch<SetStateAction<ConfirmState | null>>;
19
+ setPickerState: Dispatch<SetStateAction<PickerState | null>>;
20
+ /** 作答:结算 Promise 并关掉覆盖层(无覆盖层时为空操作) */
21
+ resolveConfirm: (value: boolean) => void;
22
+ resolvePicker: (value: string | null) => void;
23
+ }
24
+ export declare function useChatOverlays(): ChatOverlays;
25
+ //# sourceMappingURL=use-overlays.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"use-overlays.d.ts","sourceRoot":"","sources":["../../../src/tui/screens/use-overlays.ts"],"names":[],"mappings":"AAWA,OAAO,EAAyB,KAAK,QAAQ,EAAE,KAAK,cAAc,EAAE,MAAM,OAAO,CAAC;AAClF,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,kBAAkB,CAAC;AAEnD,8CAA8C;AAC9C,MAAM,WAAW,YAAY;IAC3B,QAAQ,EAAE,MAAM,CAAC;IACjB,OAAO,EAAE,CAAC,KAAK,EAAE,OAAO,KAAK,IAAI,CAAC;CACnC;AAED,4DAA4D;AAC5D,MAAM,WAAW,WAAW;IAC1B,KAAK,EAAE,MAAM,CAAC;IACd,KAAK,EAAE,UAAU,CAAC,MAAM,CAAC,EAAE,CAAC;IAC5B,OAAO,EAAE,CAAC,KAAK,EAAE,MAAM,GAAG,IAAI,KAAK,IAAI,CAAC;CACzC;AAED,MAAM,WAAW,YAAY;IAC3B,YAAY,EAAE,YAAY,GAAG,IAAI,CAAC;IAClC,WAAW,EAAE,WAAW,GAAG,IAAI,CAAC;IAChC,0DAA0D;IAC1D,eAAe,EAAE,QAAQ,CAAC,cAAc,CAAC,YAAY,GAAG,IAAI,CAAC,CAAC,CAAC;IAC/D,cAAc,EAAE,QAAQ,CAAC,cAAc,CAAC,WAAW,GAAG,IAAI,CAAC,CAAC,CAAC;IAC7D,sCAAsC;IACtC,cAAc,EAAE,CAAC,KAAK,EAAE,OAAO,KAAK,IAAI,CAAC;IACzC,aAAa,EAAE,CAAC,KAAK,EAAE,MAAM,GAAG,IAAI,KAAK,IAAI,CAAC;CAC/C;AAED,wBAAgB,eAAe,IAAI,YAAY,CAqB9C"}
@@ -0,0 +1,32 @@
1
+ // 覆盖层(确认框 / 选择器)—— 「弹一个、答一个、再关掉」的唯一实现
2
+ //
3
+ // 两种覆盖层共用同一套契约:调用方给一个 `resolve` 回调,拿到一个 Promise,
4
+ // **用户在覆盖层上作答后 Promise 才结算**。这正是命令处理器能写成
5
+ // `const ok = await confirm(...)` 的原因。
6
+ //
7
+ // 从 `chat.tsx` 抽出来的理由:这套 `resolve` 生命周期被埋在 God Component 里,
8
+ // 既没法单测(要从键盘打到命令层才能碰到),也容易被下一个人的 `setState` 写出
9
+ // 「先清状态、后调 resolve」的竞态。抽出来后:
10
+ // · 关状态与调 resolve 在**同一次**函数式更新里完成,不会读到过期状态;
11
+ // · 已经是 `null` 时重复 resolve 是安全空操作(Esc 连按不会炸)。
12
+ import { useCallback, useState } from "react";
13
+ export function useChatOverlays() {
14
+ const [confirmState, setConfirmState] = useState(null);
15
+ const [pickerState, setPickerState] = useState(null);
16
+ // 用函数式更新而不是闭包里的 `confirmState`:连按两下 Esc / 两个键同一帧到达时,
17
+ // 第二次更新仍能看到「已被第一次清掉」的真实状态,不会重复 resolve。
18
+ const resolveConfirm = useCallback((value) => {
19
+ setConfirmState((cs) => {
20
+ cs?.resolve(value);
21
+ return null;
22
+ });
23
+ }, []);
24
+ const resolvePicker = useCallback((value) => {
25
+ setPickerState((ps) => {
26
+ ps?.resolve(value);
27
+ return null;
28
+ });
29
+ }, []);
30
+ return { confirmState, pickerState, setConfirmState, setPickerState, resolveConfirm, resolvePicker };
31
+ }
32
+ //# sourceMappingURL=use-overlays.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"use-overlays.js","sourceRoot":"","sources":["../../../src/tui/screens/use-overlays.ts"],"names":[],"mappings":"AAAA,sCAAsC;AACtC,EAAE;AACF,iDAAiD;AACjD,yCAAyC;AACzC,uCAAuC;AACvC,EAAE;AACF,4DAA4D;AAC5D,+CAA+C;AAC/C,6BAA6B;AAC7B,+CAA+C;AAC/C,gDAAgD;AAChD,OAAO,EAAE,WAAW,EAAE,QAAQ,EAAsC,MAAM,OAAO,CAAC;AA2BlF,MAAM,UAAU,eAAe;IAC7B,MAAM,CAAC,YAAY,EAAE,eAAe,CAAC,GAAG,QAAQ,CAAsB,IAAI,CAAC,CAAC;IAC5E,MAAM,CAAC,WAAW,EAAE,cAAc,CAAC,GAAG,QAAQ,CAAqB,IAAI,CAAC,CAAC;IAEzE,qDAAqD;IACrD,wCAAwC;IACxC,MAAM,cAAc,GAAG,WAAW,CAAC,CAAC,KAAc,EAAE,EAAE;QACpD,eAAe,CAAC,CAAC,EAAE,EAAE,EAAE;YACrB,EAAE,EAAE,OAAO,CAAC,KAAK,CAAC,CAAC;YACnB,OAAO,IAAI,CAAC;QACd,CAAC,CAAC,CAAC;IACL,CAAC,EAAE,EAAE,CAAC,CAAC;IAEP,MAAM,aAAa,GAAG,WAAW,CAAC,CAAC,KAAoB,EAAE,EAAE;QACzD,cAAc,CAAC,CAAC,EAAE,EAAE,EAAE;YACpB,EAAE,EAAE,OAAO,CAAC,KAAK,CAAC,CAAC;YACnB,OAAO,IAAI,CAAC;QACd,CAAC,CAAC,CAAC;IACL,CAAC,EAAE,EAAE,CAAC,CAAC;IAEP,OAAO,EAAE,YAAY,EAAE,WAAW,EAAE,eAAe,EAAE,cAAc,EAAE,cAAc,EAAE,aAAa,EAAE,CAAC;AACvG,CAAC"}
@@ -0,0 +1,35 @@
1
+ import type { AppConfig, ReasoningPresets } from "@ai-zen/agents-sdk";
2
+ import { type AgentStore } from "../../agents-store.js";
3
+ import type { McpSnapshot, WizardCloseResult, WizardStep } from "../config-wizard/index.js";
4
+ import type { Notice } from "./notice.js";
5
+ export interface WizardHostDeps {
6
+ /** 当前模型 id:`/key` 直达它所用端点的密钥;也是「当前端点是否被改动」的比较基准 */
7
+ modelId: string;
8
+ /** 通知出口(读写失败、变更摘要都从这里出去,不进食转录) */
9
+ notify: (text: string, tone?: Notice["tone"]) => void;
10
+ /** 会话是否已建好(未建好时改配置只能重启) */
11
+ hasSession: () => boolean;
12
+ /** 会话还没建好就改了配置 → 只能重启进程让新配置生效 */
13
+ restart: () => void;
14
+ /** 改了当前端点 / MCP / Agent 定义 → 重载会话(消息保留) */
15
+ reloadSession: () => void;
16
+ }
17
+ export interface WizardHost {
18
+ wizardStep: WizardStep | null;
19
+ wizardConfig: AppConfig | null;
20
+ wizardPresets: ReasoningPresets;
21
+ wizardAgents: {
22
+ id: string;
23
+ name: string;
24
+ }[];
25
+ wizardMcp: McpSnapshot | null;
26
+ wizardAgentStore: AgentStore | null;
27
+ /** 打开向导:先读齐快照再接管整屏(读失败则只报错、不进向导) */
28
+ openWizard: (step: WizardStep) => Promise<void>;
29
+ /** `/key`:直达当前模型所用端点的 API Key 编辑步骤 */
30
+ openKeyEditor: () => Promise<void>;
31
+ /** 向导关闭:渲染变更摘要,并让改动生效 */
32
+ closeWizard: (result: WizardCloseResult) => void;
33
+ }
34
+ export declare function useWizardHost({ modelId, notify, hasSession, restart, reloadSession, }: WizardHostDeps): WizardHost;
35
+ //# sourceMappingURL=use-wizard-host.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"use-wizard-host.d.ts","sourceRoot":"","sources":["../../../src/tui/screens/use-wizard-host.ts"],"names":[],"mappings":"AAUA,OAAO,KAAK,EAAE,SAAS,EAAE,gBAAgB,EAAE,MAAM,oBAAoB,CAAC;AAEtE,OAAO,EAAkB,KAAK,UAAU,EAAE,MAAM,uBAAuB,CAAC;AAExE,OAAO,KAAK,EAAE,WAAW,EAAE,iBAAiB,EAAE,UAAU,EAAE,MAAM,2BAA2B,CAAC;AAE5F,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,aAAa,CAAC;AAE1C,MAAM,WAAW,cAAc;IAC7B,mDAAmD;IACnD,OAAO,EAAE,MAAM,CAAC;IAChB,kCAAkC;IAClC,MAAM,EAAE,CAAC,IAAI,EAAE,MAAM,EAAE,IAAI,CAAC,EAAE,MAAM,CAAC,MAAM,CAAC,KAAK,IAAI,CAAC;IACtD,2BAA2B;IAC3B,UAAU,EAAE,MAAM,OAAO,CAAC;IAC1B,iCAAiC;IACjC,OAAO,EAAE,MAAM,IAAI,CAAC;IACpB,2CAA2C;IAC3C,aAAa,EAAE,MAAM,IAAI,CAAC;CAC3B;AAED,MAAM,WAAW,UAAU;IACzB,UAAU,EAAE,UAAU,GAAG,IAAI,CAAC;IAC9B,YAAY,EAAE,SAAS,GAAG,IAAI,CAAC;IAC/B,aAAa,EAAE,gBAAgB,CAAC;IAChC,YAAY,EAAE;QAAE,EAAE,EAAE,MAAM,CAAC;QAAC,IAAI,EAAE,MAAM,CAAA;KAAE,EAAE,CAAC;IAC7C,SAAS,EAAE,WAAW,GAAG,IAAI,CAAC;IAC9B,gBAAgB,EAAE,UAAU,GAAG,IAAI,CAAC;IACpC,oCAAoC;IACpC,UAAU,EAAE,CAAC,IAAI,EAAE,UAAU,KAAK,OAAO,CAAC,IAAI,CAAC,CAAC;IAChD,sCAAsC;IACtC,aAAa,EAAE,MAAM,OAAO,CAAC,IAAI,CAAC,CAAC;IACnC,yBAAyB;IACzB,WAAW,EAAE,CAAC,MAAM,EAAE,iBAAiB,KAAK,IAAI,CAAC;CAClD;AAED,wBAAgB,aAAa,CAAC,EAC5B,OAAO,EACP,MAAM,EACN,UAAU,EACV,OAAO,EACP,aAAa,GACd,EAAE,cAAc,GAAG,UAAU,CA0F7B"}
@@ -0,0 +1,103 @@
1
+ // 配置向导宿主 —— 「打开向导 / 读齐快照 / 关闭后让改动生效」的唯一实现
2
+ //
3
+ // 从 `chat.tsx` 抽出来的理由:向导本身(`config-wizard/`)是独立状态机,但**宿主**
4
+ // 的活儿散落在对话屏里 —— 6 个 `useState`、3 个入口函数,还要顺手记「当前模型用的是
5
+ // 哪个端点」以便关闭时判断要不要重建会话。这些既不属于「对话」,也没法单测(要一路
6
+ // 从键盘打到向导关闭才能碰到)。
7
+ //
8
+ // 依赖方向是**注入**而非 import 会话:宿主要做的事(通知、重启、重载会话)都通过
9
+ // `WizardHostDeps` 传进来,于是 hook 可以在测试里用假依赖跑完整生命周期。
10
+ import { useRef, useState } from "react";
11
+ import { readConfig, readMcpConfig, readReasoningPresets } from "../../config.js";
12
+ import { readAgentStore } from "../../agents-store.js";
13
+ import { resolveCredential } from "../../config-editor.js";
14
+ import { t } from "../../i18n/index.js";
15
+ export function useWizardHost({ modelId, notify, hasSession, restart, reloadSession, }) {
16
+ const [wizardStep, setWizardStep] = useState(null);
17
+ const [wizardConfig, setWizardConfig] = useState(null);
18
+ const [wizardPresets, setWizardPresets] = useState({});
19
+ const [wizardAgents, setWizardAgents] = useState([]);
20
+ const [wizardMcp, setWizardMcp] = useState(null);
21
+ const [wizardAgentStore, setWizardAgentStore] = useState(null);
22
+ /**
23
+ * 打开向导时当前模型所用的端点 id —— 关闭时据此判断「当前端点有没有被改」。
24
+ *
25
+ * 用 ref 而不是 state:它只在 `openWizard` / `closeWizard` 之间传递,不上屏,
26
+ * 放 state 会多一次无意义渲染。
27
+ */
28
+ const currentEndpointRef = useRef(null);
29
+ const openWizard = async (step) => {
30
+ try {
31
+ const config = await readConfig();
32
+ currentEndpointRef.current = resolveCredential(config, modelId)?.endpointId ?? null;
33
+ const agentStore = await readAgentStore();
34
+ setWizardConfig(config);
35
+ setWizardPresets(await readReasoningPresets());
36
+ setWizardAgents(agentStore.agents.map((agent) => ({ id: agent.id, name: agent.name || agent.id })));
37
+ setWizardAgentStore(agentStore);
38
+ setWizardMcp({ global: await readMcpConfig("global"), project: await readMcpConfig("project") });
39
+ setWizardStep(step);
40
+ }
41
+ catch (error) {
42
+ notify(t("chat.configReadFailed", { error: error?.message ?? error }), "error");
43
+ }
44
+ };
45
+ const openKeyEditor = async () => {
46
+ try {
47
+ const config = await readConfig();
48
+ const credential = resolveCredential(config, modelId);
49
+ if (!credential) {
50
+ notify(t("chat.modelNoEndpoint", { id: modelId }), "error");
51
+ return;
52
+ }
53
+ currentEndpointRef.current = credential.endpointId;
54
+ setWizardConfig(config);
55
+ setWizardStep({ kind: "edit", field: "apiKey", endpointId: credential.endpointId });
56
+ }
57
+ catch (error) {
58
+ notify(t("chat.configReadFailed", { error: error?.message ?? error }), "error");
59
+ }
60
+ };
61
+ /** 向导关闭:渲染变更摘要;若改到当前端点 / MCP / Agent 定义则重建会话(消息保留) */
62
+ const closeWizard = (result) => {
63
+ setWizardStep(null);
64
+ setWizardConfig(null);
65
+ setWizardMcp(null);
66
+ setWizardAgentStore(null);
67
+ // 同一批摘要合成**一条**(多行):既能全部显示,又只占通知队列的一个槽位。
68
+ // 逐条 push 在单槽时代只剩最后一条,在队列时代则会挤掉更早的进度提示。
69
+ const { summary } = result;
70
+ if (summary.length === 1) {
71
+ notify(t("chat.configUpdated", { line: summary[0] }));
72
+ }
73
+ else if (summary.length > 1) {
74
+ // 子项缩进 = 一个汉字宽度(两个半角空格),不加 `·` 之类的项目符号:
75
+ // 通知正文本身就左对齐于行首,子项只需要一点层级差,符号是多余装饰。
76
+ notify(t("chat.configUpdatedMulti", { lines: summary.map((line) => ` ${line}`).join("\n") }));
77
+ }
78
+ if (!result.dirty)
79
+ return;
80
+ // 会话都没建好 → 只能重启让新配置生效。
81
+ if (!hasSession()) {
82
+ restart();
83
+ return;
84
+ }
85
+ // 只有「当前端点 / MCP / Agent 定义」变了才需要重建会话;改的是别的模型、
86
+ // 别的端点的参数,当前会话不受影响,不该白重连一次。
87
+ const endpointChanged = !!currentEndpointRef.current && result.changedEndpointIds.includes(currentEndpointRef.current);
88
+ if (endpointChanged || result.mcpChanged || result.agentsChanged)
89
+ reloadSession();
90
+ };
91
+ return {
92
+ wizardStep,
93
+ wizardConfig,
94
+ wizardPresets,
95
+ wizardAgents,
96
+ wizardMcp,
97
+ wizardAgentStore,
98
+ openWizard,
99
+ openKeyEditor,
100
+ closeWizard,
101
+ };
102
+ }
103
+ //# sourceMappingURL=use-wizard-host.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"use-wizard-host.js","sourceRoot":"","sources":["../../../src/tui/screens/use-wizard-host.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,37 @@
1
+ /**
2
+ * 状态栏的折行 —— 输入是「按优先级排好的信息块」,输出是「确定的若干行」
3
+ *
4
+ * 为什么不用 Ink 自己的 wrap:状态栏高度必须是**确定值**,`computeChatLayout` 要靠它
5
+ * 算出对话区还能占几行(整帧高度恒等于终端行数,输入框才吸得住底)。让 Ink 在渲染时
6
+ * 折行,行数只有渲染完才知道,布局预算就会算错——轻则顶部留白多一行,重则把输入框
7
+ * 顶出屏幕。所以这里先在纯逻辑里折好,渲染层只负责按行铺颜色。
8
+ *
9
+ * 折行以「块」为单位(见 `packSegments`):身份、用量、快捷键各是一块,窄屏时整块
10
+ * 顺延到下一行,而不是把一块劈成两半——也不再用 `justify-content: space-between` 把
11
+ * 两侧各切一半(左边「思考 高」被吃掉、右边提示只剩半截)。
12
+ */
13
+ /** 一段带样式的文本(颜色由调用方给,这里只管切分) */
14
+ export interface StatusSegment {
15
+ text: string;
16
+ color?: string;
17
+ bold?: boolean;
18
+ }
19
+ /**
20
+ * 把片段流按显示宽度折成若干行。
21
+ *
22
+ * 断行策略与 `wrapText` 一致:优先在**空格**处断(英文单词不被劈开),没有空格可断
23
+ * 时按字符硬断(CJK 本来就能任意断行)。折行后的行首空格会被吃掉,避免出现
24
+ * 「第二行无故缩进两格」。
25
+ */
26
+ export declare function wrapSegments(segments: StatusSegment[], width: number): StatusSegment[][];
27
+ /** 片段流的总显示宽度(宽字符算 2 列) */
28
+ export declare function segmentsWidth(segments: StatusSegment[]): number;
29
+ /**
30
+ * 以**块**为单位装行:能塞进当前行剩余宽度就并排(用 `separator` 连起来),
31
+ * 塞不下就整块挪到下一行;只有单块本身就比一整行还宽时,才退化成块内折行
32
+ * (`wrapSegments`),此时优先在空格 / CJK 边界断,尽量不劈开英文单词。
33
+ *
34
+ * 这样「换行时一块占一行」是默认行为:块是原子的排版单位,行的边界只会落在块边界上。
35
+ */
36
+ export declare function packSegments(blocks: StatusSegment[][], width: number, separator?: StatusSegment[]): StatusSegment[][];
37
+ //# sourceMappingURL=status-segments.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"status-segments.d.ts","sourceRoot":"","sources":["../../src/tui/status-segments.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;GAWG;AAIH,+BAA+B;AAC/B,MAAM,WAAW,aAAa;IAC5B,IAAI,EAAE,MAAM,CAAC;IACb,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,IAAI,CAAC,EAAE,OAAO,CAAC;CAChB;AAwCD;;;;;;GAMG;AACH,wBAAgB,YAAY,CAAC,QAAQ,EAAE,aAAa,EAAE,EAAE,KAAK,EAAE,MAAM,GAAG,aAAa,EAAE,EAAE,CAyDxF;AAED,0BAA0B;AAC1B,wBAAgB,aAAa,CAAC,QAAQ,EAAE,aAAa,EAAE,GAAG,MAAM,CAM/D;AAED;;;;;;GAMG;AACH,wBAAgB,YAAY,CAC1B,MAAM,EAAE,aAAa,EAAE,EAAE,EACzB,KAAK,EAAE,MAAM,EACb,SAAS,GAAE,aAAa,EAAsB,GAC7C,aAAa,EAAE,EAAE,CAwCnB"}
@@ -0,0 +1,166 @@
1
+ /**
2
+ * 状态栏的折行 —— 输入是「按优先级排好的信息块」,输出是「确定的若干行」
3
+ *
4
+ * 为什么不用 Ink 自己的 wrap:状态栏高度必须是**确定值**,`computeChatLayout` 要靠它
5
+ * 算出对话区还能占几行(整帧高度恒等于终端行数,输入框才吸得住底)。让 Ink 在渲染时
6
+ * 折行,行数只有渲染完才知道,布局预算就会算错——轻则顶部留白多一行,重则把输入框
7
+ * 顶出屏幕。所以这里先在纯逻辑里折好,渲染层只负责按行铺颜色。
8
+ *
9
+ * 折行以「块」为单位(见 `packSegments`):身份、用量、快捷键各是一块,窄屏时整块
10
+ * 顺延到下一行,而不是把一块劈成两半——也不再用 `justify-content: space-between` 把
11
+ * 两侧各切一半(左边「思考 高」被吃掉、右边提示只剩半截)。
12
+ */
13
+ import { clusterWidth, graphemes } from "./text.js";
14
+ function toChars(segments) {
15
+ const chars = [];
16
+ for (const segment of segments) {
17
+ // 按**字素簇**切,不按 code point:`✌️`(VS16)是两个 code point 但占 2 列,
18
+ // 逐 code point 累加会算成 1 列 → 状态栏行超宽 → Ink 二次折行 → 帧高多一行。
19
+ for (const ch of graphemes(segment.text)) {
20
+ chars.push({ ch, color: segment.color, bold: segment.bold, width: clusterWidth(ch) });
21
+ }
22
+ }
23
+ return chars;
24
+ }
25
+ /** 连续同色同粗细的字符合并回一个片段 */
26
+ function toSegments(chars) {
27
+ const segments = [];
28
+ for (const char of chars) {
29
+ const last = segments[segments.length - 1];
30
+ if (last && last.color === char.color && last.bold === char.bold) {
31
+ last.text += char.ch;
32
+ }
33
+ else {
34
+ segments.push({ text: char.ch, color: char.color, bold: char.bold });
35
+ }
36
+ }
37
+ return segments;
38
+ }
39
+ /** 行尾空格没有意义(会视觉上「多出一截」),折行前后都裁掉 */
40
+ function trimTail(line) {
41
+ while (line.length > 0 && line[line.length - 1].ch === " ")
42
+ line.pop();
43
+ }
44
+ /**
45
+ * 把片段流按显示宽度折成若干行。
46
+ *
47
+ * 断行策略与 `wrapText` 一致:优先在**空格**处断(英文单词不被劈开),没有空格可断
48
+ * 时按字符硬断(CJK 本来就能任意断行)。折行后的行首空格会被吃掉,避免出现
49
+ * 「第二行无故缩进两格」。
50
+ */
51
+ export function wrapSegments(segments, width) {
52
+ const limit = Math.max(1, width);
53
+ const chars = toChars(segments);
54
+ const lines = [];
55
+ let line = [];
56
+ let lineWidth = 0;
57
+ for (let index = 0; index < chars.length; index += 1) {
58
+ const char = chars[index];
59
+ // 行已满且下一个正好是空格:直接断行并把这个空格丢掉,而不是往回找上一处空格
60
+ // (否则会把已经排好的「aaaa bbbb」硬拆成「aaaa」+「bbbb」,白白浪费半行)
61
+ if (char.ch === " " && lineWidth + char.width > limit && line.length > 0) {
62
+ trimTail(line);
63
+ lines.push(line);
64
+ line = [];
65
+ lineWidth = 0;
66
+ continue;
67
+ }
68
+ if (lineWidth + char.width > limit && line.length > 0) {
69
+ // 当前行最后一个空格的位置:能在此断就在此断,把尾巴挪到下一行
70
+ let spaceAt = -1;
71
+ for (let i = line.length - 1; i > 0; i -= 1) {
72
+ if (line[i].ch === " ") {
73
+ spaceAt = i;
74
+ break;
75
+ }
76
+ }
77
+ if (spaceAt > 0) {
78
+ const carry = line.slice(spaceAt + 1);
79
+ line = line.slice(0, spaceAt);
80
+ trimTail(line);
81
+ lines.push(line);
82
+ line = carry;
83
+ lineWidth = carry.reduce((sum, item) => sum + item.width, 0);
84
+ }
85
+ else {
86
+ trimTail(line);
87
+ lines.push(line);
88
+ line = [];
89
+ lineWidth = 0;
90
+ }
91
+ }
92
+ if (line.length === 0) {
93
+ if (char.ch === " ")
94
+ continue; // 行首不吃空格
95
+ // 行首的孤儿分隔符(`·`)是折行切出来的,留在开头只会变成「· ⚡ 47 tok/s」,
96
+ // 难看且无意义(换行本身已表达分隔),连同其后的空格一并丢掉。
97
+ if (char.ch === "·" && index > 0) {
98
+ while (index + 1 < chars.length && chars[index + 1].ch === " ")
99
+ index += 1;
100
+ continue;
101
+ }
102
+ }
103
+ line.push(char);
104
+ lineWidth += char.width;
105
+ }
106
+ trimTail(line);
107
+ lines.push(line);
108
+ return lines.map(toSegments);
109
+ }
110
+ /** 片段流的总显示宽度(宽字符算 2 列) */
111
+ export function segmentsWidth(segments) {
112
+ let width = 0;
113
+ for (const segment of segments) {
114
+ for (const ch of graphemes(segment.text))
115
+ width += clusterWidth(ch);
116
+ }
117
+ return width;
118
+ }
119
+ /**
120
+ * 以**块**为单位装行:能塞进当前行剩余宽度就并排(用 `separator` 连起来),
121
+ * 塞不下就整块挪到下一行;只有单块本身就比一整行还宽时,才退化成块内折行
122
+ * (`wrapSegments`),此时优先在空格 / CJK 边界断,尽量不劈开英文单词。
123
+ *
124
+ * 这样「换行时一块占一行」是默认行为:块是原子的排版单位,行的边界只会落在块边界上。
125
+ */
126
+ export function packSegments(blocks, width, separator = [{ text: " · " }]) {
127
+ const limit = Math.max(1, width);
128
+ const separatorWidth = segmentsWidth(separator);
129
+ const lines = [];
130
+ let line = [];
131
+ let lineWidth = 0;
132
+ const flush = () => {
133
+ if (line.length === 0)
134
+ return;
135
+ lines.push(line);
136
+ line = [];
137
+ lineWidth = 0;
138
+ };
139
+ for (const block of blocks) {
140
+ const blockWidth = segmentsWidth(block);
141
+ if (blockWidth === 0)
142
+ continue; // 空块(如本轮没有速率)不占位,也不留分隔符
143
+ if (line.length > 0) {
144
+ if (lineWidth + separatorWidth + blockWidth <= limit) {
145
+ line = [...line, ...separator, ...block];
146
+ lineWidth += separatorWidth + blockWidth;
147
+ continue;
148
+ }
149
+ flush();
150
+ }
151
+ if (blockWidth <= limit) {
152
+ line = [...block];
153
+ lineWidth = blockWidth;
154
+ continue;
155
+ }
156
+ // 单块超一整行:块内折行兜底,尾巴留在 `line` 上等着接下一块
157
+ const chunks = wrapSegments(block, limit);
158
+ const tail = chunks.pop();
159
+ lines.push(...chunks);
160
+ line = tail;
161
+ lineWidth = segmentsWidth(tail);
162
+ }
163
+ flush();
164
+ return lines.length > 0 ? lines : [[]];
165
+ }
166
+ //# sourceMappingURL=status-segments.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"status-segments.js","sourceRoot":"","sources":["../../src/tui/status-segments.ts"],"names":[],"mappings":"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"}