@linxiraos/pi-tui 1.1.26 → 1.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (423) hide show
  1. package/CHANGELOG.md +51 -491
  2. package/README.md +13 -7
  3. package/dist/types/active-terminal.d.ts +30 -0
  4. package/dist/types/app-keybindings.d.ts +7 -2
  5. package/dist/types/apps/cleanse-board.d.ts +1 -1
  6. package/dist/types/apps/debug/log-viewer.d.ts +4 -0
  7. package/dist/types/apps/debug/raw-sse-buffer.d.ts +2 -0
  8. package/dist/types/apps/git/git-tui.d.ts +5 -4
  9. package/dist/types/apps/git/help.d.ts +3 -1
  10. package/dist/types/apps/if-bench-board.d.ts +2 -2
  11. package/dist/types/apps/setup-model-picker.d.ts +1 -1
  12. package/dist/types/autocomplete.d.ts +5 -2
  13. package/dist/types/charts/chart-plan.d.ts +674 -0
  14. package/dist/types/charts/chart-svg.d.ts +59 -0
  15. package/dist/types/charts/table-data.d.ts +175 -0
  16. package/dist/types/chat/assistant-message.d.ts +11 -1
  17. package/dist/types/chat/chat-transcript-builder.d.ts +3 -0
  18. package/dist/types/chat/compaction-summary-message.d.ts +1 -0
  19. package/dist/types/chat/execution-shared.d.ts +6 -5
  20. package/dist/types/chat/fence-figure.d.ts +42 -0
  21. package/dist/types/chat/figure-markdown.d.ts +42 -0
  22. package/dist/types/chat/messages.d.ts +1 -1
  23. package/dist/types/chat/svg-figure.d.ts +33 -0
  24. package/dist/types/chat/svg-source.d.ts +49 -0
  25. package/dist/types/chat/table-chart.d.ts +89 -0
  26. package/dist/types/chat/tool-execution.d.ts +2 -0
  27. package/dist/types/chat/user-message.d.ts +17 -4
  28. package/dist/types/chrome/shared.d.ts +9 -0
  29. package/dist/types/chrome/status-notice.d.ts +15 -3
  30. package/dist/types/chrome/transcript-container.d.ts +6 -0
  31. package/dist/types/components/box.d.ts +1 -0
  32. package/dist/types/components/disclosure.d.ts +1 -0
  33. package/dist/types/components/editor.d.ts +23 -5
  34. package/dist/types/components/form.d.ts +1 -0
  35. package/dist/types/components/image.d.ts +26 -44
  36. package/dist/types/components/input.d.ts +2 -1
  37. package/dist/types/components/key-value-list.d.ts +1 -0
  38. package/dist/types/components/layout/row.d.ts +1 -0
  39. package/dist/types/components/layout/stack.d.ts +1 -0
  40. package/dist/types/components/loader.d.ts +5 -6
  41. package/dist/types/components/markdown.d.ts +47 -5
  42. package/dist/types/components/scroll-view.d.ts +1 -0
  43. package/dist/types/components/section.d.ts +1 -0
  44. package/dist/types/components/table.d.ts +1 -0
  45. package/dist/types/components/text.d.ts +1 -0
  46. package/dist/types/components/tree-view.d.ts +7 -0
  47. package/dist/types/components/truncated-text.d.ts +1 -0
  48. package/dist/types/fuzzy.d.ts +29 -1
  49. package/dist/types/key-hint-format.d.ts +9 -1
  50. package/dist/types/keybinding-matchers.d.ts +3 -0
  51. package/dist/types/keybindings.d.ts +1 -1
  52. package/dist/types/kitty-graphics.d.ts +2 -2
  53. package/dist/types/loop-watchdog.d.ts +8 -7
  54. package/dist/types/multiplexers/cmux.d.ts +16 -0
  55. package/dist/types/multiplexers/herdr.d.ts +26 -0
  56. package/dist/types/multiplexers/notify.d.ts +13 -0
  57. package/dist/types/multiplexers/orca.d.ts +12 -0
  58. package/dist/types/multiplexers/screen.d.ts +9 -0
  59. package/dist/types/multiplexers/tmux.d.ts +29 -0
  60. package/dist/types/multiplexers/types.d.ts +100 -0
  61. package/dist/types/multiplexers/wmux.d.ts +12 -0
  62. package/dist/types/multiplexers/zellij.d.ts +12 -0
  63. package/dist/types/native/apply.d.ts +4 -3
  64. package/dist/types/native/backend.d.ts +5 -4
  65. package/dist/types/native/blobs.d.ts +7 -3
  66. package/dist/types/native/encode.d.ts +8 -6
  67. package/dist/types/native/node.d.ts +36 -5
  68. package/dist/types/native/overlay.d.ts +8 -3
  69. package/dist/types/native/settle.d.ts +4 -3
  70. package/dist/types/overlays/agent-hub-projection.d.ts +6 -5
  71. package/dist/types/overlays/agent-hub-types.d.ts +9 -0
  72. package/dist/types/overlays/agent-hub.d.ts +3 -0
  73. package/dist/types/overlays/agent-transcript-viewer.d.ts +12 -2
  74. package/dist/types/overlays/agents-hub.d.ts +9 -0
  75. package/dist/types/overlays/annotation-overlay.d.ts +19 -2
  76. package/dist/types/overlays/annotation-types.d.ts +2 -0
  77. package/dist/types/overlays/ask-dialog.d.ts +5 -2
  78. package/dist/types/overlays/btw-history-panel.d.ts +13 -1
  79. package/dist/types/overlays/copy-selector.d.ts +1 -0
  80. package/dist/types/overlays/history-search.d.ts +10 -0
  81. package/dist/types/overlays/hook-selector.d.ts +9 -4
  82. package/dist/types/overlays/hub-frame.d.ts +4 -1
  83. package/dist/types/overlays/jobs-panel.d.ts +1 -7
  84. package/dist/types/overlays/model-browser.d.ts +54 -7
  85. package/dist/types/overlays/model-hub.d.ts +37 -0
  86. package/dist/types/overlays/oauth-selector.d.ts +0 -1
  87. package/dist/types/overlays/plan-review-overlay.d.ts +3 -2
  88. package/dist/types/overlays/report-panel.d.ts +68 -0
  89. package/dist/types/overlays/rewind-selector.d.ts +1 -0
  90. package/dist/types/overlays/session-observer-registry.d.ts +2 -0
  91. package/dist/types/overlays/session-selector.d.ts +6 -2
  92. package/dist/types/overlays/settings-defs.d.ts +1 -0
  93. package/dist/types/overlays/thinking-selector.d.ts +2 -2
  94. package/dist/types/overlays/usage-dashboard.d.ts +12 -8
  95. package/dist/types/overlays/usage-display.d.ts +3 -1
  96. package/dist/types/prompt/composer-cache.d.ts +0 -16
  97. package/dist/types/prompt/composer.d.ts +18 -3
  98. package/dist/types/prompt/custom-editor.d.ts +25 -13
  99. package/dist/types/prompt/image-source.d.ts +2 -1
  100. package/dist/types/prompt/interactive-context-helpers.d.ts +5 -1
  101. package/dist/types/prompt/model-mention-autocomplete.d.ts +1 -1
  102. package/dist/types/prompt/welcome.d.ts +7 -2
  103. package/dist/types/render/composite.d.ts +2 -0
  104. package/dist/types/render/output-pane.d.ts +1 -0
  105. package/dist/types/render/render-utils.d.ts +1 -0
  106. package/dist/types/render/tool-card.d.ts +6 -0
  107. package/dist/types/render/width-aware-text.d.ts +2 -0
  108. package/dist/types/setup/scenes/types.d.ts +1 -0
  109. package/dist/types/space-hold.d.ts +40 -25
  110. package/dist/types/status-line/component.d.ts +3 -0
  111. package/dist/types/status-line/context-usage.d.ts +7 -5
  112. package/dist/types/status-line/host.d.ts +4 -1
  113. package/dist/types/status-line/segments.d.ts +2 -0
  114. package/dist/types/status-line/types.d.ts +14 -3
  115. package/dist/types/terminal-capabilities.d.ts +41 -36
  116. package/dist/types/terminal-multiplexer.d.ts +127 -14
  117. package/dist/types/terminal.d.ts +23 -16
  118. package/dist/types/theme/active-symbols.d.ts +1 -1
  119. package/dist/types/theme/schema.d.ts +11 -2
  120. package/dist/types/theme/symbols.d.ts +1 -1
  121. package/dist/types/theme/theme-class.d.ts +7 -0
  122. package/dist/types/theme/theme.d.ts +1 -1
  123. package/dist/types/theme/tui-adapters.d.ts +3 -0
  124. package/dist/types/thinking.d.ts +1 -1
  125. package/dist/types/tmux.d.ts +11 -4
  126. package/dist/types/tools/bash.d.ts +7 -2
  127. package/dist/types/tools/daemon.d.ts +1 -1
  128. package/dist/types/tools/eval.d.ts +22 -3
  129. package/dist/types/tools/glob.d.ts +4 -0
  130. package/dist/types/tools/native-view.d.ts +47 -0
  131. package/dist/types/tools/output-meta.d.ts +11 -1
  132. package/dist/types/tools/renderer.d.ts +37 -2
  133. package/dist/types/tools/streaming-output.d.ts +20 -1
  134. package/dist/types/tools/task.d.ts +2 -3
  135. package/dist/types/tools/write.d.ts +9 -2
  136. package/dist/types/tools/yield.d.ts +22 -0
  137. package/dist/types/tui.d.ts +36 -0
  138. package/dist/types/utils.d.ts +6 -0
  139. package/dist/types/vim.d.ts +19 -5
  140. package/dist/types/windows-input-mode.d.ts +33 -1
  141. package/package.json +13 -9
  142. package/src/active-terminal.d.ts +30 -0
  143. package/src/active-terminal.ts +80 -0
  144. package/src/app-keybindings.d.ts +7 -2
  145. package/src/app-keybindings.ts +7 -2
  146. package/src/apps/autoresearch-dashboard.ts +7 -3
  147. package/src/apps/cleanse-board.ts +2 -2
  148. package/src/apps/cleanse-picker.ts +1 -1
  149. package/src/apps/debug/log-viewer.ts +188 -72
  150. package/src/apps/debug/raw-sse-buffer.ts +8 -0
  151. package/src/apps/debug/raw-sse.ts +39 -5
  152. package/src/apps/git/diff-pane.ts +96 -23
  153. package/src/apps/git/git-tui.ts +23 -16
  154. package/src/apps/git/help.ts +29 -20
  155. package/src/apps/git/sidebar.ts +8 -3
  156. package/src/apps/if-bench-board.ts +2 -2
  157. package/src/apps/ps-top.ts +2 -2
  158. package/src/apps/setup-model-picker.ts +1 -1
  159. package/src/autocomplete.d.ts +5 -2
  160. package/src/autocomplete.ts +67 -43
  161. package/src/bracketed-paste.ts +10 -3
  162. package/src/charts/chart-plan.d.ts +674 -0
  163. package/src/charts/chart-plan.ts +5081 -0
  164. package/src/charts/chart-svg.d.ts +59 -0
  165. package/src/charts/chart-svg.ts +4657 -0
  166. package/src/charts/table-data.d.ts +175 -0
  167. package/src/charts/table-data.ts +1478 -0
  168. package/src/chat/advisor-message.ts +11 -0
  169. package/src/chat/assistant-message.d.ts +11 -1
  170. package/src/chat/assistant-message.ts +245 -54
  171. package/src/chat/chat-transcript-builder.d.ts +3 -0
  172. package/src/chat/chat-transcript-builder.ts +9 -3
  173. package/src/chat/compaction-summary-message.d.ts +1 -0
  174. package/src/chat/compaction-summary-message.ts +8 -0
  175. package/src/chat/execution-shared.d.ts +6 -5
  176. package/src/chat/execution-shared.ts +42 -25
  177. package/src/chat/fence-figure.d.ts +42 -0
  178. package/src/chat/fence-figure.ts +80 -0
  179. package/src/chat/figure-markdown.d.ts +42 -0
  180. package/src/chat/figure-markdown.ts +174 -0
  181. package/src/chat/messages.d.ts +1 -1
  182. package/src/chat/messages.ts +1 -1
  183. package/src/chat/read-tool-group.ts +6 -4
  184. package/src/chat/skill-message.ts +6 -0
  185. package/src/chat/svg-figure.d.ts +33 -0
  186. package/src/chat/svg-figure.ts +399 -0
  187. package/src/chat/svg-source.d.ts +49 -0
  188. package/src/chat/svg-source.ts +188 -0
  189. package/src/chat/table-chart.d.ts +89 -0
  190. package/src/chat/table-chart.ts +294 -0
  191. package/src/chat/tool-execution.d.ts +2 -0
  192. package/src/chat/tool-execution.ts +86 -7
  193. package/src/chat/user-message.d.ts +17 -4
  194. package/src/chat/user-message.ts +71 -8
  195. package/src/chrome/shared.ts +53 -0
  196. package/src/chrome/status-notice.ts +25 -7
  197. package/src/chrome/transcript-container.d.ts +6 -0
  198. package/src/chrome/transcript-container.ts +162 -19
  199. package/src/components/box.d.ts +1 -0
  200. package/src/components/box.ts +5 -0
  201. package/src/components/disclosure.d.ts +1 -0
  202. package/src/components/disclosure.ts +8 -0
  203. package/src/components/editor.d.ts +23 -5
  204. package/src/components/editor.ts +316 -47
  205. package/src/components/form.d.ts +1 -0
  206. package/src/components/form.ts +8 -0
  207. package/src/components/image.d.ts +26 -44
  208. package/src/components/image.ts +115 -152
  209. package/src/components/input.d.ts +2 -1
  210. package/src/components/input.ts +17 -7
  211. package/src/components/key-value-list.d.ts +1 -0
  212. package/src/components/key-value-list.ts +4 -0
  213. package/src/components/layout/row.d.ts +1 -0
  214. package/src/components/layout/row.ts +7 -0
  215. package/src/components/layout/stack.d.ts +1 -0
  216. package/src/components/layout/stack.ts +7 -0
  217. package/src/components/loader.d.ts +5 -6
  218. package/src/components/loader.ts +10 -17
  219. package/src/components/markdown.d.ts +47 -5
  220. package/src/components/markdown.ts +786 -303
  221. package/src/components/scroll-view.d.ts +1 -0
  222. package/src/components/scroll-view.ts +5 -0
  223. package/src/components/section.d.ts +1 -0
  224. package/src/components/section.ts +5 -0
  225. package/src/components/settings-list.ts +2 -0
  226. package/src/components/table.d.ts +1 -0
  227. package/src/components/table.ts +4 -0
  228. package/src/components/text.d.ts +1 -0
  229. package/src/components/text.ts +7 -0
  230. package/src/components/tree-view.d.ts +7 -0
  231. package/src/components/tree-view.ts +38 -8
  232. package/src/components/truncated-text.d.ts +1 -0
  233. package/src/components/truncated-text.ts +5 -0
  234. package/src/desktop-notify.ts +3 -2
  235. package/src/fuzzy.d.ts +29 -1
  236. package/src/fuzzy.ts +115 -30
  237. package/src/hotkeys-markdown.ts +9 -2
  238. package/src/key-hint-format.d.ts +9 -1
  239. package/src/key-hint-format.ts +26 -11
  240. package/src/keybinding-matchers.d.ts +3 -0
  241. package/src/keybinding-matchers.ts +6 -2
  242. package/src/keybindings.d.ts +1 -1
  243. package/src/keybindings.ts +1 -1
  244. package/src/kitty-graphics.d.ts +2 -2
  245. package/src/kitty-graphics.ts +11 -5
  246. package/src/loop-watchdog.d.ts +8 -7
  247. package/src/loop-watchdog.ts +18 -13
  248. package/src/multiplexers/cmux.d.ts +16 -0
  249. package/src/multiplexers/cmux.ts +29 -0
  250. package/src/multiplexers/herdr.d.ts +26 -0
  251. package/src/multiplexers/herdr.ts +64 -0
  252. package/src/multiplexers/notify.d.ts +13 -0
  253. package/src/multiplexers/notify.ts +24 -0
  254. package/src/multiplexers/orca.d.ts +12 -0
  255. package/src/multiplexers/orca.ts +16 -0
  256. package/src/multiplexers/screen.d.ts +9 -0
  257. package/src/multiplexers/screen.ts +15 -0
  258. package/src/multiplexers/tmux.d.ts +29 -0
  259. package/src/multiplexers/tmux.ts +40 -0
  260. package/src/multiplexers/types.d.ts +100 -0
  261. package/src/multiplexers/types.ts +110 -0
  262. package/src/multiplexers/wmux.d.ts +12 -0
  263. package/src/multiplexers/wmux.ts +16 -0
  264. package/src/multiplexers/zellij.d.ts +12 -0
  265. package/src/multiplexers/zellij.ts +24 -0
  266. package/src/native/apply.d.ts +4 -3
  267. package/src/native/apply.ts +4 -3
  268. package/src/native/backend.d.ts +5 -4
  269. package/src/native/backend.ts +199 -22
  270. package/src/native/blobs.d.ts +7 -3
  271. package/src/native/blobs.ts +47 -12
  272. package/src/native/encode.d.ts +8 -6
  273. package/src/native/encode.ts +76 -34
  274. package/src/native/node.d.ts +36 -5
  275. package/src/native/node.ts +31 -5
  276. package/src/native/overlay.d.ts +8 -3
  277. package/src/native/overlay.ts +17 -4
  278. package/src/native/reconcile.ts +16 -4
  279. package/src/native/settle.d.ts +4 -3
  280. package/src/native/settle.ts +4 -3
  281. package/src/overlays/agent-hub-projection.d.ts +6 -5
  282. package/src/overlays/agent-hub-projection.ts +124 -13
  283. package/src/overlays/agent-hub-types.d.ts +9 -0
  284. package/src/overlays/agent-hub-types.ts +6 -0
  285. package/src/overlays/agent-hub.ts +48 -29
  286. package/src/overlays/agent-transcript-viewer.ts +93 -23
  287. package/src/overlays/agents-hub.ts +28 -8
  288. package/src/overlays/annotation-overlay.ts +293 -90
  289. package/src/overlays/annotation-types.ts +2 -0
  290. package/src/overlays/ask-dialog.ts +10 -17
  291. package/src/overlays/btw-history-panel.ts +136 -39
  292. package/src/overlays/copy-selector.d.ts +1 -0
  293. package/src/overlays/copy-selector.ts +32 -8
  294. package/src/overlays/extensions/extension-dashboard.ts +4 -4
  295. package/src/overlays/extensions/state-manager.ts +52 -12
  296. package/src/overlays/history-search.ts +100 -7
  297. package/src/overlays/hook-selector.ts +12 -5
  298. package/src/overlays/hub-frame.ts +19 -1
  299. package/src/overlays/jobs-panel.ts +33 -52
  300. package/src/overlays/model-browser.ts +367 -122
  301. package/src/overlays/model-hub.ts +435 -59
  302. package/src/overlays/move-overlay.ts +28 -14
  303. package/src/overlays/oauth-selector.ts +54 -70
  304. package/src/overlays/pause-screen.ts +2 -2
  305. package/src/overlays/plan-review-overlay.ts +208 -70
  306. package/src/overlays/plugin-settings.ts +3 -3
  307. package/src/overlays/report-panel.ts +191 -0
  308. package/src/overlays/rewind-selector.ts +88 -17
  309. package/src/overlays/session-observer-registry.d.ts +2 -0
  310. package/src/overlays/session-observer-registry.ts +10 -2
  311. package/src/overlays/session-selector.ts +64 -26
  312. package/src/overlays/settings-defs.ts +5 -4
  313. package/src/overlays/settings-selector.ts +33 -8
  314. package/src/overlays/snapcompact-shape-preview.ts +2 -3
  315. package/src/overlays/stats-notice.ts +1 -1
  316. package/src/overlays/thinking-selector.ts +11 -10
  317. package/src/overlays/tree-selector.ts +41 -20
  318. package/src/overlays/usage-dashboard.ts +117 -58
  319. package/src/overlays/usage-display.ts +10 -4
  320. package/src/overlays/usage-row.ts +22 -8
  321. package/src/prompt/attachment-chips.ts +15 -11
  322. package/src/prompt/composer-attachments.ts +6 -4
  323. package/src/prompt/composer-cache.ts +7 -86
  324. package/src/prompt/composer.ts +37 -26
  325. package/src/prompt/custom-editor.ts +282 -105
  326. package/src/prompt/image-source.ts +2 -1
  327. package/src/prompt/interactive-context-helpers.ts +32 -18
  328. package/src/prompt/macos-spelling.ts +7 -0
  329. package/src/prompt/magic-keywords.ts +11 -2
  330. package/src/prompt/model-mention-autocomplete.ts +14 -14
  331. package/src/prompt/queued-messages.ts +6 -4
  332. package/src/prompt/tips.txt +13 -9
  333. package/src/prompt/welcome.ts +64 -46
  334. package/src/prompt/word-completion.ts +38 -5
  335. package/src/render/composite.d.ts +2 -0
  336. package/src/render/composite.ts +64 -0
  337. package/src/render/output-pane.d.ts +1 -0
  338. package/src/render/output-pane.ts +6 -0
  339. package/src/render/render-utils.d.ts +1 -0
  340. package/src/render/render-utils.ts +16 -1
  341. package/src/render/tool-card.d.ts +6 -0
  342. package/src/render/tool-card.ts +15 -0
  343. package/src/render/width-aware-text.d.ts +2 -0
  344. package/src/render/width-aware-text.ts +5 -0
  345. package/src/setup/scenes/model.ts +2 -1
  346. package/src/setup/scenes/types.ts +1 -0
  347. package/src/space-hold.d.ts +40 -25
  348. package/src/space-hold.ts +156 -52
  349. package/src/status-line/component.ts +292 -78
  350. package/src/status-line/context-usage.ts +21 -46
  351. package/src/status-line/host.ts +4 -1
  352. package/src/status-line/segments.ts +40 -22
  353. package/src/status-line/types.ts +15 -3
  354. package/src/terminal-capabilities.d.ts +41 -36
  355. package/src/terminal-capabilities.ts +137 -213
  356. package/src/terminal-multiplexer.d.ts +127 -14
  357. package/src/terminal-multiplexer.ts +112 -33
  358. package/src/terminal.d.ts +23 -16
  359. package/src/terminal.ts +203 -99
  360. package/src/theme/active-symbols.d.ts +1 -1
  361. package/src/theme/active-symbols.ts +1 -1
  362. package/src/theme/schema-validation.ts +1 -0
  363. package/src/theme/schema.d.ts +11 -2
  364. package/src/theme/schema.ts +14 -1
  365. package/src/theme/session-color.ts +1 -7
  366. package/src/theme/symbols.d.ts +1 -1
  367. package/src/theme/symbols.ts +11 -4
  368. package/src/theme/theme-class.d.ts +7 -0
  369. package/src/theme/theme-class.ts +26 -1
  370. package/src/theme/theme-schema.json +5 -1
  371. package/src/theme/theme.d.ts +1 -1
  372. package/src/theme/theme.ts +8 -4
  373. package/src/theme/tui-adapters.d.ts +3 -0
  374. package/src/theme/tui-adapters.ts +68 -0
  375. package/src/thinking.d.ts +1 -1
  376. package/src/thinking.ts +1 -1
  377. package/src/tmux.d.ts +11 -4
  378. package/src/tmux.ts +47 -11
  379. package/src/tools/ast-edit.ts +59 -35
  380. package/src/tools/ast-grep.ts +35 -16
  381. package/src/tools/bash.d.ts +7 -2
  382. package/src/tools/bash.ts +116 -47
  383. package/src/tools/daemon.d.ts +1 -1
  384. package/src/tools/daemon.ts +1 -1
  385. package/src/tools/default-renderer.ts +35 -19
  386. package/src/tools/edit.ts +142 -120
  387. package/src/tools/eval-format/index.ts +28 -2
  388. package/src/tools/eval-format/javascript.ts +7 -5
  389. package/src/tools/eval.d.ts +22 -3
  390. package/src/tools/eval.ts +364 -208
  391. package/src/tools/fetch.ts +5 -6
  392. package/src/tools/glob.d.ts +4 -0
  393. package/src/tools/glob.ts +33 -65
  394. package/src/tools/grep.ts +81 -48
  395. package/src/tools/index.ts +2 -0
  396. package/src/tools/lsp.ts +81 -59
  397. package/src/tools/mcp.ts +111 -68
  398. package/src/tools/native-view.d.ts +47 -0
  399. package/src/tools/native-view.ts +102 -1
  400. package/src/tools/output-meta.d.ts +11 -1
  401. package/src/tools/output-meta.ts +23 -3
  402. package/src/tools/read.ts +7 -5
  403. package/src/tools/renderer.d.ts +37 -2
  404. package/src/tools/renderer.ts +42 -2
  405. package/src/tools/streaming-output.d.ts +20 -1
  406. package/src/tools/streaming-output.ts +154 -50
  407. package/src/tools/task.d.ts +2 -3
  408. package/src/tools/task.ts +115 -62
  409. package/src/tools/todo.ts +119 -30
  410. package/src/tools/wait.ts +34 -24
  411. package/src/tools/web-search.ts +1 -1
  412. package/src/tools/write.d.ts +9 -2
  413. package/src/tools/write.ts +147 -95
  414. package/src/tools/yield.d.ts +22 -0
  415. package/src/tools/yield.ts +225 -0
  416. package/src/tui.d.ts +36 -0
  417. package/src/tui.ts +666 -217
  418. package/src/utils.d.ts +6 -0
  419. package/src/utils.ts +19 -0
  420. package/src/vim.d.ts +19 -5
  421. package/src/vim.ts +333 -18
  422. package/src/windows-input-mode.d.ts +33 -1
  423. package/src/windows-input-mode.ts +212 -6
@@ -52,6 +52,10 @@ class AdvisorHeader implements Component {
52
52
  this.#uiTheme = uiTheme;
53
53
  }
54
54
 
55
+ releaseRenderCaches(): void {
56
+ this.#cache = undefined;
57
+ }
58
+
55
59
  invalidate(): void {
56
60
  this.#cache = undefined;
57
61
  }
@@ -82,6 +86,10 @@ class AdvisorNotes implements Component {
82
86
  this.#uiTheme = uiTheme;
83
87
  }
84
88
 
89
+ releaseRenderCaches(): void {
90
+ this.#cache = undefined;
91
+ }
92
+
85
93
  invalidate(): void {
86
94
  this.#cache = undefined;
87
95
  }
@@ -236,6 +244,9 @@ export function createAdvisorMessageCard(
236
244
  invalidate(): void {
237
245
  disclosure.invalidate();
238
246
  },
247
+ releaseRenderCaches(): void {
248
+ disclosure.releaseRenderCaches();
249
+ },
239
250
  dispose(): void {
240
251
  disclosure.dispose();
241
252
  },
@@ -38,7 +38,7 @@ export declare class AssistantMessageComponent extends Container {
38
38
  * for lack of a target is re-rendered with its reaction stripped.
39
39
  */
40
40
  pickReactionTarget(transcript: readonly Component[]): void;
41
- constructor(message?: AssistantMessage, hideThinkingBlock?: boolean, onImageUpdate?: () => void, thinkingRenderers?: readonly AssistantThinkingRenderer[], imageBudget?: ImageBudget, proseOnlyThinking?: boolean, linkTargets?: ReadonlyMap<string, string>);
41
+ constructor(message?: AssistantMessage, hideThinkingBlock?: boolean, onImageUpdate?: () => void, thinkingRenderers?: readonly AssistantThinkingRenderer[], imageBudget?: ImageBudget, proseOnlyThinking?: boolean, linkTargets?: ReadonlyMap<string, string>, expandThinkingBlocks?: boolean);
42
42
  /**
43
43
  * Show or clear the trailing cache-invalidation divider. Set at `message_end`
44
44
  * (live) or during rebuild, once the turn's usage is known and compared
@@ -54,6 +54,7 @@ export declare class AssistantMessageComponent extends Container {
54
54
  */
55
55
  setServedModelMismatch(info: ServedModelMismatch | undefined): void;
56
56
  invalidate(): void;
57
+ releaseRenderCaches(): void;
57
58
  setHideThinkingBlock(hide: boolean): void;
58
59
  /**
59
60
  * Allow or withhold retiring finished lines into native scrollback while the
@@ -65,6 +66,11 @@ export declare class AssistantMessageComponent extends Container {
65
66
  */
66
67
  setMidStreamPublication(allowed: boolean): void;
67
68
  setProseOnlyThinking(proseOnly: boolean): void;
69
+ /**
70
+ * Keep finished thinking sections expanded instead of folding them to "Thought for 12s".
71
+ * Sections the user folded or unfolded by hand keep that choice.
72
+ */
73
+ setExpandThinkingBlocks(expand: boolean): void;
68
74
  dispose(): void;
69
75
  /**
70
76
  * Toggle suppression of the inline `Error: …` line while the same error is
@@ -81,6 +87,8 @@ export declare class AssistantMessageComponent extends Container {
81
87
  */
82
88
  setExpanded(expanded: boolean): void;
83
89
  isTranscriptBlockFinalized(): boolean;
90
+ /** Whether a figure still waits for the raster of its final source; the transcript holds retirement meanwhile. */
91
+ isTranscriptBlockPending(): boolean;
84
92
  render(width: number): readonly string[];
85
93
  /**
86
94
  * A `col` (role `zeta.assistant`) of `md` nodes keyed by content index, so
@@ -118,6 +126,8 @@ export declare class AssistantMessageComponent extends Container {
118
126
  messagePersistenceKey(): string | undefined;
119
127
  /** Toggle rendering for assistant-native and tool-result images. */
120
128
  setImagesVisible(visible: boolean): void;
129
+ /** Toggle charts under numeric tables (the main session's transcript only, never a subagent's). */
130
+ setTableChartsVisible(visible: boolean): void;
121
131
  /** Toggle only images produced by tool results; assistant-native images remain governed by setImagesVisible. */
122
132
  setToolResultImagesVisible(visible: boolean): void;
123
133
  setToolResultImages(toolCallId: string, images: ImageContent[]): void;
@@ -2,14 +2,19 @@ import type { AssistantMessage, ImageContent, TextContent } from "@linxiraos/pi-
2
2
  import { type Component, Container } from "../tui";
3
3
  import { Image, type ImageBudget } from "../components/image";
4
4
  import { ImageProtocol, TERMINAL } from "../terminal-capabilities";
5
- import { Markdown, type MarkdownTheme } from "../components/markdown";
5
+ import {
6
+ type DefaultTextStyle,
7
+ Markdown,
8
+ type MarkdownTheme,
9
+ rewriteMarkdownLinkDestinations,
10
+ } from "../components/markdown";
6
11
  import { Spacer } from "../components/spacer";
7
12
  import { Text } from "../components/text";
8
13
  import { formatDuration, formatNumber } from "@linxiraos/pi-utils";
9
14
  import chalk from "@linxiraos/pi-utils/chalk";
10
15
  import { LRUCache } from "@linxiraos/pi-utils/lru";
11
16
  import type { AssistantThinkingRenderer } from "./extension-types";
12
- import { ensureThemeSync, getMarkdownTheme, getThemeEpoch, theme } from "../theme";
17
+ import { ensureThemeSync, getMarkdownTheme, getMarkdownThemeWithLinkTargets, getThemeEpoch, theme } from "../theme";
13
18
  import { card, col, elapsed, node, span, text } from "../native/describe";
14
19
  import { hasTranscriptActions, runTranscriptAction } from "./transcript-actions";
15
20
  import type { NativeChild, NativeNode, NativeUiEvent } from "../native/node";
@@ -27,6 +32,10 @@ import { type ServedModelMismatch, ServedModelMarkerComponent } from "./served-m
27
32
  import { isReactionTarget, type ReactionSplit, type ReactionTarget, splitReaction } from "./reaction";
28
33
  import { isRowPrefix, type TranscriptStableRow, trimBlankEdges } from "../chrome/transcript-container";
29
34
  import { formatTurnUsage, type TurnUsageSummary } from "../overlays/usage-row";
35
+ import { FigureMarkdown } from "./figure-markdown";
36
+ import { svgFigureRendering } from "./svg-figure";
37
+ import { hasSvgFence } from "./svg-source";
38
+ import { describeTableChart, hasChartTable, lookupTableChart, splitTableCharts } from "./table-chart";
30
39
 
31
40
  /**
32
41
  * Max wrapped rows of a turn-ending provider error rendered inline in the
@@ -46,6 +55,15 @@ function thoughtLabel(clock: { start: number; end?: number } | undefined): strin
46
55
  }
47
56
 
48
57
  type ThinkingContentBlock = Extract<AssistantMessage["content"][number], { type: "thinking" }>;
58
+ /** Renders one text or thinking block: Markdown, or {@link FigureMarkdown} for text holding a ```svg fence. */
59
+ type ProseBlock = Markdown | FigureMarkdown;
60
+ /** A streamed block the fast path updates in place. */
61
+ interface FastPathItem {
62
+ md: ProseBlock;
63
+ contentIndex: number;
64
+ blockType: "text" | "thinking";
65
+ lastText: string;
66
+ }
49
67
  type DisplayThinkingContentBlock = ThinkingContentBlock & { rawThinking?: string };
50
68
  type StablePartKind = "thinking" | "text";
51
69
  type StablePart = { kind: StablePartKind; text: string } | { kind: "spacer" };
@@ -230,22 +248,30 @@ export class AssistantMessageComponent extends Container {
230
248
  #cacheMarker?: CacheInvalidationMarkerComponent;
231
249
  #servedModelMarker?: ServedModelMarkerComponent;
232
250
  #lastMessage?: AssistantMessage;
233
- #emergencyText?: Markdown;
251
+ #emergencyText?: ProseBlock;
234
252
  #toolImagesByCallId = new Map<string, ImageContent[]>();
235
253
  /**
236
254
  * Payload keys ({@link imagePayloadKey}) whose Kitty PNG conversion this
237
- * component already awaits, so a re-delivered image neither re-encodes nor
255
+ * component is awaiting, so a re-delivered image neither re-encodes nor
238
256
  * schedules a second {@link updateContent} cascade. The conversions
239
- * themselves are shared process-wide by {@link convertImageToPngShared}.
257
+ * themselves live in the bounded process-wide cache behind
258
+ * {@link convertImageToPngShared}; an evicted one is redone on the next
259
+ * render instead of being pinned here for the session.
240
260
  */
241
261
  #kittyConversionsAwaited = new Set<string>();
242
262
  /**
243
- * Conversions this component displays, held so a later re-render (theme
244
- * invalidation) still finds them after the bounded shared cache evicts them.
263
+ * Conversions the current image children display, by payload key. Rebuilt
264
+ * on every full render pass ({@link updateContent}), so a conversion is held
265
+ * only while it is on screen — a re-render after the shared cache evicted
266
+ * it still finds it — and released once hidden or replaced.
245
267
  */
246
- #kittyConverted = new Map<string, ImageContent>();
268
+ #kittyDisplayed = new Map<string, ImageContent>();
269
+ /** The previous pass's {@link #kittyDisplayed}, readable only during a render pass. */
270
+ #kittyPreviouslyDisplayed: Map<string, ImageContent> | undefined;
247
271
  #showImages = true;
248
272
  #showToolResultImages = true;
273
+ /** Charts under numeric tables; off for subagent transcripts. */
274
+ #showTableCharts = true;
249
275
  #transcriptBlockFinalized: boolean;
250
276
  /** See {@link setMidStreamPublication}; the wire's `stream-revision` axis decides it. */
251
277
  #midStreamPublication = true;
@@ -293,9 +319,13 @@ export class AssistantMessageComponent extends Container {
293
319
  #lastUpdateTransient = false;
294
320
  // Fast-path state: reuse Markdown children when message shape is stable during streaming.
295
321
  #fastPathKey: string | undefined;
296
- #fastPathItems:
297
- | Array<{ md: Markdown; contentIndex: number; blockType: "text" | "thinking"; lastText: string }>
298
- | undefined;
322
+ #fastPathItems: FastPathItem[] | undefined;
323
+ /**
324
+ * Text blocks whose ```svg fences are lifted into figures, by content index.
325
+ * Kept across slow-path rebuilds so a figure keeps its raster (and its
326
+ * in-flight one) instead of starting over on every update.
327
+ */
328
+ #figureBlocks = new Map<number, FigureMarkdown>();
299
329
  /** Live "thinking" pulse shown in place of a hidden thinking block while it
300
330
  * streams; undefined when not animating. Driven by {@link #thinkingDotsTimer}. */
301
331
  #thinkingDots: Text | undefined;
@@ -339,6 +369,8 @@ export class AssistantMessageComponent extends Container {
339
369
  #textColorTransform?: (text: string) => string;
340
370
  #linkTargets: ReadonlyMap<string, string> = EMPTY_LINK_TARGETS;
341
371
  #markdownTheme: MarkdownTheme | undefined;
372
+ /** Text-block sources with {@link #linkTargets} applied, for the native `md` nodes; reset with the targets. */
373
+ #nativeLinkSources = new Map<string, string>();
342
374
  /** Block this reply reacts to; undefined when the preceding block takes no reactions. */
343
375
  #reactionTarget: ReactionTarget | undefined;
344
376
  /** Reaction lifted from the reply's opening emoji, once resolved. */
@@ -356,16 +388,38 @@ export class AssistantMessageComponent extends Container {
356
388
  /** Markdown nodes by key, reused while their text and streaming flag are unchanged. */
357
389
  #nativeParts = new Map<string, { text: string; stream: boolean; node: NativeNode }>();
358
390
  readonly #nativeImages = new NativeImageCache();
391
+ /** Smart table chart picks a TSP describe is waiting on. */
392
+ readonly #chartPicks = new Set<Promise<void>>();
359
393
 
360
394
  setTextColorTransform(transform?: (text: string) => string): void {
361
395
  this.#textColorTransform = transform;
362
396
  }
363
397
 
398
+ /**
399
+ * Default text style for assistant paragraph Markdown. An explicitly
400
+ * installed transform (live-command output) always wins and keeps its
401
+ * historical paint-everything contract; otherwise the optional
402
+ * `assistantMessageText` theme token paints prose, flagged `proseFg` so
403
+ * markdown sub-elements keep their own `md*` tokens (docs/theme.md).
404
+ * Returns undefined when the token is unset or empty so the terminal
405
+ * default foreground is used byte-for-byte, exactly as before the token
406
+ * existed.
407
+ *
408
+ * The token is re-derived on every rebuild (the theme-change path calls
409
+ * `invalidate()` and rebuilds through the teardown path, which re-consults
410
+ * the active theme), so prose follows theme switches both ways.
411
+ */
412
+ #getProseTextStyle(): DefaultTextStyle | undefined {
413
+ if (this.#textColorTransform) return { color: this.#textColorTransform };
414
+ if (typeof theme === "undefined" || !theme.hasColor("assistantMessageText")) return undefined;
415
+ return { color: (text: string) => theme.fg("assistantMessageText", text), proseFg: true };
416
+ }
417
+
364
418
  #getProseTheme(): MarkdownTheme {
365
419
  if (this.#markdownTheme) return this.#markdownTheme;
366
420
  const base = getMarkdownTheme();
367
421
  const snapshot = this.#linkTargets;
368
- const markdownTheme = snapshot.size > 0 ? { ...base, resolveLink: (href: string) => snapshot.get(href) } : base;
422
+ const markdownTheme = snapshot.size > 0 ? getMarkdownThemeWithLinkTargets(snapshot) : base;
369
423
  this.#markdownTheme = markdownTheme;
370
424
  return markdownTheme;
371
425
  }
@@ -387,6 +441,9 @@ export class AssistantMessageComponent extends Container {
387
441
  this.#markdownTheme = undefined;
388
442
  this.#fastPathKey = undefined;
389
443
  this.#fastPathItems = undefined;
444
+ for (const block of this.#figureBlocks.values()) block.restyle();
445
+ this.#nativeLinkSources.clear();
446
+ this.#nativeViewVersion++;
390
447
  if (this.#lastMessage) {
391
448
  this.updateContent(this.#lastMessage, { transient: this.#lastUpdateTransient });
392
449
  }
@@ -456,6 +513,7 @@ export class AssistantMessageComponent extends Container {
456
513
  readonly #thinkingRenderers: readonly AssistantThinkingRenderer[];
457
514
  readonly #imageBudget?: ImageBudget;
458
515
  #proseOnlyThinking: boolean;
516
+ #expandThinkingBlocks: boolean;
459
517
 
460
518
  constructor(
461
519
  message?: AssistantMessage,
@@ -465,6 +523,7 @@ export class AssistantMessageComponent extends Container {
465
523
  imageBudget?: ImageBudget,
466
524
  proseOnlyThinking = true,
467
525
  linkTargets?: ReadonlyMap<string, string>,
526
+ expandThinkingBlocks = false,
468
527
  ) {
469
528
  super();
470
529
  this.#hideThinkingBlock = hideThinkingBlock;
@@ -472,6 +531,7 @@ export class AssistantMessageComponent extends Container {
472
531
  this.#thinkingRenderers = thinkingRenderers;
473
532
  this.#imageBudget = imageBudget;
474
533
  this.#proseOnlyThinking = proseOnlyThinking;
534
+ this.#expandThinkingBlocks = expandThinkingBlocks;
475
535
 
476
536
  ensureThemeSync();
477
537
  this.#transcriptBlockFinalized = message !== undefined;
@@ -534,11 +594,18 @@ export class AssistantMessageComponent extends Container {
534
594
  this.#markdownTheme = undefined;
535
595
  this.#fastPathKey = undefined;
536
596
  this.#fastPathItems = undefined;
597
+ for (const block of this.#figureBlocks.values()) block.restyle();
537
598
  if (this.#lastMessage) {
538
599
  this.updateContent(this.#lastMessage, { transient: this.#lastUpdateTransient });
539
600
  }
540
601
  }
541
602
 
603
+ override releaseRenderCaches(): void {
604
+ super.releaseRenderCaches();
605
+ this.#dropStableRenders();
606
+ this.#emergencyText?.releaseRenderCaches();
607
+ }
608
+
542
609
  setHideThinkingBlock(hide: boolean): void {
543
610
  this.#hideThinkingBlock = hide;
544
611
  this.#thinkingCollapsed.clear();
@@ -560,6 +627,16 @@ export class AssistantMessageComponent extends Container {
560
627
  this.#proseOnlyThinking = proseOnly;
561
628
  }
562
629
 
630
+ /**
631
+ * Keep finished thinking sections expanded instead of folding them to "Thought for 12s".
632
+ * Sections the user folded or unfolded by hand keep that choice.
633
+ */
634
+ setExpandThinkingBlocks(expand: boolean): void {
635
+ if (this.#expandThinkingBlocks === expand) return;
636
+ this.#expandThinkingBlocks = expand;
637
+ this.#nativeViewVersion++;
638
+ }
639
+
563
640
  override dispose(): void {
564
641
  this.#stopThinkingAnimation();
565
642
  super.dispose();
@@ -700,6 +777,12 @@ export class AssistantMessageComponent extends Container {
700
777
  return this.#transcriptBlockFinalized;
701
778
  }
702
779
 
780
+ /** Whether a figure still waits for the raster of its final source; the transcript holds retirement meanwhile. */
781
+ isTranscriptBlockPending(): boolean {
782
+ for (const block of this.#figureBlocks.values()) if (block.pending) return true;
783
+ return false;
784
+ }
785
+
703
786
  override render(width: number): readonly string[] {
704
787
  const rows = super.render(width);
705
788
  this.#publishStableSnapshot(rows, width);
@@ -793,7 +876,36 @@ export class AssistantMessageComponent extends Container {
793
876
  const content = message.content[index]!;
794
877
  const streaming = live && index === tailIndex;
795
878
  if (content.type === "text" && canonicalizeMessage(content.text)) {
796
- children.push(markdown(`t${index}`, `t${index}`, content.text.trim(), streaming));
879
+ const source = content.text.trim();
880
+ // The terminal renders this source itself and would resolve a relative
881
+ // link against its own idea of the cwd: hand it the session-resolved
882
+ // targets (resolved once the segment closes, so never while streaming).
883
+ const linked = (text: string, live: boolean): string => {
884
+ if (live || this.#linkTargets.size === 0) return text;
885
+ const targets = this.#linkTargets;
886
+ const resolved =
887
+ this.#nativeLinkSources.get(text) ??
888
+ rewriteMarkdownLinkDestinations(text, href => targets.get(href));
889
+ this.#nativeLinkSources.set(text, resolved);
890
+ return resolved;
891
+ };
892
+ if (!this.#showImages || !this.#showTableCharts || !hasChartTable(source)) {
893
+ children.push(markdown(`t${index}`, `t${index}`, linked(source, streaming), streaming));
894
+ continue;
895
+ }
896
+ // A chart follows its table as an SVG image node; the prose splits around it.
897
+ const segments = splitTableCharts(source, streaming);
898
+ segments.forEach((segment, part) => {
899
+ const slot = part === 0 ? `t${index}` : `t${index}.${part}`;
900
+ if (segment.kind === "markdown") {
901
+ const live = streaming && part === segments.length - 1;
902
+ children.push(markdown(slot, slot, linked(segment.text.trim(), live), live));
903
+ return;
904
+ }
905
+ const chart = lookupTableChart(segment.table);
906
+ if (chart instanceof Promise) this.#awaitChart(chart);
907
+ else if (chart) children.push(describeTableChart(chart, slot));
908
+ });
797
909
  } else if (content.type === "thinking") {
798
910
  const display = resolveThinkingDisplay(content, this.#proseOnlyThinking);
799
911
  if (!display.visible) continue;
@@ -831,8 +943,10 @@ export class AssistantMessageComponent extends Container {
831
943
  // `.live` while streaming: the body clamps to its tail under a fade.
832
944
  role: thinkingLive ? "zeta.thinking.live" : "zeta.thinking",
833
945
  collapsible: true,
834
- // Open while it streams; a finished thought folds to its "Thought for 12s" line.
835
- collapsed: this.#thinkingCollapsed.get(index) ?? (this.#hideThinkingBlock || !thinkingLive),
946
+ // Open while it streams; a finished thought folds to its "Thought for 12s" line unless the user keeps thinking expanded.
947
+ collapsed:
948
+ this.#thinkingCollapsed.get(index) ??
949
+ (this.#hideThinkingBlock || (!thinkingLive && !this.#expandThinkingBlocks)),
836
950
  // Tern's fold head sums it into "Worked for 12s".
837
951
  took: clock?.end === undefined ? undefined : Math.max(0, Math.round(clock.end - clock.start)),
838
952
  },
@@ -1074,12 +1188,18 @@ export class AssistantMessageComponent extends Container {
1074
1188
  for (const child of this.#contentContainer.children) {
1075
1189
  const item = items[itemIndex];
1076
1190
  if (item?.md === child) {
1191
+ const md = item.md;
1192
+ if (md instanceof FigureMarkdown) {
1193
+ // Plain Markdown reproduces only the prose ahead of the first figure.
1194
+ if (md.leadingProse) parts.push({ kind: item.blockType, text: md.leadingProse });
1195
+ break;
1196
+ }
1077
1197
  if (itemIndex === items.length - 1) {
1078
1198
  // Streaming child: publish Markdown's frozen prefix, and only
1079
1199
  // once non-blank content exists past it — the block's last
1080
1200
  // non-blank line is still being written (thinking's prose fold
1081
1201
  // may rewrite it) and must stay out of published bytes.
1082
- const raw = item.md.getLastRenderStableText();
1202
+ const raw = md.getLastRenderStableText();
1083
1203
  const frozen = raw.trim();
1084
1204
  if (frozen.length > 0 && /\S/.test(item.lastText.slice(raw.length))) {
1085
1205
  parts.push({ kind: item.blockType, text: frozen });
@@ -1107,7 +1227,7 @@ export class AssistantMessageComponent extends Container {
1107
1227
  /**
1108
1228
  * Render the first `partCount` stable parts, the final one cut to
1109
1229
  * `lastLength`. Rows match fresh Markdown renders of each part byte for
1110
- * byte — {@link #createStableMarkdown} mirrors the live children — but a
1230
+ * byte — {@link #createMarkdown} builds the live children too — but a
1111
1231
  * closed part renders once per width, and the growing final part reuses one
1112
1232
  * Markdown instance so its already-frozen blocks are not re-lexed.
1113
1233
  */
@@ -1157,7 +1277,7 @@ export class AssistantMessageComponent extends Container {
1157
1277
  // Trim like the live children, dropping the trailing blank line a frozen
1158
1278
  // prefix still carries.
1159
1279
  const trimmed = text.trim();
1160
- if (this.#transcriptBlockFinalized) return this.#createStableMarkdown(kind, trimmed).render(width);
1280
+ if (this.#transcriptBlockFinalized) return this.#createMarkdown(kind, trimmed).render(width);
1161
1281
  const head = this.#stableHeadRenderer;
1162
1282
  const replay = this.#stableReplayRenderer;
1163
1283
  const renderer = role === "head" ? head : role === "replay" ? replay : head?.index === index ? head : replay;
@@ -1165,29 +1285,74 @@ export class AssistantMessageComponent extends Container {
1165
1285
  renderer.md.setText(trimmed);
1166
1286
  return renderer.md.render(width);
1167
1287
  }
1168
- const md = this.#createStableMarkdown(kind, trimmed);
1288
+ const md = this.#createMarkdown(kind, trimmed);
1169
1289
  if (role === "head") this.#stableHeadRenderer = { index, kind, md };
1170
1290
  else if (role === "replay") this.#stableReplayRenderer = { index, kind, md };
1171
1291
  return md.render(width);
1172
1292
  }
1173
1293
 
1174
- /** Constructor args mirror the live child Markdown so stable rows prefix the block render. */
1175
- #createStableMarkdown(kind: StablePartKind, text: string): Markdown {
1294
+ /** Markdown for a text or thinking block; live children and stable-row renders share it so stable rows prefix the block render. */
1295
+ #createMarkdown(kind: StablePartKind, text: string): Markdown {
1176
1296
  return kind === "text"
1177
- ? new Markdown(
1178
- text,
1179
- 1,
1180
- 0,
1181
- this.#getProseTheme(),
1182
- this.#textColorTransform ? { color: this.#textColorTransform } : undefined,
1183
- 0,
1184
- )
1297
+ ? new Markdown(text, 1, 0, this.#getProseTheme(), this.#getProseTextStyle(), 0)
1185
1298
  : new Markdown(text, 1, 0, getMarkdownTheme(), {
1186
1299
  color: (value: string) => theme.fg("thinkingText", value),
1187
1300
  italic: true,
1188
1301
  });
1189
1302
  }
1190
1303
 
1304
+ /**
1305
+ * Whether `text` holds a ```svg fence this terminal draws as a figure, or a
1306
+ * table it draws a chart under. Native (TSP) terminals receive the fence
1307
+ * verbatim in the `md` node and draw it themselves, and get charts as image
1308
+ * nodes from {@link describe}; image-less terminals keep fences as code and
1309
+ * tables as tables.
1310
+ */
1311
+ #liftsFigures(text: string): boolean {
1312
+ return (
1313
+ this.#showImages &&
1314
+ TERMINAL.imageProtocol !== null &&
1315
+ !isNativeRendering() &&
1316
+ ((svgFigureRendering() && hasSvgFence(text)) || (this.#showTableCharts && hasChartTable(text)))
1317
+ );
1318
+ }
1319
+
1320
+ /** Re-describe once a smart table chart pick lands. */
1321
+ #awaitChart(pick: Promise<void>): void {
1322
+ if (this.#chartPicks.has(pick)) return;
1323
+ this.#chartPicks.add(pick);
1324
+ void pick.then(() => {
1325
+ this.#chartPicks.delete(pick);
1326
+ this.#blockVersion++;
1327
+ this.#onImageUpdate?.();
1328
+ });
1329
+ }
1330
+
1331
+ /**
1332
+ * The component for text block `index`: Markdown, or a {@link FigureMarkdown}
1333
+ * when it holds a ```svg fence to lift or a table to chart — taken over from
1334
+ * `previous` so its figures keep their rasters across rebuilds.
1335
+ */
1336
+ #proseBlock(index: number, text: string, previous: ReadonlyMap<number, FigureMarkdown>): ProseBlock {
1337
+ if (!this.#liftsFigures(text)) return this.#createMarkdown("text", text);
1338
+ let block = previous.get(index);
1339
+ if (block) {
1340
+ block.setText(text);
1341
+ } else {
1342
+ block = new FigureMarkdown(text, {
1343
+ markdown: value => this.#createMarkdown("text", value),
1344
+ charts: this.#showTableCharts,
1345
+ budget: this.#imageBudget,
1346
+ onChange: () => {
1347
+ this.#blockVersion++;
1348
+ this.#onImageUpdate?.();
1349
+ },
1350
+ });
1351
+ }
1352
+ this.#figureBlocks.set(index, block);
1353
+ return block;
1354
+ }
1355
+
1191
1356
  #stableLedger(width: number): StableRowLedger {
1192
1357
  let ledger = this.#stableLedgers.get(width);
1193
1358
  if (ledger === undefined) {
@@ -1307,6 +1472,18 @@ export class AssistantMessageComponent extends Container {
1307
1472
  }
1308
1473
  }
1309
1474
 
1475
+ /** Toggle charts under numeric tables (the main session's transcript only, never a subagent's). */
1476
+ setTableChartsVisible(visible: boolean): void {
1477
+ if (this.#showTableCharts === visible) return;
1478
+ this.#showTableCharts = visible;
1479
+ // Figure blocks bake the switch in; rebuild them rather than reuse.
1480
+ for (const block of this.#figureBlocks.values()) block.dispose();
1481
+ this.#figureBlocks.clear();
1482
+ if (this.#lastMessage) {
1483
+ this.updateContent(this.#lastMessage, { transient: this.#lastUpdateTransient });
1484
+ }
1485
+ }
1486
+
1310
1487
  /** Toggle only images produced by tool results; assistant-native images remain governed by setImagesVisible. */
1311
1488
  setToolResultImagesVisible(visible: boolean): void {
1312
1489
  if (this.#showToolResultImages === visible) return;
@@ -1330,22 +1507,24 @@ export class AssistantMessageComponent extends Container {
1330
1507
  }
1331
1508
  }
1332
1509
 
1510
+ /** A displayable Kitty PNG for a non-PNG `image`: the shared cache, else this component's displayed copy. */
1511
+ #kittyConversion(image: ImageContent): ImageContent | undefined {
1512
+ const cached = cachedPngConversion(image);
1513
+ if (cached) return cached;
1514
+ const key = imagePayloadKey(image);
1515
+ return this.#kittyDisplayed.get(key) ?? this.#kittyPreviouslyDisplayed?.get(key);
1516
+ }
1517
+
1333
1518
  #convertImagesForKitty(entries: Array<{ image: ImageContent; key: string }>): void {
1334
1519
  if (TERMINAL.imageProtocol !== ImageProtocol.Kitty) return;
1335
1520
  for (const { image } of entries) {
1336
- if (image.mimeType === "image/png") continue;
1521
+ if (image.mimeType === "image/png" || this.#kittyConversion(image)) continue;
1337
1522
  const key = imagePayloadKey(image);
1338
- if (this.#kittyConverted.has(key)) continue;
1339
- const cached = cachedPngConversion(image);
1340
- if (cached) {
1341
- this.#kittyConverted.set(key, cached);
1342
- continue;
1343
- }
1344
1523
  if (this.#kittyConversionsAwaited.has(key)) continue;
1345
1524
  this.#kittyConversionsAwaited.add(key);
1346
1525
  convertImageToPngShared(image)
1347
- .then(converted => {
1348
- this.#kittyConverted.set(key, converted);
1526
+ .then(() => {
1527
+ this.#kittyConversionsAwaited.delete(key);
1349
1528
  if (this.#lastMessage) {
1350
1529
  this.updateContent(this.#lastMessage, { transient: this.#lastUpdateTransient });
1351
1530
  }
@@ -1363,17 +1542,23 @@ export class AssistantMessageComponent extends Container {
1363
1542
 
1364
1543
  if (withLeadingSpacer) this.#contentContainer.addChild(new Spacer(1));
1365
1544
  for (const { image, key } of entries) {
1366
- const displayImage =
1367
- TERMINAL.imageProtocol === ImageProtocol.Kitty && image.mimeType !== "image/png"
1368
- ? this.#kittyConverted.get(imagePayloadKey(image))
1369
- : image;
1545
+ let displayImage: ImageContent | undefined = image;
1546
+ if (TERMINAL.imageProtocol === ImageProtocol.Kitty && image.mimeType !== "image/png") {
1547
+ displayImage = this.#kittyConversion(image);
1548
+ if (displayImage) this.#kittyDisplayed.set(imagePayloadKey(image), displayImage);
1549
+ }
1370
1550
  if (TERMINAL.imageProtocol && displayImage) {
1371
1551
  this.#contentContainer.addChild(
1372
1552
  new Image(
1373
1553
  displayImage.data,
1374
1554
  displayImage.mimeType,
1375
1555
  { fallbackColor: (text: string) => theme.fg("toolOutput", text) },
1376
- { ...resolveImageOptions(), budget: this.#imageBudget, imageKey: key },
1556
+ {
1557
+ ...resolveImageOptions(),
1558
+ budget: this.#imageBudget,
1559
+ imageKey: key,
1560
+ requestRender: this.#onImageUpdate,
1561
+ },
1377
1562
  ),
1378
1563
  );
1379
1564
  continue;
@@ -1415,10 +1600,14 @@ export class AssistantMessageComponent extends Container {
1415
1600
  }
1416
1601
 
1417
1602
  #computeShapeKey(message: AssistantMessage): string {
1418
- const parts: string[] = [`htb:${this.#hideThinkingBlock ? 1 : 0}|pot:${this.#proseOnlyThinking ? 1 : 0}`];
1603
+ const parts: string[] = [
1604
+ `htb:${this.#hideThinkingBlock ? 1 : 0}|pot:${this.#proseOnlyThinking ? 1 : 0}|etb:${this.#expandThinkingBlocks ? 1 : 0}`,
1605
+ ];
1419
1606
  for (const content of message.content) {
1420
1607
  if (content.type === "text") {
1421
- parts.push(canonicalizeMessage(content.text) ? "T1" : "T0");
1608
+ parts.push(
1609
+ !canonicalizeMessage(content.text) ? "T0" : this.#liftsFigures(content.text.trim()) ? "TF" : "T1",
1610
+ );
1422
1611
  } else if (content.type === "thinking") {
1423
1612
  if (this.#hideThinkingBlock) {
1424
1613
  // Match the pulse's empty/nonempty transition without formatting hidden text.
@@ -1572,6 +1761,8 @@ export class AssistantMessageComponent extends Container {
1572
1761
 
1573
1762
  // Clear content container
1574
1763
  this.#contentContainer.clear();
1764
+ this.#kittyPreviouslyDisplayed = this.#kittyDisplayed;
1765
+ this.#kittyDisplayed = new Map();
1575
1766
  this.#thinkingExtensions.clear();
1576
1767
  this.#emergencyText = undefined;
1577
1768
  this.#thinkingDots = undefined;
@@ -1579,9 +1770,9 @@ export class AssistantMessageComponent extends Container {
1579
1770
 
1580
1771
  // Determine if we should capture Markdown instances for next fast path
1581
1772
  const shouldCapture = this.#canFastPath(message);
1582
- const captureItems:
1583
- | Array<{ md: Markdown; contentIndex: number; blockType: "text" | "thinking"; lastText: string }>
1584
- | undefined = shouldCapture ? [] : undefined;
1773
+ const captureItems: FastPathItem[] | undefined = shouldCapture ? [] : undefined;
1774
+ const previousFigures = this.#figureBlocks;
1775
+ this.#figureBlocks = new Map();
1585
1776
 
1586
1777
  const hasVisibleContent = message.content.some(
1587
1778
  c =>
@@ -1600,8 +1791,7 @@ export class AssistantMessageComponent extends Container {
1600
1791
  if (content.type === "text" && canonicalizeMessage(content.text)) {
1601
1792
  // Set paddingY=0 to avoid extra spacing before tool executions
1602
1793
  const trimmed = content.text.trim();
1603
- const mdOptions = this.#textColorTransform ? { color: this.#textColorTransform } : undefined;
1604
- const md = new Markdown(trimmed, 1, 0, this.#getProseTheme(), mdOptions, 0);
1794
+ const md = this.#proseBlock(i, trimmed, previousFigures);
1605
1795
  this.#contentContainer.addChild(md);
1606
1796
  this.#emergencyText = md;
1607
1797
  captureItems?.push({ md, contentIndex: i, blockType: "text", lastText: trimmed });
@@ -1626,10 +1816,7 @@ export class AssistantMessageComponent extends Container {
1626
1816
  );
1627
1817
 
1628
1818
  // Thinking traces in thinkingText color, italic
1629
- const md = new Markdown(thinkingText, 1, 0, getMarkdownTheme(), {
1630
- color: (text: string) => theme.fg("thinkingText", text),
1631
- italic: true,
1632
- });
1819
+ const md = this.#createMarkdown("thinking", thinkingText);
1633
1820
  md.transientRenderCache = this.#lastUpdateTransient;
1634
1821
  this.#contentContainer.addChild(md);
1635
1822
  captureItems?.push({ md, contentIndex: i, blockType: "thinking", lastText: thinkingText });
@@ -1644,6 +1831,9 @@ export class AssistantMessageComponent extends Container {
1644
1831
  hasRenderedContent ||= this.#showImages;
1645
1832
  }
1646
1833
  }
1834
+ for (const [index, block] of previousFigures) {
1835
+ if (this.#figureBlocks.get(index) !== block) block.dispose();
1836
+ }
1647
1837
 
1648
1838
  if (this.#shouldAnimateThinking(message)) {
1649
1839
  if (hasVisibleContent) this.#contentContainer.addChild(new Spacer(1));
@@ -1655,6 +1845,7 @@ export class AssistantMessageComponent extends Container {
1655
1845
  }
1656
1846
 
1657
1847
  this.#renderToolImages();
1848
+ this.#kittyPreviouslyDisplayed = undefined;
1658
1849
  const errorPresentation = resolveAssistantErrorPresentation(message);
1659
1850
  const hasToolCalls = message.content.some(c => c.type === "toolCall");
1660
1851
  if (errorPresentation.kind === "compact-recovered") {
@@ -25,8 +25,11 @@ export interface ChatTranscriptBuilderDeps {
25
25
  cwd: string;
26
26
  hideThinkingBlock?: () => boolean;
27
27
  proseOnlyThinking?: () => boolean;
28
+ expandThinkingBlocks?: () => boolean;
28
29
  /** Session-scoped resolved destinations for model-authored Markdown links. */
29
30
  linkTargets?: ReadonlyMap<string, string>;
31
+ /** Draw charts under numeric tables; false for transcripts that are not the main session's. Default: true. */
32
+ tableCharts?: boolean;
30
33
  requestRender: () => void;
31
34
  }
32
35
  export declare class ChatTranscriptBuilder {