@pellux/goodvibes-agent 0.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 (398) hide show
  1. package/.goodvibes/GOODVIBES.md +35 -0
  2. package/.goodvibes/agents/reviewer.md +48 -0
  3. package/.goodvibes/skills/add-provider/SKILL.md +199 -0
  4. package/CHANGELOG.md +25 -0
  5. package/README.md +74 -0
  6. package/bin/goodvibes-agent.ts +2 -0
  7. package/docs/README.md +23 -0
  8. package/docs/deployment-and-services.md +57 -0
  9. package/docs/getting-started.md +53 -0
  10. package/docs/release-and-publishing.md +46 -0
  11. package/package.json +134 -0
  12. package/scripts/check-bun.sh +20 -0
  13. package/src/audio/player.ts +156 -0
  14. package/src/audio/spoken-turn-controller.ts +203 -0
  15. package/src/audio/spoken-turn-model-routing.ts +117 -0
  16. package/src/audio/spoken-turn-wiring.ts +44 -0
  17. package/src/audio/text-chunker.ts +110 -0
  18. package/src/cli/bundle-command.ts +227 -0
  19. package/src/cli/completion.ts +90 -0
  20. package/src/cli/config-overrides.ts +159 -0
  21. package/src/cli/endpoints.ts +63 -0
  22. package/src/cli/entrypoint.ts +172 -0
  23. package/src/cli/help.ts +299 -0
  24. package/src/cli/index.ts +11 -0
  25. package/src/cli/management-commands.ts +426 -0
  26. package/src/cli/management.ts +744 -0
  27. package/src/cli/network-posture.ts +46 -0
  28. package/src/cli/package-verification.ts +123 -0
  29. package/src/cli/parser.ts +369 -0
  30. package/src/cli/provider-auth-routes.ts +22 -0
  31. package/src/cli/provider-classification.ts +107 -0
  32. package/src/cli/redaction.ts +105 -0
  33. package/src/cli/service-command.ts +26 -0
  34. package/src/cli/service-posture.ts +482 -0
  35. package/src/cli/status.ts +383 -0
  36. package/src/cli/surface-command.ts +247 -0
  37. package/src/cli/tui-startup.ts +32 -0
  38. package/src/cli/types.ts +69 -0
  39. package/src/cli-flags.ts +21 -0
  40. package/src/config/goodvibes-home-audit.ts +465 -0
  41. package/src/config/index.ts +57 -0
  42. package/src/config/provider-model.ts +23 -0
  43. package/src/config/secret-config.ts +119 -0
  44. package/src/config/secrets.ts +71 -0
  45. package/src/config/surface.ts +1 -0
  46. package/src/core/composer-state.ts +61 -0
  47. package/src/core/conversation-rendering.ts +359 -0
  48. package/src/core/conversation.ts +551 -0
  49. package/src/core/history.ts +45 -0
  50. package/src/core/orchestrator.ts +7 -0
  51. package/src/core/system-message-router.ts +171 -0
  52. package/src/daemon/cli.ts +55 -0
  53. package/src/daemon/safe-serve.ts +61 -0
  54. package/src/input/agent-workspace.ts +428 -0
  55. package/src/input/autocomplete.ts +96 -0
  56. package/src/input/bookmark-modal.ts +115 -0
  57. package/src/input/command-args-hint.ts +36 -0
  58. package/src/input/command-registry.ts +329 -0
  59. package/src/input/commands/agent-externalized-tui.ts +73 -0
  60. package/src/input/commands/agent-workspace-runtime.ts +17 -0
  61. package/src/input/commands/branch-runtime.ts +72 -0
  62. package/src/input/commands/cloudflare-runtime.ts +370 -0
  63. package/src/input/commands/config.ts +18 -0
  64. package/src/input/commands/control-room-runtime.ts +255 -0
  65. package/src/input/commands/conversation-runtime.ts +207 -0
  66. package/src/input/commands/discovery-runtime.ts +52 -0
  67. package/src/input/commands/eval.ts +204 -0
  68. package/src/input/commands/experience-runtime.ts +278 -0
  69. package/src/input/commands/guidance-runtime.ts +106 -0
  70. package/src/input/commands/health-runtime.ts +434 -0
  71. package/src/input/commands/hooks-runtime.ts +148 -0
  72. package/src/input/commands/incident-runtime.ts +95 -0
  73. package/src/input/commands/integration-runtime.ts +394 -0
  74. package/src/input/commands/intelligence-runtime.ts +223 -0
  75. package/src/input/commands/knowledge.ts +531 -0
  76. package/src/input/commands/local-auth-runtime.ts +105 -0
  77. package/src/input/commands/local-provider-runtime.ts +170 -0
  78. package/src/input/commands/local-runtime.ts +392 -0
  79. package/src/input/commands/local-setup-review.ts +199 -0
  80. package/src/input/commands/local-setup-transfer.ts +135 -0
  81. package/src/input/commands/local-setup.ts +282 -0
  82. package/src/input/commands/managed-runtime.ts +209 -0
  83. package/src/input/commands/marketplace-runtime.ts +290 -0
  84. package/src/input/commands/mcp-runtime.ts +432 -0
  85. package/src/input/commands/memory-product-runtime.ts +111 -0
  86. package/src/input/commands/memory.ts +151 -0
  87. package/src/input/commands/notify-runtime.ts +83 -0
  88. package/src/input/commands/onboarding-runtime.ts +14 -0
  89. package/src/input/commands/operator-panel-runtime.ts +146 -0
  90. package/src/input/commands/operator-runtime.ts +392 -0
  91. package/src/input/commands/planning-runtime.ts +205 -0
  92. package/src/input/commands/platform-access-runtime.ts +422 -0
  93. package/src/input/commands/platform-services-runtime.ts +246 -0
  94. package/src/input/commands/policy-dispatch.ts +339 -0
  95. package/src/input/commands/policy.ts +17 -0
  96. package/src/input/commands/product-runtime.ts +351 -0
  97. package/src/input/commands/profile-sync-runtime.ts +99 -0
  98. package/src/input/commands/provider-accounts-runtime.ts +113 -0
  99. package/src/input/commands/provider.ts +363 -0
  100. package/src/input/commands/qrcode-runtime.ts +20 -0
  101. package/src/input/commands/quit-shared.ts +162 -0
  102. package/src/input/commands/recall-bundle.ts +132 -0
  103. package/src/input/commands/recall-capture.ts +152 -0
  104. package/src/input/commands/recall-query.ts +229 -0
  105. package/src/input/commands/recall-review.ts +98 -0
  106. package/src/input/commands/recall-shared.ts +22 -0
  107. package/src/input/commands/remote-runtime-pool.ts +106 -0
  108. package/src/input/commands/remote-runtime-setup.ts +199 -0
  109. package/src/input/commands/remote-runtime.ts +431 -0
  110. package/src/input/commands/replay-runtime.ts +18 -0
  111. package/src/input/commands/runtime-services.ts +291 -0
  112. package/src/input/commands/schedule-runtime.ts +91 -0
  113. package/src/input/commands/services-runtime.ts +209 -0
  114. package/src/input/commands/session-content.ts +408 -0
  115. package/src/input/commands/session-workflow.ts +464 -0
  116. package/src/input/commands/session.ts +375 -0
  117. package/src/input/commands/settings-sync-runtime.ts +174 -0
  118. package/src/input/commands/share-runtime.ts +119 -0
  119. package/src/input/commands/shell-core.ts +307 -0
  120. package/src/input/commands/skills-runtime.ts +221 -0
  121. package/src/input/commands/subscription-runtime.ts +434 -0
  122. package/src/input/commands/tasks-runtime.ts +230 -0
  123. package/src/input/commands/teamwork-runtime.ts +339 -0
  124. package/src/input/commands/teleport-runtime.ts +57 -0
  125. package/src/input/commands/tts-runtime.ts +29 -0
  126. package/src/input/commands/work-plan-runtime.ts +169 -0
  127. package/src/input/commands.ts +131 -0
  128. package/src/input/feed-context-factory.ts +254 -0
  129. package/src/input/file-picker.ts +192 -0
  130. package/src/input/handler-command-route.ts +180 -0
  131. package/src/input/handler-content-actions.ts +497 -0
  132. package/src/input/handler-feed-routes.ts +648 -0
  133. package/src/input/handler-feed.ts +452 -0
  134. package/src/input/handler-interactions.ts +281 -0
  135. package/src/input/handler-modal-routes.ts +418 -0
  136. package/src/input/handler-modal-stack.ts +263 -0
  137. package/src/input/handler-modal-token-routes.ts +329 -0
  138. package/src/input/handler-onboarding-cloudflare.ts +391 -0
  139. package/src/input/handler-onboarding.ts +620 -0
  140. package/src/input/handler-picker-routes.ts +472 -0
  141. package/src/input/handler-prompt-buffer.ts +320 -0
  142. package/src/input/handler-shortcuts.ts +213 -0
  143. package/src/input/handler-ui-state.ts +372 -0
  144. package/src/input/handler.ts +729 -0
  145. package/src/input/input-history.ts +297 -0
  146. package/src/input/keybindings.ts +292 -0
  147. package/src/input/mcp-workspace.ts +554 -0
  148. package/src/input/model-picker-provider-filter.ts +28 -0
  149. package/src/input/model-picker-types.ts +137 -0
  150. package/src/input/model-picker.ts +797 -0
  151. package/src/input/onboarding/handler-onboarding-routes.ts +125 -0
  152. package/src/input/onboarding/onboarding-runtime-status.ts +87 -0
  153. package/src/input/onboarding/onboarding-wizard-apply.ts +277 -0
  154. package/src/input/onboarding/onboarding-wizard-cloudflare-step.ts +494 -0
  155. package/src/input/onboarding/onboarding-wizard-cloudflare.ts +204 -0
  156. package/src/input/onboarding/onboarding-wizard-constants.ts +158 -0
  157. package/src/input/onboarding/onboarding-wizard-external-surface-extra-specs.ts +130 -0
  158. package/src/input/onboarding/onboarding-wizard-external-surfaces.ts +762 -0
  159. package/src/input/onboarding/onboarding-wizard-helpers.ts +167 -0
  160. package/src/input/onboarding/onboarding-wizard-rules.ts +256 -0
  161. package/src/input/onboarding/onboarding-wizard-state.ts +365 -0
  162. package/src/input/onboarding/onboarding-wizard-steps.ts +798 -0
  163. package/src/input/onboarding/onboarding-wizard-types.ts +195 -0
  164. package/src/input/onboarding/onboarding-wizard.ts +711 -0
  165. package/src/input/panel-integration-actions.ts +78 -0
  166. package/src/input/profile-picker-modal.ts +222 -0
  167. package/src/input/search.ts +100 -0
  168. package/src/input/selection-modal.ts +163 -0
  169. package/src/input/selection.ts +135 -0
  170. package/src/input/session-picker-modal.ts +136 -0
  171. package/src/input/settings-modal-behavior.ts +37 -0
  172. package/src/input/settings-modal-secrets.ts +41 -0
  173. package/src/input/settings-modal-subscriptions.ts +95 -0
  174. package/src/input/settings-modal-types.ts +91 -0
  175. package/src/input/settings-modal.ts +793 -0
  176. package/src/input/submission-intent.ts +17 -0
  177. package/src/input/submission-router.ts +59 -0
  178. package/src/input/tts-settings-actions.ts +100 -0
  179. package/src/main.ts +792 -0
  180. package/src/mcp/runtime-reload.ts +81 -0
  181. package/src/panels/agent-inspector-panel.ts +521 -0
  182. package/src/panels/agent-inspector-shared.ts +94 -0
  183. package/src/panels/agent-logs-panel.ts +559 -0
  184. package/src/panels/agent-logs-shared.ts +129 -0
  185. package/src/panels/approval-panel.ts +150 -0
  186. package/src/panels/automation-control-panel.ts +212 -0
  187. package/src/panels/base-panel.ts +254 -0
  188. package/src/panels/builtin/agent.ts +117 -0
  189. package/src/panels/builtin/development.ts +31 -0
  190. package/src/panels/builtin/knowledge.ts +26 -0
  191. package/src/panels/builtin/operations.ts +349 -0
  192. package/src/panels/builtin/session.ts +129 -0
  193. package/src/panels/builtin/shared.ts +274 -0
  194. package/src/panels/builtin-panels.ts +23 -0
  195. package/src/panels/cockpit-panel.ts +183 -0
  196. package/src/panels/communication-panel.ts +153 -0
  197. package/src/panels/confirm-state.ts +61 -0
  198. package/src/panels/context-visualizer-panel.ts +204 -0
  199. package/src/panels/control-plane-panel.ts +211 -0
  200. package/src/panels/cost-tracker-panel.ts +444 -0
  201. package/src/panels/debug-panel.ts +432 -0
  202. package/src/panels/diff-panel.ts +520 -0
  203. package/src/panels/docs-panel.ts +283 -0
  204. package/src/panels/eval-panel.ts +399 -0
  205. package/src/panels/file-explorer-panel.ts +584 -0
  206. package/src/panels/file-preview-panel.ts +434 -0
  207. package/src/panels/forensics-panel.ts +364 -0
  208. package/src/panels/git-panel.ts +638 -0
  209. package/src/panels/hooks-panel.ts +239 -0
  210. package/src/panels/incident-review-panel.ts +197 -0
  211. package/src/panels/index.ts +46 -0
  212. package/src/panels/intelligence-panel.ts +176 -0
  213. package/src/panels/knowledge-panel.ts +345 -0
  214. package/src/panels/local-auth-panel.ts +130 -0
  215. package/src/panels/marketplace-panel.ts +212 -0
  216. package/src/panels/memory-panel.ts +225 -0
  217. package/src/panels/ops-control-panel.ts +150 -0
  218. package/src/panels/ops-strategy-panel.ts +235 -0
  219. package/src/panels/orchestration-panel.ts +273 -0
  220. package/src/panels/panel-list-panel.ts +509 -0
  221. package/src/panels/panel-manager.ts +570 -0
  222. package/src/panels/panel-picker.ts +106 -0
  223. package/src/panels/plan-dashboard-panel.ts +274 -0
  224. package/src/panels/plugins-panel.ts +178 -0
  225. package/src/panels/policy-panel.ts +308 -0
  226. package/src/panels/polish.ts +717 -0
  227. package/src/panels/project-planning-panel.ts +711 -0
  228. package/src/panels/provider-account-snapshot.ts +259 -0
  229. package/src/panels/provider-accounts-panel.ts +218 -0
  230. package/src/panels/provider-health-domains.ts +215 -0
  231. package/src/panels/provider-health-panel.ts +727 -0
  232. package/src/panels/provider-health-tracker.ts +115 -0
  233. package/src/panels/provider-stats-panel.ts +366 -0
  234. package/src/panels/qr-panel.ts +182 -0
  235. package/src/panels/remote-panel.ts +449 -0
  236. package/src/panels/routes-panel.ts +178 -0
  237. package/src/panels/sandbox-panel.ts +283 -0
  238. package/src/panels/schedule-panel.ts +329 -0
  239. package/src/panels/scrollable-list-panel.ts +491 -0
  240. package/src/panels/search-focus.ts +32 -0
  241. package/src/panels/security-panel.ts +295 -0
  242. package/src/panels/services-panel.ts +231 -0
  243. package/src/panels/session-browser-panel.ts +400 -0
  244. package/src/panels/session-maintenance.ts +125 -0
  245. package/src/panels/settings-sync-panel.ts +120 -0
  246. package/src/panels/skills-panel.ts +431 -0
  247. package/src/panels/subscription-panel.ts +263 -0
  248. package/src/panels/symbol-outline-panel.ts +486 -0
  249. package/src/panels/system-messages-panel.ts +230 -0
  250. package/src/panels/tasks-panel.ts +399 -0
  251. package/src/panels/thinking-panel.ts +304 -0
  252. package/src/panels/token-budget-panel.ts +475 -0
  253. package/src/panels/tool-inspector-panel.ts +429 -0
  254. package/src/panels/types.ts +54 -0
  255. package/src/panels/watchers-panel.ts +193 -0
  256. package/src/panels/work-plan-panel.ts +175 -0
  257. package/src/panels/worktree-panel.ts +182 -0
  258. package/src/panels/wrfc-panel.ts +609 -0
  259. package/src/permissions/prompt.ts +165 -0
  260. package/src/planning/project-planning-coordinator.ts +543 -0
  261. package/src/plugins/loader.ts +15 -0
  262. package/src/renderer/agent-detail-modal.ts +331 -0
  263. package/src/renderer/agent-workspace.ts +238 -0
  264. package/src/renderer/ansi-sanitize.ts +76 -0
  265. package/src/renderer/autocomplete-overlay.ts +154 -0
  266. package/src/renderer/block-actions.ts +76 -0
  267. package/src/renderer/bookmark-modal.ts +101 -0
  268. package/src/renderer/bottom-bar.ts +58 -0
  269. package/src/renderer/buffer.ts +113 -0
  270. package/src/renderer/code-block.ts +373 -0
  271. package/src/renderer/compositor.ts +283 -0
  272. package/src/renderer/context-inspector.ts +219 -0
  273. package/src/renderer/conversation-layout.ts +67 -0
  274. package/src/renderer/conversation-overlays.ts +140 -0
  275. package/src/renderer/conversation-surface.ts +260 -0
  276. package/src/renderer/diff-view.ts +132 -0
  277. package/src/renderer/diff.ts +130 -0
  278. package/src/renderer/file-picker-overlay.ts +101 -0
  279. package/src/renderer/file-tree.ts +153 -0
  280. package/src/renderer/fullscreen-primitives.ts +130 -0
  281. package/src/renderer/fullscreen-workspace.ts +199 -0
  282. package/src/renderer/git-status.ts +89 -0
  283. package/src/renderer/help-overlay.ts +267 -0
  284. package/src/renderer/history-search-overlay.ts +73 -0
  285. package/src/renderer/layout-engine.ts +97 -0
  286. package/src/renderer/layout.ts +32 -0
  287. package/src/renderer/live-tail-modal.ts +156 -0
  288. package/src/renderer/markdown.ts +635 -0
  289. package/src/renderer/mcp-workspace.ts +237 -0
  290. package/src/renderer/modal-factory.ts +467 -0
  291. package/src/renderer/modal-utils.ts +24 -0
  292. package/src/renderer/model-picker-overlay.ts +473 -0
  293. package/src/renderer/model-workspace.ts +488 -0
  294. package/src/renderer/onboarding/onboarding-wizard.ts +615 -0
  295. package/src/renderer/overlay-box.ts +146 -0
  296. package/src/renderer/overlay-viewport.ts +104 -0
  297. package/src/renderer/panel-composite.ts +158 -0
  298. package/src/renderer/panel-picker-overlay.ts +202 -0
  299. package/src/renderer/panel-tab-bar.ts +69 -0
  300. package/src/renderer/panel-workspace-bar.ts +42 -0
  301. package/src/renderer/process-indicator.ts +96 -0
  302. package/src/renderer/process-modal.ts +656 -0
  303. package/src/renderer/process-summary.ts +67 -0
  304. package/src/renderer/profile-picker-modal.ts +129 -0
  305. package/src/renderer/progress.ts +98 -0
  306. package/src/renderer/qr-renderer.ts +120 -0
  307. package/src/renderer/search-overlay.ts +54 -0
  308. package/src/renderer/selection-modal-overlay.ts +214 -0
  309. package/src/renderer/semantic-diff.ts +369 -0
  310. package/src/renderer/session-picker-modal.ts +127 -0
  311. package/src/renderer/settings-modal-helpers.ts +193 -0
  312. package/src/renderer/settings-modal.ts +537 -0
  313. package/src/renderer/shell-surface.ts +88 -0
  314. package/src/renderer/status-glyphs.ts +21 -0
  315. package/src/renderer/status-token.ts +67 -0
  316. package/src/renderer/surface-layout.ts +101 -0
  317. package/src/renderer/syntax-highlighter.ts +542 -0
  318. package/src/renderer/system-message.ts +83 -0
  319. package/src/renderer/tab-strip.ts +108 -0
  320. package/src/renderer/text-layout.ts +31 -0
  321. package/src/renderer/thinking.ts +17 -0
  322. package/src/renderer/tool-call.ts +234 -0
  323. package/src/renderer/ui-factory.ts +524 -0
  324. package/src/renderer/ui-primitives.ts +96 -0
  325. package/src/runtime/bootstrap-command-context.ts +278 -0
  326. package/src/runtime/bootstrap-command-parts.ts +386 -0
  327. package/src/runtime/bootstrap-core.ts +540 -0
  328. package/src/runtime/bootstrap-hook-bridge.ts +112 -0
  329. package/src/runtime/bootstrap-shell.ts +283 -0
  330. package/src/runtime/bootstrap.ts +575 -0
  331. package/src/runtime/cloudflare-control-plane.ts +349 -0
  332. package/src/runtime/context.ts +142 -0
  333. package/src/runtime/diagnostics/panels/index.ts +24 -0
  334. package/src/runtime/diagnostics/panels/ops.ts +156 -0
  335. package/src/runtime/diagnostics/panels/panel-resources.ts +118 -0
  336. package/src/runtime/diagnostics/panels/policy.ts +177 -0
  337. package/src/runtime/index.ts +662 -0
  338. package/src/runtime/onboarding/apply.ts +642 -0
  339. package/src/runtime/onboarding/derivation.ts +534 -0
  340. package/src/runtime/onboarding/index.ts +7 -0
  341. package/src/runtime/onboarding/markers.ts +148 -0
  342. package/src/runtime/onboarding/snapshot.ts +406 -0
  343. package/src/runtime/onboarding/state.ts +141 -0
  344. package/src/runtime/onboarding/types.ts +404 -0
  345. package/src/runtime/onboarding/verify.ts +171 -0
  346. package/src/runtime/operator-token-cleanup.ts +27 -0
  347. package/src/runtime/perf/panel-contracts.ts +32 -0
  348. package/src/runtime/perf/panel-health-monitor.ts +18 -0
  349. package/src/runtime/sandbox-public-gaps.ts +358 -0
  350. package/src/runtime/services.ts +670 -0
  351. package/src/runtime/store/domains/domain-read-matrix.ts +15 -0
  352. package/src/runtime/store/domains/index.ts +222 -0
  353. package/src/runtime/store/domains/panels.ts +117 -0
  354. package/src/runtime/store/domains/ui-perf.ts +103 -0
  355. package/src/runtime/store/index.ts +305 -0
  356. package/src/runtime/store/selectors/index.ts +359 -0
  357. package/src/runtime/store/state.ts +145 -0
  358. package/src/runtime/surface-feature-flags.ts +65 -0
  359. package/src/runtime/terminal-output-guard.ts +228 -0
  360. package/src/runtime/ui/index.ts +39 -0
  361. package/src/runtime/ui/model-picker/data-provider.ts +182 -0
  362. package/src/runtime/ui/model-picker/health-enrichment.ts +228 -0
  363. package/src/runtime/ui/model-picker/index.ts +59 -0
  364. package/src/runtime/ui/model-picker/types.ts +149 -0
  365. package/src/runtime/ui/provider-health/data-provider.ts +244 -0
  366. package/src/runtime/ui/provider-health/fallback-visualizer.ts +71 -0
  367. package/src/runtime/ui/provider-health/index.ts +46 -0
  368. package/src/runtime/ui/provider-health/types.ts +146 -0
  369. package/src/runtime/ui-events.ts +1 -0
  370. package/src/runtime/ui-read-model-helpers.ts +1 -0
  371. package/src/runtime/ui-read-models-observability-maintenance.ts +1 -0
  372. package/src/runtime/ui-read-models-observability-options.ts +1 -0
  373. package/src/runtime/ui-read-models-observability-remote.ts +1 -0
  374. package/src/runtime/ui-read-models-observability-security.ts +1 -0
  375. package/src/runtime/ui-read-models-observability-system.ts +1 -0
  376. package/src/runtime/ui-read-models-observability.ts +1 -0
  377. package/src/runtime/ui-read-models.ts +61 -0
  378. package/src/runtime/ui-service-queries.ts +1 -0
  379. package/src/runtime/ui-services.ts +190 -0
  380. package/src/scripts/process-messages.ts +42 -0
  381. package/src/shell/blocking-input.ts +98 -0
  382. package/src/shell/service-settings-sync.ts +273 -0
  383. package/src/shell/ui-openers.ts +352 -0
  384. package/src/tools/index.ts +1 -0
  385. package/src/tools/wrfc-agent-guard.ts +49 -0
  386. package/src/types/grid.ts +48 -0
  387. package/src/types/sql-js.d.ts +15 -0
  388. package/src/utils/clipboard.ts +22 -0
  389. package/src/utils/splash-lines.ts +46 -0
  390. package/src/utils/terminal-width.ts +185 -0
  391. package/src/verification/live-verifier.ts +430 -0
  392. package/src/verification/verification-ledger.ts +242 -0
  393. package/src/version.ts +17 -0
  394. package/src/widget/index.ts +2 -0
  395. package/src/widget/types.ts +9 -0
  396. package/src/widget/widget.ts +8 -0
  397. package/src/work-plans/work-plan-store.ts +374 -0
  398. package/tsconfig.json +18 -0
@@ -0,0 +1,145 @@
1
+ /**
2
+ * RuntimeState — the canonical top-level state shape for the goodvibes-agent
3
+ * runtime store. All domain slices are defined here.
4
+ *
5
+ * Each domain includes revision, lastUpdatedAt, and source metadata fields.
6
+ * These are defined per-domain in the domain files.
7
+ */
8
+
9
+ import type { SessionDomainState } from '@/runtime/index.ts';
10
+ import type { ModelDomainState } from '@/runtime/index.ts';
11
+ import type { ConversationDomainState } from '@/runtime/index.ts';
12
+ import type { OverlayDomainState } from '@/runtime/index.ts';
13
+ import type { PanelDomainState } from './domains/panels.ts';
14
+ import type { PermissionDomainState } from '@/runtime/index.ts';
15
+ import type { TaskDomainState } from '@/runtime/index.ts';
16
+ import type { AgentDomainState } from '@/runtime/index.ts';
17
+ import type { OrchestrationDomainState } from '@/runtime/index.ts';
18
+ import type { CommunicationDomainState } from '@/runtime/index.ts';
19
+ import type { ProviderHealthDomainState } from '@/runtime/index.ts';
20
+ import type { McpDomainState } from '@/runtime/index.ts';
21
+ import type { PluginDomainState } from '@/runtime/index.ts';
22
+ import type { DaemonDomainState } from '@/runtime/index.ts';
23
+ import type { AutomationDomainState } from '@/runtime/index.ts';
24
+ import type { AcpDomainState } from '@/runtime/index.ts';
25
+ import type { RoutesDomainState } from '@/runtime/index.ts';
26
+ import type { ControlPlaneDomainState } from '@/runtime/index.ts';
27
+ import type { DeliveryDomainState } from '@/runtime/index.ts';
28
+ import type { WatcherDomainState } from '@/runtime/index.ts';
29
+ import type { SurfaceDomainState } from '@/runtime/index.ts';
30
+ import type { IntegrationDomainState } from '@/runtime/index.ts';
31
+ import type { TelemetryDomainState } from '@/runtime/index.ts';
32
+ import type { GitDomainState } from '@/runtime/index.ts';
33
+ import type { DiscoveryDomainState } from '@/runtime/index.ts';
34
+ import type { IntelligenceDomainState } from '@/runtime/index.ts';
35
+ import type { UiPerfDomainState } from './domains/ui-perf.ts';
36
+ // UiPerfDomainState is structurally identical to SDK's SurfacePerfDomainState.
37
+ // Export as SurfacePerfDomainState alias for SDK compatibility.
38
+ export type { UiPerfDomainState };
39
+
40
+ import { createInitialSessionState } from '@/runtime/index.ts';
41
+ import { createInitialModelState } from '@/runtime/index.ts';
42
+ import { createInitialConversationState } from '@/runtime/index.ts';
43
+ import { createInitialOverlaysState } from '@/runtime/index.ts';
44
+ import { createInitialPanelsState } from './domains/panels.ts';
45
+ import { createInitialPermissionsState } from '@/runtime/index.ts';
46
+ import { createInitialTasksState } from '@/runtime/index.ts';
47
+ import { createInitialAgentsState } from '@/runtime/index.ts';
48
+ import { createInitialOrchestrationState } from '@/runtime/index.ts';
49
+ import { createInitialCommunicationState } from '@/runtime/index.ts';
50
+ import { createInitialProviderHealthState } from '@/runtime/index.ts';
51
+ import { createInitialMcpState } from '@/runtime/index.ts';
52
+ import { createInitialPluginsState } from '@/runtime/index.ts';
53
+ import { createInitialDaemonState } from '@/runtime/index.ts';
54
+ import { createInitialAutomationState } from '@/runtime/index.ts';
55
+ import { createInitialAcpState } from '@/runtime/index.ts';
56
+ import { createInitialRoutesState } from '@/runtime/index.ts';
57
+ import { createInitialControlPlaneState } from '@/runtime/index.ts';
58
+ import { createInitialDeliveryState } from '@/runtime/index.ts';
59
+ import { createInitialWatcherState } from '@/runtime/index.ts';
60
+ import { createInitialSurfaceState } from '@/runtime/index.ts';
61
+ import { createInitialIntegrationsState } from '@/runtime/index.ts';
62
+ import { createInitialTelemetryState } from '@/runtime/index.ts';
63
+ import { createInitialGitState } from '@/runtime/index.ts';
64
+ import { createInitialDiscoveryState } from '@/runtime/index.ts';
65
+ import { createInitialIntelligenceState } from '@/runtime/index.ts';
66
+ import { createInitialUiPerfState } from './domains/ui-perf.ts';
67
+
68
+ /**
69
+ * RuntimeState — the complete state shape managed by the runtime store.
70
+ *
71
+ * Domain slices, each with revision/lastUpdatedAt/source metadata.
72
+ * All mutations must go through typed domain dispatch APIs.
73
+ */
74
+ export interface RuntimeState {
75
+ session: SessionDomainState;
76
+ model: ModelDomainState;
77
+ conversation: ConversationDomainState;
78
+ overlays: OverlayDomainState;
79
+ /**
80
+ * TUI panel state. Typed as Record<string,unknown> for SDK RuntimeState
81
+ * compatibility. Use selectPanels() which casts to PanelDomainState.
82
+ */
83
+ panels: Record<string, unknown>;
84
+ permissions: PermissionDomainState;
85
+ tasks: TaskDomainState;
86
+ agents: AgentDomainState;
87
+ orchestration: OrchestrationDomainState;
88
+ communication: CommunicationDomainState;
89
+ providerHealth: ProviderHealthDomainState;
90
+ mcp: McpDomainState;
91
+ plugins: PluginDomainState;
92
+ daemon: DaemonDomainState;
93
+ automation: AutomationDomainState;
94
+ routes: RoutesDomainState;
95
+ controlPlane: ControlPlaneDomainState;
96
+ deliveries: DeliveryDomainState;
97
+ watchers: WatcherDomainState;
98
+ surfaces: SurfaceDomainState;
99
+ acp: AcpDomainState;
100
+ integrations: IntegrationDomainState;
101
+ telemetry: TelemetryDomainState;
102
+ git: GitDomainState;
103
+ discovery: DiscoveryDomainState;
104
+ intelligence: IntelligenceDomainState;
105
+ /** Surface/UI performance metrics. SDK-compatible field name. */
106
+ surfacePerf: UiPerfDomainState;
107
+ }
108
+
109
+ /**
110
+ * Creates and returns a fully initialized RuntimeState with all domains
111
+ * set to their default initial values.
112
+ *
113
+ * This is the factory used by `createRuntimeStore()` and test harnesses.
114
+ */
115
+ export function createInitialRuntimeState(): RuntimeState {
116
+ return {
117
+ session: createInitialSessionState(),
118
+ model: createInitialModelState(),
119
+ conversation: createInitialConversationState(),
120
+ overlays: createInitialOverlaysState(),
121
+ panels: createInitialPanelsState() as unknown as Record<string, unknown>,
122
+ permissions: createInitialPermissionsState(),
123
+ tasks: createInitialTasksState(),
124
+ agents: createInitialAgentsState(),
125
+ orchestration: createInitialOrchestrationState(),
126
+ communication: createInitialCommunicationState(),
127
+ providerHealth: createInitialProviderHealthState(),
128
+ mcp: createInitialMcpState(),
129
+ plugins: createInitialPluginsState(),
130
+ daemon: createInitialDaemonState(),
131
+ automation: createInitialAutomationState(),
132
+ routes: createInitialRoutesState(),
133
+ controlPlane: createInitialControlPlaneState(),
134
+ deliveries: createInitialDeliveryState(),
135
+ watchers: createInitialWatcherState(),
136
+ surfaces: createInitialSurfaceState(),
137
+ acp: createInitialAcpState(),
138
+ integrations: createInitialIntegrationsState(),
139
+ telemetry: createInitialTelemetryState(),
140
+ git: createInitialGitState(),
141
+ discovery: createInitialDiscoveryState(),
142
+ intelligence: createInitialIntelligenceState(),
143
+ surfacePerf: createInitialUiPerfState(),
144
+ };
145
+ }
@@ -0,0 +1,65 @@
1
+ import type { ConfigKey, ConfigManager, PersistedFlagState } from '../config/index.ts';
2
+ import { surfaceFeatureGateId } from '@/runtime/index.ts';
3
+
4
+ export const CONTROL_PLANE_FEATURE_FLAG = 'control-plane-gateway';
5
+ export const ROUTE_BINDING_FEATURE_FLAG = 'route-binding';
6
+ export const DELIVERY_ENGINE_FEATURE_FLAG = 'delivery-engine';
7
+ export const SERVICE_MANAGEMENT_FEATURE_FLAG = 'service-management';
8
+
9
+ const CORE_CHANNEL_FEATURE_FLAGS = [
10
+ CONTROL_PLANE_FEATURE_FLAG,
11
+ ROUTE_BINDING_FEATURE_FLAG,
12
+ DELIVERY_ENGINE_FEATURE_FLAG,
13
+ ] as const;
14
+
15
+ export type FeatureFlagConfigKey = 'featureFlags' | `featureFlags.${string}`;
16
+
17
+ export function getSurfaceFeatureFlag(surfaceId: string): string | null {
18
+ return surfaceFeatureGateId(surfaceId);
19
+ }
20
+
21
+ export function getServerSurfaceFeatureFlags(options: {
22
+ readonly serverBacked?: boolean;
23
+ readonly web?: boolean;
24
+ readonly externalSurfaces?: readonly string[];
25
+ }): readonly string[] {
26
+ const flags = new Set<string>();
27
+ const hasExternalSurfaces = (options.externalSurfaces?.length ?? 0) > 0;
28
+
29
+ if (options.serverBacked || options.web || hasExternalSurfaces) {
30
+ flags.add(CONTROL_PLANE_FEATURE_FLAG);
31
+ flags.add(SERVICE_MANAGEMENT_FEATURE_FLAG);
32
+ }
33
+ if (options.web) {
34
+ const webFlag = getSurfaceFeatureFlag('web');
35
+ if (webFlag) flags.add(webFlag);
36
+ }
37
+
38
+ if (hasExternalSurfaces) {
39
+ for (const flag of CORE_CHANNEL_FEATURE_FLAGS) flags.add(flag);
40
+ for (const surfaceId of options.externalSurfaces ?? []) {
41
+ const surfaceFlag = getSurfaceFeatureFlag(surfaceId);
42
+ if (surfaceFlag) flags.add(surfaceFlag);
43
+ }
44
+ }
45
+
46
+ return [...flags].sort((left, right) => left.localeCompare(right));
47
+ }
48
+
49
+ export function isFeatureFlagEnabled(config: Pick<ConfigManager, 'getCategory'>, flagId: string): boolean {
50
+ const flags = (config.getCategory('featureFlags') as Record<string, PersistedFlagState | undefined>) ?? {};
51
+ return flags[flagId] === 'enabled';
52
+ }
53
+
54
+ export function getMissingSurfaceFeatureFlags(config: Pick<ConfigManager, 'getCategory'>, surfaceId: string): readonly string[] {
55
+ const required = surfaceId === 'web'
56
+ ? getServerSurfaceFeatureFlags({ web: true })
57
+ : getServerSurfaceFeatureFlags({ externalSurfaces: [surfaceId] });
58
+ return required.filter((flagId) => !isFeatureFlagEnabled(config, flagId));
59
+ }
60
+
61
+ export function enableFeatureFlags(config: Pick<ConfigManager, 'setDynamic'>, flagIds: readonly string[]): void {
62
+ for (const flagId of flagIds) {
63
+ config.setDynamic(`featureFlags.${flagId}` as ConfigKey, 'enabled');
64
+ }
65
+ }
@@ -0,0 +1,228 @@
1
+ import { format } from 'node:util';
2
+ import { logger } from '@pellux/goodvibes-sdk/platform/utils';
3
+
4
+ type WritableStreamLike = {
5
+ write: {
6
+ (buffer: string | Uint8Array, cb?: (error?: Error | null) => void): boolean;
7
+ (buffer: string | Uint8Array, encoding?: BufferEncoding, cb?: (error?: Error | null) => void): boolean;
8
+ };
9
+ };
10
+
11
+ export type TerminalOutputInterceptSource =
12
+ | 'stdout'
13
+ | 'stderr'
14
+ | 'console.debug'
15
+ | 'console.error'
16
+ | 'console.info'
17
+ | 'console.log'
18
+ | 'console.warn';
19
+
20
+ export type TerminalOutputIntercept = {
21
+ readonly source: TerminalOutputInterceptSource;
22
+ readonly text: string;
23
+ readonly preview: string;
24
+ };
25
+
26
+ export type TerminalOutputGuard = {
27
+ setActive(active: boolean): void;
28
+ allowTerminalWrite<T>(fn: () => T): T;
29
+ dispose(): void;
30
+ };
31
+
32
+ export type TerminalOutputGuardOptions = {
33
+ readonly stdout: WritableStreamLike;
34
+ readonly stderr?: WritableStreamLike;
35
+ readonly active?: boolean;
36
+ readonly onIntercept?: (event: TerminalOutputIntercept) => void;
37
+ };
38
+
39
+ export type TuiTerminalOutputGuardOptions = {
40
+ readonly stdout: WritableStreamLike;
41
+ readonly stderr?: WritableStreamLike;
42
+ readonly active?: boolean;
43
+ readonly notify: (message: string) => void;
44
+ };
45
+
46
+ const MAX_LOG_TEXT = 4_000;
47
+ const MAX_PREVIEW_TEXT = 180;
48
+ const ANSI_RE = /\x1b\[[0-?]*[ -/]*[@-~]/g;
49
+
50
+ let currentGuard: TerminalOutputGuard | null = null;
51
+
52
+ function writeCallback(args: unknown[]): ((error?: Error | null) => void) | undefined {
53
+ const maybeCallback = args[args.length - 1];
54
+ return typeof maybeCallback === 'function'
55
+ ? maybeCallback as (error?: Error | null) => void
56
+ : undefined;
57
+ }
58
+
59
+ function chunkToText(chunk: unknown): string {
60
+ if (typeof chunk === 'string') return chunk;
61
+ if (chunk instanceof Uint8Array) return Buffer.from(chunk).toString('utf8');
62
+ return String(chunk);
63
+ }
64
+
65
+ function normalizeText(text: string): string {
66
+ return text.replace(ANSI_RE, '').replace(/\r/g, '').trim();
67
+ }
68
+
69
+ function previewText(text: string): string {
70
+ const singleLine = normalizeText(text).replace(/\s+/g, ' ');
71
+ if (singleLine.length <= MAX_PREVIEW_TEXT) return singleLine;
72
+ return `${singleLine.slice(0, MAX_PREVIEW_TEXT - 1)}...`;
73
+ }
74
+
75
+ function truncateForLog(text: string): string {
76
+ if (text.length <= MAX_LOG_TEXT) return text;
77
+ return `${text.slice(0, MAX_LOG_TEXT)}\n[truncated ${text.length - MAX_LOG_TEXT} byte(s)]`;
78
+ }
79
+
80
+ function invokeSuppressedCallback(args: unknown[]): void {
81
+ const callback = writeCallback(args);
82
+ if (callback) {
83
+ queueMicrotask(() => callback(null));
84
+ }
85
+ }
86
+
87
+ export function allowTerminalWrite<T>(fn: () => T): T {
88
+ return currentGuard ? currentGuard.allowTerminalWrite(fn) : fn();
89
+ }
90
+
91
+ export function installTerminalOutputGuard(options: TerminalOutputGuardOptions): TerminalOutputGuard {
92
+ const stdout = options.stdout;
93
+ const stderr = options.stderr ?? process.stderr;
94
+ const originalStdoutWriteMethod = stdout.write;
95
+ const originalStderrWriteMethod = stderr.write;
96
+ const originalStdoutWrite = (...args: unknown[]): boolean =>
97
+ Reflect.apply(originalStdoutWriteMethod, stdout, args) as boolean;
98
+ const originalStderrWrite = (...args: unknown[]): boolean =>
99
+ Reflect.apply(originalStderrWriteMethod, stderr, args) as boolean;
100
+ const originalConsole = {
101
+ debug: console.debug.bind(console),
102
+ error: console.error.bind(console),
103
+ info: console.info.bind(console),
104
+ log: console.log.bind(console),
105
+ warn: console.warn.bind(console),
106
+ };
107
+
108
+ let active = options.active ?? true;
109
+ let disposed = false;
110
+ let allowDepth = 0;
111
+ let captureDepth = 0;
112
+
113
+ const record = (source: TerminalOutputInterceptSource, text: string): void => {
114
+ if (disposed || !active) return;
115
+ const normalized = normalizeText(text);
116
+ if (!normalized) return;
117
+ if (normalized.startsWith('[ActivityLogger]')) return;
118
+ if (captureDepth > 0) return;
119
+
120
+ captureDepth++;
121
+ try {
122
+ const event: TerminalOutputIntercept = {
123
+ source,
124
+ text: truncateForLog(normalized),
125
+ preview: previewText(normalized),
126
+ };
127
+ logger.warn('Intercepted terminal output while TUI renderer was active', {
128
+ source: event.source,
129
+ text: event.text,
130
+ });
131
+ options.onIntercept?.(event);
132
+ } finally {
133
+ captureDepth--;
134
+ }
135
+ };
136
+
137
+ const shouldPassThrough = (): boolean => !active || allowDepth > 0 || disposed;
138
+
139
+ stdout.write = ((...args: unknown[]) => {
140
+ if (shouldPassThrough()) {
141
+ return originalStdoutWrite(...args);
142
+ }
143
+ record('stdout', chunkToText(args[0]));
144
+ invokeSuppressedCallback(args);
145
+ return true;
146
+ }) as WritableStreamLike['write'];
147
+
148
+ stderr.write = ((...args: unknown[]) => {
149
+ if (shouldPassThrough()) {
150
+ return originalStderrWrite(...args);
151
+ }
152
+ record('stderr', chunkToText(args[0]));
153
+ invokeSuppressedCallback(args);
154
+ return true;
155
+ }) as WritableStreamLike['write'];
156
+
157
+ console.debug = (...args: unknown[]) => {
158
+ if (!active || disposed) return originalConsole.debug(...args);
159
+ record('console.debug', format(...args));
160
+ };
161
+ console.error = (...args: unknown[]) => {
162
+ if (!active || disposed) return originalConsole.error(...args);
163
+ record('console.error', format(...args));
164
+ };
165
+ console.info = (...args: unknown[]) => {
166
+ if (!active || disposed) return originalConsole.info(...args);
167
+ record('console.info', format(...args));
168
+ };
169
+ console.log = (...args: unknown[]) => {
170
+ if (!active || disposed) return originalConsole.log(...args);
171
+ record('console.log', format(...args));
172
+ };
173
+ console.warn = (...args: unknown[]) => {
174
+ if (!active || disposed) return originalConsole.warn(...args);
175
+ record('console.warn', format(...args));
176
+ };
177
+
178
+ const guard: TerminalOutputGuard = {
179
+ setActive(nextActive) {
180
+ active = nextActive;
181
+ },
182
+ allowTerminalWrite<T>(fn: () => T): T {
183
+ allowDepth++;
184
+ try {
185
+ return fn();
186
+ } finally {
187
+ allowDepth--;
188
+ }
189
+ },
190
+ dispose() {
191
+ if (disposed) return;
192
+ disposed = true;
193
+ stdout.write = originalStdoutWriteMethod;
194
+ stderr.write = originalStderrWriteMethod;
195
+ console.debug = originalConsole.debug;
196
+ console.error = originalConsole.error;
197
+ console.info = originalConsole.info;
198
+ console.log = originalConsole.log;
199
+ console.warn = originalConsole.warn;
200
+ if (currentGuard === guard) {
201
+ currentGuard = null;
202
+ }
203
+ },
204
+ };
205
+
206
+ currentGuard = guard;
207
+ return guard;
208
+ }
209
+
210
+ export function installTuiTerminalOutputGuard(options: TuiTerminalOutputGuardOptions): TerminalOutputGuard {
211
+ let capturedWriteCount = 0;
212
+ let lastNoticeAt = 0;
213
+ return installTerminalOutputGuard({
214
+ stdout: options.stdout,
215
+ stderr: options.stderr,
216
+ active: options.active,
217
+ onIntercept: (event) => {
218
+ capturedWriteCount++;
219
+ const now = Date.now();
220
+ if (now - lastNoticeAt < 5_000) return;
221
+ const count = capturedWriteCount;
222
+ capturedWriteCount = 0;
223
+ lastNoticeAt = now;
224
+ const plural = count === 1 ? '' : 's';
225
+ options.notify(`[Terminal] Captured ${count} direct ${event.source} write${plural} that would have corrupted the TUI: ${event.preview}`);
226
+ },
227
+ });
228
+ }
@@ -0,0 +1,39 @@
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';
@@ -0,0 +1,182 @@
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
+ }