@oh-my-pi/pi-tui 18.4.3 → 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 (447) hide show
  1. package/CHANGELOG.md +36 -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/todo-reminder.d.ts +4 -1
  22. package/dist/types/chat/tool-execution.d.ts +25 -1
  23. package/dist/types/chat/transcript-actions.d.ts +37 -0
  24. package/dist/types/chat/transcript-entry.d.ts +3 -1
  25. package/dist/types/chat/user-message.d.ts +25 -0
  26. package/dist/types/chrome/collab-qrcode.d.ts +7 -0
  27. package/dist/types/chrome/context-thresholds.d.ts +11 -0
  28. package/dist/types/chrome/countdown-timer.d.ts +9 -3
  29. package/dist/types/chrome/diff.d.ts +12 -0
  30. package/dist/types/chrome/dynamic-border.d.ts +3 -0
  31. package/dist/types/chrome/format.d.ts +6 -0
  32. package/dist/types/chrome/message-divider.d.ts +13 -0
  33. package/dist/types/chrome/message-frame.d.ts +10 -0
  34. package/dist/types/chrome/message-notice.d.ts +23 -0
  35. package/dist/types/chrome/overlay-box.d.ts +11 -2
  36. package/dist/types/chrome/qrcode.d.ts +7 -0
  37. package/dist/types/chrome/segment-track.d.ts +7 -0
  38. package/dist/types/chrome/selector-helpers.d.ts +0 -6
  39. package/dist/types/chrome/status-notice.d.ts +14 -0
  40. package/dist/types/chrome/streaming-panel.d.ts +3 -0
  41. package/dist/types/chrome/tool-activity.d.ts +3 -0
  42. package/dist/types/chrome/transcript-container.d.ts +13 -0
  43. package/dist/types/chrome/transcript-status.d.ts +15 -3
  44. package/dist/types/components/box.d.ts +7 -0
  45. package/dist/types/components/disclosure.d.ts +9 -0
  46. package/dist/types/components/editor.d.ts +37 -0
  47. package/dist/types/components/form.d.ts +14 -0
  48. package/dist/types/components/image.d.ts +7 -0
  49. package/dist/types/components/input.d.ts +20 -1
  50. package/dist/types/components/key-value-list.d.ts +3 -0
  51. package/dist/types/components/layout/geometry.d.ts +16 -0
  52. package/dist/types/components/layout/row.d.ts +7 -0
  53. package/dist/types/components/layout/split-pane.d.ts +8 -0
  54. package/dist/types/components/layout/stack.d.ts +6 -0
  55. package/dist/types/components/loader.d.ts +48 -0
  56. package/dist/types/components/markdown.d.ts +8 -0
  57. package/dist/types/components/metric.d.ts +7 -0
  58. package/dist/types/components/progress-bar.d.ts +3 -0
  59. package/dist/types/components/scroll-view.d.ts +6 -0
  60. package/dist/types/components/section.d.ts +3 -0
  61. package/dist/types/components/select-list.d.ts +33 -0
  62. package/dist/types/components/settings-list.d.ts +68 -0
  63. package/dist/types/components/spacer.d.ts +4 -0
  64. package/dist/types/components/tab-bar.d.ts +9 -0
  65. package/dist/types/components/table.d.ts +7 -0
  66. package/dist/types/components/text.d.ts +6 -0
  67. package/dist/types/components/tree-view.d.ts +9 -0
  68. package/dist/types/components/truncated-text.d.ts +3 -0
  69. package/dist/types/components/wizard-step.d.ts +6 -0
  70. package/dist/types/hotkeys-markdown.d.ts +24 -0
  71. package/dist/types/index.d.ts +5 -0
  72. package/dist/types/latex-block.d.ts +7 -0
  73. package/dist/types/native/apply.d.ts +39 -0
  74. package/dist/types/native/backend.d.ts +94 -0
  75. package/dist/types/native/blobs.d.ts +29 -0
  76. package/dist/types/native/describe.d.ts +56 -0
  77. package/dist/types/native/encode.d.ts +50 -0
  78. package/dist/types/native/icons.d.ts +18 -0
  79. package/dist/types/native/memo.d.ts +27 -0
  80. package/dist/types/native/node.d.ts +107 -0
  81. package/dist/types/native/overlay.d.ts +64 -0
  82. package/dist/types/native/picker.d.ts +124 -0
  83. package/dist/types/native/reconcile.d.ts +41 -0
  84. package/dist/types/native/settle.d.ts +11 -0
  85. package/dist/types/native/spans.d.ts +21 -0
  86. package/dist/types/native/state.d.ts +6 -0
  87. package/dist/types/native/tone.d.ts +24 -0
  88. package/dist/types/overlays/advisor-config.d.ts +20 -0
  89. package/dist/types/overlays/agent-hub-renderer.d.ts +27 -11
  90. package/dist/types/overlays/agent-hub.d.ts +10 -0
  91. package/dist/types/overlays/agent-transcript-viewer.d.ts +7 -0
  92. package/dist/types/overlays/agents-hub.d.ts +8 -0
  93. package/dist/types/overlays/annotation-overlay.d.ts +3 -0
  94. package/dist/types/overlays/ask-dialog.d.ts +23 -0
  95. package/dist/types/overlays/bordered-loader.d.ts +3 -0
  96. package/dist/types/overlays/btw-history-panel.d.ts +9 -0
  97. package/dist/types/overlays/btw-panel.d.ts +3 -0
  98. package/dist/types/overlays/cleanse-panel.d.ts +5 -2
  99. package/dist/types/overlays/composer-shape-preview.d.ts +7 -1
  100. package/dist/types/overlays/copy-selector.d.ts +38 -0
  101. package/dist/types/overlays/error-banner.d.ts +9 -0
  102. package/dist/types/overlays/extensions/extension-list.d.ts +1 -0
  103. package/dist/types/overlays/history-search.d.ts +18 -0
  104. package/dist/types/overlays/hook-editor.d.ts +28 -1
  105. package/dist/types/overlays/hook-input.d.ts +3 -0
  106. package/dist/types/overlays/hook-selector.d.ts +10 -0
  107. package/dist/types/overlays/hub-frame.d.ts +18 -0
  108. package/dist/types/overlays/jobs-panel.d.ts +24 -0
  109. package/dist/types/overlays/login-dialog.d.ts +10 -0
  110. package/dist/types/overlays/logout-account-selector.d.ts +4 -0
  111. package/dist/types/overlays/mcp-add-wizard.d.ts +4 -0
  112. package/dist/types/overlays/model-browser.d.ts +69 -0
  113. package/dist/types/overlays/model-hub.d.ts +7 -0
  114. package/dist/types/overlays/model-picker.d.ts +7 -0
  115. package/dist/types/overlays/move-overlay.d.ts +3 -0
  116. package/dist/types/overlays/oauth-selector.d.ts +4 -0
  117. package/dist/types/overlays/omfg-panel.d.ts +3 -0
  118. package/dist/types/overlays/pause-screen.d.ts +12 -0
  119. package/dist/types/overlays/plan-review-overlay.d.ts +5 -0
  120. package/dist/types/overlays/plan-save-overlay.d.ts +10 -0
  121. package/dist/types/overlays/plugin-selector.d.ts +4 -0
  122. package/dist/types/overlays/plugin-settings.d.ts +30 -0
  123. package/dist/types/overlays/queue-mode-selector.d.ts +4 -0
  124. package/dist/types/overlays/reset-usage-selector.d.ts +8 -0
  125. package/dist/types/overlays/rewind-selector.d.ts +5 -0
  126. package/dist/types/overlays/session-info-overlay.d.ts +19 -1
  127. package/dist/types/overlays/session-selector.d.ts +64 -1
  128. package/dist/types/overlays/settings-defs.d.ts +14 -0
  129. package/dist/types/overlays/settings-selector.d.ts +21 -0
  130. package/dist/types/overlays/show-images-selector.d.ts +4 -0
  131. package/dist/types/overlays/snapcompact-shape-preview.d.ts +2 -0
  132. package/dist/types/overlays/stats-notice.d.ts +8 -0
  133. package/dist/types/overlays/theme-selector.d.ts +11 -0
  134. package/dist/types/overlays/thinking-selector.d.ts +4 -0
  135. package/dist/types/overlays/tree-selector.d.ts +60 -2
  136. package/dist/types/overlays/usage-dashboard.d.ts +26 -3
  137. package/dist/types/overlays/usage-row.d.ts +36 -2
  138. package/dist/types/prompt/attachment-chips.d.ts +8 -0
  139. package/dist/types/prompt/composer-attachments.d.ts +15 -3
  140. package/dist/types/prompt/composer.d.ts +18 -1
  141. package/dist/types/prompt/custom-editor.d.ts +48 -2
  142. package/dist/types/prompt/editor-top-gap.d.ts +2 -0
  143. package/dist/types/prompt/macos-spelling.d.ts +6 -0
  144. package/dist/types/prompt/magic-keywords.d.ts +9 -0
  145. package/dist/types/prompt/queued-messages.d.ts +23 -0
  146. package/dist/types/prompt/skill-tokens.d.ts +4 -2
  147. package/dist/types/prompt/welcome.d.ts +16 -0
  148. package/dist/types/render/code-cell.d.ts +10 -0
  149. package/dist/types/render/file-list.d.ts +8 -0
  150. package/dist/types/render/hyperlink.d.ts +14 -0
  151. package/dist/types/render/output-block.d.ts +43 -0
  152. package/dist/types/render/output-pane.d.ts +17 -0
  153. package/dist/types/render/tool-card.d.ts +8 -0
  154. package/dist/types/render/tree-list.d.ts +8 -0
  155. package/dist/types/render/width-aware-text.d.ts +7 -0
  156. package/dist/types/setup/scenes/outro.d.ts +7 -0
  157. package/dist/types/setup/scenes/sign-in.d.ts +7 -0
  158. package/dist/types/setup/scenes/splash.d.ts +8 -0
  159. package/dist/types/setup/scenes/types.d.ts +6 -1
  160. package/dist/types/setup/wizard-overlay.d.ts +9 -0
  161. package/dist/types/status-line/component.d.ts +49 -5
  162. package/dist/types/status-line/context-usage.d.ts +20 -0
  163. package/dist/types/status-line/footer.d.ts +8 -0
  164. package/dist/types/status-line/metrics.d.ts +1 -2
  165. package/dist/types/status-line/segments.d.ts +10 -1
  166. package/dist/types/status-line/types.d.ts +40 -1
  167. package/dist/types/terminal-capabilities.d.ts +1 -1
  168. package/dist/types/terminal.d.ts +27 -0
  169. package/dist/types/theme/defaults/index.d.ts +100 -0
  170. package/dist/types/theme/schema.d.ts +2 -0
  171. package/dist/types/theme/shimmer.d.ts +8 -0
  172. package/dist/types/theme/theme-class.d.ts +2 -0
  173. package/dist/types/theme/theme.d.ts +33 -1
  174. package/dist/types/tools/ask.d.ts +3 -0
  175. package/dist/types/tools/ast-edit.d.ts +10 -1
  176. package/dist/types/tools/ast-grep.d.ts +10 -1
  177. package/dist/types/tools/autoresearch.d.ts +21 -4
  178. package/dist/types/tools/bash.d.ts +30 -1
  179. package/dist/types/tools/cfg-render.d.ts +5 -1
  180. package/dist/types/tools/debug.d.ts +3 -1
  181. package/dist/types/tools/default-renderer.d.ts +6 -1
  182. package/dist/types/tools/edit.d.ts +14 -1
  183. package/dist/types/tools/eval.d.ts +12 -1
  184. package/dist/types/tools/fetch.d.ts +16 -1
  185. package/dist/types/tools/find.d.ts +15 -1
  186. package/dist/types/tools/github.d.ts +3 -0
  187. package/dist/types/tools/glob.d.ts +14 -1
  188. package/dist/types/tools/goal.d.ts +3 -1
  189. package/dist/types/tools/grep.d.ts +14 -1
  190. package/dist/types/tools/grouped-file-output.d.ts +58 -0
  191. package/dist/types/tools/json-tree.d.ts +20 -0
  192. package/dist/types/tools/lsp.d.ts +3 -10
  193. package/dist/types/tools/mcp.d.ts +16 -1
  194. package/dist/types/tools/memory.d.ts +26 -0
  195. package/dist/types/tools/native-view.d.ts +90 -0
  196. package/dist/types/tools/proc-render.d.ts +7 -1
  197. package/dist/types/tools/read.d.ts +23 -1
  198. package/dist/types/tools/renderer.d.ts +57 -0
  199. package/dist/types/tools/report-tool-issue.d.ts +3 -0
  200. package/dist/types/tools/resolve.d.ts +14 -1
  201. package/dist/types/tools/task.d.ts +3 -0
  202. package/dist/types/tools/think.d.ts +3 -0
  203. package/dist/types/tools/todo.d.ts +12 -0
  204. package/dist/types/tools/vibe.d.ts +3 -1
  205. package/dist/types/tools/wait.d.ts +3 -0
  206. package/dist/types/tools/web-search.d.ts +8 -0
  207. package/dist/types/tools/write.d.ts +14 -1
  208. package/dist/types/tools/xdev.d.ts +13 -0
  209. package/dist/types/tui.d.ts +49 -0
  210. package/package.json +9 -9
  211. package/src/apps/autoresearch-dashboard.ts +362 -4
  212. package/src/apps/cleanse-board.ts +219 -1
  213. package/src/apps/cleanse-picker.ts +9 -1
  214. package/src/apps/debug/log-viewer.ts +15 -15
  215. package/src/apps/debug/protocol-probe.ts +2 -20
  216. package/src/apps/git/avatar.ts +2 -22
  217. package/src/apps/git/git-tui.ts +406 -59
  218. package/src/apps/git/help.ts +130 -0
  219. package/src/apps/git/sidebar.ts +491 -0
  220. package/src/apps/live-visualizer.ts +65 -0
  221. package/src/apps/ps-top.ts +448 -17
  222. package/src/apps/session-picker.ts +1 -0
  223. package/src/apps/setup-model-picker.ts +5 -1
  224. package/src/apps/standalone-picker.ts +55 -1
  225. package/src/autocomplete.ts +44 -1
  226. package/src/chat/advisor-message.ts +72 -3
  227. package/src/chat/assistant-message.ts +346 -11
  228. package/src/chat/background-tan-message.ts +4 -4
  229. package/src/chat/bash-execution.ts +81 -0
  230. package/src/chat/cache-invalidation-marker.ts +8 -0
  231. package/src/chat/chat-transcript-builder.ts +37 -6
  232. package/src/chat/collab-prompt-message.ts +15 -3
  233. package/src/chat/compaction-summary-message.ts +74 -1
  234. package/src/chat/custom-message.ts +3 -1
  235. package/src/chat/eval-execution.ts +57 -0
  236. package/src/chat/execution-shared.ts +153 -1
  237. package/src/chat/hook-message.ts +1 -0
  238. package/src/chat/late-diagnostics-message.ts +85 -2
  239. package/src/chat/read-tool-group.ts +341 -1
  240. package/src/chat/served-model-marker.ts +8 -0
  241. package/src/chat/skill-message.ts +63 -0
  242. package/src/chat/stripped-tool-calls-placeholder.ts +18 -10
  243. package/src/chat/todo-reminder.ts +34 -4
  244. package/src/chat/tool-execution.ts +385 -10
  245. package/src/chat/transcript-actions.ts +38 -0
  246. package/src/chat/transcript-browser.ts +2 -9
  247. package/src/chat/transcript-entry.ts +10 -1
  248. package/src/chat/transcript-render-helpers.ts +16 -15
  249. package/src/chat/ttsr-notification.ts +38 -1
  250. package/src/chat/user-message.ts +167 -4
  251. package/src/chrome/collab-qrcode.ts +27 -2
  252. package/src/chrome/context-thresholds.ts +34 -0
  253. package/src/chrome/countdown-timer.ts +24 -1
  254. package/src/chrome/diff.ts +60 -1
  255. package/src/chrome/dynamic-border.ts +7 -0
  256. package/src/chrome/format.ts +12 -0
  257. package/src/chrome/message-divider.ts +33 -1
  258. package/src/chrome/message-frame.ts +53 -10
  259. package/src/chrome/message-notice.ts +71 -0
  260. package/src/chrome/overlay-box.ts +45 -2
  261. package/src/chrome/qrcode.ts +28 -0
  262. package/src/chrome/segment-track.ts +26 -0
  263. package/src/chrome/selector-helpers.ts +1 -17
  264. package/src/chrome/status-notice.ts +46 -0
  265. package/src/chrome/streaming-panel.ts +25 -1
  266. package/src/chrome/tool-activity.ts +20 -0
  267. package/src/chrome/transcript-container.ts +37 -0
  268. package/src/chrome/transcript-status.ts +40 -11
  269. package/src/components/box.ts +51 -0
  270. package/src/components/disclosure.ts +33 -0
  271. package/src/components/editor.ts +216 -27
  272. package/src/components/form.ts +93 -9
  273. package/src/components/image.ts +30 -0
  274. package/src/components/input.ts +77 -26
  275. package/src/components/key-value-list.ts +18 -0
  276. package/src/components/layout/geometry.ts +44 -0
  277. package/src/components/layout/row.ts +30 -0
  278. package/src/components/layout/split-pane.ts +65 -0
  279. package/src/components/layout/stack.ts +27 -0
  280. package/src/components/loader.ts +183 -1
  281. package/src/components/markdown.ts +21 -0
  282. package/src/components/metric.ts +39 -0
  283. package/src/components/progress-bar.ts +28 -0
  284. package/src/components/scroll-view.ts +23 -0
  285. package/src/components/section.ts +16 -0
  286. package/src/components/select-list.ts +174 -25
  287. package/src/components/settings-list.ts +264 -7
  288. package/src/components/spacer.ts +17 -0
  289. package/src/components/tab-bar.ts +42 -0
  290. package/src/components/table.ts +34 -0
  291. package/src/components/text.ts +23 -0
  292. package/src/components/tree-view.ts +65 -0
  293. package/src/components/truncated-text.ts +14 -0
  294. package/src/components/wizard-step.ts +18 -9
  295. package/src/debug-server.ts +14 -7
  296. package/src/hotkeys-markdown.ts +251 -69
  297. package/src/index.ts +6 -0
  298. package/src/latex-block.ts +11 -0
  299. package/src/native/apply.ts +275 -0
  300. package/src/native/backend.ts +682 -0
  301. package/src/native/blobs.ts +70 -0
  302. package/src/native/describe.ts +124 -0
  303. package/src/native/encode.ts +233 -0
  304. package/src/native/icons.ts +184 -0
  305. package/src/native/memo.ts +64 -0
  306. package/src/native/node.ts +112 -0
  307. package/src/native/overlay.ts +131 -0
  308. package/src/native/picker.ts +333 -0
  309. package/src/native/reconcile.ts +1063 -0
  310. package/src/native/settle.ts +23 -0
  311. package/src/native/spans.ts +308 -0
  312. package/src/native/state.ts +28 -0
  313. package/src/native/tone.ts +79 -0
  314. package/src/overlays/advisor-config.ts +420 -0
  315. package/src/overlays/agent-hub-renderer.ts +155 -18
  316. package/src/overlays/agent-hub.ts +1037 -44
  317. package/src/overlays/agent-transcript-viewer.ts +129 -0
  318. package/src/overlays/agents-hub.ts +688 -53
  319. package/src/overlays/annotation-overlay.ts +391 -52
  320. package/src/overlays/ask-dialog.ts +435 -24
  321. package/src/overlays/bordered-loader.ts +17 -0
  322. package/src/overlays/btw-history-panel.ts +182 -0
  323. package/src/overlays/btw-panel.ts +68 -2
  324. package/src/overlays/cleanse-panel.ts +105 -15
  325. package/src/overlays/codex-reset-fireworks.ts +65 -5
  326. package/src/overlays/composer-shape-preview.ts +26 -1
  327. package/src/overlays/copy-selector.ts +545 -23
  328. package/src/overlays/error-banner.ts +44 -0
  329. package/src/overlays/extensions/extension-list.ts +23 -17
  330. package/src/overlays/history-search.ts +175 -33
  331. package/src/overlays/hook-editor.ts +113 -6
  332. package/src/overlays/hook-input.ts +22 -1
  333. package/src/overlays/hook-selector.ts +311 -22
  334. package/src/overlays/hub-frame.ts +80 -0
  335. package/src/overlays/jobs-panel.ts +112 -0
  336. package/src/overlays/login-dialog.ts +183 -1
  337. package/src/overlays/logout-account-selector.ts +102 -4
  338. package/src/overlays/mcp-add-wizard.ts +232 -53
  339. package/src/overlays/model-browser.ts +681 -22
  340. package/src/overlays/model-hub.ts +1402 -78
  341. package/src/overlays/model-picker.ts +175 -1
  342. package/src/overlays/move-overlay.ts +45 -0
  343. package/src/overlays/oauth-selector.ts +227 -23
  344. package/src/overlays/omfg-panel.ts +86 -2
  345. package/src/overlays/pause-screen.ts +55 -4
  346. package/src/overlays/plan-review-overlay.ts +352 -0
  347. package/src/overlays/plan-save-overlay.ts +45 -0
  348. package/src/overlays/plugin-selector.ts +21 -1
  349. package/src/overlays/plugin-settings.ts +195 -5
  350. package/src/overlays/queue-mode-selector.ts +19 -1
  351. package/src/overlays/reset-usage-selector.ts +110 -40
  352. package/src/overlays/rewind-selector.ts +458 -44
  353. package/src/overlays/session-account-selector.ts +1 -1
  354. package/src/overlays/session-info-overlay.ts +137 -1
  355. package/src/overlays/session-selector.ts +807 -46
  356. package/src/overlays/settings-defs.ts +41 -0
  357. package/src/overlays/settings-selector.ts +583 -23
  358. package/src/overlays/show-images-selector.ts +18 -1
  359. package/src/overlays/snapcompact-shape-preview.ts +48 -3
  360. package/src/overlays/stats-notice.ts +31 -0
  361. package/src/overlays/theme-selector.ts +31 -1
  362. package/src/overlays/thinking-selector.ts +22 -1
  363. package/src/overlays/tree-selector.ts +570 -110
  364. package/src/overlays/usage-dashboard.ts +587 -12
  365. package/src/overlays/usage-row.ts +161 -25
  366. package/src/prompt/attachment-chips.ts +60 -0
  367. package/src/prompt/composer-attachments.ts +31 -9
  368. package/src/prompt/composer.ts +55 -2
  369. package/src/prompt/custom-editor.ts +302 -4
  370. package/src/prompt/editor-top-gap.ts +8 -0
  371. package/src/prompt/macos-spelling.ts +24 -9
  372. package/src/prompt/magic-keywords.ts +22 -1
  373. package/src/prompt/model-mention-autocomplete.ts +1 -0
  374. package/src/prompt/queued-messages.ts +101 -0
  375. package/src/prompt/skill-tokens.ts +5 -3
  376. package/src/prompt/welcome.ts +213 -6
  377. package/src/render/code-cell.ts +103 -2
  378. package/src/render/file-list.ts +29 -0
  379. package/src/render/hyperlink.ts +32 -0
  380. package/src/render/output-block.ts +110 -0
  381. package/src/render/output-pane.ts +37 -0
  382. package/src/render/tool-card.ts +137 -0
  383. package/src/render/tree-list.ts +39 -0
  384. package/src/render/width-aware-text.ts +20 -0
  385. package/src/setup/scenes/composer.ts +29 -0
  386. package/src/setup/scenes/glyph.ts +15 -0
  387. package/src/setup/scenes/model.ts +36 -8
  388. package/src/setup/scenes/outro.ts +28 -1
  389. package/src/setup/scenes/sign-in.ts +122 -13
  390. package/src/setup/scenes/splash.ts +28 -1
  391. package/src/setup/scenes/theme.ts +88 -3
  392. package/src/setup/scenes/types.ts +7 -1
  393. package/src/setup/startup-splash.ts +46 -9
  394. package/src/setup/wizard-overlay.ts +140 -23
  395. package/src/status-line/component.ts +398 -16
  396. package/src/status-line/context-usage.ts +279 -50
  397. package/src/status-line/footer.ts +94 -1
  398. package/src/status-line/metrics.ts +3 -6
  399. package/src/status-line/segments.ts +463 -97
  400. package/src/status-line/separators.ts +3 -16
  401. package/src/status-line/types.ts +43 -1
  402. package/src/terminal-capabilities.ts +7 -0
  403. package/src/terminal.ts +130 -2
  404. package/src/theme/defaults/dark-neon-noir.json +100 -0
  405. package/src/theme/defaults/index.ts +2 -0
  406. package/src/theme/defaults/light-prism.json +5 -5
  407. package/src/theme/defaults/onyx.json +62 -62
  408. package/src/theme/schema.ts +15 -0
  409. package/src/theme/shimmer.ts +33 -0
  410. package/src/theme/theme-class.ts +24 -19
  411. package/src/theme/theme.ts +111 -9
  412. package/src/tools/ask.ts +157 -1
  413. package/src/tools/ast-edit.ts +174 -1
  414. package/src/tools/ast-grep.ts +82 -2
  415. package/src/tools/autoresearch.ts +149 -1
  416. package/src/tools/bash.ts +180 -49
  417. package/src/tools/cfg-render.ts +66 -1
  418. package/src/tools/debug.ts +78 -1
  419. package/src/tools/default-renderer.ts +35 -1
  420. package/src/tools/edit.ts +247 -2
  421. package/src/tools/eval.ts +257 -1
  422. package/src/tools/fetch.ts +102 -3
  423. package/src/tools/find.ts +127 -1
  424. package/src/tools/github.ts +119 -10
  425. package/src/tools/glob.ts +116 -1
  426. package/src/tools/goal.ts +56 -1
  427. package/src/tools/grep.ts +83 -2
  428. package/src/tools/grouped-file-output.ts +205 -0
  429. package/src/tools/json-tree.ts +116 -1
  430. package/src/tools/lsp.ts +318 -1
  431. package/src/tools/mcp.ts +80 -1
  432. package/src/tools/memory.ts +93 -11
  433. package/src/tools/native-view.ts +292 -0
  434. package/src/tools/proc-render.ts +221 -1
  435. package/src/tools/read.ts +199 -4
  436. package/src/tools/renderer.ts +61 -0
  437. package/src/tools/report-tool-issue.ts +7 -1
  438. package/src/tools/resolve.ts +63 -1
  439. package/src/tools/task.ts +406 -4
  440. package/src/tools/think.ts +19 -1
  441. package/src/tools/todo.ts +89 -0
  442. package/src/tools/vibe.ts +230 -2
  443. package/src/tools/wait.ts +181 -1
  444. package/src/tools/web-search.ts +113 -1
  445. package/src/tools/write.ts +144 -5
  446. package/src/tools/xdev.ts +64 -1
  447. package/src/tui.ts +294 -3
@@ -1,3 +1,7 @@
1
+ import type { TspSpan } from "@oh-my-pi/pi-wire";
2
+ import { node } from "../native/describe";
3
+ import type { NativeNode } from "../native/node";
4
+ import { isNativeRendering } from "../native/state";
1
5
  import type { Theme, ThemeColor } from "./theme";
2
6
  import { FG_RESET } from "./color";
3
7
 
@@ -151,6 +155,28 @@ export function shimmerEnabled(): boolean {
151
155
  return activeMode !== "disabled";
152
156
  }
153
157
 
158
+ /**
159
+ * Describe shimmering segments for a TSP terminal, which clocks the sweep
160
+ * itself: a `shimmer` node in the active mode, or plain spans in the mid tier
161
+ * color when shimmer is disabled. Palette tiers given as theme colors travel as
162
+ * token names; raw-ANSI tiers are dropped (the terminal's theme decides).
163
+ */
164
+ export function describeShimmer(segments: readonly ShimmerSegment[], key?: string): NativeNode {
165
+ const spans: TspSpan[] = [];
166
+ for (const { text: value, palette } of segments) {
167
+ if (value.length === 0) continue;
168
+ const mid = (palette ?? DEFAULT_SHIMMER_PALETTE).mid;
169
+ spans.push(typeof mid === "string" ? { t: value, s: mid } : { t: value });
170
+ }
171
+ if (activeMode === "disabled") return node("text", { spans }, undefined, key);
172
+ const palette = segments[0]?.palette ?? DEFAULT_SHIMMER_PALETTE;
173
+ const tokens: { low?: string; mid?: string; high?: string } = {};
174
+ if (typeof palette.low === "string") tokens.low = palette.low;
175
+ if (typeof palette.mid === "string") tokens.mid = palette.mid;
176
+ if (typeof palette.high === "string") tokens.high = palette.high;
177
+ return node("shimmer", { spans, mode: activeMode, palette: tokens }, undefined, key);
178
+ }
179
+
154
180
  /**
155
181
  * Apply a shimmer sweep across one or more segments, treating them as a
156
182
  * single continuous string for band positioning. Each segment can supply
@@ -165,6 +191,13 @@ export function shimmerEnabled(): boolean {
165
191
  * - No per-char allocations beyond the run buffer.
166
192
  */
167
193
  export function shimmerSegments(segments: readonly ShimmerSegment[], theme: ShimmerTheme): string {
194
+ // A TSP terminal animates shimmer from described nodes; a frame painted
195
+ // here would freeze mid-sweep, so the text goes out unstyled.
196
+ if (isNativeRendering()) {
197
+ let out = "";
198
+ for (const { text: value } of segments) out += value;
199
+ return out;
200
+ }
168
201
  const mode = activeMode;
169
202
 
170
203
  // Disabled: no animation or code-point scan. Preserve the all-empty result,
@@ -140,6 +140,26 @@ const underlineStyler = chalk.underline;
140
140
  const strikethroughStyler = chalk.strikethrough;
141
141
  const inverseStyler = chalk.inverse;
142
142
 
143
+ /** Theme token of a thinking/effort level name (`off`…`max`); unknown levels (`auto`) map to `thinkingOff`. */
144
+ export function thinkingLevelToken(level: string): ThemeColor {
145
+ switch (level) {
146
+ case "minimal":
147
+ return "thinkingMinimal";
148
+ case "low":
149
+ return "thinkingLow";
150
+ case "medium":
151
+ return "thinkingMedium";
152
+ case "high":
153
+ return "thinkingHigh";
154
+ case "xhigh":
155
+ return "thinkingXhigh";
156
+ case "max":
157
+ return "thinkingMax";
158
+ default:
159
+ return "thinkingOff";
160
+ }
161
+ }
162
+
143
163
  export class Theme {
144
164
  #fgColors: Record<ThemeColor, string>;
145
165
  #bgColors: Record<ThemeBg, string>;
@@ -419,25 +439,10 @@ export class Theme {
419
439
 
420
440
  /** Border color for a thinking/effort level name (`off`…`max`); unknown levels fall back to `thinkingOff`. */
421
441
  getThinkingBorderColor(level: string): (str: string) => string {
422
- switch (level) {
423
- case "off":
424
- return (str: string) => this.fg("thinkingOff", str);
425
- case "minimal":
426
- return (str: string) => this.fg("thinkingMinimal", str);
427
- case "low":
428
- return (str: string) => this.fg("thinkingLow", str);
429
- case "medium":
430
- return (str: string) => this.fg("thinkingMedium", str);
431
- case "high":
432
- return (str: string) => this.fg("thinkingHigh", str);
433
- case "xhigh":
434
- return (str: string) => this.fg("thinkingXhigh", str);
435
- case "max":
436
- // thinkingMax is optional; themes without it resolve to the xhigh color.
437
- return (str: string) => this.fg(this.#fgColors.thinkingMax ? "thinkingMax" : "thinkingXhigh", str);
438
- default:
439
- return (str: string) => this.fg("thinkingOff", str);
440
- }
442
+ const token = thinkingLevelToken(level);
443
+ // thinkingMax is optional; themes without it resolve to the xhigh color.
444
+ const color = token === "thinkingMax" && !this.#fgColors.thinkingMax ? "thinkingXhigh" : token;
445
+ return (str: string) => this.fg(color, str);
441
446
  }
442
447
 
443
448
  getBashModeBorderColor(): (str: string) => string {
@@ -7,8 +7,16 @@ import { getCustomThemesDir } from "@oh-my-pi/pi-utils/dirs";
7
7
  import * as logger from "@oh-my-pi/pi-utils/logger";
8
8
  import { setActiveSymbolTheme } from "./active-symbols";
9
9
  import { ansi256ToHex, resolveThemeColors, resolveVarRefs } from "./color";
10
- import { type CreateThemeOptions, getBuiltinThemes, loadTheme, loadThemeJson, loadThemeSync } from "./loader";
11
- import type { ThemeColor, ThemeJson } from "./schema";
10
+ import {
11
+ type CreateThemeOptions,
12
+ createTheme,
13
+ getBuiltinThemes,
14
+ loadTheme,
15
+ loadThemeJson,
16
+ loadThemeJsonSync,
17
+ loadThemeSync,
18
+ } from "./loader";
19
+ import { isValidThemeBg, isValidThemeColor, type ThemeColor, type ThemeJson } from "./schema";
12
20
  import type { SymbolPreset } from "./symbols";
13
21
  import type { Theme } from "./theme-class";
14
22
 
@@ -84,6 +92,8 @@ function getDefaultTheme(): string {
84
92
  /** Active theme; imports remain live across initialization, previews, and theme switches. */
85
93
  export var theme: Theme;
86
94
  var currentThemeName: string | undefined;
95
+ /** Theme shown by `previewTheme` until the next non-preview assignment. */
96
+ var previewThemeName: string | undefined;
87
97
 
88
98
  type ThemeBinding = (value: Theme) => void;
89
99
  var themeBindings: Set<ThemeBinding> | undefined;
@@ -98,8 +108,10 @@ export function bindTheme(binding: ThemeBinding): () => void {
98
108
  };
99
109
  }
100
110
 
101
- function assignTheme(value: Theme): void {
111
+ /** `previewName` marks a theme-selector preview; any other assignment ends it. */
112
+ function assignTheme(value: Theme, previewName?: string): void {
102
113
  theme = value;
114
+ previewThemeName = previewName;
103
115
  setActiveSymbolTheme(value);
104
116
  if (themeBindings) {
105
117
  for (const binding of themeBindings) binding(value);
@@ -121,6 +133,9 @@ export interface ThemeChangeEvent {
121
133
  }
122
134
 
123
135
  var currentSymbolPresetOverride: SymbolPreset | undefined;
136
+ // Process-local preset forced while a Tern Surface Protocol surface is live;
137
+ // wins over the user's preset and is never persisted.
138
+ var nativeSymbolPreset: SymbolPreset | undefined;
124
139
  var currentColorBlindMode: boolean = false;
125
140
  var themeWatcher: fs.FSWatcher | undefined;
126
141
  var themeReloadTimer: NodeJS.Timeout | undefined;
@@ -134,7 +149,7 @@ let themeEpoch = 0;
134
149
 
135
150
  function getCurrentThemeOptions(): CreateThemeOptions {
136
151
  return {
137
- symbolPresetOverride: currentSymbolPresetOverride,
152
+ symbolPresetOverride: nativeSymbolPreset ?? currentSymbolPresetOverride,
138
153
  colorBlindMode: currentColorBlindMode,
139
154
  };
140
155
  }
@@ -162,10 +177,7 @@ export function initThemeSync(
162
177
  lightTheme?: string,
163
178
  ): void {
164
179
  const name = configureTheme(symbolPreset, colorBlindMode, darkTheme, lightTheme);
165
- const options: CreateThemeOptions = {
166
- symbolPresetOverride: currentSymbolPresetOverride,
167
- colorBlindMode: currentColorBlindMode,
168
- };
180
+ const options = getCurrentThemeOptions();
169
181
  try {
170
182
  assignTheme(loadThemeSync(name, options));
171
183
  } catch (error) {
@@ -255,7 +267,8 @@ export async function previewTheme(
255
267
  if (requestId !== themeLoadRequestId) {
256
268
  return { success: false, error: "Theme preview superseded by a newer request" };
257
269
  }
258
- assignTheme(loadedTheme);
270
+ // Previewing the active theme again (selector cancel) ends the preview.
271
+ assignTheme(loadedTheme, name === currentThemeName ? undefined : name);
259
272
  notifyThemeChange(event);
260
273
  return { success: true };
261
274
  } catch (error) {
@@ -337,6 +350,30 @@ export function getSymbolPresetOverride(): SymbolPreset | undefined {
337
350
  return currentSymbolPresetOverride;
338
351
  }
339
352
 
353
+ /**
354
+ * Force `preset` while a Tern Surface Protocol surface is live (the terminal's
355
+ * font carries every Nerd Font glyph, so icons need no Glyph Protocol
356
+ * registration); `undefined` restores the user's preset. Process-local and
357
+ * never persisted. Rebuilds the active theme synchronously so the next frame
358
+ * already uses it, and notifies theme listeners like a preset change. Returns
359
+ * whether the active theme was rebuilt (callers invalidate render caches).
360
+ */
361
+ export function setNativeSymbolPreset(preset: SymbolPreset | undefined): boolean {
362
+ if (nativeSymbolPreset === preset) return false;
363
+ nativeSymbolPreset = preset;
364
+ if (!currentThemeName || typeof theme === "undefined") return false;
365
+ ++themeLoadRequestId;
366
+ try {
367
+ assignTheme(loadThemeSync(currentThemeName, getCurrentThemeOptions()));
368
+ } catch (error) {
369
+ // In-memory themes can't be rebuilt by name; they keep their symbols.
370
+ logger.debug("Native symbol preset: theme rebuild skipped", { theme: currentThemeName, error: String(error) });
371
+ return false;
372
+ }
373
+ notifyThemeChange({ ephemeral: true });
374
+ return true;
375
+ }
376
+
340
377
  /**
341
378
  * Set color blind mode, recreating the theme with the new setting.
342
379
  * When enabled, uses blue instead of green for diff additions.
@@ -770,6 +807,71 @@ export async function getResolvedThemeColors(themeName?: string): Promise<Record
770
807
  return cssColors;
771
808
  }
772
809
 
810
+ /** One appearance variant of {@link NativeThemePalette}: token name → `#rrggbb`. */
811
+ export type NativeThemeVariant = Record<string, string>;
812
+
813
+ /** omp's resolved theme for a Tern Surface Protocol terminal (the `t` verb body minus `sf`). */
814
+ export interface NativeThemePalette {
815
+ dark?: NativeThemeVariant;
816
+ light?: NativeThemeVariant;
817
+ name: { dark?: string; light?: string };
818
+ }
819
+
820
+ /**
821
+ * Cheap identity of everything {@link getNativeThemePalette} depends on;
822
+ * recompute the palette only when it changes.
823
+ */
824
+ export function getNativeThemePaletteKey(): string {
825
+ return `${themeEpoch}|${autoDetectedTheme}|${autoDarkTheme}|${autoLightTheme}|${currentThemeName}|${previewThemeName}|${currentColorBlindMode}`;
826
+ }
827
+
828
+ /** Every token of theme `name` as hex (terminal-default tokens omitted), plus its export colours. */
829
+ function nativeThemeVariant(name: string): { light: boolean; colors: NativeThemeVariant } {
830
+ const themeJson = loadThemeJsonSync(name);
831
+ const built = createTheme(themeJson, getCurrentThemeOptions());
832
+ const raw = resolveThemeColors(themeJson.colors, themeJson.vars);
833
+ const colors: NativeThemeVariant = {};
834
+ for (const key in raw) {
835
+ // Terminal-default tokens stay unset; keys outside the schema are ignored like the theme does.
836
+ if (raw[key as keyof typeof raw] === "") continue;
837
+ if (isValidThemeColor(key)) colors[key] = built.getColorHex(key);
838
+ else if (isValidThemeBg(key)) colors[key] = built.getBgHex(key);
839
+ }
840
+ const exported = resolveThemeExportColors(themeJson);
841
+ for (const key of ["pageBg", "cardBg", "infoBg"] as const) {
842
+ const value = exported[key];
843
+ if (value) colors[key] = value;
844
+ }
845
+ return { light: isLightThemeJson(themeJson), colors };
846
+ }
847
+
848
+ /**
849
+ * omp's theme for a native surface: with auto theme, the configured dark and
850
+ * light themes; otherwise (or while the theme selector previews one) the
851
+ * active theme under its own appearance only. Themes without a loadable
852
+ * definition (in-memory instances) contribute nothing.
853
+ */
854
+ export function getNativeThemePalette(): NativeThemePalette {
855
+ const palette: NativeThemePalette = { name: {} };
856
+ const add = (name: string, as?: "dark" | "light"): void => {
857
+ try {
858
+ const variant = nativeThemeVariant(name);
859
+ const slot = as ?? (variant.light ? "light" : "dark");
860
+ palette[slot] = variant.colors;
861
+ palette.name[slot] = name;
862
+ } catch (error) {
863
+ logger.debug("Native theme palette: theme not loadable", { name, error: String(error) });
864
+ }
865
+ };
866
+ const single = previewThemeName ?? (autoDetectedTheme ? undefined : currentThemeName);
867
+ if (single !== undefined) add(single);
868
+ else {
869
+ add(autoDarkTheme, "dark");
870
+ add(autoLightTheme, "light");
871
+ }
872
+ return palette;
873
+ }
874
+
773
875
  /**
774
876
  * Check if a theme is a "light" theme by analyzing its status-line background
775
877
  * luminance. Loads theme JSON synchronously (built-in or custom file on disk)
package/src/tools/ask.ts CHANGED
@@ -1,4 +1,10 @@
1
- import type { ToolRenderer } from "./renderer";
1
+ import type { NativeToolHead, NativeToolView, ToolRenderer, ToolRenderResult } from "./renderer";
2
+ import type { TspSpan } from "@oh-my-pi/pi-wire";
3
+ import { compact, md, node, row, span, text } from "../native/describe";
4
+ import type { NativeChild, NativeNode } from "../native/node";
5
+ import { OwnerMemo } from "../native/memo";
6
+ import { plainText } from "../native/spans";
7
+ import { noteText, resultText } from "./native-view";
2
8
 
3
9
  import {
4
10
  type Component,
@@ -281,8 +287,158 @@ function renderAnswerOptionLines(
281
287
  return out;
282
288
  }
283
289
 
290
+ function askHead(question: string | undefined, meta?: string): NativeToolHead {
291
+ const first = question ? plainText(sanitizeCarriageReturns(question)).trim().split("\n")[0] : "";
292
+ return { title: "Ask", target: first || undefined, targetKind: "text", meta: meta ? [meta] : undefined };
293
+ }
294
+
295
+ /** One question of a multi-question result: the question as markdown over its answers. */
296
+ function describeQuestionSection(id: string, question: string, rest: readonly NativeChild[]): NativeNode {
297
+ return node("section", {}, [md(plainText(question)), ...rest], id);
298
+ }
299
+
300
+ /** An answer row: a success check before the chosen label, or a quiet unchosen label. */
301
+ function answerRow(key: string, label: TspSpan[], chosen: boolean): NativeNode {
302
+ return row(
303
+ compact([chosen ? node("icon", { name: "check", tone: "success" }) : undefined, text(label, { wrap: "word" })]),
304
+ { gap: "sm", align: "baseline", role: chosen ? "omp.tool.answer" : "omp.tool.answer.off", key },
305
+ );
306
+ }
307
+
308
+ /**
309
+ * Answered option list: every offered option marked chosen or not, plus any
310
+ * quoted custom answer and a muted note. A lone "Cancelled" line when nothing was chosen.
311
+ */
312
+ function describeAnswers(
313
+ options: string[] | undefined,
314
+ selectedOptions: string[] | undefined,
315
+ customInput: string | undefined,
316
+ note: string | undefined,
317
+ selectedIndices: ReadonlySet<number> | undefined,
318
+ ): NativeChild[] {
319
+ const selected = new Set(selectedOptions ?? []);
320
+ if (selected.size === 0 && customInput === undefined && note === undefined) {
321
+ return [text([span("Cancelled", "warning")], { tone: "warning" })];
322
+ }
323
+ const labels = options && options.length > 0 ? options : (selectedOptions ?? []);
324
+ const rows = labels.map((label, index) => {
325
+ const isSelected = selectedIndices !== undefined ? selectedIndices.has(index) : selected.has(label);
326
+ return answerRow(`${index}`, [span(plainText(label), isSelected ? "toolOutput" : "muted")], isSelected);
327
+ });
328
+ if (customInput !== undefined) {
329
+ rows.push(answerRow("custom", [span(`\u201c${plainText(customInput)}\u201d`, "toolOutput")], true));
330
+ }
331
+ return compact([
332
+ ...rows,
333
+ note !== undefined
334
+ ? text([span(plainText(note), "muted")], { wrap: "word", role: "omp.tool.context" })
335
+ : undefined,
336
+ ]);
337
+ }
338
+
339
+ /** Pending ask: an inline head naming the question; the ask dialog owns the form. */
340
+ function describeAskCall(args: AskRenderArgs): NativeToolView {
341
+ const questions = normalizeRenderQuestions(args.questions);
342
+ const first = questions?.[0]?.question ?? (typeof args.question === "string" ? args.question : undefined);
343
+ const more = questions && questions.length > 1 ? `${questions.length} questions` : undefined;
344
+ return {
345
+ tool: askHead(first, more),
346
+ inline: true,
347
+ body: [text([span("Waiting for your answer…", "muted")])],
348
+ };
349
+ }
350
+
351
+ function describeAskResult(result: ToolRenderResult<AskToolDetails>, args: AskRenderArgs | undefined): NativeToolView {
352
+ const rawDetails = result.details;
353
+ const fallback = plainText(sanitizeCarriageReturns(resultText(result)));
354
+ const argQuestion =
355
+ normalizeRenderQuestions(args?.questions)?.[0]?.question ??
356
+ (typeof args?.question === "string" ? args.question : undefined);
357
+ if (!rawDetails) {
358
+ return { tool: askHead(argQuestion), tone: "warning", body: fallback ? [noteText(fallback)] : undefined };
359
+ }
360
+ const details = sanitizeAskResultDetails(rawDetails);
361
+
362
+ if (details.chatRedirect) {
363
+ return {
364
+ tool: askHead(details.questions?.[0] ?? argQuestion, "chat redirect"),
365
+ tone: "info",
366
+ body: (details.questions ?? []).map(q => md(plainText(q))),
367
+ };
368
+ }
369
+
370
+ if (details.results && details.results.length > 0) {
371
+ const results = details.results;
372
+ const rawResults = rawDetails.results ?? [];
373
+ const answered = results.some(
374
+ r =>
375
+ r.customInput !== undefined || r.note !== undefined || (r.selectedOptions && r.selectedOptions.length > 0),
376
+ );
377
+ return {
378
+ tool: askHead(results[0]?.question, results.length > 1 ? `${results.length} questions` : undefined),
379
+ tone: answered ? undefined : "warning",
380
+ // Sanitizing preserves order and length, so raw indices align with `r`.
381
+ body: results.map((r, index) =>
382
+ describeQuestionSection(
383
+ r.id,
384
+ r.question,
385
+ describeAnswers(
386
+ r.options,
387
+ r.selectedOptions,
388
+ r.customInput,
389
+ r.note,
390
+ selectedIndicesFor(rawResults[index]?.options, rawResults[index]?.selectedOptions),
391
+ ),
392
+ ),
393
+ ),
394
+ };
395
+ }
396
+
397
+ if (!details.question) {
398
+ return { tool: askHead(argQuestion), body: fallback ? [text(fallback, { wrap: "word" })] : undefined };
399
+ }
400
+
401
+ const answered =
402
+ details.customInput !== undefined ||
403
+ details.note !== undefined ||
404
+ (details.selectedOptions && details.selectedOptions.length > 0);
405
+ return {
406
+ tool: askHead(details.question),
407
+ tone: answered ? undefined : "warning",
408
+ body: compact([
409
+ md(plainText(details.question)),
410
+ ...describeAnswers(
411
+ details.options,
412
+ details.selectedOptions,
413
+ details.customInput,
414
+ details.note,
415
+ selectedIndicesFor(rawDetails.options, rawDetails.selectedOptions),
416
+ ),
417
+ details.timedOut
418
+ ? text([span("auto-selected after timeout — not a user choice", "muted")], {
419
+ wrap: "word",
420
+ role: "omp.tool.notice",
421
+ })
422
+ : undefined,
423
+ ]),
424
+ };
425
+ }
426
+
427
+ const askCallMemo = new OwnerMemo<NativeToolView | undefined>();
428
+ const askResultMemo = new OwnerMemo<NativeToolView | undefined>();
429
+
284
430
  /** Render question forms and their recorded answers. */
285
431
  export const askToolRenderer = {
432
+ describeCall(args: AskRenderArgs): NativeToolView | undefined {
433
+ return askCallMemo.get(args, [JSON.stringify(args)], () => describeAskCall(args));
434
+ },
435
+ describeResult(
436
+ result: ToolRenderResult<AskToolDetails>,
437
+ _options?: RenderResultOptions,
438
+ args?: AskRenderArgs,
439
+ ): NativeToolView | undefined {
440
+ return askResultMemo.get(result, [], () => describeAskResult(result, args));
441
+ },
286
442
  mergeCallAndResult: true,
287
443
  renderCall(args: AskRenderArgs, _options: RenderResultOptions, uiTheme: Theme): Component {
288
444
  const label = formatTitle("Ask", uiTheme);
@@ -14,7 +14,11 @@ import {
14
14
  } from "../render/render-utils";
15
15
  import { classifyGroupedLines, groupLineIndicesByBlank } from "./grouped-file-output";
16
16
  import type { OutputMeta } from "./output-meta";
17
- import type { RenderResultOptions, ToolRenderer } from "./renderer";
17
+ import type { NativeToolHead, NativeToolView, RenderResultOptions, ToolRenderer } from "./renderer";
18
+ import { code, compact, diff, node, span, text } from "../native/describe";
19
+ import type { NativeChild, NativeNode } from "../native/node";
20
+ import { diffStatsMeta, displayPath, errorText, fileDiffSection, noteText, resultText, statsText } from "./native-view";
21
+ import { getLanguageFromPath } from "../lang-from-path";
18
22
 
19
23
  /** Display metadata returned by ast-edit. */
20
24
  export interface AstEditToolDetails {
@@ -82,6 +86,86 @@ function patternPreview(pat: string | undefined): string | undefined {
82
86
  return collapsed || undefined;
83
87
  }
84
88
 
89
+ const AST_EDIT_HEADER_RE = /^(#+)\s+(.*)$/;
90
+ const AST_EDIT_CHANGE_LINE_RE = /^([+\- ])\s*(\d+)(?:│|[:|])(.*)$/;
91
+ const AST_EDIT_HEADER_SUFFIX_RE = /\s+\([^)]*\)\s*$/;
92
+
93
+ /** One file's proposed rewrite: its unified diff text and change counts. */
94
+ interface AstEditFileDiff {
95
+ path: string;
96
+ lines: string[];
97
+ added: number;
98
+ removed: number;
99
+ lastOld?: number;
100
+ }
101
+
102
+ /**
103
+ * Parse ast-edit display content (`# dir/`, `## file (N replacements)`,
104
+ * `-12│old` / `+12│new`) into one unified diff per file.
105
+ */
106
+ function parseAstEditChanges(lines: readonly string[]): AstEditFileDiff[] {
107
+ const files: AstEditFileDiff[] = [];
108
+ const dirs: string[] = [];
109
+ let file: AstEditFileDiff | undefined;
110
+ const flush = () => {
111
+ if (file && file.lines.length > 0) files.push(file);
112
+ file = undefined;
113
+ };
114
+ for (const line of lines) {
115
+ const header = AST_EDIT_HEADER_RE.exec(line);
116
+ if (header) {
117
+ flush();
118
+ const depth = header[1]!.length;
119
+ const rest = header[2]!.trimEnd().replace(AST_EDIT_HEADER_SUFFIX_RE, "");
120
+ dirs.length = depth - 1;
121
+ if (rest.endsWith("/")) {
122
+ dirs[depth - 1] = rest.slice(0, -1);
123
+ continue;
124
+ }
125
+ const prefix = dirs.filter(Boolean).join("/");
126
+ file = { path: prefix ? `${prefix}/${rest}` : rest, lines: [], added: 0, removed: 0 };
127
+ continue;
128
+ }
129
+ if (!file || line.trim().length === 0) continue;
130
+ const change = AST_EDIT_CHANGE_LINE_RE.exec(line);
131
+ if (!change) continue;
132
+ const lineNumber = Number.parseInt(change[2]!, 10);
133
+ if (file.lastOld === undefined || lineNumber > file.lastOld + 1 || lineNumber < file.lastOld) {
134
+ file.lines.push(`@@ -${lineNumber} +${lineNumber} @@`);
135
+ }
136
+ file.lastOld = lineNumber;
137
+ if (change[1] === "+") file.added++;
138
+ else if (change[1] === "-") file.removed++;
139
+ file.lines.push(`${change[1]}${change[3]}`);
140
+ }
141
+ flush();
142
+ return files;
143
+ }
144
+
145
+ /** A file's proposed rewrite as a `diff`: highlighted by the path's language, no path header. */
146
+ function astEditDiff(file: AstEditFileDiff): NativeNode {
147
+ return diff(file.lines.join("\n"), { lang: getLanguageFromPath(file.path) });
148
+ }
149
+
150
+ /** The single rewrite pattern (`target:"pattern"`), or the rewrite count, for the head. */
151
+ function astEditPatternHead(args: AstEditRenderArgs | undefined): Pick<NativeToolHead, "target" | "targetKind"> {
152
+ const rewriteCount = args?.ops?.length ?? 0;
153
+ if (rewriteCount === 1) {
154
+ const pattern = patternPreview(args?.ops?.[0]?.pat);
155
+ return pattern ? { target: pattern, targetKind: "pattern" } : {};
156
+ }
157
+ return rewriteCount > 1 ? { target: `${rewriteCount} rewrites`, targetKind: "text" } : {};
158
+ }
159
+
160
+ /** Scope facts for the final quiet line: `in src · searched 12 files · limit reached; narrow path`. */
161
+ function astEditScopeStats(details: AstEditToolDetails | undefined): NativeNode | undefined {
162
+ const parts: string[] = [];
163
+ if (details?.scopePath) parts.push(`in ${details.scopePath}`);
164
+ if (details?.filesSearched) parts.push(`searched ${formatCount("file", details.filesSearched)}`);
165
+ if (details?.limitReached) parts.push("limit reached; narrow path");
166
+ return statsText(parts);
167
+ }
168
+
85
169
  /** Render AST edit calls and results. */
86
170
  export const astEditToolRenderer = {
87
171
  inline: true,
@@ -206,5 +290,94 @@ export const astEditToolRenderer = {
206
290
  };
207
291
  });
208
292
  },
293
+ describeCall(args: AstEditRenderArgs): NativeToolView {
294
+ return {
295
+ tool: {
296
+ title: "AST Edit",
297
+ ...astEditPatternHead(args),
298
+ meta: args.paths?.length ? [`in ${args.paths.map(displayPath).join(", ")}`] : undefined,
299
+ },
300
+ };
301
+ },
302
+
303
+ describeResult(
304
+ result: { content: Array<{ type: string; text?: string }>; details?: AstEditToolDetails; isError?: boolean },
305
+ _options: RenderResultOptions,
306
+ args?: AstEditRenderArgs,
307
+ ): NativeToolView {
308
+ const details = result.details;
309
+ const patternHead = astEditPatternHead(args);
310
+ if (result.isError) {
311
+ return {
312
+ tool: { title: "AST Edit", ...patternHead },
313
+ tone: "error",
314
+ body: [errorText(resultText(result) || "Unknown error")],
315
+ };
316
+ }
317
+ const parseErrors = details?.parseErrors ?? [];
318
+ const parseNote: NativeChild | undefined =
319
+ parseErrors.length > 0
320
+ ? node(
321
+ "section",
322
+ {
323
+ head: [span(formatParseErrorsCountLabel(parseErrors, details?.parseErrorsTotal), "warning")],
324
+ collapsible: true,
325
+ collapsed: true,
326
+ },
327
+ [code(parseErrors.join("\n"))],
328
+ "parse-errors",
329
+ )
330
+ : undefined;
331
+ const totalReplacements = details?.totalReplacements ?? 0;
332
+ if (totalReplacements === 0) {
333
+ return {
334
+ tool: { title: "AST Edit", ...patternHead, meta: ["0 replacements"] },
335
+ tone: "warning",
336
+ body: compact<NativeChild>([parseNote, astEditScopeStats(details)]),
337
+ };
338
+ }
339
+ const limitReached = details?.limitReached ?? false;
340
+ const allLines = (details?.displayContent ?? resultText(result)).split("\n");
341
+ const kept = groupLineIndicesByBlank(allLines)
342
+ .filter(indices => {
343
+ const first = allLines[indices[0]!]!;
344
+ return !first.startsWith("Safety cap reached") && !first.startsWith("Parse issues:");
345
+ })
346
+ .flatMap(indices => indices.map(index => allLines[index]!));
347
+ const files = parseAstEditChanges(kept);
348
+ const fileCount = Math.max(details?.filesTouched ?? 0, files.length);
349
+ let added = 0;
350
+ let removed = 0;
351
+ for (const file of files) {
352
+ added += file.added;
353
+ removed += file.removed;
354
+ }
355
+ // Same shape as edit: one file heads the call by path with a bare diff;
356
+ // several head it as `N files` with one borderless section per file.
357
+ const single = fileCount === 1 && files.length === 1 ? files[0] : undefined;
358
+ const changes: NativeChild[] = single
359
+ ? [astEditDiff(single)]
360
+ : files.map(file => fileDiffSection(file, [astEditDiff(file)], { role: "omp.tool.ast_edit.file" }));
361
+ // The head names the files, so a single rewrite's pattern moves to a quiet context line.
362
+ const pattern = args?.ops?.length === 1 ? patternPreview(args.ops[0]?.pat) : undefined;
363
+ return {
364
+ tool: {
365
+ title: "AST Edit",
366
+ target: single ? displayPath(single.path) : formatCount("file", fileCount),
367
+ targetKind: single ? "path" : "text",
368
+ meta: compact([diffStatsMeta(added, removed), formatCount("replacement", totalReplacements)]),
369
+ badges: details?.applied ? undefined : [{ text: "proposed", tone: "warning" }],
370
+ },
371
+ tone: limitReached ? "warning" : undefined,
372
+ body: compact<NativeChild>([
373
+ pattern !== undefined &&
374
+ text([span(pattern, "code")], { lines: 1, truncate: "end", role: "omp.tool.context" }),
375
+ ...(changes.length > 0 ? changes : [noteText(kept.join("\n"))]),
376
+ parseNote,
377
+ astEditScopeStats(details),
378
+ ]),
379
+ preview: { lines: COLLAPSED_CHANGE_LIMIT },
380
+ };
381
+ },
209
382
  mergeCallAndResult: true,
210
383
  } satisfies ToolRenderer<AstEditRenderArgs, AstEditToolDetails>;