@pellux/goodvibes-tui 0.18.4

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 (732) hide show
  1. package/.goodvibes/GOODVIBES.md +35 -0
  2. package/.goodvibes/agents/reviewer.md +89 -0
  3. package/.goodvibes/skills/add-provider/SKILL.md +199 -0
  4. package/CHANGELOG.md +1681 -0
  5. package/README.md +1607 -0
  6. package/bin/goodvibes +66 -0
  7. package/docs/README.md +32 -0
  8. package/docs/foundation-artifacts/README.md +16 -0
  9. package/docs/foundation-artifacts/knowledge-graphql.graphql +397 -0
  10. package/docs/foundation-artifacts/knowledge-store.sql +183 -0
  11. package/docs/foundation-artifacts/operator-contract.json +55157 -0
  12. package/docs/foundation-artifacts/peer-contract.json +2384 -0
  13. package/package.json +114 -0
  14. package/scripts/postinstall.mjs +203 -0
  15. package/src/acp/connection.ts +447 -0
  16. package/src/acp/index.ts +7 -0
  17. package/src/acp/manager.ts +133 -0
  18. package/src/adapters/bluebubbles/index.ts +127 -0
  19. package/src/adapters/discord/index.ts +297 -0
  20. package/src/adapters/github/index.ts +73 -0
  21. package/src/adapters/google-chat/index.ts +119 -0
  22. package/src/adapters/imessage/index.ts +92 -0
  23. package/src/adapters/index.ts +15 -0
  24. package/src/adapters/matrix/index.ts +116 -0
  25. package/src/adapters/mattermost/index.ts +151 -0
  26. package/src/adapters/msteams/index.ts +180 -0
  27. package/src/adapters/ntfy/index.ts +118 -0
  28. package/src/adapters/signal/index.ts +92 -0
  29. package/src/adapters/slack/index.ts +323 -0
  30. package/src/adapters/telegram/index.ts +160 -0
  31. package/src/adapters/types.ts +97 -0
  32. package/src/adapters/webhook/index.ts +178 -0
  33. package/src/adapters/whatsapp/index.ts +135 -0
  34. package/src/agents/message-bus-core.ts +312 -0
  35. package/src/agents/message-bus.ts +2 -0
  36. package/src/agents/orchestrator-prompts.ts +351 -0
  37. package/src/agents/orchestrator-runner.ts +668 -0
  38. package/src/agents/orchestrator.ts +437 -0
  39. package/src/agents/session.ts +108 -0
  40. package/src/agents/worktree.ts +153 -0
  41. package/src/agents/wrfc-config.ts +47 -0
  42. package/src/agents/wrfc-controller.ts +747 -0
  43. package/src/agents/wrfc-gate-runtime.ts +75 -0
  44. package/src/agents/wrfc-reporting.ts +284 -0
  45. package/src/agents/wrfc-runtime-events.ts +150 -0
  46. package/src/agents/wrfc-types.ts +67 -0
  47. package/src/automation/delivery-manager.ts +368 -0
  48. package/src/automation/index.ts +72 -0
  49. package/src/automation/manager-runtime-delivery.ts +139 -0
  50. package/src/automation/manager-runtime-events.ts +131 -0
  51. package/src/automation/manager-runtime-execution.ts +511 -0
  52. package/src/automation/manager-runtime-helpers.ts +433 -0
  53. package/src/automation/manager-runtime-job-mutations.ts +175 -0
  54. package/src/automation/manager-runtime-reconcile.ts +148 -0
  55. package/src/automation/manager-runtime-scheduling.ts +189 -0
  56. package/src/automation/manager-runtime-sync.ts +54 -0
  57. package/src/automation/manager-runtime.ts +721 -0
  58. package/src/automation/manager.ts +10 -0
  59. package/src/automation/service.ts +242 -0
  60. package/src/channels/builtin/account-actions.ts +490 -0
  61. package/src/channels/builtin/accounts.ts +433 -0
  62. package/src/channels/builtin/contracts.ts +405 -0
  63. package/src/channels/builtin/plugins.ts +308 -0
  64. package/src/channels/builtin/rendering.ts +174 -0
  65. package/src/channels/builtin/setup-schema.ts +504 -0
  66. package/src/channels/builtin/shared.ts +96 -0
  67. package/src/channels/builtin/surfaces.ts +57 -0
  68. package/src/channels/builtin/targets.ts +693 -0
  69. package/src/channels/builtin-runtime.ts +443 -0
  70. package/src/channels/delivery/shared.ts +199 -0
  71. package/src/channels/delivery/strategies-bridge.ts +246 -0
  72. package/src/channels/delivery/strategies-core.ts +299 -0
  73. package/src/channels/delivery/strategies-enterprise.ts +178 -0
  74. package/src/channels/delivery/types.ts +59 -0
  75. package/src/channels/delivery-router.ts +127 -0
  76. package/src/channels/index.ts +77 -0
  77. package/src/channels/plugin-registry.ts +551 -0
  78. package/src/channels/provider-runtime.ts +330 -0
  79. package/src/channels/reply-pipeline.ts +522 -0
  80. package/src/channels/route-manager.ts +340 -0
  81. package/src/channels/surface-registry.ts +186 -0
  82. package/src/config/helper-model.ts +233 -0
  83. package/src/config/index.ts +193 -0
  84. package/src/config/manager.ts +404 -0
  85. package/src/config/secrets.ts +547 -0
  86. package/src/config/service-registry.ts +329 -0
  87. package/src/config/subscription-auth.ts +31 -0
  88. package/src/config/subscription-providers.ts +127 -0
  89. package/src/config/tool-llm.ts +110 -0
  90. package/src/control-plane/approval-broker.ts +351 -0
  91. package/src/control-plane/gateway.ts +713 -0
  92. package/src/control-plane/index.ts +54 -0
  93. package/src/control-plane/media-contract-schemas.ts +208 -0
  94. package/src/control-plane/method-catalog-admin.ts +136 -0
  95. package/src/control-plane/method-catalog-channels.ts +591 -0
  96. package/src/control-plane/method-catalog-control-automation.ts +475 -0
  97. package/src/control-plane/method-catalog-control-core.ts +594 -0
  98. package/src/control-plane/method-catalog-control.ts +8 -0
  99. package/src/control-plane/method-catalog-events.ts +74 -0
  100. package/src/control-plane/method-catalog-knowledge.ts +531 -0
  101. package/src/control-plane/method-catalog-media.ts +279 -0
  102. package/src/control-plane/method-catalog-runtime.ts +304 -0
  103. package/src/control-plane/method-catalog-shared.ts +223 -0
  104. package/src/control-plane/method-catalog.ts +242 -0
  105. package/src/control-plane/operator-contract-schemas-admin.ts +639 -0
  106. package/src/control-plane/operator-contract-schemas-channels.ts +375 -0
  107. package/src/control-plane/operator-contract-schemas-control.ts +226 -0
  108. package/src/control-plane/operator-contract-schemas-domains.ts +4 -0
  109. package/src/control-plane/operator-contract-schemas-knowledge.ts +582 -0
  110. package/src/control-plane/operator-contract-schemas-media.ts +297 -0
  111. package/src/control-plane/operator-contract-schemas-permissions.ts +100 -0
  112. package/src/control-plane/operator-contract-schemas-remote.ts +38 -0
  113. package/src/control-plane/operator-contract-schemas-runtime.ts +563 -0
  114. package/src/control-plane/operator-contract-schemas-shared.ts +85 -0
  115. package/src/control-plane/operator-contract-schemas-telemetry.ts +349 -0
  116. package/src/control-plane/operator-contract-schemas.ts +6 -0
  117. package/src/control-plane/operator-contract.ts +163 -0
  118. package/src/control-plane/session-broker.ts +780 -0
  119. package/src/core/compaction-sections.ts +492 -0
  120. package/src/core/compaction-types.ts +147 -0
  121. package/src/core/composer-state.ts +59 -0
  122. package/src/core/context-compaction.ts +542 -0
  123. package/src/core/conversation-compaction.ts +68 -0
  124. package/src/core/conversation-diff.ts +55 -0
  125. package/src/core/conversation-rendering.ts +343 -0
  126. package/src/core/conversation-utils.ts +72 -0
  127. package/src/core/conversation.ts +775 -0
  128. package/src/core/event-replay.ts +287 -0
  129. package/src/core/orchestrator-context-runtime.ts +407 -0
  130. package/src/core/orchestrator-follow-up-runtime.ts +134 -0
  131. package/src/core/orchestrator-runtime.ts +132 -0
  132. package/src/core/orchestrator-tool-runtime.ts +468 -0
  133. package/src/core/orchestrator-turn-helpers.ts +355 -0
  134. package/src/core/orchestrator-turn-loop.ts +443 -0
  135. package/src/core/orchestrator.ts +733 -0
  136. package/src/core/plan-command-handler.ts +169 -0
  137. package/src/core/system-message-router.ts +210 -0
  138. package/src/core/transcript-events/classify.ts +95 -0
  139. package/src/core/transcript-events/index.ts +15 -0
  140. package/src/daemon/cli.ts +88 -0
  141. package/src/daemon/control-plane.ts +522 -0
  142. package/src/daemon/facade-composition.ts +397 -0
  143. package/src/daemon/facade.ts +638 -0
  144. package/src/daemon/helpers.ts +74 -0
  145. package/src/daemon/http/router-route-contexts.ts +370 -0
  146. package/src/daemon/http/router.ts +531 -0
  147. package/src/daemon/http-listener.ts +301 -0
  148. package/src/daemon/index.ts +3 -0
  149. package/src/daemon/server.ts +1 -0
  150. package/src/daemon/service-manager.ts +413 -0
  151. package/src/daemon/surface-actions.ts +183 -0
  152. package/src/daemon/surface-delivery.ts +530 -0
  153. package/src/daemon/surface-policy.ts +60 -0
  154. package/src/daemon/transport-events.ts +110 -0
  155. package/src/daemon/types.ts +191 -0
  156. package/src/export/markdown.ts +213 -0
  157. package/src/export/session-export.ts +633 -0
  158. package/src/git/index.ts +1 -0
  159. package/src/git/service.ts +414 -0
  160. package/src/hooks/chain-engine.ts +414 -0
  161. package/src/hooks/dispatcher.ts +414 -0
  162. package/src/hooks/hook-api.ts +170 -0
  163. package/src/hooks/index.ts +48 -0
  164. package/src/hooks/runners/agent.ts +93 -0
  165. package/src/hooks/runners/prompt.ts +69 -0
  166. package/src/hooks/workbench.ts +360 -0
  167. package/src/input/autocomplete.ts +96 -0
  168. package/src/input/bookmark-modal.ts +115 -0
  169. package/src/input/command-registry.ts +300 -0
  170. package/src/input/commands/branch-runtime.ts +72 -0
  171. package/src/input/commands/config.ts +515 -0
  172. package/src/input/commands/control-room-runtime.ts +255 -0
  173. package/src/input/commands/conversation-runtime.ts +207 -0
  174. package/src/input/commands/diff-runtime.ts +161 -0
  175. package/src/input/commands/discovery-runtime.ts +45 -0
  176. package/src/input/commands/eval.ts +204 -0
  177. package/src/input/commands/experience-runtime.ts +278 -0
  178. package/src/input/commands/git-runtime.ts +81 -0
  179. package/src/input/commands/guidance-runtime.ts +101 -0
  180. package/src/input/commands/health-runtime.ts +434 -0
  181. package/src/input/commands/hooks-runtime.ts +148 -0
  182. package/src/input/commands/incident-runtime.ts +95 -0
  183. package/src/input/commands/integration-runtime.ts +394 -0
  184. package/src/input/commands/intelligence-runtime.ts +223 -0
  185. package/src/input/commands/knowledge.ts +368 -0
  186. package/src/input/commands/local-auth-runtime.ts +105 -0
  187. package/src/input/commands/local-provider-runtime.ts +170 -0
  188. package/src/input/commands/local-runtime.ts +458 -0
  189. package/src/input/commands/local-setup-review.ts +192 -0
  190. package/src/input/commands/local-setup-transfer.ts +134 -0
  191. package/src/input/commands/local-setup.ts +292 -0
  192. package/src/input/commands/managed-runtime.ts +208 -0
  193. package/src/input/commands/marketplace-runtime.ts +290 -0
  194. package/src/input/commands/mcp-runtime.ts +202 -0
  195. package/src/input/commands/memory-product-runtime.ts +111 -0
  196. package/src/input/commands/memory.ts +151 -0
  197. package/src/input/commands/notify-runtime.ts +83 -0
  198. package/src/input/commands/operator-panel-runtime.ts +141 -0
  199. package/src/input/commands/operator-runtime.ts +392 -0
  200. package/src/input/commands/permissions-runtime.ts +104 -0
  201. package/src/input/commands/planning-runtime.ts +97 -0
  202. package/src/input/commands/platform-access-runtime.ts +422 -0
  203. package/src/input/commands/platform-runtime.ts +6 -0
  204. package/src/input/commands/platform-sandbox-qemu.ts +137 -0
  205. package/src/input/commands/platform-sandbox-runtime.ts +406 -0
  206. package/src/input/commands/platform-sandbox-session.ts +128 -0
  207. package/src/input/commands/platform-services-runtime.ts +246 -0
  208. package/src/input/commands/policy-dispatch.ts +339 -0
  209. package/src/input/commands/policy.ts +17 -0
  210. package/src/input/commands/product-runtime.ts +351 -0
  211. package/src/input/commands/profile-sync-runtime.ts +99 -0
  212. package/src/input/commands/provider-accounts-runtime.ts +113 -0
  213. package/src/input/commands/provider.ts +363 -0
  214. package/src/input/commands/quit-shared.ts +162 -0
  215. package/src/input/commands/recall-bundle.ts +132 -0
  216. package/src/input/commands/recall-capture.ts +152 -0
  217. package/src/input/commands/recall-query.ts +229 -0
  218. package/src/input/commands/recall-review.ts +98 -0
  219. package/src/input/commands/recall-shared.ts +22 -0
  220. package/src/input/commands/remote-runtime-pool.ts +106 -0
  221. package/src/input/commands/remote-runtime-setup.ts +199 -0
  222. package/src/input/commands/remote-runtime.ts +531 -0
  223. package/src/input/commands/replay-runtime.ts +18 -0
  224. package/src/input/commands/runtime-services.ts +279 -0
  225. package/src/input/commands/schedule-runtime.ts +332 -0
  226. package/src/input/commands/services-runtime.ts +207 -0
  227. package/src/input/commands/session-content.ts +408 -0
  228. package/src/input/commands/session-workflow.ts +464 -0
  229. package/src/input/commands/session.ts +376 -0
  230. package/src/input/commands/settings-sync-runtime.ts +173 -0
  231. package/src/input/commands/share-runtime.ts +114 -0
  232. package/src/input/commands/shell-core.ts +320 -0
  233. package/src/input/commands/skills-runtime.ts +221 -0
  234. package/src/input/commands/subscription-runtime.ts +434 -0
  235. package/src/input/commands/tasks-runtime.ts +230 -0
  236. package/src/input/commands/teamwork-runtime.ts +374 -0
  237. package/src/input/commands/teleport-runtime.ts +57 -0
  238. package/src/input/commands/worktree-runtime.ts +137 -0
  239. package/src/input/commands.ts +127 -0
  240. package/src/input/file-picker.ts +192 -0
  241. package/src/input/handler-command-route.ts +106 -0
  242. package/src/input/handler-content-actions.ts +465 -0
  243. package/src/input/handler-feed-routes.ts +541 -0
  244. package/src/input/handler-feed.ts +361 -0
  245. package/src/input/handler-modal-routes.ts +335 -0
  246. package/src/input/handler-modal-stack.ts +237 -0
  247. package/src/input/handler-modal-token-routes.ts +272 -0
  248. package/src/input/handler-picker-routes.ts +416 -0
  249. package/src/input/handler-prompt-buffer.ts +320 -0
  250. package/src/input/handler-shortcuts.ts +195 -0
  251. package/src/input/handler-ui-state.ts +294 -0
  252. package/src/input/handler.ts +798 -0
  253. package/src/input/input-history.ts +267 -0
  254. package/src/input/keybindings.ts +256 -0
  255. package/src/input/model-picker.ts +730 -0
  256. package/src/input/panel-integration-actions.ts +77 -0
  257. package/src/input/profile-picker-modal.ts +222 -0
  258. package/src/input/search.ts +100 -0
  259. package/src/input/selection-modal.ts +163 -0
  260. package/src/input/selection.ts +135 -0
  261. package/src/input/session-picker-modal.ts +136 -0
  262. package/src/input/settings-modal.ts +718 -0
  263. package/src/input/submission-intent.ts +18 -0
  264. package/src/input/submission-router.ts +64 -0
  265. package/src/integrations/index.ts +42 -0
  266. package/src/integrations/notifier.ts +206 -0
  267. package/src/integrations/webhooks.ts +177 -0
  268. package/src/knowledge/consolidation.ts +346 -0
  269. package/src/knowledge/graphql.ts +324 -0
  270. package/src/knowledge/index.ts +60 -0
  271. package/src/knowledge/ingest-compile.ts +386 -0
  272. package/src/knowledge/ingest-context.ts +18 -0
  273. package/src/knowledge/ingest-inputs.ts +387 -0
  274. package/src/knowledge/ingest.ts +20 -0
  275. package/src/knowledge/internal.ts +257 -0
  276. package/src/knowledge/knowledge-api.ts +432 -0
  277. package/src/knowledge/lint.ts +121 -0
  278. package/src/knowledge/memory-sync.ts +62 -0
  279. package/src/knowledge/packet.ts +370 -0
  280. package/src/knowledge/scheduling.ts +283 -0
  281. package/src/knowledge/service.ts +715 -0
  282. package/src/main.ts +798 -0
  283. package/src/mcp/client.ts +383 -0
  284. package/src/mcp/index.ts +12 -0
  285. package/src/mcp/mcp-api.ts +90 -0
  286. package/src/mcp/registry.ts +508 -0
  287. package/src/media/builtin-image-understanding.ts +303 -0
  288. package/src/media/builtin-providers.ts +26 -0
  289. package/src/media/index.ts +18 -0
  290. package/src/multimodal/index.ts +13 -0
  291. package/src/multimodal/service.ts +492 -0
  292. package/src/panels/agent-inspector-panel.ts +515 -0
  293. package/src/panels/agent-inspector-shared.ts +94 -0
  294. package/src/panels/agent-logs-panel.ts +539 -0
  295. package/src/panels/agent-logs-shared.ts +129 -0
  296. package/src/panels/approval-panel.ts +169 -0
  297. package/src/panels/automation-control-panel.ts +253 -0
  298. package/src/panels/base-panel.ts +72 -0
  299. package/src/panels/builtin/agent.ts +88 -0
  300. package/src/panels/builtin/development.ts +111 -0
  301. package/src/panels/builtin/knowledge.ts +26 -0
  302. package/src/panels/builtin/operations.ts +385 -0
  303. package/src/panels/builtin/session.ts +61 -0
  304. package/src/panels/builtin/shared.ts +240 -0
  305. package/src/panels/builtin-panels.ts +23 -0
  306. package/src/panels/cockpit-panel.ts +183 -0
  307. package/src/panels/communication-panel.ts +191 -0
  308. package/src/panels/context-visualizer-panel.ts +199 -0
  309. package/src/panels/control-plane-panel.ts +266 -0
  310. package/src/panels/cost-tracker-panel.ts +444 -0
  311. package/src/panels/debug-panel.ts +432 -0
  312. package/src/panels/diff-panel.ts +518 -0
  313. package/src/panels/docs-panel.ts +283 -0
  314. package/src/panels/eval-panel.ts +399 -0
  315. package/src/panels/file-explorer-panel.ts +556 -0
  316. package/src/panels/file-preview-panel.ts +412 -0
  317. package/src/panels/forensics-panel.ts +364 -0
  318. package/src/panels/git-panel.ts +630 -0
  319. package/src/panels/hooks-panel.ts +274 -0
  320. package/src/panels/incident-review-panel.ts +247 -0
  321. package/src/panels/index.ts +48 -0
  322. package/src/panels/intelligence-panel.ts +176 -0
  323. package/src/panels/knowledge-panel.ts +328 -0
  324. package/src/panels/local-auth-panel.ts +146 -0
  325. package/src/panels/marketplace-panel.ts +223 -0
  326. package/src/panels/mcp-panel.ts +260 -0
  327. package/src/panels/memory-panel.ts +293 -0
  328. package/src/panels/ops-control-panel.ts +184 -0
  329. package/src/panels/ops-strategy-panel.ts +235 -0
  330. package/src/panels/orchestration-panel.ts +254 -0
  331. package/src/panels/panel-list-panel.ts +508 -0
  332. package/src/panels/panel-manager.ts +538 -0
  333. package/src/panels/panel-picker.ts +106 -0
  334. package/src/panels/plan-dashboard-panel.ts +272 -0
  335. package/src/panels/plugins-panel.ts +201 -0
  336. package/src/panels/policy-panel.ts +308 -0
  337. package/src/panels/polish.ts +668 -0
  338. package/src/panels/provider-account-snapshot.ts +259 -0
  339. package/src/panels/provider-accounts-panel.ts +221 -0
  340. package/src/panels/provider-health-domains.ts +211 -0
  341. package/src/panels/provider-health-panel.ts +725 -0
  342. package/src/panels/provider-health-tracker.ts +115 -0
  343. package/src/panels/provider-stats-panel.ts +366 -0
  344. package/src/panels/remote-panel.ts +449 -0
  345. package/src/panels/routes-panel.ts +228 -0
  346. package/src/panels/sandbox-panel.ts +289 -0
  347. package/src/panels/schedule-panel.ts +344 -0
  348. package/src/panels/search-focus.ts +32 -0
  349. package/src/panels/security-panel.ts +329 -0
  350. package/src/panels/services-panel.ts +271 -0
  351. package/src/panels/session-browser-panel.ts +399 -0
  352. package/src/panels/session-maintenance.ts +125 -0
  353. package/src/panels/settings-sync-panel.ts +164 -0
  354. package/src/panels/skills-panel.ts +475 -0
  355. package/src/panels/subscription-panel.ts +273 -0
  356. package/src/panels/symbol-outline-panel.ts +486 -0
  357. package/src/panels/system-messages-panel.ts +224 -0
  358. package/src/panels/tasks-panel.ts +448 -0
  359. package/src/panels/thinking-panel.ts +304 -0
  360. package/src/panels/token-budget-panel.ts +469 -0
  361. package/src/panels/tool-inspector-panel.ts +434 -0
  362. package/src/panels/types.ts +44 -0
  363. package/src/panels/watchers-panel.ts +241 -0
  364. package/src/panels/welcome-panel.ts +64 -0
  365. package/src/panels/worktree-panel.ts +180 -0
  366. package/src/panels/wrfc-panel.ts +480 -0
  367. package/src/permissions/briefs/build.ts +88 -0
  368. package/src/permissions/manager.ts +356 -0
  369. package/src/permissions/prompt.ts +184 -0
  370. package/src/plugins/api.ts +383 -0
  371. package/src/plugins/loader.ts +304 -0
  372. package/src/plugins/manager.ts +481 -0
  373. package/src/profiles/shape.ts +58 -0
  374. package/src/providers/amazon-bedrock-mantle.ts +50 -0
  375. package/src/providers/amazon-bedrock.ts +61 -0
  376. package/src/providers/anthropic-compat.ts +373 -0
  377. package/src/providers/anthropic-sdk-provider.ts +230 -0
  378. package/src/providers/anthropic-vertex.ts +59 -0
  379. package/src/providers/anthropic.ts +469 -0
  380. package/src/providers/auto-register.ts +417 -0
  381. package/src/providers/builtin-catalog.ts +326 -0
  382. package/src/providers/builtin-registry.ts +575 -0
  383. package/src/providers/cache-planner.ts +258 -0
  384. package/src/providers/capabilities.ts +601 -0
  385. package/src/providers/custom-loader.ts +425 -0
  386. package/src/providers/discovered-compat.ts +18 -0
  387. package/src/providers/discovered-factory.ts +61 -0
  388. package/src/providers/discovered-traits.ts +138 -0
  389. package/src/providers/gemini.ts +462 -0
  390. package/src/providers/github-copilot.ts +254 -0
  391. package/src/providers/index.ts +47 -0
  392. package/src/providers/interface.ts +185 -0
  393. package/src/providers/llama-cpp.ts +402 -0
  394. package/src/providers/lm-studio-helpers.ts +367 -0
  395. package/src/providers/lm-studio.ts +484 -0
  396. package/src/providers/model-catalog-cache.ts +221 -0
  397. package/src/providers/model-catalog-notifications.ts +97 -0
  398. package/src/providers/model-catalog-synthetic.ts +202 -0
  399. package/src/providers/model-catalog.ts +211 -0
  400. package/src/providers/model-limits.ts +280 -0
  401. package/src/providers/ollama.ts +469 -0
  402. package/src/providers/openai-codex.ts +472 -0
  403. package/src/providers/openai-compat.ts +615 -0
  404. package/src/providers/openai.ts +231 -0
  405. package/src/providers/optimizer.ts +381 -0
  406. package/src/providers/provider-api.ts +553 -0
  407. package/src/providers/registry-helpers.ts +34 -0
  408. package/src/providers/registry-models.ts +77 -0
  409. package/src/providers/registry-types.ts +67 -0
  410. package/src/providers/registry.ts +729 -0
  411. package/src/providers/runtime-metadata.ts +149 -0
  412. package/src/providers/runtime-snapshot.ts +130 -0
  413. package/src/providers/synthetic.ts +561 -0
  414. package/src/providers/tier-prompts.ts +84 -0
  415. package/src/providers/tool-formats.ts +414 -0
  416. package/src/renderer/agent-detail-modal.ts +285 -0
  417. package/src/renderer/autocomplete-overlay.ts +154 -0
  418. package/src/renderer/block-actions.ts +76 -0
  419. package/src/renderer/bookmark-modal.ts +101 -0
  420. package/src/renderer/bottom-bar.ts +58 -0
  421. package/src/renderer/buffer.ts +34 -0
  422. package/src/renderer/code-block.ts +373 -0
  423. package/src/renderer/compositor.ts +261 -0
  424. package/src/renderer/context-inspector.ts +219 -0
  425. package/src/renderer/conversation-layout.ts +67 -0
  426. package/src/renderer/conversation-overlays.ts +123 -0
  427. package/src/renderer/conversation-surface.ts +260 -0
  428. package/src/renderer/diff-view.ts +132 -0
  429. package/src/renderer/diff.ts +122 -0
  430. package/src/renderer/file-picker-overlay.ts +101 -0
  431. package/src/renderer/file-tree.ts +153 -0
  432. package/src/renderer/git-status.ts +89 -0
  433. package/src/renderer/help-overlay.ts +247 -0
  434. package/src/renderer/history-search-overlay.ts +73 -0
  435. package/src/renderer/layout-engine.ts +97 -0
  436. package/src/renderer/layout.ts +32 -0
  437. package/src/renderer/live-tail-modal.ts +156 -0
  438. package/src/renderer/markdown.ts +777 -0
  439. package/src/renderer/modal-factory.ts +467 -0
  440. package/src/renderer/modal-utils.ts +24 -0
  441. package/src/renderer/model-picker-overlay.ts +396 -0
  442. package/src/renderer/overlay-box.ts +165 -0
  443. package/src/renderer/overlay-viewport.ts +104 -0
  444. package/src/renderer/panel-composite.ts +80 -0
  445. package/src/renderer/panel-picker-overlay.ts +202 -0
  446. package/src/renderer/panel-tab-bar.ts +69 -0
  447. package/src/renderer/panel-workspace-bar.ts +38 -0
  448. package/src/renderer/process-indicator.ts +96 -0
  449. package/src/renderer/process-modal.ts +295 -0
  450. package/src/renderer/profile-picker-modal.ts +129 -0
  451. package/src/renderer/progress.ts +98 -0
  452. package/src/renderer/search-overlay.ts +54 -0
  453. package/src/renderer/selection-modal-overlay.ts +214 -0
  454. package/src/renderer/semantic-diff.ts +369 -0
  455. package/src/renderer/session-picker-modal.ts +127 -0
  456. package/src/renderer/settings-modal.ts +701 -0
  457. package/src/renderer/shell-surface.ts +88 -0
  458. package/src/renderer/surface-layout.ts +101 -0
  459. package/src/renderer/syntax-highlighter.ts +542 -0
  460. package/src/renderer/system-message.ts +83 -0
  461. package/src/renderer/tab-strip.ts +108 -0
  462. package/src/renderer/text-layout.ts +31 -0
  463. package/src/renderer/thinking.ts +17 -0
  464. package/src/renderer/tool-call.ts +233 -0
  465. package/src/renderer/ui-factory.ts +524 -0
  466. package/src/renderer/ui-primitives.ts +96 -0
  467. package/src/runtime/auth/inspection.ts +125 -0
  468. package/src/runtime/bootstrap-background.ts +147 -0
  469. package/src/runtime/bootstrap-command-context.ts +265 -0
  470. package/src/runtime/bootstrap-command-parts.ts +357 -0
  471. package/src/runtime/bootstrap-core.ts +375 -0
  472. package/src/runtime/bootstrap-helpers.ts +88 -0
  473. package/src/runtime/bootstrap-hook-bridge.ts +271 -0
  474. package/src/runtime/bootstrap-runtime-events.ts +254 -0
  475. package/src/runtime/bootstrap-services.ts +197 -0
  476. package/src/runtime/bootstrap-shell.ts +262 -0
  477. package/src/runtime/bootstrap.ts +488 -0
  478. package/src/runtime/compaction/index.ts +90 -0
  479. package/src/runtime/compaction/lifecycle.ts +167 -0
  480. package/src/runtime/compaction/manager.ts +474 -0
  481. package/src/runtime/compaction/quality-score.ts +279 -0
  482. package/src/runtime/compaction/resume-repair.ts +183 -0
  483. package/src/runtime/compaction/strategies/autocompact.ts +65 -0
  484. package/src/runtime/compaction/strategies/boundary-commit.ts +106 -0
  485. package/src/runtime/compaction/strategies/collapse.ts +90 -0
  486. package/src/runtime/compaction/strategies/index.ts +23 -0
  487. package/src/runtime/compaction/strategies/microcompact.ts +74 -0
  488. package/src/runtime/compaction/strategies/reactive.ts +89 -0
  489. package/src/runtime/compaction/types.ts +221 -0
  490. package/src/runtime/context.ts +158 -0
  491. package/src/runtime/diagnostics/actions.ts +776 -0
  492. package/src/runtime/diagnostics/index.ts +99 -0
  493. package/src/runtime/diagnostics/panels/agents.ts +252 -0
  494. package/src/runtime/diagnostics/panels/events.ts +188 -0
  495. package/src/runtime/diagnostics/panels/health.ts +242 -0
  496. package/src/runtime/diagnostics/panels/index.ts +24 -0
  497. package/src/runtime/diagnostics/panels/ops.ts +156 -0
  498. package/src/runtime/diagnostics/panels/policy.ts +176 -0
  499. package/src/runtime/diagnostics/panels/tasks.ts +251 -0
  500. package/src/runtime/diagnostics/panels/tool-calls.ts +267 -0
  501. package/src/runtime/diagnostics/provider.ts +262 -0
  502. package/src/runtime/ecosystem/catalog.ts +606 -0
  503. package/src/runtime/ecosystem/recommendations.ts +117 -0
  504. package/src/runtime/emitters/agents.ts +96 -0
  505. package/src/runtime/emitters/automation.ts +112 -0
  506. package/src/runtime/emitters/communication.ts +53 -0
  507. package/src/runtime/emitters/compaction.ts +161 -0
  508. package/src/runtime/emitters/control-plane.ts +65 -0
  509. package/src/runtime/emitters/deliveries.ts +65 -0
  510. package/src/runtime/emitters/forensics.ts +17 -0
  511. package/src/runtime/emitters/index.ts +59 -0
  512. package/src/runtime/emitters/knowledge.ts +129 -0
  513. package/src/runtime/emitters/mcp.ts +95 -0
  514. package/src/runtime/emitters/ops.ts +163 -0
  515. package/src/runtime/emitters/orchestration.ts +87 -0
  516. package/src/runtime/emitters/permissions.ts +98 -0
  517. package/src/runtime/emitters/planner.ts +23 -0
  518. package/src/runtime/emitters/plugins.ts +78 -0
  519. package/src/runtime/emitters/providers.ts +30 -0
  520. package/src/runtime/emitters/routes.ts +57 -0
  521. package/src/runtime/emitters/security.ts +53 -0
  522. package/src/runtime/emitters/session.ts +93 -0
  523. package/src/runtime/emitters/surfaces.ts +57 -0
  524. package/src/runtime/emitters/tasks.ts +69 -0
  525. package/src/runtime/emitters/tools.ts +140 -0
  526. package/src/runtime/emitters/transport.ts +78 -0
  527. package/src/runtime/emitters/turn.ts +155 -0
  528. package/src/runtime/emitters/ui.ts +57 -0
  529. package/src/runtime/emitters/watchers.ts +57 -0
  530. package/src/runtime/emitters/workflows.ts +79 -0
  531. package/src/runtime/eval/index.ts +48 -0
  532. package/src/runtime/eval/runner.ts +163 -0
  533. package/src/runtime/eval/suites.ts +264 -0
  534. package/src/runtime/events/domain-map.ts +148 -0
  535. package/src/runtime/events/index.ts +194 -0
  536. package/src/runtime/events/turn.ts +60 -0
  537. package/src/runtime/events/workflows.ts +17 -0
  538. package/src/runtime/forensics/collector.ts +693 -0
  539. package/src/runtime/forensics/index.ts +23 -0
  540. package/src/runtime/foundation-clients.ts +78 -0
  541. package/src/runtime/foundation-services.ts +96 -0
  542. package/src/runtime/guidance.ts +183 -0
  543. package/src/runtime/health/effect-handlers.ts +189 -0
  544. package/src/runtime/health/index.ts +70 -0
  545. package/src/runtime/health/wiring.ts +115 -0
  546. package/src/runtime/index.ts +174 -0
  547. package/src/runtime/integration/helpers.ts +640 -0
  548. package/src/runtime/lifecycle.ts +107 -0
  549. package/src/runtime/mcp/index.ts +68 -0
  550. package/src/runtime/mcp/manager.ts +513 -0
  551. package/src/runtime/network/inbound.ts +131 -0
  552. package/src/runtime/network/index.ts +30 -0
  553. package/src/runtime/network/outbound.ts +292 -0
  554. package/src/runtime/network/shared.ts +82 -0
  555. package/src/runtime/operator-client.ts +235 -0
  556. package/src/runtime/ops/control-plane.ts +363 -0
  557. package/src/runtime/ops/index.ts +122 -0
  558. package/src/runtime/ops/playbooks/index.ts +10 -0
  559. package/src/runtime/ops/playbooks/session-unrecoverable.ts +196 -0
  560. package/src/runtime/ops/playbooks/stuck-turn.ts +197 -0
  561. package/src/runtime/ops/runtime-context.ts +100 -0
  562. package/src/runtime/ops-api.ts +27 -0
  563. package/src/runtime/orchestration/spawn-policy.ts +83 -0
  564. package/src/runtime/peer-client.ts +404 -0
  565. package/src/runtime/perf/index.ts +57 -0
  566. package/src/runtime/perf/slo-collector.ts +375 -0
  567. package/src/runtime/permissions/index.ts +190 -0
  568. package/src/runtime/permissions/policy-runtime.ts +175 -0
  569. package/src/runtime/permissions/preflight.ts +101 -0
  570. package/src/runtime/permissions/rule-suggestions.ts +36 -0
  571. package/src/runtime/plugins/hot-reload.ts +221 -0
  572. package/src/runtime/plugins/index.ts +84 -0
  573. package/src/runtime/plugins/lifecycle.ts +95 -0
  574. package/src/runtime/plugins/manager.ts +474 -0
  575. package/src/runtime/plugins/manifest.ts +167 -0
  576. package/src/runtime/plugins/quarantine.ts +202 -0
  577. package/src/runtime/plugins/trust.ts +291 -0
  578. package/src/runtime/plugins/types.ts +205 -0
  579. package/src/runtime/provider-accounts/registry.ts +326 -0
  580. package/src/runtime/remote/distributed-runtime-contract-schemas.ts +386 -0
  581. package/src/runtime/remote/index.ts +488 -0
  582. package/src/runtime/remote/runner-registry.ts +438 -0
  583. package/src/runtime/remote/supervisor.ts +70 -0
  584. package/src/runtime/runtime-hook-api.ts +5 -0
  585. package/src/runtime/runtime-knowledge-api.ts +14 -0
  586. package/src/runtime/runtime-mcp-api.ts +5 -0
  587. package/src/runtime/runtime-ops-api.ts +86 -0
  588. package/src/runtime/runtime-provider-api.ts +18 -0
  589. package/src/runtime/sandbox/backend.ts +291 -0
  590. package/src/runtime/sandbox/manager.ts +364 -0
  591. package/src/runtime/sandbox/provisioning.ts +422 -0
  592. package/src/runtime/sandbox/session-registry.ts +289 -0
  593. package/src/runtime/services.ts +541 -0
  594. package/src/runtime/session-maintenance.ts +188 -0
  595. package/src/runtime/session-persistence.ts +288 -0
  596. package/src/runtime/session-return-context.ts +195 -0
  597. package/src/runtime/settings/control-plane-store.ts +258 -0
  598. package/src/runtime/settings/control-plane.ts +599 -0
  599. package/src/runtime/shell-command-extensions.ts +54 -0
  600. package/src/runtime/shell-command-ops.ts +207 -0
  601. package/src/runtime/shell-command-platform.ts +47 -0
  602. package/src/runtime/shell-command-services.ts +143 -0
  603. package/src/runtime/shell-command-workspace.ts +31 -0
  604. package/src/runtime/store/domains/conversation.ts +181 -0
  605. package/src/runtime/store/domains/domain-read-matrix.ts +17 -0
  606. package/src/runtime/store/domains/index.ts +222 -0
  607. package/src/runtime/store/domains/panels.ts +117 -0
  608. package/src/runtime/store/domains/permissions.ts +143 -0
  609. package/src/runtime/store/domains/ui-perf.ts +103 -0
  610. package/src/runtime/store/helpers/reducers/conversation.ts +228 -0
  611. package/src/runtime/store/helpers/reducers/lifecycle.ts +440 -0
  612. package/src/runtime/store/helpers/reducers/shared.ts +60 -0
  613. package/src/runtime/store/helpers/reducers/sync.ts +555 -0
  614. package/src/runtime/store/helpers/reducers.ts +30 -0
  615. package/src/runtime/store/index.ts +304 -0
  616. package/src/runtime/store/selectors/index.ts +354 -0
  617. package/src/runtime/store/state.ts +137 -0
  618. package/src/runtime/tasks/adapters/acp-adapter.ts +211 -0
  619. package/src/runtime/tasks/adapters/agent-adapter.ts +208 -0
  620. package/src/runtime/tasks/adapters/index.ts +16 -0
  621. package/src/runtime/tasks/adapters/process-adapter.ts +214 -0
  622. package/src/runtime/tasks/adapters/scheduler-adapter.ts +193 -0
  623. package/src/runtime/tasks/index.ts +68 -0
  624. package/src/runtime/tasks/manager.ts +415 -0
  625. package/src/runtime/telemetry/api-helpers.ts +517 -0
  626. package/src/runtime/telemetry/api.ts +768 -0
  627. package/src/runtime/telemetry/index.ts +178 -0
  628. package/src/runtime/telemetry/instrumentation/domain-bridge-agent-session.ts +440 -0
  629. package/src/runtime/telemetry/instrumentation/domain-bridge-plugin-mcp.ts +200 -0
  630. package/src/runtime/telemetry/instrumentation/domain-bridge-shared.ts +18 -0
  631. package/src/runtime/telemetry/instrumentation/domain-bridge-transport-task.ts +204 -0
  632. package/src/runtime/telemetry/instrumentation/domain-bridge.ts +125 -0
  633. package/src/runtime/telemetry/instrumentation/index.ts +67 -0
  634. package/src/runtime/tools/context.ts +114 -0
  635. package/src/runtime/tools/index.ts +46 -0
  636. package/src/runtime/tools/phased-executor.ts +448 -0
  637. package/src/runtime/tools/phases/budget.ts +130 -0
  638. package/src/runtime/tools/phases/execute.ts +69 -0
  639. package/src/runtime/tools/phases/index.ts +13 -0
  640. package/src/runtime/tools/phases/map-output.ts +98 -0
  641. package/src/runtime/tools/phases/permission.ts +133 -0
  642. package/src/runtime/tools/phases/posthook.ts +57 -0
  643. package/src/runtime/tools/phases/prehook.ts +68 -0
  644. package/src/runtime/tools/phases/validate.ts +53 -0
  645. package/src/runtime/transports/direct.ts +73 -0
  646. package/src/runtime/transports/http-helpers.ts +218 -0
  647. package/src/runtime/transports/http-types.ts +364 -0
  648. package/src/runtime/transports/http.ts +629 -0
  649. package/src/runtime/transports/realtime.ts +50 -0
  650. package/src/runtime/transports/remote-events.ts +16 -0
  651. package/src/runtime/transports/shared.ts +39 -0
  652. package/src/runtime/transports/ui-runtime-events.ts +35 -0
  653. package/src/runtime/ui/index.ts +39 -0
  654. package/src/runtime/ui/model-picker/data-provider.ts +182 -0
  655. package/src/runtime/ui/model-picker/health-enrichment.ts +228 -0
  656. package/src/runtime/ui/model-picker/index.ts +59 -0
  657. package/src/runtime/ui/model-picker/types.ts +149 -0
  658. package/src/runtime/ui/provider-health/data-provider.ts +244 -0
  659. package/src/runtime/ui/provider-health/fallback-visualizer.ts +69 -0
  660. package/src/runtime/ui/provider-health/index.ts +46 -0
  661. package/src/runtime/ui/provider-health/types.ts +146 -0
  662. package/src/runtime/ui-events.ts +46 -0
  663. package/src/runtime/ui-read-model-helpers.ts +32 -0
  664. package/src/runtime/ui-read-models-core.ts +95 -0
  665. package/src/runtime/ui-read-models-observability-maintenance.ts +81 -0
  666. package/src/runtime/ui-read-models-observability-options.ts +5 -0
  667. package/src/runtime/ui-read-models-observability-remote.ts +73 -0
  668. package/src/runtime/ui-read-models-observability-security.ts +172 -0
  669. package/src/runtime/ui-read-models-observability-system.ts +217 -0
  670. package/src/runtime/ui-read-models-observability.ts +59 -0
  671. package/src/runtime/ui-read-models-operations.ts +203 -0
  672. package/src/runtime/ui-read-models.ts +61 -0
  673. package/src/runtime/ui-service-queries.ts +114 -0
  674. package/src/runtime/ui-services.ts +163 -0
  675. package/src/runtime/worktree/registry.ts +252 -0
  676. package/src/scripts/process-messages.ts +42 -0
  677. package/src/sessions/manager.ts +388 -0
  678. package/src/shell/blocking-input.ts +89 -0
  679. package/src/shell/ui-openers.ts +163 -0
  680. package/src/state/file-watcher.ts +294 -0
  681. package/src/state/index.ts +56 -0
  682. package/src/state/knowledge-injection.ts +214 -0
  683. package/src/state/memory-embedding-http.ts +642 -0
  684. package/src/state/memory-embeddings.ts +312 -0
  685. package/src/state/memory-ingest.ts +132 -0
  686. package/src/state/memory-registry.ts +111 -0
  687. package/src/state/memory-store-helpers.ts +160 -0
  688. package/src/state/memory-store.ts +728 -0
  689. package/src/state/memory-vector-store.ts +418 -0
  690. package/src/templates/manager.ts +187 -0
  691. package/src/tools/agent/index.ts +610 -0
  692. package/src/tools/agent/manager.ts +476 -0
  693. package/src/tools/analyze/git-modes.ts +380 -0
  694. package/src/tools/analyze/index.ts +128 -0
  695. package/src/tools/channel/agent-tools.ts +16 -0
  696. package/src/tools/channel/index.ts +268 -0
  697. package/src/tools/control/index.ts +90 -0
  698. package/src/tools/edit/core.ts +619 -0
  699. package/src/tools/edit/index.ts +4 -0
  700. package/src/tools/edit/phased.ts +33 -0
  701. package/src/tools/fetch/index.ts +3 -0
  702. package/src/tools/fetch/phased.ts +34 -0
  703. package/src/tools/fetch/runtime.ts +499 -0
  704. package/src/tools/index.ts +186 -0
  705. package/src/tools/mcp/index.ts +190 -0
  706. package/src/tools/remote-trigger/index.ts +130 -0
  707. package/src/tools/repl/index.ts +318 -0
  708. package/src/tools/shared/auto-heal.ts +282 -0
  709. package/src/tools/state/index.ts +688 -0
  710. package/src/tools/web-search/index.ts +38 -0
  711. package/src/tools/write/index.ts +604 -0
  712. package/src/tools/write/phased.ts +41 -0
  713. package/src/types/generated/foundation-client-types.ts +22 -0
  714. package/src/types/sql-js.d.ts +15 -0
  715. package/src/utils/splash-lines.ts +46 -0
  716. package/src/version.ts +17 -0
  717. package/src/watchers/index.ts +11 -0
  718. package/src/watchers/registry.ts +517 -0
  719. package/src/web-search/index.ts +26 -0
  720. package/src/web-search/provider-registry.ts +64 -0
  721. package/src/web-search/providers/brave.ts +100 -0
  722. package/src/web-search/providers/duckduckgo.ts +270 -0
  723. package/src/web-search/providers/exa.ts +77 -0
  724. package/src/web-search/providers/firecrawl.ts +90 -0
  725. package/src/web-search/providers/perplexity.ts +86 -0
  726. package/src/web-search/providers/searxng.ts +88 -0
  727. package/src/web-search/providers/shared.ts +249 -0
  728. package/src/web-search/providers/tavily.ts +90 -0
  729. package/src/web-search/service.ts +142 -0
  730. package/src/widget/index.ts +2 -0
  731. package/src/widget/types.ts +9 -0
  732. package/src/widget/widget.ts +8 -0
@@ -0,0 +1,730 @@
1
+ import type { ModelDefinition } from '../providers/registry.ts';
2
+ import type { FavoritesStore } from '@pellux/goodvibes-sdk/platform/providers/favorites';
3
+ import { EFFORT_DESCRIPTIONS } from '@pellux/goodvibes-sdk/platform/providers/effort-levels';
4
+ import { getQualityTier, getQualityTierFromScore, compositeScore, A_TIER_THRESHOLD } from '@pellux/goodvibes-sdk/platform/providers/model-benchmarks';
5
+ import type { BenchmarkStore } from '@pellux/goodvibes-sdk/platform/providers/model-benchmarks';
6
+ import type { ProviderRegistry } from '../providers/registry.ts';
7
+
8
+ export type PickerMode = 'model' | 'provider' | 'effort' | 'contextCap';
9
+
10
+ /**
11
+ * Pricing tier filter.
12
+ * 'paid' matches ModelDefinition tiers 'standard' and 'premium' for forward-compat
13
+ * with future CatalogModel tiers ('free' | 'paid' | 'subscription').
14
+ */
15
+ export type CategoryFilter = 'all' | 'free' | 'paid' | 'subscription';
16
+
17
+ /** Model family grouping names. */
18
+ export type ModelFamily =
19
+ | 'GPT'
20
+ | 'Claude'
21
+ | 'Gemini'
22
+ | 'Llama'
23
+ | 'Qwen'
24
+ | 'GLM'
25
+ | 'MiniMax'
26
+ | 'DeepSeek'
27
+ | 'Mistral'
28
+ | 'Command'
29
+ | 'Grok'
30
+ | 'Kimi'
31
+ | 'Other';
32
+
33
+ /** Capability filter — subset of ModelDefinition capabilities. */
34
+ export type CapabilityFilter = 'reasoning' | 'toolUse' | 'multimodal' | 'none';
35
+
36
+ /** Benchmark score sort order. */
37
+ export type BenchmarkSort = 'none' | 'composite' | 'swe' | 'gpqa';
38
+
39
+ /** Group-by cycling order. */
40
+ export type GroupByMode = 'provider' | 'family' | 'pricingTier' | 'qualityTier';
41
+
42
+ // ── Family detection helpers ──────────────────────────────────────────────────
43
+
44
+ /** Patterns for detecting model family from id/displayName. */
45
+ const FAMILY_PATTERNS: Array<{ pattern: RegExp; family: ModelFamily }> = [
46
+ { pattern: /claude/i, family: 'Claude' },
47
+ { pattern: /gpt|\bo1\b|\bo3\b|\bo4\b/i, family: 'GPT' },
48
+ { pattern: /gemini/i, family: 'Gemini' },
49
+ { pattern: /llama/i, family: 'Llama' },
50
+ { pattern: /qwen/i, family: 'Qwen' },
51
+ { pattern: /glm|chatglm/i, family: 'GLM' },
52
+ { pattern: /minimax|abab/i, family: 'MiniMax' },
53
+ { pattern: /deepseek/i, family: 'DeepSeek' },
54
+ { pattern: /mistral|mixtral/i, family: 'Mistral' },
55
+ { pattern: /command|cohere/i, family: 'Command' },
56
+ { pattern: /grok/i, family: 'Grok' },
57
+ { pattern: /kimi|moonshot/i, family: 'Kimi' },
58
+ ];
59
+
60
+ /** Detect the model family from id and displayName. */
61
+ export function detectFamily(model: ModelDefinition): ModelFamily {
62
+ const haystack = `${model.id} ${model.displayName}`;
63
+ for (const { pattern, family } of FAMILY_PATTERNS) {
64
+ if (pattern.test(haystack)) return family;
65
+ }
66
+ return 'Other';
67
+ }
68
+
69
+ /**
70
+ * Map ModelDefinition tier to CategoryFilter bucket.
71
+ * 'standard' and 'premium' both map to 'paid' for forward-compat.
72
+ */
73
+ export function tierToCategoryFilter(tier: string | undefined): CategoryFilter {
74
+ if (tier === 'free') return 'free';
75
+ if (tier === 'subscription') return 'subscription';
76
+ return 'paid';
77
+ }
78
+
79
+ /** A generic selectable item for non-model modes. */
80
+ export interface PickerItem {
81
+ id: string;
82
+ label: string;
83
+ detail?: string;
84
+ /** If true, this item is a group header (not selectable). */
85
+ isGroupHeader?: boolean;
86
+ /** Quality tier badge for model items: S/A/B/C. */
87
+ qualityTier?: string;
88
+ /** Whether this model is pinned/favorited. */
89
+ isPinned?: boolean;
90
+ /** True when model tier is free. */
91
+ isFree?: boolean;
92
+ /** True when this provider item has a configured API key. */
93
+ isConfigured?: boolean;
94
+ }
95
+
96
+ /** Provider IDs treated as "Popular" in the provider picker. */
97
+ export const POPULAR_PROVIDERS: ReadonlySet<string> = new Set([
98
+ 'anthropic',
99
+ 'google',
100
+ 'groq',
101
+ 'mistral',
102
+ 'nvidia',
103
+ 'ollama',
104
+ 'openai',
105
+ 'openrouter',
106
+ 'synthetic',
107
+ ]);
108
+
109
+ /**
110
+ * ModelPickerModal - Multi-step interactive picker for model, provider, and effort.
111
+ * Supports three modes: 'model', 'provider', 'effort'.
112
+ *
113
+ * Stage 5 features:
114
+ * - Pricing tier filter: Free / Paid / Subscription / All
115
+ * - Family grouping: GPT, Claude, Gemini, Llama, Qwen, etc.
116
+ * - Capability filters: reasoning, toolUse, multimodal
117
+ * - Available-only toggle (filters to configured providers)
118
+ * - Benchmark sort: composite / SWE / GPQA
119
+ * - Group-by cycling: provider → family → pricingTier → qualityTier
120
+ * - Quality tier badge (S/A/B/C) from model-benchmarks
121
+ * - Pinned/favorite indicator
122
+ */
123
+ export class ModelPickerModal {
124
+ constructor(
125
+ private readonly favoritesStore: Pick<FavoritesStore, 'getRecentModels'>,
126
+ private readonly benchmarkStore: Pick<BenchmarkStore, 'getBenchmarks'>,
127
+ private readonly providerRegistry: Pick<ProviderRegistry, 'getSyntheticModelInfoFromCatalog'>,
128
+ ) {}
129
+
130
+ public active = false;
131
+ public mode: PickerMode = 'model';
132
+ public searchFocused = false;
133
+ /** Tracks the mode we came from, for back-navigation. */
134
+ public previousMode: PickerMode | null = null;
135
+ public selectedIndex = 0;
136
+ /** Scroll offset for the visible item window (tracks first visible item index). */
137
+ public scrollOffset = 0;
138
+ public models: ModelDefinition[] = [];
139
+ public providers: string[] = [];
140
+ public effortLevels: string[] = [];
141
+ /** The model chosen in model-mode, awaiting effort selection. */
142
+ public pendingModel: ModelDefinition | null = null;
143
+ /** The model awaiting context cap input (contextCap mode). */
144
+ public contextCapPendingModel: ModelDefinition | null = null;
145
+ /** Current input string in contextCap mode. */
146
+ public contextCapQuery = '';
147
+
148
+ // ── Search / filter ──────────────────────────────────────────────────────────
149
+ /** Current search query string (empty = no filter). */
150
+ public query = '';
151
+ /** Active pricing tier filter. */
152
+ public categoryFilter: CategoryFilter = 'all';
153
+ /** Active capability filter. */
154
+ public capabilityFilter: CapabilityFilter = 'none';
155
+ /** When true, only show models from providers with a configured API key. */
156
+ public availableOnly = true;
157
+ /** Set of provider names that have a configured key (used for availableOnly filter). */
158
+ public configuredProviders: Set<string> = new Set();
159
+ /** IDs of pinned/favorite models — shown at top of list. */
160
+ public pinnedIds: Set<string> = new Set();
161
+ /** IDs of recently used models — shown after pinned, before the rest. */
162
+ public recentIds: string[] = [];
163
+ /** Benchmark score sort order. */
164
+ public benchmarkSort: BenchmarkSort = 'none';
165
+ /** Current group-by mode. */
166
+ public groupBy: GroupByMode = 'provider';
167
+
168
+ // ── Category filter cycling ───────────────────────────────────────────────
169
+ private static readonly CATEGORY_CYCLE: CategoryFilter[] = ['all', 'free', 'paid', 'subscription'];
170
+ /** Cycle to next pricing tier filter. */
171
+ cycleCategory(): void {
172
+ const idx = ModelPickerModal.CATEGORY_CYCLE.indexOf(this.categoryFilter);
173
+ this.categoryFilter = ModelPickerModal.CATEGORY_CYCLE[(idx + 1) % ModelPickerModal.CATEGORY_CYCLE.length];
174
+ this._clampSelection();
175
+ }
176
+
177
+ // ── Group-by cycling ──────────────────────────────────────────────────────
178
+ private static readonly GROUP_BY_CYCLE: GroupByMode[] = ['provider', 'family', 'pricingTier', 'qualityTier'];
179
+ /** Cycle to next group-by mode. */
180
+ cycleGroupBy(): void {
181
+ const idx = ModelPickerModal.GROUP_BY_CYCLE.indexOf(this.groupBy);
182
+ this.groupBy = ModelPickerModal.GROUP_BY_CYCLE[(idx + 1) % ModelPickerModal.GROUP_BY_CYCLE.length];
183
+ this._clampSelection();
184
+ }
185
+
186
+ // ── Benchmark sort cycling ────────────────────────────────────────────────
187
+ private static readonly BENCHMARK_SORT_CYCLE: BenchmarkSort[] = ['none', 'composite', 'swe', 'gpqa'];
188
+ /** Cycle to next benchmark sort order. */
189
+ cycleBenchmarkSort(): void {
190
+ const idx = ModelPickerModal.BENCHMARK_SORT_CYCLE.indexOf(this.benchmarkSort);
191
+ this.benchmarkSort = ModelPickerModal.BENCHMARK_SORT_CYCLE[(idx + 1) % ModelPickerModal.BENCHMARK_SORT_CYCLE.length];
192
+ this._clampSelection();
193
+ }
194
+
195
+ /**
196
+ * Return true when a model is from a custom or discovered (local) provider.
197
+ * Local models have `contextWindowProvenance` set; catalog cloud models do not.
198
+ */
199
+ isLocalModel(model: ModelDefinition): boolean {
200
+ return model.contextWindowProvenance !== undefined;
201
+ }
202
+
203
+ /** Enter context window cap input mode for a local model. */
204
+ enterContextCapMode(model: ModelDefinition): void {
205
+ this.previousMode = 'model';
206
+ this.contextCapPendingModel = model;
207
+ this.contextCapQuery = '';
208
+ this.mode = 'contextCap';
209
+ }
210
+
211
+ /** Append a character to the context cap query. */
212
+ appendContextCapChar(ch: string): void {
213
+ // Only allow digits; limit to 9 characters (max representable: 999_999_999)
214
+ if (this.contextCapQuery.length >= 9) return;
215
+ if (/^[0-9]$/.test(ch)) {
216
+ this.contextCapQuery += ch;
217
+ }
218
+ }
219
+
220
+ /** Delete the last character from the context cap query. */
221
+ deleteContextCapChar(): void {
222
+ if (this.contextCapQuery.length > 0) {
223
+ this.contextCapQuery = this.contextCapQuery.slice(0, -1);
224
+ }
225
+ }
226
+
227
+ /** Open showing all models — entry point for /model */
228
+ openAllModels(models: ModelDefinition[], currentModelId: string): void {
229
+ this.models = models;
230
+ this.mode = 'model';
231
+ this.active = true;
232
+ this.pendingModel = null;
233
+ this.searchFocused = false;
234
+ this.query = '';
235
+ this.categoryFilter = 'all';
236
+ this.capabilityFilter = 'none';
237
+ const filtered = this.getFilteredModels();
238
+ const idx = filtered.findIndex(m => m.id === currentModelId);
239
+ this.selectedIndex = idx >= 0 ? idx : 0;
240
+ this.scrollOffset = 0;
241
+ }
242
+
243
+ /** Open showing providers first — entry point for /provider */
244
+ openProviders(providers: string[], currentProvider: string): void {
245
+ this.previousMode = null;
246
+ this.providers = providers;
247
+ this.mode = 'provider';
248
+ this.active = true;
249
+ this.pendingModel = null;
250
+ this.searchFocused = false;
251
+ this.query = '';
252
+ this.categoryFilter = 'all';
253
+ this.capabilityFilter = 'none';
254
+ // Start selection at the top of the Popular providers group.
255
+ this.selectedIndex = 0;
256
+ this.scrollOffset = 0;
257
+ }
258
+
259
+ /** Transition to model list filtered by provider (called from provider mode Enter). */
260
+ showModelsForProvider(models: ModelDefinition[], _provider: string): void {
261
+ this.previousMode = 'provider';
262
+ this.models = models;
263
+ this.mode = 'model';
264
+ this.searchFocused = false;
265
+ this.query = '';
266
+ this.categoryFilter = 'all';
267
+ this.capabilityFilter = 'none';
268
+ // User explicitly chose this provider — disable availability filter so synthetic
269
+ // models (which have no real API key) are not filtered out.
270
+ this.availableOnly = false;
271
+ this.selectedIndex = 0;
272
+ this.scrollOffset = 0;
273
+ }
274
+
275
+ /** Transition to effort picker after model is chosen. */
276
+ showEffortPicker(model: ModelDefinition, currentEffort: string): void {
277
+ this.previousMode = 'model';
278
+ this.pendingModel = model;
279
+ this.searchFocused = false;
280
+ this.effortLevels = model.reasoningEffort ?? [];
281
+ this.mode = 'effort';
282
+ const idx = this.effortLevels.indexOf(currentEffort);
283
+ this.selectedIndex = idx >= 0 ? idx : 0;
284
+ this.scrollOffset = 0;
285
+ }
286
+
287
+ /** Close the picker entirely. */
288
+ close(): void {
289
+ this.active = false;
290
+ this.mode = 'model';
291
+ this.models = [];
292
+ this.providers = [];
293
+ this.pendingModel = null;
294
+ this.contextCapPendingModel = null;
295
+ this.contextCapQuery = '';
296
+ this.searchFocused = false;
297
+ this.selectedIndex = 0;
298
+ this.scrollOffset = 0;
299
+ this.query = '';
300
+ this.categoryFilter = 'all';
301
+ this.capabilityFilter = 'none';
302
+ }
303
+
304
+ // ── Search helpers ─────────────────────────────────────────────────────────
305
+
306
+ /** Append a character to the search query and clamp selectedIndex. */
307
+ appendChar(ch: string): void {
308
+ this.query += ch;
309
+ this._clampSelection();
310
+ }
311
+
312
+ /** Delete the last character from the search query and clamp selectedIndex. */
313
+ deleteChar(): void {
314
+ if (this.query.length > 0) {
315
+ this.query = this.query.slice(0, -1);
316
+ this._clampSelection();
317
+ }
318
+ }
319
+
320
+ /** Clear the search query and clamp selectedIndex. */
321
+ clearQuery(): void {
322
+ this.query = '';
323
+ this._clampSelection();
324
+ }
325
+
326
+ canFocusSearch(): boolean {
327
+ return this.mode === 'model' || this.mode === 'provider';
328
+ }
329
+
330
+ focusSearch(): void {
331
+ if (this.canFocusSearch()) this.searchFocused = true;
332
+ }
333
+
334
+ blurSearch(): void {
335
+ this.searchFocused = false;
336
+ }
337
+
338
+ /** Set category filter and clamp selectedIndex. */
339
+ setCategoryFilter(filter: CategoryFilter): void {
340
+ this.categoryFilter = filter;
341
+ this._clampSelection();
342
+ }
343
+
344
+ /** Set capability filter and clamp selectedIndex. */
345
+ setCapabilityFilter(filter: CapabilityFilter): void {
346
+ this.capabilityFilter = filter;
347
+ this._clampSelection();
348
+ }
349
+
350
+ /** Toggle the available-only filter. */
351
+ toggleAvailableOnly(): void {
352
+ this.availableOnly = !this.availableOnly;
353
+ this._clampSelection();
354
+ }
355
+
356
+ /**
357
+ * Split providers into two ordered groups: Popular, All.
358
+ * Each group is alphabetized. Popular contains providers in POPULAR_PROVIDERS;
359
+ * All contains the rest. Configuration status is shown via checkmarks in the
360
+ * renderer and does not affect grouping.
361
+ */
362
+ getGroupedProviders(): { popular: string[]; all: string[] } {
363
+ const popular: string[] = [];
364
+ const all: string[] = [];
365
+
366
+ for (const p of this.providers) {
367
+ const pLower = p.toLowerCase();
368
+ if (POPULAR_PROVIDERS.has(pLower)) {
369
+ popular.push(p);
370
+ } else {
371
+ all.push(p);
372
+ }
373
+ }
374
+
375
+ popular.sort((a, b) => a.localeCompare(b));
376
+ all.sort((a, b) => a.localeCompare(b));
377
+
378
+ return { popular, all };
379
+ }
380
+
381
+ /** Return providers matching the current query (case-insensitive substring), in grouped order. */
382
+ getFilteredProviders(): string[] {
383
+ const { popular, all } = this.getGroupedProviders();
384
+ const ordered = [...popular, ...all];
385
+ if (this.query.trim().length === 0) return ordered;
386
+ const q = this.query.toLowerCase();
387
+ return ordered.filter(p => p.toLowerCase().includes(q));
388
+ }
389
+
390
+ /** Return models matching all current filters, sorted per benchmarkSort. */
391
+ getFilteredModels(): ModelDefinition[] {
392
+ let result = this.models;
393
+
394
+ // Available-only filter
395
+ if (this.availableOnly && this.configuredProviders.size > 0) {
396
+ result = result.filter(m => this.configuredProviders.has(m.provider));
397
+ }
398
+
399
+ // Pricing tier / category filter
400
+ if (this.categoryFilter === 'free') {
401
+ result = result.filter(m => m.tier === 'free');
402
+ } else if (this.categoryFilter === 'paid') {
403
+ result = result.filter(m => m.tier === 'standard' || m.tier === 'premium' || m.tier == null);
404
+ } else if (this.categoryFilter === 'subscription') {
405
+ result = result.filter(m => tierToCategoryFilter(m.tier) === 'subscription');
406
+ }
407
+
408
+ // Capability filter
409
+ if (this.capabilityFilter === 'reasoning') {
410
+ result = result.filter(m => m.capabilities?.reasoning === true);
411
+ } else if (this.capabilityFilter === 'toolUse') {
412
+ result = result.filter(m => m.capabilities?.toolCalling === true);
413
+ } else if (this.capabilityFilter === 'multimodal') {
414
+ result = result.filter(m => m.capabilities?.multimodal === true);
415
+ }
416
+
417
+ // Query filter — fuzzy: every space-separated word must appear somewhere
418
+ if (this.query.trim().length > 0) {
419
+ const words = this.query.toLowerCase().split(/\s+/).filter(Boolean);
420
+ result = result.filter(m => {
421
+ const haystack = `${m.id} ${m.displayName} ${m.provider}`.toLowerCase();
422
+ return words.every(w => haystack.includes(w));
423
+ });
424
+ }
425
+
426
+ // Benchmark sort
427
+ if (this.benchmarkSort !== 'none') {
428
+ result = [...result].sort((a, b) => {
429
+ let scoreA: number | null = null;
430
+ let scoreB: number | null = null;
431
+
432
+ // For synthetic models, use pre-computed bestCompositeScore from backend lookup
433
+ // (synthetic canonical slugs don't exist in ZeroEval benchmark data)
434
+ if (this.benchmarkSort === 'composite') {
435
+ if (a.provider === 'synthetic') {
436
+ scoreA = this.providerRegistry.getSyntheticModelInfoFromCatalog(a.id)?.bestCompositeScore ?? null;
437
+ } else {
438
+ const bA = this.benchmarkStore.getBenchmarks(a.id) ?? this.benchmarkStore.getBenchmarks(a.displayName);
439
+ scoreA = bA ? compositeScore(bA.benchmarks) : null;
440
+ }
441
+ if (b.provider === 'synthetic') {
442
+ scoreB = this.providerRegistry.getSyntheticModelInfoFromCatalog(b.id)?.bestCompositeScore ?? null;
443
+ } else {
444
+ const bB = this.benchmarkStore.getBenchmarks(b.id) ?? this.benchmarkStore.getBenchmarks(b.displayName);
445
+ scoreB = bB ? compositeScore(bB.benchmarks) : null;
446
+ }
447
+ } else {
448
+ // swe/gpqa sort — individual benchmark scores not available for synthetic models — only composite is cached
449
+ const bA = a.provider === 'synthetic' ? null : (this.benchmarkStore.getBenchmarks(a.id) ?? this.benchmarkStore.getBenchmarks(a.displayName));
450
+ const bB = b.provider === 'synthetic' ? null : (this.benchmarkStore.getBenchmarks(b.id) ?? this.benchmarkStore.getBenchmarks(b.displayName));
451
+ if (this.benchmarkSort === 'swe') {
452
+ scoreA = bA?.benchmarks.swe ?? null;
453
+ scoreB = bB?.benchmarks.swe ?? null;
454
+ } else if (this.benchmarkSort === 'gpqa') {
455
+ scoreA = bA?.benchmarks.gpqa ?? null;
456
+ scoreB = bB?.benchmarks.gpqa ?? null;
457
+ }
458
+ }
459
+ // Models with no score sink to the end
460
+ if (scoreA == null && scoreB == null) return 0;
461
+ if (scoreA == null) return 1;
462
+ if (scoreB == null) return -1;
463
+ return scoreB - scoreA; // descending
464
+ });
465
+ }
466
+
467
+ // Synthetic sub-grouping: when groupBy is 'provider', order synthetic models so that
468
+ // "Top Models" (score ≥ 0.65) appear before "All Synthetic", each sub-group internally
469
+ // sorted: top by composite score desc, all alphabetically by id.
470
+ if (this.groupBy === 'provider' && this.benchmarkSort === 'none') {
471
+ const nonSynthetic = result.filter(m => m.provider !== 'synthetic');
472
+ const synthetic = result.filter(m => m.provider === 'synthetic');
473
+
474
+ if (synthetic.length > 0) {
475
+ const topModels = synthetic.filter(m => this._getSyntheticSubgroup(m) === 'top');
476
+ const allModels = synthetic.filter(m => this._getSyntheticSubgroup(m) === 'all');
477
+
478
+ // Sort top models by composite score descending
479
+ topModels.sort((a, b) => {
480
+ const sA = this.providerRegistry.getSyntheticModelInfoFromCatalog(a.id)?.bestCompositeScore ?? null;
481
+ const sB = this.providerRegistry.getSyntheticModelInfoFromCatalog(b.id)?.bestCompositeScore ?? null;
482
+ if (sA == null && sB == null) return 0;
483
+ if (sA == null) return 1;
484
+ if (sB == null) return -1;
485
+ return sB - sA;
486
+ });
487
+
488
+ // Sort remaining alphabetically by id
489
+ allModels.sort((a, b) => a.id.localeCompare(b.id));
490
+
491
+ result = [...nonSynthetic, ...topModels, ...allModels];
492
+ }
493
+ }
494
+
495
+ // Boost recent (non-pinned) models to the front of the list,
496
+ // preserving relative order within the recent group and within the rest.
497
+ if (this.recentIds.length > 0) {
498
+ const recentSet = new Set(this.recentIds);
499
+ const recent = this.recentIds
500
+ .filter(id => result.some(m => m.id === id && !this.pinnedIds.has(id)))
501
+ .map(id => result.find(m => m.id === id)!)
502
+ .filter(Boolean);
503
+ const rest = result.filter(m => !recentSet.has(m.id) || this.pinnedIds.has(m.id));
504
+ result = [...recent, ...rest];
505
+ }
506
+
507
+ return result;
508
+ }
509
+
510
+ /**
511
+ * Load recently used model IDs from favorites and cache them in recentIds.
512
+ * Call this when opening the picker to ensure recent models appear near the top.
513
+ */
514
+ async loadRecentModels(n = 10): Promise<void> {
515
+ this.recentIds = await this.favoritesStore.getRecentModels(n);
516
+ }
517
+
518
+ /**
519
+ * Return the group key for a model under the current groupBy mode.
520
+ * Used for inserting group headers in getItems().
521
+ *
522
+ * For synthetic provider models with groupBy 'provider', returns sub-group keys:
523
+ * - 'Top Models' — benchmark composite score ≥ 0.65 (A-tier or S-tier)
524
+ * - 'All Synthetic' — remaining synthetic models
525
+ */
526
+ getModelGroupKey(model: ModelDefinition): string {
527
+ switch (this.groupBy) {
528
+ case 'provider':
529
+ if (model.provider === 'synthetic') {
530
+ return this._getSyntheticSubgroup(model) === 'top' ? 'Top Models' : 'All Synthetic';
531
+ }
532
+ return model.provider;
533
+ case 'family': return detectFamily(model);
534
+ case 'pricingTier': return tierToCategoryFilter(model.tier);
535
+ case 'qualityTier': {
536
+ if (model.provider === 'synthetic') {
537
+ const info = this.providerRegistry.getSyntheticModelInfoFromCatalog(model.id);
538
+ return info?.bestCompositeScore != null ? getQualityTierFromScore(info.bestCompositeScore) : 'C';
539
+ }
540
+ const b = this.benchmarkStore.getBenchmarks(model.id) ?? this.benchmarkStore.getBenchmarks(model.displayName);
541
+ return b ? getQualityTier(b.benchmarks) : 'C';
542
+ }
543
+ }
544
+ }
545
+
546
+ /**
547
+ * Classify a synthetic model as 'top' or 'all' based on benchmark composite score.
548
+ * 'top': has benchmark data and score ≥ 0.65 (A-tier or S-tier)
549
+ * 'all': no benchmark data or score < 0.65
550
+ */
551
+ private _getSyntheticSubgroup(model: ModelDefinition): 'top' | 'all' {
552
+ const info = this.providerRegistry.getSyntheticModelInfoFromCatalog(model.id);
553
+ const score = info?.bestCompositeScore ?? null;
554
+ return score !== null && score >= A_TIER_THRESHOLD ? 'top' : 'all';
555
+ }
556
+
557
+ /** Get the items for the current mode as a unified list. */
558
+ getItems(): PickerItem[] {
559
+ if (this.mode === 'model') {
560
+ const filtered = this.getFilteredModels();
561
+
562
+ // Separate pinned and unpinned
563
+ const pinned = filtered.filter(m => this.pinnedIds.has(m.id));
564
+ const unpinned = filtered.filter(m => !this.pinnedIds.has(m.id));
565
+
566
+ const items: PickerItem[] = [];
567
+
568
+ // Pinned section header (only if pinned models are in the filtered list)
569
+ if (pinned.length > 0) {
570
+ items.push({ id: '__header__pinned', label: 'Favorites', isGroupHeader: true });
571
+ for (const m of pinned) {
572
+ items.push(this._modelToItem(m, true));
573
+ }
574
+ }
575
+
576
+ // Grouped unpinned models
577
+ let lastGroupKey = '';
578
+ for (const m of unpinned) {
579
+ const groupKey = this.getModelGroupKey(m);
580
+ if (groupKey !== lastGroupKey) {
581
+ items.push({ id: `__header__${groupKey}`, label: groupKey, isGroupHeader: true });
582
+ lastGroupKey = groupKey;
583
+ }
584
+ items.push(this._modelToItem(m, false));
585
+ }
586
+
587
+ return items;
588
+ }
589
+ if (this.mode === 'provider') {
590
+ const q = this.query.trim().toLowerCase();
591
+ const { popular, all } = this.getGroupedProviders();
592
+
593
+ const filterGroup = (group: string[]) =>
594
+ q.length === 0 ? group : group.filter(p => p.toLowerCase().includes(q));
595
+
596
+ const filteredPopular = filterGroup(popular);
597
+ const filteredAll = filterGroup(all);
598
+
599
+ const providerItems: PickerItem[] = [];
600
+
601
+ if (filteredPopular.length > 0) {
602
+ providerItems.push({ id: '__header__popular', label: 'Popular', isGroupHeader: true });
603
+ for (const p of filteredPopular) {
604
+ providerItems.push({ id: p, label: p, isConfigured: this.configuredProviders.has(p) });
605
+ }
606
+ }
607
+ if (filteredAll.length > 0) {
608
+ providerItems.push({ id: '__header__all', label: 'All Providers', isGroupHeader: true });
609
+ for (const p of filteredAll) {
610
+ providerItems.push({ id: p, label: p, isConfigured: this.configuredProviders.has(p) });
611
+ }
612
+ }
613
+
614
+ return providerItems;
615
+ }
616
+ // effort mode
617
+ return this.effortLevels.map(e => ({ id: e, label: e, detail: EFFORT_DESCRIPTIONS[e] ?? '' }));
618
+ }
619
+
620
+ /** Build a PickerItem for a model, including quality tier and pin status. */
621
+ private _modelToItem(model: ModelDefinition, isPinned: boolean): PickerItem {
622
+ // For synthetic models, derive quality tier from cached bestCompositeScore
623
+ // (synthetic canonical slugs don't exist in ZeroEval benchmark data)
624
+ let qualityTier: string | undefined;
625
+ let detail: string;
626
+ if (model.provider === 'synthetic') {
627
+ const synthInfo = this.providerRegistry.getSyntheticModelInfoFromCatalog(model.id);
628
+ if (synthInfo?.bestCompositeScore != null) {
629
+ qualityTier = getQualityTierFromScore(synthInfo.bestCompositeScore);
630
+ }
631
+ // Reuse synthInfo for provider count detail
632
+ detail = synthInfo !== null
633
+ ? `${model.provider} [${synthInfo.keyedBackendCount} provider${synthInfo.keyedBackendCount !== 1 ? 's' : ''}]`
634
+ : model.provider;
635
+ } else {
636
+ detail = model.provider;
637
+ const b = this.benchmarkStore.getBenchmarks(model.id) ?? this.benchmarkStore.getBenchmarks(model.displayName);
638
+ qualityTier = b ? getQualityTier(b.benchmarks) : undefined;
639
+ }
640
+ const isFree = tierToCategoryFilter(model.tier) === 'free';
641
+
642
+ return {
643
+ id: model.id,
644
+ label: model.displayName,
645
+ detail,
646
+ qualityTier,
647
+ isPinned,
648
+ isFree,
649
+ };
650
+ }
651
+
652
+ /** Get count of selectable (non-header) items in current mode. */
653
+ getItemCount(): number {
654
+ if (this.mode === 'model') return this.getFilteredModels().length;
655
+ if (this.mode === 'provider') return this.getFilteredProviders().length;
656
+ return this.effortLevels.length;
657
+ }
658
+
659
+ /**
660
+ * Move selection up (stops at 0 — no wrap to avoid going off-screen).
661
+ * Updates scrollOffset to keep selection visible.
662
+ */
663
+ moveUp(maxVisible = 20): void {
664
+ const count = this.getItemCount();
665
+ if (count === 0) return;
666
+ if (this.selectedIndex > 0) {
667
+ this.selectedIndex--;
668
+ this._scrollToSelection(maxVisible);
669
+ }
670
+ // At index 0 — stop. Do NOT wrap to count-1 (that puts selection off-screen).
671
+ }
672
+
673
+ /**
674
+ * Move selection down (wraps to 0 at bottom).
675
+ * Updates scrollOffset to keep selection visible.
676
+ */
677
+ moveDown(maxVisible = 20): void {
678
+ const count = this.getItemCount();
679
+ if (count === 0) return;
680
+ this.selectedIndex = this.selectedIndex < count - 1
681
+ ? this.selectedIndex + 1
682
+ : 0;
683
+ this._scrollToSelection(maxVisible);
684
+ }
685
+
686
+ /** Get the currently highlighted model, or null if not in model mode / empty. */
687
+ getSelected(): ModelDefinition | null {
688
+ if (this.mode !== 'model') return null;
689
+ const filtered = this.getFilteredModels();
690
+ if (filtered.length === 0) return null;
691
+ return filtered[this.selectedIndex] ?? null;
692
+ }
693
+
694
+ // ── Private helpers ────────────────────────────────────────────────────────
695
+
696
+ private _clampSelection(): void {
697
+ const count = this.getItemCount();
698
+ if (count === 0) {
699
+ this.selectedIndex = 0;
700
+ this.scrollOffset = 0;
701
+ } else if (this.selectedIndex >= count) {
702
+ this.selectedIndex = count - 1;
703
+ }
704
+ // Clamp scrollOffset too
705
+ const maxOffset = Math.max(0, count - 1);
706
+ if (this.scrollOffset > maxOffset) this.scrollOffset = maxOffset;
707
+ }
708
+
709
+ /**
710
+ * Adjust scrollOffset so selectedIndex is within the visible window [scrollOffset, scrollOffset + maxVisible).
711
+ * Called after every navigation action.
712
+ */
713
+ _scrollToSelection(maxVisible: number): void {
714
+ if (this.selectedIndex < this.scrollOffset) {
715
+ // Selection moved above viewport — scroll up
716
+ this.scrollOffset = this.selectedIndex;
717
+ } else if (this.selectedIndex >= this.scrollOffset + maxVisible) {
718
+ // Selection moved below viewport — scroll down
719
+ this.scrollOffset = this.selectedIndex - maxVisible + 1;
720
+ }
721
+ }
722
+
723
+ getSyntheticModelInfo(modelId: string) {
724
+ return this.providerRegistry.getSyntheticModelInfoFromCatalog(modelId);
725
+ }
726
+
727
+ getBenchmarkEntry(model: ModelDefinition) {
728
+ return this.benchmarkStore.getBenchmarks(model.id) ?? this.benchmarkStore.getBenchmarks(model.displayName);
729
+ }
730
+ }