@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
@@ -5,11 +5,17 @@ import { ImageProtocol, TERMINAL } from "../terminal-capabilities";
5
5
  import { Markdown, type MarkdownTheme } from "../components/markdown";
6
6
  import { Spacer } from "../components/spacer";
7
7
  import { Text } from "../components/text";
8
- import { formatNumber } from "@oh-my-pi/pi-utils";
8
+ import { formatDuration, formatNumber } from "@oh-my-pi/pi-utils";
9
9
  import chalk from "@oh-my-pi/pi-utils/chalk";
10
10
  import { LRUCache } from "@oh-my-pi/pi-utils/lru";
11
11
  import type { AssistantThinkingRenderer } from "./extension-types";
12
- import { ensureThemeSync, getMarkdownTheme, theme } from "../theme";
12
+ import { ensureThemeSync, getMarkdownTheme, getThemeEpoch, theme } from "../theme";
13
+ import { card, col, elapsed, node, span, text } from "../native/describe";
14
+ import { hasTranscriptActions, runTranscriptAction } from "./transcript-actions";
15
+ import type { NativeChild, NativeNode, NativeUiEvent } from "../native/node";
16
+ import { isNativeRendering } from "../native/state";
17
+ import { NativeImageCache } from "../native/blobs";
18
+ import { Memo } from "../native/memo";
13
19
  import { EMPTY_LINK_TARGETS, resolveImageOptions } from "../render/render-utils";
14
20
  import { WidthAwareText } from "../render";
15
21
  import { cachedPngConversion, convertImageToPngShared, imagePayloadKey } from "./image-loading";
@@ -20,6 +26,7 @@ import { formatErrorBlock } from "../chrome/error-block";
20
26
  import { type ServedModelMismatch, ServedModelMarkerComponent } from "./served-model-marker";
21
27
  import { isReactionTarget, type ReactionSplit, type ReactionTarget, splitReaction } from "./reaction";
22
28
  import { isRowPrefix, type TranscriptStableRow, trimBlankEdges } from "../chrome/transcript-container";
29
+ import { formatTurnUsage, type TurnUsageSummary } from "../overlays/usage-row";
23
30
 
24
31
  /**
25
32
  * Max wrapped rows of a turn-ending provider error rendered inline in the
@@ -31,6 +38,13 @@ import { isRowPrefix, type TranscriptStableRow, trimBlankEdges } from "../chrome
31
38
  const MAX_TRANSCRIPT_ERROR_ROWS = 8;
32
39
  const EMPTY_STABLE_RENDER: readonly string[] = [];
33
40
 
41
+ /** The native head of a finished thinking block: "Thought for 12s", or "Thought" when it was never seen streaming. */
42
+ function thoughtLabel(clock: { start: number; end?: number } | undefined): string {
43
+ if (clock?.end === undefined) return "Thought";
44
+ const ms = clock.end - clock.start;
45
+ return `Thought for ${ms < 60_000 ? `${Math.max(1, Math.round(ms / 1000))}s` : formatDuration(ms)}`;
46
+ }
47
+
34
48
  type ThinkingContentBlock = Extract<AssistantMessage["content"][number], { type: "thinking" }>;
35
49
  type DisplayThinkingContentBlock = ThinkingContentBlock & { rawThinking?: string };
36
50
  type StablePartKind = "thinking" | "text";
@@ -251,6 +265,12 @@ export class AssistantMessageComponent extends Container {
251
265
  * provider error whose tail would otherwise be unreachable in the live TUI.
252
266
  */
253
267
  #errorExpanded = false;
268
+ /** The deduplicated message the native error frame shows (its "Copy error" copies it). */
269
+ #errorText: string | undefined;
270
+ /** A TTSR rule aborted this text; the turn re-streams below it. */
271
+ #rewound = false;
272
+ /** The turn's totals when this answer ends it; a TSP terminal shows them under the answer. */
273
+ #turnUsage: TurnUsageSummary | undefined;
254
274
  /**
255
275
  * True when the current {@link updateContent} message carries a truncatable
256
276
  * inline provider error (the `#appendErrorBlock` path) — set whether or not
@@ -323,6 +343,19 @@ export class AssistantMessageComponent extends Container {
323
343
  #reactionTarget: ReactionTarget | undefined;
324
344
  /** Reaction lifted from the reply's opening emoji, once resolved. */
325
345
  #reaction: string | undefined;
346
+ /** Display form of {@link #lastMessage} (reaction handled) the native description is built from. */
347
+ #displayedMessage: AssistantMessage | undefined;
348
+ /** Thinking-extension components per content index, recorded when the slow path mounts them. */
349
+ #thinkingExtensions = new Map<number, Component[]>();
350
+ /** Collapse state of thinking sections toggled in the terminal, by content index; cleared by {@link setHideThinkingBlock}. */
351
+ #thinkingCollapsed = new Map<number, boolean>();
352
+ /** When each thinking block was seen streaming and when it stopped (native "Thought for 12s"), by content index. */
353
+ #thinkingClock = new Map<number, { start: number; end?: number; tokens: number }>();
354
+ #nativeViewVersion = 0;
355
+ readonly #native = new Memo();
356
+ /** Markdown nodes by key, reused while their text and streaming flag are unchanged. */
357
+ #nativeParts = new Map<string, { text: string; stream: boolean; node: NativeNode }>();
358
+ readonly #nativeImages = new NativeImageCache();
326
359
 
327
360
  setTextColorTransform(transform?: (text: string) => string): void {
328
361
  this.#textColorTransform = transform;
@@ -469,6 +502,12 @@ export class AssistantMessageComponent extends Container {
469
502
  this.#refreshMarkers();
470
503
  }
471
504
 
505
+ /** Mark this answer as the end of a turn with the turn's totals (native only: ANSI keeps them in the status line). */
506
+ setTurnUsage(summary: TurnUsageSummary | undefined): void {
507
+ this.#turnUsage = summary;
508
+ this.#nativeViewVersion++;
509
+ }
510
+
472
511
  /**
473
512
  * Show or clear the trailing served-model divider. Set once the turn's
474
513
  * signed thinking block (or router report) has named the model that actually
@@ -502,6 +541,7 @@ export class AssistantMessageComponent extends Container {
502
541
 
503
542
  setHideThinkingBlock(hide: boolean): void {
504
543
  this.#hideThinkingBlock = hide;
544
+ this.#thinkingCollapsed.clear();
505
545
  }
506
546
 
507
547
  /**
@@ -534,14 +574,30 @@ export class AssistantMessageComponent extends Container {
534
574
  * Once text starts, a tool call streams, or the block is sealed, the pulse ends.
535
575
  */
536
576
  #shouldAnimateThinking(message: AssistantMessage): boolean {
537
- if (!this.#hideThinkingBlock || this.#transcriptBlockFinalized) return false;
577
+ return this.#hideThinkingBlock && this.#thinkingTailIndex(message) !== undefined;
578
+ }
579
+
580
+ /**
581
+ * Content index of the thinking block the model is producing right now:
582
+ * the block is still streaming (not finalized), no tool call has started,
583
+ * and the tail visible block is thinking. Undefined otherwise.
584
+ */
585
+ #thinkingTailIndex(message: AssistantMessage): number | undefined {
586
+ if (this.#transcriptBlockFinalized) return undefined;
538
587
  let tail: "text" | "thinking" | undefined;
539
- for (const content of message.content) {
540
- if (content.type === "toolCall") return false;
541
- if (content.type === "text" && canonicalizeMessage(content.text)) tail = "text";
542
- else if (content.type === "thinking" && canonicalizeMessage(content.thinking)) tail = "thinking";
588
+ let tailIndex = -1;
589
+ for (let index = 0; index < message.content.length; index++) {
590
+ const content = message.content[index]!;
591
+ if (content.type === "toolCall") return undefined;
592
+ if (content.type === "text" && canonicalizeMessage(content.text)) {
593
+ tail = "text";
594
+ tailIndex = index;
595
+ } else if (content.type === "thinking" && canonicalizeMessage(content.thinking)) {
596
+ tail = "thinking";
597
+ tailIndex = index;
598
+ }
543
599
  }
544
- return tail === "thinking";
600
+ return tail === "thinking" ? tailIndex : undefined;
545
601
  }
546
602
 
547
603
  #thinkingDotsLabel(): string {
@@ -571,7 +627,8 @@ export class AssistantMessageComponent extends Container {
571
627
  }
572
628
 
573
629
  #startThinkingAnimation(): void {
574
- if (this.#thinkingDotsTimer) return;
630
+ // A native terminal clocks the described starburst itself.
631
+ if (this.#thinkingDotsTimer || isNativeRendering()) return;
575
632
  this.#scheduleThinkingFrame();
576
633
  }
577
634
 
@@ -592,7 +649,7 @@ export class AssistantMessageComponent extends Container {
592
649
 
593
650
  #advanceThinkingDots(): void {
594
651
  this.#thinkingDotsTimer = undefined;
595
- if (!this.#thinkingDots) {
652
+ if (!this.#thinkingDots || isNativeRendering()) {
596
653
  this.#stopThinkingAnimation();
597
654
  return;
598
655
  }
@@ -649,6 +706,276 @@ export class AssistantMessageComponent extends Container {
649
706
  return rows;
650
707
  }
651
708
 
709
+ /**
710
+ * A `col` (role `omp.assistant`) of `md` nodes keyed by content index, so
711
+ * streamed deltas reach the terminal as `text append` on the same node;
712
+ * the tail block carries `stream: true` until the message finalizes.
713
+ * Thinking blocks are quiet collapsible `section`s (collapsed per
714
+ * `hideThinkingBlock`): a muted "Thinking…" shimmer while live, then
715
+ * "Thought for 12s"; tokens and rate ride in the head's `title`.
716
+ */
717
+ override describe(): NativeNode {
718
+ const tail = this.#displayedMessage ? this.#thinkingTailIndex(this.#displayedMessage) : undefined;
719
+ const rate =
720
+ tail !== undefined && this.#lastUpdateTransient && this.#thinkingRateLive
721
+ ? Math.round(Math.min(SPEED_MAX, sharedSpeedTracker.getSpeed()) * 10) / 10
722
+ : 0;
723
+ const key = [
724
+ this.#blockVersion,
725
+ this.#transcriptBlockFinalized,
726
+ this.#hideThinkingBlock,
727
+ this.#showImages,
728
+ this.#showToolResultImages,
729
+ this.#errorExpanded,
730
+ this.#nativeViewVersion,
731
+ rate,
732
+ getThemeEpoch(),
733
+ ];
734
+ return this.#native.get(key, () => this.#describeMessage(tail, rate));
735
+ }
736
+
737
+ /**
738
+ * A TTSR rule rewound this partial answer: a native terminal dims it and
739
+ * tags it `↺ rewound` (the ANSI render leaves it as it stopped).
740
+ */
741
+ markRewound(): void {
742
+ if (this.#rewound) return;
743
+ this.#rewound = true;
744
+ this.#nativeViewVersion++;
745
+ }
746
+
747
+ /** Mirrors a thinking section collapsed or expanded in the terminal. */
748
+ handleNativeEvent(event: NativeUiEvent): void {
749
+ if (event.type === "action") {
750
+ this.#handleErrorAction(event.act);
751
+ return;
752
+ }
753
+ if (event.type !== "toggle" || !event.key.startsWith("k")) return;
754
+ const index = Number(event.key.slice(1));
755
+ if (!Number.isInteger(index)) return;
756
+ this.#thinkingCollapsed.set(index, event.collapsed);
757
+ this.#nativeViewVersion++;
758
+ }
759
+
760
+ #describeMessage(thinkingTail: number | undefined, rate: number): NativeNode {
761
+ const message = this.#displayedMessage;
762
+ const previousParts = this.#nativeParts;
763
+ const parts = new Map<string, { text: string; stream: boolean; node: NativeNode }>();
764
+ // `slot` identifies the node across rebuilds; `key` is its sibling key inside its parent.
765
+ const markdown = (slot: string, key: string, source: string, stream: boolean): NativeNode => {
766
+ const cached = previousParts.get(slot);
767
+ const entry =
768
+ cached?.text === source && cached.stream === stream
769
+ ? cached
770
+ : {
771
+ text: source,
772
+ stream,
773
+ node: node("md", stream ? { text: source, stream: true } : { text: source }, undefined, key),
774
+ };
775
+ parts.set(slot, entry);
776
+ return entry.node;
777
+ };
778
+ const children: NativeChild[] = [];
779
+ if (message) {
780
+ const live = !this.#transcriptBlockFinalized && this.#lastUpdateTransient;
781
+ let tailIndex = -1;
782
+ for (let index = 0; index < message.content.length; index++) {
783
+ const content = message.content[index]!;
784
+ if (
785
+ (content.type === "text" && canonicalizeMessage(content.text)) ||
786
+ (content.type === "thinking" && canonicalizeMessage(content.thinking))
787
+ ) {
788
+ tailIndex = index;
789
+ }
790
+ }
791
+ for (let index = 0; index < message.content.length; index++) {
792
+ const content = message.content[index]!;
793
+ const streaming = live && index === tailIndex;
794
+ if (content.type === "text" && canonicalizeMessage(content.text)) {
795
+ children.push(markdown(`t${index}`, `t${index}`, content.text.trim(), streaming));
796
+ } else if (content.type === "thinking") {
797
+ const display = resolveThinkingDisplay(content, this.#proseOnlyThinking);
798
+ if (!display.visible) continue;
799
+ const thinkingLive = streaming && thinkingTail === index;
800
+ const clock = this.#thinkingClock.get(index);
801
+ if (thinkingLive) {
802
+ if (clock) clock.tokens = this.#thinkingTokens || clock.tokens;
803
+ else this.#thinkingClock.set(index, { start: performance.now(), tokens: this.#thinkingTokens });
804
+ } else if (clock && clock.end === undefined) {
805
+ clock.end = performance.now();
806
+ }
807
+ const tokens = thinkingLive ? this.#thinkingTokens : (clock?.tokens ?? 0);
808
+ const title = tokens > 0 ? `${formatNumber(tokens)} tokens` : undefined;
809
+ // Live: starburst · "Thinking…" · ticking timer · tok/s. Done: "Thought for 12s".
810
+ const head = thinkingLive
811
+ ? node(
812
+ "row",
813
+ { gap: "sm", title },
814
+ [
815
+ node("spinner", { style: "starburst", role: "omp.thinking.spin" }),
816
+ text([span("Thinking…", "muted")]),
817
+ elapsed(performance.now() - (this.#thinkingClock.get(index)?.start ?? performance.now())),
818
+ ...(rate >= 0.05 ? [node("rate", { value: rate, unit: "tok/s" })] : []),
819
+ ],
820
+ "head",
821
+ )
822
+ : node("text", { spans: [span(thoughtLabel(clock), "muted")], title }, undefined, "head");
823
+ const body = markdown(`k${index}`, "body", display.text, streaming);
824
+ children.push(
825
+ node(
826
+ "section",
827
+ {
828
+ // `.live` while streaming: the body clamps to its tail under a fade.
829
+ // `.ghost` while thinking is hidden (Ctrl+T): only a faint "Thought for 12s" stays.
830
+ role: thinkingLive
831
+ ? "omp.thinking.live"
832
+ : this.#hideThinkingBlock
833
+ ? "omp.thinking.ghost"
834
+ : "omp.thinking",
835
+ collapsible: true,
836
+ // Open while it streams; a finished thought folds to its "Thought for 12s" line.
837
+ collapsed: this.#thinkingCollapsed.get(index) ?? (this.#hideThinkingBlock || !thinkingLive),
838
+ // Tern's fold head sums it into "Worked for 12s".
839
+ took: clock?.end === undefined ? undefined : Math.max(0, Math.round(clock.end - clock.start)),
840
+ },
841
+ [head, body],
842
+ `k${index}`,
843
+ ),
844
+ );
845
+ if (!this.#hideThinkingBlock) children.push(...(this.#thinkingExtensions.get(index) ?? []));
846
+ } else if (content.type === "image" && content.data && content.mimeType && this.#showImages) {
847
+ children.push(this.#nativeImages.get(`i${index}`, content.data, content.mimeType));
848
+ }
849
+ }
850
+ if (this.#showImages && this.#showToolResultImages) {
851
+ for (const [toolCallId, images] of this.#toolImagesByCallId) {
852
+ images.forEach((image, index) => {
853
+ children.push(this.#nativeImages.get(`r${toolCallId}:${index}`, image.data, image.mimeType));
854
+ });
855
+ }
856
+ }
857
+ const errorNode = this.#describeError(message);
858
+ if (errorNode) children.push(errorNode);
859
+ }
860
+ this.#nativeParts = parts;
861
+ children.push(...this.#markerSlot.children);
862
+ if (this.#rewound) {
863
+ children.push(
864
+ node(
865
+ "badge",
866
+ { text: "↺ rewound", tone: "muted", role: "omp.assistant.rewound-tag" },
867
+ undefined,
868
+ "rewound",
869
+ ),
870
+ );
871
+ }
872
+ if (this.#turnUsage) {
873
+ const usage = formatTurnUsage(this.#turnUsage);
874
+ children.push(
875
+ node(
876
+ "row",
877
+ { role: "omp.turn.usage", gap: "xs", align: "center", title: usage.title },
878
+ [
879
+ node("icon", { name: "time" }, undefined, "icon"),
880
+ node("text", { text: usage.text }, undefined, "text"),
881
+ ],
882
+ "turn-usage",
883
+ ),
884
+ );
885
+ }
886
+ return col(children, { role: this.#rewound ? "omp.assistant.rewound" : "omp.assistant" });
887
+ }
888
+
889
+ /**
890
+ * Turn-ending error, recovered-retry note or abort label. A failed request
891
+ * is one error frame (head: "Request failed" + the HTTP status chip; body:
892
+ * the message once; then Retry / Copy error / Switch model), and stays in
893
+ * the transcript: the pinned banner is ANSI-only. A recovered attempt is an
894
+ * inline row that discloses the original error.
895
+ */
896
+ #describeError(message: AssistantMessage): NativeNode | undefined {
897
+ const presentation = resolveAssistantErrorPresentation(message);
898
+ if (presentation.kind === "compact-recovered") {
899
+ const attempt = message.retryRecovery?.attempt ?? 1;
900
+ const body = [message.errorMessage?.trim(), presentation.text].filter(
901
+ (line, index, all): line is string => !!line && all.indexOf(line) === index,
902
+ );
903
+ return node(
904
+ "section",
905
+ {
906
+ role: "omp.assistant.recovered",
907
+ head: [span(`↻ Recovered after ${attempt} ${attempt === 1 ? "retry" : "retries"}`, "muted")],
908
+ collapsible: true,
909
+ collapsed: true,
910
+ },
911
+ [text([span(body.join("\n"), "mono")], { wrap: "word" })],
912
+ "error",
913
+ );
914
+ }
915
+ if (presentation.kind !== "full" || message.content.some(content => content.type === "toolCall"))
916
+ return undefined;
917
+ if (message.stopReason === "aborted") {
918
+ return text([span(presentation.text, "error")], { wrap: "word", key: "error", role: "omp.assistant.abort" });
919
+ }
920
+ const lines = presentation.text
921
+ .split("\n")
922
+ .map(line => line.trim())
923
+ .filter(line => line.length > 0);
924
+ // "500 upstream overloaded" → chip "500", then the message once: a
925
+ // line the next one repeats with more detail is dropped.
926
+ const code = /^(\d{3})\s+/.exec(lines[0] ?? "");
927
+ if (code) lines[0] = lines[0]!.slice(code[0].length);
928
+ const detail = lines.filter((line, index) => !lines.slice(index + 1).some(next => next.startsWith(line)));
929
+ const errorText = detail.join("\n") || "Unknown error";
930
+ const children: NativeChild[] = [
931
+ node(
932
+ "row",
933
+ { gap: "sm" },
934
+ [
935
+ text([span("Request failed", "error strong")]),
936
+ ...(code ? [node("badge", { text: code[1]!, tone: "error", role: "omp.error.code" })] : []),
937
+ ],
938
+ "head",
939
+ ),
940
+ text([span(errorText, "mono")], {
941
+ wrap: "word",
942
+ lines: this.#errorExpanded ? undefined : MAX_TRANSCRIPT_ERROR_ROWS,
943
+ role: "omp.error.message",
944
+ key: "message",
945
+ }),
946
+ ];
947
+ if (message.stopReason === "error" && hasTranscriptActions()) {
948
+ const button = (act: string, label: string, keys: readonly string[], title: string): NativeNode =>
949
+ node(
950
+ "row",
951
+ { gap: "xs", role: "omp.error.action", actions: { click: act }, title },
952
+ keys.length > 0 ? [text(label), node("kbd", { keys })] : [text(label)],
953
+ act,
954
+ );
955
+ children.push(
956
+ node(
957
+ "row",
958
+ { gap: "sm", role: "omp.error.actions" },
959
+ [
960
+ button("retry", "Retry", ["F5"], "Retry the failed turn"),
961
+ button("copy-error", "Copy error", [], "Copy the error message"),
962
+ button("switch-model", "Switch model", ["⌥P"], "Pick another model for this session"),
963
+ ],
964
+ "actions",
965
+ ),
966
+ );
967
+ }
968
+ this.#errorText = errorText;
969
+ return card({ role: "omp.error", tone: "error", key: "error" }, children);
970
+ }
971
+
972
+ /** Error frame action clicks: omp's own retry, clipboard and model-picker paths. */
973
+ #handleErrorAction(act: string): void {
974
+ if (act === "retry") runTranscriptAction({ act: "retry" });
975
+ else if (act === "switch-model") runTranscriptAction({ act: "switch-model" });
976
+ else if (act === "copy-error" && this.#errorText) runTranscriptAction({ act: "copy", text: this.#errorText });
977
+ }
978
+
652
979
  /** Width-independent stable identities for the streamed leading thinking run. */
653
980
  getTranscriptStableRows(): readonly TranscriptStableRow[] {
654
981
  return this.#transcriptStableRows;
@@ -1066,6 +1393,8 @@ export class AssistantMessageComponent extends Container {
1066
1393
  }
1067
1394
 
1068
1395
  #appendThinkingExtensions(contentIndex: number, thinkingIndex: number, text: string): void {
1396
+ const mounted: Component[] = [];
1397
+ this.#thinkingExtensions.set(contentIndex, mounted);
1069
1398
  for (const renderer of this.#thinkingRenderers) {
1070
1399
  try {
1071
1400
  const component = renderer(
@@ -1079,6 +1408,7 @@ export class AssistantMessageComponent extends Container {
1079
1408
  );
1080
1409
  if (component) {
1081
1410
  this.#contentContainer.addChild(component);
1411
+ mounted.push(component);
1082
1412
  }
1083
1413
  } catch {
1084
1414
  // Ignore extension renderer failures and keep the original thinking block visible.
@@ -1198,6 +1528,7 @@ export class AssistantMessageComponent extends Container {
1198
1528
  // Everything below renders the display form; #lastMessage keeps the
1199
1529
  // verbatim message so re-renders re-derive the reaction deterministically.
1200
1530
  message = this.#displayMessage(message, this.#lastUpdateTransient);
1531
+ this.#displayedMessage = message;
1201
1532
 
1202
1533
  // Streaming-speed gauge: only a live, in-flight render of the single
1203
1534
  // animating hidden-thinking block feeds the shared session tracker. The
@@ -1211,7 +1542,10 @@ export class AssistantMessageComponent extends Container {
1211
1542
  // the gauge and pollute the next block. Providers that report usage only at
1212
1543
  // turn end leave the live count flat, so the rate stays 0 and the badge
1213
1544
  // self-suppresses (see #thinkingDotsLabel).
1214
- const isThinkingNow = this.#lastUpdateTransient && this.#shouldAnimateThinking(message);
1545
+ // Native terminals show the live rate on visible thinking too, not only the hidden pulse.
1546
+ const isThinkingNow =
1547
+ this.#lastUpdateTransient &&
1548
+ (isNativeRendering() ? this.#thinkingTailIndex(message) !== undefined : this.#shouldAnimateThinking(message));
1215
1549
  if (isThinkingNow) {
1216
1550
  const currentTokens = message.usage.reasoningTokens ?? message.usage.output;
1217
1551
  this.#thinkingTokens = currentTokens;
@@ -1240,6 +1574,7 @@ export class AssistantMessageComponent extends Container {
1240
1574
 
1241
1575
  // Clear content container
1242
1576
  this.#contentContainer.clear();
1577
+ this.#thinkingExtensions.clear();
1243
1578
  this.#emergencyText = undefined;
1244
1579
  this.#thinkingDots = undefined;
1245
1580
  this.#hasTruncatableError = false;
@@ -25,10 +25,10 @@ export function createBackgroundTanDispatchBlock(message: CustomMessage<unknown>
25
25
  return new TranscriptStatusBlock([
26
26
  {
27
27
  parts: [
28
- theme.fg("muted", `${theme.icon.output} Tangent dispatched`),
29
- theme.fg("dim", "[task]"),
30
- theme.fg("accent", jobId),
31
- work ? theme.fg("dim", `${theme.format.dash} ${work}`) : undefined,
28
+ { text: `${theme.icon.output} Tangent dispatched`, color: "muted" },
29
+ { text: "[task]", color: "dim" },
30
+ { text: jobId, color: "accent" },
31
+ work ? { text: `${theme.format.dash} ${work}`, color: "dim" } : undefined,
32
32
  ],
33
33
  },
34
34
  ]);
@@ -21,10 +21,17 @@ import {
21
21
  buildExecutionFrame,
22
22
  buildStatusFooter,
23
23
  clampDisplayLine,
24
+ describeExecutionCard,
25
+ describeExecutionTool,
26
+ type ExecutionColorKey,
24
27
  type ExecutionStatus,
25
28
  PREVIEW_LINES,
26
29
  resolveExecutionStatus,
27
30
  } from "./execution-shared";
31
+ import { span, text } from "../native/describe";
32
+ import { type DescribeContext, type NativeNode, type NativeUiEvent, rootToggleExpanded } from "../native/node";
33
+ import { NativeImageCache } from "../native/blobs";
34
+ import { Memo } from "../native/memo";
28
35
 
29
36
  const STREAMING_LINE_CAP = PREVIEW_LINES * 5;
30
37
  // Minimum interval between processing incoming chunks for display (ms).
@@ -78,6 +85,13 @@ export class BashExecutionComponent extends Container {
78
85
  #showImages = true;
79
86
  readonly #instanceId = nextBashExecutionId++;
80
87
  readonly #command: string;
88
+ readonly #colorKey: ExecutionColorKey;
89
+ readonly #startedAt = performance.now();
90
+ #endedAt: number | undefined;
91
+ // Bumped whenever the displayed output lines change.
92
+ #outputVersion = 0;
93
+ readonly #native = new Memo();
94
+ readonly #nativeImages = new NativeImageCache();
81
95
 
82
96
  constructor(command: string, ui: TUI, excludeFromContext = false) {
83
97
  super();
@@ -86,6 +100,7 @@ export class BashExecutionComponent extends Container {
86
100
 
87
101
  // Use dim border for excluded-from-context commands (!! prefix)
88
102
  const colorKey = excludeFromContext ? "dim" : "bashMode";
103
+ this.#colorKey = colorKey;
89
104
  const { contentContainer, loader } = buildExecutionFrame(this, ui, colorKey);
90
105
  this.#contentContainer = contentContainer;
91
106
  this.#loader = loader;
@@ -136,6 +151,68 @@ export class BashExecutionComponent extends Container {
136
151
  this.#updateDisplay();
137
152
  }
138
153
 
154
+ handleNativeEvent(event: NativeUiEvent): void {
155
+ const expanded = rootToggleExpanded(event);
156
+ if (expanded !== undefined) this.setExpanded(expanded);
157
+ }
158
+
159
+ /**
160
+ * The agent's bash `tool` frame (role `omp.bash`) with a `you` badge: the
161
+ * command in the head, the output as an `ansi` mini terminal following its
162
+ * tail. Terminals without the `tool` kind get a `card` headed by the
163
+ * command.
164
+ */
165
+ override describe(cx?: DescribeContext): NativeNode {
166
+ const dataFirst = cx?.supports("tool") === true;
167
+ const key = [
168
+ dataFirst,
169
+ this.#outputVersion,
170
+ this.#blockVersion,
171
+ this.#status,
172
+ this.#expanded,
173
+ this.#showImages,
174
+ this.#images.length,
175
+ ];
176
+ return this.#native.get(key, () => {
177
+ const images = this.#images.map((image, index) =>
178
+ this.#showImages
179
+ ? this.#nativeImages.get(`img${index}`, image.data, image.mimeType)
180
+ : text([
181
+ span(
182
+ imageFallback(image.mimeType, getImageDimensions(image.data, image.mimeType) ?? undefined),
183
+ "muted",
184
+ ),
185
+ ]),
186
+ );
187
+ const common = {
188
+ role: "omp.bash",
189
+ status: this.#status,
190
+ startedAt: this.#startedAt,
191
+ expanded: this.#expanded,
192
+ output: this.#outputLines.join("\n"),
193
+ images,
194
+ exitCode: this.#exitCode,
195
+ truncation: this.#truncation,
196
+ artifactError: this.#artifactError,
197
+ };
198
+ return dataFirst
199
+ ? describeExecutionTool({
200
+ ...common,
201
+ name: "bash",
202
+ title: "Bash",
203
+ command: this.#command,
204
+ lang: "bash",
205
+ excluded: this.#colorKey === "dim",
206
+ endedAt: this.#endedAt,
207
+ })
208
+ : describeExecutionCard({
209
+ ...common,
210
+ head: [span(`$ ${this.#command}`, `${this.#colorKey} strong`)],
211
+ muted: this.#colorKey === "dim",
212
+ });
213
+ });
214
+ }
215
+
139
216
  appendOutput(chunk: string): void {
140
217
  if (this.#ptyMode) return;
141
218
  // During high-throughput output (e.g. seq 1 500M), processing every
@@ -165,6 +242,7 @@ export class BashExecutionComponent extends Container {
165
242
  }
166
243
 
167
244
  this.#displayDirty = true;
245
+ this.#outputVersion++;
168
246
  }
169
247
 
170
248
  /** Switch to PTY rendering and feed raw terminal bytes through the vterm replay. */
@@ -240,6 +318,7 @@ export class BashExecutionComponent extends Container {
240
318
  const base = theme.getFgAnsi("muted");
241
319
  this.#outputLines = rows.map(row => (row ? styleTerminalRow(row, base) : ""));
242
320
  this.#displayDirty = true;
321
+ this.#outputVersion++;
243
322
  }
244
323
 
245
324
  /** Final full-scrollback read; the terminal is disposed once lines are snapshotted. */
@@ -268,6 +347,7 @@ export class BashExecutionComponent extends Container {
268
347
  ): void {
269
348
  this.#exitCode = exitCode;
270
349
  this.#status = resolveExecutionStatus(exitCode, cancelled);
350
+ this.#endedAt ??= performance.now();
271
351
  this.#truncation = options?.truncation;
272
352
  this.#artifactError = options?.artifactError;
273
353
  this.#images = options?.images ?? [];
@@ -376,6 +456,7 @@ export class BashExecutionComponent extends Container {
376
456
  #setOutput(output: string): void {
377
457
  const clean = sanitizeWithOptionalSixelPassthrough(output, sanitizeText);
378
458
  this.#outputLines = clean ? this.#clampLinesPreservingSixel(clean.split("\n")) : [];
459
+ this.#outputVersion++;
379
460
  }
380
461
 
381
462
  /**
@@ -87,6 +87,14 @@ export class CacheInvalidationMarkerComponent extends MessageDividerComponent {
87
87
  labelColor: "muted",
88
88
  ruleColor: "dim",
89
89
  ruleWidth: 10,
90
+ role: "omp.marker.cache-miss",
91
+ native: {
92
+ icon: "database",
93
+ label: () =>
94
+ info.reprocessedTokens > 0
95
+ ? `Cache miss · ${formatNumber(info.reprocessedTokens)} tokens`
96
+ : "Cache miss",
97
+ },
90
98
  // Too narrow to frame — preserve the legacy bare label.
91
99
  truncateWhenNarrow: false,
92
100
  });