@oh-my-pi/pi-tui 18.2.4 → 18.2.6

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 (748) hide show
  1. package/CHANGELOG.md +23 -0
  2. package/README.md +43 -0
  3. package/dist/types/app-keybindings.d.ts +395 -0
  4. package/dist/types/apps/autoresearch-dashboard.d.ts +43 -0
  5. package/dist/types/apps/autoresearch-data.d.ts +15 -0
  6. package/dist/types/apps/cleanse-board.d.ts +66 -0
  7. package/dist/types/apps/cleanse-picker.d.ts +23 -0
  8. package/dist/types/apps/debug/log-formatting.d.ts +6 -0
  9. package/dist/types/apps/debug/log-viewer.d.ts +81 -0
  10. package/dist/types/apps/debug/protocol-probe.d.ts +42 -0
  11. package/dist/types/apps/debug/raw-sse-buffer.d.ts +58 -0
  12. package/dist/types/apps/debug/raw-sse.d.ts +26 -0
  13. package/dist/types/apps/debug/terminal-info.d.ts +34 -0
  14. package/dist/types/apps/debug/viewer-frame.d.ts +52 -0
  15. package/dist/types/apps/git/avatar.d.ts +9 -0
  16. package/dist/types/apps/git/colors.d.ts +49 -0
  17. package/dist/types/apps/git/diff-pane.d.ts +188 -0
  18. package/dist/types/apps/git/git-tui.d.ts +66 -0
  19. package/dist/types/apps/git/sidebar.d.ts +149 -0
  20. package/dist/types/apps/git/state.d.ts +117 -0
  21. package/dist/types/apps/if-bench-board.d.ts +75 -0
  22. package/dist/types/apps/live-visualizer.d.ts +33 -0
  23. package/dist/types/apps/ps-data.d.ts +51 -0
  24. package/dist/types/apps/ps-top.d.ts +33 -0
  25. package/dist/types/apps/session-picker.d.ts +26 -0
  26. package/dist/types/apps/setup-model-picker.d.ts +13 -0
  27. package/dist/types/apps/standalone-picker.d.ts +42 -0
  28. package/dist/types/autocomplete.d.ts +4 -0
  29. package/dist/types/chat/advisor-message.d.ts +11 -0
  30. package/dist/types/chat/assistant-message.d.ts +106 -0
  31. package/dist/types/chat/background-tan-message.d.ts +9 -0
  32. package/dist/types/chat/bash-execution.d.ts +47 -0
  33. package/dist/types/chat/cache-invalidation-marker.d.ts +50 -0
  34. package/dist/types/chat/chat-transcript-builder.d.ts +50 -0
  35. package/dist/types/chat/collab-prompt-message.d.ts +10 -0
  36. package/dist/types/chat/compaction-summary-message.d.ts +54 -0
  37. package/dist/types/chat/custom-message.d.ts +10 -0
  38. package/dist/types/chat/display-preferences.d.ts +13 -0
  39. package/dist/types/chat/eval-execution.d.ts +29 -0
  40. package/dist/types/chat/execution-shared.d.ts +51 -0
  41. package/dist/types/chat/extension-types.d.ts +28 -0
  42. package/dist/types/chat/hook-message.d.ts +10 -0
  43. package/dist/types/chat/image-loading.d.ts +46 -0
  44. package/dist/types/chat/late-diagnostics-message.d.ts +21 -0
  45. package/dist/types/chat/messages.d.ts +189 -0
  46. package/dist/types/chat/reaction.d.ts +30 -0
  47. package/dist/types/chat/read-target.d.ts +4 -0
  48. package/dist/types/chat/read-tool-group.d.ts +67 -0
  49. package/dist/types/chat/served-model-marker.d.ts +47 -0
  50. package/dist/types/chat/skill-message.d.ts +33 -0
  51. package/dist/types/chat/skill-title-input.d.ts +14 -0
  52. package/dist/types/chat/stripped-tool-calls-placeholder.d.ts +12 -0
  53. package/dist/types/chat/thinking-display.d.ts +12 -0
  54. package/dist/types/chat/todo-reminder.d.ts +13 -0
  55. package/dist/types/chat/tool-execution.d.ts +138 -0
  56. package/dist/types/chat/transcript-browser.d.ts +93 -0
  57. package/dist/types/chat/transcript-entry.d.ts +45 -0
  58. package/dist/types/chat/transcript-outline.d.ts +86 -0
  59. package/dist/types/chat/transcript-render-helpers.d.ts +92 -0
  60. package/dist/types/chat/ttsr-notification.d.ts +23 -0
  61. package/dist/types/chat/user-message.d.ts +52 -0
  62. package/dist/types/chrome/chat-block.d.ts +64 -0
  63. package/dist/types/chrome/collab-qrcode.d.ts +28 -0
  64. package/dist/types/chrome/context-thresholds.d.ts +10 -0
  65. package/dist/types/chrome/countdown-timer.d.ts +14 -0
  66. package/dist/types/chrome/diff.d.ts +14 -0
  67. package/dist/types/chrome/dynamic-border.d.ts +16 -0
  68. package/dist/types/chrome/error-block.d.ts +17 -0
  69. package/dist/types/chrome/form-theme.d.ts +3 -0
  70. package/dist/types/chrome/format.d.ts +12 -0
  71. package/dist/types/chrome/index.d.ts +20 -0
  72. package/dist/types/chrome/keybinding-hints.d.ts +40 -0
  73. package/dist/types/chrome/live-board.d.ts +23 -0
  74. package/dist/types/chrome/local-date.d.ts +4 -0
  75. package/dist/types/chrome/message-divider.d.ts +22 -0
  76. package/dist/types/chrome/message-frame.d.ts +52 -0
  77. package/dist/types/chrome/message-notice.d.ts +35 -0
  78. package/dist/types/chrome/overlay-box.d.ts +74 -0
  79. package/dist/types/chrome/qrcode.d.ts +48 -0
  80. package/dist/types/chrome/segment-track.d.ts +27 -0
  81. package/dist/types/chrome/select-list-mouse-routing.d.ts +4 -0
  82. package/dist/types/chrome/selector-helpers.d.ts +52 -0
  83. package/dist/types/chrome/shared.d.ts +5 -0
  84. package/dist/types/chrome/streaming-panel.d.ts +19 -0
  85. package/dist/types/chrome/tool-activity.d.ts +17 -0
  86. package/dist/types/chrome/transcript-container.d.ts +129 -0
  87. package/dist/types/chrome/transcript-status.d.ts +12 -0
  88. package/dist/types/chrome/visual-truncate.d.ts +19 -0
  89. package/dist/types/components/disclosure.d.ts +42 -0
  90. package/dist/types/components/form.d.ts +130 -0
  91. package/dist/types/components/key-value-list.d.ts +24 -0
  92. package/dist/types/components/layout/geometry.d.ts +73 -0
  93. package/dist/types/components/layout/row.d.ts +55 -0
  94. package/dist/types/components/layout/split-pane.d.ts +80 -0
  95. package/dist/types/components/layout/stack.d.ts +50 -0
  96. package/dist/types/components/menu-selection.d.ts +71 -0
  97. package/dist/types/components/metric.d.ts +44 -0
  98. package/dist/types/components/progress-bar.d.ts +36 -0
  99. package/dist/types/components/scroll-view.d.ts +53 -19
  100. package/dist/types/components/scroll-viewport.d.ts +46 -0
  101. package/dist/types/components/section.d.ts +19 -0
  102. package/dist/types/components/select-list.d.ts +46 -2
  103. package/dist/types/components/settings-list.d.ts +20 -0
  104. package/dist/types/components/table.d.ts +32 -0
  105. package/dist/types/components/tree-view.d.ts +130 -0
  106. package/dist/types/components/wizard-step.d.ts +60 -0
  107. package/dist/types/hotkeys-markdown.d.ts +7 -0
  108. package/dist/types/index.d.ts +17 -0
  109. package/dist/types/keybinding-matchers.d.ts +35 -0
  110. package/dist/types/keys.d.ts +2 -2
  111. package/dist/types/lang-from-path.d.ts +9 -0
  112. package/dist/types/overlays/advisor-config.d.ts +114 -0
  113. package/dist/types/overlays/agent-activity.d.ts +41 -0
  114. package/dist/types/overlays/agent-hub-projection.d.ts +33 -0
  115. package/dist/types/overlays/agent-hub-renderer.d.ts +53 -0
  116. package/dist/types/overlays/agent-hub-types.d.ts +99 -0
  117. package/dist/types/overlays/agent-hub.d.ts +126 -0
  118. package/dist/types/overlays/agent-transcript-viewer.d.ts +71 -0
  119. package/dist/types/overlays/agents-hub.d.ts +72 -0
  120. package/dist/types/overlays/ask-dialog.d.ts +88 -0
  121. package/dist/types/overlays/bordered-loader.d.ts +11 -0
  122. package/dist/types/overlays/btw-history-panel.d.ts +29 -0
  123. package/dist/types/overlays/btw-history.d.ts +17 -0
  124. package/dist/types/overlays/btw-panel.d.ts +26 -0
  125. package/dist/types/overlays/cleanse-panel.d.ts +36 -0
  126. package/dist/types/overlays/codex-reset-fireworks.d.ts +52 -0
  127. package/dist/types/overlays/composer-shape-preview.d.ts +47 -0
  128. package/dist/types/overlays/composer-shape-registry.d.ts +50 -0
  129. package/dist/types/overlays/copy-selector.d.ts +48 -0
  130. package/dist/types/overlays/copy-targets.d.ts +63 -0
  131. package/dist/types/overlays/error-banner.d.ts +13 -0
  132. package/dist/types/overlays/extensions/display-text.d.ts +10 -0
  133. package/dist/types/overlays/extensions/extension-dashboard.d.ts +63 -0
  134. package/dist/types/overlays/extensions/extension-list.d.ts +54 -0
  135. package/dist/types/overlays/extensions/inspector-model.d.ts +109 -0
  136. package/dist/types/overlays/extensions/inspector-panel.d.ts +17 -0
  137. package/dist/types/overlays/extensions/live-tool-session.d.ts +28 -0
  138. package/dist/types/overlays/extensions/mcp-runtime.d.ts +107 -0
  139. package/dist/types/overlays/extensions/state-manager.d.ts +42 -0
  140. package/dist/types/overlays/extensions/types.d.ts +165 -0
  141. package/dist/types/overlays/history-search.d.ts +16 -0
  142. package/dist/types/overlays/hook-editor.d.ts +44 -0
  143. package/dist/types/overlays/hook-input.d.ts +20 -0
  144. package/dist/types/overlays/hook-selector.d.ts +71 -0
  145. package/dist/types/overlays/hub-frame.d.ts +77 -0
  146. package/dist/types/overlays/login-dialog.d.ts +43 -0
  147. package/dist/types/overlays/logout-account-selector.d.ts +15 -0
  148. package/dist/types/overlays/mcp-add-wizard.d.ts +87 -0
  149. package/dist/types/overlays/model-browser.d.ts +213 -0
  150. package/dist/types/overlays/model-hub.d.ts +72 -0
  151. package/dist/types/overlays/model-picker.d.ts +74 -0
  152. package/dist/types/overlays/model-selector.d.ts +52 -0
  153. package/dist/types/overlays/move-overlay.d.ts +39 -0
  154. package/dist/types/overlays/oauth-selector.d.ts +41 -0
  155. package/dist/types/overlays/omfg-panel.d.ts +20 -0
  156. package/dist/types/overlays/pause-screen.d.ts +43 -0
  157. package/dist/types/overlays/plan-review-overlay.d.ts +89 -0
  158. package/dist/types/overlays/plan-save-overlay.d.ts +19 -0
  159. package/dist/types/overlays/plan-toc.d.ts +41 -0
  160. package/dist/types/overlays/plugin-selector.d.ts +28 -0
  161. package/dist/types/overlays/plugin-settings.d.ts +180 -0
  162. package/dist/types/overlays/queue-mode-selector.d.ts +11 -0
  163. package/dist/types/overlays/reset-usage-selector.d.ts +23 -0
  164. package/dist/types/overlays/rewind-selector.d.ts +61 -0
  165. package/dist/types/overlays/running-subagent-badge.d.ts +6 -0
  166. package/dist/types/overlays/session-account-selector.d.ts +24 -0
  167. package/dist/types/overlays/session-info-overlay.d.ts +17 -0
  168. package/dist/types/overlays/session-observer-registry.d.ts +77 -0
  169. package/dist/types/overlays/session-selector.d.ts +131 -0
  170. package/dist/types/overlays/settings-defs.d.ts +119 -0
  171. package/dist/types/overlays/settings-selector.d.ts +73 -0
  172. package/dist/types/overlays/show-images-selector.d.ts +11 -0
  173. package/dist/types/overlays/snapcompact-shape-preview.d.ts +31 -0
  174. package/dist/types/overlays/theme-selector.d.ts +12 -0
  175. package/dist/types/overlays/thinking-selector.d.ts +12 -0
  176. package/dist/types/overlays/tiny-title-download-progress.d.ts +21 -0
  177. package/dist/types/overlays/tree-selector.d.ts +92 -0
  178. package/dist/types/overlays/usage-dashboard.d.ts +94 -0
  179. package/dist/types/overlays/usage-display.d.ts +5 -0
  180. package/dist/types/overlays/usage-row.d.ts +17 -0
  181. package/dist/types/prompt/attachment-chips.d.ts +19 -0
  182. package/dist/types/prompt/composer-attachments.d.ts +71 -0
  183. package/dist/types/prompt/composer-cache.d.ts +37 -0
  184. package/dist/types/prompt/composer.d.ts +159 -0
  185. package/dist/types/prompt/custom-editor.d.ts +261 -0
  186. package/dist/types/prompt/editor-top-gap.d.ts +18 -0
  187. package/dist/types/prompt/emoji-autocomplete.d.ts +16 -0
  188. package/dist/types/prompt/github-ref-autocomplete.d.ts +35 -0
  189. package/dist/types/prompt/gradient-highlight.d.ts +32 -0
  190. package/dist/types/prompt/image-format.d.ts +4 -0
  191. package/dist/types/prompt/image-references.d.ts +26 -0
  192. package/dist/types/prompt/interactive-context-helpers.d.ts +43 -0
  193. package/dist/types/prompt/internal-url-autocomplete.d.ts +56 -0
  194. package/dist/types/prompt/macos-spelling.d.ts +44 -0
  195. package/dist/types/prompt/magic-keyword-boundary.d.ts +9 -0
  196. package/dist/types/prompt/magic-keywords.d.ts +26 -0
  197. package/dist/types/prompt/markdown-prose.d.ts +15 -0
  198. package/dist/types/prompt/model-mention-autocomplete.d.ts +25 -0
  199. package/dist/types/prompt/model-mention-syntax.d.ts +24 -0
  200. package/dist/types/prompt/orchestrate.d.ts +13 -0
  201. package/dist/types/prompt/prompt-action-autocomplete.d.ts +56 -0
  202. package/dist/types/prompt/queue-input.d.ts +8 -0
  203. package/dist/types/prompt/skill-tokens.d.ts +13 -0
  204. package/dist/types/prompt/tools-markdown.d.ts +12 -0
  205. package/dist/types/prompt/ultrathink.d.ts +13 -0
  206. package/dist/types/prompt/usage-amounts.d.ts +26 -0
  207. package/dist/types/prompt/video.d.ts +18 -0
  208. package/dist/types/prompt/welcome.d.ts +83 -0
  209. package/dist/types/prompt/workflow.d.ts +13 -0
  210. package/dist/types/render/code-cell.d.ts +51 -0
  211. package/dist/types/render/file-list.d.ts +22 -0
  212. package/dist/types/render/hyperlink.d.ts +80 -0
  213. package/dist/types/render/index.d.ts +16 -0
  214. package/dist/types/render/output-block.d.ts +54 -0
  215. package/dist/types/render/output-pane.d.ts +80 -0
  216. package/dist/types/render/render-utils.d.ts +312 -0
  217. package/dist/types/render/sixel.d.ts +21 -0
  218. package/dist/types/render/status-line.d.ts +23 -0
  219. package/dist/types/render/tool-card.d.ts +71 -0
  220. package/dist/types/render/tree-list.d.ts +29 -0
  221. package/dist/types/render/types.d.ts +17 -0
  222. package/dist/types/render/utils.d.ts +43 -0
  223. package/dist/types/render/width-aware-text.d.ts +23 -0
  224. package/dist/types/setup/lazy.d.ts +3 -0
  225. package/dist/types/setup/scenes/composer.d.ts +3 -0
  226. package/dist/types/setup/scenes/glyph.d.ts +3 -0
  227. package/dist/types/setup/scenes/model.d.ts +3 -0
  228. package/dist/types/setup/scenes/outro.d.ts +2 -0
  229. package/dist/types/setup/scenes/providers.d.ts +3 -0
  230. package/dist/types/setup/scenes/sign-in.d.ts +21 -0
  231. package/dist/types/setup/scenes/splash.d.ts +11 -0
  232. package/dist/types/setup/scenes/theme.d.ts +3 -0
  233. package/dist/types/setup/scenes/types.d.ts +110 -0
  234. package/dist/types/setup/scenes/web-search.d.ts +22 -0
  235. package/dist/types/setup/setup-version.d.ts +11 -0
  236. package/dist/types/setup/startup-splash.d.ts +9 -0
  237. package/dist/types/setup/wizard-overlay.d.ts +16 -0
  238. package/dist/types/setup/wizard.d.ts +25 -0
  239. package/dist/types/status-line/component.d.ts +182 -0
  240. package/dist/types/status-line/context-usage.d.ts +156 -0
  241. package/dist/types/status-line/footer.d.ts +30 -0
  242. package/dist/types/status-line/git-utils.d.ts +22 -0
  243. package/dist/types/status-line/host.d.ts +129 -0
  244. package/dist/types/status-line/index.d.ts +6 -0
  245. package/dist/types/status-line/loop.d.ts +18 -0
  246. package/dist/types/status-line/metrics.d.ts +21 -0
  247. package/dist/types/status-line/presets.d.ts +3 -0
  248. package/dist/types/status-line/schema.d.ts +15 -0
  249. package/dist/types/status-line/segments.d.ts +5 -0
  250. package/dist/types/status-line/separators.d.ts +3 -0
  251. package/dist/types/status-line/types.d.ts +236 -0
  252. package/dist/types/stdin-buffer.d.ts +4 -0
  253. package/dist/types/theme/color.d.ts +24 -0
  254. package/dist/types/theme/defaults/index.d.ts +9489 -0
  255. package/dist/types/theme/index.d.ts +4 -0
  256. package/dist/types/theme/loader.d.ts +23 -0
  257. package/dist/types/theme/mermaid-cache.d.ts +26 -0
  258. package/dist/types/theme/schema-validation.d.ts +3 -0
  259. package/dist/types/theme/schema.d.ts +25 -0
  260. package/dist/types/theme/session-color.d.ts +43 -0
  261. package/dist/types/theme/shimmer.d.ts +44 -0
  262. package/dist/types/theme/symbols.d.ts +34 -0
  263. package/dist/types/theme/theme-class.d.ts +299 -0
  264. package/dist/types/theme/theme.d.ts +111 -0
  265. package/dist/types/theme/tui-adapters.d.ts +30 -0
  266. package/dist/types/thinking.d.ts +165 -0
  267. package/dist/types/tools/agent-tree.d.ts +36 -0
  268. package/dist/types/tools/ask.d.ts +67 -0
  269. package/dist/types/tools/ast-edit.d.ts +53 -0
  270. package/dist/types/tools/ast-grep.d.ts +52 -0
  271. package/dist/types/tools/autoresearch.d.ts +156 -0
  272. package/dist/types/tools/bash-interactive.d.ts +28 -0
  273. package/dist/types/tools/bash.d.ts +103 -0
  274. package/dist/types/tools/conflict-detect.d.ts +43 -0
  275. package/dist/types/tools/debug.d.ts +60 -0
  276. package/dist/types/tools/default-renderer.d.ts +24 -0
  277. package/dist/types/tools/edit.d.ts +158 -0
  278. package/dist/types/tools/eval-format/code-mode-declarations.d.ts +5 -0
  279. package/dist/types/tools/eval-format/index.d.ts +5 -0
  280. package/dist/types/tools/eval-format/javascript.d.ts +2 -0
  281. package/dist/types/tools/eval-format/python.d.ts +2 -0
  282. package/dist/types/tools/eval.d.ts +94 -0
  283. package/dist/types/tools/fetch.d.ts +42 -0
  284. package/dist/types/tools/gh-format.d.ts +8 -0
  285. package/dist/types/tools/github.d.ts +144 -0
  286. package/dist/types/tools/glob.d.ts +44 -0
  287. package/dist/types/tools/goal.d.ts +42 -0
  288. package/dist/types/tools/grep.d.ts +64 -0
  289. package/dist/types/tools/grouped-file-output.d.ts +71 -0
  290. package/dist/types/tools/hashline-format.d.ts +28 -0
  291. package/dist/types/tools/hub.d.ts +340 -0
  292. package/dist/types/tools/index.d.ts +4 -0
  293. package/dist/types/tools/json-tree.d.ts +59 -0
  294. package/dist/types/tools/line-ranges.d.ts +29 -0
  295. package/dist/types/tools/list-limit.d.ts +15 -0
  296. package/dist/types/tools/lsp.d.ts +72 -0
  297. package/dist/types/tools/match-line-format.d.ts +11 -0
  298. package/dist/types/tools/mcp.d.ts +81 -0
  299. package/dist/types/tools/memory.d.ts +67 -0
  300. package/dist/types/tools/output-meta.d.ts +151 -0
  301. package/dist/types/tools/read.d.ts +139 -0
  302. package/dist/types/tools/renderer.d.ts +98 -0
  303. package/dist/types/tools/report-tool-issue.d.ts +8 -0
  304. package/dist/types/tools/resolve.d.ts +47 -0
  305. package/dist/types/tools/streaming-output.d.ts +282 -0
  306. package/dist/types/tools/subprocess.d.ts +24 -0
  307. package/dist/types/tools/task-repair-args.d.ts +53 -0
  308. package/dist/types/tools/task-yield-assembly.d.ts +20 -0
  309. package/dist/types/tools/task.d.ts +425 -0
  310. package/dist/types/tools/terminal-output.d.ts +8 -0
  311. package/dist/types/tools/think.d.ts +13 -0
  312. package/dist/types/tools/todo.d.ts +118 -0
  313. package/dist/types/tools/tool-errors.d.ts +7 -0
  314. package/dist/types/tools/vibe.d.ts +120 -0
  315. package/dist/types/tools/web-search.d.ts +195 -0
  316. package/dist/types/tools/write.d.ts +76 -0
  317. package/dist/types/tools/xd-url.d.ts +13 -0
  318. package/dist/types/tools/xdev.d.ts +52 -0
  319. package/dist/types/tui.d.ts +18 -0
  320. package/dist/types/utils.d.ts +2 -0
  321. package/package.json +37 -3
  322. package/src/app-keybindings.ts +760 -0
  323. package/src/apps/autoresearch-dashboard.ts +500 -0
  324. package/src/apps/autoresearch-data.ts +64 -0
  325. package/src/apps/cleanse-board.ts +341 -0
  326. package/src/apps/cleanse-picker.ts +57 -0
  327. package/src/apps/debug/log-formatting.ts +53 -0
  328. package/src/apps/debug/log-viewer.ts +868 -0
  329. package/src/apps/debug/protocol-probe.ts +267 -0
  330. package/src/apps/debug/raw-sse-buffer.ts +449 -0
  331. package/src/apps/debug/raw-sse.ts +282 -0
  332. package/src/apps/debug/terminal-info.ts +115 -0
  333. package/src/apps/debug/viewer-frame.ts +174 -0
  334. package/src/apps/git/avatar.ts +52 -0
  335. package/src/apps/git/colors.ts +109 -0
  336. package/src/apps/git/diff-pane.ts +1512 -0
  337. package/src/apps/git/git-tui.ts +889 -0
  338. package/src/apps/git/sidebar.ts +1204 -0
  339. package/src/apps/git/state.ts +113 -0
  340. package/src/apps/if-bench-board.ts +324 -0
  341. package/src/apps/live-visualizer.ts +238 -0
  342. package/src/apps/ps-data.ts +116 -0
  343. package/src/apps/ps-top.ts +422 -0
  344. package/src/apps/session-picker.ts +95 -0
  345. package/src/apps/setup-model-picker.ts +21 -0
  346. package/src/apps/standalone-picker.ts +111 -0
  347. package/src/autocomplete.ts +9 -13
  348. package/src/chat/advisor-message.ts +175 -0
  349. package/src/chat/assistant-message.ts +1214 -0
  350. package/src/chat/background-tan-message.ts +35 -0
  351. package/src/chat/bash-execution.ts +394 -0
  352. package/src/chat/cache-invalidation-marker.ts +94 -0
  353. package/src/chat/chat-transcript-builder.ts +593 -0
  354. package/src/chat/collab-prompt-message.ts +34 -0
  355. package/src/chat/compaction-summary-message.ts +261 -0
  356. package/src/chat/custom-message.ts +23 -0
  357. package/src/chat/display-preferences.ts +24 -0
  358. package/src/chat/eval-execution.ts +172 -0
  359. package/src/chat/execution-shared.ts +113 -0
  360. package/src/chat/extension-types.ts +46 -0
  361. package/src/chat/hook-message.ts +20 -0
  362. package/src/chat/image-loading.ts +122 -0
  363. package/src/chat/late-diagnostics-message.ts +92 -0
  364. package/src/chat/messages.ts +305 -0
  365. package/src/chat/reaction.ts +65 -0
  366. package/src/chat/read-target.ts +7 -0
  367. package/src/chat/read-tool-group.ts +873 -0
  368. package/src/chat/served-model-marker.ts +91 -0
  369. package/src/chat/skill-message.ts +220 -0
  370. package/src/chat/skill-title-input.ts +41 -0
  371. package/src/chat/stripped-tool-calls-placeholder.ts +35 -0
  372. package/src/chat/thinking-display.ts +312 -0
  373. package/src/chat/todo-reminder.ts +33 -0
  374. package/src/chat/tool-execution.ts +1395 -0
  375. package/src/chat/transcript-browser.ts +268 -0
  376. package/src/chat/transcript-entry.ts +96 -0
  377. package/src/chat/transcript-outline.ts +260 -0
  378. package/src/chat/transcript-render-helpers.ts +315 -0
  379. package/src/chat/ttsr-notification.ts +123 -0
  380. package/src/chat/user-message.ts +287 -0
  381. package/src/chrome/chat-block.ts +110 -0
  382. package/src/chrome/collab-qrcode.ts +74 -0
  383. package/src/chrome/context-thresholds.ts +85 -0
  384. package/src/chrome/countdown-timer.ts +74 -0
  385. package/src/chrome/diff.ts +266 -0
  386. package/src/chrome/dynamic-border.ts +36 -0
  387. package/src/chrome/error-block.ts +63 -0
  388. package/src/chrome/form-theme.ts +10 -0
  389. package/src/chrome/format.ts +66 -0
  390. package/src/chrome/index.ts +21 -0
  391. package/src/chrome/keybinding-hints.ts +55 -0
  392. package/src/chrome/live-board.ts +123 -0
  393. package/src/chrome/local-date.ts +20 -0
  394. package/src/chrome/message-divider.ts +51 -0
  395. package/src/chrome/message-frame.ts +164 -0
  396. package/src/chrome/message-notice.ts +103 -0
  397. package/src/chrome/overlay-box.ts +301 -0
  398. package/src/chrome/qrcode.ts +539 -0
  399. package/src/chrome/segment-track.ts +86 -0
  400. package/src/chrome/select-list-mouse-routing.ts +11 -0
  401. package/src/chrome/selector-helpers.ts +119 -0
  402. package/src/chrome/shared.ts +27 -0
  403. package/src/chrome/streaming-panel.ts +72 -0
  404. package/src/chrome/tool-activity.ts +44 -0
  405. package/src/chrome/transcript-container.ts +902 -0
  406. package/src/chrome/transcript-status.ts +27 -0
  407. package/src/chrome/visual-truncate.ts +70 -0
  408. package/src/components/disclosure.ts +235 -0
  409. package/src/components/editor.ts +10 -12
  410. package/src/components/form.ts +566 -0
  411. package/src/components/input.ts +6 -27
  412. package/src/components/key-value-list.ts +88 -0
  413. package/src/components/layout/geometry.ts +202 -0
  414. package/src/components/layout/row.ts +283 -0
  415. package/src/components/layout/split-pane.ts +349 -0
  416. package/src/components/layout/stack.ts +261 -0
  417. package/src/components/markdown.ts +16 -12
  418. package/src/components/menu-selection.ts +248 -0
  419. package/src/components/metric.ts +132 -0
  420. package/src/components/progress-bar.ts +140 -0
  421. package/src/components/scroll-view.ts +372 -70
  422. package/src/components/scroll-viewport.ts +154 -0
  423. package/src/components/section.ts +77 -0
  424. package/src/components/select-list.ts +238 -159
  425. package/src/components/settings-list.ts +103 -80
  426. package/src/components/table.ts +127 -0
  427. package/src/components/tree-view.ts +481 -0
  428. package/src/components/wizard-step.ts +229 -0
  429. package/src/deccara.ts +1 -3
  430. package/src/hotkeys-markdown.ts +100 -0
  431. package/src/index.ts +18 -0
  432. package/src/keybinding-matchers.ts +99 -0
  433. package/src/keys.ts +2 -2
  434. package/src/lang-from-path.ts +252 -0
  435. package/src/overlays/advisor-config.ts +761 -0
  436. package/src/overlays/agent-activity.ts +113 -0
  437. package/src/overlays/agent-hub-projection.ts +250 -0
  438. package/src/overlays/agent-hub-renderer.ts +281 -0
  439. package/src/overlays/agent-hub-types.ts +74 -0
  440. package/src/overlays/agent-hub.ts +1562 -0
  441. package/src/overlays/agent-transcript-viewer.ts +669 -0
  442. package/src/overlays/agents-hub.ts +1203 -0
  443. package/src/overlays/ask-dialog.ts +1202 -0
  444. package/src/overlays/bordered-loader.ts +41 -0
  445. package/src/overlays/btw-history-panel.ts +591 -0
  446. package/src/overlays/btw-history.ts +33 -0
  447. package/src/overlays/btw-panel.ts +167 -0
  448. package/src/overlays/cleanse-panel.ts +167 -0
  449. package/src/overlays/codex-reset-fireworks.ts +375 -0
  450. package/src/overlays/composer-shape-preview.ts +137 -0
  451. package/src/overlays/composer-shape-registry.ts +82 -0
  452. package/src/overlays/copy-selector.ts +618 -0
  453. package/src/overlays/copy-targets.ts +210 -0
  454. package/src/overlays/error-banner.ts +39 -0
  455. package/src/overlays/extensions/display-text.ts +39 -0
  456. package/src/overlays/extensions/extension-dashboard.ts +645 -0
  457. package/src/overlays/extensions/extension-list.ts +667 -0
  458. package/src/overlays/extensions/inspector-model.ts +484 -0
  459. package/src/overlays/extensions/inspector-panel.ts +669 -0
  460. package/src/overlays/extensions/live-tool-session.ts +67 -0
  461. package/src/overlays/extensions/mcp-runtime.ts +289 -0
  462. package/src/overlays/extensions/state-manager.ts +337 -0
  463. package/src/overlays/extensions/types.ts +208 -0
  464. package/src/overlays/history-search.ts +268 -0
  465. package/src/overlays/hook-editor.ts +272 -0
  466. package/src/overlays/hook-input.ts +84 -0
  467. package/src/overlays/hook-selector.ts +635 -0
  468. package/src/overlays/hub-frame.ts +262 -0
  469. package/src/overlays/login-dialog.ts +231 -0
  470. package/src/overlays/logout-account-selector.ts +115 -0
  471. package/src/overlays/mcp-add-wizard.ts +1436 -0
  472. package/src/overlays/model-browser.ts +1176 -0
  473. package/src/overlays/model-hub.ts +2279 -0
  474. package/src/overlays/model-picker.ts +285 -0
  475. package/src/overlays/model-selector.ts +107 -0
  476. package/src/overlays/move-overlay.ts +197 -0
  477. package/src/overlays/oauth-selector.ts +446 -0
  478. package/src/overlays/omfg-panel.ts +136 -0
  479. package/src/overlays/pause-screen.ts +198 -0
  480. package/src/overlays/plan-review-overlay.ts +1227 -0
  481. package/src/overlays/plan-save-overlay.ts +74 -0
  482. package/src/overlays/plan-toc.ts +139 -0
  483. package/src/overlays/plugin-selector.ts +97 -0
  484. package/src/overlays/plugin-settings.ts +839 -0
  485. package/src/overlays/queue-mode-selector.ts +55 -0
  486. package/src/overlays/reset-usage-selector.ts +170 -0
  487. package/src/overlays/rewind-selector.ts +499 -0
  488. package/src/overlays/running-subagent-badge.ts +19 -0
  489. package/src/overlays/session-account-selector.ts +68 -0
  490. package/src/overlays/session-info-overlay.ts +96 -0
  491. package/src/overlays/session-observer-registry.ts +289 -0
  492. package/src/overlays/session-selector.ts +1137 -0
  493. package/src/overlays/settings-defs.ts +315 -0
  494. package/src/overlays/settings-selector.ts +1415 -0
  495. package/src/overlays/show-images-selector.ts +44 -0
  496. package/src/overlays/snapcompact-shape-preview-doc.md +14 -0
  497. package/src/overlays/snapcompact-shape-preview.ts +192 -0
  498. package/src/overlays/theme-selector.ts +62 -0
  499. package/src/overlays/thinking-selector.ts +51 -0
  500. package/src/overlays/tiny-title-download-progress.ts +102 -0
  501. package/src/overlays/tree-selector.ts +1121 -0
  502. package/src/overlays/usage-dashboard.ts +629 -0
  503. package/src/overlays/usage-display.ts +45 -0
  504. package/src/overlays/usage-row.ts +118 -0
  505. package/src/prompt/attachment-chips.ts +201 -0
  506. package/src/prompt/composer-attachments.ts +280 -0
  507. package/src/prompt/composer-cache.ts +304 -0
  508. package/src/prompt/composer.ts +934 -0
  509. package/src/prompt/custom-editor.ts +1392 -0
  510. package/src/prompt/data/emojis.json +1 -0
  511. package/src/prompt/editor-top-gap.ts +28 -0
  512. package/src/prompt/emoji-autocomplete.ts +285 -0
  513. package/src/prompt/github-ref-autocomplete.ts +75 -0
  514. package/src/prompt/gradient-highlight.ts +101 -0
  515. package/src/prompt/image-format.ts +28 -0
  516. package/src/prompt/image-references.ts +102 -0
  517. package/src/prompt/interactive-context-helpers.ts +126 -0
  518. package/src/prompt/internal-url-autocomplete.ts +155 -0
  519. package/src/prompt/macos-spelling.ts +456 -0
  520. package/src/prompt/magic-keyword-boundary.ts +23 -0
  521. package/src/prompt/magic-keywords.ts +42 -0
  522. package/src/prompt/markdown-prose.ts +245 -0
  523. package/src/prompt/model-mention-autocomplete.ts +94 -0
  524. package/src/prompt/model-mention-syntax.ts +41 -0
  525. package/src/prompt/orchestrate.ts +26 -0
  526. package/src/prompt/prompt-action-autocomplete.ts +337 -0
  527. package/src/prompt/queue-input.ts +132 -0
  528. package/src/prompt/skill-tokens.ts +36 -0
  529. package/src/prompt/tips.txt +27 -0
  530. package/src/prompt/tools-markdown.ts +29 -0
  531. package/src/prompt/ultrathink.ts +26 -0
  532. package/src/prompt/usage-amounts.ts +99 -0
  533. package/src/prompt/video.ts +47 -0
  534. package/src/prompt/welcome.ts +605 -0
  535. package/src/prompt/workflow.ts +26 -0
  536. package/src/render/code-cell.ts +244 -0
  537. package/src/render/file-list.ts +58 -0
  538. package/src/render/hyperlink.ts +192 -0
  539. package/src/render/index.ts +17 -0
  540. package/src/render/output-block.ts +267 -0
  541. package/src/render/output-pane.ts +299 -0
  542. package/src/render/render-utils.ts +1182 -0
  543. package/src/render/sixel.ts +69 -0
  544. package/src/render/status-line.ts +55 -0
  545. package/src/render/tool-card.ts +272 -0
  546. package/src/render/tree-list.ts +188 -0
  547. package/src/render/types.ts +19 -0
  548. package/src/render/utils.ts +109 -0
  549. package/src/render/width-aware-text.ts +60 -0
  550. package/src/setup/lazy.ts +17 -0
  551. package/src/setup/scenes/composer.ts +127 -0
  552. package/src/setup/scenes/glyph.ts +120 -0
  553. package/src/setup/scenes/model.ts +138 -0
  554. package/src/setup/scenes/outro.ts +24 -0
  555. package/src/setup/scenes/providers.ts +107 -0
  556. package/src/setup/scenes/sign-in.ts +373 -0
  557. package/src/setup/scenes/splash.ts +192 -0
  558. package/src/setup/scenes/theme.ts +343 -0
  559. package/src/setup/scenes/types.ts +111 -0
  560. package/src/setup/scenes/web-search.ts +165 -0
  561. package/src/setup/setup-version.ts +11 -0
  562. package/src/setup/startup-splash.ts +107 -0
  563. package/src/setup/wizard-overlay.ts +319 -0
  564. package/src/setup/wizard.ts +101 -0
  565. package/src/status-line/component.ts +3003 -0
  566. package/src/status-line/context-usage.ts +688 -0
  567. package/src/status-line/footer.ts +323 -0
  568. package/src/status-line/git-utils.ts +42 -0
  569. package/src/status-line/host.ts +97 -0
  570. package/src/status-line/index.ts +6 -0
  571. package/src/status-line/loop.ts +28 -0
  572. package/src/status-line/metrics.ts +94 -0
  573. package/src/status-line/presets.ts +107 -0
  574. package/src/status-line/schema.ts +57 -0
  575. package/src/status-line/segments.ts +934 -0
  576. package/src/status-line/separators.ts +55 -0
  577. package/src/status-line/types.ts +227 -0
  578. package/src/stdin-buffer.ts +4 -2
  579. package/src/theme/color.ts +95 -0
  580. package/src/theme/dark.json +95 -0
  581. package/src/theme/defaults/alabaster.json +93 -0
  582. package/src/theme/defaults/amethyst.json +96 -0
  583. package/src/theme/defaults/anthracite.json +93 -0
  584. package/src/theme/defaults/basalt.json +91 -0
  585. package/src/theme/defaults/birch.json +95 -0
  586. package/src/theme/defaults/dark-abyss.json +91 -0
  587. package/src/theme/defaults/dark-arctic.json +104 -0
  588. package/src/theme/defaults/dark-aurora.json +95 -0
  589. package/src/theme/defaults/dark-catppuccin.json +107 -0
  590. package/src/theme/defaults/dark-cavern.json +91 -0
  591. package/src/theme/defaults/dark-celestial.json +106 -0
  592. package/src/theme/defaults/dark-copper.json +95 -0
  593. package/src/theme/defaults/dark-cosmos.json +90 -0
  594. package/src/theme/defaults/dark-cyberpunk.json +102 -0
  595. package/src/theme/defaults/dark-dracula.json +98 -0
  596. package/src/theme/defaults/dark-eclipse.json +91 -0
  597. package/src/theme/defaults/dark-ember.json +95 -0
  598. package/src/theme/defaults/dark-equinox.json +90 -0
  599. package/src/theme/defaults/dark-forest.json +96 -0
  600. package/src/theme/defaults/dark-github.json +105 -0
  601. package/src/theme/defaults/dark-gruvbox.json +112 -0
  602. package/src/theme/defaults/dark-lavender.json +95 -0
  603. package/src/theme/defaults/dark-lunar.json +89 -0
  604. package/src/theme/defaults/dark-midnight.json +95 -0
  605. package/src/theme/defaults/dark-monochrome.json +94 -0
  606. package/src/theme/defaults/dark-monokai.json +98 -0
  607. package/src/theme/defaults/dark-nebula.json +90 -0
  608. package/src/theme/defaults/dark-nord.json +98 -0
  609. package/src/theme/defaults/dark-ocean.json +101 -0
  610. package/src/theme/defaults/dark-one.json +100 -0
  611. package/src/theme/defaults/dark-poimandres.json +115 -0
  612. package/src/theme/defaults/dark-rainforest.json +91 -0
  613. package/src/theme/defaults/dark-reef.json +91 -0
  614. package/src/theme/defaults/dark-retro.json +92 -0
  615. package/src/theme/defaults/dark-rose-pine.json +96 -0
  616. package/src/theme/defaults/dark-sakura.json +95 -0
  617. package/src/theme/defaults/dark-slate.json +95 -0
  618. package/src/theme/defaults/dark-solarized.json +97 -0
  619. package/src/theme/defaults/dark-solstice.json +90 -0
  620. package/src/theme/defaults/dark-starfall.json +91 -0
  621. package/src/theme/defaults/dark-sunset.json +99 -0
  622. package/src/theme/defaults/dark-swamp.json +90 -0
  623. package/src/theme/defaults/dark-synthwave.json +103 -0
  624. package/src/theme/defaults/dark-taiga.json +91 -0
  625. package/src/theme/defaults/dark-terminal.json +95 -0
  626. package/src/theme/defaults/dark-tokyo-night.json +101 -0
  627. package/src/theme/defaults/dark-tundra.json +91 -0
  628. package/src/theme/defaults/dark-twilight.json +91 -0
  629. package/src/theme/defaults/dark-volcanic.json +91 -0
  630. package/src/theme/defaults/graphite.json +92 -0
  631. package/src/theme/defaults/index.ts +201 -0
  632. package/src/theme/defaults/light-arctic.json +107 -0
  633. package/src/theme/defaults/light-aurora-day.json +91 -0
  634. package/src/theme/defaults/light-canyon.json +91 -0
  635. package/src/theme/defaults/light-catppuccin.json +106 -0
  636. package/src/theme/defaults/light-cirrus.json +90 -0
  637. package/src/theme/defaults/light-coral.json +95 -0
  638. package/src/theme/defaults/light-cyberpunk.json +96 -0
  639. package/src/theme/defaults/light-dawn.json +90 -0
  640. package/src/theme/defaults/light-dunes.json +91 -0
  641. package/src/theme/defaults/light-eucalyptus.json +95 -0
  642. package/src/theme/defaults/light-forest.json +100 -0
  643. package/src/theme/defaults/light-frost.json +95 -0
  644. package/src/theme/defaults/light-github.json +115 -0
  645. package/src/theme/defaults/light-glacier.json +91 -0
  646. package/src/theme/defaults/light-gruvbox.json +108 -0
  647. package/src/theme/defaults/light-haze.json +90 -0
  648. package/src/theme/defaults/light-honeycomb.json +95 -0
  649. package/src/theme/defaults/light-lagoon.json +91 -0
  650. package/src/theme/defaults/light-lavender.json +95 -0
  651. package/src/theme/defaults/light-meadow.json +91 -0
  652. package/src/theme/defaults/light-mint.json +95 -0
  653. package/src/theme/defaults/light-monochrome.json +101 -0
  654. package/src/theme/defaults/light-ocean.json +99 -0
  655. package/src/theme/defaults/light-one.json +99 -0
  656. package/src/theme/defaults/light-opal.json +91 -0
  657. package/src/theme/defaults/light-orchard.json +91 -0
  658. package/src/theme/defaults/light-paper.json +95 -0
  659. package/src/theme/defaults/light-poimandres.json +115 -0
  660. package/src/theme/defaults/light-prism.json +90 -0
  661. package/src/theme/defaults/light-retro.json +98 -0
  662. package/src/theme/defaults/light-sand.json +95 -0
  663. package/src/theme/defaults/light-savanna.json +91 -0
  664. package/src/theme/defaults/light-solarized.json +102 -0
  665. package/src/theme/defaults/light-soleil.json +90 -0
  666. package/src/theme/defaults/light-sunset.json +99 -0
  667. package/src/theme/defaults/light-synthwave.json +98 -0
  668. package/src/theme/defaults/light-tokyo-night.json +111 -0
  669. package/src/theme/defaults/light-wetland.json +91 -0
  670. package/src/theme/defaults/light-zenith.json +89 -0
  671. package/src/theme/defaults/limestone.json +94 -0
  672. package/src/theme/defaults/mahogany.json +97 -0
  673. package/src/theme/defaults/marble.json +93 -0
  674. package/src/theme/defaults/obsidian.json +91 -0
  675. package/src/theme/defaults/onyx.json +91 -0
  676. package/src/theme/defaults/pearl.json +93 -0
  677. package/src/theme/defaults/porcelain.json +91 -0
  678. package/src/theme/defaults/quartz.json +96 -0
  679. package/src/theme/defaults/sandstone.json +95 -0
  680. package/src/theme/defaults/titanium.json +90 -0
  681. package/src/theme/index.ts +5 -0
  682. package/src/theme/light.json +93 -0
  683. package/src/theme/loader.ts +200 -0
  684. package/src/theme/mermaid-cache.ts +92 -0
  685. package/src/theme/schema-validation.ts +119 -0
  686. package/src/theme/schema.ts +168 -0
  687. package/src/theme/session-color.ts +253 -0
  688. package/src/theme/shimmer.ts +307 -0
  689. package/src/theme/symbols.ts +1433 -0
  690. package/src/theme/theme-class.ts +787 -0
  691. package/src/theme/theme-schema.json +463 -0
  692. package/src/theme/theme.ts +810 -0
  693. package/src/theme/tui-adapters.ts +339 -0
  694. package/src/thinking.ts +380 -0
  695. package/src/tools/agent-tree.ts +128 -0
  696. package/src/tools/ask.ts +482 -0
  697. package/src/tools/ast-edit.ts +210 -0
  698. package/src/tools/ast-grep.ts +171 -0
  699. package/src/tools/autoresearch.ts +287 -0
  700. package/src/tools/bash-interactive.ts +295 -0
  701. package/src/tools/bash.ts +468 -0
  702. package/src/tools/conflict-detect.ts +77 -0
  703. package/src/tools/debug.ts +167 -0
  704. package/src/tools/default-renderer.ts +172 -0
  705. package/src/tools/edit.ts +1258 -0
  706. package/src/tools/eval-format/code-mode-declarations.ts +61 -0
  707. package/src/tools/eval-format/index.ts +16 -0
  708. package/src/tools/eval-format/javascript.ts +952 -0
  709. package/src/tools/eval-format/python.ts +544 -0
  710. package/src/tools/eval.ts +835 -0
  711. package/src/tools/fetch.ts +226 -0
  712. package/src/tools/gh-format.ts +18 -0
  713. package/src/tools/github.ts +686 -0
  714. package/src/tools/glob.ts +213 -0
  715. package/src/tools/goal.ts +153 -0
  716. package/src/tools/grep.ts +385 -0
  717. package/src/tools/grouped-file-output.ts +211 -0
  718. package/src/tools/hashline-format.ts +59 -0
  719. package/src/tools/hub.ts +1488 -0
  720. package/src/tools/index.ts +71 -0
  721. package/src/tools/json-tree.ts +388 -0
  722. package/src/tools/line-ranges.ts +98 -0
  723. package/src/tools/list-limit.ts +43 -0
  724. package/src/tools/lsp.ts +702 -0
  725. package/src/tools/match-line-format.ts +20 -0
  726. package/src/tools/mcp.ts +309 -0
  727. package/src/tools/memory.ts +220 -0
  728. package/src/tools/output-meta.ts +367 -0
  729. package/src/tools/read.ts +628 -0
  730. package/src/tools/renderer.ts +107 -0
  731. package/src/tools/report-tool-issue.ts +13 -0
  732. package/src/tools/resolve.ts +139 -0
  733. package/src/tools/streaming-output.ts +1576 -0
  734. package/src/tools/subprocess.ts +39 -0
  735. package/src/tools/task-repair-args.ts +118 -0
  736. package/src/tools/task-yield-assembly.ts +147 -0
  737. package/src/tools/task.ts +2110 -0
  738. package/src/tools/terminal-output.ts +153 -0
  739. package/src/tools/think.ts +28 -0
  740. package/src/tools/todo.ts +580 -0
  741. package/src/tools/tool-errors.ts +15 -0
  742. package/src/tools/vibe.ts +420 -0
  743. package/src/tools/web-search.ts +409 -0
  744. package/src/tools/write.ts +511 -0
  745. package/src/tools/xd-url.ts +28 -0
  746. package/src/tools/xdev.ts +201 -0
  747. package/src/tui.ts +57 -4
  748. package/src/utils.ts +8 -0
@@ -0,0 +1,1488 @@
1
+ import { styleTerminalRow } from "./terminal-output";
2
+ import { TERMINAL_STATES } from "../apps/ps-data";
3
+ import type { Component } from "../tui";
4
+ import { Text } from "../components/text";
5
+ import { visibleWidth } from "../utils";
6
+ import { formatAge, pluralize } from "@oh-my-pi/pi-utils";
7
+ import { shimmerEnabled, shimmerText } from "../theme/shimmer";
8
+ import type { Theme, ThemeColor } from "../theme/theme";
9
+ import { Ellipsis, Hasher, type RenderCache, renderStatusLine, renderTreeList, truncateToWidth } from "../render/index";
10
+ import { framedToolCard } from "../render/tool-card";
11
+ import { formatArtifactErrorNotice, stripOutputNotice, type OutputMeta } from "./output-meta";
12
+ import {
13
+ FEED_MODEL_BADGE_WIDTH,
14
+ formatBadge,
15
+ formatDuration,
16
+ formatEmptyMessage,
17
+ formatFeedModelBadge,
18
+ formatStatusIcon,
19
+ getPreviewLines,
20
+ isFeedModelBadgeEnabled,
21
+ PREVIEW_LIMITS,
22
+ replaceTabs,
23
+ type ToolUIColor,
24
+ type ToolUIStatus,
25
+ capPreviewLines,
26
+ cappedHeadLines,
27
+ createCachedComponent,
28
+ DEFAULT_TERMINAL_PREVIEW_LINES,
29
+ formatMoreItems,
30
+ formatExpandHint,
31
+ previewLine,
32
+ TRUNCATE_LENGTHS,
33
+ shortenPath,
34
+ formatErrorDetail,
35
+ type ConfiguredThinkingLevel,
36
+ } from "../render/render-utils";
37
+ import type { StructuredSubagentOutput } from "./task";
38
+ import type { RenderResultOptions, ToolRenderer, ToolActivitySummary } from "./renderer";
39
+
40
+ /** Whether a wait snapshot contains only running jobs and no cancellations. */
41
+ export function isWaitingPollDetails(details: unknown): boolean {
42
+ const d = details as CoordinationDetails | undefined;
43
+ if (!d || !Array.isArray(d.jobs) || d.jobs.length === 0) return false;
44
+ if (d.cancelled?.length) return false;
45
+ return d.jobs.every(job => job?.status === "running");
46
+ }
47
+ /**
48
+ * Hub operations: messaging (`send`/`wait`/`inbox`/`list`), jobs
49
+ * (`wait`/`cancel`/`jobs`), and process supervision (`start`/`ps`/`logs`/
50
+ * `stop`/`restart`/`describe`, plus `send`/`wait` when they carry `name`).
51
+ */
52
+ export type HubOp =
53
+ | "send"
54
+ | "wait"
55
+ | "inbox"
56
+ | "list"
57
+ | "jobs"
58
+ | "cancel"
59
+ | "start"
60
+ | "ps"
61
+ | "logs"
62
+ | "stop"
63
+ | "restart"
64
+ | "describe";
65
+
66
+ /** Peer row surfaced by `op:"list"`. */
67
+ export interface HubPeerInfo {
68
+ id: string;
69
+ displayName: string;
70
+ kind: string;
71
+ status: string;
72
+ parentId?: string;
73
+ unread: number;
74
+ lastActivity: number;
75
+ activity?: string;
76
+ }
77
+
78
+ /** Status values `op:"list"` can filter on. Advisor is a kind, not a status. */
79
+ export type HubListStatus = "running" | "idle" | "parked";
80
+
81
+ /** Model-facing roster bounds shared by the hub schema and executor. */
82
+ export const DEFAULT_HUB_LIST_LIMIT = 32;
83
+
84
+ /** Maximum number of peers in a hub roster page. */
85
+ export const MAX_HUB_LIST_LIMIT = 100;
86
+
87
+ /** Addressable roster tallies always returned by `op:"list"`. */
88
+ export interface HubRosterCounts {
89
+ running: number;
90
+ idle: number;
91
+ parked: number;
92
+ shown: number;
93
+ truncated: number;
94
+ }
95
+
96
+ /** Background-job row surfaced by `wait`/`cancel`/`jobs` results. */
97
+ export interface JobSnapshot {
98
+ id: string;
99
+ type: "bash" | "task" | "eval";
100
+ status: "running" | "completed" | "failed" | "cancelled";
101
+ label: string;
102
+ durationMs: number;
103
+ /** Effective task model selector, including an explicit reasoning suffix when configured. */
104
+ resolvedModel?: string;
105
+ /** Provider/id including routing, with no added thinking suffix. */
106
+ resolvedModelIdentity?: string;
107
+ /** Explicit thinking metadata, independent of the model identity. */
108
+ resolvedThinkingLevel?: ConfiguredThinkingLevel;
109
+ /** True when the task progress reports an attached live advisor. */
110
+ advisor?: boolean;
111
+ resultText?: string;
112
+ errorText?: string;
113
+ /** Source-output metadata retained for per-job warnings and persisted row rendering. */
114
+ meta?: OutputMeta;
115
+ /** Capture error in historical snapshots; new snapshots store source metadata in `meta`. */
116
+ artifactError?: OutputMeta["artifactError"];
117
+ structured?: StructuredSubagentOutput;
118
+ /**
119
+ * `agent://<id>` handle backing this job's artifacts — the job-row's
120
+ * registry `agentId` when the manager disambiguated a requested job id
121
+ * on collision, else the job id itself. See {@link AsyncJob.agentId}.
122
+ */
123
+ agentUrlId?: string;
124
+ }
125
+
126
+ /** Outcome of cancelling one background job. */
127
+ export type CancelStatus = "cancelled" | "not_found" | "already_completed";
128
+
129
+ /** Cancellation outcome and its model-facing explanation. */
130
+ export interface CancelOutcome {
131
+ id: string;
132
+ status: CancelStatus;
133
+ message: string;
134
+ }
135
+
136
+ /**
137
+ * A live subagent from the AgentRegistry that has no backing job in the
138
+ * AsyncJobManager — e.g. an idle agent woken (or a parked agent revived) via
139
+ * a hub message, or a spawn owned by another agent. Surfaced by `jobs` and
140
+ * empty-wait snapshots so the hub's picture matches the UI's running-agent
141
+ * count.
142
+ */
143
+ export interface AgentActivitySnapshot {
144
+ id: string;
145
+ parentId?: string;
146
+ /** Latest activity gist recorded by the registry (display-only). */
147
+ activity?: string;
148
+ /** Time since the agent was registered. */
149
+ ageMs: number;
150
+ /**
151
+ * Whether an attached session corroborates the `running` claim. False marks
152
+ * a ref that says `running` with no turn in flight — either a spawn still
153
+ * wiring up or a stale registration that `hub cancel <id>` clears (#8634).
154
+ */
155
+ live: boolean;
156
+ /**
157
+ * Acceptance time of the agent's final result, when the registry recorded
158
+ * one. With `live: false` this is an accepted run the parent may cancel
159
+ * instead of waiting on (#11079).
160
+ */
161
+ acceptedAt?: number;
162
+ }
163
+
164
+ /** Result details for messaging and job ops; fields are disjoint per op. */
165
+ export interface CoordinationDetails {
166
+ meta?: OutputMeta;
167
+ op: HubOp;
168
+ from?: string;
169
+ to?: string;
170
+ receipts?: IrcDeliveryReceipt[];
171
+ /** Message consumed by `wait` / `send await:true`; null when the wait timed out. */
172
+ waited?: IrcMessage | null;
173
+ inbox?: IrcMessage[];
174
+ peers?: HubPeerInfo[];
175
+ /** Present on `op:"list"`: addressable running/idle/parked plus page size. */
176
+ counts?: HubRosterCounts;
177
+ jobs?: JobSnapshot[];
178
+ cancelled?: { id: string; status: CancelStatus }[];
179
+ /** Running subagents not represented by a job row in this result. */
180
+ agents?: AgentActivitySnapshot[];
181
+ }
182
+
183
+ /** Hub result details: coordination snapshots or launch (process) state. */
184
+ export type HubDetails = CoordinationDetails | LaunchToolDetails;
185
+
186
+ /** Partially-streamed hub call arguments, as seen by the renderers. */
187
+ export type HubRenderArgs = {
188
+ op?: string;
189
+ to?: string;
190
+ message?: string;
191
+ replyTo?: string;
192
+ await?: boolean;
193
+ from?: string;
194
+ peek?: boolean;
195
+ ids?: string[];
196
+ } & Partial<Omit<LaunchParams, "op">>;
197
+ /** Stable lifecycle states exposed by the launch tool. */
198
+ export type DaemonState = "starting" | "running" | "ready" | "restarting" | "stopping" | "exited" | "failed";
199
+
200
+ /** Restart behavior applied after an unexpected daemon exit. */
201
+ export type DaemonRestartPolicy = "no" | "on-failure" | "always";
202
+
203
+ /** Readiness conditions; every configured condition must pass. */
204
+ export interface DaemonReadySpec {
205
+ log?: string;
206
+ port?: number;
207
+ host?: string;
208
+ timeoutMs: number;
209
+ }
210
+
211
+ /** Immutable launch specification retained for restart and inspection. */
212
+ export interface DaemonSpec {
213
+ name: string;
214
+ application: string;
215
+ args: string[];
216
+ env: Record<string, string>;
217
+ cwd: string;
218
+ pty: boolean;
219
+ ready?: DaemonReadySpec;
220
+ restart: DaemonRestartPolicy;
221
+ persist: boolean;
222
+ detached: boolean;
223
+ }
224
+
225
+ /** Serializable daemon state visible to every client in one broker scope. */
226
+ export interface DaemonSnapshot {
227
+ name: string;
228
+ id: string;
229
+ state: DaemonState;
230
+ pid?: number;
231
+ createdAt: number;
232
+ startedAt: number;
233
+ readyAt?: number;
234
+ exitedAt?: number;
235
+ exitCode?: number;
236
+ exitReason?: string;
237
+ restartCount: number;
238
+ outputBytes: number;
239
+ owner?: string;
240
+ readyMatch?: string;
241
+ /** Readiness conditions still unmet while `state` is `starting`; absent once ready or without a ready spec. */
242
+ readyPending?: ("log" | "port")[];
243
+ persist: boolean;
244
+ detached: boolean;
245
+ }
246
+ /** Serializable peer message retained in hub result snapshots. */
247
+ export interface IrcMessage {
248
+ id: string;
249
+ /** Sender agent id. */
250
+ from: string;
251
+ /** Recipient agent id (resolved; "all" is expanded by the tool, not stored). */
252
+ to: string;
253
+ body: string;
254
+ ts: number;
255
+ /** Message id being answered. */
256
+ replyTo?: string;
257
+ /**
258
+ * Automated wake-turn relay of a woken subagent's stop output (task executor
259
+ * `relayWakeTurnOutput`). Relays are answers, never wake sources: the
260
+ * recipient's own wake-turn relay must skip them or two idle peers
261
+ * ping-pong forever.
262
+ */
263
+ wakeRelay?: boolean;
264
+ }
265
+
266
+ /** Delivery outcome for one peer recipient. */
267
+ export interface IrcDeliveryReceipt {
268
+ to: string;
269
+ outcome: "injected" | "woken" | "revived" | "failed";
270
+ error?: string;
271
+ }
272
+ /** Broker-facing launch parameters; the hub adapts its `ps` op to `list` before calling in. */
273
+ export interface LaunchParams {
274
+ op: "start" | "list" | "logs" | "wait" | "send" | "stop" | "restart" | "describe";
275
+ name?: string;
276
+ application?: string;
277
+ args?: string[];
278
+ env?: Record<string, string>;
279
+ cwd?: string;
280
+ pty?: boolean;
281
+ ready?: { log?: string; port?: number; host?: string; timeout?: number };
282
+ restart?: "no" | "on-failure" | "always";
283
+ persist?: boolean;
284
+ detached?: boolean;
285
+ lines?: number;
286
+ head?: boolean;
287
+ grep?: string;
288
+ follow?: boolean;
289
+ cursor?: number;
290
+ for?: "ready" | "exit";
291
+ pattern?: string;
292
+ text?: string;
293
+ enter?: boolean;
294
+ keys?: string[];
295
+ signal?: "SIGINT" | "SIGTERM" | "SIGHUP" | "SIGQUIT" | "SIGKILL";
296
+ timeout?: number;
297
+ }
298
+
299
+ /** Structured launch state retained for compact TUI rendering. */
300
+ export interface LaunchToolDetails {
301
+ op: LaunchParams["op"];
302
+ daemon?: DaemonSnapshot;
303
+ daemons?: DaemonSnapshot[];
304
+ cursor?: number;
305
+ timedOut?: boolean;
306
+ /** logs: daemon lifecycle state at read time. */
307
+ state?: DaemonState;
308
+ /** logs: virtual terminal rows for display; model-facing content remains sanitized text. */
309
+ terminalRows?: string[];
310
+ /** wait: output line that satisfied the pattern. */
311
+ matched?: string;
312
+ /** describe: immutable launch spec backing the command/cwd detail lines. */
313
+ spec?: DaemonSpec;
314
+ }
315
+
316
+ /**
317
+ * Human sentences for the readiness conditions still unmet, e.g.
318
+ * `port 5173 on 127.0.0.1 never accepted connections`. `ready` (from the start
319
+ * params) adds the concrete pattern/port; absent it falls back to generic labels.
320
+ */
321
+ export function readyPendingSummary(daemon: DaemonSnapshot, ready?: LaunchParams["ready"]): string[] {
322
+ const parts: string[] = [];
323
+ for (const condition of daemon.readyPending ?? []) {
324
+ if (condition === "log") {
325
+ parts.push(ready?.log ? `log pattern /${ready.log}/ never matched` : "the log pattern never matched");
326
+ } else {
327
+ parts.push(
328
+ ready?.port !== undefined
329
+ ? `port ${ready.port} on ${ready.host ?? "127.0.0.1"} never accepted connections`
330
+ : "the port never accepted connections",
331
+ );
332
+ }
333
+ }
334
+ return parts;
335
+ }
336
+
337
+ /**
338
+ * What a timed-out `wait` was actually blocked on: the output `pattern`, the
339
+ * process exiting (`for: "exit"`, the default), or the unmet readiness
340
+ * conditions (`for: "ready"`). Never reports readiness for an exit/pattern
341
+ * wait — the process may well be ready and simply still running.
342
+ */
343
+ export function waitPendingSummary(daemon: DaemonSnapshot, params: Pick<LaunchParams, "for" | "pattern">): string[] {
344
+ if (params.pattern) return [`output pattern /${params.pattern}/ never matched`];
345
+ if ((params.for ?? "exit") === "exit") return [`process exit (still ${daemon.state})`];
346
+ const pending = readyPendingSummary(daemon);
347
+ return pending.length > 0 ? pending : [`readiness (still ${daemon.state})`];
348
+ }
349
+ /** Hub roster ordering (running before idle before parked) shared with the child prompt's live-row cap. */
350
+ export const LIST_STATUS_ORDER: Record<string, number> = { running: 0, idle: 1, parked: 2 };
351
+
352
+ // =============================================================================
353
+ // TUI Renderer (jobs half)
354
+ // =============================================================================
355
+
356
+ interface JobRenderArgs {
357
+ poll?: string[];
358
+ cancel?: string[];
359
+ list?: boolean;
360
+ }
361
+
362
+ /** Hub args → legacy job-renderer arg shape, preserving the exact frame titles. */
363
+ function toJobRenderArgs(args: HubRenderArgs | undefined): JobRenderArgs | undefined {
364
+ if (!args) return undefined;
365
+ switch (args.op) {
366
+ case "wait":
367
+ return { poll: args.ids };
368
+ case "cancel":
369
+ return { cancel: args.ids ?? [] };
370
+ case "jobs":
371
+ return { list: true };
372
+ default:
373
+ return {};
374
+ }
375
+ }
376
+
377
+ const LABEL_MAX_WIDTH = 60;
378
+ const PREVIEW_LINES_COLLAPSED = 1;
379
+ const PREVIEW_LINES_EXPANDED = 4;
380
+ const LABEL_LINES_COLLAPSED = 1;
381
+ const LABEL_LINES_EXPANDED = 3;
382
+ const PREVIEW_LINE_WIDTH = 80;
383
+
384
+ function statusToIcon(status: JobSnapshot["status"]): ToolUIStatus {
385
+ switch (status) {
386
+ case "completed":
387
+ return "done";
388
+ case "failed":
389
+ return "error";
390
+ case "cancelled":
391
+ return "aborted";
392
+ case "running":
393
+ return "running";
394
+ }
395
+ }
396
+
397
+ function statusToColor(status: JobSnapshot["status"]): ToolUIColor {
398
+ switch (status) {
399
+ case "completed":
400
+ return "success";
401
+ case "failed":
402
+ return "error";
403
+ case "cancelled":
404
+ return "warning";
405
+ case "running":
406
+ return "accent";
407
+ }
408
+ }
409
+
410
+ /**
411
+ * Task job results are delivered in the model-facing `<task-result>` envelope
412
+ * (prompts/tools/task-summary.md) so the parent agent can parse status and the
413
+ * `agent://` pointer. The wrapper markup is noise to a human — preview the
414
+ * inner <output>/<preview> body instead.
415
+ */
416
+ function stripTaskResultEnvelope(text: string): string {
417
+ if (!text.startsWith("<task-result")) return text;
418
+ const body = /<(output|preview)(?:\s[^>]*)?>\n?([\s\S]*?)\n?<\/\1>/.exec(text)?.[2];
419
+ return body?.trim() || text;
420
+ }
421
+
422
+ /**
423
+ * Pretty-printed JSON output wastes the collapsed one-line preview on a lone
424
+ * "{" — flatten structured-looking bodies onto a single line. Slice first:
425
+ * downstream truncation keeps at most a few hundred columns, so collapsing
426
+ * whitespace across a multi-KB body would be pure waste.
427
+ */
428
+ function flattenStructuredPreview(text: string): string {
429
+ const first = text[0];
430
+ if (first !== "{" && first !== "[") return text;
431
+ return text.slice(0, PREVIEW_LINES_EXPANDED * PREVIEW_LINE_WIDTH * 2).replace(/\s+/g, " ");
432
+ }
433
+
434
+ function describeTarget(args: JobRenderArgs | undefined): string {
435
+ if (args?.list) return "background jobs";
436
+ const poll = args?.poll ?? [];
437
+ const cancel = args?.cancel ?? [];
438
+ const parts: string[] = [];
439
+ if (cancel.length > 0) {
440
+ parts.push(cancel.length === 1 ? `cancel ${cancel[0]}` : `cancel ${cancel.length} jobs`);
441
+ }
442
+ if (poll.length > 0) {
443
+ parts.push(poll.length === 1 ? `poll ${poll[0]}` : `poll ${poll.length} jobs`);
444
+ }
445
+ if (parts.length === 0) return "all running jobs";
446
+ return parts.join(", ");
447
+ }
448
+
449
+ /** Pending-call frame for job ops (wait/cancel/jobs). */
450
+ export function jobsRenderCall(args: HubRenderArgs, _options: RenderResultOptions, uiTheme: Theme): Component {
451
+ const text = renderStatusLine({ icon: "pending", title: describeTarget(toJobRenderArgs(args)) || "Job" }, uiTheme);
452
+ return new Text(text, 0, 0);
453
+ }
454
+
455
+ /** Result frame for job snapshots (wait/cancel/jobs and the agents roster). */
456
+ export function jobsRenderResult(
457
+ result: { content: Array<{ type: string; text?: string }>; details?: CoordinationDetails; isError?: boolean },
458
+ options: RenderResultOptions,
459
+ uiTheme: Theme,
460
+ hubArgs?: HubRenderArgs,
461
+ ): Component {
462
+ const args = toJobRenderArgs(hubArgs);
463
+ let jobs = result.details?.jobs ?? [];
464
+ const agents = result.details?.agents ?? [];
465
+
466
+ if (jobs.length === 0 && agents.length === 0) {
467
+ const fallback = result.content?.find(c => c.type === "text")?.text || "No jobs to process";
468
+ const header = renderStatusLine({ icon: "warning", title: describeTarget(args) || "Job" }, uiTheme);
469
+ return new Text([header, formatEmptyMessage(fallback, uiTheme)].join("\n"), 0, 0);
470
+ }
471
+
472
+ const isPollCall = args ? !args.list && (!args.cancel || args.cancel.length === 0 || args.poll !== undefined) : true;
473
+
474
+ // Agent-carrying results (jobs snapshot / empty-wait roster) are real
475
+ // snapshots, not displaceable waiting frames — only agentless waits
476
+ // collapse their still-running rows once sealed.
477
+ if (!options.isPartial && isPollCall && agents.length === 0) {
478
+ jobs = jobs.filter(job => job.status !== "running");
479
+ if (jobs.length === 0) {
480
+ return new Text("", 0, 0);
481
+ }
482
+ }
483
+
484
+ const counts = { completed: 0, failed: 0, cancelled: 0, running: 0 };
485
+ for (const job of jobs) counts[job.status]++;
486
+
487
+ // The title already carries the running count, so meta lists only the
488
+ // settled categories — "waiting on 19 of 19 · 19 running" read awkward.
489
+ const meta: string[] = [];
490
+ if (counts.completed > 0) meta.push(uiTheme.fg("success", `${counts.completed} done`));
491
+ if (counts.failed > 0) meta.push(uiTheme.fg("error", `${counts.failed} failed`));
492
+ if (counts.cancelled > 0) meta.push(uiTheme.fg("warning", `${counts.cancelled} cancelled`));
493
+ if (agents.length > 0 && jobs.length > 0) {
494
+ meta.push(uiTheme.fg("accent", `${agents.length} agent${agents.length === 1 ? "" : "s"}`));
495
+ }
496
+
497
+ const headerIcon: ToolUIStatus =
498
+ counts.failed > 0 ? "warning" : counts.running > 0 || agents.length > 0 ? "info" : "success";
499
+ const jobsNoun = jobs.length === 1 ? "job" : "jobs";
500
+ const description =
501
+ jobs.length === 0
502
+ ? `${agents.length} running agent${agents.length === 1 ? "" : "s"} — no jobs`
503
+ : counts.running > 0
504
+ ? counts.running === jobs.length
505
+ ? `waiting on ${jobs.length} ${jobsNoun}`
506
+ : `waiting on ${counts.running} of ${jobs.length} ${jobsNoun}`
507
+ : `${jobs.length} ${jobsNoun} settled`;
508
+
509
+ const header = renderStatusLine(
510
+ {
511
+ icon: headerIcon,
512
+ spinnerFrame: counts.running > 0 || agents.length > 0 ? options.spinnerFrame : undefined,
513
+ title: description,
514
+ meta,
515
+ },
516
+ uiTheme,
517
+ );
518
+
519
+ const outputMeta = result.details?.meta;
520
+ // Historical snapshots promoted a job failure to the root. Render it there
521
+ // only when no row identifies that failure; new report capture errors are distinct.
522
+ const aggregateArtifactError =
523
+ outputMeta?.artifactError &&
524
+ (outputMeta.source?.type === "report" ||
525
+ !jobs.some(job => (job.meta?.artifactError ?? job.artifactError) === outputMeta.artifactError))
526
+ ? outputMeta.artifactError
527
+ : undefined;
528
+
529
+ // Sort: running first (so user sees what's still pending), then failed, then completed/cancelled.
530
+ const statusOrder: Record<JobSnapshot["status"], number> = {
531
+ running: 0,
532
+ failed: 1,
533
+ cancelled: 2,
534
+ completed: 3,
535
+ };
536
+ const sortedJobs = [...jobs].sort((a, b) => {
537
+ const diff = statusOrder[a.status] - statusOrder[b.status];
538
+ if (diff !== 0) return diff;
539
+ return b.durationMs - a.durationMs;
540
+ });
541
+
542
+ let cached: RenderCache | undefined;
543
+ return {
544
+ render(width: number): readonly string[] {
545
+ const expanded = options.expanded;
546
+ const spinnerFrame = options.spinnerFrame ?? 0;
547
+ // Running-job labels shimmer while the wait block is live; the band
548
+ // phase is Date.now()-sampled at render time, so serving cached bytes
549
+ // would pin it to the ~12.5fps spinner-glyph cadence instead of the
550
+ // 30fps redraw. Bypass the cache while any row animates, and key on
551
+ // the animation state so a sealed block never hits stale shimmered
552
+ // bytes (spinnerFrame falls back to 0 on both sides of the seal).
553
+ const shimmerActive = counts.running > 0 && options.spinnerFrame !== undefined && shimmerEnabled();
554
+ const showModelBadge = isFeedModelBadgeEnabled();
555
+ const key = new Hasher()
556
+ .bool(expanded)
557
+ .u32(width)
558
+ .u32(spinnerFrame)
559
+ .bool(shimmerActive)
560
+ .bool(showModelBadge)
561
+ .digest();
562
+ if (!shimmerActive && cached?.key === key) return cached.lines;
563
+
564
+ const itemLines = renderTreeList<JobSnapshot>(
565
+ {
566
+ items: sortedJobs,
567
+ expanded,
568
+ maxCollapsed: PREVIEW_LIMITS.COLLAPSED_ITEMS,
569
+ itemType: "job",
570
+ renderItem: (job, context) => {
571
+ const rowWidth = Math.max(0, width - (context.prefixWidth ?? 0));
572
+ const lines: string[] = [];
573
+ const icon = formatStatusIcon(
574
+ statusToIcon(job.status),
575
+ uiTheme,
576
+ job.status === "running" ? options.spinnerFrame : undefined,
577
+ );
578
+ const typeBadge = formatBadge(job.type, statusToColor(job.status), uiTheme);
579
+ const durationSuffix = `${uiTheme.sep.dot}${uiTheme.fg("dim", formatDuration(job.durationMs))}`;
580
+ const displayId = truncateToWidth(
581
+ replaceTabs(job.id).replace(/\s+/g, " "),
582
+ Math.max(0, rowWidth - visibleWidth(`${icon} ${typeBadge} ${durationSuffix}`)),
583
+ Ellipsis.Unicode,
584
+ );
585
+ const rawLabelLines = (job.label || "(no label)").split(/\r?\n/);
586
+ const maxLabelLines = expanded ? LABEL_LINES_EXPANDED : LABEL_LINES_COLLAPSED;
587
+ const visibleLabelLines = rawLabelLines
588
+ .slice(0, maxLabelLines)
589
+ .map(l => truncateToWidth(replaceTabs(l), LABEL_MAX_WIDTH, Ellipsis.Unicode));
590
+ if (rawLabelLines.length > maxLabelLines && visibleLabelLines.length > 0) {
591
+ const last = visibleLabelLines[visibleLabelLines.length - 1]!;
592
+ visibleLabelLines[visibleLabelLines.length - 1] = `${last} …`;
593
+ }
594
+ const rowPrefix = `${icon} ${typeBadge} `;
595
+ const modelIdentity = job.resolvedModelIdentity ?? job.resolvedModel;
596
+ const modelBadge =
597
+ job.type === "task" && showModelBadge && typeof modelIdentity === "string"
598
+ ? formatFeedModelBadge(
599
+ modelIdentity,
600
+ job.resolvedThinkingLevel,
601
+ job.advisor === true,
602
+ uiTheme,
603
+ Math.min(
604
+ FEED_MODEL_BADGE_WIDTH,
605
+ Math.max(0, rowWidth - visibleWidth(`${rowPrefix}${displayId}${durationSuffix}`) - 1),
606
+ ),
607
+ )
608
+ : "";
609
+ const modelLead = modelBadge ? `${modelBadge} ` : "";
610
+ const headRaw = displayId;
611
+ // Running rows in a live block shimmer their label; once the block
612
+ // stops animating (sealed, or a settled snapshot — spinnerFrame
613
+ // cleared) they render static so scrollback never keeps a mid-sweep
614
+ // shimmer band.
615
+ const live = job.status === "running" && options.spinnerFrame !== undefined;
616
+ const headLabel = live
617
+ ? shimmerEnabled()
618
+ ? shimmerText(headRaw, uiTheme)
619
+ : uiTheme.fg("accent", headRaw)
620
+ : uiTheme.fg("toolOutput", headRaw);
621
+ let row = `${rowPrefix}${modelLead}${headLabel}`;
622
+ const distinctLabel = job.label.trim() !== job.id;
623
+ const label = visibleLabelLines[0] ?? "";
624
+ const inlineLabel = distinctLabel && visibleWidth(`${row} ${label}${durationSuffix}`) <= rowWidth;
625
+ if (inlineLabel) row += ` ${uiTheme.fg("toolOutput", label)}`;
626
+ row += durationSuffix;
627
+ lines.push(truncateToWidth(row, rowWidth, ""));
628
+ const continuationWidth = Math.max(0, rowWidth - visibleWidth(" "));
629
+ for (let i = distinctLabel && !inlineLabel ? 0 : 1; i < visibleLabelLines.length; i++) {
630
+ lines.push(
631
+ ` ${uiTheme.fg("toolOutput", truncateToWidth(visibleLabelLines[i]!, continuationWidth))}`,
632
+ );
633
+ }
634
+ const artifactError = job.meta?.artifactError ?? job.artifactError;
635
+ if (artifactError) {
636
+ lines.push(
637
+ uiTheme.fg("warning", truncateToWidth(formatArtifactErrorNotice(artifactError), rowWidth)),
638
+ );
639
+ }
640
+
641
+ // Legacy rows did not retain full metadata. Strip the known warning
642
+ // footer so the dedicated row/root warning remains the only copy.
643
+ const previewError =
644
+ artifactError ?? (outputMeta?.source?.type !== "report" ? outputMeta?.artifactError : undefined);
645
+ const previewMeta = job.meta ?? (previewError ? { artifactError: previewError } : undefined);
646
+
647
+ const preview = flattenStructuredPreview(
648
+ stripTaskResultEnvelope(
649
+ stripOutputNotice(job.errorText?.trim() || job.resultText?.trim() || "", previewMeta).trim(),
650
+ ),
651
+ );
652
+ if (preview) {
653
+ const maxLines = expanded ? PREVIEW_LINES_EXPANDED : PREVIEW_LINES_COLLAPSED;
654
+ const previewLines = getPreviewLines(
655
+ preview,
656
+ maxLines,
657
+ Math.min(PREVIEW_LINE_WIDTH, continuationWidth),
658
+ Ellipsis.Unicode,
659
+ );
660
+ const tone = job.errorText ? "error" : "dim";
661
+ for (const pl of previewLines) {
662
+ lines.push(` ${uiTheme.fg(tone, pl)}`);
663
+ }
664
+ }
665
+ return lines;
666
+ },
667
+ },
668
+ uiTheme,
669
+ );
670
+
671
+ // Agents run outside job control; render them as their own tree so
672
+ // they never skew the job counts or the "waiting on N jobs" title.
673
+ const agentLines =
674
+ agents.length === 0
675
+ ? []
676
+ : renderTreeList<AgentActivitySnapshot>(
677
+ {
678
+ items: agents,
679
+ expanded,
680
+ maxCollapsed: PREVIEW_LIMITS.COLLAPSED_ITEMS,
681
+ itemType: "agent",
682
+ renderItem: (agent, context) => {
683
+ const rowWidth = Math.max(0, width - (context.prefixWidth ?? 0));
684
+ const icon = agent.live
685
+ ? formatStatusIcon("running", uiTheme, options.spinnerFrame)
686
+ : formatStatusIcon("warning", uiTheme);
687
+ const badge = agent.live
688
+ ? formatBadge("agent", "accent", uiTheme)
689
+ : formatBadge("agent · no turn", "warning", uiTheme);
690
+ const id = truncateToWidth(
691
+ replaceTabs(agent.id).replace(/\s+/g, " "),
692
+ Math.max(0, rowWidth - visibleWidth(`${icon} ${badge}`)),
693
+ Ellipsis.Unicode,
694
+ );
695
+ const gist = agent.activity
696
+ ? ` ${uiTheme.fg("toolOutput", truncateToWidth(replaceTabs(agent.activity), LABEL_MAX_WIDTH, Ellipsis.Unicode))}`
697
+ : "";
698
+ const parent = agent.parentId ? uiTheme.fg("dim", ` ← ${agent.parentId}`) : "";
699
+ const age = uiTheme.fg("dim", formatDuration(agent.ageMs));
700
+ return [
701
+ truncateToWidth(
702
+ `${icon} ${uiTheme.fg("muted", id)} ${badge}${gist} ${age}${parent}`,
703
+ rowWidth,
704
+ "",
705
+ ),
706
+ ];
707
+ },
708
+ },
709
+ uiTheme,
710
+ );
711
+
712
+ const all = [header];
713
+ if (aggregateArtifactError) {
714
+ all.push(uiTheme.fg("warning", formatArtifactErrorNotice(aggregateArtifactError)));
715
+ }
716
+ all.push(...itemLines, ...agentLines);
717
+ for (let i = 0; i < all.length; i++) all[i] = truncateToWidth(all[i]!, width, Ellipsis.Unicode);
718
+ cached = { key, lines: all };
719
+ return all;
720
+ },
721
+ invalidate() {
722
+ cached = undefined;
723
+ },
724
+ };
725
+ }
726
+
727
+ // =============================================================================
728
+ // TUI Renderer (launch half)
729
+ // =============================================================================
730
+
731
+ /** Args shape visible to the renderer, possibly mid-stream (every field optional). */
732
+ export type LaunchRenderArgs = Partial<Omit<LaunchParams, "op">> & { op?: string };
733
+
734
+ function stateColor(state: DaemonState): ThemeColor {
735
+ switch (state) {
736
+ case "running":
737
+ case "ready":
738
+ return "success";
739
+ case "failed":
740
+ return "error";
741
+ case "exited":
742
+ return "muted";
743
+ default:
744
+ return "warning";
745
+ }
746
+ }
747
+
748
+ /** Compact `state · pid · uptime` fragments for the status-line meta slot. */
749
+ function daemonMeta(daemon: DaemonSnapshot, theme: Theme): string[] {
750
+ const meta = [theme.fg(stateColor(daemon.state), daemon.state)];
751
+ if (daemon.readyPending?.length) meta.push(theme.fg("warning", `waiting on ${daemon.readyPending.join("+")}`));
752
+ if (daemon.exitCode !== undefined) {
753
+ meta.push(theme.fg(daemon.exitCode === 0 ? "muted" : "error", `exit ${daemon.exitCode}`));
754
+ } else if (daemon.pid !== undefined) {
755
+ meta.push(`pid ${daemon.pid}`);
756
+ }
757
+ const lifespan = formatDuration((daemon.exitedAt ?? Date.now()) - daemon.startedAt);
758
+ meta.push(daemon.exitedAt === undefined ? `up ${lifespan}` : `ran ${lifespan}`);
759
+ if (daemon.restartCount > 0) meta.push(`restarts ${daemon.restartCount}`);
760
+ if (daemon.detached) meta.push("detached");
761
+ else if (daemon.persist) meta.push("persistent");
762
+ return meta;
763
+ }
764
+
765
+ /** Op-specific call context (command line, log filters, wait condition, send payload). */
766
+ function launchCallMeta(args: LaunchRenderArgs): string[] {
767
+ const meta: string[] = [];
768
+ switch (args.op) {
769
+ case "start":
770
+ if (args.application) meta.push([args.application, ...(args.args ?? [])].join(" "));
771
+ break;
772
+ case "logs":
773
+ if (args.follow) meta.push("follow");
774
+ if (args.grep) meta.push(`grep /${args.grep}/`);
775
+ break;
776
+ case "wait":
777
+ meta.push(args.pattern ? `for /${args.pattern}/` : `for ${args.for ?? "exit"}`);
778
+ break;
779
+ case "send":
780
+ if (args.signal) meta.push(args.signal);
781
+ else if (args.text) meta.push(args.text);
782
+ if (args.keys?.length) meta.push(args.keys.join(" "));
783
+ break;
784
+ }
785
+ return meta.map(entry => previewLine(replaceTabs(entry), TRUNCATE_LENGTHS.SHORT));
786
+ }
787
+
788
+ /** Pending-call frame for launch ops; consumes the spinner while the broker call is live. */
789
+ export function launchRenderCall(args: LaunchRenderArgs, options: RenderResultOptions, theme: Theme): Component {
790
+ const target = args.name ?? args.application;
791
+ const header = renderStatusLine(
792
+ {
793
+ icon: options.spinnerFrame !== undefined ? "running" : "pending",
794
+ spinnerFrame: options.spinnerFrame,
795
+ title: `Launch ${args.op ?? "…"}`,
796
+ description: target ? replaceTabs(target) : undefined,
797
+ meta: launchCallMeta(args),
798
+ },
799
+ theme,
800
+ );
801
+ return new Text(header, 0, 0);
802
+ }
803
+
804
+ /** Result frame: one status header per op, meta from structured details, capped body lines. */
805
+ export function launchRenderResult(
806
+ result: { content: Array<{ type: string; text?: string }>; details?: LaunchToolDetails; isError?: boolean },
807
+ options: RenderResultOptions,
808
+ theme: Theme,
809
+ args?: LaunchRenderArgs,
810
+ ): Component {
811
+ const details = result.details;
812
+ const params = args ?? {};
813
+ const op = details?.op ?? params.op;
814
+ const isError = result.isError === true;
815
+ const daemon = details?.daemon;
816
+ const failed = isError || daemon?.state === "failed";
817
+ const text =
818
+ result.content
819
+ ?.filter(item => item.type === "text")
820
+ .map(item => item.text ?? "")
821
+ .join("\n") ?? "";
822
+
823
+ const meta: string[] = [];
824
+ const body: string[] = [];
825
+ let description = params.name ?? daemon?.name;
826
+
827
+ if (isError) {
828
+ for (const line of replaceTabs(text.trimEnd()).split("\n")) body.push(theme.fg("error", line));
829
+ } else {
830
+ switch (op) {
831
+ case "start": {
832
+ meta.push(...launchCallMeta(params));
833
+ if (daemon) meta.push(...daemonMeta(daemon, theme));
834
+ if (daemon?.readyMatch) body.push(theme.fg("dim", `log matched: ${replaceTabs(daemon.readyMatch)}`));
835
+ if (daemon?.state === "failed" && daemon.exitReason)
836
+ body.push(theme.fg("error", replaceTabs(daemon.exitReason)));
837
+ if (details?.timedOut) {
838
+ const pending = daemon ? readyPendingSummary(daemon, params.ready) : [];
839
+ body.push(
840
+ theme.fg(
841
+ "warning",
842
+ pending.length > 0
843
+ ? `Not ready — ${pending.join("; ")}. Still running.`
844
+ : "Readiness timed out; the process is still running.",
845
+ ),
846
+ );
847
+ } else if (params.ready && daemon && daemon.readyAt === undefined && TERMINAL_STATES[daemon.state]) {
848
+ body.push(theme.fg("warning", "Process exited before readiness was observed."));
849
+ }
850
+ break;
851
+ }
852
+ case "send":
853
+ meta.push(...launchCallMeta(params));
854
+ if (daemon) meta.push(...daemonMeta(daemon, theme));
855
+ break;
856
+ case "stop":
857
+ case "restart":
858
+ if (daemon) meta.push(...daemonMeta(daemon, theme));
859
+ break;
860
+ case "wait": {
861
+ meta.push(...launchCallMeta(params));
862
+ if (daemon) meta.push(...daemonMeta(daemon, theme));
863
+ if (details?.matched) body.push(theme.fg("dim", `matched: ${replaceTabs(details.matched)}`));
864
+ if (details?.timedOut) {
865
+ body.push(
866
+ theme.fg(
867
+ "warning",
868
+ daemon
869
+ ? `Wait timed out — still waiting on ${waitPendingSummary(daemon, params).join("; ")}.`
870
+ : "Wait timed out.",
871
+ ),
872
+ );
873
+ }
874
+ break;
875
+ }
876
+ case "list": {
877
+ const daemons = details?.daemons ?? [];
878
+ description = `${daemons.length || "no"} ${pluralize("process", daemons.length)}`;
879
+ for (const item of daemons) {
880
+ body.push(
881
+ `${theme.fg("accent", replaceTabs(item.name))} ${theme.fg("dim", daemonMeta(item, theme).join(theme.sep.dot))}`,
882
+ );
883
+ }
884
+ break;
885
+ }
886
+ case "logs": {
887
+ if (details?.state) meta.push(theme.fg(stateColor(details.state), details.state));
888
+ if (details?.cursor !== undefined) meta.push(`cursor ${details.cursor}`);
889
+ if (details?.timedOut) meta.push(theme.fg("warning", "follow timed out"));
890
+ // Strip the trailing `[name: state; cursor=N]` status suffix `toolContent` appends.
891
+ const logText = text.replace(/\n?\[[^\n]*\]$/, "").trimEnd();
892
+ const terminalRows = details?.terminalRows;
893
+ if (terminalRows) {
894
+ for (const row of terminalRows) body.push(styleTerminalRow(row, theme.getFgAnsi("toolOutput")));
895
+ } else if (logText) {
896
+ for (const line of logText.split("\n")) body.push(theme.fg("toolOutput", replaceTabs(line)));
897
+ }
898
+ break;
899
+ }
900
+ case "describe": {
901
+ if (daemon) meta.push(...daemonMeta(daemon, theme));
902
+ const spec = details?.spec;
903
+ if (spec) {
904
+ body.push(theme.fg("toolOutput", replaceTabs([spec.application, ...spec.args].join(" "))));
905
+ body.push(theme.fg("dim", `cwd ${shortenPath(spec.cwd)}`));
906
+ const flags = [`pty ${spec.pty}`, `restart ${spec.restart}`];
907
+ if (spec.detached) flags.push("detached");
908
+ else if (spec.persist) flags.push("persistent");
909
+ body.push(theme.fg("dim", flags.join(theme.sep.dot)));
910
+ }
911
+ break;
912
+ }
913
+ default:
914
+ if (text.trim()) {
915
+ for (const line of replaceTabs(text.trimEnd()).split("\n")) body.push(theme.fg("toolOutput", line));
916
+ }
917
+ }
918
+ }
919
+
920
+ const header = renderStatusLine(
921
+ {
922
+ ...(failed
923
+ ? { icon: "error" as const }
924
+ : options.isPartial
925
+ ? { icon: "pending" as const }
926
+ : { iconOverride: theme.styledSymbol("tool.launch", "accent") }),
927
+ title: `Launch ${op ?? ""}`.trimEnd(),
928
+ description: description ? replaceTabs(description) : undefined,
929
+ meta,
930
+ },
931
+ theme,
932
+ );
933
+
934
+ if (op === "logs") {
935
+ return framedToolCard(theme, ({ contentWidth }) => {
936
+ const rows = body.map(line => truncateToWidth(line, contentWidth));
937
+ return {
938
+ header,
939
+ phase: options.isPartial ? "partial" : failed ? "error" : "success",
940
+ sections: [
941
+ {
942
+ label: theme.fg("toolTitle", "Output"),
943
+ content: capPreviewLines(rows, theme, {
944
+ expanded: options.expanded,
945
+ max: DEFAULT_TERMINAL_PREVIEW_LINES,
946
+ }),
947
+ },
948
+ ],
949
+ };
950
+ });
951
+ }
952
+
953
+ return createCachedComponent(
954
+ () => options.expanded,
955
+ (width, expanded) => {
956
+ let visible = body;
957
+ if (!expanded && op === "list" && body.length > PREVIEW_LIMITS.COLLAPSED_ITEMS) {
958
+ const remaining = body.length - PREVIEW_LIMITS.COLLAPSED_ITEMS;
959
+ visible = [
960
+ ...body.slice(0, PREVIEW_LIMITS.COLLAPSED_ITEMS),
961
+ theme.fg("dim", `${formatMoreItems(remaining, "process")} ${formatExpandHint(theme, false, true)}`),
962
+ ];
963
+ }
964
+ return [header, ...visible].map(line => truncateToWidth(line, width));
965
+ },
966
+ );
967
+ }
968
+
969
+ // =============================================================================
970
+ // TUI Renderer (messaging half)
971
+ // =============================================================================
972
+
973
+ const BODY_LINES_COLLAPSED = 2;
974
+ const BODY_LINES_EXPANDED = 12;
975
+ const BODY_LINE_WIDTH = 100;
976
+
977
+ function ircGlyph(theme: Theme): string {
978
+ return theme.styledSymbol("tool.irc", "accent");
979
+ }
980
+
981
+ function outcomeColor(outcome: IrcDeliveryReceipt["outcome"]): ToolUIColor {
982
+ switch (outcome) {
983
+ case "woken":
984
+ return "success";
985
+ case "revived":
986
+ return "warning";
987
+ case "injected":
988
+ return "accent";
989
+ case "failed":
990
+ return "error";
991
+ }
992
+ }
993
+
994
+ /** Glyph + status word, matching the agent-hub status conventions. */
995
+ function peerStatusBadge(status: string, theme: Theme): string {
996
+ switch (status) {
997
+ case "running":
998
+ return theme.fg("accent", `${theme.status.running} running`);
999
+ case "idle":
1000
+ return theme.fg("success", `${theme.status.enabled} idle`);
1001
+ case "parked":
1002
+ return theme.fg("muted", `${theme.status.shadowed} parked`);
1003
+ default:
1004
+ return theme.fg("error", `${theme.status.aborted} ${status}`);
1005
+ }
1006
+ }
1007
+
1008
+ function messageAge(ts: number | undefined): string {
1009
+ if (!ts) return "";
1010
+ return formatAge(Math.max(1, Math.round((Date.now() - ts) / 1000)));
1011
+ }
1012
+
1013
+ function firstTextBlock(result: { content: Array<{ type: string; text?: string }> }): string {
1014
+ return result.content.find(part => part.type === "text")?.text?.trim() ?? "";
1015
+ }
1016
+
1017
+ /**
1018
+ * Quote-bordered message body preview. `tone` separates outbound text (dim)
1019
+ * from received text (toolOutput); a trailing dim counter marks elided lines.
1020
+ */
1021
+ function bodyLines(
1022
+ body: string,
1023
+ expanded: boolean,
1024
+ theme: Theme,
1025
+ options: { indent?: string; tone?: "dim" | "toolOutput"; collapsedLines?: number } = {},
1026
+ ): string[] {
1027
+ const indent = options.indent ?? "";
1028
+ const tone = options.tone ?? "toolOutput";
1029
+ const max = expanded ? BODY_LINES_EXPANDED : (options.collapsedLines ?? BODY_LINES_COLLAPSED);
1030
+ const preview = cappedHeadLines(
1031
+ body.split("\n").filter(line => line.trim()),
1032
+ max,
1033
+ );
1034
+ const quote = theme.fg("dim", theme.md.quoteBorder);
1035
+ const lines = preview.lines.map(
1036
+ line =>
1037
+ `${indent}${quote} ${theme.fg(tone, replaceTabs(truncateToWidth(line.trim(), BODY_LINE_WIDTH, Ellipsis.Unicode)))}`,
1038
+ );
1039
+ const hidden = preview.hidden;
1040
+ if (hidden > 0) {
1041
+ lines.push(`${indent}${quote} ${theme.fg("dim", `… +${hidden} more ${hidden === 1 ? "line" : "lines"}`)}`);
1042
+ }
1043
+ return lines;
1044
+ }
1045
+
1046
+ /** Header title carrying the op direction: `IRC ➤ peer` out, `IRC ⟵ peer` in. */
1047
+ function callTitle(args: HubRenderArgs | undefined, theme: Theme): string {
1048
+ switch (args?.op) {
1049
+ case "send":
1050
+ return `IRC ${theme.nav.selected} ${args.to?.trim() || "…"}`;
1051
+ case "wait":
1052
+ return `IRC ${theme.nav.back} ${args.from?.trim() || "anyone"}`;
1053
+ case "inbox":
1054
+ return "IRC inbox";
1055
+ case "list":
1056
+ return "IRC peers";
1057
+ default:
1058
+ return "Hub";
1059
+ }
1060
+ }
1061
+
1062
+ function messagingCallMeta(args: HubRenderArgs | undefined): string[] {
1063
+ const meta: string[] = [];
1064
+ if (args?.op === "send") {
1065
+ if (args.to === "all") meta.push("broadcast");
1066
+ if (args.await) meta.push("await reply");
1067
+ if (args.replyTo) meta.push("reply");
1068
+ }
1069
+ if (args?.op === "inbox" && args.peek) meta.push("peek");
1070
+ return meta;
1071
+ }
1072
+
1073
+ function renderErrorResult(
1074
+ result: { content: Array<{ type: string; text?: string }> },
1075
+ args: HubRenderArgs | undefined,
1076
+ theme: Theme,
1077
+ ): string[] {
1078
+ const text = firstTextBlock(result) || "IRC call failed.";
1079
+ return [
1080
+ renderStatusLine({ icon: "error", title: callTitle(args, theme), meta: messagingCallMeta(args) }, theme),
1081
+ formatErrorDetail(text, theme),
1082
+ ];
1083
+ }
1084
+
1085
+ /**
1086
+ * Display-only transcript card for live IRC traffic: `irc:incoming` DMs
1087
+ * delivered to this session, `irc:autoreply` side-channel replies sent on
1088
+ * this session's behalf, and `irc:relay` observations of agent↔agent
1089
+ * traffic. Shares the tool renderer's glyph + quote-border conventions so
1090
+ * cards and hub messaging output look identical in the transcript.
1091
+ */
1092
+ export function createIrcMessageCard(
1093
+ card: {
1094
+ kind: "incoming" | "autoreply" | "relay" | "workpool";
1095
+ from?: string;
1096
+ to?: string;
1097
+ body?: string;
1098
+ replyTo?: string;
1099
+ timestamp?: number;
1100
+ pool?: string;
1101
+ mode?: string;
1102
+ },
1103
+ getExpanded: () => boolean,
1104
+ uiTheme: Theme,
1105
+ ): Component {
1106
+ const from = card.from?.trim() || "?";
1107
+ const title =
1108
+ card.kind === "incoming"
1109
+ ? `IRC ${uiTheme.nav.back} ${from}`
1110
+ : card.kind === "autoreply"
1111
+ ? `IRC ${uiTheme.nav.selected} ${card.to?.trim() || "?"}`
1112
+ : card.kind === "workpool"
1113
+ ? `Pool ${card.pool?.trim() || "?"} ${uiTheme.nav.selected} ${card.to?.trim() || "?"}`
1114
+ : `IRC ${from} ${uiTheme.nav.selected} ${card.to?.trim() || "?"}`;
1115
+ const body = card.body ?? "";
1116
+ const meta: string[] = [];
1117
+ if (card.kind === "autoreply") meta.push("auto");
1118
+ if (card.kind === "workpool" && card.mode) meta.push(card.mode);
1119
+ if (card.replyTo) meta.push("reply");
1120
+ const age = messageAge(card.timestamp);
1121
+ if (age) meta.push(age);
1122
+ return createCachedComponent(
1123
+ getExpanded,
1124
+ (width, expanded) => {
1125
+ const lines = [renderStatusLine({ iconOverride: ircGlyph(uiTheme), title, meta }, uiTheme)];
1126
+ if (body.trim()) {
1127
+ lines.push(...bodyLines(body, expanded, uiTheme, { indent: " ", collapsedLines: 3 }));
1128
+ }
1129
+ return lines.map(line => truncateToWidth(line, width, Ellipsis.Unicode));
1130
+ },
1131
+ { paddingX: 1 },
1132
+ );
1133
+ }
1134
+
1135
+ function renderSendResult(
1136
+ result: { content: Array<{ type: string; text?: string }>; isError?: boolean },
1137
+ details: Partial<CoordinationDetails>,
1138
+ args: HubRenderArgs | undefined,
1139
+ expanded: boolean,
1140
+ theme: Theme,
1141
+ ): string[] {
1142
+ const receipts = details.receipts ?? [];
1143
+ const to = details.to ?? args?.to?.trim() ?? "?";
1144
+ const title = `IRC ${theme.nav.selected} ${to}`;
1145
+
1146
+ // Pre-delivery failures (validation) and empty broadcasts carry no receipts.
1147
+ if (receipts.length === 0) {
1148
+ const text = firstTextBlock(result) || (result.isError ? "Send failed." : "Nothing to deliver.");
1149
+ return [
1150
+ renderStatusLine({ icon: result.isError ? "error" : "warning", title }, theme),
1151
+ result.isError ? formatErrorDetail(text, theme) : ` ${theme.fg("muted", replaceTabs(text))}`,
1152
+ ];
1153
+ }
1154
+
1155
+ const delivered = receipts.filter(receipt => receipt.outcome !== "failed");
1156
+ const failedCount = receipts.length - delivered.length;
1157
+ const waited = details.waited;
1158
+ const timedOut = waited === null;
1159
+
1160
+ const meta: string[] = [];
1161
+ if (to === "all") meta.push("broadcast");
1162
+ if (receipts.length === 1) {
1163
+ const receipt = receipts[0]!;
1164
+ meta.push(theme.fg(outcomeColor(receipt.outcome), receipt.outcome));
1165
+ } else {
1166
+ if (delivered.length > 0) meta.push(theme.fg("success", `${delivered.length} delivered`));
1167
+ if (failedCount > 0) meta.push(theme.fg("error", `${failedCount} failed`));
1168
+ }
1169
+ if (timedOut) meta.push(theme.fg("warning", "no reply"));
1170
+
1171
+ const icon = result.isError
1172
+ ? { icon: "error" as const }
1173
+ : timedOut
1174
+ ? { icon: "warning" as const }
1175
+ : { iconOverride: ircGlyph(theme) };
1176
+ const lines = [renderStatusLine({ ...icon, title, meta }, theme)];
1177
+
1178
+ const sent = args?.message?.trim();
1179
+ if (sent) lines.push(...bodyLines(sent, expanded, theme, { indent: " ", tone: "dim" }));
1180
+
1181
+ if (receipts.length > 1 || failedCount > 0) {
1182
+ lines.push(
1183
+ ...renderTreeList<IrcDeliveryReceipt>(
1184
+ {
1185
+ items: receipts,
1186
+ expanded,
1187
+ maxCollapsed: PREVIEW_LIMITS.COLLAPSED_ITEMS,
1188
+ itemType: "recipient",
1189
+ renderItem: receipt => {
1190
+ const badge = formatBadge(receipt.outcome, outcomeColor(receipt.outcome), theme);
1191
+ const error =
1192
+ receipt.outcome === "failed" && receipt.error
1193
+ ? ` ${theme.fg("error", `${theme.format.dash} ${receipt.error}`)}`
1194
+ : "";
1195
+ return `${theme.fg("toolOutput", receipt.to)} ${badge}${error}`;
1196
+ },
1197
+ },
1198
+ theme,
1199
+ ),
1200
+ );
1201
+ }
1202
+
1203
+ if (waited) {
1204
+ const age = messageAge(waited.ts);
1205
+ lines.push(
1206
+ ` ${theme.fg("dim", theme.nav.back)} ${theme.fg("accent", waited.from)}${age ? ` ${theme.fg("dim", age)}` : ""}`,
1207
+ );
1208
+ lines.push(...bodyLines(waited.body, expanded, theme, { indent: " " }));
1209
+ } else if (timedOut) {
1210
+ lines.push(` ${theme.fg("warning", "No reply yet — they may answer later; check inbox or wait again.")}`);
1211
+ }
1212
+ return lines;
1213
+ }
1214
+
1215
+ function renderWaitResult(
1216
+ result: { content: Array<{ type: string; text?: string }>; isError?: boolean },
1217
+ details: Partial<CoordinationDetails>,
1218
+ args: HubRenderArgs | undefined,
1219
+ expanded: boolean,
1220
+ theme: Theme,
1221
+ ): string[] {
1222
+ const waited = details.waited;
1223
+ if (!waited) {
1224
+ const text = firstTextBlock(result) || "No message arrived.";
1225
+ return [
1226
+ renderStatusLine(
1227
+ { icon: "warning", title: `IRC ${theme.nav.back} ${args?.from?.trim() || "anyone"}`, meta: ["timed out"] },
1228
+ theme,
1229
+ ),
1230
+ ` ${theme.fg("muted", replaceTabs(text))}`,
1231
+ ];
1232
+ }
1233
+ const meta = [messageAge(waited.ts)];
1234
+ if (waited.replyTo) meta.push("reply");
1235
+ return [
1236
+ renderStatusLine({ iconOverride: ircGlyph(theme), title: `IRC ${theme.nav.back} ${waited.from}`, meta }, theme),
1237
+ ...bodyLines(waited.body, expanded, theme, { indent: " " }),
1238
+ ];
1239
+ }
1240
+
1241
+ function renderInboxResult(
1242
+ details: Partial<CoordinationDetails>,
1243
+ args: HubRenderArgs | undefined,
1244
+ expanded: boolean,
1245
+ theme: Theme,
1246
+ ): string[] {
1247
+ const messages = details.inbox ?? [];
1248
+ if (messages.length === 0) {
1249
+ return [renderStatusLine({ iconOverride: ircGlyph(theme), title: "IRC inbox", meta: ["empty"] }, theme)];
1250
+ }
1251
+ const meta = [`${messages.length} ${messages.length === 1 ? "message" : "messages"}`];
1252
+ if (args?.peek) meta.push("peek");
1253
+ const header = renderStatusLine({ iconOverride: ircGlyph(theme), title: "IRC inbox", meta }, theme);
1254
+ const items = renderTreeList<IrcMessage>(
1255
+ {
1256
+ items: messages,
1257
+ expanded,
1258
+ maxCollapsed: PREVIEW_LIMITS.COLLAPSED_ITEMS,
1259
+ itemType: "message",
1260
+ renderItem: msg => {
1261
+ const age = messageAge(msg.ts);
1262
+ const replyBadge = msg.replyTo ? ` ${formatBadge("reply", "muted", theme)}` : "";
1263
+ const head = `${theme.fg("accent", msg.from)}${age ? ` ${theme.fg("dim", age)}` : ""}${replyBadge}`;
1264
+ return [head, ...bodyLines(msg.body, expanded, theme, { collapsedLines: 1 })];
1265
+ },
1266
+ },
1267
+ theme,
1268
+ );
1269
+ return [header, ...items];
1270
+ }
1271
+
1272
+ function renderListResult(details: Partial<CoordinationDetails>, expanded: boolean, theme: Theme): string[] {
1273
+ const peers = [...(details.peers ?? [])].sort(
1274
+ (a, b) =>
1275
+ (LIST_STATUS_ORDER[a.status] ?? 9) - (LIST_STATUS_ORDER[b.status] ?? 9) || b.lastActivity - a.lastActivity,
1276
+ );
1277
+ const rosterCounts = details.counts;
1278
+ if (peers.length === 0) {
1279
+ const meta =
1280
+ rosterCounts && rosterCounts.running + rosterCounts.idle + rosterCounts.parked > 0
1281
+ ? [
1282
+ `${rosterCounts.running} running`,
1283
+ `${rosterCounts.idle} idle`,
1284
+ `${rosterCounts.parked} parked`,
1285
+ ...(rosterCounts.truncated > 0 ? [`${rosterCounts.truncated} truncated`] : []),
1286
+ ]
1287
+ : ["no other agents"];
1288
+ return [renderStatusLine({ icon: "info", title: "IRC peers", meta }, theme)];
1289
+ }
1290
+ const counts = new Map<string, number>();
1291
+ for (const peer of peers) counts.set(peer.status, (counts.get(peer.status) ?? 0) + 1);
1292
+ const meta = rosterCounts
1293
+ ? [
1294
+ `${rosterCounts.running} running`,
1295
+ `${rosterCounts.idle} idle`,
1296
+ `${rosterCounts.parked} parked`,
1297
+ ...(rosterCounts.truncated > 0 ? [`${rosterCounts.truncated} truncated`] : []),
1298
+ ]
1299
+ : [...counts].map(([status, count]) => `${count} ${status}`);
1300
+ const unreadTotal = peers.reduce((sum, peer) => sum + peer.unread, 0);
1301
+ if (unreadTotal > 0) meta.push(theme.fg("warning", `${unreadTotal} unread`));
1302
+ const header = renderStatusLine({ iconOverride: ircGlyph(theme), title: "IRC peers", meta }, theme);
1303
+ const items = renderTreeList(
1304
+ {
1305
+ items: peers,
1306
+ expanded,
1307
+ maxCollapsed: PREVIEW_LIMITS.COLLAPSED_ITEMS,
1308
+ itemType: "peer",
1309
+ renderItem: peer => {
1310
+ const kindText = peer.parentId ? `${peer.kind}${theme.sep.dot}of ${peer.parentId}` : peer.kind;
1311
+ const unread = peer.unread > 0 ? ` ${formatBadge(`${peer.unread} unread`, "warning", theme)}` : "";
1312
+ const age = messageAge(peer.lastActivity);
1313
+ const activity = peer.activity ? ` ${theme.fg("dim", replaceTabs(peer.activity))}` : "";
1314
+ const name = theme.fg("dim", replaceTabs(peer.displayName));
1315
+ return `${peerStatusBadge(peer.status, theme)} ${theme.bold(replaceTabs(peer.id))} ${name} ${theme.fg("dim", kindText)}${activity}${unread}${age ? ` ${theme.fg("dim", age)}` : ""}`;
1316
+ },
1317
+ },
1318
+ theme,
1319
+ );
1320
+ return [header, ...items];
1321
+ }
1322
+ function buildResultLines(
1323
+ result: { content: Array<{ type: string; text?: string }>; isError?: boolean },
1324
+ details: Partial<CoordinationDetails>,
1325
+ args: HubRenderArgs | undefined,
1326
+ expanded: boolean,
1327
+ theme: Theme,
1328
+ ): string[] {
1329
+ switch (details.op ?? args?.op) {
1330
+ case "send":
1331
+ return renderSendResult(result, details, args, expanded, theme);
1332
+ case "wait":
1333
+ return renderWaitResult(result, details, args, expanded, theme);
1334
+ case "inbox":
1335
+ return result.isError
1336
+ ? renderErrorResult(result, args, theme)
1337
+ : renderInboxResult(details, args, expanded, theme);
1338
+ case "list":
1339
+ return result.isError ? renderErrorResult(result, args, theme) : renderListResult(details, expanded, theme);
1340
+ default: {
1341
+ const text = firstTextBlock(result) || (result.isError ? "Hub call failed." : "Done.");
1342
+ return [
1343
+ renderStatusLine({ icon: result.isError ? "error" : "success", title: callTitle(args, theme) }, theme),
1344
+ result.isError ? formatErrorDetail(text, theme) : ` ${theme.fg("muted", replaceTabs(text))}`,
1345
+ ];
1346
+ }
1347
+ }
1348
+ }
1349
+
1350
+ /** Pending-call frame for messaging ops (send/wait-from/inbox/list). */
1351
+ export function messagingRenderCall(args: HubRenderArgs, _options: RenderResultOptions, uiTheme: Theme): Component {
1352
+ const lines = [
1353
+ renderStatusLine({ icon: "pending", title: callTitle(args, uiTheme), meta: messagingCallMeta(args) }, uiTheme),
1354
+ ];
1355
+ if (args?.op === "send" && args.message?.trim()) {
1356
+ lines.push(...bodyLines(args.message, false, uiTheme, { indent: " ", tone: "dim", collapsedLines: 1 }));
1357
+ }
1358
+ return new Text(lines.join("\n"), 0, 0);
1359
+ }
1360
+
1361
+ /** Result frame for messaging ops and message-carrying `wait` results. */
1362
+ export function messagingRenderResult(
1363
+ result: { content: Array<{ type: string; text?: string }>; details?: CoordinationDetails; isError?: boolean },
1364
+ options: RenderResultOptions,
1365
+ uiTheme: Theme,
1366
+ args?: HubRenderArgs,
1367
+ ): Component {
1368
+ const details: Partial<CoordinationDetails> = result.details ?? {};
1369
+ return createCachedComponent(
1370
+ () => options.expanded,
1371
+ (width, expanded) =>
1372
+ buildResultLines(result, details, args, expanded, uiTheme).map(line =>
1373
+ truncateToWidth(line, width, Ellipsis.Unicode),
1374
+ ),
1375
+ );
1376
+ }
1377
+
1378
+ // =============================================================================
1379
+ // TUI Renderer — dispatches to the preserved messaging/job/launch renderings.
1380
+ // =============================================================================
1381
+
1382
+ const LAUNCH_OPS: Record<string, true> = {
1383
+ start: true,
1384
+ ps: true,
1385
+ logs: true,
1386
+ stop: true,
1387
+ restart: true,
1388
+ describe: true,
1389
+ };
1390
+
1391
+ /** Launch-style call: an explicit process op, or `send`/`wait` targeting a process `name`. */
1392
+ function isLaunchStyleArgs(args: HubRenderArgs | undefined): boolean {
1393
+ if (!args?.op) return false;
1394
+ if (LAUNCH_OPS[args.op]) return true;
1395
+ return (args.op === "send" || args.op === "wait") && !!args.name && !args.to && !args.from;
1396
+ }
1397
+
1398
+ /** Job-style call: job ops, or a `wait` that does not target a peer or process. */
1399
+ function isJobStyleArgs(args: HubRenderArgs | undefined): boolean {
1400
+ switch (args?.op) {
1401
+ case "jobs":
1402
+ case "cancel":
1403
+ return true;
1404
+ case "wait":
1405
+ return !!args.ids?.length || (!args.from && !args.name);
1406
+ default:
1407
+ return false;
1408
+ }
1409
+ }
1410
+
1411
+ /** Launch details carry process/broker state; coordination details never define these keys. */
1412
+ function isLaunchDetails(details: HubDetails): details is LaunchToolDetails {
1413
+ // `state`/`cursor` cover logs results, which may carry neither a daemon
1414
+ // snapshot nor terminal rows; coordination details never define these keys.
1415
+ return (
1416
+ "daemon" in details ||
1417
+ "daemons" in details ||
1418
+ "terminalRows" in details ||
1419
+ "spec" in details ||
1420
+ "state" in details ||
1421
+ "cursor" in details
1422
+ );
1423
+ }
1424
+
1425
+ /** Hub args → launch renderer args: `ps` is the broker's `list`; everything else is verbatim. */
1426
+ function toLaunchArgs(args: HubRenderArgs | undefined): LaunchRenderArgs {
1427
+ if (!args) return {};
1428
+ const { op, ...rest } = args;
1429
+ return { ...rest, op: op === "ps" ? "list" : op };
1430
+ }
1431
+
1432
+ /** Renders hub messaging, jobs, and supervised processes. */
1433
+ export const hubToolRenderer = {
1434
+ inline: true,
1435
+ mergeCallAndResult: true,
1436
+ /** Compact one-line activity: op plus its peer, process, or job target. */
1437
+ activitySummary(args: unknown): ToolActivitySummary {
1438
+ const hubArgs = (args ?? {}) as HubRenderArgs;
1439
+ const op = hubArgs.op;
1440
+ if (!op) return { label: "Hub" };
1441
+ let detail = op;
1442
+ if (op === "send" && (hubArgs.to || hubArgs.name)) detail = `send → ${hubArgs.to ?? hubArgs.name}`;
1443
+ else if (op === "wait" && (hubArgs.from || hubArgs.name)) detail = `wait ${hubArgs.from ?? hubArgs.name}`;
1444
+ else if ((op === "wait" || op === "cancel") && hubArgs.ids?.length) {
1445
+ detail = `${op} ${hubArgs.ids.length} job${hubArgs.ids.length === 1 ? "" : "s"}`;
1446
+ } else if (hubArgs.name) detail = `${op} ${hubArgs.name}`;
1447
+ return { label: "Hub", detail };
1448
+ },
1449
+ // Only launch pending frames consume the spinner (broker RPC in flight);
1450
+ // messaging/job pending frames are static, exactly as before the merge.
1451
+ animatedPendingPreview: (args: unknown): boolean => isLaunchStyleArgs(args as HubRenderArgs | undefined),
1452
+
1453
+ renderCall(args: HubRenderArgs, options: RenderResultOptions, uiTheme: Theme): Component {
1454
+ if (isLaunchStyleArgs(args)) return launchRenderCall(toLaunchArgs(args), options, uiTheme);
1455
+ return isJobStyleArgs(args)
1456
+ ? jobsRenderCall(args, options, uiTheme)
1457
+ : messagingRenderCall(args, options, uiTheme);
1458
+ },
1459
+
1460
+ renderResult(
1461
+ result: { content: Array<{ type: string; text?: string }>; details?: HubDetails; isError?: boolean },
1462
+ options: RenderResultOptions,
1463
+ uiTheme: Theme,
1464
+ args?: HubRenderArgs,
1465
+ ): Component {
1466
+ // Results dispatch on what actually happened, falling back to the call
1467
+ // shape when details are absent (framework-generated errors).
1468
+ const details = result.details;
1469
+ if (details && isLaunchDetails(details)) {
1470
+ return launchRenderResult({ ...result, details }, options, uiTheme, toLaunchArgs(args));
1471
+ }
1472
+ const coordination = details;
1473
+ if (coordination && (Array.isArray(coordination.jobs) || Array.isArray(coordination.agents))) {
1474
+ return jobsRenderResult({ ...result, details: coordination }, options, uiTheme, args);
1475
+ }
1476
+ if (
1477
+ coordination &&
1478
+ ("receipts" in coordination || "waited" in coordination || "inbox" in coordination || "peers" in coordination)
1479
+ ) {
1480
+ return messagingRenderResult({ ...result, details: coordination }, options, uiTheme, args);
1481
+ }
1482
+ // Detail-less or op-only results (validation errors, disabled gates).
1483
+ if (isLaunchStyleArgs(args))
1484
+ return launchRenderResult({ ...result, details: undefined }, options, uiTheme, toLaunchArgs(args));
1485
+ if (isJobStyleArgs(args)) return jobsRenderResult({ ...result, details: coordination }, options, uiTheme, args);
1486
+ return messagingRenderResult({ ...result, details: coordination }, options, uiTheme, args);
1487
+ },
1488
+ } satisfies ToolRenderer<HubRenderArgs, HubDetails>;