@pellux/goodvibes-tui 2.0.21 → 2.1.1

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 (391) hide show
  1. package/CHANGELOG.md +65 -1
  2. package/README.md +30 -29
  3. package/docs/README.md +8 -5
  4. package/docs/foundation-artifacts/operator-contract.json +1 -1
  5. package/package.json +28 -19
  6. package/src/cli/management.ts +5 -2
  7. package/src/cli/tui-startup.ts +34 -39
  8. package/src/core/agent-view-render.ts +157 -0
  9. package/src/core/approval-alert.ts +49 -11
  10. package/src/core/bookmark-navigation.ts +7 -1
  11. package/src/core/budget-breach-notifier.ts +27 -8
  12. package/src/core/compaction-receipt.ts +1 -1
  13. package/src/core/context-usage.ts +1 -1
  14. package/src/core/conversation-compaction-render.ts +3 -3
  15. package/src/core/conversation-display-only.ts +86 -0
  16. package/src/core/conversation-event-navigation.ts +27 -0
  17. package/src/core/conversation-line-cache.ts +216 -447
  18. package/src/core/conversation-render-context.ts +20 -27
  19. package/src/core/conversation-rendering.ts +157 -575
  20. package/src/core/conversation-splash-state.ts +6 -1
  21. package/src/core/conversation-types.ts +35 -0
  22. package/src/core/conversation-usage.ts +12 -0
  23. package/src/core/conversation.ts +130 -131
  24. package/src/core/long-task-notifier.ts +100 -31
  25. package/src/core/notices.ts +75 -0
  26. package/src/core/pairing-tailscale-gateway.ts +1 -1
  27. package/src/core/process-output-log.ts +143 -0
  28. package/src/core/rewind-receipt.ts +1 -1
  29. package/src/core/session-focus.ts +190 -0
  30. package/src/core/session-resume-core.ts +23 -81
  31. package/src/core/stream-event-wiring.ts +31 -5
  32. package/src/core/system-message-noise.ts +2 -2
  33. package/src/core/system-message-router.ts +16 -18
  34. package/src/core/turn-event-wiring.ts +272 -33
  35. package/src/core/work-tree-focus.ts +290 -0
  36. package/src/core/work-tree-fold-store.ts +143 -0
  37. package/src/core/work-tree-model.ts +799 -0
  38. package/src/core/work-tree-render.ts +384 -0
  39. package/src/core/work-tree-sources.ts +184 -0
  40. package/src/core/work-tree-wiring.ts +176 -0
  41. package/src/core/workstream-notification.ts +36 -6
  42. package/src/core/wrfc-notice-titles.ts +37 -0
  43. package/src/input/agents-modal-text.ts +119 -0
  44. package/src/input/agents-modal.ts +699 -0
  45. package/src/input/bookmark-modal.ts +33 -9
  46. package/src/input/changes-git.ts +251 -0
  47. package/src/input/changes-modal.ts +629 -0
  48. package/src/input/changes-model.ts +125 -0
  49. package/src/input/command-palette-types.ts +41 -0
  50. package/src/input/command-palette.ts +272 -0
  51. package/src/input/command-reference.ts +2 -2
  52. package/src/input/command-registry.ts +77 -35
  53. package/src/input/commands/acp-runtime.ts +19 -9
  54. package/src/input/commands/channel-runtime.ts +2 -2
  55. package/src/input/commands/ci-runtime.ts +1 -1
  56. package/src/input/commands/codebase-runtime.ts +1 -1
  57. package/src/input/commands/config.ts +7 -1
  58. package/src/input/commands/context-window.ts +25 -19
  59. package/src/input/commands/control-room-runtime.ts +18 -18
  60. package/src/input/commands/conversation-runtime.ts +0 -1
  61. package/src/input/commands/cost-runtime.ts +29 -20
  62. package/src/input/commands/diff-runtime.ts +35 -181
  63. package/src/input/commands/eval.ts +1 -1
  64. package/src/input/commands/experience-runtime.ts +3 -7
  65. package/src/input/commands/fresh-conversation.ts +86 -0
  66. package/src/input/commands/git-runtime.ts +5 -22
  67. package/src/input/commands/graph-runtime.ts +1 -1
  68. package/src/input/commands/guidance-runtime.ts +1 -1
  69. package/src/input/commands/health-runtime.ts +8 -9
  70. package/src/input/commands/hooks-runtime.ts +2 -2
  71. package/src/input/commands/hosted-runtime.ts +3 -3
  72. package/src/input/commands/incident-runtime.ts +3 -3
  73. package/src/input/commands/intelligence-runtime.ts +6 -9
  74. package/src/input/commands/knowledge.ts +2 -2
  75. package/src/input/commands/legacy-panel-command.ts +57 -0
  76. package/src/input/commands/local-auth-runtime.ts +8 -9
  77. package/src/input/commands/local-provider-runtime.ts +7 -4
  78. package/src/input/commands/local-runtime.ts +12 -15
  79. package/src/input/commands/local-setup-review.ts +1 -1
  80. package/src/input/commands/local-setup.ts +13 -19
  81. package/src/input/commands/memory.ts +1 -1
  82. package/src/input/commands/notify-runtime.ts +9 -0
  83. package/src/input/commands/operator-runtime.ts +10 -12
  84. package/src/input/commands/permissions-provenance.ts +3 -3
  85. package/src/input/commands/planning-runtime.ts +7 -7
  86. package/src/input/commands/platform-sandbox-runtime.ts +2 -2
  87. package/src/input/commands/platform-services-runtime.ts +1 -1
  88. package/src/input/commands/policy-dispatch.ts +2 -2
  89. package/src/input/commands/policy.ts +3 -3
  90. package/src/input/commands/provider.ts +1 -1
  91. package/src/input/commands/remote-runtime.ts +1 -1
  92. package/src/input/commands/review-runtime.ts +39 -94
  93. package/src/input/commands/rewind-runtime.ts +81 -108
  94. package/src/input/commands/runtime-services.ts +15 -17
  95. package/src/input/commands/services-runtime.ts +2 -2
  96. package/src/input/commands/session-content.ts +21 -5
  97. package/src/input/commands/session-workflow.ts +13 -38
  98. package/src/input/commands/settings-sync-runtime.ts +2 -2
  99. package/src/input/commands/shell-core.ts +19 -4
  100. package/src/input/commands/skills-runtime.ts +2 -2
  101. package/src/input/commands/status-runtime.ts +13 -0
  102. package/src/input/commands/subscription-runtime.ts +1 -1
  103. package/src/input/commands/tasks-runtime.ts +2 -8
  104. package/src/input/commands/work-plan-runtime.ts +4 -4
  105. package/src/input/commands/workstream-attempts.ts +24 -47
  106. package/src/input/commands/workstream-runtime.ts +2 -2
  107. package/src/input/commands/worktree-runtime.ts +3 -3
  108. package/src/input/commands.ts +3 -1
  109. package/src/input/composer-editor.ts +44 -0
  110. package/src/input/concealed-input.ts +1 -1
  111. package/src/input/config-modal-types.ts +20 -8
  112. package/src/input/config-modal.ts +106 -105
  113. package/src/input/confirm-dialog-types.ts +28 -0
  114. package/src/input/confirm-dialog.ts +70 -0
  115. package/src/input/delete-key-policy.ts +3 -3
  116. package/src/input/feed-context-factory.ts +15 -16
  117. package/src/input/handler-command-route.ts +7 -42
  118. package/src/input/handler-content-actions.ts +16 -15
  119. package/src/input/handler-feed-routes.ts +26 -211
  120. package/src/input/handler-feed.ts +68 -95
  121. package/src/input/handler-interactions.ts +5 -3
  122. package/src/input/handler-modal-routes.ts +215 -216
  123. package/src/input/handler-modal-stack.ts +81 -11
  124. package/src/input/handler-modal-token-routes.ts +4 -0
  125. package/src/input/handler-onboarding.ts +0 -1
  126. package/src/input/handler-picker-routes.ts +65 -107
  127. package/src/input/handler-session-view-route.ts +149 -0
  128. package/src/input/handler-shortcuts.ts +16 -154
  129. package/src/input/handler-types.ts +4 -2
  130. package/src/input/handler-ui-state.ts +45 -15
  131. package/src/input/handler-work-tree-route.ts +123 -0
  132. package/src/input/handler.ts +25 -70
  133. package/src/input/keybindings.ts +16 -71
  134. package/src/input/masked-entry-modal.ts +117 -0
  135. package/src/input/mcp-workspace.ts +48 -17
  136. package/src/input/model-picker-filter.ts +8 -0
  137. package/src/input/model-picker-open.ts +99 -0
  138. package/src/input/model-picker-types.ts +1 -1
  139. package/src/input/model-picker.ts +92 -9
  140. package/src/input/notifications-modal.ts +123 -0
  141. package/src/input/overlay-filter.ts +26 -0
  142. package/src/input/profile-picker-modal.ts +37 -8
  143. package/src/input/search.ts +3 -3
  144. package/src/input/selection-modal-queue.ts +2 -0
  145. package/src/input/selection-modal.ts +28 -16
  146. package/src/input/session-picker-modal.ts +33 -8
  147. package/src/input/settings-modal-activation.ts +7 -2
  148. package/src/input/settings-modal-data.ts +11 -6
  149. package/src/input/settings-modal-types.ts +1 -1
  150. package/src/input/settings-modal.ts +20 -20
  151. package/src/input/submission-intent.ts +1 -1
  152. package/src/input/submission-router.ts +4 -9
  153. package/src/input/surface-modal-host.ts +110 -0
  154. package/src/input/theme-settings-actions.ts +82 -0
  155. package/src/input/usage-modal.ts +183 -0
  156. package/src/input/views.ts +97 -0
  157. package/src/main.ts +175 -165
  158. package/src/permissions/hunk-selection.ts +1 -1
  159. package/src/permissions/prompt-card.ts +511 -0
  160. package/src/permissions/prompt.ts +59 -622
  161. package/src/renderer/agents-modal.ts +310 -0
  162. package/src/renderer/autocomplete-overlay.ts +42 -220
  163. package/src/renderer/back-to-bottom.ts +80 -0
  164. package/src/renderer/block-actions-overlay.ts +30 -48
  165. package/src/renderer/bookmark-modal.ts +48 -79
  166. package/src/renderer/changes-modal.ts +325 -0
  167. package/src/renderer/code-block.ts +184 -106
  168. package/src/renderer/command-palette.ts +93 -0
  169. package/src/renderer/composer-fragments.ts +4 -4
  170. package/src/renderer/composer.ts +146 -0
  171. package/src/renderer/compositor.ts +26 -160
  172. package/src/renderer/config-modal.ts +185 -81
  173. package/src/renderer/confirm-dialog.ts +50 -0
  174. package/src/renderer/context-inspector.ts +116 -154
  175. package/src/renderer/context-status-hint.ts +4 -4
  176. package/src/renderer/conversation-layout.ts +20 -0
  177. package/src/renderer/conversation-overlays.ts +94 -84
  178. package/src/renderer/conversation-surface.ts +19 -38
  179. package/src/renderer/diff-tint.ts +98 -0
  180. package/src/renderer/file-picker-overlay.ts +63 -90
  181. package/src/renderer/footer-targets.ts +38 -0
  182. package/src/renderer/git-status.ts +3 -4
  183. package/src/renderer/header-line.ts +140 -117
  184. package/src/renderer/help-overlay.ts +133 -219
  185. package/src/renderer/history-search-overlay.ts +15 -12
  186. package/src/renderer/lane-graph/bead.ts +800 -0
  187. package/src/renderer/lane-graph/glyphs.ts +134 -0
  188. package/src/renderer/lane-graph/layout.ts +248 -0
  189. package/src/renderer/lane-graph/paint.ts +537 -0
  190. package/src/renderer/lane-graph/semantic-memo.ts +77 -0
  191. package/src/renderer/layout-engine.ts +2 -57
  192. package/src/renderer/layout.ts +7 -13
  193. package/src/renderer/markdown-inline.ts +2 -1
  194. package/src/renderer/markdown-table.ts +17 -12
  195. package/src/renderer/markdown.ts +92 -34
  196. package/src/renderer/masked-entry-modal.ts +78 -0
  197. package/src/renderer/mcp-workspace.ts +166 -203
  198. package/src/renderer/model-workspace.ts +381 -436
  199. package/src/renderer/notifications-modal.ts +116 -0
  200. package/src/renderer/onboarding/onboarding-wizard.ts +178 -588
  201. package/src/renderer/process-view.ts +192 -0
  202. package/src/renderer/profile-picker-modal.ts +58 -110
  203. package/src/renderer/search-overlay.ts +6 -3
  204. package/src/renderer/selection-modal-overlay.ts +87 -327
  205. package/src/renderer/semantic-diff.ts +2 -2
  206. package/src/renderer/session-chips.ts +102 -0
  207. package/src/renderer/session-picker-modal.ts +177 -297
  208. package/src/renderer/settings-modal-connections.ts +14 -23
  209. package/src/renderer/settings-modal-context.ts +367 -0
  210. package/src/renderer/settings-modal-helpers.ts +6 -11
  211. package/src/renderer/settings-modal.ts +322 -726
  212. package/src/renderer/shell-surface.ts +277 -175
  213. package/src/renderer/shortcuts-overlay.ts +217 -0
  214. package/src/renderer/status-line.ts +475 -0
  215. package/src/renderer/surface-compose.ts +174 -0
  216. package/src/renderer/surface-kit-extra.ts +303 -0
  217. package/src/renderer/surface-kit-list.ts +366 -0
  218. package/src/renderer/surface-kit-parts.ts +360 -0
  219. package/src/renderer/surface-kit.ts +568 -0
  220. package/src/renderer/syntax-highlighter.ts +280 -197
  221. package/src/renderer/syntax-theme.ts +51 -0
  222. package/src/renderer/system-message.ts +0 -1
  223. package/src/renderer/term-caps.ts +42 -1
  224. package/src/renderer/terminal-bg-probe.ts +116 -19
  225. package/src/renderer/terminal-palette-probe.ts +322 -0
  226. package/src/renderer/terminal-palette-reader.ts +183 -0
  227. package/src/renderer/terminal-palette.ts +58 -0
  228. package/src/renderer/theme-mode-config.ts +16 -1
  229. package/src/renderer/theme.ts +234 -207
  230. package/src/renderer/thinking.ts +0 -1
  231. package/src/renderer/throbber.ts +188 -0
  232. package/src/renderer/toast-center.ts +119 -0
  233. package/src/renderer/ui-factory.ts +39 -602
  234. package/src/renderer/ui-primitives.ts +8 -25
  235. package/src/renderer/usage-modal.ts +353 -0
  236. package/src/renderer/user-message.ts +58 -0
  237. package/src/renderer/voice-capture-chip.ts +53 -0
  238. package/src/runtime/bootstrap-command-context.ts +0 -5
  239. package/src/runtime/bootstrap-command-parts.ts +38 -89
  240. package/src/runtime/bootstrap-core.ts +13 -15
  241. package/src/runtime/bootstrap-hook-bridge.ts +12 -21
  242. package/src/runtime/bootstrap-notifier-sync.ts +17 -1
  243. package/src/runtime/bootstrap-shell.ts +16 -50
  244. package/src/runtime/bootstrap.ts +14 -14
  245. package/src/runtime/client/fleet-union.ts +5 -5
  246. package/src/runtime/client/hosted-exit.ts +2 -2
  247. package/src/runtime/client/hosted-sessions.ts +1 -1
  248. package/src/runtime/client/spine-adoption.ts +1 -1
  249. package/src/runtime/context-accounting-source.ts +5 -4
  250. package/src/runtime/index.ts +1 -0
  251. package/src/runtime/notification-dispatch.ts +102 -29
  252. package/src/runtime/recovery-offer-wiring.ts +4 -1
  253. package/src/runtime/recovery-prompt.ts +31 -15
  254. package/src/runtime/runtime-services-types.ts +10 -6
  255. package/src/runtime/services.ts +17 -12
  256. package/src/runtime/session-continuity-hints.ts +2 -5
  257. package/src/runtime/session-inbound-inputs.ts +1 -1
  258. package/src/runtime/session-resume-liveness-confirm.ts +14 -2
  259. package/src/runtime/startup-typeahead-gate.ts +103 -0
  260. package/src/runtime/store/domains/domain-read-matrix.ts +0 -1
  261. package/src/runtime/store/domains/index.ts +0 -8
  262. package/src/runtime/store/selectors/index.ts +0 -28
  263. package/src/runtime/store/state.ts +0 -8
  264. package/src/runtime/ui-services.ts +4 -6
  265. package/src/runtime/usage-tracker.ts +348 -0
  266. package/src/runtime/wrfc-persistence.ts +4 -4
  267. package/src/shell/blocking-input.ts +92 -2
  268. package/src/shell/recovery-input-helpers.ts +1 -1
  269. package/src/shell/session-views.ts +539 -0
  270. package/src/shell/status-report.ts +86 -0
  271. package/src/shell/transcript-scroll.ts +68 -0
  272. package/src/shell/ui-openers.ts +84 -172
  273. package/src/shell/view-openers.ts +205 -0
  274. package/src/tools/wrfc-agent-guard.ts +22 -2
  275. package/src/utils/format-duration.ts +4 -8
  276. package/src/utils/splash-lines.ts +9 -1
  277. package/src/utils/terminal-width.ts +56 -0
  278. package/src/verification/live-verifier.ts +0 -10
  279. package/src/version.ts +1 -1
  280. package/src/{panels → views}/agent-inspector-shared.ts +2 -2
  281. package/src/{panels → views}/builtin-modals.ts +22 -21
  282. package/src/views/builtin-views.ts +135 -0
  283. package/src/{panels → views}/confirm-state.ts +9 -9
  284. package/src/{panels → views}/diff-review-model.ts +10 -3
  285. package/src/{panels → views}/eval-registry.ts +1 -1
  286. package/src/{panels → views}/fleet-acts.ts +49 -85
  287. package/src/{panels/fleet-panel-format.ts → views/fleet-format.ts} +40 -40
  288. package/src/{panels → views}/fleet-gateway.ts +5 -5
  289. package/src/{panels → views}/fleet-observed-render.ts +16 -16
  290. package/src/{panels → views}/fleet-read-model.ts +9 -9
  291. package/src/{panels/fleet-tabs.ts → views/fleet-session-tabs.ts} +13 -13
  292. package/src/{panels → views}/fleet-spawn.ts +33 -49
  293. package/src/{panels → views}/fleet-steer.ts +12 -12
  294. package/src/{panels → views}/fleet-stop.ts +4 -4
  295. package/src/{panels → views}/fleet-transcript.ts +26 -27
  296. package/src/{panels/fleet-panel-worktree-detail.ts → views/fleet-worktree-detail.ts} +7 -7
  297. package/src/{panels → views}/hosted-session-feed.ts +6 -6
  298. package/src/views/modal-surface-registry.ts +41 -0
  299. package/src/{panels → views}/modals/devices-modal.ts +2 -2
  300. package/src/{panels → views}/modals/hooks-modal.ts +13 -13
  301. package/src/{panels → views}/modals/keybindings-modal.ts +19 -27
  302. package/src/{panels → views}/modals/knowledge-modal.ts +6 -5
  303. package/src/{panels → views}/modals/local-auth-modal.ts +22 -21
  304. package/src/{panels → views}/modals/marketplace-modal.ts +11 -11
  305. package/src/{panels → views}/modals/memory-modal.ts +8 -8
  306. package/src/{panels → views}/modals/modal-surface-helpers.ts +15 -5
  307. package/src/{panels → views}/modals/pairing-modal.ts +9 -9
  308. package/src/{panels → views}/modals/planning-modal.ts +15 -20
  309. package/src/{panels → views}/modals/plugins-modal.ts +6 -6
  310. package/src/{panels → views}/modals/policy-modal.ts +13 -5
  311. package/src/{panels → views}/modals/provider-health-modal.ts +4 -4
  312. package/src/{panels → views}/modals/remote-modal.ts +3 -3
  313. package/src/{panels → views}/modals/sandbox-modal.ts +4 -4
  314. package/src/{panels → views}/modals/security-modal.ts +22 -16
  315. package/src/{panels → views}/modals/services-modal.ts +3 -3
  316. package/src/{panels → views}/modals/settings-sync-modal.ts +4 -4
  317. package/src/{panels → views}/modals/skills-modal.ts +9 -9
  318. package/src/{panels → views}/modals/subscription-modal.ts +3 -3
  319. package/src/{panels → views}/modals/work-plan-modal.ts +10 -13
  320. package/src/views/notifications-feed.ts +314 -0
  321. package/src/{panels → views}/polish-core.ts +38 -36
  322. package/src/{panels → views}/polish-tables.ts +35 -26
  323. package/src/{panels → views}/polish.ts +101 -95
  324. package/src/{panels → views}/project-planning-answer-actions.ts +3 -3
  325. package/src/{panels → views}/provider-health-routes.ts +14 -14
  326. package/src/{panels → views}/search-focus.ts +8 -8
  327. package/src/{panels → views}/session-maintenance.ts +11 -11
  328. package/src/views/skills-discovery.ts +159 -0
  329. package/src/views/view-deps.ts +80 -0
  330. package/src/views/view-panel-adapter.ts +53 -0
  331. package/src/{panels → views}/workstream-graph-render.ts +1 -1
  332. package/src/core/conversation-fold.ts +0 -101
  333. package/src/core/conversation-turn-structure.ts +0 -464
  334. package/src/core/conversation-user-receipts.ts +0 -42
  335. package/src/input/commands/operator-panel-runtime.ts +0 -186
  336. package/src/input/panel-integration-actions.ts +0 -33
  337. package/src/input/panel-mouse-geometry.ts +0 -97
  338. package/src/input/panel-paste-flood-guard.ts +0 -86
  339. package/src/panels/base-panel.ts +0 -301
  340. package/src/panels/builtin/agent.ts +0 -27
  341. package/src/panels/builtin/development.ts +0 -92
  342. package/src/panels/builtin/knowledge.ts +0 -12
  343. package/src/panels/builtin/operations.ts +0 -177
  344. package/src/panels/builtin/session.ts +0 -81
  345. package/src/panels/builtin/shared.ts +0 -357
  346. package/src/panels/builtin-panels.ts +0 -28
  347. package/src/panels/cost-tracker-panel.ts +0 -746
  348. package/src/panels/diff-panel.ts +0 -797
  349. package/src/panels/diff-review-panel.ts +0 -395
  350. package/src/panels/expandable-list-panel.ts +0 -189
  351. package/src/panels/fleet-deep-link.ts +0 -31
  352. package/src/panels/fleet-panel.ts +0 -799
  353. package/src/panels/git-panel.ts +0 -800
  354. package/src/panels/hosted-session-panel.ts +0 -221
  355. package/src/panels/index.ts +0 -21
  356. package/src/panels/local-auth-panel.ts +0 -478
  357. package/src/panels/notifications-feed.ts +0 -127
  358. package/src/panels/notifications-panel.ts +0 -160
  359. package/src/panels/panel-confirm-overlay.ts +0 -72
  360. package/src/panels/panel-manager.ts +0 -791
  361. package/src/panels/plugins-panel.ts +0 -371
  362. package/src/panels/scrollable-list-panel.ts +0 -498
  363. package/src/panels/skills-panel.ts +0 -454
  364. package/src/panels/token-budget-panel.ts +0 -688
  365. package/src/panels/types.ts +0 -191
  366. package/src/panels/wrfc-panel-format.ts +0 -133
  367. package/src/renderer/diff-view.ts +0 -134
  368. package/src/renderer/fleet-tab-strip.ts +0 -56
  369. package/src/renderer/footer-tips.ts +0 -53
  370. package/src/renderer/fullscreen-primitives.ts +0 -146
  371. package/src/renderer/fullscreen-workspace.ts +0 -194
  372. package/src/renderer/hint-grammar.ts +0 -52
  373. package/src/renderer/modal-factory.ts +0 -480
  374. package/src/renderer/overlay-box.ts +0 -154
  375. package/src/renderer/overlay-viewport.ts +0 -104
  376. package/src/renderer/panel-composite.ts +0 -145
  377. package/src/renderer/panel-workspace-bar.ts +0 -54
  378. package/src/renderer/process-indicator.ts +0 -163
  379. package/src/renderer/tab-strip.ts +0 -222
  380. package/src/renderer/text-layout.ts +0 -31
  381. package/src/renderer/tool-call.ts +0 -276
  382. package/src/renderer/tool-result-expanded-lines.ts +0 -48
  383. package/src/renderer/tool-result-summary.ts +0 -219
  384. package/src/runtime/diagnostics/panels/index.ts +0 -21
  385. package/src/runtime/diagnostics/panels/ops.ts +0 -156
  386. package/src/runtime/perf/panel-contracts.ts +0 -32
  387. package/src/runtime/store/domains/panels.ts +0 -117
  388. package/src/verification/verification-ledger.ts +0 -707
  389. /package/src/{panels → views}/memory-consolidation-gateway.ts +0 -0
  390. /package/src/{panels → views}/modals/modal-theme.ts +0 -0
  391. /package/src/{panels → views}/provider-health-domains.ts +0 -0
@@ -0,0 +1,176 @@
1
+ /**
2
+ * work-tree-wiring.ts, connecting the conversation work tree to the live
3
+ * runtime: the timing store fed by tool and turn events, agent lanes read
4
+ * from the agent manager (WRFC chains from the chain controller, cost from
5
+ * the fleet read model), the call a permission prompt is holding, and the
6
+ * per-session fold state.
7
+ *
8
+ * One call from main.ts; everything the work tree reads at render time goes
9
+ * through the WorkTreeSources this installs.
10
+ */
11
+
12
+ import type { AgentManager } from '@pellux/goodvibes-sdk/platform/tools';
13
+ import type { WrfcChain } from '@pellux/goodvibes-sdk/platform/agents';
14
+ import type { ProcessNode } from '@pellux/goodvibes-sdk/platform/runtime/fleet';
15
+ import type { ConversationMessageSnapshot } from '@pellux/goodvibes-sdk/platform/core';
16
+ import type { UiRuntimeEvents } from '@/runtime/index.ts';
17
+ import type { ConversationManager } from './conversation.ts';
18
+ import { WorkTreeTimingStore, userMessageFingerprint, type AgentLaneInfo, type TurnOutcome, type WrfcPhaseInfo } from './work-tree-sources.ts';
19
+ import { loadWorkTreeFolds, loadWorkTreeTurnOutcomes, saveWorkTreeFolds, sweepOrphanWorkTreeFolds } from './work-tree-fold-store.ts';
20
+ import { onSemanticSummaryReady } from '../renderer/lane-graph/semantic-memo.ts';
21
+ import { onSyntaxHighlightReady } from '../renderer/code-block.ts';
22
+
23
+ type AgentRecord = ReturnType<AgentManager['list']>[number];
24
+
25
+ export interface WorkTreeWiringDeps {
26
+ readonly conversation: ConversationManager;
27
+ readonly events: UiRuntimeEvents;
28
+ readonly agentManager: Pick<AgentManager, 'getStatus' | 'getConversationSnapshot'>;
29
+ readonly listChains: () => readonly WrfcChain[];
30
+ readonly fleetNodes: () => readonly ProcessNode[];
31
+ /** The call id a permission prompt is holding, if any. */
32
+ readonly pendingCallId: () => string | undefined;
33
+ readonly turnActive: () => boolean;
34
+ readonly sessionsDir: string;
35
+ readonly sessionId: () => string;
36
+ /** Repaint (a ◈ summary of an opened edit landed). */
37
+ readonly requestRender: () => void;
38
+ }
39
+
40
+ function firstLines(text: string | undefined, n: number): string | undefined {
41
+ if (!text) return undefined;
42
+ const lines = text.split('\n').filter((l) => l.trim().length > 0).slice(0, n);
43
+ return lines.length > 0 ? lines.join('\n') : undefined;
44
+ }
45
+
46
+ /** A finished agent's transcript never changes: read it once. */
47
+ interface SnapshotMemo { readonly stamp: string; readonly messages: readonly ConversationMessageSnapshot[] }
48
+
49
+ export function wireWorkTree(deps: WorkTreeWiringDeps): { readonly timings: WorkTreeTimingStore; readonly unsubs: Array<() => void> } {
50
+ const timings = new WorkTreeTimingStore();
51
+ const unsubs: Array<() => void> = [];
52
+ const { conversation, events } = deps;
53
+
54
+ unsubs.push(events.tools.on('TOOL_EXECUTING', (ev) => timings.callStarted(ev.callId, ev.startedAt)));
55
+ unsubs.push(events.tools.on('TOOL_SUCCEEDED', (ev) => timings.callSettled(ev.callId, ev.durationMs, Date.now())));
56
+ unsubs.push(events.tools.on('TOOL_FAILED', (ev) => timings.callSettled(ev.callId, ev.durationMs, Date.now())));
57
+ unsubs.push(events.tools.on('TOOL_CANCELLED', (ev) => timings.callSettled(ev.callId, undefined, Date.now())));
58
+ // Everything the session sidecar keeps: fold decisions and failed/cancelled turn endings.
59
+ const persistView = (): void => saveWorkTreeFolds(deps.sessionsDir, deps.sessionId(), conversation.workTree.foldState(), conversation.workTree.turnOutcomes());
60
+
61
+ unsubs.push(events.turns.on('TURN_SUBMITTED', () => {
62
+ const snapshot = conversation.getMessageSnapshot();
63
+ for (let i = snapshot.length - 1; i >= 0; i--) {
64
+ if (snapshot[i]!.role === 'user') { timings.turnStarted(i, Date.now(), userMessageFingerprint(snapshot[i])); break; }
65
+ }
66
+ }));
67
+ const endTurn = (outcome: TurnOutcome): void => {
68
+ const ended = timings.turnEnded(Date.now(), outcome);
69
+ // The header drops "working" and states a failure on the next build.
70
+ conversation.workTree.invalidate();
71
+ if (!ended || ended.timing.fingerprint === undefined) return;
72
+ if (conversation.workTree.recordTurnOutcome({ index: ended.index, fingerprint: ended.timing.fingerprint, outcome })) persistView();
73
+ };
74
+ unsubs.push(events.turns.on('TURN_COMPLETED', (ev) => endTurn(ev.stopReason === 'empty_response' ? 'failed' : 'completed')));
75
+ unsubs.push(events.turns.on('TURN_ERROR', () => endTurn('failed')));
76
+ unsubs.push(events.turns.on('TURN_CANCEL', () => endTurn('cancelled')));
77
+ unsubs.push(events.turns.on('PREFLIGHT_FAIL', () => endTurn('failed')));
78
+ // A new or resumed transcript: turn timings are keyed by message index and belong to the old one.
79
+ unsubs.push(conversation.workTree.onReset(() => timings.clear()));
80
+
81
+ const snapshots = new Map<string, SnapshotMemo>();
82
+ const messagesOf = (record: AgentRecord): readonly ConversationMessageSnapshot[] => {
83
+ const finished = record.status !== 'running' && record.status !== 'pending';
84
+ const stamp = `${record.status}:${record.completedAt ?? ''}:${record.toolCallCount}`;
85
+ const memo = snapshots.get(record.id);
86
+ if (finished && memo && memo.stamp === stamp) return memo.messages;
87
+ const messages = deps.agentManager.getConversationSnapshot(record.id);
88
+ if (finished) snapshots.set(record.id, { stamp, messages });
89
+ return messages;
90
+ };
91
+
92
+ const costOf = (agentId: string): number | undefined => {
93
+ const node = deps.fleetNodes().find((n) => n.id === agentId);
94
+ // Only a real reading: an unpriced node has no cost to state.
95
+ return node && node.costState !== 'unpriced' && typeof node.costUsd === 'number' ? node.costUsd : undefined;
96
+ };
97
+
98
+ const wrfcPhases = (record: AgentRecord): { phases: WrfcPhaseInfo[]; passed: boolean | undefined } | undefined => {
99
+ const chain = deps.listChains().find((c) => c.ownerAgentId === record.id || (c.engineerAgentId === record.id && c.ownerAgentId === c.engineerAgentId));
100
+ if (!chain) return undefined;
101
+ const phases: WrfcPhaseInfo[] = [];
102
+ for (const id of chain.allAgentIds) {
103
+ if (id === chain.ownerAgentId && id !== chain.engineerAgentId) continue;
104
+ const phase = deps.agentManager.getStatus(id);
105
+ if (!phase) continue;
106
+ const role = (phase.wrfcRole ?? (id === chain.engineerAgentId ? 'engineer' : id === chain.reviewerAgentId ? 'reviewer' : id === chain.fixerAgentId ? 'fixer' : 'engineer')) as WrfcPhaseInfo['role'];
107
+ const latestReview = role === 'reviewer' && id === chain.reviewerAgentId ? chain.reviewerReport : undefined;
108
+ phases.push({
109
+ agentId: id,
110
+ role,
111
+ task: phase.task,
112
+ status: phase.status,
113
+ startedAt: phase.startedAt,
114
+ completedAt: phase.completedAt,
115
+ findings: latestReview?.issues.map((issue) => issue.description),
116
+ passed: latestReview ? (chain.lastReviewVerdict?.passed ?? latestReview.passed) : undefined,
117
+ output: firstLines(phase.fullOutput, 6),
118
+ });
119
+ }
120
+ phases.sort((a, b) => (a.startedAt ?? 0) - (b.startedAt ?? 0));
121
+ const passed = chain.state === 'passed' ? true : chain.state === 'failed' ? false : undefined;
122
+ return { phases, passed };
123
+ };
124
+
125
+ const agent = (agentId: string): AgentLaneInfo | null => {
126
+ const record = deps.agentManager.getStatus(agentId);
127
+ if (!record) return null;
128
+ const wrfc = wrfcPhases(record);
129
+ return {
130
+ id: record.id,
131
+ name: record.template,
132
+ task: record.task,
133
+ status: record.status,
134
+ startedAt: record.startedAt,
135
+ completedAt: record.completedAt,
136
+ toolCallCount: record.toolCallCount,
137
+ costUsd: costOf(record.id),
138
+ error: record.error,
139
+ messages: wrfc ? [] : messagesOf(record),
140
+ wrfcPhases: wrfc?.phases,
141
+ wrfcPassed: wrfc?.passed,
142
+ };
143
+ };
144
+
145
+ conversation.setWorkTreeSources({
146
+ callTiming: (callId) => timings.callTiming(callId),
147
+ turnTiming: (index) => timings.turnTiming(index),
148
+ agent,
149
+ waitingCallIds: () => {
150
+ const id = deps.pendingCallId();
151
+ return id ? new Set([id]) : new Set<string>();
152
+ },
153
+ turnActive: deps.turnActive,
154
+ turnOutcome: (index) => conversation.workTree.turnOutcome(index),
155
+ now: () => Date.now(),
156
+ });
157
+
158
+ // Fold decisions persist per session, beside the session file.
159
+ conversation.workTree.onFoldChange(persistView);
160
+ sweepOrphanWorkTreeFolds(deps.sessionsDir);
161
+ unsubs.push(onSemanticSummaryReady(() => { conversation.workTree.invalidate(); deps.requestRender(); }));
162
+ // A tree-sitter parse landed: code drawn with the regex placeholder is redrawn with it.
163
+ unsubs.push(onSyntaxHighlightReady(() => { conversation.workTree.invalidate(); deps.requestRender(); }));
164
+ return { timings, unsubs };
165
+ }
166
+
167
+ /**
168
+ * Restore a resumed session's fold decisions and failed/cancelled turn
169
+ * endings (called by the resume routine, after the transcript is restored).
170
+ */
171
+ export function restoreWorkTreeFolds(conversation: ConversationManager, sessionsDir: string, sessionId: string): number {
172
+ const entries = loadWorkTreeFolds(sessionsDir, sessionId);
173
+ conversation.workTree.restoreFoldState(entries);
174
+ conversation.workTree.restoreTurnOutcomes(loadWorkTreeTurnOutcomes(sessionsDir, sessionId));
175
+ return entries.length;
176
+ }
@@ -18,11 +18,17 @@
18
18
  * their reasons, which is the same principle the channel renderer follows: in
19
19
  * plain words, never by an opaque identifier.
20
20
  *
21
+ * The title names the work (owner ruling 2026-09-29): the workstream's task,
22
+ * trimmed at a word boundary, and the body adds the last review score when
23
+ * one was reached. behavior.notificationsMetadataOnly (default off) drops the
24
+ * task, the reason and the score, leaving what kind of stop it was.
25
+ *
21
26
  * Split out of turn-event-wiring.ts as a pure function so the text is testable
22
27
  * on its own, the wiring itself cannot be asserted against without mocking a
23
28
  * process-global notifier.
24
29
  */
25
30
  import { formatTurnBudgetOutcome } from './turn-budget-outcome.ts';
31
+ import { NOTIFICATION_TEXT_LIMITS, trimAtWordBoundary } from '@pellux/goodvibes-sdk/platform/runtime/operations';
26
32
 
27
33
  /** The fields of WORKFLOW_CHAIN_FAILED this narration reads. */
28
34
  export interface WorkstreamFailureNarrationInput {
@@ -32,11 +38,32 @@ export interface WorkstreamFailureNarrationInput {
32
38
  readonly turnLimitSource?: 'default' | 'spawn-override' | 'policy-bound' | undefined;
33
39
  }
34
40
 
41
+ /** What the host remembers about the workstream, and the privacy setting. */
42
+ export interface WorkstreamNotificationContext {
43
+ /** The workstream's task, from WORKFLOW_CHAIN_CREATED. */
44
+ readonly task?: string | null | undefined;
45
+ /** The last review score (out of 10), from WORKFLOW_REVIEW_COMPLETED. */
46
+ readonly reviewScore?: number | null | undefined;
47
+ /** behavior.notificationsMetadataOnly */
48
+ readonly metadataOnly?: boolean | undefined;
49
+ }
50
+
35
51
  export interface WorkstreamNotification {
36
52
  readonly title: string;
37
53
  readonly body: string;
38
54
  }
39
55
 
56
+ function titled(outcome: string, context: WorkstreamNotificationContext): string {
57
+ const prefix = `Workstream ${outcome}: `;
58
+ const task = context.metadataOnly ? '' : trimAtWordBoundary(context.task ?? '', NOTIFICATION_TEXT_LIMITS.desktopTitle - prefix.length);
59
+ return task ? `${prefix}${task}` : `GoodVibes: workstream ${outcome}`;
60
+ }
61
+
62
+ function withScore(body: string, context: WorkstreamNotificationContext): string {
63
+ if (context.metadataOnly || typeof context.reviewScore !== 'number' || !Number.isFinite(context.reviewScore)) return body;
64
+ return `${body} (last review ${Math.round(context.reviewScore * 10) / 10}/10)`;
65
+ }
66
+
40
67
  /**
41
68
  * Title and body for a workstream that reached a terminal state.
42
69
  *
@@ -49,21 +76,24 @@ export interface WorkstreamNotification {
49
76
  */
50
77
  export function workstreamFailureNotification(
51
78
  payload: WorkstreamFailureNarrationInput,
79
+ context: WorkstreamNotificationContext = {},
52
80
  ): WorkstreamNotification {
81
+ const metadataOnly = context.metadataOnly === true;
53
82
  if (payload.failureKind === 'cancelled') {
54
83
  return {
55
- title: 'GoodVibes: workstream cancelled',
56
- body: `Cancelled: ${payload.reason}`,
84
+ title: titled('cancelled', context),
85
+ body: metadataOnly ? 'Cancelled' : withScore(`Cancelled: ${payload.reason}`, context),
57
86
  };
58
87
  }
59
88
  if (payload.failureKind === 'max_turns') {
60
89
  return {
61
- title: 'GoodVibes: workstream hit its turn budget',
62
- body: `The workstream ${formatTurnBudgetOutcome({ limit: payload.turnLimit, source: payload.turnLimitSource })}`,
90
+ title: titled('hit its turn budget', context),
91
+ body: withScore(`The workstream ${formatTurnBudgetOutcome({ limit: payload.turnLimit, source: payload.turnLimitSource })}`, context),
63
92
  };
64
93
  }
94
+ const reason = payload.failureKind === 'transport' ? 'transient transport error' : payload.reason;
65
95
  return {
66
- title: 'GoodVibes: workstream failed',
67
- body: `Failed: ${payload.failureKind === 'transport' ? 'transient transport error' : payload.reason}`,
96
+ title: titled('failed', context),
97
+ body: metadataOnly ? (payload.failureKind === 'transport' ? `Failed: ${reason}` : 'Failed') : withScore(`Failed: ${reason}`, context),
68
98
  };
69
99
  }
@@ -0,0 +1,37 @@
1
+ /**
2
+ * wrfc-notice-titles.ts, the plain title of each review-chain line this
3
+ * terminal writes itself.
4
+ *
5
+ * runtime/bootstrap-core.ts and runtime/wrfc-persistence.ts write a few
6
+ * "[WRFC] …" lines of their own (the SDK's lines are titled by its
7
+ * runtimeEventOfNotice). The notification history keeps each such event as
8
+ * one entry under a plain title, with the line's detail as the body
9
+ * (core/notices.ts). The test feeds every producer's template through
10
+ * shellChainNoticeOf so the lines and this table cannot drift apart.
11
+ */
12
+
13
+ export interface ShellChainNotice {
14
+ readonly title: string;
15
+ readonly level: 'info' | 'warning';
16
+ /** The line without its bracket tag or status mark. */
17
+ readonly detail: string;
18
+ }
19
+
20
+ const SHELL_CHAIN_LINES: ReadonlyArray<{ readonly pattern: RegExp; readonly title: string; readonly level: ShellChainNotice['level'] }> = [
21
+ { pattern: /^\[WRFC\] Engineer enumerated \d+ constraints? for chain \S+/, title: 'Review chain constraints listed', level: 'info' },
22
+ { pattern: /^\[WRFC\] Fix #\d+ targeting \d+ constraints? on chain \S+/, title: 'Review chain fix started', level: 'info' },
23
+ { pattern: /^\[WRFC\] ✗ Chain \S+: \d+ constraint violations? forced failure/, title: 'Review chain failed its constraints', level: 'warning' },
24
+ { pattern: /^\[WRFC\] Guard: .*\(spawn-forced-wrfc\)$/s, title: 'Agent request sent through a review chain', level: 'info' },
25
+ { pattern: /^\[WRFC\] Guard: .*\(spawn-suppressed-wrfc\)$/s, title: 'Agent request kept out of a review chain', level: 'info' },
26
+ { pattern: /^\[WRFC\] Guard: .*\(batch-collapsed-to-wrfc\)$/s, title: 'Agent requests combined into one review chain', level: 'info' },
27
+ { pattern: /^\[WRFC\] Chain \S+ \(.*\) was interrupted by a restart; /s, title: 'Review chain interrupted by a restart', level: 'warning' },
28
+ { pattern: /^\[WRFC\] Pre-router buffer overflowed: /, title: 'Early review chain messages dropped', level: 'warning' },
29
+ ];
30
+
31
+ /** The plain title of a review-chain line this terminal writes, or undefined for any other line. */
32
+ export function shellChainNoticeOf(text: string): ShellChainNotice | undefined {
33
+ const line = text.trim();
34
+ const match = SHELL_CHAIN_LINES.find((entry) => entry.pattern.test(line));
35
+ if (!match) return undefined;
36
+ return { title: match.title, level: match.level, detail: line.replace(/^\[WRFC\]\s*/, '').replace(/^✗\s*/, '') };
37
+ }
@@ -0,0 +1,119 @@
1
+ /**
2
+ * agents-modal-text.ts, the plain-text pieces the Agents modal shows: the
3
+ * hosted session's header facts and rows, and a compact live tail of an
4
+ * agent's conversation for the side column.
5
+ *
6
+ * Every hosted fact is a field of the daemon's own record; nothing is derived
7
+ * here. The tail shows what an agent said and did most recently: its words,
8
+ * the tools it called (→) and the first line of each result (←).
9
+ */
10
+
11
+ import type { ConversationMessageSnapshot } from '@pellux/goodvibes-sdk/platform/core';
12
+ import { createEmptyLine, type Line } from '@pellux/goodvibes-sdk/platform/types';
13
+ import type { HostedSessionFeedState, HostedSessionRow } from '../views/hosted-session-feed.ts';
14
+ import type { AgentsText } from '../renderer/agents-modal.ts';
15
+ import { activeTokens } from '../renderer/theme.ts';
16
+ import { wrapText, getDisplayWidth } from '../utils/terminal-width.ts';
17
+
18
+ /** What the hosted session is, what it is doing, and what leaving it would do. */
19
+ export function hostedHeaderTexts(state: HostedSessionFeedState): AgentsText[] {
20
+ const record = state.record;
21
+ if (!record) return [];
22
+ const lines: AgentsText[] = [
23
+ { text: `${record.status} · ${record.turnCount} turn${record.turnCount === 1 ? '' : 's'} · ${record.messageCount} message${record.messageCount === 1 ? '' : 's'}`, tone: 'text' },
24
+ { text: `workspace ${record.workspaceRoot}`, tone: 'faint' },
25
+ {
26
+ text: `${record.effectiveDetachPolicy === 'survive' ? 'leaving keeps it running (survive)' : 'leaving ends it (kill)'}, ${record.detachPolicy === null ? 'from the setting' : 'set for this session'}`,
27
+ tone: record.effectiveDetachPolicy === 'survive' ? 'success' : 'warning',
28
+ },
29
+ { text: `attached: ${record.attachedClients.length > 0 ? record.attachedClients.join(', ') : 'nobody'}`, tone: 'muted' },
30
+ ];
31
+ if (record.status === 'terminated') lines.push({ text: `ended: ${record.terminatedReason ?? 'no reason recorded'}`, tone: 'error' });
32
+ if (record.restoredFromDisk) lines.push({ text: 'restored from disk after a daemon restart', tone: 'warning' });
33
+ lines.push(state.streaming
34
+ ? { text: 'live event stream open', tone: 'success' }
35
+ : { text: `no live stream: ${state.streamNote ?? 'not subscribed'}`, tone: 'warning' });
36
+ if (state.runningToolCalls.length > 0) {
37
+ lines.push({ text: `running: ${state.runningToolCalls.map((call) => call.tool).join(', ')}`, tone: 'brand' });
38
+ }
39
+ if (state.droppedRows > 0) lines.push({ text: `${state.droppedRows} earlier row(s) dropped; reattach to backfill`, tone: 'faint' });
40
+ return lines;
41
+ }
42
+
43
+ /** The hosted conversation's rows as text. */
44
+ export function hostedRowTexts(rows: readonly HostedSessionRow[]): AgentsText[] {
45
+ return rows.map((row) => {
46
+ const prefix = row.kind === 'user' ? 'you: ' : row.kind === 'tool' ? '' : row.kind === 'error' ? 'error: ' : '';
47
+ const text = `${prefix}${row.text}${row.streaming ? ' …' : ''}`;
48
+ if (row.kind === 'tool') return { text, tone: 'brand', glyph: '→' };
49
+ if (row.kind === 'error') return { text, tone: 'error' };
50
+ if (row.kind === 'user') return { text, tone: 'text', bold: true };
51
+ if (row.kind === 'system') return { text, tone: 'faint' };
52
+ return { text, tone: 'text' };
53
+ });
54
+ }
55
+
56
+ /** The hosted transcript as lines at a width (text from column 5, like the transcript). */
57
+ export function hostedBody(rows: readonly HostedSessionRow[], width: number, height: number): Line[] {
58
+ const t = activeTokens();
59
+ const out: Line[] = [];
60
+ const textWidth = Math.max(8, width - 9);
61
+ for (const item of hostedRowTexts(rows)) {
62
+ const fg = item.tone === 'error' ? t.error : item.tone === 'brand' ? t.brand : item.tone === 'faint' ? t.textFaint : t.text;
63
+ const text = item.glyph ? `${item.glyph} ${item.text}` : item.text;
64
+ for (const part of wrapText(text, textWidth)) {
65
+ const line = createEmptyLine(width);
66
+ let x = 5;
67
+ for (const ch of part) {
68
+ const w = getDisplayWidth(ch);
69
+ if (w <= 0 || x + w > width - 4) break;
70
+ line[x] = { ...line[x]!, char: ch, fg, bold: item.bold === true };
71
+ if (w === 2) line[x + 1] = { ...line[x]!, char: '' };
72
+ x += w;
73
+ }
74
+ out.push(line);
75
+ }
76
+ }
77
+ return out.slice(-Math.max(1, height));
78
+ }
79
+
80
+ function contentText(content: string | ReadonlyArray<{ type: string; text?: string }>): string {
81
+ if (typeof content === 'string') return content;
82
+ return content.map((part) => (part.type === 'text' ? part.text ?? '' : `[${part.type}]`)).join(' ');
83
+ }
84
+
85
+ /** The one argument that says what a tool call is about (a path, a command, a query). */
86
+ function callSubject(args: Record<string, unknown>): string {
87
+ for (const key of ['path', 'file_path', 'filePath', 'command', 'cmd', 'pattern', 'query', 'url', 'name']) {
88
+ const value = args[key];
89
+ if (typeof value === 'string' && value.trim()) return value.trim();
90
+ }
91
+ return '';
92
+ }
93
+
94
+ function firstLine(text: string): string {
95
+ return text.split('\n').find((line) => line.trim().length > 0)?.trim() ?? '';
96
+ }
97
+
98
+ /** The most recent `limit` entries of an agent's conversation, as tail lines. */
99
+ export function transcriptTail(snapshot: readonly ConversationMessageSnapshot[], limit: number): AgentsText[] {
100
+ const out: AgentsText[] = [];
101
+ for (const message of snapshot) {
102
+ if (message.role === 'user') {
103
+ const text = firstLine(contentText(message.content as string));
104
+ if (text) out.push({ text: `you: ${text}`, tone: 'text', bold: true });
105
+ } else if (message.role === 'assistant') {
106
+ const text = message.content.trim();
107
+ if (text) for (const paragraph of text.split(/\n\s*\n/).slice(-2)) out.push({ text: paragraph.replace(/\s+/g, ' ').trim(), tone: 'text' });
108
+ for (const call of message.toolCalls ?? []) {
109
+ const subject = callSubject(call.arguments);
110
+ out.push({ text: subject ? `${call.name} ${subject}` : call.name, tone: 'muted', glyph: '→' });
111
+ }
112
+ } else if (message.role === 'tool') {
113
+ const text = firstLine(message.content);
114
+ out.push({ text: `${message.toolName ?? 'result'}${text ? `: ${text}` : ''}`, tone: 'faint', glyph: '←' });
115
+ }
116
+ }
117
+ if (out.length === 0) out.push({ text: 'No messages yet.', tone: 'faint' });
118
+ return out.slice(-limit);
119
+ }