cartethyia 2.4.6 → 2.5.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 (302) hide show
  1. package/README.md +45 -13
  2. package/dist/dashboard/assets/BannedPage-9igfctfZ.js +1 -0
  3. package/dist/dashboard/assets/BannedPage-9igfctfZ.js.gz +0 -0
  4. package/dist/dashboard/assets/{CliToolDetailPage-Bcy150Jo.js → CliToolDetailPage-ZRr2K2sF.js} +1 -1
  5. package/dist/dashboard/assets/CliToolDetailPage-ZRr2K2sF.js.gz +0 -0
  6. package/dist/dashboard/assets/{CliToolsPage-DyNXefph.js → CliToolsPage-D3xqZbN-.js} +1 -1
  7. package/dist/dashboard/assets/CliToolsPage-D3xqZbN-.js.gz +0 -0
  8. package/dist/dashboard/assets/CombosPage-CVnQPBBz.js +1 -0
  9. package/dist/dashboard/assets/CombosPage-CVnQPBBz.js.gz +0 -0
  10. package/dist/dashboard/assets/{ConsoleLogPage-CATpAEOz.js → ConsoleLogPage-u6W1UXSc.js} +1 -1
  11. package/dist/dashboard/assets/ConsoleLogPage-u6W1UXSc.js.gz +0 -0
  12. package/dist/dashboard/assets/{CustomizationPage-D6jEU7nL.js → CustomizationPage-CTTpCb25.js} +1 -1
  13. package/dist/dashboard/assets/CustomizationPage-CTTpCb25.js.gz +0 -0
  14. package/dist/dashboard/assets/{HealthEventsModal-CZkpn0a7.js → HealthEventsModal-C5XpWlvy.js} +1 -1
  15. package/dist/dashboard/assets/HealthEventsModal-C5XpWlvy.js.gz +0 -0
  16. package/dist/dashboard/assets/{LoginPage-BJlMVN4U.js → LoginPage-DHoB3Agn.js} +1 -1
  17. package/dist/dashboard/assets/LoginPage-DHoB3Agn.js.gz +0 -0
  18. package/dist/dashboard/assets/{ModelPicker-Br3CXEau.js → ModelPicker-oQ-SDXp8.js} +1 -1
  19. package/dist/dashboard/assets/ModelPicker-oQ-SDXp8.js.gz +0 -0
  20. package/dist/dashboard/assets/OverviewPage-BrY4SgIt.js +1 -0
  21. package/dist/dashboard/assets/OverviewPage-BrY4SgIt.js.gz +0 -0
  22. package/dist/dashboard/assets/ProviderDetailPage-D9VSt7T_.js +3 -0
  23. package/dist/dashboard/assets/ProviderDetailPage-D9VSt7T_.js.gz +0 -0
  24. package/dist/dashboard/assets/ProviderIcon-CmfzxpVM.js +1 -0
  25. package/dist/dashboard/assets/ProviderIcon-CmfzxpVM.js.gz +0 -0
  26. package/dist/dashboard/assets/ProvidersPage-Df77V5I5.js +1 -0
  27. package/dist/dashboard/assets/ProvidersPage-Df77V5I5.js.gz +0 -0
  28. package/dist/dashboard/assets/ProxyPage-CDfxP7AC.js +7 -0
  29. package/dist/dashboard/assets/ProxyPage-CDfxP7AC.js.gz +0 -0
  30. package/dist/dashboard/assets/QuotaPage-CY4o2t4p.js +1 -0
  31. package/dist/dashboard/assets/QuotaPage-CY4o2t4p.js.gz +0 -0
  32. package/dist/dashboard/assets/{SettingsPage-BHW4Zt06.js → SettingsPage-CvjZxHE4.js} +1 -1
  33. package/dist/dashboard/assets/SettingsPage-CvjZxHE4.js.gz +0 -0
  34. package/dist/dashboard/assets/{SetupPage-VOYC_lIa.js → SetupPage-D3nza7LH.js} +1 -1
  35. package/dist/dashboard/assets/SetupPage-D3nza7LH.js.gz +0 -0
  36. package/dist/dashboard/assets/SortableList-Dtt12WT1.js +1 -0
  37. package/dist/dashboard/assets/SortableList-Dtt12WT1.js.gz +0 -0
  38. package/dist/dashboard/assets/{StudioPage-BNIDLiU6.js → StudioPage-CbK3hXvU.js} +4 -4
  39. package/dist/dashboard/assets/StudioPage-CbK3hXvU.js.gz +0 -0
  40. package/dist/dashboard/assets/UsagePage-BQlWbWg2.js +56 -0
  41. package/dist/dashboard/assets/UsagePage-BQlWbWg2.js.gz +0 -0
  42. package/dist/dashboard/assets/{api-keys-BETfaMYZ.js → api-keys-BmDbsior.js} +1 -1
  43. package/dist/dashboard/assets/api-keys-BmDbsior.js.gz +0 -0
  44. package/dist/dashboard/assets/app-BbpSarVV.css +2 -0
  45. package/dist/dashboard/assets/app-BbpSarVV.css.gz +0 -0
  46. package/dist/dashboard/assets/assert-BHfaei-X.js +1 -0
  47. package/dist/dashboard/assets/assert-BHfaei-X.js.gz +0 -0
  48. package/dist/dashboard/assets/{badge-BTyGk_DL.js → badge-DpXT4Ixt.js} +1 -1
  49. package/dist/dashboard/assets/badge-DpXT4Ixt.js.gz +0 -0
  50. package/dist/dashboard/assets/{banner-bujTWYM4.js → banner-CSAAWLJv.js} +1 -1
  51. package/dist/dashboard/assets/banner-CSAAWLJv.js.gz +0 -0
  52. package/dist/dashboard/assets/{card-B0Bnx5CA.js → card-DEhRXXw4.js} +1 -1
  53. package/dist/dashboard/assets/card-DEhRXXw4.js.gz +0 -0
  54. package/dist/dashboard/assets/{cli-tools-DE2ug812.js → cli-tools-B0wBq-RL.js} +1 -1
  55. package/dist/dashboard/assets/cli-tools-B0wBq-RL.js.gz +0 -0
  56. package/dist/dashboard/assets/{clipboard-button-R_rArH8S.js → clipboard-button-DOo6EU8O.js} +1 -1
  57. package/dist/dashboard/assets/clipboard-button-DOo6EU8O.js.gz +0 -0
  58. package/dist/dashboard/assets/{dialog-BWIP_EvW.js → dialog-BvQeBMfP.js} +1 -1
  59. package/dist/dashboard/assets/dialog-BvQeBMfP.js.gz +0 -0
  60. package/dist/dashboard/assets/{drawer-BKcgVugF.js → drawer-CMwPXwQB.js} +1 -1
  61. package/dist/dashboard/assets/drawer-CMwPXwQB.js.gz +0 -0
  62. package/dist/dashboard/assets/{entry-CcWkGR4a.js → entry-D4O2229X.js} +1 -1
  63. package/dist/dashboard/assets/{entry-CcWkGR4a.js.gz → entry-D4O2229X.js.gz} +0 -0
  64. package/dist/dashboard/assets/{entry-kTiTFrvh.js → entry-DKvr5Q9K.js} +1 -1
  65. package/dist/dashboard/assets/entry-DKvr5Q9K.js.gz +0 -0
  66. package/dist/dashboard/assets/entry-FQCejoDY.js +4 -0
  67. package/dist/dashboard/assets/entry-FQCejoDY.js.gz +0 -0
  68. package/dist/dashboard/assets/{github-badge-Booc5mNe.js → github-badge-zliLvBwr.js} +1 -1
  69. package/dist/dashboard/assets/github-badge-zliLvBwr.js.gz +0 -0
  70. package/dist/dashboard/assets/{index-D70pChfn.js → index-CXm3FIyt.js} +2 -2
  71. package/dist/dashboard/assets/index-CXm3FIyt.js.gz +0 -0
  72. package/dist/dashboard/assets/{inline-D1TCqM2D.js → inline-D1IlZZ8H.js} +1 -1
  73. package/dist/dashboard/assets/inline-D1IlZZ8H.js.gz +0 -0
  74. package/dist/dashboard/assets/{input-B9oyU93y.js → input-BpVZdrbc.js} +1 -1
  75. package/dist/dashboard/assets/input-BpVZdrbc.js.gz +0 -0
  76. package/dist/dashboard/assets/{layout-BArzXirQ.js → layout-BioLW7gN.js} +1 -1
  77. package/dist/dashboard/assets/layout-BioLW7gN.js.gz +0 -0
  78. package/dist/dashboard/assets/{list-row-BVZzaoHF.js → list-row-CwVcGAIc.js} +1 -1
  79. package/dist/dashboard/assets/list-row-CwVcGAIc.js.gz +0 -0
  80. package/dist/dashboard/assets/{live-EmuIayqp.js → live-yfEJFF9T.js} +1 -1
  81. package/dist/dashboard/assets/live-yfEJFF9T.js.gz +0 -0
  82. package/dist/dashboard/assets/{model-bans-CSPQ9xB0.js → model-bans-CT-TxSNX.js} +1 -1
  83. package/dist/dashboard/assets/model-bans-CT-TxSNX.js.gz +0 -0
  84. package/dist/dashboard/assets/providers/cursor.webp +0 -0
  85. package/dist/dashboard/assets/providers/trae.webp +0 -0
  86. package/dist/dashboard/assets/{routing-BExgxxBU.js → routing-Cs6jl49L.js} +1 -1
  87. package/dist/dashboard/assets/routing-Cs6jl49L.js.gz +0 -0
  88. package/dist/dashboard/assets/select-C0Rer3gF.js +41 -0
  89. package/dist/dashboard/assets/select-C0Rer3gF.js.gz +0 -0
  90. package/dist/dashboard/assets/settings-CfcTcOyU.js +1 -0
  91. package/dist/dashboard/assets/settings-CfcTcOyU.js.gz +0 -0
  92. package/dist/dashboard/assets/{stack-Ca5HxI5i.js → stack-C9_uUjaA.js} +1 -1
  93. package/dist/dashboard/assets/stack-C9_uUjaA.js.gz +0 -0
  94. package/dist/dashboard/assets/{state-CzUPfLXo.js → state-DA0B95UQ.js} +1 -1
  95. package/dist/dashboard/assets/state-DA0B95UQ.js.gz +0 -0
  96. package/dist/dashboard/assets/{switch-DOUIky_i.js → switch-Cittxy-x.js} +1 -1
  97. package/dist/dashboard/assets/switch-Cittxy-x.js.gz +0 -0
  98. package/dist/dashboard/assets/{toast-C76WrMhc.js → toast-CNt_jzO4.js} +1 -1
  99. package/dist/dashboard/assets/toast-CNt_jzO4.js.gz +0 -0
  100. package/dist/dashboard/assets/{useMutation-D87FZwH0.js → useMutation-BQKMcJ1H.js} +1 -1
  101. package/dist/dashboard/assets/useMutation-BQKMcJ1H.js.gz +0 -0
  102. package/dist/dashboard/index.html +1 -1
  103. package/dist/dashboard/index.html.gz +0 -0
  104. package/dist/dashboard/precompressed-assets.json +51 -52
  105. package/migrations/full-postgres/0015_add_payload_stages.sql +18 -0
  106. package/migrations/full-postgres/0016_add_share_link_slug.sql +20 -0
  107. package/migrations/full-postgres/0017_add_models_pro_only.sql +18 -0
  108. package/migrations/full-postgres/0018_add_accounts_quota_plan.sql +18 -0
  109. package/migrations/full-postgres/0019_add_models_free_tier.sql +15 -0
  110. package/migrations/lite-sqlite3/0015_add_payload_stages.sql +18 -0
  111. package/migrations/lite-sqlite3/0016_add_share_link_slug.sql +20 -0
  112. package/migrations/lite-sqlite3/0017_add_models_pro_only.sql +18 -0
  113. package/migrations/lite-sqlite3/0018_add_accounts_quota_plan.sql +18 -0
  114. package/migrations/lite-sqlite3/0019_add_models_free_tier.sql +15 -0
  115. package/package.json +3 -2
  116. package/scripts/cli/command/console-menu.ts +30 -11
  117. package/scripts/cli/command/start.ts +77 -13
  118. package/scripts/cli/command/update.ts +1 -1
  119. package/scripts/cli/gateway/instance-lock.ts +105 -0
  120. package/scripts/cli/gateway/spawn.ts +173 -101
  121. package/scripts/cli/ui/background.ts +110 -49
  122. package/src/boot/app.ts +20 -6
  123. package/src/boot/dependencies.ts +19 -0
  124. package/src/boot/elysia-typebox-bridge.ts +37 -0
  125. package/src/boot/lifecycle.ts +2 -0
  126. package/src/config/cooldowns.ts +138 -138
  127. package/src/config/providers.ts +5 -5
  128. package/src/config/runtime.ts +19 -3
  129. package/src/config/spec.ts +9 -0
  130. package/src/console/api-keys/contracts.ts +414 -391
  131. package/src/console/api-keys/routes.ts +880 -870
  132. package/src/console/auth/account-reset-store.ts +14 -0
  133. package/src/console/auth/account-reset.ts +3 -2
  134. package/src/console/auth/setup-service.ts +6 -3
  135. package/src/console/domain-registration.ts +9 -2
  136. package/src/console/observability/contracts.ts +475 -455
  137. package/src/console/observability/store.ts +1015 -952
  138. package/src/console/providers/catalog/contracts.ts +31 -0
  139. package/src/console/providers/catalog/provider-operations.ts +8 -0
  140. package/src/console/providers/catalog/store.ts +1532 -1525
  141. package/src/console/providers/oauth/callback-listener.ts +18 -3
  142. package/src/console/providers/oauth/routes.ts +38 -15
  143. package/src/console/quota/refresh.ts +19 -0
  144. package/src/console/updates/routes.ts +162 -4
  145. package/src/main.ts +3 -0
  146. package/src/network/http2-bidi.ts +85 -0
  147. package/src/network/outbound-fetch.ts +1 -26
  148. package/src/network/pool/agent.ts +478 -460
  149. package/src/network/pool/resolver.ts +1104 -544
  150. package/src/network/pool-health-machine.ts +12 -5
  151. package/src/observability/payload-capture.ts +344 -324
  152. package/src/persistence/db-pg.ts +9 -3
  153. package/src/persistence/db-sqlite.ts +497 -495
  154. package/src/persistence/health-event-flush.ts +137 -0
  155. package/src/persistence/migrate.ts +181 -129
  156. package/src/persistence/schema.ts +1360 -1295
  157. package/src/persistence/settings-revision.ts +25 -0
  158. package/src/persistence/shared-cache.ts +217 -0
  159. package/src/persistence/stores/share-store.ts +556 -528
  160. package/src/persistence/usage-flush.ts +61 -1
  161. package/src/protocol/canonical-model.ts +23 -1
  162. package/src/protocol/response/chat.ts +143 -10
  163. package/src/protocol/response/omp-tool-call-markup.ts +221 -0
  164. package/src/providers/default-registry.ts +173 -1
  165. package/src/providers/discovery/openai-model-discovery.ts +39 -3
  166. package/src/providers/discovery/openrouter-free-roster.ts +98 -0
  167. package/src/providers/discovery/probing-service.ts +1022 -1001
  168. package/src/providers/integrations/buddy/buddy-catalog-shared.ts +39 -0
  169. package/src/providers/integrations/buddy/buddy-chat-shared.ts +913 -914
  170. package/src/providers/integrations/buddy/buddy-conversation-headers.ts +18 -5
  171. package/src/providers/integrations/buddy/codebuddy/cn-provider.ts +4 -0
  172. package/src/providers/integrations/buddy/codebuddy/provider.ts +180 -175
  173. package/src/providers/integrations/buddy/codebuddy/shared.ts +15 -6
  174. package/src/providers/integrations/buddy/workbuddy/provider.ts +206 -204
  175. package/src/providers/integrations/buddy/workbuddy/shared.ts +3 -3
  176. package/src/providers/integrations/claude/claude.ts +606 -582
  177. package/src/providers/integrations/cline/cline-quota.ts +18 -16
  178. package/src/providers/integrations/cline/cline.ts +539 -468
  179. package/src/providers/integrations/commandcode/commandcode.ts +675 -541
  180. package/src/providers/integrations/commandcode/discovery.ts +116 -0
  181. package/src/providers/integrations/commandcode/quota.ts +160 -0
  182. package/src/providers/integrations/cursor/cursor-auth.ts +418 -0
  183. package/src/providers/integrations/cursor/cursor-catalog.ts +368 -0
  184. package/src/providers/integrations/cursor/cursor-http2.ts +300 -0
  185. package/src/providers/integrations/cursor/cursor-identity.ts +92 -0
  186. package/src/providers/integrations/cursor/cursor-request.ts +471 -0
  187. package/src/providers/integrations/cursor/cursor-session-manager.ts +281 -0
  188. package/src/providers/integrations/cursor/cursor-shared.ts +108 -0
  189. package/src/providers/integrations/cursor/cursor.ts +960 -0
  190. package/src/providers/integrations/cursor/protocol/framing.ts +129 -0
  191. package/src/providers/integrations/cursor/protocol/messages.ts +2355 -0
  192. package/src/providers/integrations/cursor/protocol/struct.ts +305 -0
  193. package/src/providers/integrations/github/github.ts +4 -4
  194. package/src/providers/integrations/hermes.ts +79 -8
  195. package/src/providers/integrations/kilo/kilo-discovery.ts +10 -1
  196. package/src/providers/integrations/kilo/kilo.ts +153 -146
  197. package/src/providers/integrations/opencode/provider.ts +532 -538
  198. package/src/providers/integrations/openrouter/provider.ts +139 -281
  199. package/src/providers/integrations/qoder/qoder-catalog.ts +450 -0
  200. package/src/providers/integrations/qoder/qoder-oauth.ts +310 -257
  201. package/src/providers/integrations/qoder/qoder-realm.ts +106 -0
  202. package/src/providers/integrations/qoder/qoder-virtual.ts +77 -0
  203. package/src/providers/integrations/qoder/qoder.ts +865 -730
  204. package/src/providers/integrations/trae/trae-catalog.ts +190 -0
  205. package/src/providers/integrations/trae/trae-headers.ts +149 -0
  206. package/src/providers/integrations/trae/trae-oauth.ts +773 -0
  207. package/src/providers/integrations/trae/trae-protocol.ts +400 -0
  208. package/src/providers/integrations/trae/trae-realm.ts +220 -0
  209. package/src/providers/integrations/trae/trae.ts +458 -0
  210. package/src/providers/operations/account-health-service.ts +107 -10
  211. package/src/providers/operations/client-versions.ts +133 -6
  212. package/src/providers/operations/provider-catalog-seeder.ts +95 -86
  213. package/src/providers/operations/provider-credential-service.ts +46 -7
  214. package/src/providers/provider-contracts.ts +76 -0
  215. package/src/providers/provider-metadata.ts +78 -6
  216. package/src/providers/provider-model-definition.ts +373 -349
  217. package/src/security/api-key-auth.ts +126 -8
  218. package/src/transport/contracts.ts +3 -0
  219. package/src/transport/dispatch/attempt-finalize.ts +32 -1
  220. package/src/transport/dispatch/attempt-loop.ts +530 -515
  221. package/src/transport/dispatch/generation.ts +88 -21
  222. package/src/transport/dispatch/leases.ts +99 -2
  223. package/src/transport/dispatch/proxy-request.ts +60 -3
  224. package/src/transport/dispatch/streaming/finalize.ts +16 -1
  225. package/src/transport/dispatch/streaming/stream.ts +22 -2
  226. package/src/transport/dispatch/upstream.ts +19 -1
  227. package/src/transport/guards/auth-guard.ts +26 -1
  228. package/src/transport/guards/body-policy.ts +5 -4
  229. package/src/transport/request/preparer.ts +103 -0
  230. package/src/transport/request/state.ts +14 -0
  231. package/src/transport/routing/plan-gate.ts +45 -0
  232. package/src/transport/routing/route-catalog.ts +799 -777
  233. package/src/transport/routing/route-model.ts +52 -1
  234. package/src/transport/routing/router.ts +51 -0
  235. package/dist/dashboard/assets/BannedPage-QhvbJB9o.js +0 -1
  236. package/dist/dashboard/assets/BannedPage-QhvbJB9o.js.gz +0 -0
  237. package/dist/dashboard/assets/CliToolDetailPage-Bcy150Jo.js.gz +0 -0
  238. package/dist/dashboard/assets/CliToolsPage-DyNXefph.js.gz +0 -0
  239. package/dist/dashboard/assets/CombosPage-Cak0ljnO.js +0 -1
  240. package/dist/dashboard/assets/CombosPage-Cak0ljnO.js.gz +0 -0
  241. package/dist/dashboard/assets/ConsoleLogPage-CATpAEOz.js.gz +0 -0
  242. package/dist/dashboard/assets/CustomizationPage-D6jEU7nL.js.gz +0 -0
  243. package/dist/dashboard/assets/HealthEventsModal-CZkpn0a7.js.gz +0 -0
  244. package/dist/dashboard/assets/LoginPage-BJlMVN4U.js.gz +0 -0
  245. package/dist/dashboard/assets/ModelPicker-Br3CXEau.js.gz +0 -0
  246. package/dist/dashboard/assets/OverviewPage-CQxXbo2g.js +0 -1
  247. package/dist/dashboard/assets/OverviewPage-CQxXbo2g.js.gz +0 -0
  248. package/dist/dashboard/assets/ProviderDetailPage--0RBNh99.js +0 -3
  249. package/dist/dashboard/assets/ProviderDetailPage--0RBNh99.js.gz +0 -0
  250. package/dist/dashboard/assets/ProviderIcon-paOCq8I0.js +0 -1
  251. package/dist/dashboard/assets/ProviderIcon-paOCq8I0.js.gz +0 -0
  252. package/dist/dashboard/assets/ProvidersPage-1M-C3mbu.js +0 -1
  253. package/dist/dashboard/assets/ProvidersPage-1M-C3mbu.js.gz +0 -0
  254. package/dist/dashboard/assets/ProxyPage-CXiumA53.js +0 -7
  255. package/dist/dashboard/assets/ProxyPage-CXiumA53.js.gz +0 -0
  256. package/dist/dashboard/assets/QuotaPage-BGDIfIYP.js +0 -1
  257. package/dist/dashboard/assets/QuotaPage-BGDIfIYP.js.gz +0 -0
  258. package/dist/dashboard/assets/SettingsPage-BHW4Zt06.js.gz +0 -0
  259. package/dist/dashboard/assets/SetupPage-VOYC_lIa.js.gz +0 -0
  260. package/dist/dashboard/assets/SortableList-DpOTnGuH.js +0 -1
  261. package/dist/dashboard/assets/SortableList-DpOTnGuH.js.gz +0 -0
  262. package/dist/dashboard/assets/StudioPage-BNIDLiU6.js.gz +0 -0
  263. package/dist/dashboard/assets/UsagePage-BAFaSDQV.js +0 -56
  264. package/dist/dashboard/assets/UsagePage-BAFaSDQV.js.gz +0 -0
  265. package/dist/dashboard/assets/api-keys-BETfaMYZ.js.gz +0 -0
  266. package/dist/dashboard/assets/app-BLCvGdzb.css +0 -2
  267. package/dist/dashboard/assets/app-BLCvGdzb.css.gz +0 -0
  268. package/dist/dashboard/assets/assert-7J6_LVD3.js +0 -1
  269. package/dist/dashboard/assets/assert-7J6_LVD3.js.gz +0 -0
  270. package/dist/dashboard/assets/badge-BTyGk_DL.js.gz +0 -0
  271. package/dist/dashboard/assets/banner-bujTWYM4.js.gz +0 -0
  272. package/dist/dashboard/assets/card-B0Bnx5CA.js.gz +0 -0
  273. package/dist/dashboard/assets/cli-tools-DE2ug812.js.gz +0 -0
  274. package/dist/dashboard/assets/clipboard-button-R_rArH8S.js.gz +0 -0
  275. package/dist/dashboard/assets/dialog-BWIP_EvW.js.gz +0 -0
  276. package/dist/dashboard/assets/drawer-BKcgVugF.js.gz +0 -0
  277. package/dist/dashboard/assets/entry-7dpgvpJ3.js +0 -4
  278. package/dist/dashboard/assets/entry-7dpgvpJ3.js.gz +0 -0
  279. package/dist/dashboard/assets/entry-kTiTFrvh.js.gz +0 -0
  280. package/dist/dashboard/assets/github-badge-Booc5mNe.js.gz +0 -0
  281. package/dist/dashboard/assets/index-D70pChfn.js.gz +0 -0
  282. package/dist/dashboard/assets/inline-D1TCqM2D.js.gz +0 -0
  283. package/dist/dashboard/assets/input-B9oyU93y.js.gz +0 -0
  284. package/dist/dashboard/assets/layout-BArzXirQ.js.gz +0 -0
  285. package/dist/dashboard/assets/list-row-BVZzaoHF.js.gz +0 -0
  286. package/dist/dashboard/assets/live-EmuIayqp.js.gz +0 -0
  287. package/dist/dashboard/assets/model-bans-CSPQ9xB0.js.gz +0 -0
  288. package/dist/dashboard/assets/routing-BExgxxBU.js.gz +0 -0
  289. package/dist/dashboard/assets/select-C9mMoEpJ.js +0 -41
  290. package/dist/dashboard/assets/select-C9mMoEpJ.js.gz +0 -0
  291. package/dist/dashboard/assets/settings-C4_Gn_wq.js +0 -1
  292. package/dist/dashboard/assets/settings-C4_Gn_wq.js.gz +0 -0
  293. package/dist/dashboard/assets/sliders-horizontal-ClG7dBeJ.js +0 -1
  294. package/dist/dashboard/assets/sliders-horizontal-ClG7dBeJ.js.gz +0 -0
  295. package/dist/dashboard/assets/stack-Ca5HxI5i.js.gz +0 -0
  296. package/dist/dashboard/assets/state-CzUPfLXo.js.gz +0 -0
  297. package/dist/dashboard/assets/switch-DOUIky_i.js.gz +0 -0
  298. package/dist/dashboard/assets/toast-C76WrMhc.js.gz +0 -0
  299. package/dist/dashboard/assets/useMutation-D87FZwH0.js.gz +0 -0
  300. package/src/network/tls/bun-client-hello.ts +0 -112
  301. /package/dist/dashboard/assets/{app-Cjq7nrwu.js → app-C-Y4oZT0.js} +0 -0
  302. /package/dist/dashboard/assets/{app-Cjq7nrwu.js.gz → app-C-Y4oZT0.js.gz} +0 -0
@@ -1,101 +1,173 @@
1
- import { openSync } from "node:fs";
2
- import { join } from "node:path";
3
- import { PACKAGE_ROOT, resolvePackagePath } from "../../../src/config/package-paths";
4
-
5
- /**
6
- * Starts the gateway and resolves once it answers its own health probe.
7
- *
8
- * The child is attached by default. A detached child cannot be killed from an
9
- * `exit` handler on Windows (measured: `kill()` runs, the gateway keeps
10
- * serving), and a terminal's Ctrl-C only reaches children in the same process
11
- * group. Staying attached means Ctrl-C and the `exit` backstop both stop the
12
- * gateway. "Hide to tray" passes `detached: true` because that gateway must
13
- * outlive this process.
14
- */
15
- export async function startGateway(
16
- port: number,
17
- drainToken: string,
18
- options: { readonly detached?: boolean } = {},
19
- ): Promise<Bun.Subprocess> {
20
- // Starting a second gateway on the same port used to fail with "stopped
21
- // before becoming ready (exit code 1)", which says nothing about the real
22
- // cause. Probe the port first and say what is actually there.
23
- const existing = await fetch(`http://127.0.0.1:${port}/health`, {
24
- signal: AbortSignal.timeout(1_000),
25
- }).catch(() => null);
26
- if (existing !== null) {
27
- throw new Error(
28
- `Cartethyia is already running on port ${port}. Open http://127.0.0.1:${port}/console, or stop the terminal that started it.`,
29
- );
30
- }
31
- // The gateway logs a startup phase line per stage. In menu mode those lines
32
- // would scroll through the same terminal the menu is drawing on, so they go
33
- // to a file instead and the operator sees only the menu.
34
- const configDir = process.env.CARTETHYIA_CONFIG_DIR;
35
- let logFd: number | null = null;
36
- if (configDir) {
37
- try {
38
- logFd = openSync(join(configDir, "gateway.log"), "a");
39
- } catch {
40
- // A read-only config directory costs only the log, not startup.
41
- }
42
- }
43
- const child = Bun.spawn(
44
- [process.execPath, "run", resolvePackagePath("src", "main.ts")],
45
- {
46
- cwd: PACKAGE_ROOT,
47
- env: { ...process.env, CARTETHYIA_DRAIN_TOKEN: drainToken },
48
- // "ignore", not "inherit": the gateway does not read stdin, and in menu
49
- // mode the menu is the only stdin consumer. With "inherit" the gateway
50
- // held the fd and swallowed every keypress after the first, so the menu
51
- // rendered but never received Enter.
52
- stdin: "ignore",
53
- stdout: logFd ?? "ignore",
54
- // stderr goes to the same file: "inherit" ties the gateway to the
55
- // terminal, so closing that terminal took the gateway down with it and
56
- // "Hide to tray" left nothing running.
57
- stderr: logFd ?? "ignore",
58
- detached: options.detached === true,
59
- },
60
- );
61
- const startupController = new AbortController();
62
- const ready = (async (): Promise<void> => {
63
- const deadline = Date.now() + 30_000;
64
- while (!startupController.signal.aborted && Date.now() < deadline) {
65
- try {
66
- const response = await fetch(`http://127.0.0.1:${port}/health`, {
67
- signal: AbortSignal.timeout(500),
68
- });
69
- if (response.ok) {
70
- const body: unknown = await response.json();
71
- if (body !== null && typeof body === "object" && "status" in body && body.status === "ok") return;
72
- }
73
- } catch {
74
- // The gateway is not accepting health requests yet.
75
- }
76
- await Bun.sleep(250);
77
- }
78
- if (startupController.signal.aborted) throw new Error("Cartethyia exited before becoming ready");
79
- throw new Error("Cartethyia did not open its configured port within 30 seconds");
80
- })();
81
- const exitedEarly = child.exited.then((code) => {
82
- startupController.abort();
83
- return { status: "exit" as const, code };
84
- });
85
- const readyResult = ready.then(
86
- () => ({ status: "ready" as const }),
87
- (error: unknown) => ({ status: "failure" as const, error }),
88
- );
89
- try {
90
- const result = await Promise.race([readyResult, exitedEarly]);
91
- if (result.status === "exit") {
92
- throw new Error(`Cartethyia stopped before becoming ready (exit code ${result.code})`);
93
- }
94
- if (result.status === "failure") throw result.error;
95
- return child;
96
- } catch (error) {
97
- child.kill();
98
- await child.exited;
99
- throw error;
100
- }
101
- }
1
+ import { openSync } from "node:fs";
2
+ import {
3
+ clearInstanceLock,
4
+ fingerprint,
5
+ pidAlive,
6
+ readInstanceLock,
7
+ writeInstanceLock,
8
+ } from "./instance-lock";
9
+ import { join } from "node:path";
10
+ import { PACKAGE_ROOT, resolvePackagePath } from "../../../src/config/package-paths";
11
+
12
+ /**
13
+ * Frees the port when what holds it is this install's own gateway.
14
+ *
15
+ * Returns `null` when the port is free (or was successfully taken), and a
16
+ * message to fail with otherwise. It never kills a process it cannot identify:
17
+ * a stranger's service on this port is reported, not drained.
18
+ *
19
+ * The probe is two-step on purpose. `/health` alone is answered by anything, so
20
+ * it only says "something is there"; the drain request is what distinguishes
21
+ * ours from someone else's, because it is token-gated — and a 401 means another
22
+ * Cartethyia install, which this one must not stop.
23
+ */
24
+ async function claimPort(port: number, drainToken: string): Promise<string | null> {
25
+ // A lock naming this install's own gateway is the strongest evidence
26
+ // available, so prefer it over the network probe when it is live.
27
+ const lock = readInstanceLock();
28
+ const ours = lock !== null && lock.port === port && lock.tokenFingerprint === fingerprint(drainToken);
29
+
30
+ const response = await fetch(`http://127.0.0.1:${port}/admin/drain`, {
31
+ method: "POST",
32
+ headers: { "x-drain-token": drainToken },
33
+ signal: AbortSignal.timeout(3_000),
34
+ }).catch(() => null);
35
+
36
+ if (response === null) {
37
+ // Nothing answered a drain request. If our own lock names a dead pid, the
38
+ // marker is stale and nothing is holding the port — clear it and continue.
39
+ if (ours && !pidAlive(lock.pid)) {
40
+ clearInstanceLock();
41
+ return null;
42
+ }
43
+ return `Port ${port} is busy and nothing there answers a drain request. Stop whatever holds it, or set a different PORT.`;
44
+ }
45
+ if (response.status === 401) {
46
+ return `Another Cartethyia gateway is already running on port ${port} under a different install. Stop it from its own terminal or tray — this install will not stop a gateway it does not own.`;
47
+ }
48
+ if (response.status === 403) {
49
+ return `Port ${port} refused a drain from loopback. Stop what holds it, then start again.`;
50
+ }
51
+ if (response.status !== 202) {
52
+ return `Port ${port} is busy and refused to drain (HTTP ${response.status}). Stop it manually, then start again.`;
53
+ }
54
+ // Drain accepted. Wait for the port to actually free: the gateway flushes its
55
+ // 202 before closing sockets, so acceptance is not release.
56
+ const deadline = Date.now() + 30_000;
57
+ while (Date.now() < deadline) {
58
+ const stillUp = await fetch(`http://127.0.0.1:${port}/health`, {
59
+ signal: AbortSignal.timeout(500),
60
+ }).catch(() => null);
61
+ if (stillUp === null) {
62
+ clearInstanceLock();
63
+ return null;
64
+ }
65
+ await Bun.sleep(200);
66
+ }
67
+ return `The gateway on port ${port} accepted a drain but did not release the port within 30 seconds. Stop it manually, then start again.`;
68
+ }
69
+
70
+ /**
71
+ * Starts the gateway and resolves once it answers its own health probe.
72
+ *
73
+ * The child is attached by default. A detached child cannot be killed from an
74
+ * `exit` handler on Windows (measured: `kill()` runs, the gateway keeps
75
+ * serving), and a terminal's Ctrl-C only reaches children in the same process
76
+ * group. Staying attached means Ctrl-C and the `exit` backstop both stop the
77
+ * gateway. "Hide to tray" passes `detached: true` because that gateway must
78
+ * outlive this process.
79
+ */
80
+ export async function startGateway(
81
+ port: number,
82
+ drainToken: string,
83
+ options: { readonly detached?: boolean } = {},
84
+ ): Promise<Bun.Subprocess> {
85
+ // Starting a second gateway on the same port used to fail with "stopped
86
+ // before becoming ready (exit code 1)", which says nothing about the real
87
+ // cause. Probe the port first and say what is actually there.
88
+ const existing = await fetch(`http://127.0.0.1:${port}/health`, {
89
+ signal: AbortSignal.timeout(1_000),
90
+ }).catch(() => null);
91
+ if (existing !== null) {
92
+ // Something already owns the port. Replace it only when it proves to be our
93
+ // own gateway — otherwise stop and say what is there. A gateway left behind
94
+ // by a closed terminal used to make every later start fail until the
95
+ // operator found and killed it by hand.
96
+ const claimed = await claimPort(port, drainToken);
97
+ if (claimed !== null) throw new Error(claimed);
98
+ }
99
+ // The gateway logs a startup phase line per stage. In menu mode those lines
100
+ // would scroll through the same terminal the menu is drawing on, so they go
101
+ // to a file instead and the operator sees only the menu.
102
+ const configDir = process.env.CARTETHYIA_CONFIG_DIR;
103
+ let logFd: number | null = null;
104
+ if (configDir) {
105
+ try {
106
+ logFd = openSync(join(configDir, "gateway.log"), "a");
107
+ } catch {
108
+ // A read-only config directory costs only the log, not startup.
109
+ }
110
+ }
111
+ const child = Bun.spawn(
112
+ [process.execPath, "run", resolvePackagePath("src", "main.ts")],
113
+ {
114
+ cwd: PACKAGE_ROOT,
115
+ env: { ...process.env, CARTETHYIA_DRAIN_TOKEN: drainToken },
116
+ // "ignore", not "inherit": the gateway does not read stdin, and in menu
117
+ // mode the menu is the only stdin consumer. With "inherit" the gateway
118
+ // held the fd and swallowed every keypress after the first, so the menu
119
+ // rendered but never received Enter.
120
+ stdin: "ignore",
121
+ stdout: logFd ?? "ignore",
122
+ // stderr goes to the same file: "inherit" ties the gateway to the
123
+ // terminal, so closing that terminal took the gateway down with it and
124
+ // "Hide to tray" left nothing running.
125
+ stderr: logFd ?? "ignore",
126
+ detached: options.detached === true,
127
+ },
128
+ );
129
+ const startupController = new AbortController();
130
+ const ready = (async (): Promise<void> => {
131
+ const deadline = Date.now() + 30_000;
132
+ while (!startupController.signal.aborted && Date.now() < deadline) {
133
+ try {
134
+ const response = await fetch(`http://127.0.0.1:${port}/health`, {
135
+ signal: AbortSignal.timeout(500),
136
+ });
137
+ if (response.ok) {
138
+ const body: unknown = await response.json();
139
+ if (body !== null && typeof body === "object" && "status" in body && body.status === "ok") return;
140
+ }
141
+ } catch {
142
+ // The gateway is not accepting health requests yet.
143
+ }
144
+ await Bun.sleep(250);
145
+ }
146
+ if (startupController.signal.aborted) throw new Error("Cartethyia exited before becoming ready");
147
+ throw new Error("Cartethyia did not open its configured port within 30 seconds");
148
+ })();
149
+ const exitedEarly = child.exited.then((code) => {
150
+ startupController.abort();
151
+ return { status: "exit" as const, code };
152
+ });
153
+ const readyResult = ready.then(
154
+ () => ({ status: "ready" as const }),
155
+ (error: unknown) => ({ status: "failure" as const, error }),
156
+ );
157
+ try {
158
+ const result = await Promise.race([readyResult, exitedEarly]);
159
+ if (result.status === "exit") {
160
+ throw new Error(`Cartethyia stopped before becoming ready (exit code ${result.code})`);
161
+ }
162
+ if (result.status === "failure") throw result.error;
163
+ // Recorded only once the child is actually serving: a marker written before
164
+ // readiness would name a gateway that never came up, and the next start
165
+ // would treat a live port as ours when it is not.
166
+ writeInstanceLock(port, child.pid, drainToken);
167
+ return child;
168
+ } catch (error) {
169
+ child.kill();
170
+ await child.exited;
171
+ throw error;
172
+ }
173
+ }
@@ -1,49 +1,110 @@
1
- import { spawn } from "node:child_process";
2
- import { PACKAGE_ROOT, resolvePackagePath } from "../../../src/config/package-paths";
3
-
4
- /** Opens a URL in the operator's default browser, ignoring a missing handler. */
5
- export function openUrl(url: string): Promise<void> {
6
- const command = process.platform === "win32"
7
- ? "explorer.exe"
8
- : process.platform === "darwin"
9
- ? "open"
10
- : "xdg-open";
11
- const child = Bun.spawn([command, url], {
12
- stdin: "ignore",
13
- stdout: "ignore",
14
- stderr: "ignore",
15
- });
16
- return child.exited.then(() => undefined);
17
- }
18
-
19
- /**
20
- * Starts a Bun process that owns no console window.
21
- *
22
- * On Windows, spawning the child directly is not enough: a console-subsystem
23
- * child of a window-less parent allocates a new console, and when the parent is
24
- * inside Windows Terminal the tab is already on screen before any flag can take
25
- * effect — measured: `detached: true` plus `windowsHide: true` still produced a
26
- * Windows Terminal window titled with the bun path. `wscript.exe` is a
27
- * GUI-subsystem host, so it never allocates a console, and a child launched
28
- * from it with window style 0 inherits that. The VBS is that launcher.
29
- *
30
- * Elsewhere `detached` is enough and the helper is not used.
31
- */
32
- export function launchBackground(args: readonly string[]): void {
33
- if (process.platform !== "win32") {
34
- const child = spawn(process.execPath, [...args], {
35
- cwd: PACKAGE_ROOT,
36
- detached: true,
37
- stdio: "ignore",
38
- });
39
- child.unref();
40
- return;
41
- }
42
- const launcher = resolvePackagePath("scripts", "cli", "tray", "hidden-window.vbs");
43
- const child = spawn(
44
- "wscript.exe",
45
- ["//nologo", launcher, process.execPath, ...args],
46
- { cwd: PACKAGE_ROOT, detached: true, stdio: "ignore", windowsHide: true },
47
- );
48
- child.unref();
49
- }
1
+ import { spawn } from "node:child_process";
2
+ import { readFileSync, unlinkSync } from "node:fs";
3
+ import { PACKAGE_ROOT, resolvePackagePath } from "../../../src/config/package-paths";
4
+
5
+ /** Opens a URL in the operator's default browser, ignoring a missing handler. */
6
+ export function openUrl(url: string): Promise<void> {
7
+ const command = process.platform === "win32"
8
+ ? "explorer.exe"
9
+ : process.platform === "darwin"
10
+ ? "open"
11
+ : "xdg-open";
12
+ const child = Bun.spawn([command, url], {
13
+ stdin: "ignore",
14
+ stdout: "ignore",
15
+ stderr: "ignore",
16
+ });
17
+ return child.exited.then(() => undefined);
18
+ }
19
+
20
+ /**
21
+ * Starts a detached process and resolves once it reports readiness.
22
+ *
23
+ * The child owns no stdio, so a signal file is the only channel that survives
24
+ * the parent. Without it the caller prints "running in the background" for a
25
+ * child that may already have died — which is exactly what a port clash looks
26
+ * like, because the child's own startup probe fails while the parent's gateway
27
+ * still holds the port.
28
+ *
29
+ * Resolves with the URL the child reported. Rejects with the child's message
30
+ * when it reports failure, or with a timeout if it never reports at all.
31
+ */
32
+ export function launchBackgroundWithStatus(
33
+ args: readonly string[],
34
+ options: { readonly signalPath: string; readonly timeoutMs?: number } = { signalPath: "" },
35
+ ): Promise<string> {
36
+ return new Promise<string>((resolve, reject) => {
37
+ const signalPath = options.signalPath;
38
+ const deadline = Date.now() + (options.timeoutMs ?? 60_000);
39
+ let settled = false;
40
+ const finish = (error: Error | null, url?: string): void => {
41
+ if (settled) return;
42
+ settled = true;
43
+ clearInterval(timer);
44
+ try {
45
+ unlinkSync(signalPath);
46
+ } catch {}
47
+ if (error) reject(error);
48
+ else resolve(url ?? "");
49
+ };
50
+
51
+ const env = { ...process.env, CARTETHYIA_TRAY_STARTUP_SIGNAL: signalPath };
52
+ spawnBackground(args, env);
53
+
54
+ const timer = setInterval(() => {
55
+ if (settled) return;
56
+ let raw: string;
57
+ try {
58
+ raw = readFileSync(signalPath, "utf8");
59
+ } catch {
60
+ if (Date.now() >= deadline) finish(new Error("the background process did not report startup within the deadline"));
61
+ return;
62
+ }
63
+ try {
64
+ const signal = JSON.parse(raw) as { status?: string; url?: string; message?: string };
65
+ if (signal.status === "ready" && typeof signal.url === "string") finish(null, signal.url);
66
+ else if (signal.status === "failed") finish(new Error(signal.message ?? "the background process failed to start"));
67
+ } catch {
68
+ // A partially written file is read again on the next tick.
69
+ }
70
+ }, 150);
71
+ timer.unref?.();
72
+ });
73
+ }
74
+
75
+ /**
76
+ * Starts a Bun process that owns no console window.
77
+ *
78
+ * On Windows, spawning the child directly is not enough: a console-subsystem
79
+ * child of a window-less parent allocates a new console, and when the parent is
80
+ * inside Windows Terminal the tab is already on screen before any flag can take
81
+ * effect — measured: `detached: true` plus `windowsHide: true` still produced a
82
+ * Windows Terminal window titled with the bun path. `wscript.exe` is a
83
+ * GUI-subsystem host, so it never allocates a console, and a child launched
84
+ * from it with window style 0 inherits that. The VBS is that launcher.
85
+ *
86
+ * Elsewhere `detached` is enough and the helper is not used.
87
+ */
88
+ export function launchBackground(args: readonly string[]): void {
89
+ spawnBackground(args, process.env);
90
+ }
91
+
92
+ function spawnBackground(args: readonly string[], env: NodeJS.ProcessEnv): void {
93
+ if (process.platform !== "win32") {
94
+ const child = spawn(process.execPath, [...args], {
95
+ cwd: PACKAGE_ROOT,
96
+ detached: true,
97
+ stdio: "ignore",
98
+ env,
99
+ });
100
+ child.unref();
101
+ return;
102
+ }
103
+ const launcher = resolvePackagePath("scripts", "cli", "tray", "hidden-window.vbs");
104
+ const child = spawn(
105
+ "wscript.exe",
106
+ ["//nologo", launcher, process.execPath, ...args],
107
+ { cwd: PACKAGE_ROOT, detached: true, stdio: "ignore", windowsHide: true, env },
108
+ );
109
+ child.unref();
110
+ }
package/src/boot/app.ts CHANGED
@@ -34,14 +34,21 @@ import {
34
34
  import { createResponsesCompactHandler } from "../transport/dispatch/responses-compact";
35
35
  import { createSystemoneHandler } from "../transport/dispatch/systemone";
36
36
  import { createWebsearchHandler } from "../transport/dispatch/websearch";
37
- import { createImageGenerationHandler, createVideoGenerationHandler } from "../transport/dispatch/generation";
37
+ import {
38
+ createAudioSpeechHandler,
39
+ createAudioTranscriptionHandler,
40
+ createEmbeddingHandler,
41
+ createImageGenerationHandler,
42
+ createVideoGenerationHandler,
43
+ } from "../transport/dispatch/generation";
38
44
  import { createTokenCountHandler } from "../transport/dispatch/token-count";
39
45
 
40
46
  import { createTransportPipeline } from "../transport/guards/pipeline";
41
47
  import { createDrainHandler, isLoopbackPeer, tokenMatches } from "../transport/drain-endpoint";
42
48
  import { createConsoleAccountResetHandler } from "../console/auth/account-reset";
49
+ import { resetConsoleAuthState } from "../console/auth/account-reset-store";
43
50
  import { createBackupExportHandler } from "../console/backup/export-endpoint";
44
- import { consoleUsers, tenants } from "../persistence/schema";
51
+ import { tenants } from "../persistence/schema";
45
52
  import { PublicModelCatalogStore, type AllowedModelEntry } from "../console/providers/catalog/public-model-store";
46
53
  import type { IpAbuseProtectionService } from "../security/abuse";
47
54
  import type { ReadinessCheckResult } from "../persistence/readiness";
@@ -514,6 +521,13 @@ export function createGatewayApp(deps: GatewayAppDeps) {
514
521
  // Mounted with no model behind it on purpose: the surface exists so it can
515
522
  // be finished when an upstream is known, rather than 404ing like a typo.
516
523
  const handleVideoGeneration = createVideoGenerationHandler(generationDeps);
524
+ // Passthrough surfaces: routed and accounted for, but never interpreted.
525
+ // No bundled adapter implements embeddings or audio, and inventing a wire
526
+ // for them would be a guess; what these need to be useful is the route, the
527
+ // pipeline, and a compatible upstream behind it.
528
+ const handleEmbedding = createEmbeddingHandler(generationDeps);
529
+ const handleAudioTranscription = createAudioTranscriptionHandler(generationDeps);
530
+ const handleAudioSpeech = createAudioSpeechHandler(generationDeps);
517
531
  // Shares the generation dependency bundle: same pipeline, and a count is
518
532
  // neither chat-shaped nor generation-shaped, so it needs exactly the same
519
533
  // routing, leasing, and accounting surface.
@@ -535,6 +549,9 @@ export function createGatewayApp(deps: GatewayAppDeps) {
535
549
  routes.post("/search", handleWebsearch);
536
550
  routes.post("/images/generations", handleImageGeneration);
537
551
  routes.post("/videos/generations", handleVideoGeneration);
552
+ routes.post("/embeddings", handleEmbedding);
553
+ routes.post("/audio/transcriptions", handleAudioTranscription);
554
+ routes.post("/audio/speech", handleAudioSpeech);
538
555
  routes.get("/models", handleModelsList);
539
556
  routes.get("/models/info", handleModelsDetail);
540
557
  routes.get("/models/*", handleModelsDetail);
@@ -582,10 +599,7 @@ export function createGatewayApp(deps: GatewayAppDeps) {
582
599
  createConsoleAccountResetHandler({
583
600
  token: deps.drainToken,
584
601
  resolvePeerAddress: (request) => peerAddresses.get(request) ?? null,
585
- resetConsoleAccount: async () => {
586
- const users = await deps.db.delete(consoleUsers);
587
- return users.rowCount ?? 0;
588
- },
602
+ resetConsoleAccount: () => resetConsoleAuthState(deps.db),
589
603
  }),
590
604
  );
591
605
  }
@@ -32,6 +32,9 @@ import { RedisAdmissionCounterStore } from "../security/admission/redis-store";
32
32
  import { sweepLeases } from "../security/admission/lease-sweep";
33
33
  import { DrizzleApiKeyStore } from "../persistence/stores/api-key-store";
34
34
  import { UsageFlushBuffer } from "../persistence/usage-flush";
35
+ import { setPendingUsageSource } from "../security/api-key-auth";
36
+ import { HealthEventFlushBuffer, setHealthEventBuffer } from "../persistence/health-event-flush";
37
+ import { subscribePeerSettingsRevisions } from "../persistence/settings-revision";
35
38
  import { deleteExpiredSessions } from "../console/auth/session-service";
36
39
  import { InMemoryIpAbuseStore, IpAbuseProtectionService, RedisIpAbuseStore } from "../security/abuse";
37
40
  import {
@@ -111,6 +114,7 @@ export interface ProductionDeps {
111
114
  poolSelector: NetworkPoolSelector;
112
115
  telemetryBuffer: TelemetryBatchBuffer;
113
116
  usageFlush: UsageFlushBuffer;
117
+ healthEventFlush: HealthEventFlushBuffer;
114
118
  trustedProxyBoundary: TrustedProxyBoundary;
115
119
  providerRegistry: ProviderRegistry;
116
120
  resolveOAuthRefresher: (providerId: string) => Promise<OAuthTokenRefresher | undefined>;
@@ -204,7 +208,21 @@ export async function buildProductionDeps(): Promise<ProductionDeps> {
204
208
  }
205
209
  return [...totals].map(([accountId, inflight]) => ({ accountId, inflight }));
206
210
  };
211
+ // Peer processes' settings mutations arrive here. Subscribed once for the
212
+ // process: the revision counter is process-global, and the handler only
213
+ // moves it forward.
214
+ subscribePeerSettingsRevisions();
207
215
  const usageFlush = new UsageFlushBuffer(db);
216
+ // Auth reads lifetime usage to seed the admission counter. The persisted
217
+ // value lags by up to one flush interval, so hand it the in-flight deltas
218
+ // too — otherwise the seed is systematically under what has actually been
219
+ // spent, worst exactly when the key is busiest.
220
+ setPendingUsageSource((apiKeyId) => usageFlush.pendingFor(apiKeyId));
221
+ // Health audit rows are append-only and read by nobody in dispatch, so they
222
+ // no longer land inside the request. The status write they accompany stays
223
+ // synchronous — routing reads it.
224
+ const healthEventFlush = new HealthEventFlushBuffer(db);
225
+ setHealthEventBuffer(healthEventFlush);
208
226
  const admissionStore = redis
209
227
  ? new RedisAdmissionCounterStore(redis)
210
228
  : new InMemoryAdmissionCounterStore();
@@ -548,6 +566,7 @@ export async function buildProductionDeps(): Promise<ProductionDeps> {
548
566
  poolAgentResolver,
549
567
  telemetryBuffer,
550
568
  usageFlush,
569
+ healthEventFlush,
551
570
  poolSelector,
552
571
  admissionService,
553
572
  trustedProxyBoundary: resolveTrustedProxyBoundary(),
@@ -0,0 +1,37 @@
1
+ /**
2
+ * Wires Elysia's TypeBox bridge statically, for a source-run process.
3
+ *
4
+ * Elysia reaches TypeBox through a lazy `require("typebox/schema")` that
5
+ * resolves to a partial namespace outside a bundler. The bridge supplies all
6
+ * five namespaces explicitly so the lookup never has to happen.
7
+ *
8
+ * This is necessary but **not sufficient** for `NODE_ENV=production`: it does
9
+ * not stop the eager validator branch from reading
10
+ * `this.tb.buildResult.external`, which is still absent on a source run. That
11
+ * half is owned by `resolveElysiaPrecompile()`, which turns precompilation off
12
+ * for a source run. Keep both — wiring the bridge without the precompile guard
13
+ * still crashes, and the guard without the bridge leaves the lazy require in
14
+ * place for anything else that needs it.
15
+ *
16
+ * Keep this import first in `main.ts` so it runs before any module builds a
17
+ * route.
18
+ */
19
+ import { setupTypebox } from "elysia";
20
+ import * as typeboxType from "typebox";
21
+ import * as typeboxCompile from "typebox/compile";
22
+ import * as typeboxSchema from "typebox/schema";
23
+ import * as typeboxSystem from "typebox/system";
24
+ import * as typeboxValue from "typebox/value";
25
+
26
+ setupTypebox({
27
+ typebox: {
28
+ // The type side (`type` + `system`) and the value side (`value` + `schema`
29
+ // + `compile`) must both be complete; Elysia rejects a partial set because
30
+ // a half-wired bridge fails later with a less obvious error.
31
+ type: typeboxType,
32
+ system: typeboxSystem,
33
+ value: typeboxValue,
34
+ schema: typeboxSchema,
35
+ compile: typeboxCompile,
36
+ },
37
+ });
@@ -220,12 +220,14 @@ export async function bootstrap(): Promise<CartethyiaBoot> {
220
220
  flushTelemetry: async () => {
221
221
  await deps.telemetryBuffer.flush();
222
222
  await deps.usageFlush.flush();
223
+ await deps.healthEventFlush.flush();
223
224
  },
224
225
  closePools: async () => {
225
226
  await boot.server?.stop();
226
227
  await deps.scheduledTasks.stop();
227
228
  await deps.telemetryBuffer.stop({ flush: true });
228
229
  await deps.usageFlush.stop({ flush: true });
230
+ await deps.healthEventFlush.stop();
229
231
  await Promise.allSettled([
230
232
  closeDb(),
231
233
  closeRedis(),