@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
@@ -84,6 +84,7 @@ export interface RewindSelectorDeps {
84
84
  cwd: string;
85
85
  hideThinkingBlock?: () => boolean;
86
86
  proseOnlyThinking?: () => boolean;
87
+ expandThinkingBlocks?: () => boolean;
87
88
  linkTargets?: ReadonlyMap<string, string>;
88
89
  requestRender: () => void;
89
90
  /** Sibling branch paths of `entryId`'s turn (excluding the turn itself). */
@@ -157,6 +158,14 @@ const STRIP_GAP = 2;
157
158
  /** Duration of the branch-swap camera slide. */
158
159
  const SLIDE_MS = 160;
159
160
 
161
+ /** Same length and identical elements (or both undefined). */
162
+ function sameElements<T>(a: readonly T[] | undefined, b: readonly T[] | undefined): boolean {
163
+ if (a === b) return true;
164
+ if (a === undefined || b === undefined || a.length !== b.length) return false;
165
+ for (let i = 0; i < a.length; i++) if (a[i] !== b[i]) return false;
166
+ return true;
167
+ }
168
+
160
169
  export class RewindSelectorComponent implements Component {
161
170
  #builder: ChatTranscriptBuilder;
162
171
  #browser: TranscriptBrowser;
@@ -195,6 +204,24 @@ export class RewindSelectorComponent implements Component {
195
204
  #rowText = new WeakMap<readonly string[], string>();
196
205
  /** Native filter haystack: lowercased turn text per main target (no rendered rows under TSP). */
197
206
  #nativeTexts: { targets: OutlineTarget[]; texts: string[] } | undefined;
207
+ /** Compiled word patterns of the last filter query. */
208
+ #filterPatterns: { query: string; patterns: RegExp[] } | undefined;
209
+ /**
210
+ * Last {@link #filterMatches} result and its inputs. Frames hand back fresh
211
+ * outer `mainRows`/`mainVisible` arrays over cached per-child rows, so those
212
+ * compare element-wise.
213
+ */
214
+ #filterMemo:
215
+ | {
216
+ query: string;
217
+ targets: OutlineTarget[];
218
+ expanded: boolean;
219
+ native: boolean;
220
+ mainRows: readonly (readonly string[])[];
221
+ mainVisible: readonly boolean[] | undefined;
222
+ matches: readonly number[];
223
+ }
224
+ | undefined;
198
225
  /** Last described bar and the state it was built from. */
199
226
  #bar: { memo: string; targets: OutlineTarget[]; node: NativeNode } | undefined;
200
227
 
@@ -227,6 +254,7 @@ export class RewindSelectorComponent implements Component {
227
254
  cwd: this.deps.cwd,
228
255
  hideThinkingBlock: this.deps.hideThinkingBlock,
229
256
  proseOnlyThinking: this.deps.proseOnlyThinking,
257
+ expandThinkingBlocks: this.deps.expandThinkingBlocks,
230
258
  linkTargets: this.deps.linkTargets,
231
259
  requestRender: this.deps.requestRender,
232
260
  });
@@ -527,14 +555,25 @@ export class RewindSelectorComponent implements Component {
527
555
  * Matching the rendered rows keeps results honest: collapsed tool output
528
556
  * only matches once Ctrl+O expands it.
529
557
  */
530
- #filterMatches(): number[] {
531
- const words = (this.#filter ?? "").toLowerCase().split(/\s+/).filter(Boolean);
532
- const patterns = words.map(word =>
533
- /^[\p{Script=Latin}\p{N}_]+$/u.test(word)
534
- ? new RegExp(`(?<![\\p{L}\\p{N}_])${RegExp.escape(word)}(?![\\p{L}\\p{N}_])`, "u")
535
- : new RegExp(RegExp.escape(word), "u"),
536
- );
537
- if (isNativeRendering() && this.#nativeTexts?.targets !== this.#targets) {
558
+ #filterMatches(): readonly number[] {
559
+ const query = this.#filter ?? "";
560
+ const native = isNativeRendering();
561
+ const memo = this.#filterMemo;
562
+ if (
563
+ memo &&
564
+ memo.query === query &&
565
+ memo.targets === this.#targets &&
566
+ memo.expanded === this.#expanded &&
567
+ memo.native === native &&
568
+ sameElements(memo.mainVisible, this.#mainVisible) &&
569
+ (native || sameElements(memo.mainRows, this.#mainRows))
570
+ ) {
571
+ memo.mainRows = this.#mainRows;
572
+ memo.mainVisible = this.#mainVisible;
573
+ return memo.matches;
574
+ }
575
+ const patterns = this.#compileFilter(query);
576
+ if (native && this.#nativeTexts?.targets !== this.#targets) {
538
577
  this.#nativeTexts = {
539
578
  targets: this.#targets,
540
579
  // Turn text plus its commands and tool output, like the expanded rendered rows.
@@ -549,21 +588,53 @@ export class RewindSelectorComponent implements Component {
549
588
  for (let index = 0; index < this.#targets.length; index++) {
550
589
  if (!this.#isMainSelectable(index)) continue;
551
590
  const target = this.#targets[index]!;
552
- const texts = isNativeRendering()
553
- ? [this.#nativeTexts?.texts[index] ?? ""]
554
- : this.#mainRows.slice(target.start, target.end).map(rows => {
555
- let text = this.#rowText.get(rows);
556
- if (text === undefined) {
557
- text = Bun.stripANSI(rows.join("\n")).toLowerCase();
558
- this.#rowText.set(rows, text);
591
+ const matched = native
592
+ ? patterns.every(pattern => pattern.test(this.#nativeTexts?.texts[index] ?? ""))
593
+ : patterns.every(pattern => {
594
+ for (let child = target.start; child < target.end; child++) {
595
+ const rows = this.#mainRows[child];
596
+ if (rows !== undefined && pattern.test(this.#plainRowText(rows))) return true;
559
597
  }
560
- return text;
598
+ return false;
561
599
  });
562
- if (patterns.every(pattern => texts.some(text => pattern.test(text)))) matches.push(index);
600
+ if (matched) matches.push(index);
563
601
  }
602
+ this.#filterMemo = {
603
+ query,
604
+ targets: this.#targets,
605
+ expanded: this.#expanded,
606
+ native,
607
+ mainRows: this.#mainRows,
608
+ mainVisible: this.#mainVisible,
609
+ matches,
610
+ };
564
611
  return matches;
565
612
  }
566
613
 
614
+ /** Word patterns of `query`, compiled once per distinct query. */
615
+ #compileFilter(query: string): RegExp[] {
616
+ const cached = this.#filterPatterns;
617
+ if (cached?.query === query) return cached.patterns;
618
+ const words = query.toLowerCase().split(/\s+/).filter(Boolean);
619
+ const patterns = words.map(word =>
620
+ /^[\p{Script=Latin}\p{N}_]+$/u.test(word)
621
+ ? new RegExp(`(?<![\\p{L}\\p{N}_])${RegExp.escape(word)}(?![\\p{L}\\p{N}_])`, "u")
622
+ : new RegExp(RegExp.escape(word), "u"),
623
+ );
624
+ this.#filterPatterns = { query, patterns };
625
+ return patterns;
626
+ }
627
+
628
+ /** Lowercased plain text of one rendered child row array, cached by array identity. */
629
+ #plainRowText(rows: readonly string[]): string {
630
+ let text = this.#rowText.get(rows);
631
+ if (text === undefined) {
632
+ text = Bun.stripANSI(rows.join("\n")).toLowerCase();
633
+ this.#rowText.set(rows, text);
634
+ }
635
+ return text;
636
+ }
637
+
567
638
  /** Up/Down: step within the active column; leaving a sibling column's top exits the strip. */
568
639
  #moveVertical(delta: -1 | 1): void {
569
640
  if (this.#activeVariant > 0) {
@@ -58,6 +58,8 @@ export interface ObservableSession {
58
58
  lastUpdate: number;
59
59
  /** Latest progress snapshot from the subagent executor */
60
60
  progress?: AgentProgress;
61
+ /** `Date.now()` when {@link progress} arrived; its `durationMs` was current then. */
62
+ progressAt?: number;
61
63
  }
62
64
  /** Coarse source of an observer change; callers use it to separate lifecycle work from high-frequency progress. */
63
65
  export type SessionObserverChangeKind = "main" | "reset" | "lifecycle" | "progress";
@@ -64,6 +64,8 @@ export interface ObservableSession {
64
64
  lastUpdate: number;
65
65
  /** Latest progress snapshot from the subagent executor */
66
66
  progress?: AgentProgress;
67
+ /** `Date.now()` when {@link progress} arrived; its `durationMs` was current then. */
68
+ progressAt?: number;
67
69
  }
68
70
 
69
71
  /** Coarse source of an observer change; callers use it to separate lifecycle work from high-frequency progress. */
@@ -225,6 +227,9 @@ export class SessionObserverRegistry {
225
227
  existing.detached = payload.detached ?? existing.detached;
226
228
  if (payload.description) existing.description = payload.description;
227
229
  if (payload.sessionFile) existing.sessionFile = payload.sessionFile;
230
+ // A kept-alive or parked agent's next turn restarts its clock; the prior turn's
231
+ // snapshot must not be aged across the idle gap before the new turn's first frame.
232
+ if (payload.status === "started") existing.progressAt = undefined;
228
233
  } else {
229
234
  this.#sessions.set(payload.id, {
230
235
  id: payload.id,
@@ -256,12 +261,14 @@ export class SessionObserverRegistry {
256
261
 
257
262
  const sortOrder = this.#ensureSortOrder(id);
258
263
  this.#ensureParentSortOrder(payload.parentToolCallId, sortOrder);
264
+ const now = Date.now();
259
265
  if (existing) {
260
- existing.lastUpdate = Date.now();
266
+ existing.lastUpdate = now;
261
267
  existing.index = payload.index;
262
268
  existing.parentToolCallId = payload.parentToolCallId ?? existing.parentToolCallId;
263
269
  existing.detached = payload.detached ?? existing.detached;
264
270
  existing.progress = progress;
271
+ existing.progressAt = now;
265
272
  if (progress.description) existing.description = progress.description;
266
273
  if (payload.sessionFile) existing.sessionFile = payload.sessionFile;
267
274
  } else {
@@ -276,8 +283,9 @@ export class SessionObserverRegistry {
276
283
  parentToolCallId: payload.parentToolCallId,
277
284
  detached: payload.detached,
278
285
  index: payload.index,
279
- lastUpdate: Date.now(),
286
+ lastUpdate: now,
280
287
  progress,
288
+ progressAt: now,
281
289
  });
282
290
  }
283
291
  this.#notifyListeners("progress");
@@ -179,9 +179,13 @@ function sessionPreview(session: SessionSelectorEntry, forkedFrom: string | unde
179
179
  ]);
180
180
  }
181
181
 
182
+ /** Absolute dates of week-old sessions; `toLocaleDateString` goes through Intl on every call. */
183
+ const localeDateCache = new WeakMap<Date, { time: number; text: string }>();
184
+
182
185
  /** Relative age of a session's last modification (`"3 hours ago"`), falling back to the date after a week. */
183
186
  function formatSessionDate(date: Date): string {
184
- const diffMs = Date.now() - date.getTime();
187
+ const time = date.getTime();
188
+ const diffMs = Date.now() - time;
185
189
  const diffMins = Math.floor(diffMs / 60000);
186
190
  const diffHours = Math.floor(diffMs / 3600000);
187
191
  const diffDays = Math.floor(diffMs / 86400000);
@@ -192,7 +196,11 @@ function formatSessionDate(date: Date): string {
192
196
  if (diffDays === 1) return "1 day ago";
193
197
  if (diffDays < 7) return `${diffDays} days ago`;
194
198
 
195
- return date.toLocaleDateString();
199
+ const cached = localeDateCache.get(date);
200
+ if (cached?.time === time) return cached.text;
201
+ const text = date.toLocaleDateString();
202
+ localeDateCache.set(date, { time, text });
203
+ return text;
196
204
  }
197
205
 
198
206
  /** A cached native session item and the inputs it was built from. */
@@ -517,12 +525,12 @@ class SessionList<T extends SessionSelectorEntry> implements Component {
517
525
  readonly #getCurrentSessionPath: () => string | undefined;
518
526
  readonly #historyMatcher?: SessionHistoryMatcher;
519
527
  #historyMergeTimer: NodeJS.Timeout | undefined;
520
- /** Re-render hook for async list updates (fuzzy scan chunks, history merge). */
528
+ /** Re-render hook for async list updates (fuzzy scan completion, history merge). */
521
529
  onRequestRender?: () => void;
522
530
 
523
531
  // ── Incremental search state ──────────────────────────────────────────
524
532
  // The menu's visible list is always composed from these three inputs (see
525
- // #composeFiltered), so late-arriving fuzzy chunks and the debounced
533
+ // #composeFiltered), so a late-finishing fuzzy scan and the debounced
526
534
  // history merge can land in any order without clobbering each other.
527
535
  /** Recency-ranked sessions whose text contains every query token verbatim. */
528
536
  #literalRanked: RankedSessionMatch<T>[] = [];
@@ -535,8 +543,8 @@ class SessionList<T extends SessionSelectorEntry> implements Component {
535
543
  #scanTimer: NodeJS.Timeout | undefined;
536
544
  /**
537
545
  * True once the user moved the selection for the current query; blocks the
538
- * history merge from reordering the list under their cursor. (Fuzzy chunks
539
- * only append below the literal group, which never shifts existing rows.)
546
+ * history merge from reordering the list under their cursor. (The fuzzy scan
547
+ * only appends below the literal group, which never shifts existing rows.)
540
548
  */
541
549
  #selectionMoved = false;
542
550
  /** True after a nonempty query; empty refilter restores current only then. */
@@ -545,6 +553,8 @@ class SessionList<T extends SessionSelectorEntry> implements Component {
545
553
  #lastFilterQuery = "";
546
554
  /** Bumped whenever the visible session set may have changed (native memo key). */
547
555
  #itemsVersion = 0;
556
+ /** Per-row line heights of the visible list for the ANSI window, reused until the set changes. */
557
+ #rowHeights: { items: readonly T[]; length: number; version: number; heights: readonly number[] } | undefined;
548
558
  #itemsNative:
549
559
  | { version: number; showCwd: boolean; currentPath: string | undefined; items: NativeNode[] }
550
560
  | undefined;
@@ -677,38 +687,48 @@ class SessionList<T extends SessionSelectorEntry> implements Component {
677
687
  // Fuzzy pass: building a fuzzy index per session is too expensive to run
678
688
  // across a huge listing inside one keystroke, so scan a bounded slice now
679
689
  // and spill the remainder into async chunks.
680
- this.#scanFuzzySlice(this.#scanGeneration, tokens, rest, 0, FUZZY_SCAN_INLINE_COUNT);
690
+ const inlineEnd = Math.min(rest.length, FUZZY_SCAN_INLINE_COUNT);
691
+ this.#scoreFuzzy(tokens, rest, 0, inlineEnd);
681
692
  this.#composeFiltered();
693
+ if (inlineEnd < rest.length) {
694
+ this.#scheduleFuzzyScan(this.#scanGeneration, tokens, rest, inlineEnd, this.#fuzzyRanked.length);
695
+ }
682
696
  // New query rebuilds ranking from scratch. Same-query refilter (delete)
683
- // and async compose (fuzzy chunks / history merge) only clamp so an
697
+ // and async compose (fuzzy scan / history merge) only clamp so an
684
698
  // arrow selection survives. A live-session index > 0 would otherwise
685
699
  // land on a lower-ranked match after the first keystroke.
686
700
  if (queryChanged) this.#menu.setSelectedIndex(0);
687
701
  this.#scheduleHistoryMerge(query);
688
702
  }
689
703
 
690
- /**
691
- * Score up to `budget` sessions from `rest[start..]` (indexes into the
692
- * unfiltered list), then schedule the remainder on a macrotask so pending
693
- * input events run first. Chunks that added matches recompose the visible
694
- * list and request a render; a stale generation aborts silently.
695
- */
696
- #scanFuzzySlice(generation: number, tokens: string[], rest: number[], start: number, budget: number): void {
704
+ /** Fuzzy-score `rest[start..end)` (indexes into the unfiltered list) into {@link #fuzzyRanked}. */
705
+ #scoreFuzzy(tokens: string[], rest: number[], start: number, end: number): void {
697
706
  const all = this.#allSessions;
698
- const end = Math.min(rest.length, start + budget);
699
707
  for (let i = start; i < end; i++) {
700
708
  const index = rest[i]!;
701
709
  const session = all[index]!;
702
710
  const match = scoreFuzzySession(session, index, tokens, new FuzzyText(sessionTextLower(session)));
703
711
  if (match) this.#fuzzyRanked.push(match);
704
712
  }
705
- if (end >= rest.length) return;
713
+ }
714
+
715
+ /**
716
+ * Score `rest[start..]` in {@link FUZZY_SCAN_CHUNK_COUNT}-session macrotask
717
+ * chunks so pending input runs between them, then recompose the visible
718
+ * list once if the scan matched more than the `shown` fuzzy hits already
719
+ * composed. Publishing per chunk reorders the list dozens of times per
720
+ * keystroke on a large listing, and the native picker replays its
721
+ * row-arrival animation on every reorder. A stale generation aborts silently.
722
+ */
723
+ #scheduleFuzzyScan(generation: number, tokens: string[], rest: number[], start: number, shown: number): void {
706
724
  this.#scanTimer = setTimeout(() => {
707
725
  this.#scanTimer = undefined;
708
726
  if (generation !== this.#scanGeneration) return;
709
- const before = this.#fuzzyRanked.length;
710
- this.#scanFuzzySlice(generation, tokens, rest, end, FUZZY_SCAN_CHUNK_COUNT);
711
- if (this.#fuzzyRanked.length > before) {
727
+ const end = Math.min(rest.length, start + FUZZY_SCAN_CHUNK_COUNT);
728
+ this.#scoreFuzzy(tokens, rest, start, end);
729
+ if (end < rest.length) {
730
+ this.#scheduleFuzzyScan(generation, tokens, rest, end, shown);
731
+ } else if (this.#fuzzyRanked.length > shown) {
712
732
  this.#composeFiltered();
713
733
  this.onRequestRender?.();
714
734
  }
@@ -730,7 +750,7 @@ class SessionList<T extends SessionSelectorEntry> implements Component {
730
750
  tokenizeSessionQuery(this.#searchInput.getValue()),
731
751
  this.#literalRanked,
732
752
  );
733
- // Async chunks and the history merge only clamp the cursor so an arrow
753
+ // The fuzzy scan and the history merge only clamp the cursor so an arrow
734
754
  // selection survives recomposition; the query path resets explicitly.
735
755
  const keepIndex = Math.min(this.#menu.selectedIndex, Math.max(0, composed.length - 1));
736
756
  this.#menu.setItems(composed, keepIndex >= 0 ? composed[keepIndex]?.path : undefined);
@@ -1130,14 +1150,13 @@ class SessionList<T extends SessionSelectorEntry> implements Component {
1130
1150
  // worst-case count-based window would leave (then padded by
1131
1151
  // fill-height).
1132
1152
  const filtered = this.#menu.visibleItems;
1133
- const itemHeight = (session: T): number => (session.title ? 4 : 3);
1134
1153
  const budget = this.#lineBudget();
1135
1154
  const {
1136
1155
  startIndex,
1137
1156
  endIndex,
1138
1157
  rowOffset: offsetRows,
1139
1158
  totalRows: rawTotalRows,
1140
- } = getMenuWindow(filtered.map(itemHeight), this.#menu.selectedIndex, budget);
1159
+ } = getMenuWindow(this.#visibleRowHeights(filtered), this.#menu.selectedIndex, budget);
1141
1160
 
1142
1161
  // Each session block is built into sessionLines, then wrapped by ScrollView
1143
1162
  // so the right-edge scrollbar is proportional at the physical-line level.
@@ -1232,6 +1251,17 @@ class SessionList<T extends SessionSelectorEntry> implements Component {
1232
1251
  return lines;
1233
1252
  }
1234
1253
 
1254
+ /** Line height per visible session (3, or 4 when a title adds a preview line), memoized per visible set. */
1255
+ #visibleRowHeights(items: readonly T[]): readonly number[] {
1256
+ const cached = this.#rowHeights;
1257
+ if (cached?.items === items && cached.length === items.length && cached.version === this.#itemsVersion) {
1258
+ return cached.heights;
1259
+ }
1260
+ const heights = items.map(session => (session.title ? 4 : 3));
1261
+ this.#rowHeights = { items, length: items.length, version: this.#itemsVersion, heights };
1262
+ return heights;
1263
+ }
1264
+
1235
1265
  handleInput(keyData: string): void {
1236
1266
  // Delete key — or Backspace on an empty search query — request delete
1237
1267
  // confirmation from the parent. macOS laptops have no dedicated Forward
@@ -1310,7 +1340,11 @@ export interface SessionSelectorOptions<T extends SessionSelectorEntry = Session
1310
1340
  title?: string;
1311
1341
  /** Fixed scope label, or false to omit the scope suffix. */
1312
1342
  scopeLabel?: string | false;
1313
- /** Show each session's working directory in the list. */
1343
+ /**
1344
+ * Show each session's working directory in the list. Defaults to on when
1345
+ * the session files live in more than one directory (e.g. the folder scope
1346
+ * merging a repository's worktrees).
1347
+ */
1314
1348
  showCwd?: boolean;
1315
1349
  /**
1316
1350
  * Reads the live terminal height so the visible window fits the viewport.
@@ -1354,6 +1388,7 @@ export class SessionSelectorComponent<T extends SessionSelectorEntry = SessionSe
1354
1388
  #onRequestRender?: () => void;
1355
1389
  readonly #loadAllSessions?: () => Promise<T[]>;
1356
1390
  #folderSessions: T[];
1391
+ readonly #folderShowCwd: boolean;
1357
1392
  #globalSessions: T[] | null = null;
1358
1393
  #scope: "folder" | "all" = "folder";
1359
1394
  #toggling = false;
@@ -1420,6 +1455,9 @@ export class SessionSelectorComponent<T extends SessionSelectorEntry = SessionSe
1420
1455
  this.#onDelete = options.onDelete;
1421
1456
  this.#loadAllSessions = options.loadAllSessions;
1422
1457
  this.#folderSessions = sessions;
1458
+ // Storage directory, not recorded cwd: one folder's sessions may record
1459
+ // symlink aliases of the same path, which must not turn the column on.
1460
+ this.#folderShowCwd = options.showCwd ?? new Set(sessions.map(session => path.dirname(session.path))).size > 1;
1423
1461
  this.#globalSessions = options.allSessions ?? null;
1424
1462
  this.#getTerminalRows = options.getTerminalRows ?? (() => 24);
1425
1463
  this.#fillHeight = options.fillHeight ?? false;
@@ -1437,7 +1475,7 @@ export class SessionSelectorComponent<T extends SessionSelectorEntry = SessionSe
1437
1475
  // projects' history (issue #3099).
1438
1476
  this.#sessionList = new SessionList(
1439
1477
  sessions,
1440
- options.showCwd ?? false,
1478
+ this.#folderShowCwd,
1441
1479
  options.historyMatcher,
1442
1480
  options.getTerminalRows,
1443
1481
  options.pinnedIds,
@@ -1510,7 +1548,7 @@ export class SessionSelectorComponent<T extends SessionSelectorEntry = SessionSe
1510
1548
  this.#sessionList.setSessions(global, true);
1511
1549
  } else {
1512
1550
  this.#scope = "folder";
1513
- this.#sessionList.setSessions(this.#folderSessions, false);
1551
+ this.#sessionList.setSessions(this.#folderSessions, this.#folderShowCwd);
1514
1552
  }
1515
1553
  this.title = this.#headerLabel();
1516
1554
  this.#onRequestRender?.();
@@ -133,7 +133,7 @@ export const TAB_GROUPS: Record<SettingTab, readonly string[]> = {
133
133
  ],
134
134
  context: ["General", "Compaction", "Zeta Context Cache", "Rules (TTSR)", "Experimental"],
135
135
  memory: ["General", "Auto-Learn", "Mnemopi", "Hindsight", "Sharpshooter"],
136
- files: ["Editing", "Reading", "Read Summaries", "LSP"],
136
+ files: ["Editing", "Reading", "Read Summaries", "LSP", "Session Trash"],
137
137
  shell: ["Bash", "Eval & Runtimes"],
138
138
  tools: [
139
139
  "Available Tools",
@@ -180,6 +180,7 @@ export type AnyUiMetadata = UiBase & {
180
180
  options?: ReadonlyArray<SubmenuOption> | "runtime";
181
181
  secret?: boolean;
182
182
  ordered?: boolean;
183
+ editor?: "json";
183
184
  };
184
185
 
185
186
  /** Structural schema entries supplied by the application host. */
@@ -330,9 +331,9 @@ function entryToSettingDef(entry: SettingsDisplayEntry): SettingDef | null {
330
331
  }
331
332
 
332
333
  if (schemaType === "array") {
333
- // Arrays without declared options stay config-file only (free-form lists
334
- // like extension paths have no finite choice set to toggle).
335
- if (!options || options === "runtime") return null;
334
+ if (ui.editor === "json") return { ...base, type: "text", secret: entry.credential === true };
335
+ if (!options) return null;
336
+ if (options === "runtime") return null;
336
337
  return { ...base, type: "multiselect", options, ordered: ui.ordered === true };
337
338
  }
338
339
 
@@ -1,10 +1,11 @@
1
1
  import type { ThinkingLevel } from "@linxiraos/pi-agent-core";
2
2
  import type { Effort } from "@linxiraos/pi-ai";
3
+ import { CLI_BIN_NAME } from "@linxiraos/pi-utils";
3
4
  import {
4
5
  type Component,
5
6
  Container,
6
7
  extractPrintableText,
7
- fuzzyRank,
8
+ FuzzyCorpus,
8
9
  getKeybindings,
9
10
  getSettingItemFilterText,
10
11
  type ImageBudget,
@@ -630,6 +631,12 @@ export class SettingsSelectorComponent implements Component {
630
631
  #searchMatchCount = 0;
631
632
  /** First matching item id per tab id, for Tab-key jumps while searching. */
632
633
  #searchFirstMatch = new Map<string, string>();
634
+ /**
635
+ * Per-tab search candidates prepared once per search session. Dropped on
636
+ * search start/end and whenever a value or condition may have changed
637
+ * (`#refreshItems`, `#onSearchSettingChange`); rebuilt lazily by `#setSearchQuery`.
638
+ */
639
+ #searchCorpora: { tab: SettingTab; corpus: FuzzyCorpus<SettingItem> }[] | null = null;
633
640
  #textInputActive = false;
634
641
  #hasSectionJump = false;
635
642
  // Frame geometry from the last render, for mouse hit-testing (the
@@ -872,7 +879,7 @@ export class SettingsSelectorComponent implements Component {
872
879
  }
873
880
 
874
881
  const props: TspPrefsProps = {
875
- title: "omp settings",
882
+ title: `${CLI_BIN_NAME} settings`,
876
883
  pages,
877
884
  page: searching ? this.#preSearchTabId : this.#currentTabId,
878
885
  lead: searching ? undefined : (pluginPage?.lead ?? (tab ? TAB_LEADS[tab] : undefined)),
@@ -1111,8 +1118,10 @@ export class SettingsSelectorComponent implements Component {
1111
1118
 
1112
1119
  /** Rebuilds the visible items after a change made outside a list's own `onChange` (changed flags, conditions). */
1113
1120
  #refreshItems(): void {
1114
- if (this.#searchList) this.#setSearchQuery(this.#searchQuery);
1115
- else if (this.#currentTabId !== "plugins")
1121
+ if (this.#searchList) {
1122
+ this.#searchCorpora = null;
1123
+ this.#setSearchQuery(this.#searchQuery);
1124
+ } else if (this.#currentTabId !== "plugins")
1116
1125
  this.#refreshCurrentTabItems(getSettingsForTab(this.#context.settings.entries, this.#currentTabId));
1117
1126
  }
1118
1127
 
@@ -1281,6 +1290,7 @@ export class SettingsSelectorComponent implements Component {
1281
1290
  /** Swap the tab content for the global search result list. */
1282
1291
  #startSearch(initialQuery: string): void {
1283
1292
  this.#preSearchTabId = this.#currentTabId;
1293
+ this.#searchCorpora = null;
1284
1294
  this.#searchInput = new Input();
1285
1295
  this.#searchInput.prompt = "";
1286
1296
  this.#searchInput.setValue(initialQuery);
@@ -1324,13 +1334,17 @@ export class SettingsSelectorComponent implements Component {
1324
1334
  const tabResults: { tab: SettingTab; matched: SettingItem[]; bestScore: number; order: number }[] = [];
1325
1335
  this.#searchFirstMatch.clear();
1326
1336
  let total = 0;
1327
- for (const tab of SETTING_TABS) {
1337
+ this.#searchCorpora ??= SETTING_TABS.map(tab => {
1328
1338
  const candidates: SettingItem[] = [];
1329
1339
  for (const def of getSettingsForTab(this.#context.settings.entries, tab)) {
1330
1340
  const item = this.#defToItem(def);
1331
1341
  if (item) candidates.push(item);
1332
1342
  }
1333
- const ranked = fuzzyRank(candidates, query, getSettingItemFilterText);
1343
+ return { tab, corpus: new FuzzyCorpus(candidates, getSettingItemFilterText) };
1344
+ });
1345
+ for (let order = 0; order < this.#searchCorpora.length; order++) {
1346
+ const { tab, corpus } = this.#searchCorpora[order]!;
1347
+ const ranked = corpus.rank(query);
1334
1348
  const matched = ranked.map(result => result.item);
1335
1349
  counts.set(tab, matched.length);
1336
1350
  if (matched.length === 0) continue;
@@ -1339,7 +1353,7 @@ export class SettingsSelectorComponent implements Component {
1339
1353
  tab,
1340
1354
  matched,
1341
1355
  bestScore: ranked[0]?.score ?? 0,
1342
- order: SETTING_TABS.indexOf(tab),
1356
+ order,
1343
1357
  });
1344
1358
  }
1345
1359
 
@@ -1378,6 +1392,7 @@ export class SettingsSelectorComponent implements Component {
1378
1392
  const targetTab: SettingTab | "plugins" = selectedDef?.tab ?? this.#preSearchTabId;
1379
1393
 
1380
1394
  this.#searchQuery = "";
1395
+ this.#searchCorpora = null;
1381
1396
  this.#searchFirstMatch.clear();
1382
1397
  this.#searchMatchCount = 0;
1383
1398
  this.#tabs = getSettingsTabs();
@@ -1441,6 +1456,7 @@ export class SettingsSelectorComponent implements Component {
1441
1456
  }
1442
1457
  // Values feed the searchable text and condition gates may have flipped:
1443
1458
  // recompute results in place (selection is preserved by item id).
1459
+ this.#searchCorpora = null;
1444
1460
  this.#setSearchQuery(this.#searchQuery);
1445
1461
  }
1446
1462
 
@@ -1648,7 +1664,7 @@ export class SettingsSelectorComponent implements Component {
1648
1664
  }
1649
1665
 
1650
1666
  /**
1651
- * Create a text input submenu for a plain string setting.
1667
+ * Create a text input submenu for a string or structured setting.
1652
1668
  */
1653
1669
  #createTextInput(
1654
1670
  def: SettingDef & { type: "text" },
@@ -1751,6 +1767,15 @@ export class SettingsSelectorComponent implements Component {
1751
1767
  this.#context.settings.set(path, -1);
1752
1768
  } else if (path === "compaction.thresholdTokens" && value === "default") {
1753
1769
  this.#context.settings.set(path, -1);
1770
+ } else if (schemaType === "array") {
1771
+ let parsed: unknown;
1772
+ try {
1773
+ parsed = JSON.parse(value);
1774
+ } catch {
1775
+ throw new Error(`Invalid array JSON for ${path}`);
1776
+ }
1777
+ if (!Array.isArray(parsed)) throw new Error(`Invalid array JSON for ${path}`);
1778
+ this.#context.settings.set(path, parsed);
1754
1779
  } else if (schemaType === "record") {
1755
1780
  let parsed: unknown;
1756
1781
  try {
@@ -31,7 +31,7 @@ import { theme } from "../theme/theme";
31
31
  import sampleDoc from "./snapcompact-shape-preview-doc.md" with { type: "text" };
32
32
  import type { DescribeContext, NativeNode } from "../native/node";
33
33
  import { col, node, span, text } from "../native/describe";
34
- import { registerNativeBlob } from "../native/blobs";
34
+ import { nativeImageNode } from "../native/blobs";
35
35
 
36
36
  /** Mini-frame edge in px — a small page from the real rasterizer ≈ a zoomed crop. */
37
37
  const SRC_FRAME_PX = 128;
@@ -100,8 +100,7 @@ export class SnapcompactShapePreview implements Component {
100
100
  else if (entry.state === "rendering") sample = node("spinner", { label: [span("rendering sample…", "dim")] });
101
101
  else if (entry.state === "failed") sample = text([span("(sample render failed)", "dim")]);
102
102
  else {
103
- sample = node("image", {
104
- blob: registerNativeBlob(entry.bytes, "image/png"),
103
+ sample = nativeImageNode(entry.bytes, "image/png", {
105
104
  alt: `snapcompact ${label} sample`,
106
105
  w: entry.edgePx,
107
106
  h: entry.edgePx,
@@ -1,5 +1,5 @@
1
1
  /**
2
- * `/stats` result: omp serves the stats dashboard in the browser. ANSI keeps
2
+ * `/stats` result: zetacode serves the stats dashboard in the browser. ANSI keeps
3
3
  * the dim status line; natively it is an inline notice with an
4
4
  * `Open dashboard ↗` button that opens the URL terminal-side.
5
5
  */
@@ -1,8 +1,8 @@
1
+ import { AUTO_THINKING, type ConfiguredThinkingLevel, getConfiguredThinkingLevelMetadata } from "../thinking";
1
2
  import type { Effort } from "@linxiraos/pi-ai";
2
3
  import { tuiText } from "../i18n";
3
4
  import { type SelectItem, SelectList, type SgrMouseEvent } from "../index";
4
5
  import { getSelectListTheme } from "../theme/theme";
5
- import { getThinkingLevelMetadata } from "../thinking";
6
6
  import { OverlayPanel } from "../chrome/overlay-box";
7
7
  import { routeSelectListMouseWithTopBorder } from "../chrome/select-list-mouse-routing";
8
8
  import type { DescribeContext, NativeNode, NativeUiEvent } from "../native/node";
@@ -16,14 +16,14 @@ export class ThinkingSelectorComponent extends OverlayPanel {
16
16
  #sheet: SelectListSheet;
17
17
 
18
18
  constructor(
19
- currentLevel: Effort,
20
- availableLevels: Effort[],
21
- onSelect: (level: Effort) => void,
19
+ currentLevel: ConfiguredThinkingLevel | undefined,
20
+ availableLevels: ConfiguredThinkingLevel[],
21
+ onSelect: (level: ConfiguredThinkingLevel) => void,
22
22
  onCancel: () => void,
23
23
  ) {
24
24
  super(tuiText("thinkingSelectorTitle", "Thinking Level"), "zeta.overlay.thinking");
25
25
 
26
- const thinkingLevels: SelectItem[] = availableLevels.map(getThinkingLevelMetadata);
26
+ const thinkingLevels: SelectItem[] = availableLevels.map(getConfiguredThinkingLevelMetadata);
27
27
 
28
28
  // Create selector
29
29
  this.#selectList = new SelectList(thinkingLevels, thinkingLevels.length, getSelectListTheme());
@@ -35,7 +35,7 @@ export class ThinkingSelectorComponent extends OverlayPanel {
35
35
  }
36
36
 
37
37
  this.#selectList.onSelect = item => {
38
- onSelect(item.value as Effort);
38
+ onSelect(item.value as ConfiguredThinkingLevel);
39
39
  };
40
40
 
41
41
  this.#selectList.onCancel = () => {
@@ -47,10 +47,11 @@ export class ThinkingSelectorComponent extends OverlayPanel {
47
47
  title: "Thinking level",
48
48
  icon: "brain",
49
49
  noun: "levels",
50
- current: [currentLevel],
51
- decorate: item => ({
52
- chips: [{ text: "", dot: `thinking${item.value.charAt(0).toUpperCase()}${item.value.slice(1)}` }],
53
- }),
50
+ current: currentLevel ? [currentLevel] : [],
51
+ decorate: item =>
52
+ item.value === AUTO_THINKING
53
+ ? {}
54
+ : { chips: [{ text: "", dot: `thinking${item.value.charAt(0).toUpperCase()}${item.value.slice(1)}` }] },
54
55
  });
55
56
  }
56
57