@oh-my-pi/pi-tui 18.4.2 → 18.4.4

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 (463) hide show
  1. package/CHANGELOG.md +56 -0
  2. package/dist/types/apps/cleanse-board.d.ts +17 -0
  3. package/dist/types/apps/git/help.d.ts +16 -0
  4. package/dist/types/apps/git/sidebar.d.ts +18 -0
  5. package/dist/types/apps/live-visualizer.d.ts +10 -0
  6. package/dist/types/apps/ps-top.d.ts +15 -0
  7. package/dist/types/apps/standalone-picker.d.ts +3 -0
  8. package/dist/types/autocomplete.d.ts +8 -0
  9. package/dist/types/chat/advisor-message.d.ts +12 -2
  10. package/dist/types/chat/assistant-message.d.ts +20 -0
  11. package/dist/types/chat/bash-execution.d.ts +9 -0
  12. package/dist/types/chat/chat-transcript-builder.d.ts +6 -1
  13. package/dist/types/chat/collab-prompt-message.d.ts +4 -0
  14. package/dist/types/chat/compaction-summary-message.d.ts +15 -0
  15. package/dist/types/chat/eval-execution.d.ts +8 -0
  16. package/dist/types/chat/execution-shared.d.ts +58 -0
  17. package/dist/types/chat/late-diagnostics-message.d.ts +15 -1
  18. package/dist/types/chat/read-tool-group.d.ts +15 -0
  19. package/dist/types/chat/skill-message.d.ts +9 -0
  20. package/dist/types/chat/stripped-tool-calls-placeholder.d.ts +3 -0
  21. package/dist/types/chat/thinking-display.d.ts +2 -0
  22. package/dist/types/chat/todo-reminder.d.ts +4 -1
  23. package/dist/types/chat/tool-execution.d.ts +25 -1
  24. package/dist/types/chat/transcript-actions.d.ts +37 -0
  25. package/dist/types/chat/transcript-entry.d.ts +3 -1
  26. package/dist/types/chat/user-message.d.ts +25 -0
  27. package/dist/types/chrome/collab-qrcode.d.ts +7 -0
  28. package/dist/types/chrome/context-thresholds.d.ts +11 -0
  29. package/dist/types/chrome/countdown-timer.d.ts +9 -3
  30. package/dist/types/chrome/diff.d.ts +12 -0
  31. package/dist/types/chrome/dynamic-border.d.ts +3 -0
  32. package/dist/types/chrome/format.d.ts +6 -0
  33. package/dist/types/chrome/message-divider.d.ts +13 -0
  34. package/dist/types/chrome/message-frame.d.ts +10 -0
  35. package/dist/types/chrome/message-notice.d.ts +23 -0
  36. package/dist/types/chrome/overlay-box.d.ts +11 -2
  37. package/dist/types/chrome/qrcode.d.ts +7 -0
  38. package/dist/types/chrome/segment-track.d.ts +7 -0
  39. package/dist/types/chrome/selector-helpers.d.ts +0 -6
  40. package/dist/types/chrome/status-notice.d.ts +14 -0
  41. package/dist/types/chrome/streaming-panel.d.ts +3 -0
  42. package/dist/types/chrome/tool-activity.d.ts +3 -0
  43. package/dist/types/chrome/transcript-container.d.ts +13 -0
  44. package/dist/types/chrome/transcript-status.d.ts +15 -3
  45. package/dist/types/components/box.d.ts +7 -0
  46. package/dist/types/components/disclosure.d.ts +9 -0
  47. package/dist/types/components/editor.d.ts +42 -0
  48. package/dist/types/components/form.d.ts +14 -0
  49. package/dist/types/components/image.d.ts +7 -0
  50. package/dist/types/components/input.d.ts +20 -1
  51. package/dist/types/components/key-value-list.d.ts +3 -0
  52. package/dist/types/components/layout/geometry.d.ts +16 -0
  53. package/dist/types/components/layout/row.d.ts +7 -0
  54. package/dist/types/components/layout/split-pane.d.ts +8 -0
  55. package/dist/types/components/layout/stack.d.ts +6 -0
  56. package/dist/types/components/loader.d.ts +48 -0
  57. package/dist/types/components/markdown.d.ts +8 -0
  58. package/dist/types/components/metric.d.ts +7 -0
  59. package/dist/types/components/progress-bar.d.ts +3 -0
  60. package/dist/types/components/scroll-view.d.ts +6 -0
  61. package/dist/types/components/section.d.ts +3 -0
  62. package/dist/types/components/select-list.d.ts +33 -0
  63. package/dist/types/components/settings-list.d.ts +68 -0
  64. package/dist/types/components/spacer.d.ts +4 -0
  65. package/dist/types/components/tab-bar.d.ts +9 -0
  66. package/dist/types/components/table.d.ts +7 -0
  67. package/dist/types/components/text.d.ts +6 -0
  68. package/dist/types/components/tree-view.d.ts +9 -0
  69. package/dist/types/components/truncated-text.d.ts +3 -0
  70. package/dist/types/components/wizard-step.d.ts +6 -0
  71. package/dist/types/hotkeys-markdown.d.ts +24 -0
  72. package/dist/types/index.d.ts +5 -0
  73. package/dist/types/latex-block.d.ts +7 -0
  74. package/dist/types/loop-watchdog.d.ts +9 -0
  75. package/dist/types/native/apply.d.ts +39 -0
  76. package/dist/types/native/backend.d.ts +94 -0
  77. package/dist/types/native/blobs.d.ts +29 -0
  78. package/dist/types/native/describe.d.ts +56 -0
  79. package/dist/types/native/encode.d.ts +50 -0
  80. package/dist/types/native/icons.d.ts +18 -0
  81. package/dist/types/native/memo.d.ts +27 -0
  82. package/dist/types/native/node.d.ts +107 -0
  83. package/dist/types/native/overlay.d.ts +64 -0
  84. package/dist/types/native/picker.d.ts +124 -0
  85. package/dist/types/native/reconcile.d.ts +41 -0
  86. package/dist/types/native/settle.d.ts +11 -0
  87. package/dist/types/native/spans.d.ts +21 -0
  88. package/dist/types/native/state.d.ts +6 -0
  89. package/dist/types/native/tone.d.ts +24 -0
  90. package/dist/types/overlays/advisor-config.d.ts +20 -0
  91. package/dist/types/overlays/agent-hub-renderer.d.ts +27 -11
  92. package/dist/types/overlays/agent-hub.d.ts +10 -0
  93. package/dist/types/overlays/agent-transcript-viewer.d.ts +7 -0
  94. package/dist/types/overlays/agents-hub.d.ts +8 -0
  95. package/dist/types/overlays/annotation-overlay.d.ts +3 -0
  96. package/dist/types/overlays/ask-dialog.d.ts +23 -0
  97. package/dist/types/overlays/bordered-loader.d.ts +3 -0
  98. package/dist/types/overlays/btw-history-panel.d.ts +9 -0
  99. package/dist/types/overlays/btw-panel.d.ts +3 -0
  100. package/dist/types/overlays/cleanse-panel.d.ts +5 -2
  101. package/dist/types/overlays/composer-shape-preview.d.ts +7 -1
  102. package/dist/types/overlays/copy-selector.d.ts +38 -0
  103. package/dist/types/overlays/error-banner.d.ts +9 -0
  104. package/dist/types/overlays/extensions/extension-list.d.ts +1 -0
  105. package/dist/types/overlays/history-search.d.ts +18 -0
  106. package/dist/types/overlays/hook-editor.d.ts +28 -1
  107. package/dist/types/overlays/hook-input.d.ts +3 -0
  108. package/dist/types/overlays/hook-selector.d.ts +10 -0
  109. package/dist/types/overlays/hub-frame.d.ts +18 -0
  110. package/dist/types/overlays/jobs-panel.d.ts +24 -0
  111. package/dist/types/overlays/login-dialog.d.ts +10 -0
  112. package/dist/types/overlays/logout-account-selector.d.ts +4 -0
  113. package/dist/types/overlays/mcp-add-wizard.d.ts +4 -0
  114. package/dist/types/overlays/model-browser.d.ts +69 -0
  115. package/dist/types/overlays/model-hub.d.ts +7 -0
  116. package/dist/types/overlays/model-picker.d.ts +9 -1
  117. package/dist/types/overlays/move-overlay.d.ts +3 -0
  118. package/dist/types/overlays/oauth-selector.d.ts +4 -0
  119. package/dist/types/overlays/omfg-panel.d.ts +3 -0
  120. package/dist/types/overlays/pause-screen.d.ts +12 -0
  121. package/dist/types/overlays/plan-review-overlay.d.ts +5 -0
  122. package/dist/types/overlays/plan-save-overlay.d.ts +10 -0
  123. package/dist/types/overlays/plugin-selector.d.ts +4 -0
  124. package/dist/types/overlays/plugin-settings.d.ts +30 -0
  125. package/dist/types/overlays/queue-mode-selector.d.ts +4 -0
  126. package/dist/types/overlays/reset-usage-selector.d.ts +8 -0
  127. package/dist/types/overlays/rewind-selector.d.ts +5 -0
  128. package/dist/types/overlays/session-info-overlay.d.ts +19 -1
  129. package/dist/types/overlays/session-selector.d.ts +64 -1
  130. package/dist/types/overlays/settings-defs.d.ts +14 -0
  131. package/dist/types/overlays/settings-selector.d.ts +21 -0
  132. package/dist/types/overlays/show-images-selector.d.ts +4 -0
  133. package/dist/types/overlays/snapcompact-shape-preview.d.ts +2 -0
  134. package/dist/types/overlays/stats-notice.d.ts +8 -0
  135. package/dist/types/overlays/theme-selector.d.ts +11 -0
  136. package/dist/types/overlays/thinking-selector.d.ts +4 -0
  137. package/dist/types/overlays/tree-selector.d.ts +60 -2
  138. package/dist/types/overlays/usage-dashboard.d.ts +26 -3
  139. package/dist/types/overlays/usage-row.d.ts +36 -2
  140. package/dist/types/prompt/attachment-chips.d.ts +8 -0
  141. package/dist/types/prompt/composer-attachments.d.ts +15 -3
  142. package/dist/types/prompt/composer.d.ts +18 -1
  143. package/dist/types/prompt/custom-editor.d.ts +48 -2
  144. package/dist/types/prompt/editor-top-gap.d.ts +2 -0
  145. package/dist/types/prompt/macos-spelling.d.ts +6 -0
  146. package/dist/types/prompt/magic-keywords.d.ts +9 -0
  147. package/dist/types/prompt/queued-messages.d.ts +23 -0
  148. package/dist/types/prompt/skill-tokens.d.ts +4 -2
  149. package/dist/types/prompt/welcome.d.ts +16 -0
  150. package/dist/types/render/code-cell.d.ts +10 -0
  151. package/dist/types/render/file-list.d.ts +8 -0
  152. package/dist/types/render/hyperlink.d.ts +14 -0
  153. package/dist/types/render/output-block.d.ts +43 -0
  154. package/dist/types/render/output-pane.d.ts +17 -0
  155. package/dist/types/render/tool-card.d.ts +8 -0
  156. package/dist/types/render/tree-list.d.ts +8 -0
  157. package/dist/types/render/width-aware-text.d.ts +7 -0
  158. package/dist/types/setup/scenes/outro.d.ts +7 -0
  159. package/dist/types/setup/scenes/sign-in.d.ts +16 -9
  160. package/dist/types/setup/scenes/splash.d.ts +8 -0
  161. package/dist/types/setup/scenes/types.d.ts +6 -27
  162. package/dist/types/setup/wizard-overlay.d.ts +9 -0
  163. package/dist/types/status-line/component.d.ts +49 -5
  164. package/dist/types/status-line/context-usage.d.ts +20 -0
  165. package/dist/types/status-line/footer.d.ts +8 -0
  166. package/dist/types/status-line/metrics.d.ts +1 -2
  167. package/dist/types/status-line/segments.d.ts +10 -1
  168. package/dist/types/status-line/types.d.ts +40 -1
  169. package/dist/types/stdin-buffer.d.ts +10 -7
  170. package/dist/types/terminal-capabilities.d.ts +1 -1
  171. package/dist/types/terminal.d.ts +36 -0
  172. package/dist/types/theme/defaults/index.d.ts +100 -0
  173. package/dist/types/theme/schema.d.ts +2 -0
  174. package/dist/types/theme/shimmer.d.ts +8 -0
  175. package/dist/types/theme/theme-class.d.ts +2 -0
  176. package/dist/types/theme/theme.d.ts +33 -1
  177. package/dist/types/tools/ask.d.ts +3 -0
  178. package/dist/types/tools/ast-edit.d.ts +10 -1
  179. package/dist/types/tools/ast-grep.d.ts +10 -1
  180. package/dist/types/tools/autoresearch.d.ts +21 -4
  181. package/dist/types/tools/bash.d.ts +30 -1
  182. package/dist/types/tools/cfg-render.d.ts +5 -1
  183. package/dist/types/tools/debug.d.ts +3 -1
  184. package/dist/types/tools/default-renderer.d.ts +6 -1
  185. package/dist/types/tools/edit.d.ts +14 -1
  186. package/dist/types/tools/eval.d.ts +12 -1
  187. package/dist/types/tools/fetch.d.ts +16 -1
  188. package/dist/types/tools/find.d.ts +15 -1
  189. package/dist/types/tools/github.d.ts +3 -0
  190. package/dist/types/tools/glob.d.ts +14 -1
  191. package/dist/types/tools/goal.d.ts +3 -1
  192. package/dist/types/tools/grep.d.ts +14 -1
  193. package/dist/types/tools/grouped-file-output.d.ts +58 -0
  194. package/dist/types/tools/json-tree.d.ts +20 -0
  195. package/dist/types/tools/lsp.d.ts +3 -10
  196. package/dist/types/tools/mcp.d.ts +16 -1
  197. package/dist/types/tools/memory.d.ts +26 -0
  198. package/dist/types/tools/native-view.d.ts +90 -0
  199. package/dist/types/tools/proc-render.d.ts +7 -1
  200. package/dist/types/tools/read.d.ts +23 -1
  201. package/dist/types/tools/renderer.d.ts +57 -0
  202. package/dist/types/tools/report-tool-issue.d.ts +3 -0
  203. package/dist/types/tools/resolve.d.ts +14 -1
  204. package/dist/types/tools/task.d.ts +3 -0
  205. package/dist/types/tools/think.d.ts +3 -0
  206. package/dist/types/tools/todo.d.ts +12 -0
  207. package/dist/types/tools/vibe.d.ts +3 -1
  208. package/dist/types/tools/wait.d.ts +3 -0
  209. package/dist/types/tools/web-search-types.d.ts +90 -0
  210. package/dist/types/tools/web-search.d.ts +9 -168
  211. package/dist/types/tools/write.d.ts +14 -1
  212. package/dist/types/tools/xdev.d.ts +13 -0
  213. package/dist/types/tui.d.ts +49 -0
  214. package/package.json +9 -9
  215. package/src/apps/autoresearch-dashboard.ts +362 -4
  216. package/src/apps/cleanse-board.ts +219 -1
  217. package/src/apps/cleanse-picker.ts +9 -1
  218. package/src/apps/debug/log-viewer.ts +15 -15
  219. package/src/apps/debug/protocol-probe.ts +2 -20
  220. package/src/apps/git/avatar.ts +2 -22
  221. package/src/apps/git/git-tui.ts +406 -59
  222. package/src/apps/git/help.ts +130 -0
  223. package/src/apps/git/sidebar.ts +491 -0
  224. package/src/apps/live-visualizer.ts +65 -0
  225. package/src/apps/ps-top.ts +448 -17
  226. package/src/apps/session-picker.ts +1 -0
  227. package/src/apps/setup-model-picker.ts +5 -1
  228. package/src/apps/standalone-picker.ts +55 -1
  229. package/src/autocomplete.ts +44 -1
  230. package/src/chat/advisor-message.ts +72 -3
  231. package/src/chat/assistant-message.ts +538 -51
  232. package/src/chat/background-tan-message.ts +4 -4
  233. package/src/chat/bash-execution.ts +81 -0
  234. package/src/chat/cache-invalidation-marker.ts +8 -0
  235. package/src/chat/chat-transcript-builder.ts +37 -6
  236. package/src/chat/collab-prompt-message.ts +15 -3
  237. package/src/chat/compaction-summary-message.ts +74 -1
  238. package/src/chat/custom-message.ts +3 -1
  239. package/src/chat/eval-execution.ts +57 -0
  240. package/src/chat/execution-shared.ts +153 -1
  241. package/src/chat/hook-message.ts +1 -0
  242. package/src/chat/late-diagnostics-message.ts +85 -2
  243. package/src/chat/read-tool-group.ts +341 -1
  244. package/src/chat/served-model-marker.ts +8 -0
  245. package/src/chat/skill-message.ts +63 -0
  246. package/src/chat/stripped-tool-calls-placeholder.ts +18 -10
  247. package/src/chat/thinking-display.ts +73 -11
  248. package/src/chat/todo-reminder.ts +34 -4
  249. package/src/chat/tool-execution.ts +385 -10
  250. package/src/chat/transcript-actions.ts +38 -0
  251. package/src/chat/transcript-browser.ts +2 -9
  252. package/src/chat/transcript-entry.ts +10 -1
  253. package/src/chat/transcript-render-helpers.ts +16 -15
  254. package/src/chat/ttsr-notification.ts +38 -1
  255. package/src/chat/user-message.ts +167 -4
  256. package/src/chrome/collab-qrcode.ts +27 -2
  257. package/src/chrome/context-thresholds.ts +34 -0
  258. package/src/chrome/countdown-timer.ts +24 -1
  259. package/src/chrome/diff.ts +60 -1
  260. package/src/chrome/dynamic-border.ts +7 -0
  261. package/src/chrome/format.ts +12 -0
  262. package/src/chrome/message-divider.ts +33 -1
  263. package/src/chrome/message-frame.ts +53 -10
  264. package/src/chrome/message-notice.ts +71 -0
  265. package/src/chrome/overlay-box.ts +45 -2
  266. package/src/chrome/qrcode.ts +28 -0
  267. package/src/chrome/segment-track.ts +26 -0
  268. package/src/chrome/selector-helpers.ts +1 -17
  269. package/src/chrome/status-notice.ts +46 -0
  270. package/src/chrome/streaming-panel.ts +25 -1
  271. package/src/chrome/tool-activity.ts +20 -0
  272. package/src/chrome/transcript-container.ts +139 -17
  273. package/src/chrome/transcript-status.ts +40 -11
  274. package/src/components/box.ts +51 -0
  275. package/src/components/disclosure.ts +33 -0
  276. package/src/components/editor.ts +239 -36
  277. package/src/components/form.ts +93 -9
  278. package/src/components/image.ts +30 -0
  279. package/src/components/input.ts +77 -26
  280. package/src/components/key-value-list.ts +18 -0
  281. package/src/components/layout/geometry.ts +44 -0
  282. package/src/components/layout/row.ts +30 -0
  283. package/src/components/layout/split-pane.ts +65 -0
  284. package/src/components/layout/stack.ts +27 -0
  285. package/src/components/loader.ts +183 -1
  286. package/src/components/markdown.ts +131 -56
  287. package/src/components/metric.ts +39 -0
  288. package/src/components/progress-bar.ts +28 -0
  289. package/src/components/scroll-view.ts +23 -0
  290. package/src/components/section.ts +16 -0
  291. package/src/components/select-list.ts +174 -25
  292. package/src/components/settings-list.ts +264 -7
  293. package/src/components/spacer.ts +17 -0
  294. package/src/components/tab-bar.ts +42 -0
  295. package/src/components/table.ts +34 -0
  296. package/src/components/text.ts +23 -0
  297. package/src/components/tree-view.ts +65 -0
  298. package/src/components/truncated-text.ts +14 -0
  299. package/src/components/wizard-step.ts +18 -9
  300. package/src/debug-server.ts +14 -7
  301. package/src/hotkeys-markdown.ts +251 -69
  302. package/src/index.ts +6 -0
  303. package/src/latex-block.ts +11 -0
  304. package/src/loop-watchdog.ts +38 -10
  305. package/src/native/apply.ts +275 -0
  306. package/src/native/backend.ts +682 -0
  307. package/src/native/blobs.ts +70 -0
  308. package/src/native/describe.ts +124 -0
  309. package/src/native/encode.ts +233 -0
  310. package/src/native/icons.ts +184 -0
  311. package/src/native/memo.ts +64 -0
  312. package/src/native/node.ts +112 -0
  313. package/src/native/overlay.ts +131 -0
  314. package/src/native/picker.ts +333 -0
  315. package/src/native/reconcile.ts +1063 -0
  316. package/src/native/settle.ts +23 -0
  317. package/src/native/spans.ts +308 -0
  318. package/src/native/state.ts +28 -0
  319. package/src/native/tone.ts +79 -0
  320. package/src/overlays/advisor-config.ts +420 -0
  321. package/src/overlays/agent-hub-renderer.ts +155 -18
  322. package/src/overlays/agent-hub.ts +1037 -44
  323. package/src/overlays/agent-transcript-viewer.ts +129 -0
  324. package/src/overlays/agents-hub.ts +688 -53
  325. package/src/overlays/annotation-overlay.ts +391 -52
  326. package/src/overlays/ask-dialog.ts +435 -24
  327. package/src/overlays/bordered-loader.ts +17 -0
  328. package/src/overlays/btw-history-panel.ts +182 -0
  329. package/src/overlays/btw-panel.ts +68 -2
  330. package/src/overlays/cleanse-panel.ts +105 -15
  331. package/src/overlays/codex-reset-fireworks.ts +65 -5
  332. package/src/overlays/composer-shape-preview.ts +26 -1
  333. package/src/overlays/copy-selector.ts +545 -23
  334. package/src/overlays/error-banner.ts +44 -0
  335. package/src/overlays/extensions/extension-list.ts +23 -17
  336. package/src/overlays/history-search.ts +175 -33
  337. package/src/overlays/hook-editor.ts +113 -6
  338. package/src/overlays/hook-input.ts +22 -1
  339. package/src/overlays/hook-selector.ts +311 -22
  340. package/src/overlays/hub-frame.ts +80 -0
  341. package/src/overlays/jobs-panel.ts +112 -0
  342. package/src/overlays/login-dialog.ts +183 -1
  343. package/src/overlays/logout-account-selector.ts +102 -4
  344. package/src/overlays/mcp-add-wizard.ts +232 -53
  345. package/src/overlays/model-browser.ts +681 -22
  346. package/src/overlays/model-hub.ts +1402 -78
  347. package/src/overlays/model-picker.ts +184 -7
  348. package/src/overlays/move-overlay.ts +45 -0
  349. package/src/overlays/oauth-selector.ts +227 -23
  350. package/src/overlays/omfg-panel.ts +86 -2
  351. package/src/overlays/pause-screen.ts +55 -4
  352. package/src/overlays/plan-review-overlay.ts +352 -0
  353. package/src/overlays/plan-save-overlay.ts +45 -0
  354. package/src/overlays/plugin-selector.ts +21 -1
  355. package/src/overlays/plugin-settings.ts +195 -5
  356. package/src/overlays/queue-mode-selector.ts +19 -1
  357. package/src/overlays/reset-usage-selector.ts +110 -40
  358. package/src/overlays/rewind-selector.ts +458 -44
  359. package/src/overlays/session-account-selector.ts +1 -1
  360. package/src/overlays/session-info-overlay.ts +137 -1
  361. package/src/overlays/session-selector.ts +807 -46
  362. package/src/overlays/settings-defs.ts +41 -0
  363. package/src/overlays/settings-selector.ts +585 -33
  364. package/src/overlays/show-images-selector.ts +18 -1
  365. package/src/overlays/snapcompact-shape-preview.ts +48 -3
  366. package/src/overlays/stats-notice.ts +31 -0
  367. package/src/overlays/theme-selector.ts +31 -1
  368. package/src/overlays/thinking-selector.ts +22 -1
  369. package/src/overlays/tree-selector.ts +570 -110
  370. package/src/overlays/usage-dashboard.ts +587 -12
  371. package/src/overlays/usage-row.ts +161 -25
  372. package/src/prompt/attachment-chips.ts +60 -0
  373. package/src/prompt/composer-attachments.ts +31 -9
  374. package/src/prompt/composer.ts +55 -2
  375. package/src/prompt/custom-editor.ts +302 -4
  376. package/src/prompt/editor-top-gap.ts +8 -0
  377. package/src/prompt/macos-spelling.ts +24 -9
  378. package/src/prompt/magic-keywords.ts +22 -1
  379. package/src/prompt/model-mention-autocomplete.ts +1 -0
  380. package/src/prompt/queued-messages.ts +101 -0
  381. package/src/prompt/skill-tokens.ts +5 -3
  382. package/src/prompt/welcome.ts +213 -6
  383. package/src/render/code-cell.ts +103 -2
  384. package/src/render/file-list.ts +29 -0
  385. package/src/render/hyperlink.ts +32 -0
  386. package/src/render/output-block.ts +110 -0
  387. package/src/render/output-pane.ts +37 -0
  388. package/src/render/tool-card.ts +137 -0
  389. package/src/render/tree-list.ts +39 -0
  390. package/src/render/width-aware-text.ts +20 -0
  391. package/src/setup/scenes/composer.ts +29 -0
  392. package/src/setup/scenes/glyph.ts +15 -0
  393. package/src/setup/scenes/model.ts +36 -8
  394. package/src/setup/scenes/outro.ts +28 -1
  395. package/src/setup/scenes/sign-in.ts +138 -24
  396. package/src/setup/scenes/splash.ts +28 -1
  397. package/src/setup/scenes/theme.ts +88 -3
  398. package/src/setup/scenes/types.ts +7 -28
  399. package/src/setup/startup-splash.ts +46 -9
  400. package/src/setup/wizard-overlay.ts +140 -23
  401. package/src/setup/wizard.ts +1 -1
  402. package/src/status-line/component.ts +414 -18
  403. package/src/status-line/context-usage.ts +279 -50
  404. package/src/status-line/footer.ts +94 -1
  405. package/src/status-line/metrics.ts +3 -6
  406. package/src/status-line/segments.ts +463 -97
  407. package/src/status-line/separators.ts +3 -16
  408. package/src/status-line/types.ts +43 -1
  409. package/src/stdin-buffer.ts +27 -21
  410. package/src/terminal-capabilities.ts +7 -0
  411. package/src/terminal.ts +153 -11
  412. package/src/theme/defaults/dark-neon-noir.json +100 -0
  413. package/src/theme/defaults/index.ts +2 -0
  414. package/src/theme/defaults/light-prism.json +5 -5
  415. package/src/theme/defaults/onyx.json +62 -62
  416. package/src/theme/mermaid-cache.ts +10 -1
  417. package/src/theme/schema.ts +15 -0
  418. package/src/theme/shimmer.ts +33 -0
  419. package/src/theme/theme-class.ts +24 -19
  420. package/src/theme/theme.ts +111 -9
  421. package/src/theme/tui-adapters.ts +14 -1
  422. package/src/tools/ask.ts +157 -1
  423. package/src/tools/ast-edit.ts +174 -1
  424. package/src/tools/ast-grep.ts +82 -2
  425. package/src/tools/autoresearch.ts +149 -1
  426. package/src/tools/bash.ts +233 -62
  427. package/src/tools/cfg-render.ts +66 -1
  428. package/src/tools/debug.ts +78 -1
  429. package/src/tools/default-renderer.ts +35 -1
  430. package/src/tools/edit.ts +357 -31
  431. package/src/tools/eval.ts +257 -1
  432. package/src/tools/fetch.ts +102 -3
  433. package/src/tools/find.ts +127 -1
  434. package/src/tools/github.ts +119 -10
  435. package/src/tools/glob.ts +116 -1
  436. package/src/tools/goal.ts +56 -1
  437. package/src/tools/grep.ts +83 -2
  438. package/src/tools/grouped-file-output.ts +205 -0
  439. package/src/tools/json-tree.ts +116 -1
  440. package/src/tools/lsp.ts +318 -1
  441. package/src/tools/mcp.ts +80 -1
  442. package/src/tools/memory.ts +93 -11
  443. package/src/tools/native-view.ts +292 -0
  444. package/src/tools/proc-render.ts +221 -1
  445. package/src/tools/read.ts +199 -4
  446. package/src/tools/renderer.ts +61 -0
  447. package/src/tools/report-tool-issue.ts +7 -1
  448. package/src/tools/resolve.ts +63 -1
  449. package/src/tools/streaming-output.ts +79 -16
  450. package/src/tools/task.ts +406 -4
  451. package/src/tools/think.ts +19 -1
  452. package/src/tools/todo.ts +89 -0
  453. package/src/tools/vibe.ts +230 -2
  454. package/src/tools/wait.ts +181 -1
  455. package/src/tools/web-search-types.ts +99 -0
  456. package/src/tools/web-search.ts +111 -170
  457. package/src/tools/write.ts +144 -5
  458. package/src/tools/xdev.ts +64 -1
  459. package/src/tui.ts +295 -4
  460. package/dist/types/setup/scenes/providers.d.ts +0 -3
  461. package/dist/types/setup/scenes/web-search.d.ts +0 -22
  462. package/src/setup/scenes/providers.ts +0 -110
  463. package/src/setup/scenes/web-search.ts +0 -165
@@ -2,7 +2,6 @@ import { ThinkingLevel } from "@oh-my-pi/pi-agent-core";
2
2
  import {
3
3
  type Component,
4
4
  Container,
5
- extractPrintableText,
6
5
  fuzzyMatch,
7
6
  Input,
8
7
  matchesKey,
@@ -56,7 +55,16 @@ import { resolveAssistantErrorPresentation } from "../chat/transcript-render-hel
56
55
  import { OverlayPanel, PanelDivider } from "../chrome/overlay-box";
57
56
  import { TreeView, type TreeRow } from "../components/tree-view";
58
57
  import { formatKeyHint, formatKeyHints } from "../app-keybindings";
59
- import { editorKeys, interruptKey } from "../chrome/keybinding-hints";
58
+ import { boundKeys, editorKeys, interruptKey } from "../chrome/keybinding-hints";
59
+ import type { TspPickerItem, TspPickerProps, TspSpan, TspText } from "@oh-my-pi/pi-wire";
60
+ import type { ThemeColor } from "../theme/schema";
61
+ import { col, keyed, node, span, text } from "../native/describe";
62
+ import type { DescribeContext, NativeChild, NativeNode, NativeUiEvent } from "../native/node";
63
+ import { actionHint, hintsRow, overlayCard } from "../native/overlay";
64
+ import { CLOSE_ACTION, type PickerEvent, picker, pickerAction, pickerEvent, pickerQuery } from "../native/picker";
65
+ import { plainText } from "../native/spans";
66
+ import type { OutlineTarget } from "../chat/transcript-outline";
67
+ import { collectBlocks, targetCopy, turnPreview } from "./copy-selector";
60
68
 
61
69
  /** Filter mode for tree display */
62
70
  type FilterMode = TreeFilterMode;
@@ -155,13 +163,49 @@ function stripSystemWrapperTags(content: string): string {
155
163
  /** Per-message cap on text folded into the tree search index. */
156
164
  const SEARCH_TEXT_LIMIT = 200;
157
165
 
166
+ /** One theme-colored piece of an entry's display text. */
167
+ interface EntryPart {
168
+ t: string;
169
+ s?: ThemeColor;
170
+ }
171
+
172
+ /** A cached native tree item and the inputs it was built from. */
173
+ interface TreeItemMemo {
174
+ label: string | undefined;
175
+ branch: boolean;
176
+ node: NativeNode;
177
+ }
178
+
179
+ /** The session tree's `picker` data (see {@link TreeList.pickerView}). */
180
+ interface TreePickerView {
181
+ items: readonly TspPickerItem[];
182
+ order: readonly string[];
183
+ selected: string | null;
184
+ current: readonly string[];
185
+ query: string;
186
+ /** UTF-16 caret into `query`. */
187
+ cursor: number;
188
+ filterMode: FilterMode;
189
+ total: number;
190
+ empty: TspText;
191
+ }
192
+
193
+ const FILTER_TABS: readonly { id: TreeFilterMode; label: string }[] = [
194
+ { id: "default", label: "Default" },
195
+ { id: "no-tools", label: "No tools" },
196
+ { id: "user-only", label: "User only" },
197
+ { id: "labeled-only", label: "Labeled" },
198
+ { id: "all", label: "All" },
199
+ ];
200
+
158
201
  class TreeList implements Component {
159
202
  #tree: TreeView<TreeSelectorNode, string>;
160
203
  #roots: TreeSelectorNode[] = [];
161
204
  #rootIds: Set<string> = new Set();
162
205
  #nodeById: Map<string, TreeSelectorNode> = new Map();
163
206
  #filterMode: FilterMode;
164
- #searchQuery = "";
207
+ /** The search field; its value is the fuzzy query. */
208
+ readonly searchInput = new Input();
165
209
  #toolCallMap: Map<string, ToolCallInfo> = new Map();
166
210
  #multipleRoots = false;
167
211
  #activePathIds: Set<string> = new Set();
@@ -171,6 +215,26 @@ class TreeList implements Component {
171
215
  onCancel?: () => void;
172
216
  onLabelEdit?: (entryId: string, currentLabel: string | undefined) => void;
173
217
 
218
+ /** Native item nodes by entry id, reused across filter/search changes. */
219
+ #itemCache = new Map<string, TreeItemMemo>();
220
+ #itemsNative: { rows: readonly TreeRow<TreeSelectorNode, string>[]; items: NativeNode[] } | undefined;
221
+ #listNative:
222
+ | {
223
+ items: readonly NativeNode[];
224
+ selected: string | undefined;
225
+ query: string;
226
+ filterMode: FilterMode;
227
+ node: NativeNode;
228
+ }
229
+ | undefined;
230
+ /** Bumped on every label edit: labels are item badges. */
231
+ #labelVersion = 0;
232
+ #pickerItems:
233
+ | { rows: readonly TreeRow<TreeSelectorNode, string>[]; labels: number; items: TspPickerItem[] }
234
+ | undefined;
235
+ #pickerOrder: { rows: readonly TreeRow<TreeSelectorNode, string>[]; order: string[] } | undefined;
236
+ #previewMemo: { node: TreeSelectorNode | undefined; labels: number; preview: NativeChild[] } | undefined;
237
+
174
238
  constructor(
175
239
  tree: TreeSelectorNode[],
176
240
  private readonly currentLeafId: string | null,
@@ -179,6 +243,8 @@ class TreeList implements Component {
179
243
  initialSelectedId?: string,
180
244
  ) {
181
245
  this.#filterMode = initialFilterMode;
246
+ this.searchInput.prompt = `${theme.fg("muted", "Search:")} `;
247
+ this.searchInput.placeholder = "Type to search";
182
248
  this.#multipleRoots = tree.length > 1;
183
249
  this.#indexSession(tree);
184
250
  this.#tree = new TreeView<TreeSelectorNode, string>({
@@ -307,7 +373,7 @@ class TreeList implements Component {
307
373
  #buildFilter(): (node: TreeSelectorNode, row: TreeRow<TreeSelectorNode, string>) => boolean {
308
374
  const filterMode = this.#filterMode;
309
375
  const leafId = this.currentLeafId;
310
- const searchTokens = this.#searchQuery.toLowerCase().split(/\s+/).filter(Boolean);
376
+ const searchTokens = this.getSearchQuery().toLowerCase().split(/\s+/).filter(Boolean);
311
377
  return node => {
312
378
  const entry = node.entry;
313
379
  const isCurrentLeaf = entry.id === leafId;
@@ -473,6 +539,7 @@ class TreeList implements Component {
473
539
 
474
540
  invalidate(): void {
475
541
  this.#tree.invalidate();
542
+ this.#itemCache.clear();
476
543
  }
477
544
 
478
545
  dispose(): void {
@@ -480,7 +547,203 @@ class TreeList implements Component {
480
547
  }
481
548
 
482
549
  getSearchQuery(): string {
483
- return this.#searchQuery;
550
+ return this.searchInput.getValue();
551
+ }
552
+
553
+ getFilterMode(): FilterMode {
554
+ return this.#filterMode;
555
+ }
556
+
557
+ /** Native filter tab: switch to that mode, like its alt+key shortcut. */
558
+ setFilterMode(mode: string): void {
559
+ const next = TREE_FILTER_MODES.find(m => m === mode);
560
+ if (next === undefined || next === this.#filterMode) return;
561
+ this.#filterMode = next;
562
+ this.#applyFilter();
563
+ }
564
+
565
+ /** Native click on an entry: highlight it, then the same outcome as Enter. */
566
+ activateEntry(entryId: string): void {
567
+ if (!this.#tree.rows.some(row => row.key === entryId)) return;
568
+ this.#tree.setSelectedKey(entryId);
569
+ const selected = this.#tree.selectedItem;
570
+ if (selected && this.onSelect) this.onSelect(selected.entry.id, { summarize: false });
571
+ }
572
+
573
+ /** Native row click: highlight the entry without switching. */
574
+ selectEntry(entryId: string): void {
575
+ if (this.#tree.rows.some(row => row.key === entryId)) this.#tree.setSelectedKey(entryId);
576
+ }
577
+
578
+ /**
579
+ * The `tree` picker's data. `items` is every structural entry, rebuilt
580
+ * only when the tree or a label changes; filter modes and typing change
581
+ * only `order` (the visible entries) and the selection.
582
+ */
583
+ pickerView(): TreePickerView {
584
+ const allRows = this.#tree.allRows;
585
+ if (this.#pickerItems?.rows !== allRows || this.#pickerItems.labels !== this.#labelVersion) {
586
+ // Real branch points get a disclosure; linear chains stay flat at their head's depth.
587
+ const branchPoints = new Set<string>();
588
+ for (const row of allRows) {
589
+ if (row.parentKey !== undefined && row.siblingCount > 1) branchPoints.add(row.parentKey);
590
+ }
591
+ this.#pickerItems = {
592
+ rows: allRows,
593
+ labels: this.#labelVersion,
594
+ items: allRows.map(row => this.#pickerItem(row, branchPoints.has(row.key))),
595
+ };
596
+ }
597
+ const rows = this.#tree.rows;
598
+ if (this.#pickerOrder?.rows !== rows) this.#pickerOrder = { rows, order: rows.map(row => row.key) };
599
+ return {
600
+ items: this.#pickerItems.items,
601
+ order: this.#pickerOrder.order,
602
+ selected: this.#tree.selectedKey ?? null,
603
+ current: this.currentLeafId === null ? [] : [this.currentLeafId],
604
+ query: this.getSearchQuery(),
605
+ cursor: this.searchInput.getCursor(),
606
+ filterMode: this.#filterMode,
607
+ total: allRows.length,
608
+ empty: this.#emptyText(),
609
+ };
610
+ }
611
+
612
+ #pickerItem(row: TreeRow<TreeSelectorNode, string>, open: boolean): TspPickerItem {
613
+ const item = row.item;
614
+ const entry = item.entry;
615
+ const label = this.#entryParts(item).map(part =>
616
+ span(part.t.includes("\x1b") ? plainText(part.t) : part.t, part.s),
617
+ );
618
+ let kind: TspPickerItem["node"] = "marker";
619
+ let role: string | undefined;
620
+ if (isUserRequestEntry(entry)) {
621
+ kind = "user";
622
+ } else if (entry.type === "message") {
623
+ const message = entry.message;
624
+ if (message.role === "assistant") kind = "assistant";
625
+ else if (message.role === "toolResult") [kind, role] = ["tool", `omp.tool.${message.toolName}`];
626
+ else if (message.role === "bashExecution") [kind, role] = ["tool", "omp.tool.bash"];
627
+ else if (message.role === "pythonExecution") [kind, role] = ["tool", "omp.tool.eval"];
628
+ }
629
+ return {
630
+ id: row.key,
631
+ label,
632
+ depth: row.depth,
633
+ node: kind,
634
+ ...(role ? { role } : {}),
635
+ ...(open ? { open: true } : {}),
636
+ ...(item.label ? { badges: [{ text: plainText(item.label), tone: "warning" as const }] } : {}),
637
+ ...(this.#activePathIds.has(row.key) ? { dot: "accent" as const } : {}),
638
+ };
639
+ }
640
+
641
+ /**
642
+ * Preview of the selected entry: a title, its label, and its content as
643
+ * the transcript shows it (markdown prose, commands and output as code).
644
+ */
645
+ pickerPreview(): readonly NativeChild[] {
646
+ const selected = this.#tree.selectedItem;
647
+ const memo = this.#previewMemo;
648
+ if (memo !== undefined && memo.node === selected && memo.labels === this.#labelVersion) return memo.preview;
649
+ const preview: NativeChild[] = [];
650
+ if (selected) {
651
+ const entry = selected.entry;
652
+ const parts = this.#entryParts(selected)
653
+ .map(part => (part.t.includes("\x1b") ? plainText(part.t) : part.t))
654
+ .join("");
655
+ if (entry.type === "message" || entry.type === "custom_message") {
656
+ const target: OutlineTarget = {
657
+ entryId: entry.id,
658
+ turnId: entry.id,
659
+ isUserTurn: isUserRequestEntry(entry),
660
+ start: 0,
661
+ end: 0,
662
+ entries: [entry],
663
+ };
664
+ const copy = targetCopy(target, collectBlocks(target.entries));
665
+ preview.push(text(`${copy.label[0]!.toUpperCase()}${copy.label.slice(1)}`, { role: "omp.picker.title" }));
666
+ if (selected.label) preview.push(text([span(plainText(selected.label), "warning")]));
667
+ preview.push(turnPreview(target, copy.content || parts));
668
+ } else {
669
+ preview.push(text(parts, { role: "omp.picker.title" }));
670
+ if (selected.label) preview.push(text([span(plainText(selected.label), "warning")]));
671
+ }
672
+ }
673
+ this.#previewMemo = { node: selected, labels: this.#labelVersion, preview };
674
+ return preview;
675
+ }
676
+
677
+ /**
678
+ * The visible entries as a native `list` keyed `"list"`, items keyed by
679
+ * entry id. Selection stays omp's; the terminal scrolls and virtualizes.
680
+ * Branch heads carry a `branch` icon and the active path an accent bullet
681
+ * instead of drawn tree connectors.
682
+ */
683
+ describeList(): NativeNode {
684
+ const rows = this.#tree.rows;
685
+ let items = this.#itemsNative;
686
+ if (items?.rows !== rows) {
687
+ items = { rows, items: rows.map(row => this.#describeRow(row)) };
688
+ this.#itemsNative = items;
689
+ }
690
+ const selected = this.#tree.selectedKey;
691
+ const query = this.getSearchQuery();
692
+ const memo = this.#listNative;
693
+ if (
694
+ memo?.items === items.items &&
695
+ memo.selected === selected &&
696
+ memo.query === query &&
697
+ memo.filterMode === this.#filterMode
698
+ ) {
699
+ return memo.node;
700
+ }
701
+ const list = node(
702
+ "list",
703
+ { selected: selected ?? null, filter: query || undefined, empty: this.#emptyText(), virtual: true },
704
+ items.items,
705
+ "list",
706
+ );
707
+ this.#listNative = { items: items.items, selected, query, filterMode: this.#filterMode, node: list };
708
+ return list;
709
+ }
710
+
711
+ /** The empty-state explanation from {@link render}, as spans. */
712
+ #emptyText(): TspText {
713
+ const total = this.#tree.allRows.length;
714
+ if (total === 0) return [span("No entries found", "muted")];
715
+ const query = this.getSearchQuery();
716
+ if (query.length > 0) {
717
+ return [
718
+ span(`No entries match search "${query}". Press `, "muted"),
719
+ span("backspace", "key"),
720
+ span(" to clear the search", "muted"),
721
+ ];
722
+ }
723
+ const filterLabel = this.#getFilterLabel().trim() || "[default]";
724
+ return [
725
+ span(`${total} entries hidden by the current filter ${filterLabel}. Press `, "muted"),
726
+ span("alt+a", "key"),
727
+ span(" to show all, ", "muted"),
728
+ span("alt+d", "key"),
729
+ span(" for default", "muted"),
730
+ ];
731
+ }
732
+
733
+ #describeRow(row: TreeRow<TreeSelectorNode, string>): NativeNode {
734
+ const item = row.item;
735
+ const branch = row.parentKey !== undefined && row.siblingCount > 1;
736
+ const cached = this.#itemCache.get(row.key);
737
+ if (cached !== undefined && cached.label === item.label && cached.branch === branch) return cached.node;
738
+ const label: TspSpan[] = [];
739
+ if (this.#activePathIds.has(row.key)) label.push(span(`${theme.md.bullet} `, "accent"));
740
+ if (item.label) label.push(span(`[${plainText(item.label)}] `, "warning"));
741
+ for (const part of this.#entryParts(item)) {
742
+ label.push(span(part.t.includes("\x1b") ? plainText(part.t) : part.t, part.s));
743
+ }
744
+ const described = node("item", { label, icon: branch ? "branch" : undefined }, undefined, row.key);
745
+ this.#itemCache.set(row.key, { label: item.label, branch, node: described });
746
+ return described;
484
747
  }
485
748
 
486
749
  getSelectedNode(): TreeSelectorNode | undefined {
@@ -490,6 +753,7 @@ class TreeList implements Component {
490
753
  updateNodeLabel(entryId: string, label: string | undefined): void {
491
754
  const node = this.#nodeById.get(entryId);
492
755
  if (node) node.label = label;
756
+ this.#labelVersion++;
493
757
  this.#tree.invalidate();
494
758
  }
495
759
 
@@ -523,8 +787,8 @@ class TreeList implements Component {
523
787
  if (totalCount === 0) {
524
788
  lines.push(truncateToWidth(theme.fg("muted", "No entries found"), width));
525
789
  lines.push(truncateToWidth(theme.fg("muted", `(0/0)${this.#getFilterLabel()}`), width));
526
- } else if (this.#searchQuery.length > 0) {
527
- lines.push(truncateToWidth(theme.fg("muted", `No entries match search "${this.#searchQuery}"`), width));
790
+ } else if (this.getSearchQuery().length > 0) {
791
+ lines.push(truncateToWidth(theme.fg("muted", `No entries match search "${this.getSearchQuery()}"`), width));
528
792
  lines.push(
529
793
  truncateToWidth(theme.fg("muted", `Press ${formatKeyHint("backspace")} to clear the search`), width),
530
794
  );
@@ -660,9 +924,14 @@ class TreeList implements Component {
660
924
  }
661
925
 
662
926
  #getEntryDisplayText(node: TreeSelectorNode, isSelected: boolean): string {
663
- const entry = node.entry;
664
- let result: string;
927
+ let result = "";
928
+ for (const part of this.#entryParts(node)) result += part.s ? theme.fg(part.s, part.t) : part.t;
929
+ return isSelected ? theme.bold(result) : result;
930
+ }
665
931
 
932
+ /** Entry display text as theme-colored parts, shared by the ANSI row and the native item label. */
933
+ #entryParts(node: TreeSelectorNode): readonly EntryPart[] {
934
+ const entry = node.entry;
666
935
  const normalize = (s: string) => s.replace(/[\n\t]/g, " ").trim();
667
936
 
668
937
  switch (entry.type) {
@@ -672,84 +941,76 @@ class TreeList implements Component {
672
941
  if (role === "user") {
673
942
  const msgWithContent = msg as { content?: unknown };
674
943
  const content = normalize(this.#extractContent(msgWithContent.content));
675
- result = theme.fg("accent", "user: ") + content;
676
- } else if (role === "developer") {
944
+ return [{ t: "user: ", s: "accent" }, { t: content }];
945
+ }
946
+ if (role === "developer") {
677
947
  const msgWithContent = msg as { content?: unknown };
678
948
  const content = normalize(this.#extractContent(msgWithContent.content));
679
- result = theme.fg("dim", "developer: ") + theme.fg("muted", content);
680
- } else if (role === "assistant") {
949
+ return [
950
+ { t: "developer: ", s: "dim" },
951
+ { t: content, s: "muted" },
952
+ ];
953
+ }
954
+ if (role === "assistant") {
681
955
  const presentation = resolveAssistantErrorPresentation(msg);
682
956
  if (presentation.kind === "compact-recovered") {
683
- result = theme.fg("success", "assistant: ") + theme.fg("dim", presentation.text);
684
- break;
957
+ return [
958
+ { t: "assistant: ", s: "success" },
959
+ { t: presentation.text, s: "dim" },
960
+ ];
685
961
  }
686
962
  const msgWithContent = msg as { content?: unknown; stopReason?: string; errorMessage?: string };
687
963
  const textContent = normalize(this.#extractContent(msgWithContent.content));
688
- if (textContent) {
689
- result = theme.fg("success", "assistant: ") + textContent;
690
- } else if (presentation.kind === "full") {
691
- result =
692
- theme.fg("success", "assistant: ") + theme.fg("error", normalize(presentation.text).slice(0, 80));
693
- } else if (msgWithContent.stopReason === "aborted") {
694
- result = theme.fg("success", "assistant: ") + theme.fg("muted", "(aborted)");
695
- } else {
696
- result = theme.fg("success", "assistant: ") + theme.fg("muted", "(no content)");
964
+ const head: EntryPart = { t: "assistant: ", s: "success" };
965
+ if (textContent) return [head, { t: textContent }];
966
+ if (presentation.kind === "full") {
967
+ return [head, { t: normalize(presentation.text).slice(0, 80), s: "error" }];
697
968
  }
698
- } else if (role === "toolResult") {
969
+ if (msgWithContent.stopReason === "aborted") return [head, { t: "(aborted)", s: "muted" }];
970
+ return [head, { t: "(no content)", s: "muted" }];
971
+ }
972
+ if (role === "toolResult") {
699
973
  const toolMsg = msg as { toolCallId?: string; toolName?: string };
700
974
  const toolCall = toolMsg.toolCallId ? this.#toolCallMap.get(toolMsg.toolCallId) : undefined;
701
- if (toolCall) {
702
- result = theme.fg("muted", this.#formatToolCall(toolCall.name, toolCall.arguments));
703
- } else {
704
- result = theme.fg("muted", `[${toolMsg.toolName ?? "tool"}]`);
705
- }
706
- } else if (role === "bashExecution") {
975
+ if (toolCall) return [{ t: this.#formatToolCall(toolCall.name, toolCall.arguments), s: "muted" }];
976
+ return [{ t: `[${toolMsg.toolName ?? "tool"}]`, s: "muted" }];
977
+ }
978
+ if (role === "bashExecution") {
707
979
  const bashMsg = msg as { command?: string };
708
- result = theme.fg("dim", `[bash]: ${normalize(bashMsg.command ?? "")}`);
709
- } else {
710
- result = theme.fg("dim", `[${role}]`);
980
+ return [{ t: `[bash]: ${normalize(bashMsg.command ?? "")}`, s: "dim" }];
711
981
  }
712
- break;
982
+ return [{ t: `[${role}]`, s: "dim" }];
713
983
  }
714
984
  case "custom_message": {
715
985
  if (entry.customType === "advisor") {
716
986
  const { qualifier, text } = advisorTreeDisplay(entry.details);
717
987
  const label = qualifier ? `advisor (${qualifier}): ` : "advisor: ";
718
- result = theme.fg("customMessageLabel", label) + normalize(text);
719
- break;
988
+ return [{ t: label, s: "customMessageLabel" }, { t: normalize(text) }];
720
989
  }
721
990
  const content = stripSystemWrapperTags(this.#joinTextContent(entry.content));
722
- result = theme.fg("customMessageLabel", `[${entry.customType}]: `) + normalize(content);
723
- break;
991
+ return [{ t: `[${entry.customType}]: `, s: "customMessageLabel" }, { t: normalize(content) }];
724
992
  }
725
993
  case "compaction": {
726
994
  const tokens = Math.round(entry.tokensBefore / 1000);
727
- result = theme.fg("borderAccent", `[compaction: ${tokens}k tokens]`);
728
- break;
995
+ return [{ t: `[compaction: ${tokens}k tokens]`, s: "borderAccent" }];
729
996
  }
730
997
  case "branch_summary":
731
- result = theme.fg("warning", `[branch summary]: `) + normalize(entry.summary);
732
- break;
998
+ return [{ t: `[branch summary]: `, s: "warning" }, { t: normalize(entry.summary) }];
733
999
  case "model_change":
734
- result = theme.fg("dim", `[model: ${entry.model}]`);
735
- break;
1000
+ return [{ t: `[model: ${entry.model}]`, s: "dim" }];
736
1001
  case "model_usage": {
737
1002
  const purpose = sanitizeTreeField(entry.purpose);
738
1003
  const role = sanitizeTreeField(entry.role ?? "");
739
1004
  const provider = sanitizeTreeField(entry.provider);
740
1005
  const model = sanitizeTreeField(entry.model);
741
- result = theme.fg("dim", `[model usage: ${purpose} ${role ? `${role} ` : ""}${provider}/${model}]`);
742
- break;
1006
+ return [{ t: `[model usage: ${purpose} ${role ? `${role} ` : ""}${provider}/${model}]`, s: "dim" }];
743
1007
  }
744
1008
  case "thinking_level_change":
745
- result = theme.fg("dim", `[thinking: ${entry.thinkingLevel ?? ThinkingLevel.Off}]`);
746
- break;
1009
+ return [{ t: `[thinking: ${entry.thinkingLevel ?? ThinkingLevel.Off}]`, s: "dim" }];
747
1010
  case "custom":
748
- result = theme.fg("dim", `[custom: ${entry.customType}]`);
749
- break;
1011
+ return [{ t: `[custom: ${entry.customType}]`, s: "dim" }];
750
1012
  case "label":
751
- result = theme.fg("dim", `[label: ${entry.label ?? "(cleared)"}]`);
752
- break;
1013
+ return [{ t: `[label: ${entry.label ?? "(cleared)"}]`, s: "dim" }];
753
1014
  case "service_tier_change": {
754
1015
  // Per-family map, or null when the session went back to the default.
755
1016
  const tiers = entry.serviceTier
@@ -757,27 +1018,21 @@ class TreeList implements Component {
757
1018
  .map(([family, tier]) => `${family}:${tier}`)
758
1019
  .join(" ")
759
1020
  : "(default)";
760
- result = theme.fg("dim", `[service tier: ${tiers}]`);
761
- break;
1021
+ return [{ t: `[service tier: ${tiers}]`, s: "dim" }];
762
1022
  }
763
1023
  case "title_change":
764
- result = theme.fg("dim", `[title: ${normalize(entry.title)}]`);
765
- break;
1024
+ return [{ t: `[title: ${normalize(entry.title)}]`, s: "dim" }];
766
1025
  case "mode_change":
767
- result = theme.fg("dim", `[mode: ${entry.mode}]`);
768
- break;
1026
+ return [{ t: `[mode: ${entry.mode}]`, s: "dim" }];
769
1027
  case "credential_pin":
770
- result = theme.fg("dim", `[credential pin: ${entry.provider}]`);
771
- break;
1028
+ return [{ t: `[credential pin: ${entry.provider}]`, s: "dim" }];
772
1029
  default:
773
1030
  // Bookkeeping entries with nothing worth spelling out still get their
774
1031
  // type. A row that renders to the empty string is worse than a
775
1032
  // useless one: it draws as a bare bullet with no way to tell what it
776
1033
  // is or why the tree has a gap in it.
777
- result = theme.fg("dim", `[${entry.type.replaceAll("_", " ")}]`);
1034
+ return [{ t: `[${entry.type.replaceAll("_", " ")}]`, s: "dim" }];
778
1035
  }
779
-
780
- return isSelected ? theme.bold(result) : result;
781
1036
  }
782
1037
 
783
1038
  #extractContent(content: unknown): string {
@@ -884,6 +1139,37 @@ class TreeList implements Component {
884
1139
  }
885
1140
  }
886
1141
 
1142
+ /** Enter (`summarize` false) / Shift+Enter: switch to the selected entry. */
1143
+ confirm(summarize: boolean): void {
1144
+ const selected = this.#tree.selectedItem;
1145
+ if (selected && this.onSelect) this.onSelect(selected.entry.id, { summarize });
1146
+ }
1147
+
1148
+ /** Esc: clear the search first, then close. */
1149
+ escape(): void {
1150
+ if (this.getSearchQuery()) this.clearSearch();
1151
+ else this.onCancel?.();
1152
+ }
1153
+
1154
+ clearSearch(): void {
1155
+ this.searchInput.setValue("");
1156
+ this.#applyFilter();
1157
+ }
1158
+
1159
+ /** Ctrl+O / Shift+Ctrl+O: default → no-tools → user-only → labeled-only → all → default. */
1160
+ cycleFilter(delta: 1 | -1): void {
1161
+ const modes = TREE_FILTER_MODES;
1162
+ const currentIndex = modes.indexOf(this.#filterMode);
1163
+ this.#filterMode = modes[(currentIndex + delta + modes.length) % modes.length];
1164
+ this.#applyFilter();
1165
+ }
1166
+
1167
+ /** Shift+L: edit the selected entry's label. */
1168
+ editLabel(): void {
1169
+ const selected = this.#tree.selectedItem;
1170
+ if (selected && this.onLabelEdit) this.onLabelEdit(selected.entry.id, selected.label);
1171
+ }
1172
+
887
1173
  handleInput(keyData: string): void {
888
1174
  if (matchesSelectUp(keyData)) {
889
1175
  this.#tree.moveSelection(-1, true);
@@ -918,36 +1204,17 @@ class TreeList implements Component {
918
1204
  keyData === "\x1b[13;2~" // Shift+Enter legacy CSI ~ form — also accepted by the composer (editor.ts:1466)
919
1205
  ) {
920
1206
  // Summarize-and-switch: fork with a branch summary without the extra prompt.
921
- const selected = this.#tree.selectedItem;
922
- if (selected && this.onSelect) {
923
- this.onSelect(selected.entry.id, { summarize: true });
924
- }
1207
+ this.confirm(true);
925
1208
  } else if (matchesKey(keyData, "enter") || matchesKey(keyData, "return")) {
926
- const selected = this.#tree.selectedItem;
927
- if (selected && this.onSelect) {
928
- this.onSelect(selected.entry.id, { summarize: false });
929
- }
1209
+ this.confirm(false);
930
1210
  } else if (matchesAppInterrupt(keyData)) {
931
- if (this.#searchQuery) {
932
- this.#searchQuery = "";
933
- this.#applyFilter();
934
- } else {
935
- this.onCancel?.();
936
- }
1211
+ this.escape();
937
1212
  } else if (matchesKey(keyData, "ctrl+c")) {
938
1213
  this.onCancel?.();
939
1214
  } else if (matchesKey(keyData, "shift+ctrl+o") || matchesKey(keyData, "ctrl+shift+o")) {
940
- // Cycle filter backwards
941
- const modes = TREE_FILTER_MODES;
942
- const currentIndex = modes.indexOf(this.#filterMode);
943
- this.#filterMode = modes[(currentIndex - 1 + modes.length) % modes.length];
944
- this.#applyFilter();
1215
+ this.cycleFilter(-1);
945
1216
  } else if (matchesKey(keyData, "ctrl+o")) {
946
- // Cycle filter forwards: default → no-tools → user-only → labeled-only → all → default
947
- const modes = TREE_FILTER_MODES;
948
- const currentIndex = modes.indexOf(this.#filterMode);
949
- this.#filterMode = modes[(currentIndex + 1) % modes.length];
950
- this.#applyFilter();
1217
+ this.cycleFilter(1);
951
1218
  } else if (matchesKey(keyData, "alt+d")) {
952
1219
  this.#filterMode = "default";
953
1220
  this.#applyFilter();
@@ -963,22 +1230,11 @@ class TreeList implements Component {
963
1230
  } else if (matchesKey(keyData, "alt+a")) {
964
1231
  this.#filterMode = "all";
965
1232
  this.#applyFilter();
966
- } else if (matchesKey(keyData, "backspace")) {
967
- if (this.#searchQuery.length > 0) {
968
- this.#searchQuery = this.#searchQuery.slice(0, -1);
969
- this.#applyFilter();
970
- }
971
- } else if (matchesKey(keyData, "shift+l") && !this.#searchQuery) {
972
- const selected = this.#tree.selectedItem;
973
- if (selected && this.onLabelEdit) {
974
- this.onLabelEdit(selected.entry.id, selected.label);
975
- }
1233
+ } else if (matchesKey(keyData, "shift+l") && !this.getSearchQuery()) {
1234
+ this.editLabel();
976
1235
  } else {
977
- const printableText = extractPrintableText(keyData);
978
- if (printableText) {
979
- this.#searchQuery += printableText;
980
- this.#applyFilter();
981
- }
1236
+ const before = this.getSearchQuery();
1237
+ if (this.searchInput.handleInput(keyData) && this.getSearchQuery() !== before) this.#applyFilter();
982
1238
  }
983
1239
  }
984
1240
  }
@@ -990,11 +1246,7 @@ class SearchLine implements Component {
990
1246
  invalidate(): void {}
991
1247
 
992
1248
  render(width: number): readonly string[] {
993
- const query = this.treeList.getSearchQuery();
994
- if (query) {
995
- return [truncateToWidth(`${theme.fg("muted", "Search:")} ${theme.fg("accent", query)}`, width)];
996
- }
997
- return [truncateToWidth(theme.fg("muted", "Search:"), width)];
1249
+ return this.treeList.searchInput.render(width);
998
1250
  }
999
1251
 
1000
1252
  handleInput(_keyData: string): void {}
@@ -1006,6 +1258,9 @@ class LabelInput implements Component {
1006
1258
  onSubmit?: (entryId: string, label: string | undefined) => void;
1007
1259
  onCancel?: () => void;
1008
1260
 
1261
+ #native: NativeNode | undefined;
1262
+ #preview: readonly NativeChild[] | undefined;
1263
+
1009
1264
  constructor(
1010
1265
  private readonly entryId: string,
1011
1266
  currentLabel: string | undefined,
@@ -1018,6 +1273,35 @@ class LabelInput implements Component {
1018
1273
 
1019
1274
  invalidate(): void {}
1020
1275
 
1276
+ /** Prompt, the label `Input`, and save/cancel hints; the Input describes its own edits. */
1277
+ describe(_cx: DescribeContext): NativeNode {
1278
+ this.#native ??= node("col", { gap: "xs" }, [
1279
+ text([span("Label (empty to remove):", "muted")]),
1280
+ this.#input,
1281
+ hintsRow([
1282
+ { keys: ["enter"], label: "save" },
1283
+ { keys: [boundKeys("app.interrupt", ["escape"])[0] ?? "escape"], label: "cancel" },
1284
+ ]),
1285
+ ]);
1286
+ return this.#native;
1287
+ }
1288
+
1289
+ /** The picker preview while editing: the prompt and the label `Input` (save/cancel sit in the action bar). */
1290
+ get preview(): readonly NativeChild[] {
1291
+ this.#preview ??= [
1292
+ text("Label", { role: "omp.picker.title" }),
1293
+ text([span("Empty to remove", "muted")]),
1294
+ this.#input,
1295
+ ];
1296
+ return this.#preview;
1297
+ }
1298
+
1299
+ /** Enter: save the label (empty removes it). */
1300
+ submit(): void {
1301
+ const value = this.#input.getValue().trim();
1302
+ this.onSubmit?.(this.entryId, value || undefined);
1303
+ }
1304
+
1021
1305
  render(width: number): readonly string[] {
1022
1306
  const lines: string[] = [];
1023
1307
  lines.push(truncateToWidth(theme.fg("muted", "Label (empty to remove):"), width));
@@ -1029,8 +1313,7 @@ class LabelInput implements Component {
1029
1313
 
1030
1314
  handleInput(keyData: string): void {
1031
1315
  if (matchesKey(keyData, "enter") || matchesKey(keyData, "return") || keyData === "\n") {
1032
- const value = this.#input.getValue().trim();
1033
- this.onSubmit?.(this.entryId, value || undefined);
1316
+ this.submit();
1034
1317
  } else if (matchesAppInterrupt(keyData)) {
1035
1318
  this.onCancel?.();
1036
1319
  } else {
@@ -1047,6 +1330,23 @@ export class TreeSelectorComponent extends OverlayPanel {
1047
1330
  #labelInput: LabelInput | null = null;
1048
1331
  #labelInputContainer: Container;
1049
1332
  #treeContainer: Container;
1333
+ #nativeMemo:
1334
+ | {
1335
+ content: NativeChild;
1336
+ query: string;
1337
+ cursor: number;
1338
+ filterMode: FilterMode;
1339
+ node: NativeNode;
1340
+ }
1341
+ | undefined;
1342
+ #pickerMemo:
1343
+ | {
1344
+ view: TreePickerView;
1345
+ preview: readonly NativeChild[];
1346
+ label: LabelInput | null;
1347
+ node: NativeNode;
1348
+ }
1349
+ | undefined;
1050
1350
 
1051
1351
  constructor(
1052
1352
  tree: TreeSelectorNode[],
@@ -1056,6 +1356,8 @@ export class TreeSelectorComponent extends OverlayPanel {
1056
1356
  onCancel: () => void,
1057
1357
  private readonly onLabelChangeCallback?: (entryId: string, label: string | undefined) => void,
1058
1358
  initialFilterMode: FilterMode = "default",
1359
+ /** The picker's subtitle. */
1360
+ private readonly sessionName?: string,
1059
1361
  ) {
1060
1362
  super("Session Tree");
1061
1363
  // The outer panel has eight fixed rows around the tree list: top/bottom
@@ -1109,6 +1411,164 @@ export class TreeSelectorComponent extends OverlayPanel {
1109
1411
  }
1110
1412
  }
1111
1413
 
1414
+ override describe(cx: DescribeContext): NativeNode {
1415
+ return cx.supports("picker") ? this.#describePicker() : this.#describeCard();
1416
+ }
1417
+
1418
+ /**
1419
+ * The `tree` picker, keyed `picker` inside a column: mounted in the dock,
1420
+ * the reconciler hoists it into `layer` as a sheet. The label editor
1421
+ * takes the preview pane while open.
1422
+ */
1423
+ #describePicker(): NativeNode {
1424
+ const view = this.#treeList.pickerView();
1425
+ const label = this.#labelInput;
1426
+ const preview = label ? label.preview : this.#treeList.pickerPreview();
1427
+ const memo = this.#pickerMemo;
1428
+ if (
1429
+ memo?.label === label &&
1430
+ memo.preview === preview &&
1431
+ memo.view.items === view.items &&
1432
+ memo.view.order === view.order &&
1433
+ memo.view.selected === view.selected &&
1434
+ memo.view.query === view.query &&
1435
+ memo.view.cursor === view.cursor &&
1436
+ memo.view.filterMode === view.filterMode
1437
+ ) {
1438
+ return memo.node;
1439
+ }
1440
+ const props: TspPickerProps = {
1441
+ title: "Session tree",
1442
+ ...(this.sessionName ? { subtitle: this.sessionName } : {}),
1443
+ icon: "git-branch",
1444
+ noun: "entries",
1445
+ size: "lg",
1446
+ layout: "tree",
1447
+ preview: "side",
1448
+ ...pickerQuery(this.#treeList.searchInput),
1449
+ placeholder: "Search entries…",
1450
+ tabs: FILTER_TABS,
1451
+ tab: view.filterMode,
1452
+ items: view.items,
1453
+ order: view.order,
1454
+ selected: view.selected,
1455
+ current: view.current,
1456
+ total: view.total,
1457
+ empty: view.empty,
1458
+ focus: label ? "preview" : "list",
1459
+ actions: label
1460
+ ? [
1461
+ pickerAction("label-save", "Save label", "enter", { primary: true }),
1462
+ { ...CLOSE_ACTION, label: "Cancel" },
1463
+ ]
1464
+ : [
1465
+ pickerAction("switch", "Switch", "enter", { primary: true }),
1466
+ pickerAction("summarize", "Summarize & switch", "shift+enter"),
1467
+ pickerAction("label", "Label", "shift+l"),
1468
+ pickerAction("filter", "Filter", "ctrl+o"),
1469
+ CLOSE_ACTION,
1470
+ ],
1471
+ };
1472
+ const result = col([keyed(picker(props, preview), "picker")]);
1473
+ this.#pickerMemo = { view, preview, label, node: result };
1474
+ return result;
1475
+ }
1476
+
1477
+ /** Picker pointer events run the same paths as their keys; the label editor owns them while open. */
1478
+ #handlePickerEvent(event: PickerEvent): void {
1479
+ const label = this.#labelInput;
1480
+ if (label) {
1481
+ if (event.kind !== "action") return;
1482
+ if (event.act === "label-save") label.submit();
1483
+ else if (event.act === "close") label.onCancel?.();
1484
+ return;
1485
+ }
1486
+ const list = this.#treeList;
1487
+ if (event.kind !== "action") {
1488
+ if (event.kind === "select") list.selectEntry(event.item);
1489
+ else list.activateEntry(event.item);
1490
+ return;
1491
+ }
1492
+ switch (event.act) {
1493
+ case "tab":
1494
+ if (event.value !== undefined) list.setFilterMode(event.value);
1495
+ return;
1496
+ case "switch":
1497
+ list.confirm(false);
1498
+ return;
1499
+ case "summarize":
1500
+ list.confirm(true);
1501
+ return;
1502
+ case "label":
1503
+ if (!list.getSearchQuery()) list.editLabel();
1504
+ return;
1505
+ case "filter":
1506
+ list.cycleFilter(1);
1507
+ return;
1508
+ case "close":
1509
+ list.escape();
1510
+ return;
1511
+ case "clear":
1512
+ list.clearSearch();
1513
+ return;
1514
+ default:
1515
+ return;
1516
+ }
1517
+ }
1518
+
1519
+ /**
1520
+ * Native tree: filter-mode `tabs`, the search query as an `input`, then
1521
+ * the entry `list` (or the label editor in its place) and the key hints.
1522
+ */
1523
+ #describeCard(): NativeNode {
1524
+ const content: NativeChild = this.#labelInput ?? this.#treeList.describeList();
1525
+ const search = this.#treeList.searchInput;
1526
+ const query = search.getValue();
1527
+ const cursor = search.getCursor();
1528
+ const filterMode = this.#treeList.getFilterMode();
1529
+ const memo = this.#nativeMemo;
1530
+ if (
1531
+ memo?.content === content &&
1532
+ memo.query === query &&
1533
+ memo.cursor === cursor &&
1534
+ memo.filterMode === filterMode
1535
+ ) {
1536
+ return memo.node;
1537
+ }
1538
+ const children: NativeChild[] = [
1539
+ node("tabs", { items: FILTER_TABS, active: filterMode }, undefined, "filter"),
1540
+ search,
1541
+ content,
1542
+ hintsRow([
1543
+ { keys: ["enter"], label: "switch" },
1544
+ { keys: ["alt+up", "alt+down"], label: "previous/next turn" },
1545
+ actionHint(["tui.select.pageUp", "tui.select.pageDown"], "page"),
1546
+ { keys: ["home", "end"], label: "first/last" },
1547
+ { keys: ["shift+enter"], label: "summarize & switch" },
1548
+ { keys: ["shift+l"], label: "label" },
1549
+ { keys: ["ctrl+o"], label: "filter" },
1550
+ ]),
1551
+ ];
1552
+ const result = overlayCard("omp.overlay.tree", "Session Tree", children);
1553
+ this.#nativeMemo = { content, query, cursor, filterMode, node: result };
1554
+ return result;
1555
+ }
1556
+
1557
+ /** Filter tab → switch mode; entry click → highlight it and switch, exactly like Enter. */
1558
+ handleNativeEvent(event: NativeUiEvent): void {
1559
+ const picked = pickerEvent(event, "picker");
1560
+ if (picked) {
1561
+ this.#handlePickerEvent(picked);
1562
+ return;
1563
+ }
1564
+ if (event.type !== "select" && event.type !== "activate") return;
1565
+ if (event.key === "filter") {
1566
+ if (event.type === "select") this.#treeList.setFilterMode(event.item);
1567
+ return;
1568
+ }
1569
+ if (event.key === "list" && !this.#labelInput) this.#treeList.activateEntry(event.item);
1570
+ }
1571
+
1112
1572
  #showLabelInput(entryId: string, currentLabel: string | undefined): void {
1113
1573
  this.#labelInput = new LabelInput(entryId, currentLabel);
1114
1574
  this.#labelInput.onSubmit = (id, label) => {