@pellux/goodvibes-tui 2.0.21 → 2.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (388) hide show
  1. package/CHANGELOG.md +58 -0
  2. package/README.md +16 -16
  3. package/docs/README.md +4 -4
  4. package/docs/foundation-artifacts/operator-contract.json +1 -1
  5. package/package.json +16 -6
  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-registry.ts +77 -35
  52. package/src/input/commands/acp-runtime.ts +19 -9
  53. package/src/input/commands/channel-runtime.ts +2 -2
  54. package/src/input/commands/ci-runtime.ts +1 -1
  55. package/src/input/commands/codebase-runtime.ts +1 -1
  56. package/src/input/commands/config.ts +7 -1
  57. package/src/input/commands/context-window.ts +25 -19
  58. package/src/input/commands/control-room-runtime.ts +18 -18
  59. package/src/input/commands/conversation-runtime.ts +0 -1
  60. package/src/input/commands/cost-runtime.ts +29 -20
  61. package/src/input/commands/diff-runtime.ts +35 -181
  62. package/src/input/commands/eval.ts +1 -1
  63. package/src/input/commands/experience-runtime.ts +3 -7
  64. package/src/input/commands/fresh-conversation.ts +86 -0
  65. package/src/input/commands/git-runtime.ts +5 -22
  66. package/src/input/commands/graph-runtime.ts +1 -1
  67. package/src/input/commands/guidance-runtime.ts +1 -1
  68. package/src/input/commands/health-runtime.ts +8 -9
  69. package/src/input/commands/hooks-runtime.ts +2 -2
  70. package/src/input/commands/hosted-runtime.ts +3 -3
  71. package/src/input/commands/incident-runtime.ts +3 -3
  72. package/src/input/commands/intelligence-runtime.ts +6 -9
  73. package/src/input/commands/knowledge.ts +2 -2
  74. package/src/input/commands/legacy-panel-command.ts +57 -0
  75. package/src/input/commands/local-auth-runtime.ts +8 -9
  76. package/src/input/commands/local-provider-runtime.ts +7 -4
  77. package/src/input/commands/local-runtime.ts +12 -15
  78. package/src/input/commands/local-setup-review.ts +1 -1
  79. package/src/input/commands/local-setup.ts +13 -19
  80. package/src/input/commands/memory.ts +1 -1
  81. package/src/input/commands/notify-runtime.ts +9 -0
  82. package/src/input/commands/operator-runtime.ts +10 -12
  83. package/src/input/commands/permissions-provenance.ts +3 -3
  84. package/src/input/commands/planning-runtime.ts +7 -7
  85. package/src/input/commands/platform-sandbox-runtime.ts +2 -2
  86. package/src/input/commands/platform-services-runtime.ts +1 -1
  87. package/src/input/commands/policy-dispatch.ts +2 -2
  88. package/src/input/commands/policy.ts +3 -3
  89. package/src/input/commands/provider.ts +1 -1
  90. package/src/input/commands/remote-runtime.ts +1 -1
  91. package/src/input/commands/review-runtime.ts +39 -94
  92. package/src/input/commands/rewind-runtime.ts +81 -108
  93. package/src/input/commands/runtime-services.ts +15 -17
  94. package/src/input/commands/services-runtime.ts +2 -2
  95. package/src/input/commands/session-content.ts +21 -5
  96. package/src/input/commands/session-workflow.ts +13 -38
  97. package/src/input/commands/settings-sync-runtime.ts +2 -2
  98. package/src/input/commands/shell-core.ts +19 -4
  99. package/src/input/commands/skills-runtime.ts +2 -2
  100. package/src/input/commands/status-runtime.ts +13 -0
  101. package/src/input/commands/subscription-runtime.ts +1 -1
  102. package/src/input/commands/tasks-runtime.ts +2 -8
  103. package/src/input/commands/work-plan-runtime.ts +4 -4
  104. package/src/input/commands/workstream-attempts.ts +24 -47
  105. package/src/input/commands/workstream-runtime.ts +2 -2
  106. package/src/input/commands/worktree-runtime.ts +3 -3
  107. package/src/input/commands.ts +3 -1
  108. package/src/input/composer-editor.ts +44 -0
  109. package/src/input/concealed-input.ts +1 -1
  110. package/src/input/config-modal-types.ts +20 -8
  111. package/src/input/config-modal.ts +106 -105
  112. package/src/input/confirm-dialog-types.ts +28 -0
  113. package/src/input/confirm-dialog.ts +70 -0
  114. package/src/input/delete-key-policy.ts +3 -3
  115. package/src/input/feed-context-factory.ts +15 -16
  116. package/src/input/handler-command-route.ts +7 -42
  117. package/src/input/handler-content-actions.ts +16 -15
  118. package/src/input/handler-feed-routes.ts +26 -211
  119. package/src/input/handler-feed.ts +68 -95
  120. package/src/input/handler-interactions.ts +5 -3
  121. package/src/input/handler-modal-routes.ts +215 -216
  122. package/src/input/handler-modal-stack.ts +81 -11
  123. package/src/input/handler-modal-token-routes.ts +4 -0
  124. package/src/input/handler-onboarding.ts +0 -1
  125. package/src/input/handler-picker-routes.ts +65 -107
  126. package/src/input/handler-session-view-route.ts +149 -0
  127. package/src/input/handler-shortcuts.ts +16 -154
  128. package/src/input/handler-types.ts +4 -2
  129. package/src/input/handler-ui-state.ts +45 -15
  130. package/src/input/handler-work-tree-route.ts +123 -0
  131. package/src/input/handler.ts +25 -70
  132. package/src/input/keybindings.ts +16 -71
  133. package/src/input/masked-entry-modal.ts +117 -0
  134. package/src/input/mcp-workspace.ts +48 -17
  135. package/src/input/model-picker-filter.ts +8 -0
  136. package/src/input/model-picker-open.ts +99 -0
  137. package/src/input/model-picker-types.ts +1 -1
  138. package/src/input/model-picker.ts +92 -9
  139. package/src/input/notifications-modal.ts +123 -0
  140. package/src/input/overlay-filter.ts +26 -0
  141. package/src/input/profile-picker-modal.ts +37 -8
  142. package/src/input/search.ts +3 -3
  143. package/src/input/selection-modal-queue.ts +2 -0
  144. package/src/input/selection-modal.ts +28 -16
  145. package/src/input/session-picker-modal.ts +33 -8
  146. package/src/input/settings-modal-activation.ts +7 -2
  147. package/src/input/settings-modal-data.ts +11 -6
  148. package/src/input/settings-modal-types.ts +1 -1
  149. package/src/input/settings-modal.ts +20 -20
  150. package/src/input/submission-intent.ts +1 -1
  151. package/src/input/submission-router.ts +4 -9
  152. package/src/input/surface-modal-host.ts +110 -0
  153. package/src/input/theme-settings-actions.ts +82 -0
  154. package/src/input/usage-modal.ts +183 -0
  155. package/src/input/views.ts +97 -0
  156. package/src/main.ts +175 -165
  157. package/src/permissions/hunk-selection.ts +1 -1
  158. package/src/permissions/prompt-card.ts +511 -0
  159. package/src/permissions/prompt.ts +59 -622
  160. package/src/renderer/agents-modal.ts +310 -0
  161. package/src/renderer/autocomplete-overlay.ts +42 -220
  162. package/src/renderer/back-to-bottom.ts +80 -0
  163. package/src/renderer/block-actions-overlay.ts +30 -48
  164. package/src/renderer/bookmark-modal.ts +48 -79
  165. package/src/renderer/changes-modal.ts +325 -0
  166. package/src/renderer/code-block.ts +184 -106
  167. package/src/renderer/command-palette.ts +93 -0
  168. package/src/renderer/composer-fragments.ts +4 -4
  169. package/src/renderer/composer.ts +146 -0
  170. package/src/renderer/compositor.ts +26 -160
  171. package/src/renderer/config-modal.ts +185 -81
  172. package/src/renderer/confirm-dialog.ts +50 -0
  173. package/src/renderer/context-inspector.ts +116 -154
  174. package/src/renderer/context-status-hint.ts +4 -4
  175. package/src/renderer/conversation-layout.ts +20 -0
  176. package/src/renderer/conversation-overlays.ts +94 -84
  177. package/src/renderer/conversation-surface.ts +19 -38
  178. package/src/renderer/diff-tint.ts +98 -0
  179. package/src/renderer/file-picker-overlay.ts +63 -90
  180. package/src/renderer/footer-targets.ts +38 -0
  181. package/src/renderer/git-status.ts +3 -4
  182. package/src/renderer/header-line.ts +140 -117
  183. package/src/renderer/help-overlay.ts +133 -219
  184. package/src/renderer/history-search-overlay.ts +15 -12
  185. package/src/renderer/lane-graph/bead.ts +800 -0
  186. package/src/renderer/lane-graph/glyphs.ts +134 -0
  187. package/src/renderer/lane-graph/layout.ts +248 -0
  188. package/src/renderer/lane-graph/paint.ts +537 -0
  189. package/src/renderer/lane-graph/semantic-memo.ts +77 -0
  190. package/src/renderer/layout-engine.ts +2 -57
  191. package/src/renderer/layout.ts +7 -13
  192. package/src/renderer/markdown-inline.ts +2 -1
  193. package/src/renderer/markdown-table.ts +17 -12
  194. package/src/renderer/markdown.ts +92 -34
  195. package/src/renderer/masked-entry-modal.ts +78 -0
  196. package/src/renderer/mcp-workspace.ts +166 -203
  197. package/src/renderer/model-workspace.ts +381 -436
  198. package/src/renderer/notifications-modal.ts +116 -0
  199. package/src/renderer/onboarding/onboarding-wizard.ts +178 -588
  200. package/src/renderer/process-view.ts +192 -0
  201. package/src/renderer/profile-picker-modal.ts +58 -110
  202. package/src/renderer/search-overlay.ts +6 -3
  203. package/src/renderer/selection-modal-overlay.ts +87 -327
  204. package/src/renderer/semantic-diff.ts +2 -2
  205. package/src/renderer/session-chips.ts +102 -0
  206. package/src/renderer/session-picker-modal.ts +177 -297
  207. package/src/renderer/settings-modal-connections.ts +14 -23
  208. package/src/renderer/settings-modal-context.ts +367 -0
  209. package/src/renderer/settings-modal-helpers.ts +6 -11
  210. package/src/renderer/settings-modal.ts +322 -726
  211. package/src/renderer/shell-surface.ts +277 -175
  212. package/src/renderer/shortcuts-overlay.ts +217 -0
  213. package/src/renderer/status-line.ts +475 -0
  214. package/src/renderer/surface-compose.ts +174 -0
  215. package/src/renderer/surface-kit-extra.ts +303 -0
  216. package/src/renderer/surface-kit-list.ts +366 -0
  217. package/src/renderer/surface-kit-parts.ts +360 -0
  218. package/src/renderer/surface-kit.ts +568 -0
  219. package/src/renderer/syntax-highlighter.ts +280 -197
  220. package/src/renderer/syntax-theme.ts +51 -0
  221. package/src/renderer/system-message.ts +0 -1
  222. package/src/renderer/term-caps.ts +42 -1
  223. package/src/renderer/terminal-bg-probe.ts +116 -19
  224. package/src/renderer/terminal-palette-probe.ts +322 -0
  225. package/src/renderer/terminal-palette-reader.ts +183 -0
  226. package/src/renderer/terminal-palette.ts +58 -0
  227. package/src/renderer/theme-mode-config.ts +16 -1
  228. package/src/renderer/theme.ts +234 -207
  229. package/src/renderer/thinking.ts +0 -1
  230. package/src/renderer/throbber.ts +188 -0
  231. package/src/renderer/toast-center.ts +119 -0
  232. package/src/renderer/ui-factory.ts +39 -602
  233. package/src/renderer/ui-primitives.ts +8 -25
  234. package/src/renderer/usage-modal.ts +353 -0
  235. package/src/renderer/user-message.ts +58 -0
  236. package/src/renderer/voice-capture-chip.ts +53 -0
  237. package/src/runtime/bootstrap-command-context.ts +0 -5
  238. package/src/runtime/bootstrap-command-parts.ts +38 -89
  239. package/src/runtime/bootstrap-core.ts +13 -15
  240. package/src/runtime/bootstrap-hook-bridge.ts +12 -21
  241. package/src/runtime/bootstrap-notifier-sync.ts +17 -1
  242. package/src/runtime/bootstrap-shell.ts +16 -50
  243. package/src/runtime/bootstrap.ts +14 -14
  244. package/src/runtime/client/fleet-union.ts +5 -5
  245. package/src/runtime/client/hosted-exit.ts +2 -2
  246. package/src/runtime/client/hosted-sessions.ts +1 -1
  247. package/src/runtime/client/spine-adoption.ts +1 -1
  248. package/src/runtime/context-accounting-source.ts +5 -4
  249. package/src/runtime/index.ts +1 -0
  250. package/src/runtime/notification-dispatch.ts +102 -29
  251. package/src/runtime/recovery-offer-wiring.ts +4 -1
  252. package/src/runtime/recovery-prompt.ts +31 -15
  253. package/src/runtime/runtime-services-types.ts +10 -6
  254. package/src/runtime/services.ts +17 -12
  255. package/src/runtime/session-continuity-hints.ts +2 -5
  256. package/src/runtime/session-inbound-inputs.ts +1 -1
  257. package/src/runtime/session-resume-liveness-confirm.ts +14 -2
  258. package/src/runtime/startup-typeahead-gate.ts +103 -0
  259. package/src/runtime/store/domains/domain-read-matrix.ts +0 -1
  260. package/src/runtime/store/domains/index.ts +0 -8
  261. package/src/runtime/store/selectors/index.ts +0 -28
  262. package/src/runtime/store/state.ts +0 -8
  263. package/src/runtime/ui-services.ts +4 -6
  264. package/src/runtime/usage-tracker.ts +348 -0
  265. package/src/runtime/wrfc-persistence.ts +4 -4
  266. package/src/shell/blocking-input.ts +92 -2
  267. package/src/shell/recovery-input-helpers.ts +1 -1
  268. package/src/shell/session-views.ts +539 -0
  269. package/src/shell/status-report.ts +86 -0
  270. package/src/shell/transcript-scroll.ts +68 -0
  271. package/src/shell/ui-openers.ts +84 -172
  272. package/src/shell/view-openers.ts +205 -0
  273. package/src/tools/wrfc-agent-guard.ts +22 -2
  274. package/src/utils/format-duration.ts +4 -8
  275. package/src/utils/splash-lines.ts +9 -1
  276. package/src/utils/terminal-width.ts +56 -0
  277. package/src/verification/verification-ledger.ts +18 -16
  278. package/src/{panels → views}/agent-inspector-shared.ts +2 -2
  279. package/src/{panels → views}/builtin-modals.ts +22 -21
  280. package/src/views/builtin-views.ts +135 -0
  281. package/src/{panels → views}/confirm-state.ts +9 -9
  282. package/src/{panels → views}/diff-review-model.ts +10 -3
  283. package/src/{panels → views}/eval-registry.ts +1 -1
  284. package/src/{panels → views}/fleet-acts.ts +49 -85
  285. package/src/{panels/fleet-panel-format.ts → views/fleet-format.ts} +40 -40
  286. package/src/{panels → views}/fleet-gateway.ts +5 -5
  287. package/src/{panels → views}/fleet-observed-render.ts +16 -16
  288. package/src/{panels → views}/fleet-read-model.ts +9 -9
  289. package/src/{panels/fleet-tabs.ts → views/fleet-session-tabs.ts} +13 -13
  290. package/src/{panels → views}/fleet-spawn.ts +33 -49
  291. package/src/{panels → views}/fleet-steer.ts +12 -12
  292. package/src/{panels → views}/fleet-stop.ts +4 -4
  293. package/src/{panels → views}/fleet-transcript.ts +26 -27
  294. package/src/{panels/fleet-panel-worktree-detail.ts → views/fleet-worktree-detail.ts} +7 -7
  295. package/src/{panels → views}/hosted-session-feed.ts +6 -6
  296. package/src/views/modal-surface-registry.ts +41 -0
  297. package/src/{panels → views}/modals/devices-modal.ts +2 -2
  298. package/src/{panels → views}/modals/hooks-modal.ts +13 -13
  299. package/src/{panels → views}/modals/keybindings-modal.ts +19 -27
  300. package/src/{panels → views}/modals/knowledge-modal.ts +6 -5
  301. package/src/{panels → views}/modals/local-auth-modal.ts +22 -21
  302. package/src/{panels → views}/modals/marketplace-modal.ts +11 -11
  303. package/src/{panels → views}/modals/memory-modal.ts +8 -8
  304. package/src/{panels → views}/modals/modal-surface-helpers.ts +15 -5
  305. package/src/{panels → views}/modals/pairing-modal.ts +9 -9
  306. package/src/{panels → views}/modals/planning-modal.ts +15 -20
  307. package/src/{panels → views}/modals/plugins-modal.ts +6 -6
  308. package/src/{panels → views}/modals/policy-modal.ts +13 -5
  309. package/src/{panels → views}/modals/provider-health-modal.ts +4 -4
  310. package/src/{panels → views}/modals/remote-modal.ts +3 -3
  311. package/src/{panels → views}/modals/sandbox-modal.ts +4 -4
  312. package/src/{panels → views}/modals/security-modal.ts +22 -16
  313. package/src/{panels → views}/modals/services-modal.ts +3 -3
  314. package/src/{panels → views}/modals/settings-sync-modal.ts +4 -4
  315. package/src/{panels → views}/modals/skills-modal.ts +9 -9
  316. package/src/{panels → views}/modals/subscription-modal.ts +3 -3
  317. package/src/{panels → views}/modals/work-plan-modal.ts +10 -13
  318. package/src/views/notifications-feed.ts +314 -0
  319. package/src/{panels → views}/polish-core.ts +38 -36
  320. package/src/{panels → views}/polish-tables.ts +35 -26
  321. package/src/{panels → views}/polish.ts +101 -95
  322. package/src/{panels → views}/project-planning-answer-actions.ts +3 -3
  323. package/src/{panels → views}/provider-health-routes.ts +14 -14
  324. package/src/{panels → views}/search-focus.ts +8 -8
  325. package/src/{panels → views}/session-maintenance.ts +11 -11
  326. package/src/views/skills-discovery.ts +159 -0
  327. package/src/views/view-deps.ts +80 -0
  328. package/src/views/view-panel-adapter.ts +53 -0
  329. package/src/{panels → views}/workstream-graph-render.ts +1 -1
  330. package/src/core/conversation-fold.ts +0 -101
  331. package/src/core/conversation-turn-structure.ts +0 -464
  332. package/src/core/conversation-user-receipts.ts +0 -42
  333. package/src/input/commands/operator-panel-runtime.ts +0 -186
  334. package/src/input/panel-integration-actions.ts +0 -33
  335. package/src/input/panel-mouse-geometry.ts +0 -97
  336. package/src/input/panel-paste-flood-guard.ts +0 -86
  337. package/src/panels/base-panel.ts +0 -301
  338. package/src/panels/builtin/agent.ts +0 -27
  339. package/src/panels/builtin/development.ts +0 -92
  340. package/src/panels/builtin/knowledge.ts +0 -12
  341. package/src/panels/builtin/operations.ts +0 -177
  342. package/src/panels/builtin/session.ts +0 -81
  343. package/src/panels/builtin/shared.ts +0 -357
  344. package/src/panels/builtin-panels.ts +0 -28
  345. package/src/panels/cost-tracker-panel.ts +0 -746
  346. package/src/panels/diff-panel.ts +0 -797
  347. package/src/panels/diff-review-panel.ts +0 -395
  348. package/src/panels/expandable-list-panel.ts +0 -189
  349. package/src/panels/fleet-deep-link.ts +0 -31
  350. package/src/panels/fleet-panel.ts +0 -799
  351. package/src/panels/git-panel.ts +0 -800
  352. package/src/panels/hosted-session-panel.ts +0 -221
  353. package/src/panels/index.ts +0 -21
  354. package/src/panels/local-auth-panel.ts +0 -478
  355. package/src/panels/notifications-feed.ts +0 -127
  356. package/src/panels/notifications-panel.ts +0 -160
  357. package/src/panels/panel-confirm-overlay.ts +0 -72
  358. package/src/panels/panel-manager.ts +0 -791
  359. package/src/panels/plugins-panel.ts +0 -371
  360. package/src/panels/scrollable-list-panel.ts +0 -498
  361. package/src/panels/skills-panel.ts +0 -454
  362. package/src/panels/token-budget-panel.ts +0 -688
  363. package/src/panels/types.ts +0 -191
  364. package/src/panels/wrfc-panel-format.ts +0 -133
  365. package/src/renderer/diff-view.ts +0 -134
  366. package/src/renderer/fleet-tab-strip.ts +0 -56
  367. package/src/renderer/footer-tips.ts +0 -53
  368. package/src/renderer/fullscreen-primitives.ts +0 -146
  369. package/src/renderer/fullscreen-workspace.ts +0 -194
  370. package/src/renderer/hint-grammar.ts +0 -52
  371. package/src/renderer/modal-factory.ts +0 -480
  372. package/src/renderer/overlay-box.ts +0 -154
  373. package/src/renderer/overlay-viewport.ts +0 -104
  374. package/src/renderer/panel-composite.ts +0 -145
  375. package/src/renderer/panel-workspace-bar.ts +0 -54
  376. package/src/renderer/process-indicator.ts +0 -163
  377. package/src/renderer/tab-strip.ts +0 -222
  378. package/src/renderer/text-layout.ts +0 -31
  379. package/src/renderer/tool-call.ts +0 -276
  380. package/src/renderer/tool-result-expanded-lines.ts +0 -48
  381. package/src/renderer/tool-result-summary.ts +0 -219
  382. package/src/runtime/diagnostics/panels/index.ts +0 -21
  383. package/src/runtime/diagnostics/panels/ops.ts +0 -156
  384. package/src/runtime/perf/panel-contracts.ts +0 -32
  385. package/src/runtime/store/domains/panels.ts +0 -117
  386. /package/src/{panels → views}/memory-consolidation-gateway.ts +0 -0
  387. /package/src/{panels → views}/modals/modal-theme.ts +0 -0
  388. /package/src/{panels → views}/provider-health-domains.ts +0 -0
@@ -11,17 +11,33 @@ import type { SessionSurface } from '@/runtime/index.ts';
11
11
  import { journalPathFor, openTranscriptJournal, type TranscriptJournal } from '@pellux/goodvibes-sdk/platform/runtime/operations';
12
12
  import type { WebhookNotifier } from '@pellux/goodvibes-sdk/platform/integrations';
13
13
  import { notifyCompletion } from '@pellux/goodvibes-sdk/platform/utils';
14
- import { maybeNotifyLongTask, readNotifyAfterSeconds, type LongTaskStatus } from './long-task-notifier.ts';
14
+ import { maybeNotifyLongTask, readNotifyAfterSeconds } from './long-task-notifier.ts';
15
15
  import type { FocusTracker } from '@pellux/goodvibes-sdk/platform/runtime/operations';
16
- import { shouldFireAlert, FORCE_NOTIFY_DURATION_MS } from '@pellux/goodvibes-sdk/platform/runtime/operations';
16
+ import {
17
+ shouldFireAlert,
18
+ FORCE_NOTIFY_DURATION_MS,
19
+ NOTIFICATION_TEXT_LIMITS,
20
+ TurnActivityTally,
21
+ buildTurnNotificationLine,
22
+ readNotificationsMetadataOnly,
23
+ resolveTurnName,
24
+ trimAtWordBoundary,
25
+ type TurnOutcome,
26
+ } from '@pellux/goodvibes-sdk/platform/runtime/operations';
17
27
  import { createBudgetBreachNotifier, type BudgetBreachNotifier } from './budget-breach-notifier.ts';
18
28
  import { readBudgetAlertUsd } from '@pellux/goodvibes-sdk/platform/providers';
19
29
 
20
30
  /** Minimal orchestrator surface required by turn-event wiring. */
21
31
  interface TurnOrchestrator {
22
32
  readonly lastInputTokens: number;
23
- /** Cumulative session usage, same object CostTrackerPanel reads, used here for budget-breach checks. */
33
+ /** Cumulative session usage, same object the usage modal reads, used here for budget-breach checks. */
24
34
  readonly usage: { readonly input: number; readonly output: number; readonly cacheRead: number; readonly cacheWrite: number };
35
+ /**
36
+ * The SDK Orchestrator's own end-of-turn popup; handOff() silences it while
37
+ * this wiring's long-task notice owns the popup and returns the release.
38
+ * Optional so bare test doubles still satisfy the type.
39
+ */
40
+ readonly turnEndNotice?: { handOff(): () => void };
25
41
  }
26
42
 
27
43
  /** Minimal provider registry surface required by turn-event wiring. */
@@ -99,6 +115,22 @@ export interface WireTurnEventHandlersOptions {
99
115
  * Absent in tests/headless.
100
116
  */
101
117
  readonly terminalNotifier?: import('./terminal-notifier.ts').TerminalNotifier | null;
118
+ /**
119
+ * Desktop delivery for the turn, budget, agent and workstream notices wired
120
+ * here; defaults to the SDK notifyCompletion. Tests pass a spy to read the text.
121
+ */
122
+ readonly notifyDesktop?: typeof notifyCompletion;
123
+ /**
124
+ * The terminal bell for a turn over 5s that did not reach the desktop popup
125
+ * (the popup rings it itself); defaults to writing BEL to stdout, which is
126
+ * what the SDK Orchestrator's own popup did before this wiring took it over.
127
+ */
128
+ readonly ringBell?: () => void;
129
+ /**
130
+ * How long a failover retry may take to start before the failed turn it
131
+ * replaces is told as failed after all. @internal, tests only.
132
+ */
133
+ readonly _failoverRetryGraceMs?: number;
102
134
  /**
103
135
  * Minimal test seam: injectable clock for controlling Date.now() in tests.
104
136
  * Defaults to the real Date.now when absent.
@@ -133,6 +165,35 @@ function wrapJournalWithSessionRebind(
133
165
  };
134
166
  }
135
167
 
168
+ /**
169
+ * How long a failover retry may take to reach TURN_SUBMITTED (it waits on the
170
+ * memory-recall refresh first) before the failure it replaced is told anyway.
171
+ */
172
+ const FAILOVER_RETRY_GRACE_MS = 30_000;
173
+
174
+ /** The Orchestrator's own popup rang the bell for a turn over this long. */
175
+ const TURN_BELL_AFTER_MS = 5_000;
176
+
177
+ /** The Orchestrator's own popup came for a turn over this long (SDK notifyCompletion). */
178
+ const TURN_DESKTOP_AFTER_MS = 30_000;
179
+
180
+ /** Most agent/workstream names kept at once; a lost terminal event cannot grow the maps unbounded. */
181
+ const MAX_REMEMBERED_NAMES = 256;
182
+
183
+ function rememberBounded<V>(map: Map<string, V>, id: string, value: V): void {
184
+ map.delete(id);
185
+ map.set(id, value);
186
+ while (map.size > MAX_REMEMBERED_NAMES) {
187
+ const oldest = map.keys().next().value;
188
+ if (oldest === undefined) break;
189
+ map.delete(oldest);
190
+ }
191
+ }
192
+
193
+ function normalizeTitleSource(source: unknown): 'user' | 'system' | null {
194
+ return source === 'user' || source === 'system' ? source : null;
195
+ }
196
+
136
197
  export interface WireTurnEventHandlersResult {
137
198
  /** Trigger a git status refresh; may be called from external code after tool execution. */
138
199
  readonly refreshGit: () => void;
@@ -140,6 +201,14 @@ export interface WireTurnEventHandlersResult {
140
201
  readonly unsubs: ReadonlyArray<() => void>;
141
202
  /** The per-session transcript journal; call appendRecord() for user-submitted events. */
142
203
  readonly transcriptJournal: TranscriptJournal;
204
+ /**
205
+ * The failover path re-submits the failed turn on another provider (see
206
+ * stream-event-wiring.ts onFailoverRetry). Call it synchronously from that
207
+ * TURN_ERROR: the failure notice for the turn is withheld, and the retry's
208
+ * TURN_SUBMITTED continues the same user turn, so the user gets one notice
209
+ * for how it finally ended.
210
+ */
211
+ readonly continueTurnAfterFailover: () => void;
143
212
  }
144
213
 
145
214
  /**
@@ -166,12 +235,23 @@ export function wireTurnEventHandlers(
166
235
  surface, gitStatusProvider,
167
236
  lastGitInfoRef, buildSessionContinuityHints, render, webhookNotifier, focusTracker,
168
237
  terminalNotifier, runtimeBus, systemMessageRouter,
238
+ notifyDesktop = notifyCompletion,
239
+ ringBell = () => { process.stdout.write('\x07'); },
240
+ _failoverRetryGraceMs = FAILOVER_RETRY_GRACE_MS,
169
241
  _clock = Date.now,
170
242
  } = options;
171
243
 
172
244
  const unsubs: Array<() => void> = [];
173
245
  const configGet = (k: string): unknown => configManager.get(k as Parameters<typeof configManager.get>[0]);
174
246
 
247
+ // ONE POPUP PER TURN. The long-task notice below owns the turn's desktop
248
+ // popup: it names the turn, counts what it did, honors
249
+ // behavior.notifyAfterSeconds and the unfocused-only gate, and reaches the
250
+ // webhook. The SDK Orchestrator pops its own end-of-turn notice too, so it
251
+ // is handed off here for as long as this wiring lives.
252
+ const releaseTurnEndNotice = orchestrator.turnEndNotice?.handOff();
253
+ if (releaseTurnEndNotice) unsubs.push(releaseTurnEndNotice);
254
+
175
255
  // Create the per-session transcript journal. Path mirrors recovery-file
176
256
  // convention (homeDirectory-scoped). Created lazily on first append.
177
257
  //
@@ -192,47 +272,162 @@ export function wireTurnEventHandlers(
192
272
  // Track turn start time for long-task notification threshold.
193
273
  let turnStartTime: number | null = null;
194
274
 
275
+ // What names the turn and what it did, for the end-of-turn notices
276
+ // (SDK runtime/turn-notification.ts): the submitted text, and a tally of
277
+ // tool calls, changed files, agents started and the latest review score.
278
+ let turnText: string | null = null;
279
+ let lastTurnName: string | null = null;
280
+ let lastEndedTurnId: string | null = null;
281
+ const tally = new TurnActivityTally();
282
+ const nameCurrentTurn = (): string | null => resolveTurnName({
283
+ title: conversation.title,
284
+ titleSource: normalizeTitleSource(conversation.getTitleSource()),
285
+ turnText,
286
+ });
287
+
288
+ // Task text for agents and workstreams, from their opening events; the
289
+ // terminal events carry only the id. Bounded, dropped at the terminal event.
290
+ const agentTasks = new Map<string, string>();
291
+ const workstreams = new Map<string, { task: string; reviewScore: number | null }>();
292
+
195
293
  // Budget-breach edge-trigger checker, one instance per session,
196
294
  // piggybacking on the same TURN_COMPLETED handler as the long-task
197
295
  // notification below rather than adding a second TURN_COMPLETED subscription.
198
296
  const budgetBreachNotifier: BudgetBreachNotifier | null = focusTracker
199
- ? createBudgetBreachNotifier({ focusTracker, configGet, webhookNotifier: webhookNotifier ?? null, sessionId: runtime.sessionId })
297
+ ? createBudgetBreachNotifier({
298
+ focusTracker, configGet, webhookNotifier: webhookNotifier ?? null, sessionId: runtime.sessionId,
299
+ getTurnName: () => lastTurnName, notifyDesktop,
300
+ })
200
301
  : null;
201
302
 
303
+ // Provider failover (stream-event-wiring.ts) re-submits a failed turn on
304
+ // another provider. The user asked once, so the user turn spans every
305
+ // attempt: its failure is withheld while the retry is on its way, and the
306
+ // retry's TURN_SUBMITTED continues it (same start time, tally and name).
307
+ let failoverRetryPending = false;
308
+ let withheldFailure: { turnId: string; reason: string | null; timer: ReturnType<typeof setTimeout> } | null = null;
309
+ const clearWithheldFailure = (): void => {
310
+ if (withheldFailure) clearTimeout(withheldFailure.timer);
311
+ withheldFailure = null;
312
+ };
313
+ const continueTurnAfterFailover = (): void => {
314
+ failoverRetryPending = true;
315
+ };
316
+
317
+ /**
318
+ * The end of a turn, however it ended: the in-terminal (OSC 9) notice on
319
+ * its own per-signal config + focus gate, then the long-task desktop and
320
+ * webhook notice past behavior.notifyAfterSeconds. Once per turnId.
321
+ */
322
+ const notifyTurnEnd = (turnId: string, outcome: TurnOutcome, reason: string | null): void => {
323
+ if (turnId === lastEndedTurnId) return;
324
+ lastEndedTurnId = turnId;
325
+ clearWithheldFailure();
326
+ failoverRetryPending = false;
327
+ const turnElapsedMs = turnStartTime !== null ? _clock() - turnStartTime : 0;
328
+ turnStartTime = null;
329
+ const name = nameCurrentTurn();
330
+ lastTurnName = name;
331
+ const activity = tally.snapshot();
332
+ const metadataOnly = readNotificationsMetadataOnly(configGet);
333
+ terminalNotifier?.notify('turn-end', buildTurnNotificationLine({
334
+ outcome, elapsedMs: turnElapsedMs, name, reason, sessionId: runtime.sessionId, ...activity,
335
+ }, { metadataOnly }, NOTIFICATION_TEXT_LIMITS.terminal));
336
+ const notifyOnComplete = configGet('behavior.notifyOnComplete') !== false;
337
+ const popped = maybeNotifyLongTask({
338
+ elapsedMs: turnElapsedMs,
339
+ status: outcome === 'completed' ? 'ok' : 'fail',
340
+ outcome,
341
+ name,
342
+ reason,
343
+ activity,
344
+ metadataOnly,
345
+ kind: 'turn',
346
+ sessionId: runtime.sessionId,
347
+ thresholdSeconds: readNotifyAfterSeconds(configGet),
348
+ webhookNotifier: webhookNotifier ?? null,
349
+ focusTracker,
350
+ configGet,
351
+ notifyDesktop,
352
+ desktop: notifyOnComplete,
353
+ // The SDK popup this replaces came past 30s whatever notifyAfterSeconds
354
+ // said (default 60): the one popup keeps that, the webhook keeps the threshold.
355
+ desktopAfterMs: TURN_DESKTOP_AFTER_MS,
356
+ });
357
+ // The popup rings the bell itself; a shorter turn still gets the bell the
358
+ // Orchestrator's own popup gave it (behavior.notifyOnComplete).
359
+ if (!popped && notifyOnComplete && turnElapsedMs > TURN_BELL_AFTER_MS) {
360
+ try { ringBell(); } catch { /* stdout may already be torn down */ }
361
+ }
362
+ };
363
+
364
+ /**
365
+ * A TURN_ERROR. The failover path decides synchronously, in its own
366
+ * TURN_ERROR handler, whether to re-submit the turn, and this handler runs
367
+ * first (main.ts wires it first), so the notice waits a microtask for that
368
+ * decision. Withheld while a retry is on its way; told anyway if the retry
369
+ * has not started within the grace period.
370
+ */
371
+ const onTurnError = (turnId: string, reason: string | null): void => {
372
+ queueMicrotask(() => {
373
+ if (!failoverRetryPending) {
374
+ notifyTurnEnd(turnId, 'failed', reason);
375
+ return;
376
+ }
377
+ clearWithheldFailure();
378
+ const timer = setTimeout(() => {
379
+ if (withheldFailure?.turnId !== turnId) return;
380
+ notifyTurnEnd(turnId, 'failed', reason);
381
+ }, _failoverRetryGraceMs);
382
+ (timer as { unref?: () => void }).unref?.();
383
+ withheldFailure = { turnId, reason, timer };
384
+ });
385
+ };
386
+ unsubs.push(clearWithheldFailure);
387
+
202
388
  const refreshGit = (): void => {
203
389
  gitStatusProvider.refresh().then((info) => { lastGitInfoRef.value = info; render(); }).catch(() => { /* non-fatal */ });
204
390
  };
205
391
 
206
392
  // Journal user message immediately on TURN_SUBMITTED so a SIGKILL during
207
393
  // the subsequent stream loses at most the in-flight token chunk.
208
- unsubs.push(events.turns.on('TURN_SUBMITTED', () => {
209
- turnStartTime = _clock();
394
+ unsubs.push(events.turns.on('TURN_SUBMITTED', (evt) => {
395
+ if (failoverRetryPending && withheldFailure !== null) {
396
+ // The failover's re-submission: the same user turn goes on.
397
+ clearWithheldFailure();
398
+ failoverRetryPending = false;
399
+ } else {
400
+ clearWithheldFailure();
401
+ failoverRetryPending = false;
402
+ turnStartTime = _clock();
403
+ turnText = typeof evt?.prompt === 'string' ? evt.prompt : null;
404
+ tally.reset();
405
+ }
210
406
  try {
211
407
  const snap = conversation.toJSON() as { messages: Array<import('./conversation.ts').ConversationMessageSnapshot> };
212
408
  transcriptJournal.appendRecord('user_message', snap.messages);
213
409
  } catch { /* best-effort */ }
214
410
  }));
215
411
 
412
+ // A turn that failed or was cancelled ends without TURN_COMPLETED; it is
413
+ // named and told the same way.
414
+ unsubs.push(events.turns.on('TURN_ERROR', (evt) => {
415
+ onTurnError(evt.turnId, evt.error);
416
+ }));
417
+ unsubs.push(events.turns.on('PREFLIGHT_FAIL', (evt) => {
418
+ notifyTurnEnd(evt.turnId, 'failed', evt.reason);
419
+ }));
420
+ unsubs.push(events.turns.on('TURN_CANCEL', (evt) => {
421
+ notifyTurnEnd(evt.turnId, 'cancelled', evt.reason ?? null);
422
+ }));
423
+
216
424
  unsubs.push(events.turns.on('TURN_COMPLETED', (evt) => {
217
- // Long-task push notification: fires when the turn exceeded the threshold.
218
- const turnElapsedMs = turnStartTime !== null ? _clock() - turnStartTime : 0;
219
- turnStartTime = null;
220
- const notifyThreshold = readNotifyAfterSeconds((k) => configManager.get(k as Parameters<typeof configManager.get>[0]));
221
425
  // stopReason 'empty_response' signals a non-successful completion.
222
- const taskStatus: LongTaskStatus = evt.stopReason === 'completed' ? 'ok' : 'fail';
223
- // In-terminal (OSC 9) turn-end notification, fires on its own per-signal
224
- // config + focus gate, independent of the long-task duration threshold below.
225
- terminalNotifier?.notify('turn-end', taskStatus === 'ok' ? 'Turn finished' : 'Turn finished with errors');
226
- maybeNotifyLongTask({
227
- elapsedMs: turnElapsedMs,
228
- status: taskStatus,
229
- kind: 'turn',
230
- sessionId: runtime.sessionId,
231
- thresholdSeconds: notifyThreshold,
232
- webhookNotifier: webhookNotifier ?? null,
233
- focusTracker,
234
- configGet,
235
- });
426
+ notifyTurnEnd(
427
+ evt.turnId,
428
+ evt.stopReason === 'completed' ? 'completed' : 'failed',
429
+ evt.stopReason === 'completed' ? null : 'The model returned an empty response',
430
+ );
236
431
  // Budget-breach alert: edge-triggered, piggybacks on this same
237
432
  // TURN_COMPLETED handler rather than a second subscription.
238
433
  if (budgetBreachNotifier) {
@@ -291,20 +486,48 @@ export function wireTurnEventHandlers(
291
486
  refreshGit();
292
487
  }));
293
488
 
489
+ // Activity for the end-of-turn notice, counted only while a turn runs.
490
+ unsubs.push(events.tools.on('TOOL_RECEIVED', (payload) => {
491
+ if (turnStartTime !== null) tally.noteToolReceived(payload.callId, payload.tool, payload.args);
492
+ }));
493
+ unsubs.push(events.tools.on('TOOL_CANCELLED', (payload) => {
494
+ if (turnStartTime !== null) tally.noteToolFailed(payload.callId);
495
+ }));
496
+ unsubs.push(events.agents.on('AGENT_SPAWNING', (payload) => {
497
+ if (turnStartTime !== null) tally.noteAgentStarted();
498
+ rememberBounded(agentTasks, payload.agentId, payload.task);
499
+ }));
500
+ unsubs.push(events.agents.on('AGENT_COMPLETED', (payload) => { agentTasks.delete(payload.agentId); }));
501
+ unsubs.push(events.agents.on('AGENT_CANCELLED', (payload) => { agentTasks.delete(payload.agentId); }));
502
+ unsubs.push(events.workflows.on('WORKFLOW_CHAIN_CREATED', (payload) => {
503
+ rememberBounded(workstreams, payload.chainId, { task: payload.task, reviewScore: null });
504
+ }));
505
+ unsubs.push(events.workflows.on('WORKFLOW_REVIEW_COMPLETED', (payload) => {
506
+ if (turnStartTime !== null) tally.noteReviewScore(payload.score);
507
+ const entry = workstreams.get(payload.chainId);
508
+ if (entry) entry.reviewScore = payload.score;
509
+ }));
510
+ unsubs.push(events.workflows.on('WORKFLOW_CHAIN_PASSED', (payload) => { workstreams.delete(payload.chainId); }));
511
+
294
512
  // In-terminal (OSC 9) agent-blocked notification: a delegated agent parked
295
513
  // waiting for a human message (AGENT_AWAITING_MESSAGE) is "blocked on you".
296
- // Gated by the notifier's own per-signal config + focus rule. PRIVACY: the
297
- // short agent id only, never task text.
514
+ // Gated by the notifier's own per-signal config + focus rule. Names the
515
+ // agent's task unless behavior.notificationsMetadataOnly is on.
298
516
  if (terminalNotifier) {
299
517
  unsubs.push(events.agents.on('AGENT_AWAITING_MESSAGE', (payload) => {
300
- terminalNotifier.notify('agent-blocked', `agent ${payload.agentId.slice(0, 8)} is waiting for your input`);
518
+ const task = readNotificationsMetadataOnly(configGet) ? null : agentTasks.get(payload.agentId);
519
+ terminalNotifier.notify('agent-blocked', task
520
+ ? trimAtWordBoundary(`Agent waiting for your input: ${task}`, NOTIFICATION_TEXT_LIMITS.terminal)
521
+ : `agent ${payload.agentId.slice(0, 8)} is waiting for your input`);
301
522
  }));
302
523
  }
303
524
 
304
- unsubs.push(events.tools.on('TOOL_SUCCEEDED', () => {
525
+ unsubs.push(events.tools.on('TOOL_SUCCEEDED', (payload) => {
526
+ if (turnStartTime !== null) tally.noteToolSucceeded(payload.callId);
305
527
  refreshGit();
306
528
  }));
307
- unsubs.push(events.tools.on('TOOL_FAILED', () => {
529
+ unsubs.push(events.tools.on('TOOL_FAILED', (payload) => {
530
+ if (turnStartTime !== null) tally.noteToolFailed(payload.callId);
308
531
  refreshGit();
309
532
  }));
310
533
 
@@ -318,22 +541,38 @@ export function wireTurnEventHandlers(
318
541
  // a webhook that's already covered.
319
542
  if (focusTracker) {
320
543
  unsubs.push(events.agents.on('AGENT_FAILED', (payload) => {
544
+ const task = agentTasks.get(payload.agentId);
545
+ agentTasks.delete(payload.agentId);
321
546
  if (!shouldFireAlert(focusTracker, configGet, 'behavior.notifyOnAgentFailure')) return;
322
547
  try {
323
- notifyCompletion('GoodVibes: agent failed', `agent ${payload.agentId.slice(0, 8)} failed: ${payload.error}`, FORCE_NOTIFY_DURATION_MS);
548
+ const metadataOnly = readNotificationsMetadataOnly(configGet);
549
+ const name = metadataOnly ? '' : trimAtWordBoundary(task ?? '', NOTIFICATION_TEXT_LIMITS.desktopTitle - 'Agent failed: '.length);
550
+ notifyDesktop(
551
+ name ? `Agent failed: ${name}` : 'GoodVibes: agent failed',
552
+ metadataOnly
553
+ ? `agent ${payload.agentId.slice(0, 8)} failed`
554
+ : trimAtWordBoundary(`agent ${payload.agentId.slice(0, 8)} failed: ${payload.error}`, NOTIFICATION_TEXT_LIMITS.desktopBody),
555
+ FORCE_NOTIFY_DURATION_MS,
556
+ );
324
557
  } catch (err) {
325
558
  logger.debug('turn-event-wiring: agent-failure notify error', { error: String(err) });
326
559
  }
327
560
  }));
328
561
  unsubs.push(events.workflows.on('WORKFLOW_CHAIN_FAILED', (payload) => {
562
+ const workstream = workstreams.get(payload.chainId);
563
+ workstreams.delete(payload.chainId);
329
564
  if (!shouldFireAlert(focusTracker, configGet, 'behavior.notifyOnChainFailure')) return;
330
565
  try {
331
566
  // Title and body come from workstream-notification.ts, which is where
332
567
  // the three branches (cancelled / turn budget / failed) are narrated and
333
568
  // tested. A notification is a message to a person, so it carries neither
334
569
  // the internal name for the machinery nor the chain id.
335
- const notice = workstreamFailureNotification(payload);
336
- notifyCompletion(notice.title, notice.body, FORCE_NOTIFY_DURATION_MS);
570
+ const notice = workstreamFailureNotification(payload, {
571
+ task: workstream?.task,
572
+ reviewScore: workstream?.reviewScore,
573
+ metadataOnly: readNotificationsMetadataOnly(configGet),
574
+ });
575
+ notifyDesktop(notice.title, notice.body, FORCE_NOTIFY_DURATION_MS);
337
576
  } catch (err) {
338
577
  logger.debug('turn-event-wiring: chain-failure notify error', { error: String(err) });
339
578
  }
@@ -368,5 +607,5 @@ export function wireTurnEventHandlers(
368
607
  }));
369
608
  }
370
609
 
371
- return { refreshGit, unsubs, transcriptJournal };
610
+ return { refreshGit, unsubs, transcriptJournal, continueTurnAfterFailover };
372
611
  }