cartethyia 2.5.4 → 2.5.7

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 (395) hide show
  1. package/.env.example +13 -4
  2. package/README.md +2 -2
  3. package/dist/dashboard/assets/{BannedPage-C2orBsI2.js → BannedPage-DE2607Tb.js} +1 -1
  4. package/dist/dashboard/assets/BannedPage-DE2607Tb.js.gz +0 -0
  5. package/dist/dashboard/assets/CliToolDetailPage-Duv6mfrl.js +1 -0
  6. package/dist/dashboard/assets/CliToolDetailPage-Duv6mfrl.js.gz +0 -0
  7. package/dist/dashboard/assets/{CliToolsPage-BV6pxVQo.js → CliToolsPage-C2PNYWzk.js} +1 -1
  8. package/dist/dashboard/assets/CliToolsPage-C2PNYWzk.js.gz +0 -0
  9. package/dist/dashboard/assets/CombosPage-TXP9OfAM.js +1 -0
  10. package/dist/dashboard/assets/CombosPage-TXP9OfAM.js.gz +0 -0
  11. package/dist/dashboard/assets/{ConsoleLogPage-CW-wPmJ6.js → ConsoleLogPage-B-9xnAQN.js} +1 -1
  12. package/dist/dashboard/assets/ConsoleLogPage-B-9xnAQN.js.gz +0 -0
  13. package/dist/dashboard/assets/{CustomizationPage-0p8uX7vB.js → CustomizationPage-xeax9nJt.js} +1 -1
  14. package/dist/dashboard/assets/CustomizationPage-xeax9nJt.js.gz +0 -0
  15. package/dist/dashboard/assets/{HealthEventsModal-BjqSMfko.js → HealthEventsModal-Df4jjaHJ.js} +1 -1
  16. package/dist/dashboard/assets/HealthEventsModal-Df4jjaHJ.js.gz +0 -0
  17. package/dist/dashboard/assets/{LoginPage-BvzCx8Kr.js → LoginPage-Dyojsm84.js} +1 -1
  18. package/dist/dashboard/assets/LoginPage-Dyojsm84.js.gz +0 -0
  19. package/dist/dashboard/assets/ModelPicker-C7pDb41R.js +1 -0
  20. package/dist/dashboard/assets/ModelPicker-C7pDb41R.js.gz +0 -0
  21. package/dist/dashboard/assets/OverviewPage-BJfg4ow6.js +1 -0
  22. package/dist/dashboard/assets/OverviewPage-BJfg4ow6.js.gz +0 -0
  23. package/dist/dashboard/assets/ProviderDetailPage-ChCX1gmT.js +3 -0
  24. package/dist/dashboard/assets/ProviderDetailPage-ChCX1gmT.js.gz +0 -0
  25. package/dist/dashboard/assets/{ProvidersPage-Js9jUPSb.js → ProvidersPage-DeWSOEUO.js} +1 -1
  26. package/dist/dashboard/assets/ProvidersPage-DeWSOEUO.js.gz +0 -0
  27. package/dist/dashboard/assets/ProxyPage-CkpWln2S.js +7 -0
  28. package/dist/dashboard/assets/ProxyPage-CkpWln2S.js.gz +0 -0
  29. package/dist/dashboard/assets/QuotaPage-B3VOgQmt.js +1 -0
  30. package/dist/dashboard/assets/QuotaPage-B3VOgQmt.js.gz +0 -0
  31. package/dist/dashboard/assets/SettingsPage-C_klIZom.js +1 -0
  32. package/dist/dashboard/assets/SettingsPage-C_klIZom.js.gz +0 -0
  33. package/dist/dashboard/assets/{SetupPage-DDbMQsoo.js → SetupPage-BYph-InK.js} +1 -1
  34. package/dist/dashboard/assets/SetupPage-BYph-InK.js.gz +0 -0
  35. package/dist/dashboard/assets/{SortableList-CB3Z7VTf.js → SortableList-CvyQCYWJ.js} +1 -1
  36. package/dist/dashboard/assets/SortableList-CvyQCYWJ.js.gz +0 -0
  37. package/dist/dashboard/assets/{StudioPage-D95UCPHI.js → StudioPage-Cm1JYilI.js} +3 -3
  38. package/dist/dashboard/assets/StudioPage-Cm1JYilI.js.gz +0 -0
  39. package/dist/dashboard/assets/UsagePage-DlFnc3_S.js +56 -0
  40. package/dist/dashboard/assets/UsagePage-DlFnc3_S.js.gz +0 -0
  41. package/dist/dashboard/assets/api-keys-DQYLQxpW.js +1 -0
  42. package/dist/dashboard/assets/api-keys-DQYLQxpW.js.gz +0 -0
  43. package/dist/dashboard/assets/app-CX_Z8cMs.css +2 -0
  44. package/dist/dashboard/assets/app-CX_Z8cMs.css.gz +0 -0
  45. package/dist/dashboard/assets/{assert-Chi7pCnt.js → assert-C9FD_JwJ.js} +1 -1
  46. package/dist/dashboard/assets/assert-C9FD_JwJ.js.gz +0 -0
  47. package/dist/dashboard/assets/{badge-D4pQapKj.js → badge-CR72dasT.js} +1 -1
  48. package/dist/dashboard/assets/badge-CR72dasT.js.gz +0 -0
  49. package/dist/dashboard/assets/{banner-DH6cg-_B.js → banner-CI9_eSfO.js} +1 -1
  50. package/dist/dashboard/assets/banner-CI9_eSfO.js.gz +0 -0
  51. package/dist/dashboard/assets/{card-BKqlUOe4.js → card-DS7CQEsO.js} +1 -1
  52. package/dist/dashboard/assets/card-DS7CQEsO.js.gz +0 -0
  53. package/dist/dashboard/assets/{cli-tools-fTgljTGA.js → cli-tools-CNqzrfdb.js} +1 -1
  54. package/dist/dashboard/assets/cli-tools-CNqzrfdb.js.gz +0 -0
  55. package/dist/dashboard/assets/{clipboard-button-D5Ys_sM0.js → clipboard-button-611pSsaX.js} +1 -1
  56. package/dist/dashboard/assets/clipboard-button-611pSsaX.js.gz +0 -0
  57. package/dist/dashboard/assets/{fingerprint-pattern-BWuHtbW4.js → concurrency-BDF0ok7y.js} +1 -1
  58. package/dist/dashboard/assets/concurrency-BDF0ok7y.js.gz +0 -0
  59. package/dist/dashboard/assets/dialog-k37exjNt.js +1 -0
  60. package/dist/dashboard/assets/dialog-k37exjNt.js.gz +0 -0
  61. package/dist/dashboard/assets/drawer-CX7N8ucR.js +1 -0
  62. package/dist/dashboard/assets/drawer-CX7N8ucR.js.gz +0 -0
  63. package/dist/dashboard/assets/{entry-CQBr6U9c.js → entry-B9Xvtnfj.js} +1 -1
  64. package/dist/dashboard/assets/entry-B9Xvtnfj.js.gz +0 -0
  65. package/dist/dashboard/assets/entry-Bog0p925.js +4 -0
  66. package/dist/dashboard/assets/entry-Bog0p925.js.gz +0 -0
  67. package/dist/dashboard/assets/entry-CTP8AyiS.js +1 -0
  68. package/dist/dashboard/assets/entry-CTP8AyiS.js.gz +0 -0
  69. package/dist/dashboard/assets/{github-badge-BlptL8Uf.js → github-badge-sltk1qUi.js} +1 -1
  70. package/dist/dashboard/assets/github-badge-sltk1qUi.js.gz +0 -0
  71. package/dist/dashboard/assets/{index-js6TJvdr.js → index-CZQIrdoX.js} +2 -2
  72. package/dist/dashboard/assets/index-CZQIrdoX.js.gz +0 -0
  73. package/dist/dashboard/assets/{inline-DUWDSmDV.js → inline-BhY2sSnO.js} +1 -1
  74. package/dist/dashboard/assets/inline-BhY2sSnO.js.gz +0 -0
  75. package/dist/dashboard/assets/{input-LVjgZq4f.js → input-V1umFiBk.js} +1 -1
  76. package/dist/dashboard/assets/input-V1umFiBk.js.gz +0 -0
  77. package/dist/dashboard/assets/layout-BBOYycyS.js +1 -0
  78. package/dist/dashboard/assets/layout-BBOYycyS.js.gz +0 -0
  79. package/dist/dashboard/assets/{list-row-DupvLfDx.js → list-row-1zzZYM0B.js} +1 -1
  80. package/dist/dashboard/assets/list-row-1zzZYM0B.js.gz +0 -0
  81. package/dist/dashboard/assets/{live-C4pEJUqI.js → live-BivGfuUs.js} +1 -1
  82. package/dist/dashboard/assets/live-BivGfuUs.js.gz +0 -0
  83. package/dist/dashboard/assets/{model-bans-BRiuufkB.js → model-bans-CmffjLuG.js} +1 -1
  84. package/dist/dashboard/assets/model-bans-CmffjLuG.js.gz +0 -0
  85. package/dist/dashboard/assets/{mutation-B4ROwk8L.js → mutation-46P0oWMe.js} +1 -1
  86. package/dist/dashboard/assets/mutation-46P0oWMe.js.gz +0 -0
  87. package/dist/dashboard/assets/numbers-DkfMR5Qs.js +2 -0
  88. package/dist/dashboard/assets/numbers-DkfMR5Qs.js.gz +0 -0
  89. package/dist/dashboard/assets/{query-keys-gmjzC3RS.js → query-keys-BFhGpwR1.js} +1 -1
  90. package/dist/dashboard/assets/query-keys-BFhGpwR1.js.gz +0 -0
  91. package/dist/dashboard/assets/{routing-C7tBT5o-.js → routing-CAb4F37R.js} +1 -1
  92. package/dist/dashboard/assets/routing-CAb4F37R.js.gz +0 -0
  93. package/dist/dashboard/assets/{select-WUCXorXw.js → select-BPpSsBgL.js} +1 -1
  94. package/dist/dashboard/assets/select-BPpSsBgL.js.gz +0 -0
  95. package/dist/dashboard/assets/{settings-C_zKl1GS.js → settings-DNX9sydi.js} +1 -1
  96. package/dist/dashboard/assets/settings-DNX9sydi.js.gz +0 -0
  97. package/dist/dashboard/assets/{stack-_3O0F8Xn.js → stack-BHTfXdz_.js} +1 -1
  98. package/dist/dashboard/assets/stack-BHTfXdz_.js.gz +0 -0
  99. package/dist/dashboard/assets/{state-BgTxHcJS.js → state-mPDRlQkl.js} +1 -1
  100. package/dist/dashboard/assets/state-mPDRlQkl.js.gz +0 -0
  101. package/dist/dashboard/assets/{switch-DEjTtOWf.js → switch-DOmJxg7H.js} +1 -1
  102. package/dist/dashboard/assets/switch-DOmJxg7H.js.gz +0 -0
  103. package/dist/dashboard/assets/tokens-COzario9.js +1 -0
  104. package/dist/dashboard/assets/tokens-COzario9.js.gz +0 -0
  105. package/dist/dashboard/assets/use-modal-focus-By7oJoGd.js +1 -0
  106. package/dist/dashboard/assets/use-modal-focus-By7oJoGd.js.gz +0 -0
  107. package/dist/dashboard/assets/{useMutation-BWSC0UAS.js → useMutation-Dem0lXQj.js} +1 -1
  108. package/dist/dashboard/assets/useMutation-Dem0lXQj.js.gz +0 -0
  109. package/dist/dashboard/index.html +1 -1
  110. package/dist/dashboard/index.html.gz +0 -0
  111. package/dist/dashboard/precompressed-assets.json +54 -54
  112. package/migrations/README.md +52 -52
  113. package/migrations/lite-sqlite3/0000_baseline.sql +2 -2
  114. package/package.json +95 -95
  115. package/scripts/cli/admin.ts +125 -0
  116. package/scripts/cli/{ui/background.ts → background.ts} +1 -1
  117. package/scripts/cli/{command/console-menu.ts → console-menu.ts} +275 -244
  118. package/scripts/cli/doctor.ts +37 -0
  119. package/scripts/cli/{support/env.ts → env.ts} +99 -99
  120. package/scripts/cli/{gateway/spawn.ts → gateway.ts} +112 -24
  121. package/scripts/cli/instance.ts +224 -0
  122. package/scripts/cli/main.ts +61 -18
  123. package/scripts/cli/{support/platform.ts → platform.ts} +64 -64
  124. package/scripts/cli/port.ts +100 -0
  125. package/scripts/cli/{support/services.ts → services.ts} +88 -88
  126. package/scripts/cli/{command/setup.ts → setup.ts} +35 -34
  127. package/scripts/cli/{command/start.ts → start.ts} +10 -5
  128. package/scripts/cli/{support/startup-signal.ts → startup-signal.ts} +23 -23
  129. package/scripts/cli/tray/index.ts +62 -12
  130. package/scripts/cli/{command/tray.ts → tray.ts} +208 -208
  131. package/scripts/cli/{command/update.ts → update.ts} +39 -39
  132. package/scripts/cli/version.ts +67 -0
  133. package/scripts/cli/{ui/wordmark.ts → wordmark.ts} +58 -58
  134. package/src/boot/app.ts +697 -693
  135. package/src/boot/dependencies.ts +647 -646
  136. package/src/boot/timeout.ts +2 -13
  137. package/src/config/cooldowns.ts +186 -138
  138. package/src/config/network.ts +8 -14
  139. package/src/config/providers.ts +7 -10
  140. package/src/config/runtime.ts +76 -76
  141. package/src/config/security.ts +15 -0
  142. package/src/config/spec.ts +20 -30
  143. package/src/config/telemetry.ts +0 -20
  144. package/src/config/timeouts.ts +0 -6
  145. package/src/console/admin/model-abuse/contracts.ts +28 -6
  146. package/src/console/admin/model-abuse/routes.ts +56 -3
  147. package/src/console/api-keys/contracts.ts +410 -414
  148. package/src/console/api-keys/routes.ts +881 -880
  149. package/src/console/auth/credentials.ts +3 -3
  150. package/src/console/auth/setup-service.ts +2 -2
  151. package/src/console/backup/export-endpoint.ts +6 -2
  152. package/src/console/backup/nine-router.ts +2 -5
  153. package/src/console/backup/service.ts +4 -0
  154. package/src/console/backup/store.ts +192 -29
  155. package/src/console/backup/validate.ts +3 -45
  156. package/src/console/cli-tools/fs-ops.ts +487 -487
  157. package/src/console/console-router-guards.ts +1 -1
  158. package/src/console/data-management/contracts.ts +13 -6
  159. package/src/console/data-management/operations.ts +23 -18
  160. package/src/console/data-management/routes.ts +0 -1
  161. package/src/console/domain-registration.ts +10 -5
  162. package/src/console/observability/live.ts +22 -0
  163. package/src/console/observability/store.ts +39 -16
  164. package/src/console/observability/usage-dimensions.ts +4 -4
  165. package/src/console/providers/catalog/catalog-projections.ts +6 -7
  166. package/src/console/providers/catalog/model-operations.ts +5 -2
  167. package/src/console/providers/catalog/provider-operations.ts +4 -4
  168. package/src/console/providers/catalog/public-model-store.ts +8 -11
  169. package/src/console/providers/catalog/store.ts +1553 -1553
  170. package/src/console/providers/oauth/routes.ts +8 -12
  171. package/src/console/quota/account-quota-tenant.ts +6 -6
  172. package/src/console/quota/quota-view.ts +1 -1
  173. package/src/console/remote-support/contracts.ts +61 -0
  174. package/src/console/remote-support/routes.ts +65 -0
  175. package/src/console/remote-support/service.ts +207 -0
  176. package/src/console/response-compression.ts +0 -3
  177. package/src/console/routing/pools/probe-result.ts +3 -2
  178. package/src/console/routing/pools/probe-verdict.ts +132 -132
  179. package/src/console/routing/pools/store.ts +8 -8
  180. package/src/console/settings/contracts.ts +58 -2
  181. package/src/console/settings/store.ts +30 -3
  182. package/src/console/shared/cursor.ts +8 -8
  183. package/src/console/shared/errors.ts +1 -3
  184. package/src/network/http2-bidi.ts +2 -4
  185. package/src/network/pool/agent.ts +478 -478
  186. package/src/network/pool/selector.ts +5 -4
  187. package/src/network/retry.ts +1 -2
  188. package/src/network/ssrf.ts +5 -4
  189. package/src/observability/event-compaction.ts +80 -78
  190. package/src/observability/event-day-index.ts +13 -15
  191. package/src/observability/event-log.ts +4 -4
  192. package/src/observability/logger.ts +6 -6
  193. package/src/observability/payload-capture.ts +151 -33
  194. package/src/observability/payload-store.ts +397 -440
  195. package/src/observability/redaction.ts +5 -6
  196. package/src/observability/request-notify.ts +43 -0
  197. package/src/observability/telemetry-status.ts +8 -9
  198. package/src/observability/token-speed.ts +5 -4
  199. package/src/persistence/db-mode.ts +7 -7
  200. package/src/persistence/readiness.ts +1 -1
  201. package/src/persistence/schema.ts +1406 -1402
  202. package/src/persistence/shared-cache.ts +1 -2
  203. package/src/persistence/stores/api-key-store.ts +18 -6
  204. package/src/persistence/stores/share-store.ts +4 -3
  205. package/src/persistence/stores/telemetry-store.ts +236 -237
  206. package/src/protocol/canonical-model.ts +11 -3
  207. package/src/protocol/codecs.ts +138 -4
  208. package/src/protocol/gateway-error.ts +6 -4
  209. package/src/protocol/request/chat.ts +21 -1
  210. package/src/protocol/request/codex.ts +10 -1
  211. package/src/protocol/request/messages.ts +4 -5
  212. package/src/protocol/request/responses.ts +9 -1
  213. package/src/protocol/response/chat.ts +34 -4
  214. package/src/protocol/response/codex.ts +7 -4
  215. package/src/protocol/response/dsml.ts +140 -28
  216. package/src/protocol/response/responses.ts +11 -5
  217. package/src/protocol/stream-error-frames.ts +4 -5
  218. package/src/providers/authentication/jwt-validator.ts +9 -10
  219. package/src/providers/authentication/oauth-refresh-service.ts +9 -11
  220. package/src/providers/configured-openai-providers.ts +134 -136
  221. package/src/providers/discovery/catalog/catalog-blob.ts +18 -18
  222. package/src/providers/discovery/catalog/catalog-format.ts +3 -6
  223. package/src/providers/discovery/models-dev-catalog.ts +354 -354
  224. package/src/providers/discovery/probe-phases.ts +4 -4
  225. package/src/providers/discovery/probe-wire.ts +6 -13
  226. package/src/providers/discovery/probing-service.ts +1117 -1045
  227. package/src/providers/integrations/alibabacp/alibabacp.ts +1 -1
  228. package/src/providers/integrations/antigravity/antigravity-quota.ts +5 -7
  229. package/src/providers/integrations/buddy/buddy-chat-shared.ts +23 -2
  230. package/src/providers/integrations/buddy/buddy-checkin.ts +1 -4
  231. package/src/providers/integrations/buddy/buddy-cli-headers.ts +23 -6
  232. package/src/providers/integrations/buddy/buddy-conversation-headers.ts +0 -1
  233. package/src/providers/integrations/buddy/buddy-identity.ts +52 -0
  234. package/src/providers/integrations/buddy/buddy-media.ts +2 -2
  235. package/src/providers/integrations/buddy/buddy-usage-store.ts +3 -4
  236. package/src/providers/integrations/buddy/codebuddy/quota.ts +2 -13
  237. package/src/providers/integrations/buddy/codebuddy/shared.ts +118 -115
  238. package/src/providers/integrations/buddy/workbuddy/quota.ts +0 -11
  239. package/src/providers/integrations/buddy/workbuddy/shared.ts +5 -2
  240. package/src/providers/integrations/claude/claude.ts +608 -608
  241. package/src/providers/integrations/cline/cline-quota.ts +132 -132
  242. package/src/providers/integrations/cline/cline.ts +537 -539
  243. package/src/providers/integrations/codex/codex-oauth.ts +1 -1
  244. package/src/providers/integrations/commandcode/commandcode.ts +674 -675
  245. package/src/providers/integrations/commandcode/discovery.ts +117 -116
  246. package/src/providers/integrations/cursor/cursor-auth.ts +418 -418
  247. package/src/providers/integrations/cursor/cursor-quota.ts +309 -310
  248. package/src/providers/integrations/cursor/cursor.ts +11 -163
  249. package/src/providers/integrations/factory/factory-models.ts +3 -5
  250. package/src/providers/integrations/grok/grok-install-id.ts +2 -3
  251. package/src/providers/integrations/grok/grok.ts +6 -7
  252. package/src/providers/integrations/hermes.ts +14 -18
  253. package/src/providers/integrations/kiro/kiro-discovery.ts +1 -1
  254. package/src/providers/integrations/kiro/kiro.ts +1 -1
  255. package/src/providers/integrations/mistral.ts +6 -6
  256. package/src/providers/integrations/opencode/fingerprint.ts +3 -3
  257. package/src/providers/integrations/opencode/provider.ts +8 -8
  258. package/src/providers/integrations/qoder/qoder-catalog.ts +450 -450
  259. package/src/providers/integrations/qoder/qoder-oauth.ts +388 -310
  260. package/src/providers/integrations/qoder/qoder.ts +884 -865
  261. package/src/providers/integrations/shared/connect.ts +6 -6
  262. package/src/providers/integrations/trae/trae-catalog.ts +1 -2
  263. package/src/providers/integrations/trae/trae.ts +2 -2
  264. package/src/providers/integrations/zcode/oauth.ts +6 -1
  265. package/src/providers/operations/account-health-service.ts +1528 -1523
  266. package/src/providers/operations/account-reset-service.ts +1 -1
  267. package/src/providers/operations/client-versions.ts +6 -6
  268. package/src/providers/operations/custom-cli-headers.ts +3 -4
  269. package/src/providers/operations/provider-catalog-seeder.ts +95 -95
  270. package/src/providers/operations/provider-catalog-service.ts +3 -4
  271. package/src/providers/provider-metadata.ts +4 -8
  272. package/src/providers/provider-registry.ts +6 -7
  273. package/src/providers/provider-usage.ts +1 -1
  274. package/src/security/abuse.ts +15 -10
  275. package/src/security/access-control.ts +144 -146
  276. package/src/security/admission/contracts.ts +11 -0
  277. package/src/security/admission/in-memory-store.ts +4 -3
  278. package/src/security/admission/lease-sweep.ts +6 -6
  279. package/src/security/admission/reasons.ts +5 -1
  280. package/src/security/admission/redis-store.ts +211 -210
  281. package/src/security/admission/service.ts +4 -0
  282. package/src/security/api-key-auth.ts +504 -504
  283. package/src/security/ip-boundary.ts +155 -15
  284. package/src/security/model-abuse.ts +366 -94
  285. package/src/security/outbound-headers.ts +3 -4
  286. package/src/shared/sleep.ts +4 -0
  287. package/src/transport/dispatch/attempt-finalize.ts +47 -5
  288. package/src/transport/dispatch/attempt-loop.ts +531 -530
  289. package/src/transport/dispatch/stream-bridge.ts +1 -3
  290. package/src/transport/dispatch/streaming/finalize.ts +1 -1
  291. package/src/transport/dispatch/streaming/stream.ts +5 -6
  292. package/src/transport/failure-policy.ts +162 -8
  293. package/src/transport/guards/auth-guard.ts +7 -4
  294. package/src/transport/guards/concurrency-guard.ts +0 -5
  295. package/src/transport/guards/error-lifecycle.ts +13 -6
  296. package/src/transport/guards/pipeline.ts +15 -3
  297. package/src/transport/guards/request-context.ts +8 -2
  298. package/src/transport/request/inflight.ts +5 -6
  299. package/src/transport/request/preparer.ts +2 -2
  300. package/src/transport/request/state.ts +12 -8
  301. package/src/transport/routing/admission.ts +4 -0
  302. package/src/transport/routing/route-catalog.ts +8 -1
  303. package/src/transport/routing/route-model.ts +46 -14
  304. package/src/transport/routing/router.ts +21 -14
  305. package/src/transport/surface/completion.ts +2 -3
  306. package/src/transport/surface/dialects.ts +5 -6
  307. package/src/transport/surface/responses/encode.ts +38 -0
  308. package/src/transport/surface/responses/parse.ts +4 -0
  309. package/src/transport/translation/capabilities.ts +2 -3
  310. package/src/transport/translation/quirks.ts +3 -3
  311. package/src/workers/buddy-growth-tasks.ts +5 -10
  312. package/src/workers/daily-checkin.ts +17 -23
  313. package/src/workers/quota-refresh-sweep.ts +26 -26
  314. package/dist/dashboard/assets/BannedPage-C2orBsI2.js.gz +0 -0
  315. package/dist/dashboard/assets/CliToolDetailPage-DUcZ6pMB.js +0 -1
  316. package/dist/dashboard/assets/CliToolDetailPage-DUcZ6pMB.js.gz +0 -0
  317. package/dist/dashboard/assets/CliToolsPage-BV6pxVQo.js.gz +0 -0
  318. package/dist/dashboard/assets/CombosPage-Bsan4679.js +0 -1
  319. package/dist/dashboard/assets/CombosPage-Bsan4679.js.gz +0 -0
  320. package/dist/dashboard/assets/ConsoleLogPage-CW-wPmJ6.js.gz +0 -0
  321. package/dist/dashboard/assets/CustomizationPage-0p8uX7vB.js.gz +0 -0
  322. package/dist/dashboard/assets/HealthEventsModal-BjqSMfko.js.gz +0 -0
  323. package/dist/dashboard/assets/LoginPage-BvzCx8Kr.js.gz +0 -0
  324. package/dist/dashboard/assets/ModelPicker-BY1aKEkP.js +0 -1
  325. package/dist/dashboard/assets/ModelPicker-BY1aKEkP.js.gz +0 -0
  326. package/dist/dashboard/assets/OverviewPage-DOQpy1y0.js +0 -1
  327. package/dist/dashboard/assets/OverviewPage-DOQpy1y0.js.gz +0 -0
  328. package/dist/dashboard/assets/ProviderDetailPage-ecxyUMzt.js +0 -3
  329. package/dist/dashboard/assets/ProviderDetailPage-ecxyUMzt.js.gz +0 -0
  330. package/dist/dashboard/assets/ProvidersPage-Js9jUPSb.js.gz +0 -0
  331. package/dist/dashboard/assets/ProxyPage-J3TBi3Kx.js +0 -7
  332. package/dist/dashboard/assets/ProxyPage-J3TBi3Kx.js.gz +0 -0
  333. package/dist/dashboard/assets/QuotaPage-C1gZS6xr.js +0 -1
  334. package/dist/dashboard/assets/QuotaPage-C1gZS6xr.js.gz +0 -0
  335. package/dist/dashboard/assets/SettingsPage-CS86ho5X.js +0 -1
  336. package/dist/dashboard/assets/SettingsPage-CS86ho5X.js.gz +0 -0
  337. package/dist/dashboard/assets/SetupPage-DDbMQsoo.js.gz +0 -0
  338. package/dist/dashboard/assets/SortableList-CB3Z7VTf.js.gz +0 -0
  339. package/dist/dashboard/assets/StudioPage-D95UCPHI.js.gz +0 -0
  340. package/dist/dashboard/assets/UsagePage-Cnae9SWA.js +0 -56
  341. package/dist/dashboard/assets/UsagePage-Cnae9SWA.js.gz +0 -0
  342. package/dist/dashboard/assets/api-keys-VY68Mhd1.js +0 -1
  343. package/dist/dashboard/assets/api-keys-VY68Mhd1.js.gz +0 -0
  344. package/dist/dashboard/assets/app-COlK95oi.css +0 -2
  345. package/dist/dashboard/assets/app-COlK95oi.css.gz +0 -0
  346. package/dist/dashboard/assets/assert-Chi7pCnt.js.gz +0 -0
  347. package/dist/dashboard/assets/badge-D4pQapKj.js.gz +0 -0
  348. package/dist/dashboard/assets/banner-DH6cg-_B.js.gz +0 -0
  349. package/dist/dashboard/assets/card-BKqlUOe4.js.gz +0 -0
  350. package/dist/dashboard/assets/cli-tools-fTgljTGA.js.gz +0 -0
  351. package/dist/dashboard/assets/clipboard-button-D5Ys_sM0.js.gz +0 -0
  352. package/dist/dashboard/assets/dialog-BYj-8WuK.js +0 -1
  353. package/dist/dashboard/assets/dialog-BYj-8WuK.js.gz +0 -0
  354. package/dist/dashboard/assets/drawer-CmXIZ6H1.js +0 -1
  355. package/dist/dashboard/assets/drawer-CmXIZ6H1.js.gz +0 -0
  356. package/dist/dashboard/assets/entry-CBg5zeu8.js +0 -1
  357. package/dist/dashboard/assets/entry-CBg5zeu8.js.gz +0 -0
  358. package/dist/dashboard/assets/entry-CQBr6U9c.js.gz +0 -0
  359. package/dist/dashboard/assets/entry-CgdSAzEV.js +0 -4
  360. package/dist/dashboard/assets/entry-CgdSAzEV.js.gz +0 -0
  361. package/dist/dashboard/assets/fingerprint-pattern-BWuHtbW4.js.gz +0 -0
  362. package/dist/dashboard/assets/github-badge-BlptL8Uf.js.gz +0 -0
  363. package/dist/dashboard/assets/index-js6TJvdr.js.gz +0 -0
  364. package/dist/dashboard/assets/inline-DUWDSmDV.js.gz +0 -0
  365. package/dist/dashboard/assets/input-LVjgZq4f.js.gz +0 -0
  366. package/dist/dashboard/assets/layout-CPfxpsfy.js +0 -1
  367. package/dist/dashboard/assets/layout-CPfxpsfy.js.gz +0 -0
  368. package/dist/dashboard/assets/list-row-DupvLfDx.js.gz +0 -0
  369. package/dist/dashboard/assets/live-C4pEJUqI.js.gz +0 -0
  370. package/dist/dashboard/assets/model-bans-BRiuufkB.js.gz +0 -0
  371. package/dist/dashboard/assets/mutation-B4ROwk8L.js.gz +0 -0
  372. package/dist/dashboard/assets/numbers-YNcHvRY7.js +0 -2
  373. package/dist/dashboard/assets/numbers-YNcHvRY7.js.gz +0 -0
  374. package/dist/dashboard/assets/query-keys-gmjzC3RS.js.gz +0 -0
  375. package/dist/dashboard/assets/routing-C7tBT5o-.js.gz +0 -0
  376. package/dist/dashboard/assets/select-WUCXorXw.js.gz +0 -0
  377. package/dist/dashboard/assets/settings-C_zKl1GS.js.gz +0 -0
  378. package/dist/dashboard/assets/stack-_3O0F8Xn.js.gz +0 -0
  379. package/dist/dashboard/assets/state-BgTxHcJS.js.gz +0 -0
  380. package/dist/dashboard/assets/switch-DEjTtOWf.js.gz +0 -0
  381. package/dist/dashboard/assets/tokens-DDxMbSRL.js +0 -1
  382. package/dist/dashboard/assets/tokens-DDxMbSRL.js.gz +0 -0
  383. package/dist/dashboard/assets/use-presence-BSZEBx7U.js +0 -1
  384. package/dist/dashboard/assets/use-presence-BSZEBx7U.js.gz +0 -0
  385. package/dist/dashboard/assets/useMutation-BWSC0UAS.js.gz +0 -0
  386. package/scripts/cli/command/admin.ts +0 -65
  387. package/scripts/cli/command/doctor.ts +0 -257
  388. package/scripts/cli/gateway/drain-token.ts +0 -78
  389. package/scripts/cli/gateway/instance-lock.ts +0 -105
  390. package/scripts/cli/gateway/stop.ts +0 -51
  391. package/scripts/cli/support/bun.ts +0 -27
  392. package/scripts/cli/support/version.ts +0 -37
  393. /package/dist/dashboard/assets/{app-DCQZRmHY.js → app-BIt446yE.js} +0 -0
  394. /package/dist/dashboard/assets/{app-DCQZRmHY.js.gz → app-BIt446yE.js.gz} +0 -0
  395. /package/scripts/cli/{ui/prompt.ts → prompt.ts} +0 -0
@@ -14,7 +14,7 @@ import { parseCookieValue, SESSION_COOKIE_NAME, isCsrfValid } from "../security/
14
14
  /**
15
15
  * Stateless double-submit CSRF guard for unsafe `/console/api/*` mutations.
16
16
  * Compares the `x-csrf-token` header against the readable `csrf_token`
17
- * cookie — zero database I/O, so dashboard writes no longer pay a token
17
+ * cookie — zero database I/O, so dashboard writes pay no token
18
18
  * SELECT + UPDATE on top of the session lookup, and there is no token
19
19
  * store whose outage could masquerade as an auth failure.
20
20
  */
@@ -4,9 +4,9 @@
4
4
  *
5
5
  * Everything here is files on disk, except one deliberate row-level action.
6
6
  * Reclaiming a file is safe because the data it held is already past the point
7
- * where the gateway would read it: payload frames expire 15 minutes after they
8
- * are written, and telemetry files past the retention window are outside every
9
- * query's scan bound. Deleting rows is a different decision with a different
7
+ * where the gateway would read it: payload frames expire after the tenant's
8
+ * chosen retention, and telemetry files past the retention window are outside
9
+ * every query's scan bound. Deleting rows is a different decision with a different
10
10
  * blast radius, and the only row data the console offers purging is the health
11
11
  * & error log — purely an operator's diagnostic trail with no request-path
12
12
  * reader, unlike everything else in this panel.
@@ -19,7 +19,7 @@
19
19
  /** One measured category of on-disk data. */
20
20
  export interface DiskUsageBucket {
21
21
  /** Stable id, so the dashboard can key rows without matching on copy. */
22
- readonly id: "telemetry_hourly" | "telemetry_compacted" | "payload_frames";
22
+ readonly id: "telemetry_events" | "telemetry_index" | "payload_frames";
23
23
  readonly label: string;
24
24
  readonly bytes: number;
25
25
  readonly files: number;
@@ -35,8 +35,15 @@ export interface DiskUsageResponse {
35
35
  readonly totalBytes: number;
36
36
  /** Retention in days; telemetry older than this is outside every scan. */
37
37
  readonly retentionDays: number;
38
- /** Payload frames live this long before they are reclaimable. */
38
+ /** Payload frames live this long by default before they are reclaimable. */
39
39
  readonly payloadRetentionMs: number;
40
+ /**
41
+ * The longest retention a tenant may have chosen. A frame is only counted as
42
+ * reclaimable once it is older than *this*, because a tenant who raised the
43
+ * window has frames the default would call dead while their rows still
44
+ * address them.
45
+ */
46
+ readonly payloadRetentionMaxMs: number;
40
47
  /**
41
48
  * Stored health & error rows, for the purge row's label. Absent when the
42
49
  * composition does not wire the health-event store (a reduced console),
@@ -45,7 +52,7 @@ export interface DiskUsageResponse {
45
52
  readonly healthEventCount?: number;
46
53
  }
47
54
 
48
- export type ReclaimAction = "payload_frames" | "telemetry_retention" | "telemetry_hourly";
55
+ export type ReclaimAction = "payload_frames" | "telemetry_retention";
49
56
 
50
57
  export interface ReclaimResponse {
51
58
  readonly action: ReclaimAction;
@@ -35,11 +35,12 @@ export interface DataManagementConfig {
35
35
  readonly retentionDays: number;
36
36
  readonly payloadRetentionMs: number;
37
37
  /**
38
- * Whether hourly files may be dropped from the console. Off unless a caller
39
- * opts in: an hourly file still inside the compaction window is the only
40
- * copy of its requests, so removing one loses real history.
38
+ * The longest payload retention a tenant may have chosen, and therefore the
39
+ * only cutoff a frame file can be judged dead against without reading it.
40
+ * Defaults to `payloadRetentionMs` for callers that predate per-tenant
41
+ * retention, which is then the true ceiling in that deployment.
41
42
  */
42
- readonly allowHourlyReclaim: boolean;
43
+ readonly payloadRetentionMaxMs?: number;
43
44
  /**
44
45
  * The health & error log store, backing the console's only row-level purge.
45
46
  * Absent (a reduced console composition) means the purge is not offered
@@ -79,9 +80,8 @@ async function scan(
79
80
 
80
81
  const isIndex = (name: string): boolean => name.endsWith(".index.json");
81
82
  const isHourly = (name: string): boolean => name.endsWith(".events.jsonl");
82
- // Both extensions: old `.jsonb` files are still on disk and still counted.
83
83
  const isFrame = (name: string): boolean =>
84
- name.endsWith(".jsonl") || name.endsWith(".jsonb");
84
+ name.endsWith(".jsonl");
85
85
 
86
86
  /** The UTC day a telemetry file name starts with, compared to a cutoff. */
87
87
  function dayBefore(name: string, cutoff: number): boolean {
@@ -91,10 +91,19 @@ function dayBefore(name: string, cutoff: number): boolean {
91
91
 
92
92
  export function createDataManagementOperations(config: DataManagementConfig) {
93
93
  const payloadDir = (): string => payloadDirectory();
94
+ /**
95
+ * The only cutoff a frame file can be judged dead against without parsing it.
96
+ *
97
+ * A file's mtime says when it was last written, not what its frames' tenants
98
+ * chose — so a file written an hour ago can hold a frame with a week to live.
99
+ * The ceiling is therefore the bound, and the default retention is only used
100
+ * when no ceiling was configured.
101
+ */
102
+ const payloadMax = (): number => config.payloadRetentionMaxMs ?? config.payloadRetentionMs;
94
103
 
95
104
  async function usage(): Promise<DiskUsageResponse> {
96
105
  const retentionCutoff = Date.now() - config.retentionDays * 24 * 60 * 60_000;
97
- const payloadCutoff = Date.now() - config.payloadRetentionMs;
106
+ const payloadCutoff = Date.now() - payloadMax();
98
107
 
99
108
  const [hourly, compacted, staleCompacted, frames, staleFrames, healthEventCount] = await Promise.all([
100
109
  scan(config.telemetryDir, isHourly),
@@ -107,8 +116,8 @@ export function createDataManagementOperations(config: DataManagementConfig) {
107
116
 
108
117
  const buckets: DiskUsageBucket[] = [
109
118
  {
110
- id: "telemetry_hourly",
111
- label: "Daily request files",
119
+ id: "telemetry_events",
120
+ label: "Request event files",
112
121
  bytes: hourly.bytes,
113
122
  files: hourly.files,
114
123
  note: "Feeds the request list and detail drawer.",
@@ -116,7 +125,7 @@ export function createDataManagementOperations(config: DataManagementConfig) {
116
125
  reclaimableFiles: null,
117
126
  },
118
127
  {
119
- id: "telemetry_compacted",
128
+ id: "telemetry_index",
120
129
  label: "Day indexes",
121
130
  bytes: compacted.bytes,
122
131
  files: compacted.files,
@@ -131,7 +140,7 @@ export function createDataManagementOperations(config: DataManagementConfig) {
131
140
  files: frames.files,
132
141
  note: `Captured bodies, expiring ${Math.round(
133
142
  config.payloadRetentionMs / 60_000,
134
- )} min after write.`,
143
+ )} min after write by default.`,
135
144
  reclaimableBytes: staleFrames.bytes,
136
145
  reclaimableFiles: staleFrames.files,
137
146
  },
@@ -142,6 +151,7 @@ export function createDataManagementOperations(config: DataManagementConfig) {
142
151
  totalBytes: buckets.reduce((sum, bucket) => sum + bucket.bytes, 0),
143
152
  retentionDays: config.retentionDays,
144
153
  payloadRetentionMs: config.payloadRetentionMs,
154
+ payloadRetentionMaxMs: payloadMax(),
145
155
  ...(healthEventCount === undefined ? {} : { healthEventCount }),
146
156
  };
147
157
  }
@@ -160,7 +170,7 @@ export function createDataManagementOperations(config: DataManagementConfig) {
160
170
  const now = new Date();
161
171
  const deletedFiles = await prunePayloadFrames(
162
172
  now,
163
- new Date(now.getTime() - config.payloadRetentionMs),
173
+ new Date(now.getTime() - payloadMax()),
164
174
  );
165
175
  const after = await scan(payloadDir(), isFrame);
166
176
  return {
@@ -178,12 +188,7 @@ export function createDataManagementOperations(config: DataManagementConfig) {
178
188
  (isIndex(name) || isHourly(name)) && dayBefore(name, cutoff),
179
189
  );
180
190
  }
181
- if (!config.allowHourlyReclaim) {
182
- throw new ReclaimNotAllowedError(
183
- "Hourly files are the only copy of their requests until compaction folds them up, so the console will not delete them.",
184
- );
185
- }
186
- return unlinkWhere(action, config.telemetryDir, isHourly);
191
+ throw new ReclaimNotAllowedError(`Unknown reclaim action: ${action}`);
187
192
  }
188
193
 
189
194
  /** The one row-level deletion the console offers. */
@@ -11,7 +11,6 @@ const reclaimBody = t.Object({
11
11
  action: t.Union([
12
12
  t.Literal("payload_frames"),
13
13
  t.Literal("telemetry_retention"),
14
- t.Literal("telemetry_hourly"),
15
14
  ]),
16
15
  });
17
16
 
@@ -36,11 +36,14 @@ import { createModelAbuseRoutes } from "./admin/model-abuse/routes";
36
36
  import { DrizzleAuditReadStore } from "./audit/store";
37
37
  import { createPerformanceRoutes } from "./observability/performance";
38
38
  import { createUpdateRoutes } from "./updates/routes";
39
+ import { createRemoteSupportRoutes } from "./remote-support/routes";
40
+ import { createRemoteSupportService } from "./remote-support/service";
41
+ import { resolvePort } from "../config/runtime";
39
42
  import { createBackupRoutes } from "./backup/routes";
40
43
  import { BackupService } from "./backup/service";
41
44
  import type { ConsoleCredentialService } from "./auth/credentials";
42
45
  import { resolveTelemetryRetentionDays } from "../config/telemetry";
43
- import { payloadRetentionMs } from "../observability/payload-capture";
46
+ import { MAX_PAYLOAD_RETENTION_MS, payloadRetentionMs } from "../observability/payload-capture";
44
47
  import { TELEMETRY_EVENT_DIR } from "../observability/event-log";
45
48
 
46
49
  import { InMemoryOAuthFlowStore, OAuthFlowStore } from "../providers/authentication/oauth-flow-store";
@@ -241,6 +244,10 @@ export function registerConsoleDomains(
241
244
  accessResolver: ctx.accessResolver,
242
245
  observabilityStore,
243
246
  }));
247
+ console.use(createRemoteSupportRoutes({
248
+ accessResolver: ctx.accessResolver,
249
+ service: createRemoteSupportService({ port: resolvePort() }),
250
+ }));
244
251
  console.use(createAuditRoutes({ store: auditReadStore, accessResolver: ctx.accessResolver }));
245
252
  if (ctx.modelStrikes) {
246
253
  console.use(createModelAbuseRoutes({
@@ -281,15 +288,13 @@ export function registerConsoleDomains(
281
288
  // same SSRF-validated fetch as every other egress.
282
289
  relayFetch: createValidatedFetch({ policy: resolveSsrfPolicy() }),
283
290
  }));
284
- console.use(createRuntimeSettingsRoutes({ store: runtimeSettingsStore, accessResolver: ctx.accessResolver, auditSink: ctx.auditRecorder }));
291
+ console.use(createRuntimeSettingsRoutes({ store: runtimeSettingsStore, accessResolver: ctx.accessResolver, auditSink: ctx.auditRecorder, snapshotInvalidator: ctx.routeSnapshotService }));
285
292
  console.use(
286
293
  createDataManagementRoutes({
287
294
  telemetryDir: TELEMETRY_EVENT_DIR,
288
295
  retentionDays: resolveTelemetryRetentionDays(),
289
296
  payloadRetentionMs: payloadRetentionMs(),
290
- // Hourly files are the only copy of their requests until compaction
291
- // folds them up, so the console does not delete them on request.
292
- allowHourlyReclaim: false,
297
+ payloadRetentionMaxMs: MAX_PAYLOAD_RETENTION_MS,
293
298
  accessResolver: ctx.accessResolver,
294
299
  auditSink: ctx.auditRecorder,
295
300
  flusher: ctx.flushBuffers,
@@ -32,6 +32,7 @@ import { networkPools } from "../../persistence/schema";
32
32
  import { subscribePoolHealth } from "../../network/pool-health-machine";
33
33
  import { poolByteSnapshot } from "../../network/pool/byte-accounting";
34
34
  import { consoleSseResponse, createConsoleSseStream } from "./sse";
35
+ import { subscribeRequestCompleted } from "../../observability/request-notify";
35
36
 
36
37
  export interface LiveConfig {
37
38
  readonly accessResolver: ConsoleAccessResolver;
@@ -87,6 +88,27 @@ function withPoolBytes<T extends { poolId: string; currentInflight?: number }>(
87
88
 
88
89
  export function createLiveRoutes(config: LiveConfig): Elysia {
89
90
  return new Elysia()
91
+ .get("/live/requests/stream", ({ request, set }) => {
92
+ try {
93
+ const access = config.accessResolver(request);
94
+ requireScope(access, "dashboard:read");
95
+ const tenantId = access?.tenantId;
96
+ if (!tenantId) return errorResponse(new Error("Tenant required"), set, "Live operation failed");
97
+ set.headers["content-type"] = "text/event-stream";
98
+ set.headers["cache-control"] = "no-cache, no-transform";
99
+ set.headers["connection"] = "keep-alive";
100
+ set.headers["x-accel-buffering"] = "no";
101
+ return consoleSseResponse(
102
+ createConsoleSseStream(request.signal, ({ send }) => {
103
+ return subscribeRequestCompleted((notice) => {
104
+ if (notice.tenantId === tenantId) send("request_completed", notice);
105
+ });
106
+ }),
107
+ );
108
+ } catch (e) {
109
+ return errorResponse(e, set, "Live operation failed");
110
+ }
111
+ })
90
112
  .get("/live/in-flight", ({ request, set }) => {
91
113
  try {
92
114
  requireScope(config.accessResolver(request), "dashboard:read");
@@ -11,6 +11,7 @@ import { isGatewayError } from "../../observability/telemetry-status";
11
11
  import { queryIndexedBuckets, type IndexedBucket } from "../../observability/event-day-index";
12
12
  import {
13
13
  LATENCY_BUCKETS_MS,
14
+ numeric,
14
15
  percentileFromHistogram,
15
16
  } from "../../observability/event-compaction";
16
17
  import { cachedReadWithEtag, type CachedResult } from "../shared/read-through-cache";
@@ -198,9 +199,9 @@ const HEALTH_WINDOW_MS = 24 * 3_600_000;
198
199
  const TOP_N = 50;
199
200
  /**
200
201
  * How long a parsed window is shared. A dashboard poll fires ~8 usage
201
- * endpoints at once, and a slow parse used to outlive a 1.5 s window — so the
202
- * endpoints that missed it each re-read and re-parsed the same files, which is
203
- * how one page load became several full parses of the retention window.
202
+ * endpoints at once, and a slow parse outlives a 1.5 s window — so the
203
+ * endpoints that miss it each re-read and re-parse the same files, which is how
204
+ * one page load becomes several full parses of the retention window.
204
205
  */
205
206
  const PERIOD_MEMO_TTL_MS = 5_000;
206
207
 
@@ -264,10 +265,20 @@ export function summarizeBucketPerformance(
264
265
  ): UsagePerformanceSummary {
265
266
  let latencySum = 0;
266
267
  let latencyCount = 0;
268
+ let resolveSum = 0;
269
+ let resolveCount = 0;
270
+ let latencyMaxMs: number | null = null;
267
271
  const histogram: number[] = [];
268
272
  for (const bucket of buckets) {
269
273
  latencySum += bucket.latencySum;
270
274
  latencyCount += bucket.latencyCount;
275
+ // Read defensively: an index written before these fields existed carries
276
+ // neither, and summing `undefined` poisons the whole period with NaN —
277
+ // including the days that do have the data.
278
+ resolveSum += numeric(bucket.resolveSumMs);
279
+ resolveCount += numeric(bucket.resolveCount);
280
+ if (bucket.latencyMaxMs !== null)
281
+ latencyMaxMs = latencyMaxMs === null ? bucket.latencyMaxMs : Math.max(latencyMaxMs, bucket.latencyMaxMs);
271
282
  for (let index = 0; index < bucket.latencyHistogram.length; index += 1) {
272
283
  histogram[index] = (histogram[index] ?? 0) + (bucket.latencyHistogram[index] ?? 0);
273
284
  }
@@ -277,13 +288,17 @@ export function summarizeBucketPerformance(
277
288
  ? histogram
278
289
  : new Array<number>(LATENCY_BUCKETS_MS.length + 1).fill(0);
279
290
  return {
280
- avgResolveMs: 0,
291
+ avgResolveMs: resolveCount > 0 ? resolveSum / resolveCount : 0,
281
292
  avgDurationMs: latencyCount > 0 ? latencySum / latencyCount : 0,
282
- latencyP50Ms: percentileFromHistogram(fill, 50) ?? 0,
283
- latencyP95Ms: percentileFromHistogram(fill, 95) ?? 0,
284
- latencyP99Ms: percentileFromHistogram(fill, 99) ?? 0,
293
+ // `latencyMaxMs` is passed as the overflow answer: a percentile past the
294
+ // histogram's last edge is at most the slowest request actually recorded.
295
+ // Without it an overflow returned `null`, and `?? 0` turned a P95 of 40 s
296
+ // into "0 ms" — the slow tail reported as nothing at all.
297
+ latencyP50Ms: percentileFromHistogram(fill, 50, latencyMaxMs) ?? 0,
298
+ latencyP95Ms: percentileFromHistogram(fill, 95, latencyMaxMs) ?? 0,
299
+ latencyP99Ms: percentileFromHistogram(fill, 99, latencyMaxMs) ?? 0,
285
300
  latencySampleCount: latencyCount,
286
- resolveSampleCount: 0,
301
+ resolveSampleCount: resolveCount,
287
302
  };
288
303
  }
289
304
 
@@ -340,9 +355,8 @@ export class DrizzleObservabilityStore implements ObservabilityStore {
340
355
  ) {}
341
356
 
342
357
  async health(tenantId: string): Promise<CachedResult<SystemHealthResponse>> {
343
- // The only usage-family read that used to bypass both the 5 s in-process
344
- // memo and the Redis read-through, so it re-parsed 24 hourly files every
345
- // page load. Same 10 s TTL as `/system/usage`.
358
+ // Cached like its siblings: reading this directly re-parses 24 hourly files
359
+ // every page load. Same 10 s TTL as `/system/usage`.
346
360
  return this.readThroughWithEtag(
347
361
  this.usageCacheKey(tenantId, "24h", "health"),
348
362
  () => this.computeHealth(tenantId),
@@ -424,8 +438,8 @@ export class DrizzleObservabilityStore implements ObservabilityStore {
424
438
  p99 = percentile(latencies, 0.99);
425
439
  // Cached share of the prompt: cached/input, the same honest definition
426
440
  // usageSummary uses, so the health card and the Usage page cannot
427
- // disagree. (This used to be prompt-share input/(input+output), which
428
- // read ~99% and was mislabeled a cache hit rate.)
441
+ // disagree. (Cached share, not prompt share: input/(input+output) reads
442
+ // ~99% and mislabels it a cache hit rate.)
429
443
  cacheHitRate = inputTokens > 0 ? Math.round((cachedTokens / inputTokens) * 10000) / 100 : 0;
430
444
  avgTokensPerSec = tokensPerSecCount > 0 ? tokensPerSecSum / tokensPerSecCount : 0;
431
445
  } catch {
@@ -596,6 +610,15 @@ export class DrizzleObservabilityStore implements ObservabilityStore {
596
610
  let tokensPerSecCount = 0;
597
611
  const statuses = new Map<number, number>();
598
612
  for (const row of buckets) {
613
+ // Folded from the index's own per-status counts. These used to be left
614
+ // empty: the loop below never filled `statuses`, so every status chip and
615
+ // the requests-list filters read zero for any day whose events had been
616
+ // folded — while `requests` beside them read the real total.
617
+ for (const [status, count] of Object.entries(row.byStatus ?? {})) {
618
+ const code = Number(status);
619
+ if (!Number.isFinite(code)) continue;
620
+ statuses.set(code, (statuses.get(code) ?? 0) + count);
621
+ }
599
622
  requests += row.requests;
600
623
  inputTokens += row.inputTokens;
601
624
  outputTokens += row.outputTokens;
@@ -983,9 +1006,9 @@ export class DrizzleObservabilityStore implements ObservabilityStore {
983
1006
  ): Promise<TelemetryEventListPage> {
984
1007
  const before = decodeDatedCursor(cursor);
985
1008
  // The cursor becomes an `until` bound inside the query, so `limit` applies
986
- // to every page and not only the first. Filtering after the read forced the
987
- // previous shape: a cursor page had to fetch the whole window to know where
988
- // it ended, so `limit` was dropped the moment a cursor appeared.
1009
+ // to every page and not only the first. Filtering after the read would force
1010
+ // a cursor page to fetch the whole window to know where it ended, dropping
1011
+ // `limit` the moment a cursor appears.
989
1012
  //
990
1013
  // `until` is inclusive while the cursor is exclusive, so rows at the exact
991
1014
  // cursor timestamp are still compared by id here — the cheap part of the
@@ -7,11 +7,11 @@
7
7
  * dashboard offers one breakdown tab per member (kept in sync by hand).
8
8
  *
9
9
  * This module is intentionally free of Elysia / node:crypto so the dashboard
10
- * can re-export the tuple as a value. The dashboard previously reached it
11
- * through `./contracts`, which imports Elysia and — through
10
+ * can re-export the tuple as a value. Reaching it through `./contracts` would
11
+ * import Elysia and — through
12
12
  * `console/shared/errors` → `protocol/codecs` → `security/outbound-headers`
13
- * — `node:crypto`, so the browser bundle externalized a Node builtin and
14
- * carried a slice of the backend graph for one string list.
13
+ * — `node:crypto`, so the browser bundle would externalize a Node builtin and
14
+ * carry a slice of the backend graph for one string list.
15
15
  */
16
16
  export const USAGE_DIMENSIONS = ["model", "provider", "key", "client", "client_ip"] as const;
17
17
 
@@ -46,13 +46,12 @@ export function endpointPathForProviderModel(
46
46
  // Operator profile > the provider's own static catalog > the generic
47
47
  // per-wire default.
48
48
  //
49
- // The path is returned as declared. It used to be rewritten against the
50
- // base URL, stripping a segment the base already carried so a versioned root
51
- // would not join into `/v1/v1/chat/completions`. That machinery hid the
52
- // mistake it was papering over: a provider whose base is already versioned
53
- // has to declare its own path, exactly as `cline` and `qianfan` do. A wrong
54
- // URL should reach the operator as the wrong URL, not be quietly corrected
55
- // into a different address that may or may not be the one they meant.
49
+ // The path is returned as declared, never rewritten against the base URL.
50
+ // Stripping a segment the base already carried would paper over the mistake
51
+ // instead of surfacing it: a provider whose base is already versioned has to
52
+ // declare its own path, exactly as `cline` and `qianfan` do. A wrong URL
53
+ // should reach the operator as the wrong URL, not be quietly corrected into
54
+ // a different address that may or may not be the one they meant.
56
55
  const endpointPath =
57
56
  profile?.endpoint_paths_by_wire_family?.[wireFamily] ??
58
57
  staticEndpoints?.[wireFamily] ??
@@ -60,9 +60,12 @@ export function createModelCatalogOperations(config: ProviderCatalogConfig) {
60
60
  },
61
61
  async listFlatModels(access: AccessDecision | undefined): Promise<readonly FlatModelCatalogEntry[]> {
62
62
  const a = requireTenantAnyScope(access, MODEL_READ_SCOPES);
63
- const providers = await config.store.list(a.tenantId);
63
+ const [providers, allAccounts] = await Promise.all([
64
+ config.store.list(a.tenantId),
65
+ config.store.listAllAccounts(a.tenantId),
66
+ ]);
64
67
  const accountsByProvider = new Map<string, number>();
65
- for (const account of await config.store.listAllAccounts(a.tenantId)) {
68
+ for (const account of allAccounts) {
66
69
  if (account.status === "active")
67
70
  accountsByProvider.set(account.providerId, (accountsByProvider.get(account.providerId) ?? 0) + 1);
68
71
  }
@@ -470,8 +470,8 @@ export function createProviderCatalogOperations(config: ProviderCatalogConfig) {
470
470
  * uses, so a batch cannot accept a shape the single create rejects. The
471
471
  * expensive per-account bookkeeping — the audit row and the routing snapshot
472
472
  * invalidation that forces a 10-table catalog rebuild — is done ONCE for the
473
- * whole batch instead of once per account, which is most of why the old
474
- * client-side loop scaled linearly with paste size.
473
+ * whole batch instead of once per account, so cost does not scale linearly
474
+ * with paste size the way a client-side loop over single creates would.
475
475
  *
476
476
  * Email/label enrichment is deliberately NOT done here. Persisting the
477
477
  * credential is local work (encrypt + hash + insert) and needs no upstream;
@@ -516,8 +516,8 @@ export function createProviderCatalogOperations(config: ProviderCatalogConfig) {
516
516
  if (created.account !== undefined) queueNewAccountCheckin(providerId, created.account);
517
517
  }
518
518
 
519
- // One audit row per successful account, but written after the batch lands
520
- // rather than inside the request path that used to await it per account.
519
+ // One audit row per batch, written after the batch lands rather than
520
+ // awaited per account inside the request path.
521
521
  if (result.created > 0) {
522
522
  await config.auditSink?.record({
523
523
  access: a,
@@ -359,11 +359,9 @@ export class PublicModelCatalogStore {
359
359
  /**
360
360
  * Lists the models this key may see.
361
361
  *
362
- * `modelPrefix` is accepted and currently has NO effect on the result. It
363
- * used to feed a `matchesModelPrefix(...) || isModelAllowed(...)` disjunct
364
- * that was immediately followed by `isModelAllowed(...)` on its own — so the
365
- * prefix could never add a row, and dropping it changes nothing. The
366
- * parameter is kept so the call sites and the auth shape stay stable; if a
362
+ * `modelPrefix` is accepted and has NO effect on the result — a prefix
363
+ * disjunct fed into `isModelAllowed(...)` on its own could never add a row.
364
+ * The parameter is kept so the call sites and the auth shape stay stable; if a
367
365
  * real prefix filter is wanted later it needs to be an AND, not the OR it
368
366
  * was written as.
369
367
  */
@@ -449,12 +447,11 @@ export class PublicModelCatalogStore {
449
447
  tenantId === null ? row.providerRequiresAccount === false : activeProviderIds.has(row.providerId);
450
448
  if (!listed) continue;
451
449
  const qualified = `${row.providerId}/${row.modelId}`;
452
- // The prefix test is deliberately gone from this path: the chain used to
453
- // be `(prefixMatch || isAllowed)` followed by `isAllowed`, which reduces
454
- // to `isAllowed` alone — the prefix disjunct could never change the
455
- // outcome, only cost a second full pass over the catalog. Collapsing the
456
- // four chained filters into one loop also drops the three intermediate
457
- // arrays they materialized.
450
+ // The prefix test is deliberately gone from this path: `(prefixMatch ||
451
+ // isAllowed)` followed by `isAllowed` reduces to `isAllowed` alone — the
452
+ // prefix disjunct could never change the outcome, only cost a second full
453
+ // pass over the catalog. Collapsing the four chained filters into one loop
454
+ // also drops the three intermediate arrays they materialized.
458
455
  if (!isModelAllowed(snapshot, row.modelId, row.providerId)) continue;
459
456
  if (shadowsAliasOrCombo(row, aliasTargets, comboNames, snapshot)) continue;
460
457
  const capabilities = normalizeModalities(row.modalities);