@pellux/goodvibes-tui 2.0.20 → 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 +70 -0
  2. package/README.md +16 -16
  3. package/docs/README.md +4 -4
  4. package/docs/foundation-artifacts/operator-contract.json +59 -4
  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
@@ -0,0 +1,290 @@
1
+ /**
2
+ * work-tree-focus.ts, moving the keyboard through the work tree.
3
+ *
4
+ * The navigable rows are the blocks the work tree registered with a
5
+ * `workTree` field: turn headers, beads, and agent lanes (their spawn or
6
+ * folded row), in screen order. Keys:
7
+ *
8
+ * ↑ ↓ previous / next row
9
+ * ← fold: close an open body; on a lane's bead or spawn row fold the
10
+ * lane to one ◉; on the spine fold the turn to its header
11
+ * → unfold: open a body (a second → shows the rest of a capped
12
+ * body); unfold a folded lane or turn
13
+ * Enter open or close a bead's body; unfold a folded lane; fold or
14
+ * unfold a turn
15
+ *
16
+ * Pure: reads the registry and collapse state, returns the collapse changes
17
+ * and the row to focus next. Nothing here stops, cancels or sends anything.
18
+ */
19
+
20
+ import type { BlockMeta } from './conversation-types.ts';
21
+ import type { TurnOutcome } from './work-tree-sources.ts';
22
+
23
+ /** A turn's saved ending: the user message it answered (by index and fingerprint) and how it ended. */
24
+ export interface TurnOutcomeRecord {
25
+ readonly index: number;
26
+ readonly fingerprint: string;
27
+ readonly outcome: TurnOutcome;
28
+ }
29
+
30
+ /** Most turn endings kept per session. */
31
+ export const MAX_TURN_OUTCOMES = 500;
32
+
33
+ export type NavBlock = BlockMeta & { readonly workTree: NonNullable<BlockMeta['workTree']> };
34
+
35
+ export interface FocusChange {
36
+ /** Collapse keys to set (true = folded / closed). */
37
+ readonly set: ReadonlyArray<readonly [string, boolean]>;
38
+ /** The row to focus afterwards. */
39
+ readonly focus: string;
40
+ }
41
+
42
+ /** Navigable rows in screen order. */
43
+ export function navigableRows(blocks: readonly BlockMeta[]): NavBlock[] {
44
+ return blocks.filter((b): b is NavBlock => b.workTree !== undefined).sort((a, b) => a.startLine - b.startLine);
45
+ }
46
+
47
+ export function findRow(blocks: readonly BlockMeta[], id: string): NavBlock | undefined {
48
+ return navigableRows(blocks).find((b) => b.workTree.id === id);
49
+ }
50
+
51
+ /** The row to focus on entering the work tree: the last one at or above `anchorLine` (else the last row). */
52
+ export function initialFocus(blocks: readonly BlockMeta[], anchorLine: number): string | null {
53
+ const rows = navigableRows(blocks);
54
+ if (rows.length === 0) return null;
55
+ const above = rows.filter((b) => b.startLine <= anchorLine);
56
+ return (above[above.length - 1] ?? rows[rows.length - 1]!).workTree.id;
57
+ }
58
+
59
+ /** The row `delta` rows from `current` (clamped); null when there are none. */
60
+ export function moveFocus(blocks: readonly BlockMeta[], current: string | null, delta: number): string | null {
61
+ const rows = navigableRows(blocks);
62
+ if (rows.length === 0) return null;
63
+ const at = current === null ? -1 : rows.findIndex((b) => b.workTree.id === current);
64
+ if (at < 0) return rows[delta < 0 ? rows.length - 1 : 0]!.workTree.id;
65
+ return rows[Math.max(0, Math.min(rows.length - 1, at + delta))]!.workTree.id;
66
+ }
67
+
68
+ /** The turn header a row belongs to (the nearest turn row above it). */
69
+ function turnOf(blocks: readonly BlockMeta[], row: NavBlock): NavBlock | undefined {
70
+ const rows = navigableRows(blocks).filter((b) => b.workTree.kind === 'turn' && b.startLine <= row.startLine);
71
+ return rows[rows.length - 1];
72
+ }
73
+
74
+ function laneRow(blocks: readonly BlockMeta[], laneKey: string): NavBlock | undefined {
75
+ return navigableRows(blocks).find((b) => b.workTree.kind === 'lane' && b.workTree.laneKey === laneKey);
76
+ }
77
+
78
+ /** ← on the focused row. */
79
+ export function foldFocused(blocks: readonly BlockMeta[], id: string): FocusChange | null {
80
+ const row = findRow(blocks, id);
81
+ if (!row) return null;
82
+ const wt = row.workTree;
83
+ if (wt.kind === 'bead') {
84
+ if (wt.open) return { set: [[row.collapseKey, true], ...(wt.moreKey ? [[wt.moreKey, false] as const] : [])], focus: id };
85
+ if (wt.laneKey) {
86
+ const lane = laneRow(blocks, wt.laneKey);
87
+ return { set: [[wt.laneKey, true]], focus: lane?.workTree.id ?? wt.laneKey };
88
+ }
89
+ const turn = turnOf(blocks, row);
90
+ return turn ? { set: [[turn.collapseKey, true]], focus: turn.workTree.id } : null;
91
+ }
92
+ if (wt.kind === 'lane') {
93
+ if (wt.open) return { set: [[row.collapseKey, true]], focus: id };
94
+ const turn = turnOf(blocks, row);
95
+ return turn ? { set: [[turn.collapseKey, true]], focus: turn.workTree.id } : null;
96
+ }
97
+ return wt.open ? { set: [[row.collapseKey, true]], focus: id } : null;
98
+ }
99
+
100
+ /** → on the focused row. */
101
+ export function unfoldFocused(blocks: readonly BlockMeta[], id: string): FocusChange | null {
102
+ const row = findRow(blocks, id);
103
+ if (!row) return null;
104
+ const wt = row.workTree;
105
+ if (wt.kind === 'bead') {
106
+ if (!wt.hasBody) return null;
107
+ if (!wt.open) return { set: [[row.collapseKey, false]], focus: id };
108
+ if (wt.capped && wt.moreKey) return { set: [[wt.moreKey, true]], focus: id };
109
+ return null;
110
+ }
111
+ return wt.open ? null : { set: [[row.collapseKey, false]], focus: id };
112
+ }
113
+
114
+ /** Enter on the focused row. */
115
+ export function activateFocused(blocks: readonly BlockMeta[], id: string): FocusChange | null {
116
+ const row = findRow(blocks, id);
117
+ if (!row) return null;
118
+ const wt = row.workTree;
119
+ if (wt.kind === 'bead') {
120
+ if (!wt.hasBody) return null;
121
+ return wt.open ? { set: [[row.collapseKey, true]], focus: id } : { set: [[row.collapseKey, false]], focus: id };
122
+ }
123
+ if (wt.kind === 'lane') return wt.open ? null : { set: [[row.collapseKey, false]], focus: id };
124
+ return { set: [[row.collapseKey, wt.open]], focus: id };
125
+ }
126
+
127
+ export interface WorkTreeControllerDeps {
128
+ /** The current block registry (flushed). */
129
+ readonly blocks: () => readonly BlockMeta[];
130
+ readonly collapseState: () => Map<string, boolean>;
131
+ readonly markDirty: () => void;
132
+ /** Exempts a key from search's close-time auto re-collapse (a deliberate user act). */
133
+ readonly noteUserTouch: (key: string) => void;
134
+ /** Whether the last build drew something live. */
135
+ readonly drewLive: () => boolean;
136
+ }
137
+
138
+ /** Collapse keys the work tree owns (work-tree-model.ts). */
139
+ const FOLD_KEY = /^(turn_\d+|bead_|beadmore_|lane_)/;
140
+
141
+ /**
142
+ * The work tree's keyboard focus, live repaint tick and fold state, owned by a
143
+ * ConversationManager (as `conversation.workTree`).
144
+ */
145
+ export class WorkTreeController {
146
+ private focusId: string | null = null;
147
+ private liveFrame = 0;
148
+ private foldListener: (() => void) | null = null;
149
+ /** Failed and cancelled turn endings of this session, by user message index. */
150
+ private readonly outcomes = new Map<number, TurnOutcomeRecord>();
151
+ private readonly resetListeners = new Set<() => void>();
152
+
153
+ constructor(private readonly deps: WorkTreeControllerDeps) {}
154
+
155
+ /** The focused row id, or null while the keyboard is in the composer. */
156
+ get focus(): string | null { return this.focusId; }
157
+ /** Spinner frame for running beads. */
158
+ get frame(): number { return this.liveFrame; }
159
+ get focused(): boolean { return this.focusId !== null; }
160
+
161
+ /**
162
+ * The transcript was reset or replaced wholesale (a new or resumed session):
163
+ * focus, turn endings and anything keyed by message index belong to the old
164
+ * transcript and go.
165
+ */
166
+ reset(): void {
167
+ this.focusId = null;
168
+ this.outcomes.clear();
169
+ for (const listener of this.resetListeners) listener();
170
+ }
171
+
172
+ /** Be told when the transcript is reset (the timing store keys turns by message index). */
173
+ onReset(listener: () => void): () => void {
174
+ this.resetListeners.add(listener);
175
+ return () => { this.resetListeners.delete(listener); };
176
+ }
177
+
178
+ /**
179
+ * Record how a turn ended. Only failed and cancelled endings are kept (a
180
+ * completed turn is the default and says nothing extra). Returns whether the
181
+ * kept records changed.
182
+ */
183
+ recordTurnOutcome(record: TurnOutcomeRecord): boolean {
184
+ const had = this.outcomes.delete(record.index);
185
+ if (record.outcome === 'completed') return had;
186
+ this.outcomes.set(record.index, record);
187
+ while (this.outcomes.size > MAX_TURN_OUTCOMES) {
188
+ const oldest = this.outcomes.keys().next();
189
+ if (oldest.done) break;
190
+ this.outcomes.delete(oldest.value);
191
+ }
192
+ this.deps.markDirty();
193
+ return true;
194
+ }
195
+
196
+ /** The recorded ending of the turn answering the user message at `index`. */
197
+ turnOutcome(index: number): TurnOutcomeRecord | undefined {
198
+ return this.outcomes.get(index);
199
+ }
200
+
201
+ /** Every recorded turn ending, oldest first (the session sidecar keeps them). */
202
+ turnOutcomes(): TurnOutcomeRecord[] {
203
+ return [...this.outcomes.values()];
204
+ }
205
+
206
+ /** Restore saved turn endings (a resumed session keeps its failed and cancelled headers). */
207
+ restoreTurnOutcomes(records: Iterable<TurnOutcomeRecord>): void {
208
+ for (const record of records) this.recordTurnOutcome(record);
209
+ this.deps.markDirty();
210
+ }
211
+
212
+ /** Something the work tree draws changed outside the transcript (a ◈ summary landed): rebuild. */
213
+ invalidate(): void { this.deps.markDirty(); }
214
+
215
+ /** Be told when a fold decision changes (the session keeps them in its sidecar). */
216
+ onFoldChange(listener: (() => void) | null): void { this.foldListener = listener; }
217
+ notifyFoldChange(): void { this.foldListener?.(); }
218
+
219
+ /**
220
+ * Repaint live rows as time passes (a running bead spins, its time counts
221
+ * up). Call before each frame; it marks the transcript dirty only while the
222
+ * last build drew something live and the frame moved on.
223
+ */
224
+ tickLive(now = Date.now()): void {
225
+ const frame = Math.floor(now / 150);
226
+ if (frame === this.liveFrame) return;
227
+ this.liveFrame = frame;
228
+ if (this.deps.drewLive()) this.deps.markDirty();
229
+ }
230
+
231
+ /** Enter the work tree at the last row at or above `anchorLine`; false when there is nothing to focus. */
232
+ enter(anchorLine: number): boolean {
233
+ const id = initialFocus(this.deps.blocks(), anchorLine);
234
+ if (id === null) return false;
235
+ this.focusId = id;
236
+ this.deps.markDirty();
237
+ return true;
238
+ }
239
+
240
+ /** Leave the work tree (Esc). Never stops or cancels anything. */
241
+ leave(): void {
242
+ if (this.focusId === null) return;
243
+ this.focusId = null;
244
+ this.deps.markDirty();
245
+ }
246
+
247
+ move(delta: number): boolean {
248
+ const next = moveFocus(this.deps.blocks(), this.focusId, delta);
249
+ if (next === null || next === this.focusId) return false;
250
+ this.focusId = next;
251
+ this.deps.markDirty();
252
+ return true;
253
+ }
254
+
255
+ /** ← (fold), → (unfold) or Enter (activate) on the focused row. */
256
+ act(action: 'fold' | 'unfold' | 'activate'): boolean {
257
+ if (this.focusId === null) return false;
258
+ const blocks = this.deps.blocks();
259
+ const change = action === 'fold' ? foldFocused(blocks, this.focusId)
260
+ : action === 'unfold' ? unfoldFocused(blocks, this.focusId)
261
+ : activateFocused(blocks, this.focusId);
262
+ if (!change) return false;
263
+ const collapse = this.deps.collapseState();
264
+ for (const [key, value] of change.set) {
265
+ collapse.set(key, value);
266
+ this.deps.noteUserTouch(key);
267
+ }
268
+ this.focusId = change.focus;
269
+ this.deps.markDirty();
270
+ this.foldListener?.();
271
+ return true;
272
+ }
273
+
274
+ /** The focused row's block (copy, save and bookmark act on it). */
275
+ focusBlock(): BlockMeta | null {
276
+ return this.focusId === null ? null : findRow(this.deps.blocks(), this.focusId) ?? null;
277
+ }
278
+
279
+ /** Every work-tree fold decision (turns, lanes, open beads), oldest first. */
280
+ foldState(): Array<[string, boolean]> {
281
+ return [...this.deps.collapseState()].filter(([key]) => FOLD_KEY.test(key));
282
+ }
283
+
284
+ /** Restore saved fold decisions (a resumed session keeps its view). */
285
+ restoreFoldState(entries: Iterable<readonly [string, boolean]>): void {
286
+ const collapse = this.deps.collapseState();
287
+ for (const [key, value] of entries) if (FOLD_KEY.test(key)) collapse.set(key, value);
288
+ this.deps.markDirty();
289
+ }
290
+ }
@@ -0,0 +1,143 @@
1
+ /**
2
+ * work-tree-fold-store.ts, the work tree's fold state, kept per session.
3
+ *
4
+ * Which turns are folded, which agent lanes are folded, which beads are open
5
+ * and which opened bodies show everything, plus which turns failed or were
6
+ * cancelled (keyed by their user message's index and fingerprint, so a
7
+ * resumed header still says so): kept beside the session file as
8
+ * `<stem>.work-tree.json` (the same stem rule the session file uses, see
9
+ * sanitizeSessionName), so reopening a session keeps the view.
10
+ *
11
+ * Bounded (the most recent MAX_KEYS decisions), validated on load (anything
12
+ * that is not a known key shape with a boolean value is dropped), written
13
+ * atomically (temp file + rename), and swept: a sidecar whose session file is
14
+ * gone is removed by sweepOrphanWorkTreeFolds.
15
+ */
16
+
17
+ import { existsSync, mkdirSync, readdirSync, readFileSync, renameSync, statSync, unlinkSync, writeFileSync } from 'node:fs';
18
+ import { join } from 'node:path';
19
+ import { sanitizeSessionName } from '@pellux/goodvibes-sdk/platform/sessions';
20
+ import { MAX_TURN_OUTCOMES, type TurnOutcomeRecord } from './work-tree-focus.ts';
21
+
22
+ const SUFFIX = '.work-tree.json';
23
+ const VERSION = 1;
24
+ /** Most fold decisions kept per session. */
25
+ export const MAX_FOLD_KEYS = 2000;
26
+ /** A staging file older than this is crash residue. */
27
+ const TMP_MAX_AGE_MS = 60 * 60 * 1000;
28
+
29
+ /** Collapse keys the work tree owns (work-tree-model.ts). */
30
+ const KEY_SHAPE = /^(turn_\d+|bead_[\w:./-]+|beadmore_[\w:./-]+|lane_[\w:./-]+)$/;
31
+
32
+ export function isWorkTreeFoldKey(key: string): boolean {
33
+ return KEY_SHAPE.test(key);
34
+ }
35
+
36
+ function sidecarPath(sessionsDir: string, sessionId: string): string | null {
37
+ if (!sessionsDir || !sessionId) return null;
38
+ const stem = sanitizeSessionName(sessionId);
39
+ if (stem === '') return null;
40
+ return join(sessionsDir, `${stem}${SUFFIX}`);
41
+ }
42
+
43
+ /** Keep only work-tree keys with boolean values, the most recent MAX_FOLD_KEYS. */
44
+ export function boundFoldState(entries: Iterable<readonly [string, unknown]>): Array<[string, boolean]> {
45
+ const out: Array<[string, boolean]> = [];
46
+ for (const [key, value] of entries) if (typeof value === 'boolean' && isWorkTreeFoldKey(key)) out.push([key, value]);
47
+ return out.length > MAX_FOLD_KEYS ? out.slice(out.length - MAX_FOLD_KEYS) : out;
48
+ }
49
+
50
+ /** Keep only well-formed failed/cancelled turn endings, the most recent MAX_TURN_OUTCOMES. */
51
+ export function boundTurnOutcomes(records: Iterable<unknown>): TurnOutcomeRecord[] {
52
+ const out: TurnOutcomeRecord[] = [];
53
+ for (const record of records) {
54
+ if (!record || typeof record !== 'object') continue;
55
+ const r = record as Record<string, unknown>;
56
+ if (typeof r.index !== 'number' || !Number.isInteger(r.index) || r.index < 0) continue;
57
+ if (typeof r.fingerprint !== 'string' || r.fingerprint.length === 0 || r.fingerprint.length > 200) continue;
58
+ if (r.outcome !== 'failed' && r.outcome !== 'cancelled') continue;
59
+ out.push({ index: r.index, fingerprint: r.fingerprint, outcome: r.outcome });
60
+ }
61
+ return out.length > MAX_TURN_OUTCOMES ? out.slice(out.length - MAX_TURN_OUTCOMES) : out;
62
+ }
63
+
64
+ /**
65
+ * Write the session's fold state and its failed/cancelled turn endings.
66
+ * Best effort: a failed write never breaks the session.
67
+ */
68
+ export function saveWorkTreeFolds(sessionsDir: string, sessionId: string, state: Iterable<readonly [string, boolean]>, turns: Iterable<TurnOutcomeRecord> = []): void {
69
+ const path = sidecarPath(sessionsDir, sessionId);
70
+ if (!path) return;
71
+ try {
72
+ mkdirSync(sessionsDir, { recursive: true });
73
+ const tmp = `${path}.tmp-${process.pid}`;
74
+ const outcomes = boundTurnOutcomes(turns);
75
+ writeFileSync(tmp, JSON.stringify({ version: VERSION, sessionId, folds: Object.fromEntries(boundFoldState(state)), ...(outcomes.length > 0 ? { turns: outcomes } : {}) }));
76
+ renameSync(tmp, path);
77
+ } catch {
78
+ // best effort; the view falls back to defaults on the next resume
79
+ }
80
+ }
81
+
82
+ /** Read the session's fold state; an absent or unreadable sidecar reads as empty. */
83
+ export function loadWorkTreeFolds(sessionsDir: string, sessionId: string): Array<[string, boolean]> {
84
+ const path = sidecarPath(sessionsDir, sessionId);
85
+ if (!path || !existsSync(path)) return [];
86
+ try {
87
+ const parsed: unknown = JSON.parse(readFileSync(path, 'utf8'));
88
+ if (!parsed || typeof parsed !== 'object') return [];
89
+ const folds = (parsed as { folds?: unknown }).folds;
90
+ if (!folds || typeof folds !== 'object' || Array.isArray(folds)) return [];
91
+ return boundFoldState(Object.entries(folds as Record<string, unknown>));
92
+ } catch {
93
+ return [];
94
+ }
95
+ }
96
+
97
+ /** Read the session's saved failed/cancelled turn endings; an absent or unreadable sidecar reads as none. */
98
+ export function loadWorkTreeTurnOutcomes(sessionsDir: string, sessionId: string): TurnOutcomeRecord[] {
99
+ const path = sidecarPath(sessionsDir, sessionId);
100
+ if (!path || !existsSync(path)) return [];
101
+ try {
102
+ const parsed: unknown = JSON.parse(readFileSync(path, 'utf8'));
103
+ if (!parsed || typeof parsed !== 'object') return [];
104
+ const turns = (parsed as { turns?: unknown }).turns;
105
+ return Array.isArray(turns) ? boundTurnOutcomes(turns) : [];
106
+ } catch {
107
+ return [];
108
+ }
109
+ }
110
+
111
+ /**
112
+ * Remove fold sidecars whose session file no longer exists, and staging files
113
+ * a crash left behind. Returns how many files were removed.
114
+ */
115
+ export function sweepOrphanWorkTreeFolds(sessionsDir: string, now = Date.now()): number {
116
+ if (!sessionsDir || !existsSync(sessionsDir)) return 0;
117
+ let removed = 0;
118
+ let names: string[];
119
+ try {
120
+ names = readdirSync(sessionsDir);
121
+ } catch {
122
+ return 0;
123
+ }
124
+ const present = new Set(names);
125
+ for (const name of names) {
126
+ try {
127
+ if (name.includes(`${SUFFIX}.tmp-`)) {
128
+ if (now - statSync(join(sessionsDir, name)).mtimeMs > TMP_MAX_AGE_MS) { unlinkSync(join(sessionsDir, name)); removed++; }
129
+ continue;
130
+ }
131
+ if (!name.endsWith(SUFFIX)) continue;
132
+ const stem = name.slice(0, -SUFFIX.length);
133
+ // Settled for an hour first: a session folds things before its first save writes the session file.
134
+ if (!present.has(`${stem}.jsonl`) && now - statSync(join(sessionsDir, name)).mtimeMs > TMP_MAX_AGE_MS) {
135
+ unlinkSync(join(sessionsDir, name));
136
+ removed++;
137
+ }
138
+ } catch {
139
+ // a file that vanished or cannot be removed is left for the next sweep
140
+ }
141
+ }
142
+ return removed;
143
+ }