@pellux/goodvibes-tui 1.27.0 → 2.0.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 (404) hide show
  1. package/CHANGELOG.md +125 -2
  2. package/README.md +7 -9
  3. package/bin/goodvibes +2 -2
  4. package/bin/launcher-support.js +8 -4
  5. package/docs/README.md +11 -2
  6. package/docs/foundation-artifacts/operator-contract.json +6403 -2347
  7. package/package.json +14 -25
  8. package/scripts/postinstall.js +35 -35
  9. package/src/audio/spoken-turn-model-routing.ts +1 -1
  10. package/src/cli/bundle-command.ts +5 -5
  11. package/src/cli/completions/generate.ts +1 -1
  12. package/src/cli/doctor.ts +3 -3
  13. package/src/cli/entrypoint.ts +12 -7
  14. package/src/cli/help.ts +4 -5
  15. package/src/cli/hooks-command.ts +1 -1
  16. package/src/cli/index.ts +1 -4
  17. package/src/cli/launch-auto-update.ts +59 -7
  18. package/src/cli/management-commands.ts +6 -6
  19. package/src/cli/management-utils.ts +5 -5
  20. package/src/cli/management.ts +7 -7
  21. package/src/cli/network-posture.ts +1 -1
  22. package/src/cli/package-verification.ts +7 -6
  23. package/src/cli/plugin-bundles-command.ts +1 -1
  24. package/src/cli/plugin-command.ts +2 -2
  25. package/src/cli/service-command.ts +2 -2
  26. package/src/cli/service-posture.ts +4 -4
  27. package/src/cli/status.ts +9 -8
  28. package/src/cli/surface-command.ts +4 -4
  29. package/src/cli/tui-startup.ts +12 -3
  30. package/src/cli-flags.ts +3 -3
  31. package/src/config/goodvibes-home-audit.ts +0 -2
  32. package/src/config/secret-config.ts +37 -5
  33. package/src/config/tui-extension-settings.ts +1 -1
  34. package/src/core/approval-alert.ts +2 -2
  35. package/src/core/budget-breach-notifier.ts +3 -3
  36. package/src/core/conversation-compaction-render.ts +1 -1
  37. package/src/core/conversation-line-cache.ts +1 -1
  38. package/src/core/conversation-render-context.ts +1 -1
  39. package/src/core/conversation-rendering.ts +2 -2
  40. package/src/core/conversation.ts +10 -10
  41. package/src/core/long-task-notifier.ts +2 -2
  42. package/src/core/memory-diagnostics-gateway.ts +1 -1
  43. package/src/core/scriptable-statusline.ts +1 -1
  44. package/src/core/session-auto-titler.ts +1 -1
  45. package/src/core/session-resume-core.ts +2 -2
  46. package/src/core/stream-event-wiring.ts +24 -44
  47. package/src/core/terminal-notifier.ts +3 -3
  48. package/src/core/turn-cancellation.ts +1 -1
  49. package/src/core/turn-event-wiring.ts +5 -5
  50. package/src/input/command-registry.ts +6 -6
  51. package/src/input/commands/channel-pairing.ts +1 -1
  52. package/src/input/commands/cluster-runtime.ts +1 -1
  53. package/src/input/commands/codebase-runtime.ts +1 -1
  54. package/src/input/commands/devices-runtime.ts +1 -1
  55. package/src/input/commands/hosted-runtime.ts +330 -0
  56. package/src/input/commands/local-setup-review.ts +1 -1
  57. package/src/input/commands/local-setup-transfer.ts +2 -2
  58. package/src/input/commands/local-setup.ts +3 -3
  59. package/src/input/commands/managed-runtime.ts +2 -2
  60. package/src/input/commands/operator-rpc.ts +24 -110
  61. package/src/input/commands/payment-card-intake.ts +1 -1
  62. package/src/input/commands/permissions-provenance.ts +2 -2
  63. package/src/input/commands/plugin-runtime.ts +1 -1
  64. package/src/input/commands/provider.ts +1 -1
  65. package/src/input/commands/relay-runtime.ts +18 -9
  66. package/src/input/commands/rewind-runtime.ts +1 -1
  67. package/src/input/commands/runtime-services.ts +1 -1
  68. package/src/input/commands/services-runtime.ts +1 -1
  69. package/src/input/commands/session-workflow.ts +1 -1
  70. package/src/input/commands/settings-sync-runtime.ts +2 -2
  71. package/src/input/commands/share-runtime.ts +1 -1
  72. package/src/input/commands/tasks-runtime.ts +145 -55
  73. package/src/input/commands/update-runtime.ts +31 -28
  74. package/src/input/commands/work-plan-runtime.ts +2 -2
  75. package/src/input/commands/workstream-attempts.ts +1 -1
  76. package/src/input/commands/workstream-runtime.ts +2 -2
  77. package/src/input/commands.ts +2 -0
  78. package/src/input/feature-unit-layout.ts +1 -1
  79. package/src/input/feed-context-factory.ts +3 -3
  80. package/src/input/handler-content-actions.ts +2 -2
  81. package/src/input/handler-feed-routes.ts +1 -1
  82. package/src/input/handler-feed.ts +3 -3
  83. package/src/input/handler-modal-token-routes.ts +1 -1
  84. package/src/input/handler-onboarding-daemon-adopt.ts +3 -2
  85. package/src/input/handler-onboarding.ts +1 -1
  86. package/src/input/handler-types.ts +2 -2
  87. package/src/input/handler-ui-state.ts +1 -1
  88. package/src/input/handler.ts +4 -4
  89. package/src/input/input-history.ts +1 -1
  90. package/src/input/onboarding/onboarding-feature-units.ts +2 -2
  91. package/src/input/onboarding/onboarding-runtime-status.ts +15 -1
  92. package/src/input/onboarding/onboarding-wizard-apply.ts +17 -8
  93. package/src/input/onboarding/onboarding-wizard-cloudflare-step.ts +5 -3
  94. package/src/input/onboarding/onboarding-wizard-external-surfaces.ts +1 -1
  95. package/src/input/onboarding/onboarding-wizard-types.ts +1 -1
  96. package/src/input/payments-config.ts +6 -6
  97. package/src/input/search.ts +1 -1
  98. package/src/input/session-picker-modal.ts +46 -0
  99. package/src/input/settings-modal-activation.ts +1 -1
  100. package/src/input/settings-modal-behavior.ts +4 -4
  101. package/src/input/settings-modal-commit.ts +11 -0
  102. package/src/input/settings-modal-data.ts +13 -80
  103. package/src/input/settings-modal-mutations.ts +33 -0
  104. package/src/input/settings-modal-secrets.ts +34 -0
  105. package/src/input/settings-modal-subscriptions.ts +1 -1
  106. package/src/input/settings-modal-types.ts +15 -1
  107. package/src/input/settings-modal.ts +28 -3
  108. package/src/input/worktree-setup-config.ts +1 -1
  109. package/src/main.ts +11 -9
  110. package/src/panels/agent-inspector-shared.ts +2 -2
  111. package/src/panels/base-panel.ts +2 -2
  112. package/src/panels/builtin/operations.ts +6 -8
  113. package/src/panels/builtin/session.ts +17 -0
  114. package/src/panels/builtin/shared.ts +5 -5
  115. package/src/panels/builtin-modals.ts +3 -3
  116. package/src/panels/confirm-state.ts +1 -1
  117. package/src/panels/cost-tracker-panel.ts +4 -4
  118. package/src/panels/diff-panel.ts +2 -2
  119. package/src/panels/diff-review-panel.ts +2 -2
  120. package/src/panels/expandable-list-panel.ts +2 -2
  121. package/src/panels/fleet-acts.ts +1 -1
  122. package/src/panels/fleet-observed-render.ts +1 -1
  123. package/src/panels/fleet-panel-format.ts +1 -1
  124. package/src/panels/fleet-panel.ts +1 -1
  125. package/src/panels/fleet-spawn.ts +1 -1
  126. package/src/panels/fleet-steer.ts +1 -1
  127. package/src/panels/fleet-transcript.ts +1 -1
  128. package/src/panels/git-panel.ts +1 -1
  129. package/src/panels/hosted-session-feed.ts +352 -0
  130. package/src/panels/hosted-session-panel.ts +221 -0
  131. package/src/panels/local-auth-panel.ts +3 -3
  132. package/src/panels/modals/devices-modal.ts +1 -1
  133. package/src/panels/modals/local-auth-modal.ts +1 -1
  134. package/src/panels/modals/pairing-modal.ts +1 -1
  135. package/src/panels/modals/services-modal.ts +1 -1
  136. package/src/panels/modals/settings-sync-modal.ts +1 -1
  137. package/src/panels/modals/subscription-modal.ts +1 -1
  138. package/src/panels/modals/work-plan-modal.ts +1 -1
  139. package/src/panels/notifications-panel.ts +1 -1
  140. package/src/panels/panel-confirm-overlay.ts +1 -1
  141. package/src/panels/plugins-panel.ts +2 -2
  142. package/src/panels/polish-core.ts +2 -2
  143. package/src/panels/polish-tables.ts +1 -1
  144. package/src/panels/polish.ts +2 -2
  145. package/src/panels/scrollable-list-panel.ts +3 -3
  146. package/src/panels/skills-panel.ts +3 -3
  147. package/src/panels/token-budget-panel.ts +5 -5
  148. package/src/panels/types.ts +1 -2
  149. package/src/permissions/prompt.ts +1 -1
  150. package/src/renderer/autocomplete-overlay.ts +1 -1
  151. package/src/renderer/block-actions-overlay.ts +1 -1
  152. package/src/renderer/bookmark-modal.ts +1 -1
  153. package/src/renderer/buffer.ts +1 -1
  154. package/src/renderer/code-block.ts +1 -1
  155. package/src/renderer/composer-fragments.ts +1 -1
  156. package/src/renderer/compositor.ts +2 -2
  157. package/src/renderer/config-modal.ts +1 -1
  158. package/src/renderer/context-inspector.ts +1 -1
  159. package/src/renderer/conversation-layout.ts +2 -2
  160. package/src/renderer/conversation-overlays.ts +1 -1
  161. package/src/renderer/conversation-surface.ts +2 -2
  162. package/src/renderer/diff-view.ts +1 -1
  163. package/src/renderer/diff.ts +1 -1
  164. package/src/renderer/file-picker-overlay.ts +1 -1
  165. package/src/renderer/fleet-tab-strip.ts +1 -1
  166. package/src/renderer/fullscreen-primitives.ts +2 -2
  167. package/src/renderer/fullscreen-workspace.ts +1 -1
  168. package/src/renderer/header-line.ts +1 -1
  169. package/src/renderer/help-overlay.ts +1 -1
  170. package/src/renderer/history-search-overlay.ts +2 -2
  171. package/src/renderer/layout.ts +5 -8
  172. package/src/renderer/markdown-inline.ts +1 -1
  173. package/src/renderer/markdown-table.ts +1 -1
  174. package/src/renderer/markdown.ts +1 -1
  175. package/src/renderer/mcp-workspace.ts +1 -1
  176. package/src/renderer/modal-factory.ts +2 -2
  177. package/src/renderer/model-workspace.ts +2 -2
  178. package/src/renderer/onboarding/onboarding-wizard.ts +1 -1
  179. package/src/renderer/overlay-box.ts +1 -1
  180. package/src/renderer/panel-composite.ts +1 -1
  181. package/src/renderer/panel-workspace-bar.ts +1 -1
  182. package/src/renderer/process-indicator.ts +1 -1
  183. package/src/renderer/profile-picker-modal.ts +1 -1
  184. package/src/renderer/search-overlay.ts +2 -2
  185. package/src/renderer/selection-modal-overlay.ts +1 -1
  186. package/src/renderer/session-picker-modal.ts +124 -3
  187. package/src/renderer/settings-modal-helpers.ts +2 -0
  188. package/src/renderer/settings-modal.ts +2 -2
  189. package/src/renderer/shell-surface.ts +2 -2
  190. package/src/renderer/system-message.ts +1 -1
  191. package/src/renderer/tab-strip.ts +1 -1
  192. package/src/renderer/theme-mode-config.ts +1 -1
  193. package/src/renderer/theme.ts +11 -12
  194. package/src/renderer/thinking.ts +1 -1
  195. package/src/renderer/tool-call.ts +2 -2
  196. package/src/renderer/tool-result-expanded-lines.ts +1 -1
  197. package/src/renderer/turn-injection.ts +7 -8
  198. package/src/renderer/ui-factory.ts +3 -3
  199. package/src/runtime/bootstrap-command-context.ts +4 -4
  200. package/src/runtime/bootstrap-command-parts.ts +5 -5
  201. package/src/runtime/bootstrap-core.ts +30 -30
  202. package/src/runtime/bootstrap-shell.ts +4 -4
  203. package/src/runtime/bootstrap.ts +128 -157
  204. package/src/runtime/client/approval-updates.ts +82 -0
  205. package/src/runtime/client/build-floors.ts +167 -0
  206. package/src/runtime/client/daemon-attach-handshake.ts +115 -0
  207. package/src/runtime/client/fleet-union.ts +147 -0
  208. package/src/runtime/client/host-service-status.ts +71 -0
  209. package/src/runtime/client/hosted-exit.ts +86 -0
  210. package/src/runtime/client/hosted-roster.ts +110 -0
  211. package/src/runtime/client/hosted-session-stream.ts +178 -0
  212. package/src/runtime/client/hosted-sessions.ts +187 -0
  213. package/src/runtime/client/operator-endpoint.ts +213 -0
  214. package/src/runtime/client/spine-adoption.ts +99 -0
  215. package/src/runtime/cloudflare-control-plane.ts +1 -1
  216. package/src/runtime/context-accounting-source.ts +1 -1
  217. package/src/runtime/context.ts +2 -2
  218. package/src/runtime/conversation-rewind-port.ts +42 -14
  219. package/src/runtime/daemon-attach-notices.ts +55 -19
  220. package/src/runtime/diagnostics/panels/ops.ts +1 -1
  221. package/src/runtime/disposal-wiring.ts +4 -24
  222. package/src/runtime/index.ts +119 -105
  223. package/src/runtime/install-self-check-startup.ts +2 -2
  224. package/src/runtime/interaction-seams.ts +1 -1
  225. package/src/runtime/knowledge-services.ts +8 -2
  226. package/src/runtime/legacy-daemon-migration.ts +15 -3
  227. package/src/runtime/legacy-daemon-reconcile.ts +3 -2
  228. package/src/runtime/memory-spine-transport.ts +37 -267
  229. package/src/runtime/notification-dispatch.ts +2 -2
  230. package/src/runtime/onboarding/apply.ts +2 -2
  231. package/src/runtime/onboarding/derivation.ts +2 -2
  232. package/src/runtime/onboarding/markers.ts +2 -2
  233. package/src/runtime/onboarding/progress.ts +2 -2
  234. package/src/runtime/onboarding/snapshot.ts +1 -1
  235. package/src/runtime/onboarding/state.ts +2 -2
  236. package/src/runtime/onboarding/types.ts +2 -2
  237. package/src/runtime/orchestrator-core-services.ts +1 -1
  238. package/src/runtime/path-shadow-startup.ts +28 -221
  239. package/src/runtime/power-keepawake-remote.ts +3 -2
  240. package/src/runtime/process-lifecycle.ts +2 -2
  241. package/src/runtime/provider-fallback.ts +10 -66
  242. package/src/runtime/recovery-autosave.ts +1 -1
  243. package/src/runtime/recovery-offer-wiring.ts +39 -23
  244. package/src/runtime/recovery-prompt.ts +31 -17
  245. package/src/runtime/relay-reachability-bridge.ts +53 -42
  246. package/src/runtime/release-artifacts.ts +2 -1
  247. package/src/runtime/runtime-services-types.ts +82 -35
  248. package/src/runtime/services.ts +305 -300
  249. package/src/runtime/session-resume-liveness-confirm.ts +1 -1
  250. package/src/runtime/ui-services.ts +7 -6
  251. package/src/runtime/update-check.ts +23 -43
  252. package/src/runtime/workspace-checkpointing.ts +1 -1
  253. package/src/runtime/wrfc-persistence.ts +2 -2
  254. package/src/shell/ui-openers.ts +30 -7
  255. package/src/utils/clipboard.ts +1 -1
  256. package/src/verification/verification-ledger.ts +10 -8
  257. package/src/version.ts +1 -1
  258. package/bin/goodvibes-daemon +0 -90
  259. package/src/cli/config-overrides.ts +0 -267
  260. package/src/cli/endpoints.ts +0 -109
  261. package/src/cli/parser.ts +0 -395
  262. package/src/cli/redaction.ts +0 -170
  263. package/src/cli/types.ts +0 -84
  264. package/src/cluster/commands.ts +0 -382
  265. package/src/cluster/remote-daemon-target.ts +0 -225
  266. package/src/cluster/render.ts +0 -268
  267. package/src/config/atomic-write.ts +0 -70
  268. package/src/config/credential-availability.ts +0 -122
  269. package/src/config/goodvibes-home.ts +0 -149
  270. package/src/config/index.ts +0 -88
  271. package/src/config/provider-model.ts +0 -23
  272. package/src/config/read-versioned.ts +0 -228
  273. package/src/core/alert-gating.ts +0 -67
  274. package/src/core/consolidation-receipt.ts +0 -41
  275. package/src/core/focus-tracker.ts +0 -41
  276. package/src/core/history.ts +0 -45
  277. package/src/core/memory-status.ts +0 -118
  278. package/src/core/orchestrator.ts +0 -7
  279. package/src/core/pairing-devices.ts +0 -49
  280. package/src/core/pairing-handoff.ts +0 -101
  281. package/src/core/pairing-offers.ts +0 -83
  282. package/src/core/pairing-origin.ts +0 -78
  283. package/src/core/rewind-turn-anchors.ts +0 -311
  284. package/src/core/session-recovery.ts +0 -210
  285. package/src/core/stable-host.ts +0 -171
  286. package/src/core/transcript-journal.ts +0 -503
  287. package/src/daemon/cli.ts +0 -641
  288. package/src/daemon/handlers/context.ts +0 -29
  289. package/src/daemon/handlers/contracts.ts +0 -77
  290. package/src/daemon/handlers/credentials.ts +0 -139
  291. package/src/daemon/handlers/drafts/draft-store.ts +0 -427
  292. package/src/daemon/handlers/drafts/index.ts +0 -17
  293. package/src/daemon/handlers/drafts/register.ts +0 -331
  294. package/src/daemon/handlers/errors.ts +0 -18
  295. package/src/daemon/handlers/inbox/cursor-store.ts +0 -457
  296. package/src/daemon/handlers/inbox/index.ts +0 -284
  297. package/src/daemon/handlers/inbox/mapping.ts +0 -192
  298. package/src/daemon/handlers/inbox/poller.ts +0 -212
  299. package/src/daemon/handlers/inbox/provider-adapter.ts +0 -156
  300. package/src/daemon/handlers/inbox/providers/discord.ts +0 -252
  301. package/src/daemon/handlers/inbox/providers/email.ts +0 -152
  302. package/src/daemon/handlers/inbox/providers/imap-client.ts +0 -300
  303. package/src/daemon/handlers/inbox/providers/route-util.ts +0 -24
  304. package/src/daemon/handlers/inbox/providers/slack.ts +0 -263
  305. package/src/daemon/handlers/index.ts +0 -117
  306. package/src/daemon/handlers/register.ts +0 -168
  307. package/src/daemon/handlers/remote/backends/cloud-terminal.ts +0 -142
  308. package/src/daemon/handlers/remote/backends/docker.ts +0 -79
  309. package/src/daemon/handlers/remote/backends/index.ts +0 -40
  310. package/src/daemon/handlers/remote/backends/local-process.ts +0 -113
  311. package/src/daemon/handlers/remote/backends/process-runner.ts +0 -127
  312. package/src/daemon/handlers/remote/backends/ssh.ts +0 -125
  313. package/src/daemon/handlers/remote/backends/types.ts +0 -97
  314. package/src/daemon/handlers/remote/dispatcher.ts +0 -181
  315. package/src/daemon/handlers/remote/index.ts +0 -119
  316. package/src/daemon/handlers/remote/peer-registry.ts +0 -357
  317. package/src/daemon/handlers/remote/service.ts +0 -191
  318. package/src/daemon/handlers/routing/inbox-bridge.ts +0 -71
  319. package/src/daemon/handlers/routing/index.ts +0 -261
  320. package/src/daemon/handlers/routing/route-store.ts +0 -319
  321. package/src/daemon/handlers/routing/routing-resolver.ts +0 -75
  322. package/src/daemon/handlers/sqlite-store.ts +0 -278
  323. package/src/daemon/handlers/triage/index.ts +0 -57
  324. package/src/daemon/handlers/triage/integration.ts +0 -213
  325. package/src/daemon/handlers/triage/pipeline.ts +0 -274
  326. package/src/daemon/handlers/triage/scorer.ts +0 -287
  327. package/src/daemon/handlers/triage/tagger/discord.ts +0 -187
  328. package/src/daemon/handlers/triage/tagger/imap.ts +0 -384
  329. package/src/daemon/handlers/triage/tagger/index.ts +0 -184
  330. package/src/daemon/handlers/triage/tagger/shared.ts +0 -70
  331. package/src/daemon/handlers/triage/tagger/slack.ts +0 -69
  332. package/src/daemon/handlers/triage/types.ts +0 -50
  333. package/src/daemon/lifecycle.ts +0 -41
  334. package/src/daemon/provision-wake-model.ts +0 -81
  335. package/src/daemon/safe-serve.ts +0 -61
  336. package/src/daemon/send/channels.ts +0 -200
  337. package/src/daemon/send/command.ts +0 -332
  338. package/src/daemon/send/composition.ts +0 -99
  339. package/src/daemon/send/failure-text.ts +0 -93
  340. package/src/daemon/send/inert-text.ts +0 -225
  341. package/src/daemon/send/stdin.ts +0 -24
  342. package/src/daemon/service-commands.ts +0 -329
  343. package/src/export/cost-utils.ts +0 -319
  344. package/src/input/selection.ts +0 -135
  345. package/src/plugins/loader.ts +0 -15
  346. package/src/renderer/bottom-bar.ts +0 -58
  347. package/src/renderer/conversation-tree.ts +0 -228
  348. package/src/runtime/agent-graph-composition.ts +0 -78
  349. package/src/runtime/channel-composition.ts +0 -46
  350. package/src/runtime/cluster-composition.ts +0 -124
  351. package/src/runtime/cluster-group-composition.ts +0 -284
  352. package/src/runtime/code-index-services.ts +0 -167
  353. package/src/runtime/daemon-handler-composition.ts +0 -76
  354. package/src/runtime/device-posture-composition.ts +0 -110
  355. package/src/runtime/durability-housekeeping.ts +0 -183
  356. package/src/runtime/durability-services.ts +0 -155
  357. package/src/runtime/embedded-service-factories.ts +0 -58
  358. package/src/runtime/feature-settings.ts +0 -154
  359. package/src/runtime/fleet-needs-input-push.ts +0 -70
  360. package/src/runtime/fleet-services.ts +0 -41
  361. package/src/runtime/idle-power-services.ts +0 -85
  362. package/src/runtime/mail-composition.ts +0 -65
  363. package/src/runtime/memory-fold.ts +0 -55
  364. package/src/runtime/operator-token-cleanup.ts +0 -109
  365. package/src/runtime/path-shadow.ts +0 -418
  366. package/src/runtime/perf/panel-health-monitor.ts +0 -18
  367. package/src/runtime/provider-optimizer-wiring.ts +0 -55
  368. package/src/runtime/reachability-notice.ts +0 -100
  369. package/src/runtime/remote-execution-composition.ts +0 -62
  370. package/src/runtime/session-liveness-marker.ts +0 -257
  371. package/src/runtime/session-pointer-surface.ts +0 -22
  372. package/src/runtime/session-storage-services.ts +0 -41
  373. package/src/runtime/surface-feature-flags.ts +0 -96
  374. package/src/runtime/terminal-output-guard.ts +0 -235
  375. package/src/runtime/trust/workspace-registration.ts +0 -192
  376. package/src/runtime/trust/workspace-trust.ts +0 -229
  377. package/src/runtime/ui/index.ts +0 -39
  378. package/src/runtime/ui/model-picker/data-provider.ts +0 -182
  379. package/src/runtime/ui/model-picker/health-enrichment.ts +0 -231
  380. package/src/runtime/ui/model-picker/index.ts +0 -59
  381. package/src/runtime/ui/model-picker/types.ts +0 -153
  382. package/src/runtime/ui/provider-health/data-provider.ts +0 -244
  383. package/src/runtime/ui/provider-health/fallback-visualizer.ts +0 -71
  384. package/src/runtime/ui/provider-health/index.ts +0 -46
  385. package/src/runtime/ui/provider-health/types.ts +0 -146
  386. package/src/runtime/ui-events.ts +0 -1
  387. package/src/runtime/ui-read-model-helpers.ts +0 -1
  388. package/src/runtime/ui-read-models-observability-maintenance.ts +0 -1
  389. package/src/runtime/ui-read-models-observability-options.ts +0 -1
  390. package/src/runtime/ui-read-models-observability-remote.ts +0 -1
  391. package/src/runtime/ui-read-models-observability-security.ts +0 -1
  392. package/src/runtime/ui-read-models-observability-system.ts +0 -1
  393. package/src/runtime/ui-read-models-observability.ts +0 -1
  394. package/src/runtime/ui-service-queries.ts +0 -1
  395. package/src/runtime/voice-setup-services.ts +0 -98
  396. package/src/runtime/workstream-attempts-validation.ts +0 -77
  397. package/src/runtime/workstream-draft-edits.ts +0 -95
  398. package/src/runtime/workstream-draft-store.ts +0 -274
  399. package/src/runtime/workstream-draft-types.ts +0 -42
  400. package/src/runtime/workstream-services.ts +0 -373
  401. package/src/tools/index.ts +0 -1
  402. package/src/types/grid.ts +0 -48
  403. package/src/types/sql-js.d.ts +0 -15
  404. package/src/work-plans/work-plan-store.ts +0 -702
@@ -1,192 +0,0 @@
1
- /**
2
- * workspace-registration.ts — first-open workspace REGISTRATION half.
3
- *
4
- * Companion to the per-workspace trust gate (workspace-trust.ts). Trust is
5
- * TUI-local (a decision persisted under <cwd>/.goodvibes/tui); REGISTRATION is
6
- * the platform-wide "which project roots has the operator opted GoodVibes into"
7
- * registry, owned by the SDK.
8
- *
9
- * SINGLE SOURCE OF TRUTH. Resolution and mutation both go through the SDK's
10
- * shared WorkspaceRegistrationStore — the exact class the daemon composes its
11
- * `workspaces.*` gateway verbs over (control-plane/routes/register-gateway-verb-
12
- * groups.ts constructs `new WorkspaceRegistrationStore({ path: resolveUserPath(
13
- * 'control-plane', 'workspace-registrations.json'), homeDir: dirname(daemonState
14
- * Dir), daemonStateDir })`). We point an in-process store at that identical path
15
- * and roots, so whether the daemon's `workspaces.resolve` verb reads it over the
16
- * wire or we read it in-process, both operate on ONE persisted document. This is
17
- * not a divergent reimplementation: same SDK store class, same file, same
18
- * root-guard. In-process (rather than an RPC to a composed daemon) because the
19
- * first-open prompt renders synchronously at startup and must not depend on a
20
- * daemon being up — the file is the shared source either way.
21
- *
22
- * Coverage semantics are the store's (implemented in the SDK, not here): coverage
23
- * flows DOWN a registered root's subtree, a linked git worktree inherits its main
24
- * repo's registration, and a remembered decline is subtree-scoped at the root it
25
- * was asked. So the register half of the first-open prompt only appears when
26
- * resolution says UNKNOWN; a covered or declined path never re-asks.
27
- */
28
- import { dirname } from 'node:path';
29
- import { parse as parsePath } from 'node:path';
30
- import {
31
- WorkspaceRegistrationStore,
32
- WorkspaceRegistrationError,
33
- normalizeWorkspaceRoot,
34
- type WorkspaceCoverageStatus,
35
- type RegisterWorkspaceResult,
36
- } from '@pellux/goodvibes-sdk/platform/workspace';
37
-
38
- /** The slice of ShellPathService this manager needs. */
39
- export interface WorkspaceRegistrationShellPaths {
40
- readonly workingDirectory: string;
41
- readonly homeDirectory: string;
42
- resolveUserPath(...segments: string[]): string;
43
- }
44
-
45
- /** Filename of the shared control-plane registry document (matches the daemon composition). */
46
- const REGISTRATIONS_FILE = 'workspace-registrations.json';
47
-
48
- export interface WorkspaceRegistrationEvaluation {
49
- /** Normalized absolute working directory the prompt would ask about. */
50
- readonly root: string;
51
- /** Coverage verdict from the shared store: covered / declined / unknown. */
52
- readonly status: WorkspaceCoverageStatus;
53
- /** The nearest registered root covering the path, or null. */
54
- readonly coveredBy: string | null;
55
- /** True when coverage was inherited through the git worktree→main-repo link. */
56
- readonly viaWorktreeLink: boolean;
57
- /** True when the root is one the store would refuse ($HOME, filesystem root, ~/.goodvibes). */
58
- readonly broad: boolean;
59
- /**
60
- * Whether to OFFER the register half of the first-open prompt: only when the
61
- * store says UNKNOWN and the root is registrable. Broad roots are never
62
- * offered — the store refuses them, so we do not prompt for what would be
63
- * refused. Covered / declined roots never re-ask.
64
- */
65
- readonly offerRegister: boolean;
66
- /** Human-readable justification from the resolver. */
67
- readonly reason: string;
68
- }
69
-
70
- export interface WorkspaceRegistrationManagerOptions {
71
- readonly shellPaths: WorkspaceRegistrationShellPaths;
72
- /**
73
- * Inject a pre-built store (e.g. a `:memory:` store for tests). Defaults to an
74
- * in-process store at the shared control-plane path, configured identically to
75
- * the daemon's.
76
- */
77
- readonly store?: WorkspaceRegistrationStore;
78
- }
79
-
80
- export class WorkspaceRegistrationManager {
81
- private readonly store: WorkspaceRegistrationStore;
82
- private readonly workingDirectory: string;
83
- private readonly homeDir: string;
84
- private readonly daemonStateDir: string;
85
-
86
- constructor(options: WorkspaceRegistrationManagerOptions) {
87
- const { shellPaths } = options;
88
- this.workingDirectory = shellPaths.workingDirectory;
89
- // Mirror the daemon's composition exactly (register-gateway-verb-groups.ts):
90
- // the daemon state dir IS resolveUserPath() (~/.goodvibes); its parent is the
91
- // home directory — both refused as broad roots by the same guard.
92
- this.daemonStateDir = shellPaths.resolveUserPath();
93
- this.homeDir = dirname(this.daemonStateDir);
94
- this.store =
95
- options.store ??
96
- new WorkspaceRegistrationStore({
97
- path: shellPaths.resolveUserPath('control-plane', REGISTRATIONS_FILE),
98
- homeDir: this.homeDir,
99
- daemonStateDir: this.daemonStateDir,
100
- });
101
- }
102
-
103
- /**
104
- * isBroadRoot — mirrors the SDK store's broad-root refusal SET (filesystem
105
- * root, home directory, daemon state dir) so the register half is never
106
- * offered for a root the store would refuse. This is a UI-suppression predicate
107
- * only; the store's `add` remains the enforcement authority (see register()),
108
- * which is why register() also catches WorkspaceRegistrationError.
109
- */
110
- private isBroadRoot(normalizedRoot: string): boolean {
111
- if (normalizedRoot === parsePath(normalizedRoot).root) return true;
112
- if (normalizedRoot === normalizeWorkspaceRoot(this.homeDir)) return true;
113
- if (normalizedRoot === normalizeWorkspaceRoot(this.daemonStateDir)) return true;
114
- return false;
115
- }
116
-
117
- /**
118
- * Resolve the current working directory against the shared registry and decide
119
- * whether the register half of the first-open prompt should appear. Read-only —
120
- * never writes, so it is safe to call from `status`/`doctor`.
121
- */
122
- async evaluate(): Promise<WorkspaceRegistrationEvaluation> {
123
- const resolution = await this.store.resolve(this.workingDirectory);
124
- const broad = this.isBroadRoot(resolution.path);
125
- return {
126
- root: resolution.path,
127
- status: resolution.status,
128
- coveredBy: resolution.coveredBy,
129
- viaWorktreeLink: resolution.viaWorktreeLink,
130
- broad,
131
- offerRegister: resolution.status === 'unknown' && !broad,
132
- reason: resolution.reason,
133
- };
134
- }
135
-
136
- /**
137
- * Register the current working directory at its resolved project root. The
138
- * store refuses broad roots (WorkspaceRegistrationError) — we surface that
139
- * honestly rather than record a phantom registration.
140
- *
141
- * `origin` stamps which surface wrote the record (honest provenance). This
142
- * NEVER passes `checkpointEligible`: absent means false, so a TUI self-record
143
- * can never widen the checkpoint-owning consumer's boundary — only that
144
- * consumer stamps its own roots eligible on boot.
145
- */
146
- async register(label?: string, origin?: string): Promise<
147
- | { readonly registered: true; readonly result: RegisterWorkspaceResult }
148
- | { readonly registered: false; readonly refusedReason: string }
149
- > {
150
- const opts = label || origin
151
- ? { ...(label ? { label } : {}), ...(origin ? { origin } : {}) }
152
- : undefined;
153
- try {
154
- const result = await this.store.add(this.workingDirectory, opts);
155
- return { registered: true, result };
156
- } catch (error) {
157
- if (error instanceof WorkspaceRegistrationError) {
158
- return { registered: false, refusedReason: error.message };
159
- }
160
- throw error;
161
- }
162
- }
163
-
164
- /** Remember a subtree-scoped decline at the current working directory. Idempotent. */
165
- async decline(): Promise<{ readonly root: string; readonly alreadyDeclined: boolean }> {
166
- return this.store.decline(this.workingDirectory);
167
- }
168
-
169
- /** The normalized working directory this manager asks about. */
170
- getWorkingRoot(): string {
171
- return normalizeWorkspaceRoot(this.workingDirectory);
172
- }
173
-
174
- /**
175
- * A one-liner registration posture for `status`/`doctor`. Read-only.
176
- */
177
- async describe(): Promise<string> {
178
- const evaluation = await this.evaluate();
179
- switch (evaluation.status) {
180
- case 'covered':
181
- return evaluation.viaWorktreeLink
182
- ? `registered (via worktree link to ${evaluation.coveredBy})`
183
- : `registered (covered by ${evaluation.coveredBy})`;
184
- case 'declined':
185
- return 'not registered (registration declined for this directory)';
186
- case 'unknown':
187
- return evaluation.broad
188
- ? 'not registered (this root is too broad to register)'
189
- : 'not registered (never asked)';
190
- }
191
- }
192
- }
@@ -1,229 +0,0 @@
1
- /**
2
- * workspace-trust.ts — per-workspace trust gate.
3
- *
4
- * The first time GoodVibes opens a workspace (a cwd / project root it has no
5
- * prior decision for), that workspace is "undecided": only read-category
6
- * tools run; the first write, execute, or delegate tool request raises the
7
- * trust question AT THAT MOMENT — the exact point it has a real consequence —
8
- * instead of silently failing or being decided by a side effect of the
9
- * product's own droppings. This is NOT a parallel permission checker — the
10
- * decision is consulted by the real permission machinery at its final ask
11
- * layer (the requestPermission callback wired in bootstrap-core.ts), so it
12
- * composes with, and cannot drift from, the existing PermissionManager layer
13
- * chain.
14
- *
15
- * Trust is persisted per-workspace in <cwd>/.goodvibes/tui/trust.json. There
16
- * is no more grandfathering: a workspace that already carries prior GoodVibes
17
- * runtime state gets no special treatment (that side-effect-based shortcut
18
- * used to paper over the fact that the first session's write attempts were
19
- * silently denied without ever recording a real decision — now that the first
20
- * attempt properly raises the question and persists the answer, the shortcut
21
- * is unnecessary and would only hide a decision the user should actually see
22
- * once, even on an upgrade).
23
- */
24
- import { existsSync, readdirSync, readFileSync, statSync } from 'node:fs';
25
- import { join } from 'node:path';
26
- import { JsonFileStore } from '@pellux/goodvibes-sdk/platform/state';
27
- import type { PermissionCategory } from '@pellux/goodvibes-sdk/platform/permissions';
28
- import type { PermissionPromptRequest, PermissionPromptDecision } from '@pellux/goodvibes-sdk/platform/permissions';
29
-
30
- type AskCallback = (request: PermissionPromptRequest) => Promise<PermissionPromptDecision>;
31
-
32
- /**
33
- * Wrap the permission machinery's final ask callback with the workspace trust
34
- * gate. This runs inside PermissionManager's own layer chain (it IS the ask
35
- * layer), so it composes with — and cannot drift from — the real machinery.
36
- *
37
- * - Trusted workspace: every category passes through to `ask` unchanged.
38
- * - Restricted workspace with an EXPLICIT prior decision: non-read categories
39
- * are denied outright, same as before — that IS what "restricted" means
40
- * once the user has actually chosen it.
41
- * - Undecided workspace (no decision yet, ever): the first non-read request
42
- * raises `requestTrustDecision()` — one modal, persisted via `setLevel` —
43
- * and, if the answer is 'trusted', the ORIGINAL request is forwarded to
44
- * `ask` so the very thing the user just approved actually happens instead
45
- * of failing anyway. Concurrent requests that arrive before the first
46
- * decision resolves await the same in-flight prompt rather than opening a
47
- * second one.
48
- */
49
- export function trustGatedAsk(
50
- manager: Pick<WorkspaceTrustManager, 'isCategoryAllowed' | 'isDecided' | 'setLevel'>,
51
- ask: AskCallback,
52
- requestTrustDecision: () => Promise<WorkspaceTrustLevel>,
53
- ): AskCallback {
54
- let pendingDecision: Promise<WorkspaceTrustLevel> | null = null;
55
- return async (request) => {
56
- if (manager.isCategoryAllowed(request.category)) return ask(request);
57
- if (!manager.isDecided()) {
58
- if (!pendingDecision) {
59
- pendingDecision = requestTrustDecision().finally(() => {
60
- pendingDecision = null;
61
- });
62
- }
63
- const level = await pendingDecision;
64
- await manager.setLevel(level);
65
- if (manager.isCategoryAllowed(request.category)) return ask(request);
66
- }
67
- return { approved: false };
68
- };
69
- }
70
-
71
- export type WorkspaceTrustLevel = 'trusted' | 'restricted';
72
-
73
- interface PersistedWorkspaceTrust {
74
- level: WorkspaceTrustLevel;
75
- decidedAt: string;
76
- grandfathered?: boolean;
77
- }
78
-
79
- const TRUST_FILE = 'trust.json';
80
-
81
- /**
82
- * detectPriorWorkspaceState — true if <workingDirectory>/.goodvibes already
83
- * holds GoodVibes RUNTIME state from a prior session. Deliberately keyed on
84
- * generated state (sessions/checkpoints/state/memory/onboarding marker), not on
85
- * committed scaffolding like .goodvibes/agents or GOODVIBES.md, so a checked-out
86
- * template does not read as "previously used". MUST be sampled before any
87
- * service initializes the workspace this session (bootstrap-core samples it
88
- * before createRuntimeServices), or this-run scaffolding would look like prior
89
- * use.
90
- */
91
- export function detectPriorWorkspaceState(workingDirectory: string): boolean {
92
- const root = join(workingDirectory, '.goodvibes');
93
- if (!existsSync(root)) return false;
94
-
95
- const fileSignals = [
96
- join(root, 'checkpoints', 'index.json'),
97
- join(root, 'tui', 'onboarding-checked.json'),
98
- join(root, 'goodvibes.json'),
99
- ];
100
- for (const file of fileSignals) {
101
- if (existsSync(file)) return true;
102
- }
103
-
104
- const dirSignals = ['sessions', 'checkpoints', 'state', 'memory'];
105
- for (const dir of dirSignals) {
106
- const path = join(root, dir);
107
- try {
108
- if (existsSync(path) && statSync(path).isDirectory() && readdirSync(path).length > 0) {
109
- return true;
110
- }
111
- } catch {
112
- // Unreadable — treat as absent rather than crash the gate.
113
- }
114
- }
115
- return false;
116
- }
117
-
118
- export interface WorkspaceTrustPaths {
119
- readonly projectGoodVibesRoot: string;
120
- resolveProjectPath(...segments: string[]): string;
121
- }
122
-
123
- /** Read-only view of the persisted trust decision, for status/doctor reporting. */
124
- export interface PersistedWorkspaceTrustView {
125
- /** 'trusted' | 'restricted' when a decision exists; 'undecided' when none is persisted yet. */
126
- readonly level: WorkspaceTrustLevel | 'undecided';
127
- readonly grandfathered: boolean;
128
- }
129
-
130
- /**
131
- * readPersistedWorkspaceTrust — read <cwd>/.goodvibes/tui/trust.json WITHOUT the
132
- * side effects of WorkspaceTrustManager.load() (which grandfathers and persists).
133
- * Reporting surfaces (`status`/`doctor`) must never mutate trust state, so they
134
- * use this pure reader instead of constructing a manager.
135
- */
136
- export function readPersistedWorkspaceTrust(paths: WorkspaceTrustPaths): PersistedWorkspaceTrustView {
137
- const file = paths.resolveProjectPath('tui', TRUST_FILE);
138
- try {
139
- if (!existsSync(file)) return { level: 'undecided', grandfathered: false };
140
- const parsed = JSON.parse(readFileSync(file, 'utf-8')) as Partial<PersistedWorkspaceTrust>;
141
- if (parsed.level === 'trusted' || parsed.level === 'restricted') {
142
- return { level: parsed.level, grandfathered: parsed.grandfathered ?? false };
143
- }
144
- } catch {
145
- // Unreadable/corrupt — report undecided rather than crash the report.
146
- }
147
- return { level: 'undecided', grandfathered: false };
148
- }
149
-
150
- export interface WorkspaceTrustManagerOptions {
151
- readonly shellPaths: WorkspaceTrustPaths;
152
- }
153
-
154
- export class WorkspaceTrustManager {
155
- private level: WorkspaceTrustLevel | null = null; // null = undecided (new place)
156
- private grandfathered = false;
157
- private loaded = false;
158
- private readonly store: JsonFileStore<PersistedWorkspaceTrust>;
159
-
160
- constructor(options: WorkspaceTrustManagerOptions) {
161
- this.store = new JsonFileStore<PersistedWorkspaceTrust>(
162
- options.shellPaths.resolveProjectPath('tui', TRUST_FILE),
163
- );
164
- }
165
-
166
- /**
167
- * Load the persisted decision, if one exists. No grandfathering: a
168
- * workspace with no persisted decision stays undecided (the gate treats it
169
- * as restricted, and the first non-read tool request raises the trust
170
- * question via `trustGatedAsk`'s `requestTrustDecision` callback) — even
171
- * one that already carries prior GoodVibes runtime state. `grandfathered`
172
- * on an already-persisted decision is read-only history from before this
173
- * fix (status/doctor still report it honestly); nothing new is ever
174
- * grandfathered.
175
- */
176
- async load(): Promise<void> {
177
- if (this.loaded) return;
178
- const persisted = await this.store.load().catch(() => null);
179
- if (persisted && (persisted.level === 'trusted' || persisted.level === 'restricted')) {
180
- this.level = persisted.level;
181
- this.grandfathered = persisted.grandfathered ?? false;
182
- }
183
- this.loaded = true;
184
- }
185
-
186
- isDecided(): boolean {
187
- return this.level !== null;
188
- }
189
-
190
- /** Undecided workspaces read as 'restricted' — the safe default before a choice. */
191
- getLevel(): WorkspaceTrustLevel {
192
- return this.level ?? 'restricted';
193
- }
194
-
195
- isTrusted(): boolean {
196
- return this.level === 'trusted';
197
- }
198
-
199
- wasGrandfathered(): boolean {
200
- return this.grandfathered;
201
- }
202
-
203
- async setLevel(level: WorkspaceTrustLevel): Promise<void> {
204
- this.level = level;
205
- this.grandfathered = false;
206
- this.loaded = true;
207
- await this.persist();
208
- }
209
-
210
- /**
211
- * The restriction rule the permission machinery consults. Read tools always
212
- * run (read-only exploration is the whole point of "restricted"); every other
213
- * category (write / execute / delegate) is denied until the workspace is
214
- * trusted.
215
- */
216
- isCategoryAllowed(category: PermissionCategory): boolean {
217
- if (this.isTrusted()) return true;
218
- return category === 'read';
219
- }
220
-
221
- private async persist(): Promise<void> {
222
- if (this.level === null) return;
223
- await this.store.save({
224
- level: this.level,
225
- decidedAt: new Date().toISOString(),
226
- grandfathered: this.grandfathered,
227
- });
228
- }
229
- }
@@ -1,39 +0,0 @@
1
- /**
2
- * Runtime UI data surface barrel.
3
- *
4
- * Exports enriched data providers and one-shot factory functions for
5
- * the model picker and provider health surfaces.
6
- *
7
- * These modules produce structured data for renderers — no UI rendering
8
- * logic lives here.
9
- */
10
-
11
- // ── Model picker ──────────────────────────────────────────────────────────────
12
- export type {
13
- CapabilityFlags,
14
- ProviderLatencyStats,
15
- ProviderHealthContext,
16
- ModelPickerEntry,
17
- ModelPickerGroup,
18
- ModelPickerData,
19
- } from '@/runtime/index.ts';
20
- export type { ProviderStatus as ModelPickerProviderStatus } from '@/runtime/index.ts';
21
- export { ModelPickerDataProvider, createModelPickerData } from '@/runtime/index.ts';
22
- export type { ModelPickerDataProviderOptions } from '@/runtime/index.ts';
23
-
24
- // ── Provider health ───────────────────────────────────────────────────────────
25
- export type {
26
- HealthTimelinePoint,
27
- HealthTimeline,
28
- ProviderHealthEntry,
29
- FallbackChainNode,
30
- FallbackChainData,
31
- ProviderHealthData,
32
- CompositeHealthStatus,
33
- } from '@/runtime/index.ts';
34
- export type { ProviderStatus } from '@/runtime/index.ts';
35
- export {
36
- ProviderHealthDataProvider,
37
- buildFallbackChainData,
38
- createProviderHealthData,
39
- } from '@/runtime/index.ts';
@@ -1,182 +0,0 @@
1
- /**
2
- * ModelPickerDataProvider — enriched model picker data surface.
3
- *
4
- * Combines ModelDefinition records from the provider registry with
5
- * ProviderHealthDomainState and ModelDomainState to produce a single,
6
- * sorted, health-enriched ModelPickerData snapshot for UI consumption.
7
- *
8
- * This class is a data provider only — it contains no rendering logic.
9
- * Subscribe to change notifications and call getSnapshot() to render.
10
- */
11
- import type { ModelDefinition } from '@pellux/goodvibes-sdk/platform/providers';
12
- import type { ProviderRegistry } from '@pellux/goodvibes-sdk/platform/providers';
13
- import type { BenchmarkStore } from '@pellux/goodvibes-sdk/platform/providers';
14
- import type { ProviderHealthDomainState } from '@/runtime/index.ts';
15
- import type { ModelDomainState } from '@/runtime/index.ts';
16
- import { enrichModelEntries, groupEntriesByProvider } from '@/runtime/index.ts';
17
- import type { ModelPickerData, ModelPickerEntry } from '@/runtime/index.ts';
18
-
19
- /** Options for constructing a ModelPickerDataProvider. */
20
- export interface ModelPickerDataProviderOptions {
21
- /**
22
- * Initial set of pinned model IDs.
23
- * Call updatePinnedIds() to update at runtime.
24
- */
25
- readonly pinnedIds?: ReadonlySet<string>;
26
- readonly benchmarkStore: Pick<BenchmarkStore, 'getBenchmarks'>;
27
- readonly providerRegistry: Pick<ProviderRegistry, 'getSyntheticModelInfoFromCatalog' | 'getContextWindowForModel'>;
28
- }
29
-
30
- /**
31
- * ModelPickerDataProvider produces enriched model picker data snapshots.
32
- *
33
- * Usage:
34
- * ```ts
35
- * const provider = new ModelPickerDataProvider(models, healthState, modelState);
36
- * const unsub = provider.subscribe(() => {
37
- * const data = provider.getSnapshot();
38
- * // render data.entries or data.groups
39
- * });
40
- * // When health or model state changes:
41
- * provider.updateHealthState(newHealthState);
42
- * provider.updateModelState(newModelState);
43
- * // Cleanup:
44
- * unsub();
45
- * provider.dispose();
46
- * ```
47
- */
48
- export class ModelPickerDataProvider {
49
- private _models: readonly ModelDefinition[];
50
- private _healthState: ProviderHealthDomainState;
51
- private _modelState: ModelDomainState;
52
- private _pinnedIds: ReadonlySet<string>;
53
- private _snapshot: ModelPickerData;
54
- private readonly benchmarkStore: Pick<BenchmarkStore, 'getBenchmarks'>;
55
- private readonly providerRegistry: Pick<ProviderRegistry, 'getSyntheticModelInfoFromCatalog' | 'getContextWindowForModel'>;
56
- private readonly _subscribers = new Set<() => void>();
57
-
58
- constructor(
59
- models: readonly ModelDefinition[],
60
- healthState: ProviderHealthDomainState,
61
- modelState: ModelDomainState,
62
- options: ModelPickerDataProviderOptions,
63
- ) {
64
- this._models = models;
65
- this._healthState = healthState;
66
- this._modelState = modelState;
67
- this._pinnedIds = options.pinnedIds ?? new Set();
68
- this.benchmarkStore = options.benchmarkStore;
69
- this.providerRegistry = options.providerRegistry;
70
- this._snapshot = this._buildSnapshot();
71
- }
72
-
73
- /**
74
- * Return the current enriched model picker data snapshot.
75
- * This is updated synchronously when state changes via update methods.
76
- */
77
- public getSnapshot(): ModelPickerData {
78
- return this._snapshot;
79
- }
80
-
81
- /**
82
- * Register a callback invoked whenever the snapshot changes.
83
- * @returns An unsubscribe function.
84
- */
85
- public subscribe(callback: () => void): () => void {
86
- this._subscribers.add(callback);
87
- return () => this._subscribers.delete(callback);
88
- }
89
-
90
- /**
91
- * Update the model list (e.g. after registry reload).
92
- * Triggers a snapshot rebuild and notifies subscribers.
93
- */
94
- public updateModels(models: readonly ModelDefinition[]): void {
95
- this._models = models;
96
- this._rebuild();
97
- }
98
-
99
- /**
100
- * Update health state (e.g. on ProviderHealthDomainState change).
101
- * Triggers a snapshot rebuild and notifies subscribers.
102
- */
103
- public updateHealthState(healthState: ProviderHealthDomainState): void {
104
- this._healthState = healthState;
105
- this._rebuild();
106
- }
107
-
108
- /**
109
- * Update model domain state (e.g. on active model or fallback change).
110
- * Triggers a snapshot rebuild and notifies subscribers.
111
- */
112
- public updateModelState(modelState: ModelDomainState): void {
113
- this._modelState = modelState;
114
- this._rebuild();
115
- }
116
-
117
- /**
118
- * Update the pinned/favorites model ID set.
119
- * Triggers a snapshot rebuild and notifies subscribers.
120
- */
121
- public updatePinnedIds(pinnedIds: ReadonlySet<string>): void {
122
- this._pinnedIds = pinnedIds;
123
- this._rebuild();
124
- }
125
-
126
- /**
127
- * Release all subscriber references.
128
- * Does not clear internal state — getSnapshot() remains usable after disposal.
129
- */
130
- public dispose(): void {
131
- this._subscribers.clear();
132
- }
133
-
134
- // ── Private ────────────────────────────────────────────────────────────────
135
-
136
- private _rebuild(): void {
137
- this._snapshot = this._buildSnapshot();
138
- this._notify();
139
- }
140
-
141
- private _buildSnapshot(): ModelPickerData {
142
- const entries: readonly ModelPickerEntry[] = enrichModelEntries(
143
- this._models,
144
- this._healthState,
145
- this._modelState,
146
- this._pinnedIds,
147
- this.benchmarkStore,
148
- this.providerRegistry,
149
- );
150
-
151
- const groups = groupEntriesByProvider(entries);
152
-
153
- const degradedProviderIds: string[] = [];
154
- const unavailableProviderIds: string[] = [];
155
- for (const [id, record] of this._healthState.providers) {
156
- if (record.status === 'degraded' || record.status === 'rate_limited') {
157
- degradedProviderIds.push(id);
158
- } else if (record.status === 'unavailable' || record.status === 'auth_error') {
159
- unavailableProviderIds.push(id);
160
- }
161
- }
162
-
163
- return {
164
- entries,
165
- groups,
166
- degradedProviderIds,
167
- unavailableProviderIds,
168
- activeModelId: this._modelState.activeModelId,
169
- snapshotAt: Date.now(),
170
- };
171
- }
172
-
173
- private _notify(): void {
174
- for (const cb of this._subscribers) {
175
- try {
176
- cb();
177
- } catch {
178
- // Non-fatal: subscriber errors must not crash the provider
179
- }
180
- }
181
- }
182
- }