@oh-my-pi/pi-tui 18.4.2 → 18.4.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (463) hide show
  1. package/CHANGELOG.md +56 -0
  2. package/dist/types/apps/cleanse-board.d.ts +17 -0
  3. package/dist/types/apps/git/help.d.ts +16 -0
  4. package/dist/types/apps/git/sidebar.d.ts +18 -0
  5. package/dist/types/apps/live-visualizer.d.ts +10 -0
  6. package/dist/types/apps/ps-top.d.ts +15 -0
  7. package/dist/types/apps/standalone-picker.d.ts +3 -0
  8. package/dist/types/autocomplete.d.ts +8 -0
  9. package/dist/types/chat/advisor-message.d.ts +12 -2
  10. package/dist/types/chat/assistant-message.d.ts +20 -0
  11. package/dist/types/chat/bash-execution.d.ts +9 -0
  12. package/dist/types/chat/chat-transcript-builder.d.ts +6 -1
  13. package/dist/types/chat/collab-prompt-message.d.ts +4 -0
  14. package/dist/types/chat/compaction-summary-message.d.ts +15 -0
  15. package/dist/types/chat/eval-execution.d.ts +8 -0
  16. package/dist/types/chat/execution-shared.d.ts +58 -0
  17. package/dist/types/chat/late-diagnostics-message.d.ts +15 -1
  18. package/dist/types/chat/read-tool-group.d.ts +15 -0
  19. package/dist/types/chat/skill-message.d.ts +9 -0
  20. package/dist/types/chat/stripped-tool-calls-placeholder.d.ts +3 -0
  21. package/dist/types/chat/thinking-display.d.ts +2 -0
  22. package/dist/types/chat/todo-reminder.d.ts +4 -1
  23. package/dist/types/chat/tool-execution.d.ts +25 -1
  24. package/dist/types/chat/transcript-actions.d.ts +37 -0
  25. package/dist/types/chat/transcript-entry.d.ts +3 -1
  26. package/dist/types/chat/user-message.d.ts +25 -0
  27. package/dist/types/chrome/collab-qrcode.d.ts +7 -0
  28. package/dist/types/chrome/context-thresholds.d.ts +11 -0
  29. package/dist/types/chrome/countdown-timer.d.ts +9 -3
  30. package/dist/types/chrome/diff.d.ts +12 -0
  31. package/dist/types/chrome/dynamic-border.d.ts +3 -0
  32. package/dist/types/chrome/format.d.ts +6 -0
  33. package/dist/types/chrome/message-divider.d.ts +13 -0
  34. package/dist/types/chrome/message-frame.d.ts +10 -0
  35. package/dist/types/chrome/message-notice.d.ts +23 -0
  36. package/dist/types/chrome/overlay-box.d.ts +11 -2
  37. package/dist/types/chrome/qrcode.d.ts +7 -0
  38. package/dist/types/chrome/segment-track.d.ts +7 -0
  39. package/dist/types/chrome/selector-helpers.d.ts +0 -6
  40. package/dist/types/chrome/status-notice.d.ts +14 -0
  41. package/dist/types/chrome/streaming-panel.d.ts +3 -0
  42. package/dist/types/chrome/tool-activity.d.ts +3 -0
  43. package/dist/types/chrome/transcript-container.d.ts +13 -0
  44. package/dist/types/chrome/transcript-status.d.ts +15 -3
  45. package/dist/types/components/box.d.ts +7 -0
  46. package/dist/types/components/disclosure.d.ts +9 -0
  47. package/dist/types/components/editor.d.ts +42 -0
  48. package/dist/types/components/form.d.ts +14 -0
  49. package/dist/types/components/image.d.ts +7 -0
  50. package/dist/types/components/input.d.ts +20 -1
  51. package/dist/types/components/key-value-list.d.ts +3 -0
  52. package/dist/types/components/layout/geometry.d.ts +16 -0
  53. package/dist/types/components/layout/row.d.ts +7 -0
  54. package/dist/types/components/layout/split-pane.d.ts +8 -0
  55. package/dist/types/components/layout/stack.d.ts +6 -0
  56. package/dist/types/components/loader.d.ts +48 -0
  57. package/dist/types/components/markdown.d.ts +8 -0
  58. package/dist/types/components/metric.d.ts +7 -0
  59. package/dist/types/components/progress-bar.d.ts +3 -0
  60. package/dist/types/components/scroll-view.d.ts +6 -0
  61. package/dist/types/components/section.d.ts +3 -0
  62. package/dist/types/components/select-list.d.ts +33 -0
  63. package/dist/types/components/settings-list.d.ts +68 -0
  64. package/dist/types/components/spacer.d.ts +4 -0
  65. package/dist/types/components/tab-bar.d.ts +9 -0
  66. package/dist/types/components/table.d.ts +7 -0
  67. package/dist/types/components/text.d.ts +6 -0
  68. package/dist/types/components/tree-view.d.ts +9 -0
  69. package/dist/types/components/truncated-text.d.ts +3 -0
  70. package/dist/types/components/wizard-step.d.ts +6 -0
  71. package/dist/types/hotkeys-markdown.d.ts +24 -0
  72. package/dist/types/index.d.ts +5 -0
  73. package/dist/types/latex-block.d.ts +7 -0
  74. package/dist/types/loop-watchdog.d.ts +9 -0
  75. package/dist/types/native/apply.d.ts +39 -0
  76. package/dist/types/native/backend.d.ts +94 -0
  77. package/dist/types/native/blobs.d.ts +29 -0
  78. package/dist/types/native/describe.d.ts +56 -0
  79. package/dist/types/native/encode.d.ts +50 -0
  80. package/dist/types/native/icons.d.ts +18 -0
  81. package/dist/types/native/memo.d.ts +27 -0
  82. package/dist/types/native/node.d.ts +107 -0
  83. package/dist/types/native/overlay.d.ts +64 -0
  84. package/dist/types/native/picker.d.ts +124 -0
  85. package/dist/types/native/reconcile.d.ts +41 -0
  86. package/dist/types/native/settle.d.ts +11 -0
  87. package/dist/types/native/spans.d.ts +21 -0
  88. package/dist/types/native/state.d.ts +6 -0
  89. package/dist/types/native/tone.d.ts +24 -0
  90. package/dist/types/overlays/advisor-config.d.ts +20 -0
  91. package/dist/types/overlays/agent-hub-renderer.d.ts +27 -11
  92. package/dist/types/overlays/agent-hub.d.ts +10 -0
  93. package/dist/types/overlays/agent-transcript-viewer.d.ts +7 -0
  94. package/dist/types/overlays/agents-hub.d.ts +8 -0
  95. package/dist/types/overlays/annotation-overlay.d.ts +3 -0
  96. package/dist/types/overlays/ask-dialog.d.ts +23 -0
  97. package/dist/types/overlays/bordered-loader.d.ts +3 -0
  98. package/dist/types/overlays/btw-history-panel.d.ts +9 -0
  99. package/dist/types/overlays/btw-panel.d.ts +3 -0
  100. package/dist/types/overlays/cleanse-panel.d.ts +5 -2
  101. package/dist/types/overlays/composer-shape-preview.d.ts +7 -1
  102. package/dist/types/overlays/copy-selector.d.ts +38 -0
  103. package/dist/types/overlays/error-banner.d.ts +9 -0
  104. package/dist/types/overlays/extensions/extension-list.d.ts +1 -0
  105. package/dist/types/overlays/history-search.d.ts +18 -0
  106. package/dist/types/overlays/hook-editor.d.ts +28 -1
  107. package/dist/types/overlays/hook-input.d.ts +3 -0
  108. package/dist/types/overlays/hook-selector.d.ts +10 -0
  109. package/dist/types/overlays/hub-frame.d.ts +18 -0
  110. package/dist/types/overlays/jobs-panel.d.ts +24 -0
  111. package/dist/types/overlays/login-dialog.d.ts +10 -0
  112. package/dist/types/overlays/logout-account-selector.d.ts +4 -0
  113. package/dist/types/overlays/mcp-add-wizard.d.ts +4 -0
  114. package/dist/types/overlays/model-browser.d.ts +69 -0
  115. package/dist/types/overlays/model-hub.d.ts +7 -0
  116. package/dist/types/overlays/model-picker.d.ts +9 -1
  117. package/dist/types/overlays/move-overlay.d.ts +3 -0
  118. package/dist/types/overlays/oauth-selector.d.ts +4 -0
  119. package/dist/types/overlays/omfg-panel.d.ts +3 -0
  120. package/dist/types/overlays/pause-screen.d.ts +12 -0
  121. package/dist/types/overlays/plan-review-overlay.d.ts +5 -0
  122. package/dist/types/overlays/plan-save-overlay.d.ts +10 -0
  123. package/dist/types/overlays/plugin-selector.d.ts +4 -0
  124. package/dist/types/overlays/plugin-settings.d.ts +30 -0
  125. package/dist/types/overlays/queue-mode-selector.d.ts +4 -0
  126. package/dist/types/overlays/reset-usage-selector.d.ts +8 -0
  127. package/dist/types/overlays/rewind-selector.d.ts +5 -0
  128. package/dist/types/overlays/session-info-overlay.d.ts +19 -1
  129. package/dist/types/overlays/session-selector.d.ts +64 -1
  130. package/dist/types/overlays/settings-defs.d.ts +14 -0
  131. package/dist/types/overlays/settings-selector.d.ts +21 -0
  132. package/dist/types/overlays/show-images-selector.d.ts +4 -0
  133. package/dist/types/overlays/snapcompact-shape-preview.d.ts +2 -0
  134. package/dist/types/overlays/stats-notice.d.ts +8 -0
  135. package/dist/types/overlays/theme-selector.d.ts +11 -0
  136. package/dist/types/overlays/thinking-selector.d.ts +4 -0
  137. package/dist/types/overlays/tree-selector.d.ts +60 -2
  138. package/dist/types/overlays/usage-dashboard.d.ts +26 -3
  139. package/dist/types/overlays/usage-row.d.ts +36 -2
  140. package/dist/types/prompt/attachment-chips.d.ts +8 -0
  141. package/dist/types/prompt/composer-attachments.d.ts +15 -3
  142. package/dist/types/prompt/composer.d.ts +18 -1
  143. package/dist/types/prompt/custom-editor.d.ts +48 -2
  144. package/dist/types/prompt/editor-top-gap.d.ts +2 -0
  145. package/dist/types/prompt/macos-spelling.d.ts +6 -0
  146. package/dist/types/prompt/magic-keywords.d.ts +9 -0
  147. package/dist/types/prompt/queued-messages.d.ts +23 -0
  148. package/dist/types/prompt/skill-tokens.d.ts +4 -2
  149. package/dist/types/prompt/welcome.d.ts +16 -0
  150. package/dist/types/render/code-cell.d.ts +10 -0
  151. package/dist/types/render/file-list.d.ts +8 -0
  152. package/dist/types/render/hyperlink.d.ts +14 -0
  153. package/dist/types/render/output-block.d.ts +43 -0
  154. package/dist/types/render/output-pane.d.ts +17 -0
  155. package/dist/types/render/tool-card.d.ts +8 -0
  156. package/dist/types/render/tree-list.d.ts +8 -0
  157. package/dist/types/render/width-aware-text.d.ts +7 -0
  158. package/dist/types/setup/scenes/outro.d.ts +7 -0
  159. package/dist/types/setup/scenes/sign-in.d.ts +16 -9
  160. package/dist/types/setup/scenes/splash.d.ts +8 -0
  161. package/dist/types/setup/scenes/types.d.ts +6 -27
  162. package/dist/types/setup/wizard-overlay.d.ts +9 -0
  163. package/dist/types/status-line/component.d.ts +49 -5
  164. package/dist/types/status-line/context-usage.d.ts +20 -0
  165. package/dist/types/status-line/footer.d.ts +8 -0
  166. package/dist/types/status-line/metrics.d.ts +1 -2
  167. package/dist/types/status-line/segments.d.ts +10 -1
  168. package/dist/types/status-line/types.d.ts +40 -1
  169. package/dist/types/stdin-buffer.d.ts +10 -7
  170. package/dist/types/terminal-capabilities.d.ts +1 -1
  171. package/dist/types/terminal.d.ts +36 -0
  172. package/dist/types/theme/defaults/index.d.ts +100 -0
  173. package/dist/types/theme/schema.d.ts +2 -0
  174. package/dist/types/theme/shimmer.d.ts +8 -0
  175. package/dist/types/theme/theme-class.d.ts +2 -0
  176. package/dist/types/theme/theme.d.ts +33 -1
  177. package/dist/types/tools/ask.d.ts +3 -0
  178. package/dist/types/tools/ast-edit.d.ts +10 -1
  179. package/dist/types/tools/ast-grep.d.ts +10 -1
  180. package/dist/types/tools/autoresearch.d.ts +21 -4
  181. package/dist/types/tools/bash.d.ts +30 -1
  182. package/dist/types/tools/cfg-render.d.ts +5 -1
  183. package/dist/types/tools/debug.d.ts +3 -1
  184. package/dist/types/tools/default-renderer.d.ts +6 -1
  185. package/dist/types/tools/edit.d.ts +14 -1
  186. package/dist/types/tools/eval.d.ts +12 -1
  187. package/dist/types/tools/fetch.d.ts +16 -1
  188. package/dist/types/tools/find.d.ts +15 -1
  189. package/dist/types/tools/github.d.ts +3 -0
  190. package/dist/types/tools/glob.d.ts +14 -1
  191. package/dist/types/tools/goal.d.ts +3 -1
  192. package/dist/types/tools/grep.d.ts +14 -1
  193. package/dist/types/tools/grouped-file-output.d.ts +58 -0
  194. package/dist/types/tools/json-tree.d.ts +20 -0
  195. package/dist/types/tools/lsp.d.ts +3 -10
  196. package/dist/types/tools/mcp.d.ts +16 -1
  197. package/dist/types/tools/memory.d.ts +26 -0
  198. package/dist/types/tools/native-view.d.ts +90 -0
  199. package/dist/types/tools/proc-render.d.ts +7 -1
  200. package/dist/types/tools/read.d.ts +23 -1
  201. package/dist/types/tools/renderer.d.ts +57 -0
  202. package/dist/types/tools/report-tool-issue.d.ts +3 -0
  203. package/dist/types/tools/resolve.d.ts +14 -1
  204. package/dist/types/tools/task.d.ts +3 -0
  205. package/dist/types/tools/think.d.ts +3 -0
  206. package/dist/types/tools/todo.d.ts +12 -0
  207. package/dist/types/tools/vibe.d.ts +3 -1
  208. package/dist/types/tools/wait.d.ts +3 -0
  209. package/dist/types/tools/web-search-types.d.ts +90 -0
  210. package/dist/types/tools/web-search.d.ts +9 -168
  211. package/dist/types/tools/write.d.ts +14 -1
  212. package/dist/types/tools/xdev.d.ts +13 -0
  213. package/dist/types/tui.d.ts +49 -0
  214. package/package.json +9 -9
  215. package/src/apps/autoresearch-dashboard.ts +362 -4
  216. package/src/apps/cleanse-board.ts +219 -1
  217. package/src/apps/cleanse-picker.ts +9 -1
  218. package/src/apps/debug/log-viewer.ts +15 -15
  219. package/src/apps/debug/protocol-probe.ts +2 -20
  220. package/src/apps/git/avatar.ts +2 -22
  221. package/src/apps/git/git-tui.ts +406 -59
  222. package/src/apps/git/help.ts +130 -0
  223. package/src/apps/git/sidebar.ts +491 -0
  224. package/src/apps/live-visualizer.ts +65 -0
  225. package/src/apps/ps-top.ts +448 -17
  226. package/src/apps/session-picker.ts +1 -0
  227. package/src/apps/setup-model-picker.ts +5 -1
  228. package/src/apps/standalone-picker.ts +55 -1
  229. package/src/autocomplete.ts +44 -1
  230. package/src/chat/advisor-message.ts +72 -3
  231. package/src/chat/assistant-message.ts +538 -51
  232. package/src/chat/background-tan-message.ts +4 -4
  233. package/src/chat/bash-execution.ts +81 -0
  234. package/src/chat/cache-invalidation-marker.ts +8 -0
  235. package/src/chat/chat-transcript-builder.ts +37 -6
  236. package/src/chat/collab-prompt-message.ts +15 -3
  237. package/src/chat/compaction-summary-message.ts +74 -1
  238. package/src/chat/custom-message.ts +3 -1
  239. package/src/chat/eval-execution.ts +57 -0
  240. package/src/chat/execution-shared.ts +153 -1
  241. package/src/chat/hook-message.ts +1 -0
  242. package/src/chat/late-diagnostics-message.ts +85 -2
  243. package/src/chat/read-tool-group.ts +341 -1
  244. package/src/chat/served-model-marker.ts +8 -0
  245. package/src/chat/skill-message.ts +63 -0
  246. package/src/chat/stripped-tool-calls-placeholder.ts +18 -10
  247. package/src/chat/thinking-display.ts +73 -11
  248. package/src/chat/todo-reminder.ts +34 -4
  249. package/src/chat/tool-execution.ts +385 -10
  250. package/src/chat/transcript-actions.ts +38 -0
  251. package/src/chat/transcript-browser.ts +2 -9
  252. package/src/chat/transcript-entry.ts +10 -1
  253. package/src/chat/transcript-render-helpers.ts +16 -15
  254. package/src/chat/ttsr-notification.ts +38 -1
  255. package/src/chat/user-message.ts +167 -4
  256. package/src/chrome/collab-qrcode.ts +27 -2
  257. package/src/chrome/context-thresholds.ts +34 -0
  258. package/src/chrome/countdown-timer.ts +24 -1
  259. package/src/chrome/diff.ts +60 -1
  260. package/src/chrome/dynamic-border.ts +7 -0
  261. package/src/chrome/format.ts +12 -0
  262. package/src/chrome/message-divider.ts +33 -1
  263. package/src/chrome/message-frame.ts +53 -10
  264. package/src/chrome/message-notice.ts +71 -0
  265. package/src/chrome/overlay-box.ts +45 -2
  266. package/src/chrome/qrcode.ts +28 -0
  267. package/src/chrome/segment-track.ts +26 -0
  268. package/src/chrome/selector-helpers.ts +1 -17
  269. package/src/chrome/status-notice.ts +46 -0
  270. package/src/chrome/streaming-panel.ts +25 -1
  271. package/src/chrome/tool-activity.ts +20 -0
  272. package/src/chrome/transcript-container.ts +139 -17
  273. package/src/chrome/transcript-status.ts +40 -11
  274. package/src/components/box.ts +51 -0
  275. package/src/components/disclosure.ts +33 -0
  276. package/src/components/editor.ts +239 -36
  277. package/src/components/form.ts +93 -9
  278. package/src/components/image.ts +30 -0
  279. package/src/components/input.ts +77 -26
  280. package/src/components/key-value-list.ts +18 -0
  281. package/src/components/layout/geometry.ts +44 -0
  282. package/src/components/layout/row.ts +30 -0
  283. package/src/components/layout/split-pane.ts +65 -0
  284. package/src/components/layout/stack.ts +27 -0
  285. package/src/components/loader.ts +183 -1
  286. package/src/components/markdown.ts +131 -56
  287. package/src/components/metric.ts +39 -0
  288. package/src/components/progress-bar.ts +28 -0
  289. package/src/components/scroll-view.ts +23 -0
  290. package/src/components/section.ts +16 -0
  291. package/src/components/select-list.ts +174 -25
  292. package/src/components/settings-list.ts +264 -7
  293. package/src/components/spacer.ts +17 -0
  294. package/src/components/tab-bar.ts +42 -0
  295. package/src/components/table.ts +34 -0
  296. package/src/components/text.ts +23 -0
  297. package/src/components/tree-view.ts +65 -0
  298. package/src/components/truncated-text.ts +14 -0
  299. package/src/components/wizard-step.ts +18 -9
  300. package/src/debug-server.ts +14 -7
  301. package/src/hotkeys-markdown.ts +251 -69
  302. package/src/index.ts +6 -0
  303. package/src/latex-block.ts +11 -0
  304. package/src/loop-watchdog.ts +38 -10
  305. package/src/native/apply.ts +275 -0
  306. package/src/native/backend.ts +682 -0
  307. package/src/native/blobs.ts +70 -0
  308. package/src/native/describe.ts +124 -0
  309. package/src/native/encode.ts +233 -0
  310. package/src/native/icons.ts +184 -0
  311. package/src/native/memo.ts +64 -0
  312. package/src/native/node.ts +112 -0
  313. package/src/native/overlay.ts +131 -0
  314. package/src/native/picker.ts +333 -0
  315. package/src/native/reconcile.ts +1063 -0
  316. package/src/native/settle.ts +23 -0
  317. package/src/native/spans.ts +308 -0
  318. package/src/native/state.ts +28 -0
  319. package/src/native/tone.ts +79 -0
  320. package/src/overlays/advisor-config.ts +420 -0
  321. package/src/overlays/agent-hub-renderer.ts +155 -18
  322. package/src/overlays/agent-hub.ts +1037 -44
  323. package/src/overlays/agent-transcript-viewer.ts +129 -0
  324. package/src/overlays/agents-hub.ts +688 -53
  325. package/src/overlays/annotation-overlay.ts +391 -52
  326. package/src/overlays/ask-dialog.ts +435 -24
  327. package/src/overlays/bordered-loader.ts +17 -0
  328. package/src/overlays/btw-history-panel.ts +182 -0
  329. package/src/overlays/btw-panel.ts +68 -2
  330. package/src/overlays/cleanse-panel.ts +105 -15
  331. package/src/overlays/codex-reset-fireworks.ts +65 -5
  332. package/src/overlays/composer-shape-preview.ts +26 -1
  333. package/src/overlays/copy-selector.ts +545 -23
  334. package/src/overlays/error-banner.ts +44 -0
  335. package/src/overlays/extensions/extension-list.ts +23 -17
  336. package/src/overlays/history-search.ts +175 -33
  337. package/src/overlays/hook-editor.ts +113 -6
  338. package/src/overlays/hook-input.ts +22 -1
  339. package/src/overlays/hook-selector.ts +311 -22
  340. package/src/overlays/hub-frame.ts +80 -0
  341. package/src/overlays/jobs-panel.ts +112 -0
  342. package/src/overlays/login-dialog.ts +183 -1
  343. package/src/overlays/logout-account-selector.ts +102 -4
  344. package/src/overlays/mcp-add-wizard.ts +232 -53
  345. package/src/overlays/model-browser.ts +681 -22
  346. package/src/overlays/model-hub.ts +1402 -78
  347. package/src/overlays/model-picker.ts +184 -7
  348. package/src/overlays/move-overlay.ts +45 -0
  349. package/src/overlays/oauth-selector.ts +227 -23
  350. package/src/overlays/omfg-panel.ts +86 -2
  351. package/src/overlays/pause-screen.ts +55 -4
  352. package/src/overlays/plan-review-overlay.ts +352 -0
  353. package/src/overlays/plan-save-overlay.ts +45 -0
  354. package/src/overlays/plugin-selector.ts +21 -1
  355. package/src/overlays/plugin-settings.ts +195 -5
  356. package/src/overlays/queue-mode-selector.ts +19 -1
  357. package/src/overlays/reset-usage-selector.ts +110 -40
  358. package/src/overlays/rewind-selector.ts +458 -44
  359. package/src/overlays/session-account-selector.ts +1 -1
  360. package/src/overlays/session-info-overlay.ts +137 -1
  361. package/src/overlays/session-selector.ts +807 -46
  362. package/src/overlays/settings-defs.ts +41 -0
  363. package/src/overlays/settings-selector.ts +585 -33
  364. package/src/overlays/show-images-selector.ts +18 -1
  365. package/src/overlays/snapcompact-shape-preview.ts +48 -3
  366. package/src/overlays/stats-notice.ts +31 -0
  367. package/src/overlays/theme-selector.ts +31 -1
  368. package/src/overlays/thinking-selector.ts +22 -1
  369. package/src/overlays/tree-selector.ts +570 -110
  370. package/src/overlays/usage-dashboard.ts +587 -12
  371. package/src/overlays/usage-row.ts +161 -25
  372. package/src/prompt/attachment-chips.ts +60 -0
  373. package/src/prompt/composer-attachments.ts +31 -9
  374. package/src/prompt/composer.ts +55 -2
  375. package/src/prompt/custom-editor.ts +302 -4
  376. package/src/prompt/editor-top-gap.ts +8 -0
  377. package/src/prompt/macos-spelling.ts +24 -9
  378. package/src/prompt/magic-keywords.ts +22 -1
  379. package/src/prompt/model-mention-autocomplete.ts +1 -0
  380. package/src/prompt/queued-messages.ts +101 -0
  381. package/src/prompt/skill-tokens.ts +5 -3
  382. package/src/prompt/welcome.ts +213 -6
  383. package/src/render/code-cell.ts +103 -2
  384. package/src/render/file-list.ts +29 -0
  385. package/src/render/hyperlink.ts +32 -0
  386. package/src/render/output-block.ts +110 -0
  387. package/src/render/output-pane.ts +37 -0
  388. package/src/render/tool-card.ts +137 -0
  389. package/src/render/tree-list.ts +39 -0
  390. package/src/render/width-aware-text.ts +20 -0
  391. package/src/setup/scenes/composer.ts +29 -0
  392. package/src/setup/scenes/glyph.ts +15 -0
  393. package/src/setup/scenes/model.ts +36 -8
  394. package/src/setup/scenes/outro.ts +28 -1
  395. package/src/setup/scenes/sign-in.ts +138 -24
  396. package/src/setup/scenes/splash.ts +28 -1
  397. package/src/setup/scenes/theme.ts +88 -3
  398. package/src/setup/scenes/types.ts +7 -28
  399. package/src/setup/startup-splash.ts +46 -9
  400. package/src/setup/wizard-overlay.ts +140 -23
  401. package/src/setup/wizard.ts +1 -1
  402. package/src/status-line/component.ts +414 -18
  403. package/src/status-line/context-usage.ts +279 -50
  404. package/src/status-line/footer.ts +94 -1
  405. package/src/status-line/metrics.ts +3 -6
  406. package/src/status-line/segments.ts +463 -97
  407. package/src/status-line/separators.ts +3 -16
  408. package/src/status-line/types.ts +43 -1
  409. package/src/stdin-buffer.ts +27 -21
  410. package/src/terminal-capabilities.ts +7 -0
  411. package/src/terminal.ts +153 -11
  412. package/src/theme/defaults/dark-neon-noir.json +100 -0
  413. package/src/theme/defaults/index.ts +2 -0
  414. package/src/theme/defaults/light-prism.json +5 -5
  415. package/src/theme/defaults/onyx.json +62 -62
  416. package/src/theme/mermaid-cache.ts +10 -1
  417. package/src/theme/schema.ts +15 -0
  418. package/src/theme/shimmer.ts +33 -0
  419. package/src/theme/theme-class.ts +24 -19
  420. package/src/theme/theme.ts +111 -9
  421. package/src/theme/tui-adapters.ts +14 -1
  422. package/src/tools/ask.ts +157 -1
  423. package/src/tools/ast-edit.ts +174 -1
  424. package/src/tools/ast-grep.ts +82 -2
  425. package/src/tools/autoresearch.ts +149 -1
  426. package/src/tools/bash.ts +233 -62
  427. package/src/tools/cfg-render.ts +66 -1
  428. package/src/tools/debug.ts +78 -1
  429. package/src/tools/default-renderer.ts +35 -1
  430. package/src/tools/edit.ts +357 -31
  431. package/src/tools/eval.ts +257 -1
  432. package/src/tools/fetch.ts +102 -3
  433. package/src/tools/find.ts +127 -1
  434. package/src/tools/github.ts +119 -10
  435. package/src/tools/glob.ts +116 -1
  436. package/src/tools/goal.ts +56 -1
  437. package/src/tools/grep.ts +83 -2
  438. package/src/tools/grouped-file-output.ts +205 -0
  439. package/src/tools/json-tree.ts +116 -1
  440. package/src/tools/lsp.ts +318 -1
  441. package/src/tools/mcp.ts +80 -1
  442. package/src/tools/memory.ts +93 -11
  443. package/src/tools/native-view.ts +292 -0
  444. package/src/tools/proc-render.ts +221 -1
  445. package/src/tools/read.ts +199 -4
  446. package/src/tools/renderer.ts +61 -0
  447. package/src/tools/report-tool-issue.ts +7 -1
  448. package/src/tools/resolve.ts +63 -1
  449. package/src/tools/streaming-output.ts +79 -16
  450. package/src/tools/task.ts +406 -4
  451. package/src/tools/think.ts +19 -1
  452. package/src/tools/todo.ts +89 -0
  453. package/src/tools/vibe.ts +230 -2
  454. package/src/tools/wait.ts +181 -1
  455. package/src/tools/web-search-types.ts +99 -0
  456. package/src/tools/web-search.ts +111 -170
  457. package/src/tools/write.ts +144 -5
  458. package/src/tools/xdev.ts +64 -1
  459. package/src/tui.ts +295 -4
  460. package/dist/types/setup/scenes/providers.d.ts +0 -3
  461. package/dist/types/setup/scenes/web-search.d.ts +0 -22
  462. package/src/setup/scenes/providers.ts +0 -110
  463. package/src/setup/scenes/web-search.ts +0 -165
@@ -14,11 +14,20 @@
14
14
  * Replaces the old SessionObserverOverlayComponent (ctrl+s observer).
15
15
  */
16
16
  import type { AgentTool } from "@oh-my-pi/pi-agent-core";
17
+ import type {
18
+ TspPickerAction,
19
+ TspPickerColumn,
20
+ TspPickerItem,
21
+ TspPickerProps,
22
+ TspPickerScope,
23
+ TspSpan,
24
+ TspTreeNode,
25
+ } from "@oh-my-pi/pi-wire";
17
26
  import { Container, type OverlayHandle, type TUI } from "../tui";
18
27
  import { matchesKey } from "../keys";
19
28
  import { routeSelectListMouse, routeSgrMouseInput, type SelectListMouseTarget } from "../mouse";
20
29
  import { padding, visibleWidth, wrapTextWithAnsi } from "../utils";
21
- import { formatAge, formatNumber, getProjectDir, logger } from "@oh-my-pi/pi-utils";
30
+ import { formatAge, formatDuration, formatNumber, getProjectDir, logger } from "@oh-my-pi/pi-utils";
22
31
  import {
23
32
  type AgentActivitySource,
24
33
  type AgentActivityKind,
@@ -32,6 +41,7 @@ import { type AgentRecordLike, type AgentHubRegistry, type AgentStatus, MAIN_AGE
32
41
  import { USER_INTERRUPT_LABEL } from "../chat/messages";
33
42
  import { shortenPath, truncateToWidth } from "../render/render-utils";
34
43
  import { formatLocalDateTimeWithOffset } from "../chrome/local-date";
44
+ import { getContextUsageLevel, getContextUsageTone } from "../chrome/context-thresholds";
35
45
  import type { ObservableSession, SessionObserverRegistry } from "./session-observer-registry";
36
46
  import { theme } from "../theme/theme";
37
47
  import { matchesSelectDown, matchesSelectUp } from "../keybinding-matchers";
@@ -52,11 +62,19 @@ import {
52
62
  formatMetricDuration,
53
63
  formatMetrics,
54
64
  formatRoleBadge,
65
+ metricsText,
55
66
  modelBadge,
67
+ modelBadgeSpans,
68
+ modelChip,
69
+ roleBadgeSpan,
56
70
  type RosterRender,
57
71
  sanitizeLine,
58
72
  statusGlyph,
73
+ statusDot,
74
+ statusGlyphSpan,
59
75
  statusText,
76
+ statusTone,
77
+ taskSummary,
60
78
  treeBranch,
61
79
  treeContinuation,
62
80
  treeMetadataIndent,
@@ -68,6 +86,23 @@ import { fuzzyMatch } from "../fuzzy";
68
86
  import { bottomBorder, divider, dividerSplit, PanelRows, row, topBorder, topBorderSplit } from "../chrome/overlay-box";
69
87
  import { SplitPane } from "../components/layout/split-pane";
70
88
  import { Stack } from "../components/layout/stack";
89
+ import { node, span, stableKey, text } from "../native/describe";
90
+ import { type DescribeContext, leafKey, type NativeChild, type NativeNode, type NativeUiEvent } from "../native/node";
91
+ import { hintsRow, type NativeHint, overlayCard } from "../native/overlay";
92
+ import { CLOSE_ACTION, picker, pickerQuery } from "../native/picker";
93
+ import { Input } from "../components/input";
94
+
95
+ /** A chrome-less search field: the hub draws its own prompt. */
96
+ function filterInput(): Input {
97
+ const input = new Input();
98
+ input.prompt = "";
99
+ return input;
100
+ }
101
+
102
+ /** The field's text with its caret drawn, sized to the value (no trailing padding). */
103
+ function filterText(input: Input): string {
104
+ return input.render(visibleWidth(input.getValue()) + 1)[0] ?? "";
105
+ }
71
106
 
72
107
  /** Two-pane mode needs a useful roster and a readable inspector. */
73
108
  const SPLIT_MIN_WIDTH = 96;
@@ -80,6 +115,84 @@ type ActivityScope = "all" | "agent" | "subtree";
80
115
 
81
116
  type HubViewMode = "roster" | "tree";
82
117
 
118
+ const SECTION_TABS = [
119
+ { id: "agents", label: "1 Agents" },
120
+ { id: "activity", label: "2 Activity" },
121
+ ] as const satisfies ReadonlyArray<{ id: AgentHubSection; label: string }>;
122
+ const VIEW_TABS = [
123
+ { id: "roster", label: "Flat" },
124
+ { id: "tree", label: "By parent" },
125
+ ] as const satisfies ReadonlyArray<{ id: HubViewMode; label: string }>;
126
+ const AGENT_HINTS: Readonly<Record<HubViewMode, readonly NativeHint[]>> = {
127
+ roster: agentHints("by parent"),
128
+ tree: agentHints("flat"),
129
+ };
130
+ const ACTIVITY_HINTS: readonly NativeHint[] = [
131
+ { keys: ["j", "k"], label: "select" },
132
+ { keys: ["enter"], label: "transcript" },
133
+ { keys: ["space"], label: "follow" },
134
+ { keys: ["f"], label: "filter" },
135
+ { keys: ["s"], label: "scope" },
136
+ { keys: ["/"], label: "search" },
137
+ { keys: ["escape"], label: "close" },
138
+ ];
139
+ /** Recent-activity entries the native inspector lists for the selected agent. */
140
+ const NATIVE_RECENT_ACTIVITY = 20;
141
+ /** Child ids the native inspector names before summarizing the rest. */
142
+ const NATIVE_CHILD_IDS = 12;
143
+
144
+ /** Picker tabs: the two top-level projections (keys 1/2). */
145
+ const PICKER_TABS = [
146
+ { id: "agents", label: "Agents" },
147
+ { id: "activity", label: "Activity" },
148
+ ] as const satisfies ReadonlyArray<{ id: AgentHubSection; label: string }>;
149
+ /** Roster fact columns; lower priorities hide first on narrow sheets. */
150
+ const AGENT_COLUMNS: readonly TspPickerColumn[] = [
151
+ { id: "cost", head: "Cost", format: "price", priority: 6 },
152
+ { id: "time", head: "Time", format: "elapsed", priority: 5 },
153
+ { id: "req", head: "Req", format: "num", priority: 2 },
154
+ { id: "tools", head: "Tools", format: "num", priority: 1 },
155
+ { id: "tok", head: "Tok", format: "num", priority: 3 },
156
+ { id: "ctx", head: "Ctx", format: "bar", priority: 4 },
157
+ ];
158
+ const ACTIVITY_COLUMNS: readonly TspPickerColumn[] = [
159
+ { id: "agent", head: "Agent", format: "text", priority: 2 },
160
+ { id: "at", head: "Time", format: "dim", priority: 1 },
161
+ ];
162
+ /** What the roster/activity projections contribute to the picker (the shared head is added once). */
163
+ type PickerBody = Omit<Partial<TspPickerProps>, "title">;
164
+ const ACTIVITY_FILTERS: readonly ActivityFilter[] = ["all", "errors", "responses", "tools"];
165
+ /** Activity filters as the picker's scope column (`f` cycles them). */
166
+ const ACTIVITY_FILTER_SCOPES: readonly TspPickerScope[] = [
167
+ { id: "all", label: "All", icon: "list", group: "Show" },
168
+ { id: "errors", label: "Errors", icon: "x-circle", group: "Show" },
169
+ { id: "responses", label: "Responses", icon: "message", group: "Show" },
170
+ { id: "tools", label: "Tools", icon: "wrench", group: "Show" },
171
+ ];
172
+ const ACTIVITY_KIND_ICON: Readonly<Record<AgentActivityKind, string>> = {
173
+ response: "message",
174
+ tool: "wrench",
175
+ irc: "send",
176
+ lifecycle: "activity",
177
+ };
178
+
179
+ function agentHints(nextView: string): readonly NativeHint[] {
180
+ return [
181
+ { keys: ["j", "k"], label: "select" },
182
+ { keys: ["enter"], label: "open" },
183
+ { keys: ["t"], label: nextView },
184
+ { keys: ["/"], label: "filter" },
185
+ { keys: ["r"], label: "revive" },
186
+ { keys: ["x"], label: "kill" },
187
+ { keys: ["escape"], label: "close" },
188
+ ];
189
+ }
190
+
191
+ /** A tree node whose children are still being attached while the roster projects. */
192
+ interface AgentTreeEntry extends TspTreeNode {
193
+ children?: AgentTreeEntry[];
194
+ }
195
+
83
196
  /** Refresh cadence for the relative-time column. */
84
197
  const AGE_TICK_MS = 5_000;
85
198
  const DATA_CHANGE_RENDER_COALESCE_MS = 100;
@@ -102,6 +215,23 @@ function activityGlyph(row: AgentActivityRow): string {
102
215
  }
103
216
  }
104
217
 
218
+ /** Native span of {@link activityGlyph}. */
219
+ function activityGlyphSpan(row: AgentActivityRow): TspSpan {
220
+ if (row.status === "error") return span(theme.status.error, "error");
221
+ if (row.status === "aborted") return span(theme.status.aborted, "warning");
222
+ if (row.status === "pending") return span(theme.status.running, "accent");
223
+ switch (row.kind) {
224
+ case "response":
225
+ return span("◆", "success");
226
+ case "tool":
227
+ return span(theme.status.success, "success");
228
+ case "irc":
229
+ return span("→", "accent");
230
+ case "lifecycle":
231
+ return span("○", "muted");
232
+ }
233
+ }
234
+
105
235
  const ACTIVITY_CLOCK_FORMAT = new Intl.DateTimeFormat(undefined, {
106
236
  hour: "2-digit",
107
237
  minute: "2-digit",
@@ -215,7 +345,8 @@ export class AgentHubOverlayComponent<TRecord extends AgentRecordLike = AgentRec
215
345
  #selectedActivityRow = 0;
216
346
  #activityFilter: ActivityFilter = "all";
217
347
  #activityScope: ActivityScope = "all";
218
- #activitySearch = "";
348
+ /** Activity search (`/`); shown while editing or non-empty. */
349
+ readonly #activitySearch = filterInput();
219
350
  #activitySearchEditing = false;
220
351
  #activityFollow = true;
221
352
  #activitySyncGeneration = 0;
@@ -243,7 +374,7 @@ export class AgentHubOverlayComponent<TRecord extends AgentRecordLike = AgentRec
243
374
  #treeLastSiblingById = new Map<string, boolean>();
244
375
  #treeMaxDepth = 0;
245
376
  /** Fuzzy agent filter (`/`), applied to id and display name. */
246
- #agentFilter = "";
377
+ readonly #agentFilter = filterInput();
247
378
  #agentFilterEditing = false;
248
379
  /** Current observer index and summary data, rebuilt on source changes rather than every paint. */
249
380
  #observedById = new Map<string, ObservableSession>();
@@ -320,6 +451,13 @@ export class AgentHubOverlayComponent<TRecord extends AgentRecordLike = AgentRec
320
451
  #transcriptOverlay: OverlayHandle | undefined;
321
452
  #transcriptViewer: AgentTranscriptViewer | undefined;
322
453
 
454
+ /** Last native description; every visible state change goes through requestRender, which drops it. */
455
+ #native: NativeNode | undefined;
456
+ /** Which terminal kinds {@link #native} was described for (support may change between frames). */
457
+ #nativeVariant = "";
458
+ /** Preview recent-activity rows by node key, for their pointer actions. */
459
+ #recentByKey = new Map<string, AgentActivityRow>();
460
+
323
461
  constructor(deps: AgentHubDeps<TRecord>) {
324
462
  super();
325
463
  this.#section = deps.initialSection ?? "agents";
@@ -332,7 +470,10 @@ export class AgentHubOverlayComponent<TRecord extends AgentRecordLike = AgentRec
332
470
  this.#irc = deps.irc;
333
471
  this.#lifecycle = deps.lifecycle;
334
472
  this.#onDone = deps.onDone;
335
- this.#requestRender = deps.requestRender;
473
+ this.#requestRender = () => {
474
+ this.#native = undefined;
475
+ deps.requestRender();
476
+ };
336
477
  this.#hubKeys = deps.hubKeys;
337
478
  this.#remote = deps.remote;
338
479
  this.#loadingPersistedSubagents = !this.#remote && Boolean(deps.sessionFile?.endsWith(".jsonl"));
@@ -529,6 +670,827 @@ export class AgentHubOverlayComponent<TRecord extends AgentRecordLike = AgentRec
529
670
  }
530
671
  }
531
672
 
673
+ // ========================================================================
674
+ // Native description
675
+ // ========================================================================
676
+
677
+ /** A `picker` is its own sheet: the backend mounts it in `layer` without an `overlay` wrapper. */
678
+ nativeSheet(cx: DescribeContext): boolean {
679
+ return cx.supports("picker");
680
+ }
681
+
682
+ override describe(cx: DescribeContext): NativeNode {
683
+ const usePicker = cx.supports("picker");
684
+ const nativeTree = cx.supports("tree");
685
+ const meter = cx.supports("meter");
686
+ const variant = usePicker ? `picker:${meter}` : nativeTree ? "tree" : "list";
687
+ if (this.#native && this.#nativeVariant === variant) return this.#native;
688
+ this.#nativeVariant = variant;
689
+ if (usePicker) {
690
+ this.#native = this.#describePicker(meter);
691
+ return this.#native;
692
+ }
693
+ const tabs = node("tabs", { items: SECTION_TABS, active: this.#section }, undefined, "section");
694
+ const body = this.#section === "activity" ? this.#describeActivity() : this.#describeAgents(nativeTree);
695
+ this.#native = overlayCard("omp.overlay.agentHub", "Agent Hub", [tabs, ...body]);
696
+ return this.#native;
697
+ }
698
+
699
+ /**
700
+ * Pointer actions on the described hub. `select` mirrors keyboard selection
701
+ * (roster row, activity row, section tab, projection tab); `activate` does
702
+ * what Enter does on that row. Picker actions run the key handlers' paths.
703
+ */
704
+ handleNativeEvent(event: NativeUiEvent): void {
705
+ if (event.key === "") {
706
+ this.#handlePickerEvent(event);
707
+ return;
708
+ }
709
+ if (event.type === "action") {
710
+ const activity = this.#recentByKey.get(leafKey(event.key));
711
+ if (event.act === "transcript" && activity) this.openChat(activity.agentId, activity.entryId);
712
+ return;
713
+ }
714
+ if (event.type !== "select" && event.type !== "activate") return;
715
+ const target = leafKey(event.key);
716
+ const item = event.item;
717
+ switch (target) {
718
+ case "section":
719
+ if (item === "agents" || item === "activity") this.#switchSection(item);
720
+ return;
721
+ case "view":
722
+ if ((item === "roster" || item === "tree") && item !== this.#viewMode) {
723
+ this.#hoveredRow = null;
724
+ this.#viewMode = item;
725
+ this.#refreshRows();
726
+ this.#requestRender();
727
+ }
728
+ return;
729
+ case "agents": {
730
+ const index = this.#rows.findIndex(ref => ref.id === item);
731
+ const ref = this.#rows[index];
732
+ if (!ref) return;
733
+ this.#hoveredRow = null;
734
+ this.#selectRow(index);
735
+ this.#requestRender();
736
+ if (event.type === "activate") this.#activateAgent(ref);
737
+ return;
738
+ }
739
+ case "activity": {
740
+ const index = this.#activityRows.findIndex(row => row.id === item);
741
+ const activity = this.#activityRows[index];
742
+ if (!activity) return;
743
+ if (event.type === "activate") {
744
+ this.openChat(activity.agentId, activity.entryId);
745
+ return;
746
+ }
747
+ this.#activityFollow = false;
748
+ this.#selectedActivityRow = index;
749
+ this.#requestRender();
750
+ return;
751
+ }
752
+ case "recent": {
753
+ const agentId = this.#rows[this.#selectedRow]?.id;
754
+ if (event.type !== "activate" || !agentId) return;
755
+ const activity = this.#activity.recent(agentId, NATIVE_RECENT_ACTIVITY).find(row => row.id === item);
756
+ if (activity) this.openChat(activity.agentId, activity.entryId);
757
+ return;
758
+ }
759
+ }
760
+ }
761
+
762
+ /** Picker events (the sheet is this component's root): same paths as the keys. */
763
+ #handlePickerEvent(event: NativeUiEvent): void {
764
+ const activity = this.#section === "activity";
765
+ if (event.type === "select" || event.type === "activate") {
766
+ if (activity) {
767
+ const index = this.#activityRows.findIndex(row => row.id === event.item);
768
+ const row = this.#activityRows[index];
769
+ if (!row) return;
770
+ if (event.type === "activate") {
771
+ this.openChat(row.agentId, row.entryId);
772
+ return;
773
+ }
774
+ this.#activityFollow = false;
775
+ this.#selectedActivityRow = index;
776
+ this.#requestRender();
777
+ return;
778
+ }
779
+ const index = this.#rows.findIndex(ref => ref.id === event.item);
780
+ const ref = this.#rows[index];
781
+ if (!ref) return;
782
+ this.#hoveredRow = null;
783
+ this.#selectRow(index);
784
+ this.#requestRender();
785
+ if (event.type === "activate") this.#activateAgent(ref);
786
+ return;
787
+ }
788
+ if (event.type !== "action") return;
789
+ switch (event.act) {
790
+ case "tab":
791
+ if (event.value === "agents" || event.value === "activity") this.#switchSection(event.value);
792
+ return;
793
+ case "scope":
794
+ if (activity && ACTIVITY_FILTERS.includes(event.value as ActivityFilter)) {
795
+ this.#setActivityFilter(event.value as ActivityFilter);
796
+ }
797
+ return;
798
+ case "close":
799
+ this.#onDone();
800
+ return;
801
+ case "filter":
802
+ if (activity) this.#activitySearchEditing = true;
803
+ else this.#agentFilterEditing = true;
804
+ this.#requestRender();
805
+ return;
806
+ }
807
+ if (activity) {
808
+ switch (event.act) {
809
+ case "open": {
810
+ const row = this.#activityRows[this.#selectedActivityRow];
811
+ if (row) this.openChat(row.agentId, row.entryId);
812
+ return;
813
+ }
814
+ case "follow":
815
+ this.#toggleActivityFollow();
816
+ return;
817
+ case "agents":
818
+ this.#cycleActivityScope();
819
+ return;
820
+ }
821
+ return;
822
+ }
823
+ switch (event.act) {
824
+ case "open": {
825
+ const ref = this.#rows[this.#selectedRow];
826
+ if (ref) this.#activateAgent(ref);
827
+ return;
828
+ }
829
+ case "revive":
830
+ this.#reviveSelected();
831
+ return;
832
+ case "kill":
833
+ this.#killSelected();
834
+ return;
835
+ case "view":
836
+ this.#toggleViewMode();
837
+ return;
838
+ }
839
+ }
840
+
841
+ /** The hub as a data-first `picker` (§9.1): roster or activity log, selected agent as preview. */
842
+ #describePicker(meter: boolean): NativeNode {
843
+ const activity = this.#section === "activity";
844
+ const props: TspPickerProps = {
845
+ title: "Agents",
846
+ subtitle: this.#pickerSubtitle(),
847
+ icon: "users",
848
+ size: "lg",
849
+ tabs: PICKER_TABS,
850
+ tab: this.#section,
851
+ ...(activity ? this.#activityPickerProps() : this.#agentPickerProps()),
852
+ };
853
+ const preview = activity ? [] : this.#describePreview(this.#rows[this.#selectedRow], meter);
854
+ return picker(props, preview);
855
+ }
856
+
857
+ /** Roster totals: `$0.389 · 18.7s · 6 req · 114K tok`. */
858
+ #pickerSubtitle(): string {
859
+ const metrics = this.#aggregate;
860
+ if (metrics.reportedAgents === 0) return "No usage reported yet";
861
+ const parts = [formatCost(metrics.cost)];
862
+ if (metrics.durationMs > 0) parts.push(formatDuration(metrics.durationMs));
863
+ parts.push(`${formatNumber(metrics.requests)} req`, `${formatNumber(metrics.tokens)} tok`);
864
+ return parts.join(theme.sep.dot);
865
+ }
866
+
867
+ #agentPickerProps(): PickerBody {
868
+ const selected = this.#rows[this.#selectedRow];
869
+ const readOnly = selected?.kind === "advisor" ? "Read-only advisor transcript" : undefined;
870
+ const actions: TspPickerAction[] = [
871
+ {
872
+ id: "open",
873
+ label: "Open transcript",
874
+ keys: ["enter"],
875
+ primary: true,
876
+ disabled: selected ? undefined : true,
877
+ },
878
+ {
879
+ id: "revive",
880
+ label: "Revive",
881
+ keys: ["r"],
882
+ disabled: readOnly ?? (selected?.status === "parked" ? undefined : "Only parked agents can be revived"),
883
+ },
884
+ { id: "kill", label: "Kill", keys: ["x"], danger: true, disabled: readOnly ?? (selected ? undefined : true) },
885
+ { id: "view", label: "By parent", keys: ["t"], on: this.#viewMode === "tree" },
886
+ { id: "filter", label: "Filter", keys: ["/"] },
887
+ CLOSE_ACTION,
888
+ ];
889
+ return {
890
+ noun: "agents",
891
+ layout: this.#viewMode === "tree" ? "tree" : "rows",
892
+ preview: "side",
893
+ ...pickerQuery(this.#agentFilterEditing || this.#agentFilter.getValue() ? this.#agentFilter : null),
894
+ placeholder: "Filter agents…",
895
+ columns: AGENT_COLUMNS,
896
+ items: this.#rows.map(ref => this.#pickerAgentItem(ref)),
897
+ selected: selected?.id ?? null,
898
+ total: this.#registry.list().length - (this.#registry.get(MAIN_AGENT_ID) ? 1 : 0),
899
+ state: this.#rows.length === 0 && this.#loadingPersistedSubagents ? "loading" : "ready",
900
+ empty: "No agents in this session. Finished, parked and killed subagents stay with the session that created them.",
901
+ actions,
902
+ focus: "list",
903
+ };
904
+ }
905
+
906
+ #pickerAgentItem(ref: TRecord): TspPickerItem {
907
+ const observed = this.#observableFor(ref.id);
908
+ const metrics = this.#metricsFor(ref, observed);
909
+ const task = observed?.description ?? observed?.progress?.task ?? ref.activity;
910
+ const badges: { text: string; tone?: "warning" | "muted"; title?: string }[] = [];
911
+ const modelRole = observed?.progress?.modelRole ?? ref.history?.modelRole;
912
+ if (modelRole && this.#getRoleInfo) {
913
+ const info = this.#getRoleInfo(modelRole);
914
+ badges.push({ text: sanitizeDisplaySingleLine(info.tag ?? info.name ?? modelRole), title: "Model role" });
915
+ }
916
+ if (ref.kind === "advisor") badges.push({ text: "read-only", tone: "warning" });
917
+ const unread = this.#irc.unreadCount(ref.id);
918
+ if (unread > 0) badges.push({ text: `${unread} unread`, tone: "warning" });
919
+ if (this.#viewMode === "roster" && ref.parentId && ref.parentId !== MAIN_AGENT_ID) {
920
+ badges.push({ text: `↳ ${sanitizeDisplaySingleLine(ref.parentId)}`, tone: "muted", title: "Spawned by" });
921
+ }
922
+ const model = modelChip(ref, observed);
923
+ const item: TspPickerItem = {
924
+ id: ref.id,
925
+ label: sanitizeDisplaySingleLine(ref.id),
926
+ mono: true,
927
+ dot: statusDot(ref.status),
928
+ detail: task ? taskSummary(task) : undefined,
929
+ chips: model
930
+ ? [{ text: model.fallback ? `fallback → ${model.text}` : model.text, dot: model.dot }]
931
+ : undefined,
932
+ badges: badges.length > 0 ? badges.slice(0, 3) : undefined,
933
+ };
934
+ if (this.#viewMode === "tree") {
935
+ const depth = this.#treeDepthById.get(ref.id) ?? 0;
936
+ return { ...item, depth, open: this.#childrenByParent.has(ref.id) ? true : undefined };
937
+ }
938
+ if (!metrics) return item;
939
+ const facts: Record<string, string | number> = {
940
+ cost: formatCost(metrics.cost),
941
+ // A running agent's active time ticks terminal-side; a settled one is frozen text.
942
+ time:
943
+ ref.status === "running" && metrics.durationMs > 0
944
+ ? metrics.durationMs
945
+ : formatDuration(metrics.durationMs),
946
+ req: metrics.requests,
947
+ tools: metrics.tools,
948
+ tok: metrics.tokens,
949
+ };
950
+ let title: string | undefined;
951
+ if (metrics.contextTokens !== undefined && metrics.contextWindow) {
952
+ facts.ctx = Math.max(0, Math.min(1, metrics.contextTokens / metrics.contextWindow));
953
+ title = `Context ${formatNumber(metrics.contextTokens)} / ${formatNumber(metrics.contextWindow)}`;
954
+ }
955
+ return { ...item, facts, title };
956
+ }
957
+
958
+ /** Selected agent's preview: title with status, facts, context meter, recent activity. */
959
+ #describePreview(ref: TRecord | undefined, meter: boolean): NativeChild[] {
960
+ this.#recentByKey.clear();
961
+ if (!ref) return [];
962
+ const observed = this.#observableFor(ref.id);
963
+ const metrics = this.#metricsFor(ref, observed);
964
+ const out: NativeChild[] = [
965
+ node(
966
+ "row",
967
+ { role: "omp.hub.title", gap: "sm", align: "center" },
968
+ [
969
+ text(sanitizeDisplaySingleLine(ref.displayName || ref.id), {
970
+ role: "omp.picker.title",
971
+ truncate: "end",
972
+ }),
973
+ node("badge", { text: ref.status, tone: statusDot(ref.status) }),
974
+ ],
975
+ "title",
976
+ ),
977
+ ];
978
+ if (this.#notice) {
979
+ out.push(node("text", { text: this.#notice, tone: "error", wrap: "word" }, undefined, "notice"));
980
+ }
981
+ const facts = this.#detailFacts(ref, observed, metrics);
982
+ const chip = modelChip(ref, observed);
983
+ if (chip) {
984
+ const model: TspSpan[] = [];
985
+ if (chip.fallback) model.push(span("fallback → ", "warning"));
986
+ model.push(span(chip.text, "mono"));
987
+ if (chip.level && chip.dot) model.push(span(theme.sep.dot, "dim"), span(chip.level, chip.dot));
988
+ const modelRole = observed?.progress?.modelRole ?? ref.history?.modelRole;
989
+ if (modelRole && this.#getRoleInfo) {
990
+ model.push(span(theme.sep.dot, "dim"), roleBadgeSpan(modelRole, this.#getRoleInfo(modelRole)));
991
+ }
992
+ facts.splice(facts[0]?.k === "Task" ? 1 : 0, 0, { k: "Model", v: model });
993
+ }
994
+ out.push(node("kv", { items: facts, layout: "grid", role: "omp.hub.kv" }, undefined, "facts"));
995
+ if (metrics?.contextTokens !== undefined && metrics.contextWindow) {
996
+ const ratio = Math.max(0, Math.min(1, metrics.contextTokens / metrics.contextWindow));
997
+ const label = `${formatNumber(metrics.contextTokens)} / ${formatNumber(metrics.contextWindow)} · ${Math.round(ratio * 100)}%`;
998
+ out.push(
999
+ meter
1000
+ ? node(
1001
+ "meter",
1002
+ {
1003
+ value: ratio,
1004
+ style: "bar",
1005
+ size: "md",
1006
+ label,
1007
+ tone: getContextUsageTone(getContextUsageLevel(ratio * 100, metrics.contextWindow)),
1008
+ },
1009
+ undefined,
1010
+ "context",
1011
+ )
1012
+ : node("progress", { value: ratio, label }, undefined, "context"),
1013
+ );
1014
+ }
1015
+ const recent = this.#activity.recent(ref.id, NATIVE_RECENT_ACTIVITY);
1016
+ const rows: NativeChild[] = recent.map(activity => {
1017
+ const key = stableKey(activity.id);
1018
+ this.#recentByKey.set(key, activity);
1019
+ const name = activity.kind === "tool" ? (activity.toolName ?? activity.title) : activity.title;
1020
+ return node(
1021
+ "row",
1022
+ {
1023
+ role: "omp.hub.activity.row",
1024
+ gap: "sm",
1025
+ align: "baseline",
1026
+ actions: { click: "transcript" },
1027
+ title: "Open in transcript",
1028
+ },
1029
+ [
1030
+ text([span(activityClock(activity.timestamp), "mono dim")]),
1031
+ text([activityGlyphSpan(activity)]),
1032
+ text([span(sanitizeDisplaySingleLine(name), "mono muted")]),
1033
+ text(sanitizeDisplaySingleLine(activity.summary), { truncate: "end", grow: 1 }),
1034
+ ],
1035
+ key,
1036
+ );
1037
+ });
1038
+ if (rows.length === 0) rows.push(text([span("No response or tool activity yet", "dim")]));
1039
+ out.push(node("section", { head: "Recent activity", role: "omp.hub.activity" }, rows, "recentActivity"));
1040
+ return out;
1041
+ }
1042
+
1043
+ #activityPickerProps(): PickerBody {
1044
+ const agent = this.#rows[this.#selectedRow]?.id;
1045
+ const scope =
1046
+ this.#activityScope === "all"
1047
+ ? "All agents"
1048
+ : this.#activityScope === "agent"
1049
+ ? (agent ?? "Selected agent")
1050
+ : `${agent ?? "Selected"} subtree`;
1051
+ const selected = this.#activityRows[this.#selectedActivityRow];
1052
+ return {
1053
+ noun: "events",
1054
+ layout: "rows",
1055
+ preview: "none",
1056
+ ...pickerQuery(this.#activitySearchEditing || this.#activitySearch.getValue() ? this.#activitySearch : null),
1057
+ placeholder: "Search activity…",
1058
+ scopes: ACTIVITY_FILTER_SCOPES,
1059
+ scope: this.#activityFilter,
1060
+ columns: ACTIVITY_COLUMNS,
1061
+ items: this.#activityRows.map(activity => this.#pickerActivityItem(activity)),
1062
+ selected: selected?.id ?? null,
1063
+ empty: "No agent activity recorded yet",
1064
+ actions: [
1065
+ {
1066
+ id: "open",
1067
+ label: "Open transcript",
1068
+ keys: ["enter"],
1069
+ primary: true,
1070
+ disabled: selected ? undefined : true,
1071
+ },
1072
+ { id: "follow", label: "Follow", keys: ["space"], on: this.#activityFollow },
1073
+ { id: "agents", label: sanitizeDisplaySingleLine(scope), keys: ["s"] },
1074
+ { id: "filter", label: "Search", keys: ["/"] },
1075
+ CLOSE_ACTION,
1076
+ ],
1077
+ focus: "list",
1078
+ };
1079
+ }
1080
+
1081
+ #pickerActivityItem(activity: AgentActivityRow): TspPickerItem {
1082
+ const title = activity.kind === "tool" ? (activity.toolName ?? activity.title) : activity.title;
1083
+ const tone = activity.status === "error" ? "error" : activity.status === "aborted" ? "warning" : undefined;
1084
+ return {
1085
+ id: activity.id,
1086
+ label: sanitizeDisplaySingleLine(title),
1087
+ mono: activity.kind === "tool" || undefined,
1088
+ detail: sanitizeDisplaySingleLine(activity.summary),
1089
+ icon: ACTIVITY_KIND_ICON[activity.kind],
1090
+ dot: activity.status === "pending" ? "pending" : undefined,
1091
+ tone,
1092
+ facts: {
1093
+ agent: [span(sanitizeDisplaySingleLine(activity.agentId), "mono")],
1094
+ at: activityClock(activity.timestamp),
1095
+ },
1096
+ title: formatLocalDateTimeWithOffset(new Date(activity.timestamp)),
1097
+ };
1098
+ }
1099
+
1100
+ #describeAgents(nativeTree: boolean): NativeChild[] {
1101
+ const head: NativeChild[] = [
1102
+ text([span("Roster", "strong")]),
1103
+ node("tabs", { items: VIEW_TABS, active: this.#viewMode }, undefined, "view"),
1104
+ ];
1105
+ const counts: TspSpan[] = [];
1106
+ for (const status of ["running", "idle", "parked", "aborted"] as const) {
1107
+ const count = this.#statusCounts[status];
1108
+ if (count === 0) continue;
1109
+ if (counts.length > 0) counts.push(span(theme.sep.dot, "dim"));
1110
+ counts.push(statusGlyphSpan(status), span(` ${count} ${status}`, statusTone(status)));
1111
+ }
1112
+ if (counts.length > 0) head.push(text(counts));
1113
+
1114
+ const children: NativeChild[] = [
1115
+ node("row", { gap: "sm", wrap: true, align: "center" }, head, "head"),
1116
+ node("text", { spans: this.#usageSpans(), wrap: "word" }, undefined, "usage"),
1117
+ ];
1118
+ if (this.#agentFilterEditing) {
1119
+ children.push(
1120
+ node(
1121
+ "input",
1122
+ {
1123
+ text: this.#agentFilter.getValue(),
1124
+ cursor: this.#agentFilter.getCursor(),
1125
+ prompt: [span("/", "muted")],
1126
+ },
1127
+ undefined,
1128
+ "filter",
1129
+ ),
1130
+ );
1131
+ } else if (this.#agentFilter.getValue()) {
1132
+ children.push(
1133
+ node("text", { spans: [span(`/${this.#agentFilter.getValue()}`, "accent")] }, undefined, "filter"),
1134
+ );
1135
+ }
1136
+ children.push(
1137
+ node(
1138
+ "row",
1139
+ { gap: "md", wrap: true, align: "start", grow: 1 },
1140
+ [this.#describeRoster(nativeTree), this.#describeDetail(this.#rows[this.#selectedRow])],
1141
+ "body",
1142
+ ),
1143
+ );
1144
+ if (this.#notice) {
1145
+ children.push(
1146
+ node("text", { spans: [span(this.#notice, "error")], wrap: "word", tone: "error" }, undefined, "notice"),
1147
+ );
1148
+ }
1149
+ children.push(hintsRow(AGENT_HINTS[this.#viewMode]));
1150
+ return children;
1151
+ }
1152
+
1153
+ #usageSpans(): TspSpan[] {
1154
+ const metrics = this.#aggregate;
1155
+ const dot = theme.sep.dot;
1156
+ if (metrics.reportedAgents === 0) return [span(`Usage —${dot}0/${this.#rows.length} measured`, "dim")];
1157
+ const activeTime = formatMetricDuration(metrics);
1158
+ return [
1159
+ span(formatCost(metrics.cost), "statusLineCost"),
1160
+ span(
1161
+ [
1162
+ "",
1163
+ activeTime ? `${activeTime} agent time` : "agent time —",
1164
+ `${formatNumber(metrics.requests)} req`,
1165
+ `${formatNumber(metrics.tools)} tools`,
1166
+ `${formatNumber(metrics.tokens)} tok`,
1167
+ `${metrics.activeDurationAgents}/${metrics.reportedAgents} timed`,
1168
+ `${metrics.reportedAgents}/${this.#rows.length} measured`,
1169
+ ].join(dot),
1170
+ "dim",
1171
+ ),
1172
+ ];
1173
+ }
1174
+
1175
+ #describeRoster(nativeTree: boolean): NativeNode {
1176
+ const layout = { grow: 3, min: { w: `${ROSTER_MIN_WIDTH}ch` } } as const;
1177
+ if (this.#rows.length === 0) {
1178
+ if (this.#loadingPersistedSubagents) {
1179
+ return node(
1180
+ "spinner",
1181
+ { ...layout, label: [span("Loading saved agents…", "accent")] },
1182
+ undefined,
1183
+ "loading",
1184
+ );
1185
+ }
1186
+ return node(
1187
+ "col",
1188
+ layout,
1189
+ [
1190
+ text([span(`${theme.status.shadowed} `, "muted"), span("No agents in this session", "strong")]),
1191
+ text(
1192
+ [span("Finished, parked, and killed subagents remain with the session that created them.", "dim")],
1193
+ { wrap: "word" },
1194
+ ),
1195
+ text([span("Resume that session with omp-dev --continue, or spawn a task here.", "dim")], {
1196
+ wrap: "word",
1197
+ }),
1198
+ ],
1199
+ "empty",
1200
+ );
1201
+ }
1202
+ if (this.#viewMode === "tree" && nativeTree) {
1203
+ return node("tree", { ...layout, nodes: this.#agentTreeNodes() }, undefined, "agents");
1204
+ }
1205
+ // Without native trees the parent view degrades to the parent-first list, naming each parent.
1206
+ const showParent = this.#viewMode === "roster" || !nativeTree;
1207
+ return node(
1208
+ "list",
1209
+ { ...layout, selected: this.#rows[this.#selectedRow]?.id ?? null, virtual: true },
1210
+ this.#rows.map((ref, index) => this.#describeAgentItem(ref, index === this.#selectedRow, showParent)),
1211
+ "agents",
1212
+ );
1213
+ }
1214
+
1215
+ #agentLabel(ref: TRecord, selected: boolean, showParent: boolean): TspSpan[] {
1216
+ const label = [
1217
+ statusGlyphSpan(ref.status),
1218
+ span(" "),
1219
+ span(sanitizeDisplaySingleLine(ref.id), selected ? "accent strong" : "strong"),
1220
+ ];
1221
+ if (showParent && ref.parentId && ref.parentId !== MAIN_AGENT_ID) {
1222
+ label.push(span(` ↳ ${sanitizeDisplaySingleLine(ref.parentId)}`, "dim"));
1223
+ }
1224
+ if (ref.kind === "advisor") label.push(span(" read-only", "warning"));
1225
+ const unread = this.#irc.unreadCount(ref.id);
1226
+ if (unread > 0) label.push(span(` ⧉ ${unread}`, "warning"));
1227
+ return label;
1228
+ }
1229
+
1230
+ #modelSpans(ref: TRecord, observed: ObservableSession | undefined): TspSpan[] | undefined {
1231
+ const spans: TspSpan[] = [];
1232
+ const modelRole = observed?.progress?.modelRole ?? ref.history?.modelRole;
1233
+ if (modelRole && this.#getRoleInfo) spans.push(roleBadgeSpan(modelRole, this.#getRoleInfo(modelRole)));
1234
+ const model = modelBadgeSpans(ref, observed);
1235
+ if (model) {
1236
+ if (spans.length > 0) spans.push(span(theme.sep.dot, "dim"));
1237
+ spans.push(...model);
1238
+ }
1239
+ return spans.length > 0 ? spans : undefined;
1240
+ }
1241
+
1242
+ #describeAgentItem(ref: TRecord, selected: boolean, showParent: boolean): NativeNode {
1243
+ const observed = this.#observableFor(ref.id);
1244
+ const metrics = this.#metricsFor(ref, observed);
1245
+ const age = formatAge(Math.max(1, Math.round((Date.now() - ref.lastActivity) / 1000)));
1246
+ const detail = [
1247
+ span(metrics ? `${metricsText(metrics)}${theme.sep.dot}${age}` : `usage${theme.sep.dot}${age}`, "dim"),
1248
+ ];
1249
+ const task = observed?.description ?? observed?.progress?.task ?? ref.activity;
1250
+ if (task) detail.push(span(theme.sep.dot, "dim"), span(sanitizeDisplaySingleLine(task), "muted"));
1251
+ return node(
1252
+ "item",
1253
+ { label: this.#agentLabel(ref, selected, showParent), detail, value: this.#modelSpans(ref, observed) },
1254
+ undefined,
1255
+ ref.id,
1256
+ );
1257
+ }
1258
+
1259
+ /** The parent projection as a disclosure tree; node ids are agent ids (the `item` of select events). */
1260
+ #agentTreeNodes(): AgentTreeEntry[] {
1261
+ const roots: AgentTreeEntry[] = [];
1262
+ const byId = new Map<string, AgentTreeEntry>();
1263
+ for (let index = 0; index < this.#rows.length; index++) {
1264
+ const ref = this.#rows[index]!;
1265
+ const observed = this.#observableFor(ref.id);
1266
+ const label = this.#agentLabel(ref, index === this.#selectedRow, false);
1267
+ const task = observed?.description ?? observed?.progress?.task ?? ref.activity;
1268
+ if (task) label.push(span(theme.sep.dot, "dim"), span(sanitizeDisplaySingleLine(task), "muted"));
1269
+ const model = this.#modelSpans(ref, observed);
1270
+ if (model) label.push(span(theme.sep.dot, "dim"), ...model);
1271
+ const entry: AgentTreeEntry = { id: ref.id, label, open: true };
1272
+ byId.set(ref.id, entry);
1273
+ const parentId = (this.#treeDepthById.get(ref.id) ?? 0) > 0 ? this.#treeParentById.get(ref.id) : undefined;
1274
+ const parent = parentId === undefined ? undefined : byId.get(parentId);
1275
+ if (!parent) roots.push(entry);
1276
+ else if (parent.children) parent.children.push(entry);
1277
+ else parent.children = [entry];
1278
+ }
1279
+ return roots;
1280
+ }
1281
+
1282
+ #describeDetail(ref: TRecord | undefined): NativeNode {
1283
+ const layout = {
1284
+ grow: 2,
1285
+ min: { w: `${DETAIL_MIN_WIDTH}ch` },
1286
+ gap: "sm",
1287
+ role: "omp.overlay.agentHub.detail",
1288
+ } as const;
1289
+ if (!ref) return node("col", layout, [text([span("Select an agent to inspect", "dim")])], "detail");
1290
+ const observed = this.#observableFor(ref.id);
1291
+ const metrics = this.#metricsFor(ref, observed);
1292
+
1293
+ const out: NativeChild[] = [
1294
+ text([
1295
+ statusGlyphSpan(ref.status),
1296
+ span(" "),
1297
+ span(sanitizeDisplaySingleLine(ref.displayName || ref.id), "strong"),
1298
+ ]),
1299
+ ];
1300
+ if (ref.displayName && ref.displayName !== ref.id)
1301
+ out.push(text([span(sanitizeDisplaySingleLine(ref.id), "dim")]));
1302
+ const state: NativeChild[] = [
1303
+ ref.status === "running"
1304
+ ? node("spinner", { label: [span("running", "accent")] })
1305
+ : text([span(ref.status, statusTone(ref.status))]),
1306
+ ];
1307
+ const duration = metrics ? formatMetricDuration(metrics) : undefined;
1308
+ if (duration) state.push(text([span(duration, "dim")]));
1309
+ state.push(
1310
+ text([span("last active", "dim")]),
1311
+ node("elapsed", { age: Math.max(0, Date.now() - ref.lastActivity), format: "short" }),
1312
+ text([span("ago", "dim")]),
1313
+ );
1314
+ out.push(node("row", { gap: "sm", wrap: true, align: "baseline" }, state, "state"));
1315
+ const model = this.#modelSpans(ref, observed);
1316
+ if (model) out.push(node("text", { spans: model }, undefined, "model"));
1317
+ out.push(node("kv", { items: this.#detailFacts(ref, observed, metrics), layout: "grid" }, undefined, "facts"));
1318
+ if (metrics?.contextTokens !== undefined && metrics.contextWindow) {
1319
+ const ratio = Math.max(0, Math.min(1, metrics.contextTokens / metrics.contextWindow));
1320
+ out.push(
1321
+ node(
1322
+ "progress",
1323
+ {
1324
+ value: ratio,
1325
+ label: `${formatNumber(metrics.contextTokens)}/${formatNumber(metrics.contextWindow)} ${Math.round(ratio * 100)}%`,
1326
+ },
1327
+ undefined,
1328
+ "context",
1329
+ ),
1330
+ );
1331
+ }
1332
+ const recent = this.#activity.recent(ref.id, NATIVE_RECENT_ACTIVITY);
1333
+ out.push(
1334
+ node(
1335
+ "section",
1336
+ { head: [span("Recent activity", "accent strong")] },
1337
+ [
1338
+ node(
1339
+ "list",
1340
+ { empty: [span("No response or tool activity yet", "muted")] },
1341
+ recent.map(activity => this.#describeActivityItem(activity)),
1342
+ "recent",
1343
+ ),
1344
+ ],
1345
+ "recentActivity",
1346
+ ),
1347
+ );
1348
+ return node("col", layout, out, "detail");
1349
+ }
1350
+
1351
+ /** Inspector facts shared by the fallback detail pane and the picker preview. */
1352
+ #detailFacts(
1353
+ ref: TRecord,
1354
+ observed: ObservableSession | undefined,
1355
+ metrics: AgentMetrics | undefined,
1356
+ ): Array<{ k: string; v: TspSpan[] | string }> {
1357
+ const progress = observed?.progress;
1358
+ const children = this.#childrenByParent.get(ref.id) ?? [];
1359
+ const dot = theme.sep.dot;
1360
+ const facts: Array<{ k: string; v: TspSpan[] | string }> = [];
1361
+ const task = observed?.description ?? progress?.task ?? ref.activity;
1362
+ if (task) facts.push({ k: "Task", v: taskSummary(task) });
1363
+ const current = progress?.currentTool
1364
+ ? `${progress.currentTool}${progress.currentToolArgs ? `${dot}${progress.currentToolArgs}` : ""}`
1365
+ : (progress?.lastIntent ?? ref.activity);
1366
+ if (current) {
1367
+ const value = [span(sanitizeDisplaySingleLine(current))];
1368
+ if (progress?.retryState) {
1369
+ value.push(
1370
+ span(`${dot}retry ${progress.retryState.attempt}/${progress.retryState.maxAttempts}`, "warning"),
1371
+ );
1372
+ }
1373
+ facts.push({ k: "Current", v: value });
1374
+ }
1375
+ facts.push({ k: "Usage", v: metrics ? metricsText(metrics) : [span("usage —", "dim")] });
1376
+ facts.push({
1377
+ k: "Lineage",
1378
+ v: `Spawned by ${sanitizeDisplaySingleLine(ref.parentId ?? MAIN_AGENT_ID)}${children.length > 0 ? `${dot}${children.length} ${children.length === 1 ? "child" : "children"}` : ""}`,
1379
+ });
1380
+ if (children.length > 0) {
1381
+ const named = children.slice(0, NATIVE_CHILD_IDS).map(child => sanitizeDisplaySingleLine(child.id));
1382
+ const more = children.length - named.length;
1383
+ facts.push({ k: "Children", v: [span(`${named.join(", ")}${more > 0 ? `, … +${more}` : ""}`, "dim")] });
1384
+ }
1385
+ facts.push({ k: "Registered", v: [span(formatLocalDateTimeWithOffset(new Date(ref.createdAt)), "dim")] });
1386
+ facts.push({
1387
+ k: "Changes",
1388
+ v: [
1389
+ span(
1390
+ ref.kind === "advisor" || ref.history?.readOnly
1391
+ ? "Read-only · 0 LoC"
1392
+ : "Shared workspace · per-agent LoC not attributable",
1393
+ "dim",
1394
+ ),
1395
+ ],
1396
+ });
1397
+ const artifacts = ref.history;
1398
+ if (artifacts?.outputPath) {
1399
+ facts.push({
1400
+ k: "Output",
1401
+ v: [span(shortenPath(artifacts.outputPath), "path", { href: `file://${artifacts.outputPath}` })],
1402
+ });
1403
+ }
1404
+ if (artifacts?.patchPath) facts.push({ k: "Patch", v: [span(shortenPath(artifacts.patchPath), "path")] });
1405
+ for (const nestedPath of artifacts?.nestedPatchPaths ?? []) {
1406
+ facts.push({ k: "Nested patch", v: [span(shortenPath(nestedPath), "path")] });
1407
+ }
1408
+ if (artifacts?.branchName) facts.push({ k: "Worktree branch", v: [span(artifacts.branchName, "code")] });
1409
+ return facts;
1410
+ }
1411
+
1412
+ #describeActivityItem(activity: AgentActivityRow): NativeNode {
1413
+ const ref = this.#registry.get(activity.agentId);
1414
+ const observed = this.#observedById.get(activity.agentId);
1415
+ const role = observed?.progress?.modelRole ?? ref?.history?.modelRole;
1416
+ const label = [activityGlyphSpan(activity), span(" ")];
1417
+ if (role && this.#getRoleInfo) label.push(roleBadgeSpan(role, this.#getRoleInfo(role)), span(" "));
1418
+ const title = activity.kind === "tool" ? (activity.toolName ?? activity.title) : activity.title;
1419
+ label.push(
1420
+ span(sanitizeDisplaySingleLine(activity.agentId), "strong"),
1421
+ span(" "),
1422
+ span(sanitizeDisplaySingleLine(title), activity.kind === "response" ? "success" : "muted"),
1423
+ );
1424
+ return node(
1425
+ "item",
1426
+ {
1427
+ label,
1428
+ detail: sanitizeDisplaySingleLine(activity.summary),
1429
+ value: [span(activityClock(activity.timestamp), "dim")],
1430
+ },
1431
+ undefined,
1432
+ activity.id,
1433
+ );
1434
+ }
1435
+
1436
+ #describeActivity(): NativeChild[] {
1437
+ const selectedAgent = this.#rows[this.#selectedRow]?.id;
1438
+ const scope =
1439
+ this.#activityScope === "all"
1440
+ ? "all agents"
1441
+ : this.#activityScope === "agent"
1442
+ ? (selectedAgent ?? "selected agent")
1443
+ : `${selectedAgent ?? "selected"} subtree`;
1444
+ const dot = theme.sep.dot;
1445
+ const status: NativeChild[] = [
1446
+ text([
1447
+ span(
1448
+ `${sanitizeDisplaySingleLine(scope)}${dot}${this.#activityFilter}${dot}${this.#activityFollow ? "following" : "paused"}`,
1449
+ "dim",
1450
+ ),
1451
+ ]),
1452
+ this.#activitySearchEditing
1453
+ ? node(
1454
+ "input",
1455
+ {
1456
+ text: this.#activitySearch.getValue(),
1457
+ cursor: this.#activitySearch.getCursor(),
1458
+ prompt: [span("search: ", "muted")],
1459
+ },
1460
+ undefined,
1461
+ "search",
1462
+ )
1463
+ : text([
1464
+ span(
1465
+ this.#activitySearch.getValue() ? `search: ${this.#activitySearch.getValue()}` : "search: —",
1466
+ "dim",
1467
+ ),
1468
+ ]),
1469
+ ];
1470
+ const selected = this.#activityRows[this.#selectedActivityRow]?.id ?? null;
1471
+ return [
1472
+ node("row", { gap: "md", wrap: true, align: "baseline" }, status, "status"),
1473
+ node(
1474
+ "list",
1475
+ {
1476
+ selected,
1477
+ filter: this.#activitySearch.getValue() || undefined,
1478
+ virtual: true,
1479
+ grow: 1,
1480
+ empty: [
1481
+ span(
1482
+ this.#activitySearch.getValue() ? "No matching activity" : "No agent activity recorded yet",
1483
+ "muted",
1484
+ ),
1485
+ ],
1486
+ },
1487
+ this.#activityRows.map(activity => this.#describeActivityItem(activity)),
1488
+ "activity",
1489
+ ),
1490
+ hintsRow(ACTIVITY_HINTS),
1491
+ ];
1492
+ }
1493
+
532
1494
  // ========================================================================
533
1495
  // Live data plumbing
534
1496
  // ========================================================================
@@ -582,7 +1544,7 @@ export class AgentHubOverlayComponent<TRecord extends AgentRecordLike = AgentRec
582
1544
  if (!rowOrder.has(ref)) rowOrder.set(ref, this.#nextRowOrder++);
583
1545
  }
584
1546
  }
585
- const query = this.#agentFilter.trim();
1547
+ const query = this.#agentFilter.getValue().trim();
586
1548
  const rosterRows =
587
1549
  query.length > 0
588
1550
  ? ordered.filter(ref => fuzzyMatch(query, `${ref.id} ${ref.displayName ?? ""}`).matches)
@@ -688,7 +1650,7 @@ export class AgentHubOverlayComponent<TRecord extends AgentRecordLike = AgentRec
688
1650
  let rows = this.#activity.query({
689
1651
  agentIds: this.#activityAgentIds(),
690
1652
  kinds,
691
- search: this.#activitySearch,
1653
+ search: this.#activitySearch.getValue(),
692
1654
  limit: 2_000,
693
1655
  });
694
1656
  if (this.#activityFilter === "errors") rows = rows.filter(row => row.status === "error");
@@ -739,9 +1701,9 @@ export class AgentHubOverlayComponent<TRecord extends AgentRecordLike = AgentRec
739
1701
  ? (selectedAgent ?? "selected agent")
740
1702
  : `${selectedAgent ?? "selected"} subtree`;
741
1703
  const search = this.#activitySearchEditing
742
- ? theme.fg("accent", `search: ${this.#activitySearch}▌`)
743
- : this.#activitySearch
744
- ? `search: ${this.#activitySearch}`
1704
+ ? theme.fg("accent", `search: ${filterText(this.#activitySearch)}`)
1705
+ : this.#activitySearch.getValue()
1706
+ ? `search: ${this.#activitySearch.getValue()}`
745
1707
  : "search: —";
746
1708
  body.push(
747
1709
  theme.fg(
@@ -753,7 +1715,12 @@ export class AgentHubOverlayComponent<TRecord extends AgentRecordLike = AgentRec
753
1715
 
754
1716
  const budget = Math.max(0, contentRows - body.length);
755
1717
  if (this.#activityRows.length === 0 && budget > 0) {
756
- body.push(theme.fg("muted", this.#activitySearch ? "No matching activity" : "No agent activity recorded yet"));
1718
+ body.push(
1719
+ theme.fg(
1720
+ "muted",
1721
+ this.#activitySearch.getValue() ? "No matching activity" : "No agent activity recorded yet",
1722
+ ),
1723
+ );
757
1724
  } else if (budget > 0) {
758
1725
  const selected = Math.min(this.#selectedActivityRow, this.#activityRows.length - 1);
759
1726
  const start = this.#activityFollow
@@ -839,8 +1806,11 @@ export class AgentHubOverlayComponent<TRecord extends AgentRecordLike = AgentRec
839
1806
 
840
1807
  #footer(showingNarrowDetails: boolean, availableWidth: number): string {
841
1808
  const nextView = this.#viewMode === "roster" ? "by parent" : "flat";
842
- const filter =
843
- this.#agentFilter.length > 0 ? `/${this.#agentFilter}${this.#agentFilterEditing ? "▌" : ""} · ` : "";
1809
+ const filter = this.#agentFilterEditing
1810
+ ? `/${filterText(this.#agentFilter)} · `
1811
+ : this.#agentFilter.getValue()
1812
+ ? `/${this.#agentFilter.getValue()} · `
1813
+ : "";
844
1814
  if (showingNarrowDetails) {
845
1815
  return theme.fg(
846
1816
  "dim",
@@ -1312,12 +2282,13 @@ export class AgentHubOverlayComponent<TRecord extends AgentRecordLike = AgentRec
1312
2282
  #handleActivitySearchInput(keyData: string): void {
1313
2283
  if (matchesKey(keyData, "escape") || matchesKey(keyData, "enter") || keyData === "\r" || keyData === "\n") {
1314
2284
  this.#activitySearchEditing = false;
1315
- } else if (matchesKey(keyData, "backspace")) {
1316
- this.#activitySearch = this.#activitySearch.slice(0, -1);
1317
- } else if (keyData.length === 1 && keyData >= " " && keyData !== "\u007f") {
1318
- this.#activitySearch += keyData;
1319
2285
  } else {
1320
- return;
2286
+ const before = this.#activitySearch.getValue();
2287
+ if (!this.#activitySearch.handleInput(keyData)) return;
2288
+ if (this.#activitySearch.getValue() === before) {
2289
+ this.#requestRender();
2290
+ return;
2291
+ }
1321
2292
  }
1322
2293
  this.#refreshActivityRows();
1323
2294
  this.#requestRender();
@@ -1325,8 +2296,8 @@ export class AgentHubOverlayComponent<TRecord extends AgentRecordLike = AgentRec
1325
2296
 
1326
2297
  #handleActivityInput(keyData: string): void {
1327
2298
  if (matchesKey(keyData, "escape")) {
1328
- if (this.#activitySearch) {
1329
- this.#activitySearch = "";
2299
+ if (this.#activitySearch.getValue()) {
2300
+ this.#activitySearch.setValue("");
1330
2301
  this.#refreshActivityRows();
1331
2302
  this.#requestRender();
1332
2303
  } else {
@@ -1344,25 +2315,17 @@ export class AgentHubOverlayComponent<TRecord extends AgentRecordLike = AgentRec
1344
2315
  return;
1345
2316
  }
1346
2317
  if (keyData === " ") {
1347
- this.#activityFollow = !this.#activityFollow;
1348
- if (this.#activityFollow && this.#activityRows.length > 0) {
1349
- this.#selectedActivityRow = this.#activityRows.length - 1;
1350
- }
1351
- this.#requestRender();
2318
+ this.#toggleActivityFollow();
1352
2319
  return;
1353
2320
  }
1354
2321
  if (keyData === "f") {
1355
- const filters: ActivityFilter[] = ["all", "errors", "responses", "tools"];
1356
- this.#activityFilter = filters[(filters.indexOf(this.#activityFilter) + 1) % filters.length]!;
1357
- this.#refreshActivityRows();
1358
- this.#requestRender();
2322
+ this.#setActivityFilter(
2323
+ ACTIVITY_FILTERS[(ACTIVITY_FILTERS.indexOf(this.#activityFilter) + 1) % ACTIVITY_FILTERS.length]!,
2324
+ );
1359
2325
  return;
1360
2326
  }
1361
2327
  if (keyData === "s") {
1362
- const scopes: ActivityScope[] = ["all", "agent", "subtree"];
1363
- this.#activityScope = scopes[(scopes.indexOf(this.#activityScope) + 1) % scopes.length]!;
1364
- this.#refreshActivityRows();
1365
- this.#requestRender();
2328
+ this.#cycleActivityScope();
1366
2329
  return;
1367
2330
  }
1368
2331
  if (matchesKey(keyData, "j") || matchesSelectDown(keyData)) {
@@ -1387,25 +2350,58 @@ export class AgentHubOverlayComponent<TRecord extends AgentRecordLike = AgentRec
1387
2350
  }
1388
2351
  }
1389
2352
 
2353
+ /** Space on the activity log: follow the newest row, or hold the selection. */
2354
+ #toggleActivityFollow(): void {
2355
+ this.#activityFollow = !this.#activityFollow;
2356
+ if (this.#activityFollow && this.#activityRows.length > 0) {
2357
+ this.#selectedActivityRow = this.#activityRows.length - 1;
2358
+ }
2359
+ this.#requestRender();
2360
+ }
2361
+
2362
+ /** `f` cycles through these; the picker's filter scopes set one directly. */
2363
+ #setActivityFilter(filter: ActivityFilter): void {
2364
+ this.#activityFilter = filter;
2365
+ this.#refreshActivityRows();
2366
+ this.#requestRender();
2367
+ }
2368
+
2369
+ /** `s`: all agents → the selected agent → its subtree. */
2370
+ #cycleActivityScope(): void {
2371
+ const scopes: ActivityScope[] = ["all", "agent", "subtree"];
2372
+ this.#activityScope = scopes[(scopes.indexOf(this.#activityScope) + 1) % scopes.length]!;
2373
+ this.#refreshActivityRows();
2374
+ this.#requestRender();
2375
+ }
2376
+
2377
+ /** `t`: flat roster ⇄ grouped by parent. */
2378
+ #toggleViewMode(): void {
2379
+ this.#hoveredRow = null;
2380
+ this.#viewMode = this.#viewMode === "roster" ? "tree" : "roster";
2381
+ this.#refreshRows();
2382
+ this.#requestRender();
2383
+ }
2384
+
1390
2385
  #handleTableInput(keyData: string): void {
1391
2386
  if (this.#agentFilterEditing) {
1392
2387
  if (matchesKey(keyData, "escape") || matchesKey(keyData, "enter") || keyData === "\r" || keyData === "\n") {
1393
2388
  this.#agentFilterEditing = false;
1394
- if (matchesKey(keyData, "escape")) this.#agentFilter = "";
1395
- } else if (matchesKey(keyData, "backspace")) {
1396
- this.#agentFilter = this.#agentFilter.slice(0, -1);
1397
- } else if (keyData.length === 1 && keyData >= " " && keyData !== "\u007f") {
1398
- this.#agentFilter += keyData;
2389
+ if (matchesKey(keyData, "escape")) this.#agentFilter.setValue("");
1399
2390
  } else {
1400
- return;
2391
+ const before = this.#agentFilter.getValue();
2392
+ if (!this.#agentFilter.handleInput(keyData)) return;
2393
+ if (this.#agentFilter.getValue() === before) {
2394
+ this.#requestRender();
2395
+ return;
2396
+ }
1401
2397
  }
1402
2398
  this.#refreshRows();
1403
2399
  this.#requestRender();
1404
2400
  return;
1405
2401
  }
1406
2402
  if (matchesKey(keyData, "escape")) {
1407
- if (this.#agentFilter) {
1408
- this.#agentFilter = "";
2403
+ if (this.#agentFilter.getValue()) {
2404
+ this.#agentFilter.setValue("");
1409
2405
  this.#refreshRows();
1410
2406
  this.#requestRender();
1411
2407
  } else if (this.#narrowDetailsOpen && this.#split.mode !== "split") {
@@ -1437,10 +2433,7 @@ export class AgentHubOverlayComponent<TRecord extends AgentRecordLike = AgentRec
1437
2433
  }
1438
2434
  }
1439
2435
  if (keyData === "t") {
1440
- this.#hoveredRow = null;
1441
- this.#viewMode = this.#viewMode === "roster" ? "tree" : "roster";
1442
- this.#refreshRows();
1443
- this.#requestRender();
2436
+ this.#toggleViewMode();
1444
2437
  return;
1445
2438
  }
1446
2439
  if (matchesKey(keyData, "left")) {