@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
@@ -53,13 +53,19 @@ interface AstEditRenderArgs {
53
53
  const COLLAPSED_CHANGE_LIMIT = PREVIEW_LIMITS.COLLAPSED_LINES * 2;
54
54
 
55
55
  /**
56
- * Flatten pre-styled change groups into frame body lines. Groups are separated
57
- * by a blank line and carry no tree guides — the frame border is the container,
58
- * so nested `├─ │` gutters would just be noise. Collapsed mode always shows at
59
- * least the first group, then fills up to `budget` lines before summarizing the
60
- * rest as `… N more changes`.
56
+ * Flatten change groups (line indices, styled on demand) into frame body
57
+ * lines. Groups are separated by a blank line and carry no tree guides — the
58
+ * frame border is the container, so nested `├─ │` gutters would just be
59
+ * noise. Collapsed mode always shows at least the first group, then fills up
60
+ * to `budget` lines before summarizing the rest as `… N more changes`.
61
61
  */
62
- function buildChangeBody(groups: string[][], expanded: boolean, budget: number, theme: Theme): string[] {
62
+ function buildChangeBody(
63
+ groups: readonly (readonly number[])[],
64
+ styleLine: (index: number) => string,
65
+ expanded: boolean,
66
+ budget: number,
67
+ theme: Theme,
68
+ ): string[] {
63
69
  const lines: string[] = [];
64
70
  let shown = 0;
65
71
  for (let i = 0; i < groups.length; i++) {
@@ -70,7 +76,7 @@ function buildChangeBody(groups: string[][], expanded: boolean, budget: number,
70
76
  // Always emit the first group; budget only gates subsequent ones.
71
77
  if (!expanded && shown > 0 && lines.length + separator + group.length + reserved > budget) break;
72
78
  if (separator) lines.push("");
73
- lines.push(...group);
79
+ for (const index of group) lines.push(styleLine(index));
74
80
  shown++;
75
81
  }
76
82
  const remaining = groups.length - shown;
@@ -239,29 +245,36 @@ export const astEditToolRenderer = {
239
245
  // Resolve hyperlinks over the whole output so nested directory headers
240
246
  // reconstruct across the blank-line groups the tree list collapses by.
241
247
  const contexts = classifyGroupedLines(allLines, details?.cwd ?? details?.searchPath, details?.searchPath);
242
- const styledLines = allLines.map((line, index) => {
248
+ // Style lazily: collapsed bodies show only the first groups.
249
+ const styledLines: (string | undefined)[] = Array.from({ length: allLines.length }, () => undefined);
250
+ const styleLine = (index: number): string => {
251
+ const cached = styledLines[index];
252
+ if (cached !== undefined) return cached;
243
253
  const ctx = contexts[index]!;
244
254
  // Swap the inner code-frame gutter `│` for a space so it does not nest a
245
255
  // second vertical bar inside the frame border.
246
- const display = replaceTabs(line.replace("│", " "));
256
+ const display = replaceTabs(allLines[index]!.replace("│", " "));
257
+ let styled: string;
247
258
  if (ctx.kind === "dir") {
248
- const styled = uiTheme.fg("accent", display);
249
- return ctx.headerPath ? fileHyperlink(ctx.headerPath, styled) : styled;
259
+ const accent = uiTheme.fg("accent", display);
260
+ styled = ctx.headerPath ? fileHyperlink(ctx.headerPath, accent) : accent;
261
+ } else if (ctx.kind === "file") {
262
+ const tinted = uiTheme.fg(ctx.depth === 1 ? "accent" : "dim", display);
263
+ styled = ctx.headerPath ? fileHyperlink(ctx.headerPath, tinted) : tinted;
264
+ } else if (display.startsWith("+")) {
265
+ styled = uiTheme.fg("toolDiffAdded", display);
266
+ } else if (display.startsWith("-")) {
267
+ styled = uiTheme.fg("toolDiffRemoved", display);
268
+ } else {
269
+ styled = uiTheme.fg("toolOutput", display);
250
270
  }
251
- if (ctx.kind === "file") {
252
- const styled = uiTheme.fg(ctx.depth === 1 ? "accent" : "dim", display);
253
- return ctx.headerPath ? fileHyperlink(ctx.headerPath, styled) : styled;
254
- }
255
- if (display.startsWith("+")) return uiTheme.fg("toolDiffAdded", display);
256
- if (display.startsWith("-")) return uiTheme.fg("toolDiffRemoved", display);
257
- return uiTheme.fg("toolOutput", display);
271
+ styledLines[index] = styled;
272
+ return styled;
273
+ };
274
+ const changeGroups = groupLineIndicesByBlank(allLines).filter(indices => {
275
+ const first = allLines[indices[0]!]!;
276
+ return !first.startsWith("Safety cap reached") && !first.startsWith("Parse issues:");
258
277
  });
259
- const changeGroups = groupLineIndicesByBlank(allLines)
260
- .filter(indices => {
261
- const first = allLines[indices[0]!]!;
262
- return !first.startsWith("Safety cap reached") && !first.startsWith("Parse issues:");
263
- })
264
- .map(indices => indices.map(index => styledLines[index]!));
265
278
 
266
279
  const badge = { label: "proposed", color: "warning" as const };
267
280
  const header = renderStatusLine(
@@ -278,17 +291,28 @@ export const astEditToolRenderer = {
278
291
  uiTheme.fg("warning", formatParseErrorsCountLabel(details.parseErrors, details.parseErrorsTotal)),
279
292
  );
280
293
  }
281
- return framedToolCard(uiTheme, ({ contentWidth }) => {
282
- const changeLines = buildChangeBody(changeGroups, Boolean(options.expanded), COLLAPSED_CHANGE_LIMIT, uiTheme);
283
- const bodyLines = [...changeLines, ...extraLines].map(l => truncateToWidth(l, contentWidth, Ellipsis.Omit));
284
- while (bodyLines.length > 0 && bodyLines[0].trim() === "") bodyLines.shift();
285
- return {
286
- header,
287
- sections: bodyLines.length > 0 ? [{ content: bodyLines }] : [],
288
- phase: options.isPartial ? "partial" : "success",
289
- borderColor: "borderMuted",
290
- };
291
- });
294
+ // The body is spinner-invariant: rebuild it only on expansion/width change.
295
+ let bodyMemo: { expanded: boolean; width: number; lines: readonly string[] } | undefined;
296
+ return framedToolCard(
297
+ uiTheme,
298
+ ({ contentWidth }) => {
299
+ const expanded = Boolean(options.expanded);
300
+ if (bodyMemo === undefined || bodyMemo.expanded !== expanded || bodyMemo.width !== contentWidth) {
301
+ const changeLines = buildChangeBody(changeGroups, styleLine, expanded, COLLAPSED_CHANGE_LIMIT, uiTheme);
302
+ const lines = [...changeLines, ...extraLines].map(l => truncateToWidth(l, contentWidth, Ellipsis.Omit));
303
+ while (lines.length > 0 && lines[0].trim() === "") lines.shift();
304
+ bodyMemo = { expanded, width: contentWidth, lines };
305
+ }
306
+ const bodyLines = bodyMemo.lines;
307
+ return {
308
+ header,
309
+ sections: bodyLines.length > 0 ? [{ content: bodyLines }] : [],
310
+ phase: options.isPartial ? "partial" : "success",
311
+ borderColor: "borderMuted",
312
+ };
313
+ },
314
+ { onInvalidate: () => (bodyMemo = undefined) },
315
+ );
292
316
  },
293
317
  describeCall(args: AstEditRenderArgs): NativeToolView {
294
318
  return {
@@ -136,25 +136,41 @@ export const astGrepToolRenderer = {
136
136
  // Resolve hyperlinks over the whole output so nested directory headers
137
137
  // reconstruct across the blank-line groups the tree list collapses by.
138
138
  const contexts = classifyGroupedLines(allLines, details?.cwd ?? details?.searchPath, details?.searchPath);
139
- const styledLines = allLines.map((line, index) => {
139
+ // Style lazily: the collapsed tree list only renders the first few groups,
140
+ // so a large result never styles/hyperlinks rows nobody sees.
141
+ const styledLines: (string | undefined)[] = Array.from({ length: allLines.length }, () => undefined);
142
+ const styleLine = (index: number): string => {
143
+ const cached = styledLines[index];
144
+ if (cached !== undefined) return cached;
145
+ const line = allLines[index]!;
140
146
  const ctx = contexts[index]!;
147
+ let styled: string;
141
148
  if (ctx.kind === "dir") {
142
- const styled = uiTheme.fg("accent", line);
143
- return ctx.headerPath ? fileHyperlink(ctx.headerPath, styled) : styled;
149
+ const accent = uiTheme.fg("accent", line);
150
+ styled = ctx.headerPath ? fileHyperlink(ctx.headerPath, accent) : accent;
151
+ } else if (ctx.kind === "file") {
152
+ const tinted = uiTheme.fg(ctx.depth === 1 ? "accent" : "dim", line);
153
+ styled = ctx.headerPath ? fileHyperlink(ctx.headerPath, tinted) : tinted;
154
+ } else {
155
+ styled = uiTheme.fg(line.startsWith(" meta:") ? "dim" : "toolOutput", line);
144
156
  }
145
- if (ctx.kind === "file") {
146
- const styled = uiTheme.fg(ctx.depth === 1 ? "accent" : "dim", line);
147
- return ctx.headerPath ? fileHyperlink(ctx.headerPath, styled) : styled;
148
- }
149
- if (line.startsWith(" meta:")) return uiTheme.fg("dim", line);
150
- return uiTheme.fg("toolOutput", line);
157
+ styledLines[index] = styled;
158
+ return styled;
159
+ };
160
+ const matchGroups = groupLineIndicesByBlank(allLines).filter(indices => {
161
+ const first = allLines[indices[0]!]!;
162
+ return !first.startsWith("Result limit reached") && !first.startsWith("Parse issues:");
151
163
  });
152
- const matchGroups = groupLineIndicesByBlank(allLines)
153
- .filter(indices => {
154
- const first = allLines[indices[0]!]!;
155
- return !first.startsWith("Result limit reached") && !first.startsWith("Parse issues:");
156
- })
157
- .map(indices => indices.map(index => styledLines[index]!));
164
+ // Line offset at which each group starts. A collapsed tree list stops at the
165
+ // first group that overflows its line budget, so groups starting at or past
166
+ // the budget are never shown — hand it same-length placeholders instead of
167
+ // styling them (renderTreeList only needs their line counts).
168
+ const groupStarts: number[] = [];
169
+ let groupStart = 0;
170
+ for (const group of matchGroups) {
171
+ groupStarts.push(groupStart);
172
+ groupStart += group.length;
173
+ }
158
174
 
159
175
  const extraLines: string[] = [];
160
176
  if (limitReached) {
@@ -178,7 +194,10 @@ export const astGrepToolRenderer = {
178
194
  maxCollapsed: matchGroups.length,
179
195
  maxCollapsedLines: COLLAPSED_MATCH_LIMIT,
180
196
  itemType: "match",
181
- renderItem: group => group,
197
+ renderItem: (group, context) =>
198
+ !options.expanded && groupStarts[context.index]! >= COLLAPSED_MATCH_LIMIT
199
+ ? Array.from({ length: group.length }, () => "")
200
+ : group.map(styleLine),
182
201
  },
183
202
  uiTheme,
184
203
  );
@@ -4,8 +4,13 @@ import { type OutputMeta } from "./output-meta";
4
4
  import type { NativeToolView, RenderResultContextOptions, RenderResultOptions } from "./renderer";
5
5
  /** Default collapsed shell output preview height. */
6
6
  export declare const BASH_DEFAULT_PREVIEW_LINES = 10;
7
- /** LLM-facing footer appended when a tool call becomes a background job. */
8
- export declare function formatBackgroundNotice(jobId: string): string;
7
+ /**
8
+ * LLM-facing footer appended when a tool call becomes a background job. It states the job's kill
9
+ * deadline (`timeoutSec`, `undefined` when disabled) so the model knows the job will die at it before
10
+ * it waits on the result. The deadline counts the job's whole run time, not time left from now: an
11
+ * auto-backgrounded call has already spent its foreground wait.
12
+ */
13
+ export declare function formatBackgroundNotice(jobId: string, timeoutSec: number | undefined): string;
9
14
  /** Shell execution metadata used by transcript rendering. */
10
15
  export interface BashToolDetails {
11
16
  meta?: OutputMeta;
package/src/tools/bash.ts CHANGED
@@ -25,16 +25,34 @@ import type {
25
25
  RenderResultOptions,
26
26
  ToolRenderer,
27
27
  } from "./renderer";
28
- import { ansi, compact, keyed } from "../native/describe";
29
- import type { NativeChild } from "../native/node";
30
- import { footnoteText, resultText } from "./native-view";
28
+ import { INTENT_FIELD, type TspTone } from "@linxiraos/pi-wire";
29
+ import { plainText } from "../native/spans";
30
+ import { resultText, runBox, runFoot, runInput, runOutput, type RunState } from "./native-view";
31
31
 
32
32
  /** Default collapsed shell output preview height. */
33
33
  export const BASH_DEFAULT_PREVIEW_LINES = DEFAULT_TERMINAL_PREVIEW_LINES;
34
34
 
35
- /** LLM-facing footer appended when a tool call becomes a background job. */
36
- export function formatBackgroundNotice(jobId: string): string {
37
- return `Backgrounded as job ${jobId}; its output is injected into the conversation as a follow-up the moment it finishes. Do NOT poll for it (no \`sleep\`, \`ps\`, \`pgrep\`, \`top\`, \`pidwait\`, log tailing): every poll is a wasted turn. Do other work, or end your reply and wait to be woken.`;
35
+ /**
36
+ * LLM-facing footer appended when a tool call becomes a background job. It states the job's kill
37
+ * deadline (`timeoutSec`, `undefined` when disabled) so the model knows the job will die at it before
38
+ * it waits on the result. The deadline counts the job's whole run time, not time left from now: an
39
+ * auto-backgrounded call has already spent its foreground wait.
40
+ */
41
+ export function formatBackgroundNotice(jobId: string, timeoutSec: number | undefined): string {
42
+ const deadline =
43
+ timeoutSec === undefined
44
+ ? " (no deadline)"
45
+ : ` (killed once it has run ${timeoutSec}s in total; \`timeout: 0\` disables the deadline)`;
46
+ return `Backgrounded as job ${jobId}${deadline}; its output is injected into the conversation as a follow-up the moment it finishes. Do NOT poll for it (no \`sleep\`, \`ps\`, \`pgrep\`, \`top\`, \`pidwait\`, log tailing): every poll is a wasted turn. Do other work, or end your reply and wait to be woken.`;
47
+ }
48
+
49
+ /**
50
+ * Whether `line` is `formatBackgroundNotice(jobId, …)` for any deadline, including the deadline-less
51
+ * `Backgrounded as job <id>; …` form persisted in older transcripts.
52
+ */
53
+ function isBackgroundNotice(line: string, jobId: string): boolean {
54
+ const prefix = `Backgrounded as job ${jobId}`;
55
+ return line.startsWith(prefix) && (line[prefix.length] === ";" || line[prefix.length] === " ");
38
56
  }
39
57
 
40
58
  /** Shell execution metadata used by transcript rendering. */
@@ -289,9 +307,10 @@ function stripBashNotices(
289
307
  rawOutput: string,
290
308
  details: BashToolDetails | undefined,
291
309
  ): { text: string; artifactId?: string } {
310
+ const job = details?.async;
292
311
  const withoutBackground =
293
- details?.async?.state === "running"
294
- ? stripTrailingNotice(rawOutput, formatBackgroundNotice(details.async.jobId))
312
+ job?.state === "running"
313
+ ? stripTrailingNotice(rawOutput, line => isBackgroundNotice(line, job.jobId))
295
314
  : rawOutput;
296
315
  const strippedOutput = stripOutputNotice(withoutBackground, details?.meta);
297
316
  const withoutExit =
@@ -348,34 +367,41 @@ function bashStatsParts(
348
367
  }
349
368
 
350
369
  /**
351
- * Native head facts of a shell call: title, the command once (`cd`/env
352
- * prefix included), a `background` badge for a backgrounded job, the
353
- * `exit N` chip and a `timed out` note only when the deadline hit.
370
+ * Native head facts of a shell call: title, the model's intent as the target
371
+ * (none without one), the command line for Copy command, the `exit N` chip
372
+ * and a `timed out` note only when the deadline hit. The command itself and a
373
+ * backgrounded job's state draw in the run box.
354
374
  */
355
375
  function shellToolHead(
356
376
  title: string,
357
377
  command: string,
378
+ intent: string | undefined,
358
379
  details: BashToolDetails | undefined,
359
380
  settled: boolean,
360
381
  ): NativeToolHead {
361
382
  const exit = settled && details?.exitCode !== undefined && details.exitCode !== 0 ? details.exitCode : undefined;
362
383
  return {
363
384
  title,
364
- target: command,
365
- targetKind: "command",
366
- lang: "bash",
367
- badges:
368
- details?.async?.state === "running"
369
- ? [{ text: "background", title: `Backgrounded as job ${details.async.jobId}` }]
370
- : undefined,
385
+ target: intent,
386
+ targetKind: intent ? "text" : undefined,
387
+ command,
371
388
  exit,
372
389
  note: settled && details?.timedOut === true ? "timed out" : undefined,
373
390
  };
374
391
  }
375
392
 
376
- /** The quiet final line's facts under shell output: service state and the full-output artifact. */
393
+ /** The model's intent line (`i` arg) as plain head text, or undefined when absent. */
394
+ function callIntent(args: unknown): string | undefined {
395
+ if (!args || typeof args !== "object" || !(INTENT_FIELD in args)) return undefined;
396
+ const value = args[INTENT_FIELD];
397
+ if (typeof value !== "string") return undefined;
398
+ return plainText(value).trim() || undefined;
399
+ }
400
+
401
+ /** The run foot's facts under shell output: background job, service state and the full-output artifact. */
377
402
  function shellFootParts(details: BashToolDetails | undefined, artifactId: string | undefined): string[] {
378
403
  const parts: string[] = [];
404
+ if (details?.async?.state === "running") parts.push(`Job ${details.async.jobId}`);
379
405
  const service = details?.service;
380
406
  if (service) {
381
407
  parts.push(`Service ${service.name}`, service.state);
@@ -386,6 +412,27 @@ function shellFootParts(details: BashToolDetails | undefined, artifactId: string
386
412
  return parts;
387
413
  }
388
414
 
415
+ /** The command line as a run's input: wrapped shell source. */
416
+ const SHELL_INPUT = { role: "zeta.tool.bash.command", lang: "bash", wrap: true } as const;
417
+
418
+ /** Box tint per run state: failed runs read as errors, timed-out ones as warnings. */
419
+ const RUN_BOX_TONE: Partial<Record<RunState, TspTone>> = { failed: "error", "timed-out": "warning" };
420
+
421
+ /** The foot state of a shell call from its result. */
422
+ function shellRunState(
423
+ details: BashToolDetails | undefined,
424
+ isError: boolean,
425
+ isPartial: boolean,
426
+ cancelled: boolean,
427
+ ): RunState {
428
+ if (cancelled) return "cancelled";
429
+ if (isPartial) return "running";
430
+ if (details?.timedOut === true) return "timed-out";
431
+ if (details?.async?.state === "running") return "background";
432
+ if (isError || (details?.exitCode !== undefined && details.exitCode !== 0)) return "failed";
433
+ return "done";
434
+ }
435
+
389
436
  function toBashRenderArgs<TArgs>(args: TArgs | undefined, config: ShellRendererConfig<TArgs>): BashRenderArgs {
390
437
  return {
391
438
  command: config.resolveCommand?.(args),
@@ -475,6 +522,15 @@ export function createShellRenderer<TArgs>(config: ShellRendererConfig<TArgs>) {
475
522
  let cachedIsPartial: boolean | undefined;
476
523
  let cachedPreviewWindow: number | undefined;
477
524
  let cachedSnapshot: ToolCardSnapshot | undefined;
525
+ const dropSnapshot = () => {
526
+ cachedSnapshot = undefined;
527
+ cachedWidth = undefined;
528
+ cachedPreviewLines = undefined;
529
+ cachedExpanded = undefined;
530
+ cachedRawOutput = undefined;
531
+ cachedIsPartial = undefined;
532
+ cachedPreviewWindow = undefined;
533
+ };
478
534
 
479
535
  return framedToolCard(
480
536
  uiTheme,
@@ -577,25 +633,26 @@ export function createShellRenderer<TArgs>(config: ShellRendererConfig<TArgs>) {
577
633
  cachedSnapshot = snapshot;
578
634
  return snapshot;
579
635
  },
580
- {
581
- onInvalidate: () => {
582
- cachedSnapshot = undefined;
583
- cachedWidth = undefined;
584
- cachedPreviewLines = undefined;
585
- cachedExpanded = undefined;
586
- cachedRawOutput = undefined;
587
- cachedIsPartial = undefined;
588
- cachedPreviewWindow = undefined;
589
- },
590
- },
636
+ { onInvalidate: dropSnapshot, onReleaseRenderCaches: dropSnapshot },
591
637
  );
592
638
  },
593
639
  describeCall(args: TArgs, options: RenderResultOptions): NativeToolView {
594
640
  const command = formatBashCommandText(toBashRenderArgs(args, config));
595
- // The command shows once, in the head; the body is only the output.
641
+ // No foot until the command starts; once it runs (before any output) the clock shows.
642
+ const started = options.executionStarted === true;
596
643
  return {
597
- tool: shellToolHead(config.resolveTitle(args, options), command, undefined, false),
598
- preview: { tail: BASH_DEFAULT_PREVIEW_LINES },
644
+ tool: shellToolHead(config.resolveTitle(args, options), command, callIntent(args), undefined, false),
645
+ body: [
646
+ runBox(
647
+ [
648
+ runInput(command, SHELL_INPUT),
649
+ started &&
650
+ runFoot({ state: options.cancelled ? "cancelled" : "running", elapsedMs: options.elapsedMs }),
651
+ ],
652
+ { key: "run" },
653
+ ),
654
+ ],
655
+ preview: "children",
599
656
  };
600
657
  },
601
658
 
@@ -619,22 +676,34 @@ export function createShellRenderer<TArgs>(config: ShellRendererConfig<TArgs>) {
619
676
  const showingFullOutput = expanded && renderContext?.isFullOutput === true;
620
677
  const command = formatBashCommandText(toBashRenderArgs(args, config));
621
678
  const previewLines = renderContext?.previewLines ?? BASH_DEFAULT_PREVIEW_LINES;
622
- // Wall time is the head timer, the timeout a note only when hit, the exit a head chip:
623
- // what is left (service state, artifact, truncation) is one quiet final line.
679
+ const state = shellRunState(details, isError, isPartial, options.cancelled === true);
680
+ // Settled: the measured wall time wins. A background job outlives the call: its run time is unknown here.
681
+ const elapsedMs =
682
+ state === "background"
683
+ ? undefined
684
+ : isPartial
685
+ ? options.elapsedMs
686
+ : (details?.wallTimeMs ?? options.elapsedMs);
624
687
  const meta = details?.meta;
625
- const body: NativeChild[] = compact([
626
- output.trim().length > 0 &&
627
- keyed(ansi(output, { follow: isPartial, role: "zeta.tool.bash.output" }), "output"),
628
- footnoteText(shellFootParts(details, stripped.artifactId), {
629
- ...meta,
630
- truncation: showingFullOutput ? undefined : meta?.truncation,
631
- }),
632
- ]);
688
+ const box = runBox(
689
+ [
690
+ runInput(command, SHELL_INPUT),
691
+ output.trim().length > 0 && runOutput(output, { role: "zeta.tool.bash.output", previewLines }),
692
+ runFoot({
693
+ state,
694
+ exitCode: details?.exitCode,
695
+ elapsedMs,
696
+ facts: shellFootParts(details, stripped.artifactId),
697
+ meta: { ...meta, truncation: showingFullOutput ? undefined : meta?.truncation },
698
+ }),
699
+ ],
700
+ { key: "run", tone: RUN_BOX_TONE[state] },
701
+ );
633
702
  return {
634
- tool: shellToolHead(config.resolveTitle(args, options), command, details, !isPartial),
635
- body,
636
- tone: !isPartial && isError && details?.timedOut === true ? "warning" : undefined,
637
- preview: { tail: previewLines },
703
+ tool: shellToolHead(config.resolveTitle(args, options), command, callIntent(args), details, !isPartial),
704
+ body: [box],
705
+ tone: state === "timed-out" ? "warning" : undefined,
706
+ preview: "children",
638
707
  };
639
708
  },
640
709
  mergeCallAndResult: true,
@@ -1,4 +1,4 @@
1
- /** Launch-broker daemon types shared by services, `proc://`, and `omp ps`. */
1
+ /** Launch-broker daemon types shared by services, `proc://`, and `zetacode ps`. */
2
2
  /** Stable lifecycle states exposed by the launch broker. */
3
3
  export type DaemonState = "starting" | "running" | "ready" | "restarting" | "stopping" | "exited" | "failed";
4
4
  /** Restart behavior applied after an unexpected daemon exit. */
@@ -1,4 +1,4 @@
1
- /** Launch-broker daemon types shared by services, `proc://`, and `omp ps`. */
1
+ /** Launch-broker daemon types shared by services, `proc://`, and `zetacode ps`. */
2
2
 
3
3
  /** Stable lifecycle states exposed by the launch broker. */
4
4
  export type DaemonState = "starting" | "running" | "ready" | "restarting" | "stopping" | "exited" | "failed";
@@ -39,19 +39,8 @@ export interface DefaultToolRenderInput {
39
39
  options: RenderResultOptions;
40
40
  }
41
41
 
42
- /** Header/body assembly shared by the string and Component APIs. */
43
- interface DefaultToolSnapshot {
44
- status: StatusLineOptions;
45
- phase: ToolCardPhase;
46
- body: readonly string[];
47
- }
48
-
49
- /** Compute the single header/body assembly behind both default-tool entry points. */
50
- function buildDefaultToolSnapshot(
51
- input: DefaultToolRenderInput,
52
- uiTheme: Theme,
53
- contentWidth: number,
54
- ): DefaultToolSnapshot {
42
+ /** Spinner-dependent header state; cheap, recomputed every frame. */
43
+ function buildDefaultToolStatus(input: DefaultToolRenderInput): { status: StatusLineOptions; phase: ToolCardPhase } {
55
44
  const { options, result } = input;
56
45
  const status: StatusLineOptions = {
57
46
  icon: options.isPartial
@@ -76,7 +65,12 @@ function buildDefaultToolSnapshot(
76
65
  : result?.isError
77
66
  ? "error"
78
67
  : "success";
68
+ return { status, phase };
69
+ }
79
70
 
71
+ /** Args preview plus JSON-tree/raw output body; depends only on args, output, expansion and width. */
72
+ function buildDefaultToolBody(input: DefaultToolRenderInput, uiTheme: Theme, contentWidth: number): string[] {
73
+ const { options, result } = input;
80
74
  const body: string[] = [];
81
75
  const args = isRecord(input.args) ? input.args : undefined;
82
76
  if (!options.expanded && args && Object.keys(args).length > 0) {
@@ -149,7 +143,7 @@ function buildDefaultToolSnapshot(
149
143
  }
150
144
  }
151
145
 
152
- return { status, phase, body };
146
+ return body;
153
147
  }
154
148
 
155
149
  /** Format one generic tool call/result card at the available content width. */
@@ -158,8 +152,8 @@ export function formatDefaultToolExecution(
158
152
  contentWidth: number,
159
153
  uiTheme: Theme,
160
154
  ): string {
161
- const snapshot = buildDefaultToolSnapshot(input, uiTheme, contentWidth);
162
- return [renderStatusLine(snapshot.status, uiTheme), ...snapshot.body].join("\n");
155
+ const { status } = buildDefaultToolStatus(input);
156
+ return [renderStatusLine(status, uiTheme), ...buildDefaultToolBody(input, uiTheme, contentWidth)].join("\n");
163
157
  }
164
158
 
165
159
  /** Inline args summary budget in characters (a data cap, not a width). */
@@ -195,12 +189,34 @@ export function describeDefaultToolExecution(input: DefaultToolRenderInput): Nat
195
189
 
196
190
  /** Render the generic fallback as the state-tinted card used by direct custom tools. */
197
191
  export function renderDefaultToolExecution(input: DefaultToolRenderInput, uiTheme: Theme): Component {
192
+ // The body (JSON parse + tree walk or output styling) is spinner-invariant;
193
+ // rebuild it only when its inputs change instead of on every animated frame.
194
+ let memo:
195
+ | { args: unknown; output: string | undefined; expanded: boolean; contentWidth: number; body: readonly string[] }
196
+ | undefined;
198
197
  return plainToolCard(
199
198
  uiTheme,
200
199
  ({ contentWidth }) => {
201
- const snapshot = buildDefaultToolSnapshot(input, uiTheme, contentWidth);
202
- return { status: snapshot.status, phase: snapshot.phase, body: snapshot.body };
200
+ const { status, phase } = buildDefaultToolStatus(input);
201
+ const output = input.result?.output;
202
+ const expanded = input.options.expanded;
203
+ if (
204
+ memo === undefined ||
205
+ memo.args !== input.args ||
206
+ memo.output !== output ||
207
+ memo.expanded !== expanded ||
208
+ memo.contentWidth !== contentWidth
209
+ ) {
210
+ memo = {
211
+ args: input.args,
212
+ output,
213
+ expanded,
214
+ contentWidth,
215
+ body: buildDefaultToolBody(input, uiTheme, contentWidth),
216
+ };
217
+ }
218
+ return { status, phase, body: memo.body };
203
219
  },
204
- { paddingX: 1, paddingY: 1, ignoreTight: true },
220
+ { paddingX: 1, paddingY: 1, ignoreTight: true, onInvalidate: () => (memo = undefined) },
205
221
  );
206
222
  }