@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
@@ -15,13 +15,16 @@
15
15
  * mouse selection nor cmd-click.
16
16
  */
17
17
  import type { AgentTool } from "@oh-my-pi/pi-agent-core";
18
+ import type { TspPickerColumn, TspPickerItem, TspPickerProps, TspText } from "@oh-my-pi/pi-wire";
18
19
  import { type Component, matchesKey, routeSgrMouseInput, type TUI, truncateToWidth, visibleWidth } from "../index";
19
20
  import type { MessageRenderer } from "../chat/extension-types";
20
21
  import {
21
22
  recentTranscriptEntries,
22
23
  type SessionMessageEntryLike as SessionMessageEntry,
23
24
  type TranscriptEntryLike as TranscriptEntry,
25
+ textContent,
24
26
  transcriptEntryMessage,
27
+ userMessageLabel,
25
28
  userTurnDraft,
26
29
  } from "../chat/transcript-entry";
27
30
  import { expandKeyHint, replaceTabs } from "../render/render-utils";
@@ -39,6 +42,228 @@ import {
39
42
  type OutlineTarget,
40
43
  outlineRows,
41
44
  } from "../chat/transcript-outline";
45
+ import { code, compact, md, node, row, span, text } from "../native/describe";
46
+ import type { DescribeContext, NativeChild, NativeNode, NativeUiEvent } from "../native/node";
47
+ import { actionHint, hintsRow, type NativeHint, overlayCard } from "../native/overlay";
48
+ import { CLOSE_ACTION, type PickerEvent, picker, pickerAction, pickerEvent } from "../native/picker";
49
+
50
+ /** Key of the leading list item that replays the history older than the startup tail. */
51
+ export const EARLIER_TURNS_KEY = "earlier";
52
+
53
+ /** Leading item of a truncated transcript list; selecting it loads the older turns. */
54
+ export const earlierTurnsItem: NativeNode = node(
55
+ "item",
56
+ { label: [span("Earlier turns…", "muted")], hint: ["a"] },
57
+ undefined,
58
+ EARLIER_TURNS_KEY,
59
+ );
60
+
61
+ /** First non-blank line of `source`, whitespace collapsed. */
62
+ function firstLine(source: string): string {
63
+ const line = source.split("\n").find(part => part.trim().length > 0) ?? "";
64
+ return line.replace(/\s+/g, " ").trim();
65
+ }
66
+
67
+ /** Plain one-line label and styling role for the turn a target opens. */
68
+ function turnSummary(entry: TranscriptEntry): { label: string; role: string } {
69
+ const message = transcriptEntryMessage(entry);
70
+ switch (message?.role) {
71
+ case "user":
72
+ return { label: userMessageLabel(message.content), role: "omp.user" };
73
+ case "assistant": {
74
+ let prose = "";
75
+ const tools: string[] = [];
76
+ for (const content of message.content) {
77
+ if (content.type === "text") prose += content.text;
78
+ else if (content.type === "toolCall") tools.push(content.name);
79
+ }
80
+ const label = firstLine(prose) || tools.join(", ") || "thinking";
81
+ return { label, role: "omp.assistant" };
82
+ }
83
+ case "toolResult":
84
+ return { label: `${message.toolName} result`, role: `omp.tool.${message.toolName}` };
85
+ case "bashExecution":
86
+ return { label: `$ ${firstLine(message.command)}`, role: "omp.tool.bash" };
87
+ case "pythonExecution":
88
+ return { label: firstLine(message.code), role: "omp.tool.eval" };
89
+ case "compactionSummary":
90
+ return { label: "Compaction summary", role: "omp.summary" };
91
+ case "branchSummary":
92
+ return { label: "Branch summary", role: "omp.summary" };
93
+ case "custom":
94
+ case "hookMessage": {
95
+ const draft = userTurnDraft(entry);
96
+ if (draft !== undefined) return { label: firstLine(draft), role: "omp.user" };
97
+ return { label: firstLine(textContent(message.content, " ")) || message.customType, role: "omp.custom" };
98
+ }
99
+ default:
100
+ return { label: entry.id, role: "omp.message" };
101
+ }
102
+ }
103
+
104
+ /** Local `HH:MM` of an entry's persisted timestamp; empty when unparsable. */
105
+ function entryTime(entry: TranscriptEntry): string {
106
+ const date = new Date(entry.timestamp);
107
+ if (Number.isNaN(date.getTime())) return "";
108
+ return `${String(date.getHours()).padStart(2, "0")}:${String(date.getMinutes()).padStart(2, "0")}`;
109
+ }
110
+
111
+ /**
112
+ * One list item per outline target, keyed by the turn's opening entry id:
113
+ * the plain turn summary as label, `detail` (or the entry time) on the right.
114
+ */
115
+ export function turnItem(target: OutlineTarget, detail?: TspText): NativeNode {
116
+ const entry = target.entries[0]!;
117
+ const { label, role } = turnSummary(entry);
118
+ const time = entryTime(entry);
119
+ return node(
120
+ "item",
121
+ {
122
+ label: label || "(empty)",
123
+ detail: detail ?? (time || undefined),
124
+ value: detail !== undefined && time ? [span(time, "dim")] : undefined,
125
+ role,
126
+ tone: target.isUserTurn ? "user" : undefined,
127
+ },
128
+ undefined,
129
+ target.turnId,
130
+ );
131
+ }
132
+
133
+ /** Preview of a turn's text: prose as markdown, command/tool output as a plain code block. */
134
+ export function turnPreview(target: OutlineTarget, content: string): NativeNode {
135
+ if (!content.trim()) return text([span("(no text)", "muted")]);
136
+ const message = transcriptEntryMessage(target.entries[0]!);
137
+ switch (message?.role) {
138
+ case "assistant":
139
+ // A pure tool turn previews its joined commands/results, not prose.
140
+ return message.content.some(part => part.type === "text" && part.text.trim())
141
+ ? md(content)
142
+ : code(content, { wrap: true });
143
+ case "user":
144
+ case "custom":
145
+ case "hookMessage":
146
+ case "compactionSummary":
147
+ case "branchSummary":
148
+ return md(content);
149
+ default:
150
+ return code(content, { wrap: true });
151
+ }
152
+ }
153
+
154
+ /** Argument keys naming what a tool call works on, in preference order. */
155
+ const TOOL_TARGET_KEYS = ["command", "pattern", "path", "file_path", "query", "url", "code"] as const;
156
+
157
+ /** One-line label of a tool call: its name and main argument (`grep ack`, `bash ls -la`). */
158
+ function toolCallLabel(name: string, args: Record<string, unknown> | undefined): string {
159
+ for (const key of TOOL_TARGET_KEYS) {
160
+ const value = args?.[key];
161
+ if (typeof value === "string" && value.trim()) return `${name} ${firstLine(value)}`;
162
+ }
163
+ return name;
164
+ }
165
+
166
+ /** The timeline pickers' one fact column: a user turn's clock time. */
167
+ export const TIMELINE_COLUMNS: readonly TspPickerColumn[] = [{ id: "at", format: "dim", priority: 1 }];
168
+
169
+ /**
170
+ * The `timeline` picker item of one outline target (rewind, copy), keyed by
171
+ * the turn's opening entry id: user turns carry their `HH:MM`, pure tool turns
172
+ * the tool's role and a `name target` label.
173
+ */
174
+ export function timelineItem(target: OutlineTarget): TspPickerItem {
175
+ const entry = target.entries[0]!;
176
+ const id = target.turnId;
177
+ const summary = turnSummary(entry);
178
+ const label = summary.label || "(empty)";
179
+ if (target.isUserTurn) {
180
+ const at = entryTime(entry);
181
+ return { id, label, node: "user", ...(at ? { facts: { at } } : {}) };
182
+ }
183
+ const message = transcriptEntryMessage(entry);
184
+ switch (message?.role) {
185
+ case "assistant": {
186
+ const prose = message.content.some(part => part.type === "text" && part.text.trim());
187
+ const calls = message.content.filter(part => part.type === "toolCall");
188
+ if (prose || calls.length === 0) return { id, label, node: "assistant" };
189
+ return {
190
+ id,
191
+ label: calls.map(call => toolCallLabel(call.name, call.arguments)).join(" · "),
192
+ node: "tool",
193
+ role: `omp.tool.${calls[0]!.name}`,
194
+ };
195
+ }
196
+ case "toolResult":
197
+ case "bashExecution":
198
+ case "pythonExecution":
199
+ return { id, label, node: "tool", role: summary.role };
200
+ default:
201
+ return { id, label, node: "marker" };
202
+ }
203
+ }
204
+
205
+ /** Timeline picker items for `targets`, reused while `targets` is the same array. */
206
+ export class TimelineItems {
207
+ #memo: { targets: readonly OutlineTarget[]; items: TspPickerItem[] } | undefined;
208
+
209
+ of(targets: readonly OutlineTarget[]): TspPickerItem[] {
210
+ if (this.#memo?.targets !== targets) this.#memo = { targets, items: targets.map(timelineItem) };
211
+ return this.#memo.items;
212
+ }
213
+ }
214
+
215
+ /** Lines of a copy block shown in the native preview; longer blocks copy through omp. */
216
+ const PREVIEW_BLOCK_LINES = 400;
217
+
218
+ /** A block's preview caption: `rust · 12 lines`, `quote`, `link · docs`. */
219
+ function blockCaption(block: CopyBlock): string {
220
+ if (block.href || block.kind === "quote") return block.label;
221
+ const lines = block.content.split("\n").length;
222
+ const kind = block.kind === "code" ? (block.language ?? "code") : block.label;
223
+ return `${kind}${theme.sep.dot}${lines} line${lines === 1 ? "" : "s"}`;
224
+ }
225
+
226
+ /** A block's preview content: the link, the quote as markdown, else highlighted code. */
227
+ function blockBody(block: CopyBlock): NativeNode {
228
+ if (block.href) return text([span(block.href, "link")], { wrap: "char", href: block.href });
229
+ if (block.kind === "quote") return md(block.content);
230
+ const lines = block.content.split("\n");
231
+ if (lines.length <= PREVIEW_BLOCK_LINES) return code(block.content, { lang: block.language, wrap: true });
232
+ const shown = lines.slice(0, PREVIEW_BLOCK_LINES).join("\n");
233
+ return node("col", { gap: "xs" }, [
234
+ code(shown, { lang: block.language, wrap: true }),
235
+ text([span(`… +${lines.length - PREVIEW_BLOCK_LINES} more lines`, "dim")]),
236
+ ]);
237
+ }
238
+
239
+ /**
240
+ * One borderless preview section of the copy picker. A click copies it in the
241
+ * terminal (`copy`) when the preview holds the whole text, else asks omp to
242
+ * (`pick`); link sections also offer `open`. The focused block's section takes
243
+ * the `omp.picker.block.focused` role and the accent tone.
244
+ */
245
+ function previewSection(
246
+ key: string,
247
+ caption: string,
248
+ body: NativeNode,
249
+ content: string,
250
+ focused: boolean,
251
+ href?: string,
252
+ ): NativeNode {
253
+ const whole = content.split("\n").length <= PREVIEW_BLOCK_LINES;
254
+ return node(
255
+ "section",
256
+ {
257
+ head: caption,
258
+ role: focused ? "omp.picker.block.focused" : "omp.picker.block",
259
+ ...(focused ? { tone: "accent" as const } : {}),
260
+ ...(href ? { href } : {}),
261
+ actions: { click: whole ? "copy" : "pick", ...(href ? { menu: ["copy", "open"] } : {}) },
262
+ },
263
+ [body],
264
+ key,
265
+ );
266
+ }
42
267
 
43
268
  export interface CopySelectorDeps {
44
269
  ui: TUI;
@@ -120,6 +345,15 @@ export class CopySelectorComponent implements Component {
120
345
  /** True while older history is still unreplayed. */
121
346
  #truncated = false;
122
347
 
348
+ /** Last described root and the state it was built from. */
349
+ #native: { memo: string; targets: OutlineTarget[]; blocks: CopyBlock[] | undefined; node: NativeNode } | undefined;
350
+ /** Transcript list items, rebuilt only when the replayed targets change. */
351
+ #nativeItems: { targets: OutlineTarget[]; truncated: boolean; items: NativeNode[] } | undefined;
352
+ /** Timeline picker items of the replayed targets. */
353
+ #pickerItems = new TimelineItems();
354
+ /** Last described picker and the state it was built from. */
355
+ #picker: { memo: string; targets: OutlineTarget[]; blocks: CopyBlock[] | undefined; node: NativeNode } | undefined;
356
+
123
357
  constructor(
124
358
  entries: TranscriptEntry[],
125
359
  private readonly deps: CopySelectorDeps,
@@ -213,8 +447,7 @@ export class CopySelectorComponent implements Component {
213
447
  return;
214
448
  }
215
449
  if (matchesSelectCancel(data) || matchesKey(data, "escape")) {
216
- if (this.#blocks) this.#ascend();
217
- else this.deps.onCancel();
450
+ this.#escape();
218
451
  return;
219
452
  }
220
453
  if (matchesAppToolsExpand(data)) {
@@ -232,14 +465,7 @@ export class CopySelectorComponent implements Component {
232
465
  return;
233
466
  }
234
467
  if (matchesKey(data, "right")) {
235
- if (this.#blocks) return;
236
- const target = this.#targets[this.#selected];
237
- if (!target) return;
238
- const blocks = this.#blocksFor(target);
239
- if (blocks.length === 0) return;
240
- this.#blocks = blocks;
241
- this.#blockSelected = 0;
242
- this.deps.requestRender();
468
+ this.#descend();
243
469
  return;
244
470
  }
245
471
  if (matchesKey(data, "left")) {
@@ -251,20 +477,11 @@ export class CopySelectorComponent implements Component {
251
477
  return;
252
478
  }
253
479
  if (data === "o" || data === "O") {
254
- const block = this.#blocks?.[this.#blockSelected];
255
- if (block?.href && this.deps.onOpen) this.deps.onOpen(block.href, block.label);
480
+ this.#openSelectedBlock();
256
481
  return;
257
482
  }
258
483
  if (matchesKey(data, "enter") || matchesKey(data, "return") || data === "\n") {
259
- if (this.#blocks) {
260
- const block = this.#blocks[this.#blockSelected];
261
- if (block) this.deps.onPick(block.content, block.label, { entry: block.entry, block });
262
- return;
263
- }
264
- const target = this.#targets[this.#selected];
265
- if (!target) return;
266
- const item = targetCopy(target, this.#blocksFor(target));
267
- this.deps.onPick(item.content, item.label, { entry: target.entries[0]! });
484
+ this.#pickOutlined();
268
485
  return;
269
486
  }
270
487
  // Page/home/end/shift+arrow scrolling without moving the selection.
@@ -273,6 +490,42 @@ export class CopySelectorComponent implements Component {
273
490
  }
274
491
  }
275
492
 
493
+ /** Esc: leave the block view, else close. */
494
+ #escape(): void {
495
+ if (this.#blocks) this.#ascend();
496
+ else this.deps.onCancel();
497
+ }
498
+
499
+ /** Right: replace the outlined turn with its inner blocks. */
500
+ #descend(): void {
501
+ if (this.#blocks) return;
502
+ const target = this.#targets[this.#selected];
503
+ if (!target) return;
504
+ const blocks = this.#blocksFor(target);
505
+ if (blocks.length === 0) return;
506
+ this.#blocks = blocks;
507
+ this.#blockSelected = 0;
508
+ this.deps.requestRender();
509
+ }
510
+
511
+ /** Enter: pick the outlined block, or the outlined turn's text. */
512
+ #pickOutlined(): void {
513
+ if (this.#blocks) {
514
+ const block = this.#blocks[this.#blockSelected];
515
+ if (block) this.deps.onPick(block.content, block.label, { entry: block.entry, block });
516
+ return;
517
+ }
518
+ const target = this.#targets[this.#selected];
519
+ if (!target) return;
520
+ const item = targetCopy(target, this.#blocksFor(target));
521
+ this.deps.onPick(item.content, item.label, { entry: target.entries[0]! });
522
+ }
523
+
524
+ #openSelectedBlock(): void {
525
+ const block = this.#blocks?.[this.#blockSelected];
526
+ if (block?.href && this.deps.onOpen) this.deps.onOpen(block.href, block.label);
527
+ }
528
+
276
529
  #ascend(): void {
277
530
  this.#blocks = undefined;
278
531
  this.#blockSelected = 0;
@@ -324,6 +577,275 @@ export class CopySelectorComponent implements Component {
324
577
  }
325
578
  }
326
579
 
580
+ // ========================================================================
581
+ // Native
582
+ // ========================================================================
583
+
584
+ /** A `picker` is its own sheet: the backend mounts it in `layer` without a wrapper. */
585
+ nativeSheet(cx: DescribeContext): boolean {
586
+ return cx.supports("picker");
587
+ }
588
+
589
+ handleNativeEvent(event: NativeUiEvent): void {
590
+ const picked = pickerEvent(event);
591
+ if (picked) {
592
+ this.#handlePickerEvent(picked);
593
+ return;
594
+ }
595
+ if (event.type === "action" && event.act === "pick") {
596
+ // A preview section too long to copy in the terminal: omp copies the full text.
597
+ this.#pickSection(event.key);
598
+ return;
599
+ }
600
+ if (event.type === "action") {
601
+ if (event.act === "blocks") this.#descend();
602
+ else if (event.act === "back") this.#ascend();
603
+ else if (event.act === "open") this.#openSelectedBlock();
604
+ else if (event.act === "copy") this.#pickOutlined();
605
+ return;
606
+ }
607
+ if (event.type !== "select" && event.type !== "activate") return;
608
+ // A click on an item outlines it and picks it, as Enter would.
609
+ if (event.key === "list" && !this.#blocks) {
610
+ if (event.item === EARLIER_TURNS_KEY) {
611
+ this.#loadFullHistory();
612
+ return;
613
+ }
614
+ const index = this.#targets.findIndex(target => target.turnId === event.item);
615
+ if (index < 0) return;
616
+ this.#selected = index;
617
+ } else if (event.key === "blocks" && this.#blocks) {
618
+ const index = Number(event.item);
619
+ if (!Number.isInteger(index) || !this.#blocks[index]) return;
620
+ this.#blockSelected = index;
621
+ } else {
622
+ return;
623
+ }
624
+ this.deps.requestRender();
625
+ this.#pickOutlined();
626
+ }
627
+
628
+ /** Row click selects a turn (leaving the block view), a second click copies it; the action bar runs its key. */
629
+ #handlePickerEvent(event: PickerEvent): void {
630
+ if (event.kind === "action") {
631
+ switch (event.act) {
632
+ case "pick":
633
+ this.#pickOutlined();
634
+ return;
635
+ case "blocks":
636
+ this.#descend();
637
+ return;
638
+ case "open-link":
639
+ this.#openSelectedBlock();
640
+ return;
641
+ case "earlier":
642
+ this.#loadFullHistory();
643
+ return;
644
+ case "close":
645
+ this.#escape();
646
+ return;
647
+ default:
648
+ return;
649
+ }
650
+ }
651
+ const index = this.#targets.findIndex(target => target.turnId === event.item);
652
+ if (index < 0) return;
653
+ if (index !== this.#selected || this.#blocks) {
654
+ this.#selected = index;
655
+ this.#blocks = undefined;
656
+ this.#blockSelected = 0;
657
+ this.deps.requestRender();
658
+ }
659
+ if (event.kind === "activate") this.#pickOutlined();
660
+ }
661
+
662
+ /** Pick the preview section keyed `key` (`whole` or `b<index>`) of the selected turn. */
663
+ #pickSection(key: string): void {
664
+ const target = this.#targets[this.#selected];
665
+ if (!target) return;
666
+ const blocks = this.#blocksFor(target);
667
+ if (key === "whole") {
668
+ const item = targetCopy(target, blocks);
669
+ this.deps.onPick(item.content, item.label, { entry: target.entries[0]! });
670
+ return;
671
+ }
672
+ const block = key.startsWith("b") ? blocks[Number(key.slice(1))] : undefined;
673
+ if (block) this.deps.onPick(block.content, block.label, { entry: block.entry, block });
674
+ }
675
+
676
+ describe(cx: DescribeContext): NativeNode {
677
+ return cx.supports("picker") ? this.#describePicker() : this.#describeCard();
678
+ }
679
+
680
+ /**
681
+ * The `timeline` picker: the turns as rows, the selected turn's blocks as
682
+ * preview sections (the whole message first), each copied by a click. In
683
+ * the block view the focused block's section takes the
684
+ * `omp.picker.block.focused` role and the preview owns the focus.
685
+ */
686
+ #describePicker(): NativeNode {
687
+ const memo = `${this.#selected}|${this.#blockSelected}|${this.#truncated}`;
688
+ const cached = this.#picker;
689
+ if (cached?.memo === memo && cached.targets === this.#targets && cached.blocks === this.#blocks) {
690
+ return cached.node;
691
+ }
692
+ const target = this.#targets[this.#selected];
693
+ const blocks = target ? this.#blocksFor(target) : [];
694
+ const descended = this.#blocks !== undefined;
695
+ const focused = this.#blocks?.[this.#blockSelected];
696
+ const action = this.deps.actionLabel ?? "copy";
697
+ const props: TspPickerProps = {
698
+ title: this.deps.title ?? "Copy",
699
+ ...(this.deps.title ? {} : { subtitle: "Pick what to put on the clipboard" }),
700
+ icon: "clipboard",
701
+ noun: "turns",
702
+ size: "lg",
703
+ layout: "timeline",
704
+ preview: "side",
705
+ query: null,
706
+ columns: TIMELINE_COLUMNS,
707
+ items: this.#pickerItems.of(this.#targets),
708
+ selected: target?.turnId ?? null,
709
+ total: this.#targets.length,
710
+ empty: "Nothing to copy",
711
+ focus: descended ? "preview" : "list",
712
+ actions: compact([
713
+ pickerAction("pick", `${action[0]!.toUpperCase()}${action.slice(1)}`, "enter", { primary: true }),
714
+ !descended && blocks.length > 0 ? pickerAction("blocks", "Blocks", "right") : undefined,
715
+ focused?.href && this.deps.onOpen ? pickerAction("open-link", "Open link", "o") : undefined,
716
+ !descended && this.#truncated ? pickerAction("earlier", "Earlier turns", "a") : undefined,
717
+ descended ? { ...CLOSE_ACTION, label: "Back" } : CLOSE_ACTION,
718
+ ]),
719
+ };
720
+ const preview: NativeNode[] = [];
721
+ if (target) {
722
+ const whole = targetCopy(target, blocks);
723
+ if (whole.content.trim()) {
724
+ preview.push(
725
+ previewSection("whole", "Whole message", turnPreview(target, whole.content), whole.content, false),
726
+ );
727
+ }
728
+ blocks.forEach((block, index) => {
729
+ preview.push(
730
+ previewSection(
731
+ `b${index}`,
732
+ blockCaption(block),
733
+ blockBody(block),
734
+ block.content,
735
+ descended && index === this.#blockSelected,
736
+ block.href,
737
+ ),
738
+ );
739
+ });
740
+ }
741
+ const root = picker(props, preview);
742
+ this.#picker = { memo, targets: this.#targets, blocks: this.#blocks, node: root };
743
+ return root;
744
+ }
745
+
746
+ #describeCard(): NativeNode {
747
+ const memo = `${this.#selected}|${this.#blockSelected}|${this.#truncated}`;
748
+ const cached = this.#native;
749
+ if (cached?.memo === memo && cached.targets === this.#targets && cached.blocks === this.#blocks) {
750
+ return cached.node;
751
+ }
752
+ const target = this.#targets[this.#selected];
753
+ const action = this.deps.actionLabel ?? "copy";
754
+ const upDown = actionHint(["tui.select.up", "tui.select.down"], this.#blocks ? "block" : "step");
755
+ const cancel = actionHint("tui.select.cancel", this.#blocks ? "back" : "close");
756
+ const children: NativeChild[] = [];
757
+ let hints: (NativeHint | undefined)[];
758
+ if (this.#blocks && target) {
759
+ const block = this.#blocks[this.#blockSelected];
760
+ children.push(
761
+ row([text([span("‹ back", "accent")], { actions: { click: "back" } })], { gap: "md" }),
762
+ node(
763
+ "list",
764
+ { selected: String(this.#blockSelected), empty: "No blocks" },
765
+ this.#blocks.map((item, index) => {
766
+ const lines = item.content.split("\n").length;
767
+ return node(
768
+ "item",
769
+ { label: item.label, detail: `${lines} line${lines === 1 ? "" : "s"}` },
770
+ undefined,
771
+ String(index),
772
+ );
773
+ }),
774
+ "blocks",
775
+ ),
776
+ );
777
+ if (block) children.push(this.#blockPreview(block, action));
778
+ hints = [
779
+ upDown,
780
+ { keys: ["left", ...(cancel?.keys ?? [])], label: "back" },
781
+ { keys: ["enter"], label: action },
782
+ block?.href && this.deps.onOpen ? { keys: ["o"], label: "open" } : undefined,
783
+ ];
784
+ } else {
785
+ const blocks = target ? this.#blocksFor(target) : [];
786
+ children.push(
787
+ node(
788
+ "list",
789
+ { selected: target?.turnId ?? null, virtual: true, max: 0.5, empty: "Nothing to copy" },
790
+ this.#transcriptItems(),
791
+ "list",
792
+ ),
793
+ );
794
+ if (target) {
795
+ const item = targetCopy(target, blocks);
796
+ const body: NativeChild[] = [turnPreview(target, item.content)];
797
+ if (blocks.length > 0) {
798
+ body.push(
799
+ text([span(`${blocks.length} block${blocks.length === 1 ? "" : "s"} ›`, "accent")], {
800
+ actions: { click: "blocks" },
801
+ }),
802
+ );
803
+ }
804
+ children.push(node("section", { head: [span(item.label, "strong")] }, body, "preview"));
805
+ }
806
+ hints = [
807
+ upDown,
808
+ blocks.length > 0 ? { keys: ["right"], label: "blocks" } : undefined,
809
+ { keys: ["enter"], label: action },
810
+ this.#truncated ? { keys: ["a"], label: "earlier turns" } : undefined,
811
+ cancel,
812
+ ];
813
+ }
814
+ children.push(hintsRow(hints));
815
+ const head: TspText = this.deps.title ?? [
816
+ span(`${theme.cmd.copy} `),
817
+ span("Copy", "strong"),
818
+ span(`${theme.sep.dot}pick what to put on the clipboard`, "dim"),
819
+ ];
820
+ const root = overlayCard("omp.overlay.copy", head, children);
821
+ this.#native = { memo, targets: this.#targets, blocks: this.#blocks, node: root };
822
+ return root;
823
+ }
824
+
825
+ #transcriptItems(): NativeNode[] {
826
+ const cached = this.#nativeItems;
827
+ if (cached?.targets === this.#targets && cached.truncated === this.#truncated) return cached.items;
828
+ const items = this.#targets.map(target => turnItem(target));
829
+ if (this.#truncated) items.unshift(earlierTurnsItem);
830
+ this.#nativeItems = { targets: this.#targets, truncated: this.#truncated, items };
831
+ return items;
832
+ }
833
+
834
+ /** The selected block in full: highlighted code, a link, a quote, or plain output. */
835
+ #blockPreview(block: CopyBlock, action: string): NativeNode {
836
+ let body: NativeNode;
837
+ if (block.href) body = text([span(block.href, "link")], { wrap: "char" });
838
+ else if (block.kind === "quote") body = md(block.content);
839
+ else body = code(block.content, { lang: block.language, wrap: true });
840
+ const controls: NativeChild[] = [
841
+ text([span(`${theme.cmd.copy} ${action}`, "accent")], { actions: { click: "copy" } }),
842
+ ];
843
+ if (block.href && this.deps.onOpen) {
844
+ controls.push(text([span(`${theme.cmd.share} open`, "accent")], { actions: { click: "open" } }));
845
+ }
846
+ return node("section", { head: [span(block.label, "strong")] }, [body, row(controls, { gap: "md" })], "preview");
847
+ }
848
+
327
849
  // ========================================================================
328
850
  // Render
329
851
  // ========================================================================
@@ -530,7 +1052,7 @@ function pushMarkdownBlocks(blocks: CopyBlock[], text: string, entry: Transcript
530
1052
  }
531
1053
 
532
1054
  /** Inner blocks of one turn: markdown code/quotes, commands, and tool output. */
533
- function collectBlocks(entries: readonly TranscriptEntry[]): CopyBlock[] {
1055
+ export function collectBlocks(entries: readonly TranscriptEntry[]): CopyBlock[] {
534
1056
  const blocks: CopyBlock[] = [];
535
1057
  for (const entry of entries) {
536
1058
  const message = transcriptEntryMessage(entry);
@@ -577,7 +1099,7 @@ function collectBlocks(entries: readonly TranscriptEntry[]): CopyBlock[] {
577
1099
  }
578
1100
 
579
1101
  /** Clipboard payload for a whole turn, falling back to its blocks when the turn has no prose. */
580
- function targetCopy(target: OutlineTarget, blocks: readonly CopyBlock[]): { content: string; label: string } {
1102
+ export function targetCopy(target: OutlineTarget, blocks: readonly CopyBlock[]): { content: string; label: string } {
581
1103
  const entry = target.entries[0]!;
582
1104
  const message = transcriptEntryMessage(entry);
583
1105
  switch (message?.role) {