@linxiraos/pi-tui 1.1.26 → 1.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (423) hide show
  1. package/CHANGELOG.md +51 -491
  2. package/README.md +13 -7
  3. package/dist/types/active-terminal.d.ts +30 -0
  4. package/dist/types/app-keybindings.d.ts +7 -2
  5. package/dist/types/apps/cleanse-board.d.ts +1 -1
  6. package/dist/types/apps/debug/log-viewer.d.ts +4 -0
  7. package/dist/types/apps/debug/raw-sse-buffer.d.ts +2 -0
  8. package/dist/types/apps/git/git-tui.d.ts +5 -4
  9. package/dist/types/apps/git/help.d.ts +3 -1
  10. package/dist/types/apps/if-bench-board.d.ts +2 -2
  11. package/dist/types/apps/setup-model-picker.d.ts +1 -1
  12. package/dist/types/autocomplete.d.ts +5 -2
  13. package/dist/types/charts/chart-plan.d.ts +674 -0
  14. package/dist/types/charts/chart-svg.d.ts +59 -0
  15. package/dist/types/charts/table-data.d.ts +175 -0
  16. package/dist/types/chat/assistant-message.d.ts +11 -1
  17. package/dist/types/chat/chat-transcript-builder.d.ts +3 -0
  18. package/dist/types/chat/compaction-summary-message.d.ts +1 -0
  19. package/dist/types/chat/execution-shared.d.ts +6 -5
  20. package/dist/types/chat/fence-figure.d.ts +42 -0
  21. package/dist/types/chat/figure-markdown.d.ts +42 -0
  22. package/dist/types/chat/messages.d.ts +1 -1
  23. package/dist/types/chat/svg-figure.d.ts +33 -0
  24. package/dist/types/chat/svg-source.d.ts +49 -0
  25. package/dist/types/chat/table-chart.d.ts +89 -0
  26. package/dist/types/chat/tool-execution.d.ts +2 -0
  27. package/dist/types/chat/user-message.d.ts +17 -4
  28. package/dist/types/chrome/shared.d.ts +9 -0
  29. package/dist/types/chrome/status-notice.d.ts +15 -3
  30. package/dist/types/chrome/transcript-container.d.ts +6 -0
  31. package/dist/types/components/box.d.ts +1 -0
  32. package/dist/types/components/disclosure.d.ts +1 -0
  33. package/dist/types/components/editor.d.ts +23 -5
  34. package/dist/types/components/form.d.ts +1 -0
  35. package/dist/types/components/image.d.ts +26 -44
  36. package/dist/types/components/input.d.ts +2 -1
  37. package/dist/types/components/key-value-list.d.ts +1 -0
  38. package/dist/types/components/layout/row.d.ts +1 -0
  39. package/dist/types/components/layout/stack.d.ts +1 -0
  40. package/dist/types/components/loader.d.ts +5 -6
  41. package/dist/types/components/markdown.d.ts +47 -5
  42. package/dist/types/components/scroll-view.d.ts +1 -0
  43. package/dist/types/components/section.d.ts +1 -0
  44. package/dist/types/components/table.d.ts +1 -0
  45. package/dist/types/components/text.d.ts +1 -0
  46. package/dist/types/components/tree-view.d.ts +7 -0
  47. package/dist/types/components/truncated-text.d.ts +1 -0
  48. package/dist/types/fuzzy.d.ts +29 -1
  49. package/dist/types/key-hint-format.d.ts +9 -1
  50. package/dist/types/keybinding-matchers.d.ts +3 -0
  51. package/dist/types/keybindings.d.ts +1 -1
  52. package/dist/types/kitty-graphics.d.ts +2 -2
  53. package/dist/types/loop-watchdog.d.ts +8 -7
  54. package/dist/types/multiplexers/cmux.d.ts +16 -0
  55. package/dist/types/multiplexers/herdr.d.ts +26 -0
  56. package/dist/types/multiplexers/notify.d.ts +13 -0
  57. package/dist/types/multiplexers/orca.d.ts +12 -0
  58. package/dist/types/multiplexers/screen.d.ts +9 -0
  59. package/dist/types/multiplexers/tmux.d.ts +29 -0
  60. package/dist/types/multiplexers/types.d.ts +100 -0
  61. package/dist/types/multiplexers/wmux.d.ts +12 -0
  62. package/dist/types/multiplexers/zellij.d.ts +12 -0
  63. package/dist/types/native/apply.d.ts +4 -3
  64. package/dist/types/native/backend.d.ts +5 -4
  65. package/dist/types/native/blobs.d.ts +7 -3
  66. package/dist/types/native/encode.d.ts +8 -6
  67. package/dist/types/native/node.d.ts +36 -5
  68. package/dist/types/native/overlay.d.ts +8 -3
  69. package/dist/types/native/settle.d.ts +4 -3
  70. package/dist/types/overlays/agent-hub-projection.d.ts +6 -5
  71. package/dist/types/overlays/agent-hub-types.d.ts +9 -0
  72. package/dist/types/overlays/agent-hub.d.ts +3 -0
  73. package/dist/types/overlays/agent-transcript-viewer.d.ts +12 -2
  74. package/dist/types/overlays/agents-hub.d.ts +9 -0
  75. package/dist/types/overlays/annotation-overlay.d.ts +19 -2
  76. package/dist/types/overlays/annotation-types.d.ts +2 -0
  77. package/dist/types/overlays/ask-dialog.d.ts +5 -2
  78. package/dist/types/overlays/btw-history-panel.d.ts +13 -1
  79. package/dist/types/overlays/copy-selector.d.ts +1 -0
  80. package/dist/types/overlays/history-search.d.ts +10 -0
  81. package/dist/types/overlays/hook-selector.d.ts +9 -4
  82. package/dist/types/overlays/hub-frame.d.ts +4 -1
  83. package/dist/types/overlays/jobs-panel.d.ts +1 -7
  84. package/dist/types/overlays/model-browser.d.ts +54 -7
  85. package/dist/types/overlays/model-hub.d.ts +37 -0
  86. package/dist/types/overlays/oauth-selector.d.ts +0 -1
  87. package/dist/types/overlays/plan-review-overlay.d.ts +3 -2
  88. package/dist/types/overlays/report-panel.d.ts +68 -0
  89. package/dist/types/overlays/rewind-selector.d.ts +1 -0
  90. package/dist/types/overlays/session-observer-registry.d.ts +2 -0
  91. package/dist/types/overlays/session-selector.d.ts +6 -2
  92. package/dist/types/overlays/settings-defs.d.ts +1 -0
  93. package/dist/types/overlays/thinking-selector.d.ts +2 -2
  94. package/dist/types/overlays/usage-dashboard.d.ts +12 -8
  95. package/dist/types/overlays/usage-display.d.ts +3 -1
  96. package/dist/types/prompt/composer-cache.d.ts +0 -16
  97. package/dist/types/prompt/composer.d.ts +18 -3
  98. package/dist/types/prompt/custom-editor.d.ts +25 -13
  99. package/dist/types/prompt/image-source.d.ts +2 -1
  100. package/dist/types/prompt/interactive-context-helpers.d.ts +5 -1
  101. package/dist/types/prompt/model-mention-autocomplete.d.ts +1 -1
  102. package/dist/types/prompt/welcome.d.ts +7 -2
  103. package/dist/types/render/composite.d.ts +2 -0
  104. package/dist/types/render/output-pane.d.ts +1 -0
  105. package/dist/types/render/render-utils.d.ts +1 -0
  106. package/dist/types/render/tool-card.d.ts +6 -0
  107. package/dist/types/render/width-aware-text.d.ts +2 -0
  108. package/dist/types/setup/scenes/types.d.ts +1 -0
  109. package/dist/types/space-hold.d.ts +40 -25
  110. package/dist/types/status-line/component.d.ts +3 -0
  111. package/dist/types/status-line/context-usage.d.ts +7 -5
  112. package/dist/types/status-line/host.d.ts +4 -1
  113. package/dist/types/status-line/segments.d.ts +2 -0
  114. package/dist/types/status-line/types.d.ts +14 -3
  115. package/dist/types/terminal-capabilities.d.ts +41 -36
  116. package/dist/types/terminal-multiplexer.d.ts +127 -14
  117. package/dist/types/terminal.d.ts +23 -16
  118. package/dist/types/theme/active-symbols.d.ts +1 -1
  119. package/dist/types/theme/schema.d.ts +11 -2
  120. package/dist/types/theme/symbols.d.ts +1 -1
  121. package/dist/types/theme/theme-class.d.ts +7 -0
  122. package/dist/types/theme/theme.d.ts +1 -1
  123. package/dist/types/theme/tui-adapters.d.ts +3 -0
  124. package/dist/types/thinking.d.ts +1 -1
  125. package/dist/types/tmux.d.ts +11 -4
  126. package/dist/types/tools/bash.d.ts +7 -2
  127. package/dist/types/tools/daemon.d.ts +1 -1
  128. package/dist/types/tools/eval.d.ts +22 -3
  129. package/dist/types/tools/glob.d.ts +4 -0
  130. package/dist/types/tools/native-view.d.ts +47 -0
  131. package/dist/types/tools/output-meta.d.ts +11 -1
  132. package/dist/types/tools/renderer.d.ts +37 -2
  133. package/dist/types/tools/streaming-output.d.ts +20 -1
  134. package/dist/types/tools/task.d.ts +2 -3
  135. package/dist/types/tools/write.d.ts +9 -2
  136. package/dist/types/tools/yield.d.ts +22 -0
  137. package/dist/types/tui.d.ts +36 -0
  138. package/dist/types/utils.d.ts +6 -0
  139. package/dist/types/vim.d.ts +19 -5
  140. package/dist/types/windows-input-mode.d.ts +33 -1
  141. package/package.json +13 -9
  142. package/src/active-terminal.d.ts +30 -0
  143. package/src/active-terminal.ts +80 -0
  144. package/src/app-keybindings.d.ts +7 -2
  145. package/src/app-keybindings.ts +7 -2
  146. package/src/apps/autoresearch-dashboard.ts +7 -3
  147. package/src/apps/cleanse-board.ts +2 -2
  148. package/src/apps/cleanse-picker.ts +1 -1
  149. package/src/apps/debug/log-viewer.ts +188 -72
  150. package/src/apps/debug/raw-sse-buffer.ts +8 -0
  151. package/src/apps/debug/raw-sse.ts +39 -5
  152. package/src/apps/git/diff-pane.ts +96 -23
  153. package/src/apps/git/git-tui.ts +23 -16
  154. package/src/apps/git/help.ts +29 -20
  155. package/src/apps/git/sidebar.ts +8 -3
  156. package/src/apps/if-bench-board.ts +2 -2
  157. package/src/apps/ps-top.ts +2 -2
  158. package/src/apps/setup-model-picker.ts +1 -1
  159. package/src/autocomplete.d.ts +5 -2
  160. package/src/autocomplete.ts +67 -43
  161. package/src/bracketed-paste.ts +10 -3
  162. package/src/charts/chart-plan.d.ts +674 -0
  163. package/src/charts/chart-plan.ts +5081 -0
  164. package/src/charts/chart-svg.d.ts +59 -0
  165. package/src/charts/chart-svg.ts +4657 -0
  166. package/src/charts/table-data.d.ts +175 -0
  167. package/src/charts/table-data.ts +1478 -0
  168. package/src/chat/advisor-message.ts +11 -0
  169. package/src/chat/assistant-message.d.ts +11 -1
  170. package/src/chat/assistant-message.ts +245 -54
  171. package/src/chat/chat-transcript-builder.d.ts +3 -0
  172. package/src/chat/chat-transcript-builder.ts +9 -3
  173. package/src/chat/compaction-summary-message.d.ts +1 -0
  174. package/src/chat/compaction-summary-message.ts +8 -0
  175. package/src/chat/execution-shared.d.ts +6 -5
  176. package/src/chat/execution-shared.ts +42 -25
  177. package/src/chat/fence-figure.d.ts +42 -0
  178. package/src/chat/fence-figure.ts +80 -0
  179. package/src/chat/figure-markdown.d.ts +42 -0
  180. package/src/chat/figure-markdown.ts +174 -0
  181. package/src/chat/messages.d.ts +1 -1
  182. package/src/chat/messages.ts +1 -1
  183. package/src/chat/read-tool-group.ts +6 -4
  184. package/src/chat/skill-message.ts +6 -0
  185. package/src/chat/svg-figure.d.ts +33 -0
  186. package/src/chat/svg-figure.ts +399 -0
  187. package/src/chat/svg-source.d.ts +49 -0
  188. package/src/chat/svg-source.ts +188 -0
  189. package/src/chat/table-chart.d.ts +89 -0
  190. package/src/chat/table-chart.ts +294 -0
  191. package/src/chat/tool-execution.d.ts +2 -0
  192. package/src/chat/tool-execution.ts +86 -7
  193. package/src/chat/user-message.d.ts +17 -4
  194. package/src/chat/user-message.ts +71 -8
  195. package/src/chrome/shared.ts +53 -0
  196. package/src/chrome/status-notice.ts +25 -7
  197. package/src/chrome/transcript-container.d.ts +6 -0
  198. package/src/chrome/transcript-container.ts +162 -19
  199. package/src/components/box.d.ts +1 -0
  200. package/src/components/box.ts +5 -0
  201. package/src/components/disclosure.d.ts +1 -0
  202. package/src/components/disclosure.ts +8 -0
  203. package/src/components/editor.d.ts +23 -5
  204. package/src/components/editor.ts +316 -47
  205. package/src/components/form.d.ts +1 -0
  206. package/src/components/form.ts +8 -0
  207. package/src/components/image.d.ts +26 -44
  208. package/src/components/image.ts +115 -152
  209. package/src/components/input.d.ts +2 -1
  210. package/src/components/input.ts +17 -7
  211. package/src/components/key-value-list.d.ts +1 -0
  212. package/src/components/key-value-list.ts +4 -0
  213. package/src/components/layout/row.d.ts +1 -0
  214. package/src/components/layout/row.ts +7 -0
  215. package/src/components/layout/stack.d.ts +1 -0
  216. package/src/components/layout/stack.ts +7 -0
  217. package/src/components/loader.d.ts +5 -6
  218. package/src/components/loader.ts +10 -17
  219. package/src/components/markdown.d.ts +47 -5
  220. package/src/components/markdown.ts +786 -303
  221. package/src/components/scroll-view.d.ts +1 -0
  222. package/src/components/scroll-view.ts +5 -0
  223. package/src/components/section.d.ts +1 -0
  224. package/src/components/section.ts +5 -0
  225. package/src/components/settings-list.ts +2 -0
  226. package/src/components/table.d.ts +1 -0
  227. package/src/components/table.ts +4 -0
  228. package/src/components/text.d.ts +1 -0
  229. package/src/components/text.ts +7 -0
  230. package/src/components/tree-view.d.ts +7 -0
  231. package/src/components/tree-view.ts +38 -8
  232. package/src/components/truncated-text.d.ts +1 -0
  233. package/src/components/truncated-text.ts +5 -0
  234. package/src/desktop-notify.ts +3 -2
  235. package/src/fuzzy.d.ts +29 -1
  236. package/src/fuzzy.ts +115 -30
  237. package/src/hotkeys-markdown.ts +9 -2
  238. package/src/key-hint-format.d.ts +9 -1
  239. package/src/key-hint-format.ts +26 -11
  240. package/src/keybinding-matchers.d.ts +3 -0
  241. package/src/keybinding-matchers.ts +6 -2
  242. package/src/keybindings.d.ts +1 -1
  243. package/src/keybindings.ts +1 -1
  244. package/src/kitty-graphics.d.ts +2 -2
  245. package/src/kitty-graphics.ts +11 -5
  246. package/src/loop-watchdog.d.ts +8 -7
  247. package/src/loop-watchdog.ts +18 -13
  248. package/src/multiplexers/cmux.d.ts +16 -0
  249. package/src/multiplexers/cmux.ts +29 -0
  250. package/src/multiplexers/herdr.d.ts +26 -0
  251. package/src/multiplexers/herdr.ts +64 -0
  252. package/src/multiplexers/notify.d.ts +13 -0
  253. package/src/multiplexers/notify.ts +24 -0
  254. package/src/multiplexers/orca.d.ts +12 -0
  255. package/src/multiplexers/orca.ts +16 -0
  256. package/src/multiplexers/screen.d.ts +9 -0
  257. package/src/multiplexers/screen.ts +15 -0
  258. package/src/multiplexers/tmux.d.ts +29 -0
  259. package/src/multiplexers/tmux.ts +40 -0
  260. package/src/multiplexers/types.d.ts +100 -0
  261. package/src/multiplexers/types.ts +110 -0
  262. package/src/multiplexers/wmux.d.ts +12 -0
  263. package/src/multiplexers/wmux.ts +16 -0
  264. package/src/multiplexers/zellij.d.ts +12 -0
  265. package/src/multiplexers/zellij.ts +24 -0
  266. package/src/native/apply.d.ts +4 -3
  267. package/src/native/apply.ts +4 -3
  268. package/src/native/backend.d.ts +5 -4
  269. package/src/native/backend.ts +199 -22
  270. package/src/native/blobs.d.ts +7 -3
  271. package/src/native/blobs.ts +47 -12
  272. package/src/native/encode.d.ts +8 -6
  273. package/src/native/encode.ts +76 -34
  274. package/src/native/node.d.ts +36 -5
  275. package/src/native/node.ts +31 -5
  276. package/src/native/overlay.d.ts +8 -3
  277. package/src/native/overlay.ts +17 -4
  278. package/src/native/reconcile.ts +16 -4
  279. package/src/native/settle.d.ts +4 -3
  280. package/src/native/settle.ts +4 -3
  281. package/src/overlays/agent-hub-projection.d.ts +6 -5
  282. package/src/overlays/agent-hub-projection.ts +124 -13
  283. package/src/overlays/agent-hub-types.d.ts +9 -0
  284. package/src/overlays/agent-hub-types.ts +6 -0
  285. package/src/overlays/agent-hub.ts +48 -29
  286. package/src/overlays/agent-transcript-viewer.ts +93 -23
  287. package/src/overlays/agents-hub.ts +28 -8
  288. package/src/overlays/annotation-overlay.ts +293 -90
  289. package/src/overlays/annotation-types.ts +2 -0
  290. package/src/overlays/ask-dialog.ts +10 -17
  291. package/src/overlays/btw-history-panel.ts +136 -39
  292. package/src/overlays/copy-selector.d.ts +1 -0
  293. package/src/overlays/copy-selector.ts +32 -8
  294. package/src/overlays/extensions/extension-dashboard.ts +4 -4
  295. package/src/overlays/extensions/state-manager.ts +52 -12
  296. package/src/overlays/history-search.ts +100 -7
  297. package/src/overlays/hook-selector.ts +12 -5
  298. package/src/overlays/hub-frame.ts +19 -1
  299. package/src/overlays/jobs-panel.ts +33 -52
  300. package/src/overlays/model-browser.ts +367 -122
  301. package/src/overlays/model-hub.ts +435 -59
  302. package/src/overlays/move-overlay.ts +28 -14
  303. package/src/overlays/oauth-selector.ts +54 -70
  304. package/src/overlays/pause-screen.ts +2 -2
  305. package/src/overlays/plan-review-overlay.ts +208 -70
  306. package/src/overlays/plugin-settings.ts +3 -3
  307. package/src/overlays/report-panel.ts +191 -0
  308. package/src/overlays/rewind-selector.ts +88 -17
  309. package/src/overlays/session-observer-registry.d.ts +2 -0
  310. package/src/overlays/session-observer-registry.ts +10 -2
  311. package/src/overlays/session-selector.ts +64 -26
  312. package/src/overlays/settings-defs.ts +5 -4
  313. package/src/overlays/settings-selector.ts +33 -8
  314. package/src/overlays/snapcompact-shape-preview.ts +2 -3
  315. package/src/overlays/stats-notice.ts +1 -1
  316. package/src/overlays/thinking-selector.ts +11 -10
  317. package/src/overlays/tree-selector.ts +41 -20
  318. package/src/overlays/usage-dashboard.ts +117 -58
  319. package/src/overlays/usage-display.ts +10 -4
  320. package/src/overlays/usage-row.ts +22 -8
  321. package/src/prompt/attachment-chips.ts +15 -11
  322. package/src/prompt/composer-attachments.ts +6 -4
  323. package/src/prompt/composer-cache.ts +7 -86
  324. package/src/prompt/composer.ts +37 -26
  325. package/src/prompt/custom-editor.ts +282 -105
  326. package/src/prompt/image-source.ts +2 -1
  327. package/src/prompt/interactive-context-helpers.ts +32 -18
  328. package/src/prompt/macos-spelling.ts +7 -0
  329. package/src/prompt/magic-keywords.ts +11 -2
  330. package/src/prompt/model-mention-autocomplete.ts +14 -14
  331. package/src/prompt/queued-messages.ts +6 -4
  332. package/src/prompt/tips.txt +13 -9
  333. package/src/prompt/welcome.ts +64 -46
  334. package/src/prompt/word-completion.ts +38 -5
  335. package/src/render/composite.d.ts +2 -0
  336. package/src/render/composite.ts +64 -0
  337. package/src/render/output-pane.d.ts +1 -0
  338. package/src/render/output-pane.ts +6 -0
  339. package/src/render/render-utils.d.ts +1 -0
  340. package/src/render/render-utils.ts +16 -1
  341. package/src/render/tool-card.d.ts +6 -0
  342. package/src/render/tool-card.ts +15 -0
  343. package/src/render/width-aware-text.d.ts +2 -0
  344. package/src/render/width-aware-text.ts +5 -0
  345. package/src/setup/scenes/model.ts +2 -1
  346. package/src/setup/scenes/types.ts +1 -0
  347. package/src/space-hold.d.ts +40 -25
  348. package/src/space-hold.ts +156 -52
  349. package/src/status-line/component.ts +292 -78
  350. package/src/status-line/context-usage.ts +21 -46
  351. package/src/status-line/host.ts +4 -1
  352. package/src/status-line/segments.ts +40 -22
  353. package/src/status-line/types.ts +15 -3
  354. package/src/terminal-capabilities.d.ts +41 -36
  355. package/src/terminal-capabilities.ts +137 -213
  356. package/src/terminal-multiplexer.d.ts +127 -14
  357. package/src/terminal-multiplexer.ts +112 -33
  358. package/src/terminal.d.ts +23 -16
  359. package/src/terminal.ts +203 -99
  360. package/src/theme/active-symbols.d.ts +1 -1
  361. package/src/theme/active-symbols.ts +1 -1
  362. package/src/theme/schema-validation.ts +1 -0
  363. package/src/theme/schema.d.ts +11 -2
  364. package/src/theme/schema.ts +14 -1
  365. package/src/theme/session-color.ts +1 -7
  366. package/src/theme/symbols.d.ts +1 -1
  367. package/src/theme/symbols.ts +11 -4
  368. package/src/theme/theme-class.d.ts +7 -0
  369. package/src/theme/theme-class.ts +26 -1
  370. package/src/theme/theme-schema.json +5 -1
  371. package/src/theme/theme.d.ts +1 -1
  372. package/src/theme/theme.ts +8 -4
  373. package/src/theme/tui-adapters.d.ts +3 -0
  374. package/src/theme/tui-adapters.ts +68 -0
  375. package/src/thinking.d.ts +1 -1
  376. package/src/thinking.ts +1 -1
  377. package/src/tmux.d.ts +11 -4
  378. package/src/tmux.ts +47 -11
  379. package/src/tools/ast-edit.ts +59 -35
  380. package/src/tools/ast-grep.ts +35 -16
  381. package/src/tools/bash.d.ts +7 -2
  382. package/src/tools/bash.ts +116 -47
  383. package/src/tools/daemon.d.ts +1 -1
  384. package/src/tools/daemon.ts +1 -1
  385. package/src/tools/default-renderer.ts +35 -19
  386. package/src/tools/edit.ts +142 -120
  387. package/src/tools/eval-format/index.ts +28 -2
  388. package/src/tools/eval-format/javascript.ts +7 -5
  389. package/src/tools/eval.d.ts +22 -3
  390. package/src/tools/eval.ts +364 -208
  391. package/src/tools/fetch.ts +5 -6
  392. package/src/tools/glob.d.ts +4 -0
  393. package/src/tools/glob.ts +33 -65
  394. package/src/tools/grep.ts +81 -48
  395. package/src/tools/index.ts +2 -0
  396. package/src/tools/lsp.ts +81 -59
  397. package/src/tools/mcp.ts +111 -68
  398. package/src/tools/native-view.d.ts +47 -0
  399. package/src/tools/native-view.ts +102 -1
  400. package/src/tools/output-meta.d.ts +11 -1
  401. package/src/tools/output-meta.ts +23 -3
  402. package/src/tools/read.ts +7 -5
  403. package/src/tools/renderer.d.ts +37 -2
  404. package/src/tools/renderer.ts +42 -2
  405. package/src/tools/streaming-output.d.ts +20 -1
  406. package/src/tools/streaming-output.ts +154 -50
  407. package/src/tools/task.d.ts +2 -3
  408. package/src/tools/task.ts +115 -62
  409. package/src/tools/todo.ts +119 -30
  410. package/src/tools/wait.ts +34 -24
  411. package/src/tools/web-search.ts +1 -1
  412. package/src/tools/write.d.ts +9 -2
  413. package/src/tools/write.ts +147 -95
  414. package/src/tools/yield.d.ts +22 -0
  415. package/src/tools/yield.ts +225 -0
  416. package/src/tui.d.ts +36 -0
  417. package/src/tui.ts +666 -217
  418. package/src/utils.d.ts +6 -0
  419. package/src/utils.ts +19 -0
  420. package/src/vim.d.ts +19 -5
  421. package/src/vim.ts +333 -18
  422. package/src/windows-input-mode.d.ts +33 -1
  423. package/src/windows-input-mode.ts +212 -6
@@ -4,7 +4,8 @@
4
4
  * to the session's `local://` root, and generated video contact-sheet previews.
5
5
  * Symbol metadata stays out of serialized/model-bound image data while traveling
6
6
  * with the draft object, until AgentSession creates the hidden companion message
7
- * that tells the model the path (and link materialization prefers it over a blob copy).
7
+ * that tells the model the path. The model keeps `local://` references across
8
+ * `/move`; chips for internal URLs instead use a stable blob copy.
8
9
  */
9
10
  import type { ImageContent } from "@linxiraos/pi-ai";
10
11
  import { isRecord } from "@linxiraos/pi-utils";
@@ -1,5 +1,5 @@
1
1
  /** Shared assistant transcript construction and model-authored link caching. */
2
- import type { AssistantMessage, Model } from "@linxiraos/pi-ai";
2
+ import type { AssistantMessage, Model, TextContent } from "@linxiraos/pi-ai";
3
3
  import { getMarkdownLinkUrls } from "../index";
4
4
  import { EMPTY_LINK_TARGETS } from "../render/render-utils";
5
5
  import type { ImageBudget } from "../components/image";
@@ -16,11 +16,15 @@ export interface AssistantMessageHost {
16
16
  readonly viewSession: AssistantMessageSession;
17
17
  readonly effectiveHideThinkingBlock: boolean;
18
18
  readonly proseOnlyThinking: boolean;
19
+ readonly expandThinkingBlocks: boolean;
19
20
  readonly assistantImagesVisible: boolean;
21
+ /** Whether the viewed session's tables get charts: the main session's do, a focused subagent's do not. */
22
+ readonly tableChartsVisible: boolean;
20
23
  readonly hideToolActivity: boolean;
21
24
  readonly toolOutputExpanded: boolean;
22
25
  readonly ui: { requestRender(): void; readonly imageBudget: ImageBudget };
23
- resolveAssistantMessageLinks(texts: readonly string[]): Promise<ReadonlyMap<string, string>>;
26
+ /** Resolve already-extracted Markdown link destinations to local targets. */
27
+ resolveAssistantMessageLinkHrefs(hrefs: readonly string[]): Promise<ReadonlyMap<string, string>>;
24
28
  }
25
29
 
26
30
  const kMarkdownLinkTargets = Symbol("markdownLinkTargets");
@@ -28,14 +32,26 @@ type SessionWithMarkdownLinkTargets = AssistantMessageSession & {
28
32
  [kMarkdownLinkTargets]?: ReadonlyMap<string, string>;
29
33
  };
30
34
 
31
- function assistantTextBlocks(messages: readonly AssistantMessage[]): string[] {
32
- const texts: string[] = [];
33
- for (const message of messages) {
34
- for (const content of message.content) {
35
- if (content.type === "text") texts.push(content.text);
35
+ /** Link destinations per text block, revalidated against the block's text (streaming blocks grow in place). */
36
+ const textBlockHrefs = new WeakMap<TextContent, { text: string; hrefs: readonly string[] }>();
37
+
38
+ /**
39
+ * Distinct Markdown link destinations authored in `message`'s text blocks.
40
+ * Each block is lexed once per text revision; transcript rebuilds and the
41
+ * message_end refresh reuse the result.
42
+ */
43
+ function assistantMessageLinkHrefs(message: AssistantMessage): string[] {
44
+ const hrefs = new Set<string>();
45
+ for (const content of message.content) {
46
+ if (content.type !== "text") continue;
47
+ let memo = textBlockHrefs.get(content);
48
+ if (memo?.text !== content.text) {
49
+ memo = { text: content.text, hrefs: getMarkdownLinkUrls(content.text) };
50
+ textBlockHrefs.set(content, memo);
36
51
  }
52
+ for (const href of memo.hrefs) hrefs.add(href);
37
53
  }
38
- return texts;
54
+ return [...hrefs];
39
55
  }
40
56
 
41
57
  /**
@@ -50,13 +66,12 @@ export async function refreshAssistantMessageLinkTargets(
50
66
  ): Promise<ReadonlyMap<string, string>> {
51
67
  const session: SessionWithMarkdownLinkTargets = ctx.viewSession;
52
68
  const previous = session[kMarkdownLinkTargets] ?? EMPTY_LINK_TARGETS;
53
- const texts = assistantTextBlocks(messages);
54
69
  const hrefs = new Set<string>();
55
- for (const text of texts) {
56
- for (const href of getMarkdownLinkUrls(text)) hrefs.add(href);
70
+ for (const message of messages) {
71
+ for (const href of assistantMessageLinkHrefs(message)) hrefs.add(href);
57
72
  }
58
73
  if (hrefs.size === 0) return previous;
59
- const resolved = await ctx.resolveAssistantMessageLinks(texts);
74
+ const resolved = await ctx.resolveAssistantMessageLinkHrefs([...hrefs]);
60
75
  let changed = false;
61
76
  for (const href of hrefs) {
62
77
  if (previous.get(href) !== resolved.get(href)) {
@@ -84,12 +99,9 @@ export function assistantMessageLinkTargets(
84
99
  targets: ReadonlyMap<string, string>,
85
100
  ): ReadonlyMap<string, string> {
86
101
  const selected = new Map<string, string>();
87
- for (const content of message.content) {
88
- if (content.type !== "text") continue;
89
- for (const href of getMarkdownLinkUrls(content.text)) {
90
- const target = targets.get(href);
91
- if (target) selected.set(href, target);
92
- }
102
+ for (const href of assistantMessageLinkHrefs(message)) {
103
+ const target = targets.get(href);
104
+ if (target) selected.set(href, target);
93
105
  }
94
106
  return selected;
95
107
  }
@@ -112,8 +124,10 @@ export function createAssistantMessageComponent(
112
124
  ctx.ui.imageBudget,
113
125
  ctx.proseOnlyThinking,
114
126
  linkTargets,
127
+ ctx.expandThinkingBlocks,
115
128
  );
116
129
  component.setImagesVisible(ctx.assistantImagesVisible);
130
+ component.setTableChartsVisible(ctx.tableChartsVisible);
117
131
  component.setToolResultImagesVisible(!ctx.hideToolActivity);
118
132
  component.setExpanded(ctx.toolOutputExpanded);
119
133
  // A wire the `stream-revision` axis marks `possible` can rewrite text it has
@@ -56,6 +56,8 @@ export class MacOSSpellingProvider implements EditorTextAssistProvider {
56
56
  #availabilityChecked = false;
57
57
  #cacheGeneration = 0;
58
58
  #typoCache = new Map<string, readonly native.SpellingRange[]>();
59
+ /** {@link #projectTypoRanges} answers for the current {@link #typoCache}; segments re-ask every frame. */
60
+ #projectionMemo = new Map<string, readonly native.SpellingRange[] | undefined>();
59
61
  #typoInFlight = new Map<string, Promise<readonly native.SpellingRange[]>>();
60
62
  #automaticTypoActive = false;
61
63
  #automaticTypoQueue = new Map<string, string>();
@@ -237,6 +239,7 @@ export class MacOSSpellingProvider implements EditorTextAssistProvider {
237
239
  return request;
238
240
  }
239
241
  #projectTypoRanges(text: string): readonly native.SpellingRange[] | undefined {
242
+ if (this.#projectionMemo.has(text)) return this.#projectionMemo.get(text);
240
243
  let projected: native.SpellingRange[] | undefined;
241
244
  let matchLength = -1;
242
245
  for (const [previous, ranges] of this.#typoCache) {
@@ -265,6 +268,8 @@ export class MacOSSpellingProvider implements EditorTextAssistProvider {
265
268
  });
266
269
  matchLength = prefix + suffix;
267
270
  }
271
+ if (this.#projectionMemo.size >= CACHE_LIMIT) this.#projectionMemo.clear();
272
+ this.#projectionMemo.set(text, projected);
268
273
  return projected;
269
274
  }
270
275
 
@@ -316,6 +321,7 @@ export class MacOSSpellingProvider implements EditorTextAssistProvider {
316
321
  const hadProjectedRanges = (this.#projectTypoRanges(text)?.length ?? 0) > 0;
317
322
  if (this.#typoCache.size >= CACHE_LIMIT) this.#typoCache.clear();
318
323
  this.#typoCache.set(text, ranges);
324
+ this.#projectionMemo.clear();
319
325
  if (ranges.length > 0 || hadProjectedRanges) this.onUpdate?.();
320
326
  return ranges;
321
327
  }
@@ -323,6 +329,7 @@ export class MacOSSpellingProvider implements EditorTextAssistProvider {
323
329
  #clearCaches(): void {
324
330
  this.#cacheGeneration++;
325
331
  this.#typoCache.clear();
332
+ this.#projectionMemo.clear();
326
333
  this.#typoInFlight.clear();
327
334
  this.#automaticTypoQueue.clear();
328
335
  }
@@ -148,6 +148,9 @@ export function magicKeywordRanges(text: string): { from: number; to: number }[]
148
148
  return ranges.sort((a, b) => a.from - b.from);
149
149
  }
150
150
 
151
+ /** Last {@link hasMagicKeyword} answer: the live editor asks once per layout segment with the same buffer. */
152
+ let lastProbe: { text: string; registry: readonly RegisteredKeyword[]; found: boolean } | undefined;
153
+
151
154
  /**
152
155
  * Cheap test for "does this text contain any registered keyword as standalone
153
156
  * prose?". Short-circuits on a substring probe before paying for the
@@ -155,8 +158,14 @@ export function magicKeywordRanges(text: string): { from: number; to: number }[]
155
158
  * `String#includes` per word. Used by the live editor to gate the shimmer timer.
156
159
  */
157
160
  export function hasMagicKeyword(text: string): boolean {
161
+ if (lastProbe?.text === text && lastProbe.registry === registry) return lastProbe.found;
162
+ let found = false;
158
163
  for (const keyword of registry) {
159
- if (text.includes(keyword.word) && containsMagicKeyword(text, keyword.word)) return true;
164
+ if (text.includes(keyword.word) && containsMagicKeyword(text, keyword.word)) {
165
+ found = true;
166
+ break;
167
+ }
160
168
  }
161
- return false;
169
+ lastProbe = { text, registry, found };
170
+ return found;
162
171
  }
@@ -5,9 +5,7 @@ import { modelMentionDisplayName } from "./model-mention-syntax";
5
5
  import type {
6
6
  buildSearchAffinity as BuildSearchAffinity,
7
7
  ModelBrowserItem,
8
- rankModelItems as RankModelItems,
9
- SearchAffinity,
10
- SessionModelScope,
8
+ ModelItemRanker as ModelItemRankerClass,
11
9
  SessionModelScopeCache as SessionModelScopeCacheClass,
12
10
  } from "../overlays/model-browser";
13
11
  import { theme } from "../theme/theme";
@@ -21,7 +19,7 @@ export type ModelMentionCandidateSource = (query: string) => ReadonlyArray<Model
21
19
  interface ModelBrowserModules {
22
20
  SessionModelScopeCache: typeof SessionModelScopeCacheClass;
23
21
  buildSearchAffinity: typeof BuildSearchAffinity;
24
- rankModelItems: typeof RankModelItems;
22
+ ModelItemRanker: typeof ModelItemRankerClass;
25
23
  }
26
24
 
27
25
  /** Synchronous first-use boundary for the interactive model browser implementation. */
@@ -80,7 +78,7 @@ export function applyModelMentionCompletion(
80
78
 
81
79
  /**
82
80
  * Create a session-scoped model candidate lookup using picker ordering. The
83
- * scope and search affinity are reused across queries until their inputs change.
81
+ * scope and its prepared ranker are reused across queries until their inputs change.
84
82
  */
85
83
  export function createModelMentionSource(host: {
86
84
  source: ModelBrowserSource;
@@ -88,20 +86,22 @@ export function createModelMentionSource(host: {
88
86
  scopedModels: () => ReadonlyArray<Model>;
89
87
  }): ModelMentionCandidateSource {
90
88
  let scopeCache: SessionModelScopeCacheClass | undefined;
91
- let affinityScope: SessionModelScope | undefined;
92
- let affinityProviderOrder: readonly string[] | undefined;
93
- let affinity: SearchAffinity | undefined;
89
+ let ranker: ModelItemRankerClass | undefined;
90
+ let rankerProviderOrder: readonly string[] | undefined;
94
91
  return query => {
95
- const { SessionModelScopeCache, buildSearchAffinity, rankModelItems } = loadModelBrowser();
92
+ const { SessionModelScopeCache, buildSearchAffinity, ModelItemRanker } = loadModelBrowser();
96
93
  scopeCache ??= new SessionModelScopeCache(host.source, host.registry);
97
94
  const scope = scopeCache.get(host.scopedModels());
98
95
  if (!query.trim()) return scope.items;
99
96
  const providerOrder = host.source.modelProviderOrder;
100
- if (!affinity || affinityScope !== scope || affinityProviderOrder !== providerOrder) {
101
- affinity = buildSearchAffinity(providerOrder, scope.roles, scope.mruOrder);
102
- affinityScope = scope;
103
- affinityProviderOrder = providerOrder;
97
+ if (ranker?.items !== scope.items || rankerProviderOrder !== providerOrder) {
98
+ ranker = new ModelItemRanker(scope.items, {
99
+ roles: scope.roles,
100
+ mruOrder: scope.mruOrder,
101
+ affinity: buildSearchAffinity(providerOrder, scope.roles, scope.mruOrder),
102
+ });
103
+ rankerProviderOrder = providerOrder;
104
104
  }
105
- return rankModelItems(query, scope.items, { roles: scope.roles, mruOrder: scope.mruOrder, affinity });
105
+ return ranker.rank(query);
106
106
  };
107
107
  }
@@ -1,7 +1,7 @@
1
1
  import { Container } from "../tui";
2
2
  import { Spacer } from "../components/spacer";
3
3
  import { TruncatedText } from "../components/truncated-text";
4
- import { formatKeyHint } from "../key-hint-format";
4
+ import { formatKeyHint, formatTooltipKey } from "../key-hint-format";
5
5
  import type { KeyId } from "../keys";
6
6
  import { kbd, node, text } from "../native/describe";
7
7
  import type { NativeNode, NativeUiEvent } from "../native/node";
@@ -39,8 +39,10 @@ export class QueuedMessagesBand extends Container {
39
39
  this.addChild(new TruncatedText(theme.fg("dim", ` ${index + 1}. ${message}`), 1, 0));
40
40
  }
41
41
  }
42
- const editKey = formatKeyHint(dequeueKey);
43
- this.addChild(new TruncatedText(theme.fg("dim", ` ${theme.tree.hook} ${editKey} to edit`), 1, 0));
42
+ this.addChild(
43
+ new TruncatedText(theme.fg("dim", ` ${theme.tree.hook} ${formatKeyHint(dequeueKey)} to edit`), 1, 0),
44
+ );
45
+ const editTitle = `Edit ${formatTooltipKey(dequeueKey)}`;
44
46
 
45
47
  const count = groups.reduce((sum, group) => sum + group.messages.length, 0);
46
48
  const pills: NativeNode[] = [];
@@ -70,7 +72,7 @@ export class QueuedMessagesBand extends Container {
70
72
  role: "zeta.queue.edit",
71
73
  gap: "xs",
72
74
  align: "center",
73
- title: `Edit ${editKey}`,
75
+ title: editTitle,
74
76
  actions: { click: "queue.edit" },
75
77
  },
76
78
  [kbd(dequeueKey), text("Edit")],
@@ -1,29 +1,33 @@
1
1
  Tired of typing "keep going"? Just send a '.'
2
+ Type `#` anywhere in your prompt for quick actions — copy the line or the whole prompt, undo, jump the cursor
3
+ Type `/` at the start of your prompt to browse every command
4
+ Start your prompt with `!` to run it as bash — `!!` runs it without adding the output to context
5
+ Start your prompt with `$ ` to run Python — `$$ ` runs it without adding the output to context
2
6
  You can /btw to ask a side question
3
7
  Use /tan to fork the current conversation into a background agent
4
8
  With default keybindings, {key:ctrl+d} deletes the character under the cursor instead of quitting your draft — see /hotkeys for your exit key
5
- Find out which model you emotionally abuse the most with `omp stats`
9
+ Find out which model you emotionally abuse the most with `zetacode stats`
6
10
  Try task isolation to create CoW worktrees
7
- Need a cheap nested model call? Use `completion(x...)`. Have a big batch of tasks? Ask clanker to use it!
11
+ Need a cheap nested model call? Use `completion(x...)`
8
12
  Spaghetti code? Try complaining with /omfg
9
- Did you know? Each kitty/tmux/cmux/zellij/wezterm split keeps its own session — `omp -c` resumes the right one
13
+ Did you know? Each kitty/tmux/cmux/zellij/wezterm split keeps its own session — `zetacode -c` resumes the right one
10
14
  Drop the word `ultrathink` in your message for harder multi-step reasoning — watch it glow rainbow as you type
11
15
  Say `orchestrate` in your message to drive a multi-phase task with parallel subagents — watch it glow as you type
12
16
  Say `workflowz` in your message to drive the task with parallel subagents in eval — watch it glow as you type
13
17
  Bulk data to classify or triage? Say `jevify` in your message — the eval kernel's `judge()` processes the batch and you read only what it flags — watch it glow as you type
14
- Log in to several accounts of the same provider — `/login` again — and omp load-balances across them automatically
15
- Run `omp auth-broker serve` once and every machine pulls live tokens over the wire — refresh keys never leave the host; `omp auth-gateway` fronts it as a drop-in proxy any OpenAI-compatible client can hit
18
+ Log in to several accounts of the same provider — `/login` again — and zeta load-balances across them automatically
19
+ Run `zetacode auth-broker serve` once and every machine pulls live tokens over the wire — refresh keys never leave the host; `zetacode auth-gateway` fronts it as a drop-in proxy any OpenAI-compatible client can hit
16
20
  Press {key:alt+p} (or /switch) to switch provider, and {key:ctrl+p} to cycle role models smol -> slow -> etc
17
21
  Press {key:ctrl+r} to search your prompt history and reuse a past message
18
22
  `/force read` pins the next turn to one specific tool when the model keeps reaching for the wrong one
19
23
  `/copy code` grabs the last code block to your clipboard — `/copy cmd` grabs the last shell/python command
20
24
  `/shake` rips heavy tool results out of context to reclaim tokens without a full /compact — `/shake images` drops just images
21
- Pair up live: `/collab` shares your session through an end-to-end encrypted relay link — a teammate runs `/join <link>` to watch tool calls stream and prompt the agent from their own omp
25
+ Pair up live: `/collab` shares your session through an end-to-end encrypted relay link — a teammate runs `/join <link>` to watch tool calls stream and prompt the agent from their own zeta
22
26
  Press {tap:left} to drill into a running or finished agent and inspect its tool calls and transcript
23
27
  Hit a Codex rate limit? `/usage reset` spends a saved reset credit to immediately restore your quota
24
- No native tool_calling? Inference provider botches parsing them? `PI_DIALECT=glm|kimi|anthropic…` rolls it locally for them!
25
28
  Turn on `/advisor` to attach a second model that reviews every turn and quietly injects advice
26
29
  Try starting your prompt with a ->, and writing a list (1. Do X, 2. Do Y)
27
30
  Press {key:shift+tab} to cycle through reasoning effort levels
28
- omp can reconfigure and extend itself — ask any questions you have!
29
- Lint/type errors piling up? `omp cleanse` (or /cleanse right here) hunts project diagnostics and fixes them with parallel subagents — {key:escape} cancels
31
+ zeta can reconfigure and extend itself — ask any questions you have!
32
+ Lint/type errors piling up? `zetacode cleanse` (or /cleanse right here) hunts project diagnostics and fixes them with parallel subagents — {key:escape} cancels
33
+ [NO-TERN] Psst, zeta has a natural habitat 🐦 [tern](https://scl.so/tern)
@@ -3,21 +3,26 @@ import type { TspSpan } from "@linxiraos/pi-wire";
3
3
  import { formatDoubleTap, formatKeyHint, formatKeyHints, type KeyName } from "../app-keybindings";
4
4
  import { editorKey } from "../chrome/keybinding-hints";
5
5
  import { getKeybindings, type Keybinding } from "../keybindings";
6
- import { registerNativeBlob } from "../native/blobs";
7
6
  import { card, col, kbd, keyed, node, row, span, text } from "../native/describe";
8
7
  import type { DescribeContext, NativeChild, NativeNode, NativeUiEvent } from "../native/node";
9
8
  import { runTranscriptAction } from "../chat/transcript-actions";
10
- import { plainLine } from "../native/spans";
9
+ import { compactText, plainLine, plainText } from "../native/spans";
10
+
11
11
  import { isNativeRendering } from "../native/state";
12
+ import { isHyperlinkEnabled, urlHyperlink, urlLinkSpan } from "../render/hyperlink";
12
13
  import { TERMINAL } from "../terminal-capabilities";
13
14
  import { theme } from "../theme/theme";
14
15
  import type { Component } from "../tui";
15
16
  import { padding, replaceTabs, truncateToWidth, visibleWidth, wrapTextWithAnsi } from "../utils";
16
17
  import tipsText from "./tips.txt" with { type: "text" };
17
18
 
18
- /** Tips embedded at build time, one per line; blanks dropped. Key placeholders
19
+ /** Leading marker for a tip that pitches Tern: shown only while no TSP surface
20
+ * is live, since a live one means the user is already in Tern. */
21
+ const NO_TERN_TIP_MARKER = /^\[NO-TERN\]\s*/;
22
+
23
+ /** tips.txt lines embedded at build time; blanks dropped. Key placeholders
19
24
  * (see {@link expandTipKeys}) stay raw until render time. */
20
- const TIPS: readonly string[] = tipsText
25
+ const TIP_LINES: readonly string[] = tipsText
21
26
  .split("\n")
22
27
  .map(line => line.trim())
23
28
  .filter(line => line.length > 0);
@@ -34,6 +39,12 @@ export const WELCOME_SESSION_SLOTS = 4;
34
39
  */
35
40
  export const WELCOME_LSP_SLOTS = 4;
36
41
 
42
+ /** Tips for the classic renderer: every tip, {@link NO_TERN_TIP_MARKER} stripped. */
43
+ const TIPS: readonly string[] = TIP_LINES.map(line => line.replace(NO_TERN_TIP_MARKER, ""));
44
+
45
+ /** Tips for a live TSP surface: those marked {@link NO_TERN_TIP_MARKER} dropped. */
46
+ const TSP_TIPS: readonly string[] = TIP_LINES.filter(line => !NO_TERN_TIP_MARKER.test(line));
47
+
37
48
  /** Trailing marker that flags a tip as a "what's new" callout. Stripped before
38
49
  * wrapping (with any preceding whitespace) and replaced by {@link NEW_TAG_TEXT}
39
50
  * painted as a shimmering rainbow. Non-global so `.test` stays stateless. */
@@ -121,14 +132,44 @@ function expandTipKeys(tip: string): string {
121
132
  });
122
133
  }
123
134
 
124
- export function renderWelcomeTip(tip: string, boxWidth: number, phase = 0): string[] {
135
+ /** Markdown-style links in tips.txt: `[Tern](https://scl.so/tern)` shows the label, linked to the URL. */
136
+ const TIP_LINK = /\[([^\]]+)\]\(([^\s)]+)\)/g;
137
+
138
+ /** Tip links as underlined OSC 8 labels, or `label (url)` where the terminal cannot link. */
139
+ function linkTipText(tip: string): string {
140
+ const linkable = isHyperlinkEnabled();
141
+ return tip.replace(TIP_LINK, (_link, label: string, url: string) =>
142
+ linkable ? urlHyperlink(url, theme.underline(label)) : `${label} (${url})`,
143
+ );
144
+ }
145
+
146
+ /** The tip as native text: escapes dropped, links as `href` spans. */
147
+ function tipSpans(tip: string): TspSpan[] {
148
+ const spans: TspSpan[] = [];
149
+ let cursor = 0;
150
+ for (const match of tip.matchAll(TIP_LINK)) {
151
+ const [link, label, url] = match;
152
+ if (match.index > cursor) spans.push(span(plainText(tip.slice(cursor, match.index))));
153
+ spans.push(urlLinkSpan(url, label));
154
+ cursor = match.index + link.length;
155
+ }
156
+ if (cursor < tip.length) spans.push(span(plainText(tip.slice(cursor))));
157
+ return spans;
158
+ }
159
+
160
+ /**
161
+ * The welcome tip as lines of at most `width` columns: `Tip:` and the body
162
+ * wrapped together, with no indent, so the banner can center each line.
163
+ * `[]` when `width` leaves no room for a useful line.
164
+ */
165
+ export function renderWelcomeTip(tip: string, width: number, phase = 0): string[] {
125
166
  const label = "Tip: ";
126
167
  const labelWidth = visibleWidth(label);
127
- const bodyBudget = boxWidth - 1 - labelWidth; // 1 = leading indent
168
+ const bodyBudget = width - labelWidth;
128
169
  if (bodyBudget < 8) return [];
129
170
 
130
171
  const isNew = NEW_TIP_MARKER.test(tip);
131
- const body = expandTipKeys(isNew ? tip.replace(NEW_TIP_MARKER, "") : tip);
172
+ const body = linkTipText(expandTipKeys(isNew ? tip.replace(NEW_TIP_MARKER, "") : tip));
132
173
 
133
174
  const wrappedBody = wrapTextWithAnsi(replaceTabs(body), bodyBudget);
134
175
  if (wrappedBody.length === 0) return [];
@@ -141,8 +182,8 @@ export function renderWelcomeTip(tip: string, boxWidth: number, phase = 0): stri
141
182
 
142
183
  const lines = wrappedBody.map((line, index) => {
143
184
  const styledBody = theme.fg("muted", line);
144
- const content = index === 0 ? `${styledLabel}${styledBody}` : `${continuationIndent}${styledBody}`;
145
- return ` ${theme.italic(content)}`;
185
+ const content = index === 0 ? `${styledLabel}${styledBody}` : `${styledBody}`;
186
+ return theme.italic(content);
146
187
  });
147
188
 
148
189
  if (isNew) {
@@ -153,10 +194,10 @@ export function renderWelcomeTip(tip: string, boxWidth: number, phase = 0): stri
153
194
  const tag = renderNewTag(phase, encoding);
154
195
  const tagWidth = 1 + visibleWidth(NEW_TAG_TEXT); // 1 = space separator
155
196
  const lastLine = lines[lines.length - 1];
156
- if (lastLine !== undefined && visibleWidth(lastLine) + tagWidth <= boxWidth) {
197
+ if (lastLine !== undefined && visibleWidth(lastLine) + tagWidth <= width) {
157
198
  lines[lines.length - 1] = `${lastLine} ${tag}`;
158
199
  } else {
159
- lines.push(` ${continuationIndent}${tag}`);
200
+ lines.push(continuationIndent + tag);
160
201
  }
161
202
  }
162
203
 
@@ -184,9 +225,10 @@ export class WelcomeComponent implements Component {
184
225
  #animTimer: Timer | null = null;
185
226
  #requestRender: (() => void) | null = null;
186
227
  // Tip randomness is latched once so the tip is stable across renders, but
187
- // the nerdfont-nag gate re-reads the live preset: the startup prepaint can
188
- // run under the default "unicode" preset before settings resolve the real
189
- // one, and a memoized nag would survive the switch to "nerd".
228
+ // the nerdfont-nag gate re-reads the live preset and the pool re-reads the
229
+ // live TSP state: the startup prepaint can run under the default "unicode"
230
+ // preset before settings resolve the real one, and an optimistic Tern start
231
+ // falls back to the classic renderer when `hello` never confirms.
190
232
  #nagRoll: number | undefined;
191
233
  #tipRoll: number | undefined;
192
234
  // Render cache: the welcome box is the first transcript-area component, so
@@ -198,8 +240,8 @@ export class WelcomeComponent implements Component {
198
240
 
199
241
  constructor(
200
242
  private version: string,
201
- private modelName: string,
202
- private providerName: string,
243
+ private modelName: string = "",
244
+ private providerName: string = "",
203
245
  private recentSessions: RecentSession[] = [],
204
246
  /** Detected project servers; `null` means LSP is disabled and hides the section. */
205
247
  private lspServers: LspServerInfo[] | null = [],
@@ -210,7 +252,7 @@ export class WelcomeComponent implements Component {
210
252
  if (theme.getSymbolPreset() === "unicode" && this.#nagRoll < 0.1) {
211
253
  return "Please use nerdfont 😭.";
212
254
  }
213
- return pickWeightedTip(TIPS, this.#tipRoll) || undefined;
255
+ return pickWeightedTip(isNativeRendering() ? TSP_TIPS : TIPS, this.#tipRoll) || undefined;
214
256
  }
215
257
 
216
258
  invalidate(): void {
@@ -339,7 +381,7 @@ export class WelcomeComponent implements Component {
339
381
  ];
340
382
  if (tip) {
341
383
  const isNew = NEW_TIP_MARKER.test(tip);
342
- const tipText = plainLine(expandTipKeys(isNew ? tip.replace(NEW_TIP_MARKER, "") : tip));
384
+ const tipText = compactText(tipSpans(expandTipKeys(isNew ? tip.replace(NEW_TIP_MARKER, "") : tip)));
343
385
  const tipRow: NativeChild[] = [
344
386
  node("icon", { name: "lightbulb", role: "zeta.welcome.tip-icon" }),
345
387
  text(tipText, { wrap: "word", role: "zeta.welcome.tip-text" }),
@@ -587,7 +629,9 @@ export class WelcomeComponent implements Component {
587
629
  }
588
630
 
589
631
  // Randomly picked tip, rendered directly beneath the box.
590
- lines.push(...this.#renderTip(boxWidth));
632
+ // The banner centers every tip line, so the tip drops out entirely on
633
+ // narrow boxes rather than wrapping into unreadable slivers.
634
+ if (termWidth >= 16) lines.push(...this.#renderTip(boxWidth));
591
635
 
592
636
  return lines;
593
637
  }
@@ -680,34 +724,8 @@ export class WelcomeComponent implements Component {
680
724
  }
681
725
  }
682
726
 
683
- /**
684
- * {@link ZETA_LOGO} as SVG for the native welcome, on the terminal's grid: a
685
- * cell is 3×6 units, so the 12×5-cell art spans 36×30 from (14,16). The left
686
- * leg's `▒▒` tail is a half-opacity cell; the gradient spans the whole art in
687
- * user space (per-axis normalized, like {@link gradientLogo}) so the tail
688
- * keeps its colour. Tern mounts SVG blobs as live DOM, so the classes are
689
- * animation hooks: `trace` (the outline, `pathLength=1` for a draw-on), `mark`
690
- * (the fills) and the gradient stops `s0`–`s2`.
691
- */
692
- const WELCOME_LOGO_SVG = `<svg xmlns="http://www.w3.org/2000/svg" viewBox="10 12 44 38">
693
- <defs><linearGradient id="g" gradientUnits="userSpaceOnUse" gradientTransform="matrix(36 0 0 30 14 16)" x1="0" y1="0" x2="1" y2="1">
694
- <stop class="s0" offset="0" stop-color="#ed4abf"/><stop class="s1" offset=".5" stop-color="#9b4dff"/><stop class="s2" offset="1" stop-color="#5ad8e6"/>
695
- </linearGradient></defs>
696
- <path class="mark" fill="url(#g)" d="M14 16h36v6h-9v24h-6V22h-6v12h-6V22h-9z"/>
697
- <rect class="mark" fill="url(#g)" opacity=".5" x="23" y="34" width="6" height="6"/>
698
- <path class="trace" fill="none" stroke="url(#g)" stroke-width="1" stroke-linejoin="round" pathLength="1" d="M14 16h36v6h-9v24h-6V22h-6v18h-6V22h-9z"/>
699
- </svg>`;
700
-
701
- let welcomeLogoId: string | undefined;
702
-
703
- /** The registered blob id of {@link WELCOME_LOGO_SVG}. */
704
- function welcomeLogoBlob(): string {
705
- welcomeLogoId ??= registerNativeBlob(new TextEncoder().encode(WELCOME_LOGO_SVG), "image/svg+xml");
706
- return welcomeLogoId;
707
- }
708
-
709
727
  /** Block-grid brand mark shared by the welcome and setup surfaces. */
710
- export const ZETA_LOGO = ["███████╗", "╚════██║", " ██╔═╝", " ██╔═╝ ", "███████╗", "╚══════╝"];
728
+ export const ZETA_LOGO = ["█████████╗", "╚════████║", " ████╔═╝", " ████╔═╝ ", "█████████╗", "╚════════╝"];
711
729
 
712
730
  /** The block-grid brand mark as accent lines; `shimmer` declares the terminal-clocked shine sweep. */
713
731
  export function logoNode(lines: readonly string[], shimmer: boolean): NativeNode {
@@ -56,9 +56,20 @@ interface WordQuery extends WordCompletionQuery {
56
56
 
57
57
  /** Editor text before the word starting at `start`, truncated to its last {@link BEFORE_LIMIT} code units. */
58
58
  function textBefore(lines: readonly string[], cursorLine: number, start: number): string {
59
- let text = (lines[cursorLine] ?? "").slice(0, start);
60
- for (let line = cursorLine - 1; line >= 0 && text.length < BEFORE_LIMIT; line--) {
61
- text = `${lines[line] ?? ""}\n${text}`;
59
+ const head = (lines[cursorLine] ?? "").slice(0, start);
60
+ // Find the first line the capped tail reaches, then join once.
61
+ let length = head.length;
62
+ let first = cursorLine;
63
+ while (first > 0 && length < BEFORE_LIMIT) {
64
+ first--;
65
+ length += (lines[first] ?? "").length + 1;
66
+ }
67
+ let text = head;
68
+ if (first < cursorLine) {
69
+ const parts: string[] = [];
70
+ for (let line = first; line < cursorLine; line++) parts.push(lines[line] ?? "");
71
+ parts.push(head);
72
+ text = parts.join("\n");
62
73
  }
63
74
  if (text.length <= BEFORE_LIMIT) return text;
64
75
  let cut = text.length - BEFORE_LIMIT;
@@ -111,6 +122,16 @@ export class WordCompletionProvider implements EditorTextAssistProvider {
111
122
  #lastShown: { before: string; word: string } | undefined;
112
123
  /** What the last `getWordCompletion` returned, so a fetch repaints only when it changes that. */
113
124
  #displayed: { key: string; suffix: string | null } | undefined;
125
+ /** Last {@link #query} input snapshot (lines are copied: the editor mutates its array in place). */
126
+ #queryMemo:
127
+ | {
128
+ method: WordCompletionMethod;
129
+ lines: readonly string[];
130
+ cursorLine: number;
131
+ cursorCol: number;
132
+ query: WordQuery | undefined;
133
+ }
134
+ | undefined;
114
135
 
115
136
  /** Invoked when an asynchronous answer changes the rendered ghost text. */
116
137
  onUpdate: (() => void) | undefined;
@@ -171,11 +192,23 @@ export class WordCompletionProvider implements EditorTextAssistProvider {
171
192
 
172
193
  #query(lines: readonly string[], cursorLine: number, cursorCol: number): WordQuery | undefined {
173
194
  if (this.#method === "off") return undefined;
195
+ // Rendering asks every frame; reuse the answer until the buffer, caret, or engine changes.
196
+ const memo = this.#queryMemo;
197
+ if (
198
+ memo?.method === this.#method &&
199
+ memo.cursorLine === cursorLine &&
200
+ memo.cursorCol === cursorCol &&
201
+ memo.lines.length === lines.length &&
202
+ memo.lines.every((line, index) => line === lines[index])
203
+ ) {
204
+ return memo.query;
205
+ }
174
206
  const query = wordCompletionQuery(lines, cursorLine, cursorCol, this.#prose);
175
- if (!query) return undefined;
176
207
  // The method keeps a request still in flight for the previous engine from
177
208
  // shadowing the same word state on the new one.
178
- return { key: `${this.#method}\u0000${query.prefix}\u0000${query.before}`, ...query };
209
+ const result = query && { key: `${this.#method}\u0000${query.prefix}\u0000${query.before}`, ...query };
210
+ this.#queryMemo = { method: this.#method, lines: lines.slice(), cursorLine, cursorCol, query: result };
211
+ return result;
179
212
  }
180
213
 
181
214
  #project(query: WordQuery): string | null {
@@ -0,0 +1,2 @@
1
+ /** Splice overlay content into a base line at a specific column. Single-pass optimized. */
2
+ export declare function compositeLineAt(baseLine: string, overlayLine: string, startCol: number, overlayWidth: number, totalWidth: number): string;