cartethyia 2.5.4 → 2.5.6

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-CADDq4IU.js} +1 -1
  4. package/dist/dashboard/assets/BannedPage-CADDq4IU.js.gz +0 -0
  5. package/dist/dashboard/assets/CliToolDetailPage-_z1hID7E.js +1 -0
  6. package/dist/dashboard/assets/CliToolDetailPage-_z1hID7E.js.gz +0 -0
  7. package/dist/dashboard/assets/{CliToolsPage-BV6pxVQo.js → CliToolsPage-DuO1Z7dq.js} +1 -1
  8. package/dist/dashboard/assets/CliToolsPage-DuO1Z7dq.js.gz +0 -0
  9. package/dist/dashboard/assets/CombosPage-DNEPi3xU.js +1 -0
  10. package/dist/dashboard/assets/CombosPage-DNEPi3xU.js.gz +0 -0
  11. package/dist/dashboard/assets/{ConsoleLogPage-CW-wPmJ6.js → ConsoleLogPage-BVdpfiz1.js} +1 -1
  12. package/dist/dashboard/assets/ConsoleLogPage-BVdpfiz1.js.gz +0 -0
  13. package/dist/dashboard/assets/{CustomizationPage-0p8uX7vB.js → CustomizationPage-BR5HHvbi.js} +1 -1
  14. package/dist/dashboard/assets/CustomizationPage-BR5HHvbi.js.gz +0 -0
  15. package/dist/dashboard/assets/{HealthEventsModal-BjqSMfko.js → HealthEventsModal-mUfcknqm.js} +1 -1
  16. package/dist/dashboard/assets/HealthEventsModal-mUfcknqm.js.gz +0 -0
  17. package/dist/dashboard/assets/{LoginPage-BvzCx8Kr.js → LoginPage-V5faV9V2.js} +1 -1
  18. package/dist/dashboard/assets/LoginPage-V5faV9V2.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-BDu20Jdx.js +1 -0
  22. package/dist/dashboard/assets/OverviewPage-BDu20Jdx.js.gz +0 -0
  23. package/dist/dashboard/assets/ProviderDetailPage-Dllr_aFO.js +3 -0
  24. package/dist/dashboard/assets/ProviderDetailPage-Dllr_aFO.js.gz +0 -0
  25. package/dist/dashboard/assets/{ProvidersPage-Js9jUPSb.js → ProvidersPage-uPiGVTTK.js} +1 -1
  26. package/dist/dashboard/assets/ProvidersPage-uPiGVTTK.js.gz +0 -0
  27. package/dist/dashboard/assets/ProxyPage-BaUkO-4O.js +7 -0
  28. package/dist/dashboard/assets/ProxyPage-BaUkO-4O.js.gz +0 -0
  29. package/dist/dashboard/assets/QuotaPage-o0IFAsFb.js +1 -0
  30. package/dist/dashboard/assets/QuotaPage-o0IFAsFb.js.gz +0 -0
  31. package/dist/dashboard/assets/SettingsPage-CYWqbXjp.js +1 -0
  32. package/dist/dashboard/assets/SettingsPage-CYWqbXjp.js.gz +0 -0
  33. package/dist/dashboard/assets/{SetupPage-DDbMQsoo.js → SetupPage-UB690SE2.js} +1 -1
  34. package/dist/dashboard/assets/SetupPage-UB690SE2.js.gz +0 -0
  35. package/dist/dashboard/assets/{SortableList-CB3Z7VTf.js → SortableList-BLujdX7N.js} +1 -1
  36. package/dist/dashboard/assets/SortableList-BLujdX7N.js.gz +0 -0
  37. package/dist/dashboard/assets/{StudioPage-D95UCPHI.js → StudioPage-D5C2raAc.js} +3 -3
  38. package/dist/dashboard/assets/StudioPage-D5C2raAc.js.gz +0 -0
  39. package/dist/dashboard/assets/UsagePage-8uIcxa1W.js +56 -0
  40. package/dist/dashboard/assets/UsagePage-8uIcxa1W.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-CTP8AyiS.js +1 -0
  66. package/dist/dashboard/assets/entry-CTP8AyiS.js.gz +0 -0
  67. package/dist/dashboard/assets/entry-DxBHnauy.js +4 -0
  68. package/dist/dashboard/assets/entry-DxBHnauy.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-CmxZr14F.js} +2 -2
  72. package/dist/dashboard/assets/index-CmxZr14F.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-BYR_5K2c.js} +1 -1
  94. package/dist/dashboard/assets/select-BYR_5K2c.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 +96 -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
@@ -1,132 +1,132 @@
1
- /**
2
- * What one provider-probe answer means about the egress.
3
- *
4
- * Extracted from the store so the console, the CLI probe
5
- * (`scripts/operations/probe-egress.ts`) and the tests cannot drift into three
6
- * readings of the same response. The rule that matters is not obvious:
7
- *
8
- * **The provider's IP gate runs before it validates the body.** So a `4xx`
9
- * complaint about the payload proves the address was accepted — it is the
10
- * probe's request that is wrong, not the proxy. Mapping that to `unreachable`
11
- * ("the probe could not complete") blamed the proxy for our own defect and hid
12
- * working egresses behind a verdict that said the pool was unusable.
13
- *
14
- * Only the free-tier refusal retires an address. Every other answer either says
15
- * the address works (`allowed`, `throttled`) or says nothing about it
16
- * (`unreachable`), and treating "says nothing" as "refused" would drop a usable
17
- * pool for good.
18
- */
19
-
20
- export type ProbeVerdict = "allowed" | "blocked" | "throttled" | "unreachable";
21
-
22
- export interface ProbeAnswer {
23
- readonly status: ProbeVerdict;
24
- /** Operator-facing reason; absent when the answer needs none. */
25
- readonly reason?: string;
26
- }
27
-
28
- /** The upstream's own wording for "this address is retired". */
29
- const FREE_TIER_REFUSAL = /freetier|free tier can only be used/i;
30
-
31
- /**
32
- * Classifies one upstream response.
33
- *
34
- * `407` is separated from the generic 4xx branch on purpose: it comes from the
35
- * proxy front door, never from the provider, so it is a credential problem on
36
- * the tunnel and says nothing about whether the address is refused.
37
- */
38
- export function classifyProviderProbeAnswer(httpStatus: number, body: string): ProbeAnswer {
39
- if (httpStatus === 403 && FREE_TIER_REFUSAL.test(body)) {
40
- return { status: "blocked", reason: "Upstream refused this egress address" };
41
- }
42
- if (httpStatus === 407) {
43
- return { status: "unreachable", reason: "Proxy requires authentication" };
44
- }
45
- if (httpStatus === 429) {
46
- return { status: "throttled", reason: "Address accepted; rate limit spent" };
47
- }
48
- if (httpStatus >= 200 && httpStatus < 300) {
49
- return { status: "allowed" };
50
- }
51
- if (httpStatus >= 500) {
52
- return { status: "unreachable", reason: `Upstream answered ${httpStatus}` };
53
- }
54
- // Any other 4xx: the request reached the upstream's own validation, which
55
- // only happens once the address is accepted.
56
- return {
57
- status: "allowed",
58
- reason: `Reachable; upstream answered ${httpStatus} for the probe body`,
59
- };
60
- }
61
-
62
- /**
63
- * Error codes that mean "this attempt never reached the upstream", not "the
64
- * upstream refused this address".
65
- *
66
- * Every one of them is worth a second attempt, and the reason is the same in
67
- * each case: the failure is a property of one socket, not of the egress. A
68
- * proxy that has just been dialled for the first time has to resolve, open a
69
- * tunnel, and complete a TLS handshake, and any of those steps can be refused
70
- * or reset by a middlebox that accepts the very next attempt — which is exactly
71
- * the "fails once, works on retry" report. Retiring the pool on the first one
72
- * blames the address for a cold-start race.
73
- */
74
- const RETRYABLE_TRANSPORT_CODES = new Set([
75
- "ECONNREFUSED",
76
- "ECONNRESET",
77
- "ECONNABORTED",
78
- "EHOSTUNREACH",
79
- "ENETUNREACH",
80
- "ENETRESET",
81
- "EPIPE",
82
- "ETIMEDOUT",
83
- "EAI_AGAIN",
84
- "UND_ERR_SOCKET",
85
- "UND_ERR_CONNECT_TIMEOUT",
86
- "ECONNECT_TIMEOUT",
87
- ]);
88
- /** How many `cause` links to follow; wraps are shallow in practice. */
89
- const PROBE_ERROR_CAUSE_DEPTH = 5;
90
-
91
- /**
92
- * Whether one probe error should be retried before it becomes a verdict.
93
- *
94
- * Deliberately narrow: an abort is *our* deadline firing, so retrying it would
95
- * spend the whole probe budget on a slow egress, and an unknown error is left
96
- * alone rather than guessed at. Only transport codes that describe a single
97
- * dead connection qualify.
98
- */
99
- export function isRetryableProbeError(error: unknown): boolean {
100
-
101
- // Walk the `cause` chain: a transport failure is routinely wrapped — the
102
- // proxy layer raises `ProxyConnectHandshakeError` whose *message* carries the
103
- // real code, with nothing on `code`. Checking only the top error would miss
104
- // exactly the failure this exists for.
105
- let current: unknown = error;
106
- for (let depth = 0; depth < PROBE_ERROR_CAUSE_DEPTH && current !== undefined && current !== null; depth += 1) {
107
- if (!(current instanceof Error)) break;
108
- if (current.name === "AbortError" || current.name === "TimeoutError") return false;
109
- // `code` is not on the base `Error`; narrow before reading it rather than
110
- // asserting a shape the runtime may not have.
111
- const code: string | undefined =
112
- "code" in current && typeof current.code === "string" ? current.code : undefined;
113
- if (code !== undefined && RETRYABLE_TRANSPORT_CODES.has(code.toUpperCase())) return true;
114
- const message = String(current.message ?? "");
115
- // The wrapped code is often only in the message, so match it there too.
116
- for (const candidate of RETRYABLE_TRANSPORT_CODES) {
117
- if (message.includes(candidate)) return true;
118
- }
119
- if (
120
- message.includes("fetch failed") ||
121
- message.includes("socket connection was closed unexpectedly") ||
122
- message.includes("other side closed") ||
123
- message.startsWith("terminated") ||
124
- /connection (?:was|is) closed/i.test(message)
125
- ) {
126
- return true;
127
- }
128
- current = current.cause;
129
- }
130
- return false;
131
- }
132
-
1
+ /**
2
+ * What one provider-probe answer means about the egress.
3
+ *
4
+ * Extracted from the store so the console, the CLI probe
5
+ * (`scripts/tools/probe-egress.ts`) and the tests cannot drift into three
6
+ * readings of the same response. The rule that matters is not obvious:
7
+ *
8
+ * **The provider's IP gate runs before it validates the body.** So a `4xx`
9
+ * complaint about the payload proves the address was accepted — it is the
10
+ * probe's request that is wrong, not the proxy. Mapping that to `unreachable`
11
+ * ("the probe could not complete") blamed the proxy for our own defect and hid
12
+ * working egresses behind a verdict that said the pool was unusable.
13
+ *
14
+ * Only the free-tier refusal retires an address. Every other answer either says
15
+ * the address works (`allowed`, `throttled`) or says nothing about it
16
+ * (`unreachable`), and treating "says nothing" as "refused" would drop a usable
17
+ * pool for good.
18
+ */
19
+
20
+ export type ProbeVerdict = "allowed" | "blocked" | "throttled" | "unreachable";
21
+
22
+ export interface ProbeAnswer {
23
+ readonly status: ProbeVerdict;
24
+ /** Operator-facing reason; absent when the answer needs none. */
25
+ readonly reason?: string;
26
+ }
27
+
28
+ /** The upstream's own wording for "this address is retired". */
29
+ const FREE_TIER_REFUSAL = /freetier|free tier can only be used/i;
30
+
31
+ /**
32
+ * Classifies one upstream response.
33
+ *
34
+ * `407` is separated from the generic 4xx branch on purpose: it comes from the
35
+ * proxy front door, never from the provider, so it is a credential problem on
36
+ * the tunnel and says nothing about whether the address is refused.
37
+ */
38
+ export function classifyProviderProbeAnswer(httpStatus: number, body: string): ProbeAnswer {
39
+ if (httpStatus === 403 && FREE_TIER_REFUSAL.test(body)) {
40
+ return { status: "blocked", reason: "Upstream refused this egress address" };
41
+ }
42
+ if (httpStatus === 407) {
43
+ return { status: "unreachable", reason: "Proxy requires authentication" };
44
+ }
45
+ if (httpStatus === 429) {
46
+ return { status: "throttled", reason: "Address accepted; rate limit spent" };
47
+ }
48
+ if (httpStatus >= 200 && httpStatus < 300) {
49
+ return { status: "allowed" };
50
+ }
51
+ if (httpStatus >= 500) {
52
+ return { status: "unreachable", reason: `Upstream answered ${httpStatus}` };
53
+ }
54
+ // Any other 4xx: the request reached the upstream's own validation, which
55
+ // only happens once the address is accepted.
56
+ return {
57
+ status: "allowed",
58
+ reason: `Reachable; upstream answered ${httpStatus} for the probe body`,
59
+ };
60
+ }
61
+
62
+ /**
63
+ * Error codes that mean "this attempt never reached the upstream", not "the
64
+ * upstream refused this address".
65
+ *
66
+ * Every one of them is worth a second attempt, and the reason is the same in
67
+ * each case: the failure is a property of one socket, not of the egress. A
68
+ * proxy that has just been dialled for the first time has to resolve, open a
69
+ * tunnel, and complete a TLS handshake, and any of those steps can be refused
70
+ * or reset by a middlebox that accepts the very next attempt — which is exactly
71
+ * the "fails once, works on retry" report. Retiring the pool on the first one
72
+ * blames the address for a cold-start race.
73
+ */
74
+ const RETRYABLE_TRANSPORT_CODES = new Set([
75
+ "ECONNREFUSED",
76
+ "ECONNRESET",
77
+ "ECONNABORTED",
78
+ "EHOSTUNREACH",
79
+ "ENETUNREACH",
80
+ "ENETRESET",
81
+ "EPIPE",
82
+ "ETIMEDOUT",
83
+ "EAI_AGAIN",
84
+ "UND_ERR_SOCKET",
85
+ "UND_ERR_CONNECT_TIMEOUT",
86
+ "ECONNECT_TIMEOUT",
87
+ ]);
88
+ /** How many `cause` links to follow; wraps are shallow in practice. */
89
+ const PROBE_ERROR_CAUSE_DEPTH = 5;
90
+
91
+ /**
92
+ * Whether one probe error should be retried before it becomes a verdict.
93
+ *
94
+ * Deliberately narrow: an abort is *our* deadline firing, so retrying it would
95
+ * spend the whole probe budget on a slow egress, and an unknown error is left
96
+ * alone rather than guessed at. Only transport codes that describe a single
97
+ * dead connection qualify.
98
+ */
99
+ export function isRetryableProbeError(error: unknown): boolean {
100
+
101
+ // Walk the `cause` chain: a transport failure is routinely wrapped — the
102
+ // proxy layer raises `ProxyConnectHandshakeError` whose *message* carries the
103
+ // real code, with nothing on `code`. Checking only the top error would miss
104
+ // exactly the failure this exists for.
105
+ let current: unknown = error;
106
+ for (let depth = 0; depth < PROBE_ERROR_CAUSE_DEPTH && current !== undefined && current !== null; depth += 1) {
107
+ if (!(current instanceof Error)) break;
108
+ if (current.name === "AbortError" || current.name === "TimeoutError") return false;
109
+ // `code` is not on the base `Error`; narrow before reading it rather than
110
+ // asserting a shape the runtime may not have.
111
+ const code: string | undefined =
112
+ "code" in current && typeof current.code === "string" ? current.code : undefined;
113
+ if (code !== undefined && RETRYABLE_TRANSPORT_CODES.has(code.toUpperCase())) return true;
114
+ const message = String(current.message ?? "");
115
+ // The wrapped code is often only in the message, so match it there too.
116
+ for (const candidate of RETRYABLE_TRANSPORT_CODES) {
117
+ if (message.includes(candidate)) return true;
118
+ }
119
+ if (
120
+ message.includes("fetch failed") ||
121
+ message.includes("socket connection was closed unexpectedly") ||
122
+ message.includes("other side closed") ||
123
+ message.startsWith("terminated") ||
124
+ /connection (?:was|is) closed/i.test(message)
125
+ ) {
126
+ return true;
127
+ }
128
+ current = current.cause;
129
+ }
130
+ return false;
131
+ }
132
+
@@ -166,8 +166,8 @@ export class DrizzleNetworkPoolStore implements NetworkPoolStore {
166
166
  ...(row.lastSpeedtestAt ? { lastSpeedtestAt: row.lastSpeedtestAt.toISOString() } : {}),
167
167
  ...(() => {
168
168
  // A verdict describes the address recorded beside it. Once the pool is
169
- // re-pointed, that address is gone and the verdict is a claim about a
170
- // proxy that no longer exists — report "never probed" instead of
169
+ // re-pointed, that address is gone and the verdict is a claim about an
170
+ // address the pool no longer uses — report "never probed" instead of
171
171
  // showing a colour the operator cannot trust.
172
172
  const probe = storedProviderProbe(row, endpoint ?? "");
173
173
  return probe === undefined ? {} : { providerProbe: probe };
@@ -350,10 +350,10 @@ export class DrizzleNetworkPoolStore implements NetworkPoolStore {
350
350
  /**
351
351
  * Banks a probe verdict on the pool row.
352
352
  *
353
- * The verdict used to live in the operator's browser, which made it
354
- * decorative: another machine saw nothing, the gateway saw nothing, and
355
- * admission kept choosing the pool the badge called blocked. Storing it here
356
- * is what makes the badge a fact about the pool rather than about one tab.
353
+ * The verdict is stored here rather than per-viewer so every reader agrees:
354
+ * another machine, the gateway, and the operator alike. Otherwise admission
355
+ * keeps choosing the pool the badge calls blocked. Storing it here is what
356
+ * makes the badge a fact about the pool rather than about one tab.
357
357
  *
358
358
  * `poolId` is the pool row; `endpoint` is what the verdict was measured
359
359
  * against, so a later edit invalidates it on read. A probe against an unsaved
@@ -458,8 +458,8 @@ export class DrizzleNetworkPoolStore implements NetworkPoolStore {
458
458
  .set({
459
459
  endpointConfig,
460
460
  kind: effectiveKind === "https" ? "http" : effectiveKind,
461
- // Fields default to `undefined` when omitted from the patch; that
462
- // used to overwrite the current row with `undefined` and clobber
461
+ // Fields default to `undefined` when omitted from the patch, which
462
+ // would overwrite the current row with `undefined` and clobber
463
463
  // existing values. Fall back to the current record so a partial
464
464
  // PATCH truly leaves un-mentioned fields alone.
465
465
  maxInflight: patch.maxInflight ?? current.maxInflight,
@@ -7,6 +7,10 @@ import { t } from "elysia";
7
7
  import type { AccessDecision } from "../../security/access-control";
8
8
  import type { RedisBackend } from "../../persistence/redis";
9
9
  import type { ConsoleSettingsPreferences } from "../../persistence/schema";
10
+ import { RTK_LEVELS } from "../../transport/request/rtk/rtk-constants";
11
+ import { PONYTAIL_LEVELS } from "../../transport/request/rtk/ponytail-prompt";
12
+
13
+ export { RTK_LEVELS, PONYTAIL_LEVELS };
10
14
 
11
15
  export interface RuntimeSettingsResponse {
12
16
  readonly redisBackendActual: RedisBackend;
@@ -17,6 +21,8 @@ export interface RuntimeSettingsResponse {
17
21
  readonly telemetryPayloads: TelemetryPayloadMode;
18
22
  /** Capture depth for `full` mode; `minimum` when unset. */
19
23
  readonly telemetryPayloadDepth: TelemetryPayloadDepth;
24
+ /** How long a captured frame is kept, in minutes; `null` means deployment default. */
25
+ readonly telemetryPayloadRetentionMinutes: number | null;
20
26
  readonly privacyMode: PrivacyMode;
21
27
  /** Compacts bulky tool-result text before dispatch (RTK prune). Default false. */
22
28
  readonly rtkPruneEnabled: boolean;
@@ -63,6 +69,17 @@ export interface RuntimeSettingsConfig {
63
69
  readonly store: RuntimeSettingsStore;
64
70
  readonly accessResolver: ConsoleAccessResolver;
65
71
  readonly auditSink?: AuditSink;
72
+ /**
73
+ * Structurally satisfied by `InMemoryRouteSnapshotService`.
74
+ *
75
+ * Two preferences here are read on the request path, not per request: the
76
+ * route snapshot reads `forceDisableCooldown` when it builds candidates, and
77
+ * the admission path reads `tenantConcurrencyLimit`. The snapshot is cached
78
+ * until something invalidates it, so without this a saved setting stays
79
+ * invisible to `/v1/*` until some unrelated console write happens to
80
+ * invalidate — the toggle looks broken while the row is already correct.
81
+ */
82
+ readonly snapshotInvalidator?: { invalidate(): unknown };
66
83
  }
67
84
 
68
85
 
@@ -75,13 +92,25 @@ export type TelemetryPayloadMode = (typeof TELEMETRY_PAYLOAD_MODES)[number];
75
92
  export const TELEMETRY_PAYLOAD_DEPTHS = ["minimum", "moderate", "maximum"] as const;
76
93
  /** Capture depth for `full` mode accepted by the runtime-settings PATCH. */
77
94
  export type TelemetryPayloadDepth = (typeof TELEMETRY_PAYLOAD_DEPTHS)[number];
95
+ /**
96
+ * How long a captured payload frame is kept, in whole minutes, or `null` for
97
+ * the deployment default.
98
+ *
99
+ * Only the offered values are accepted, and a value off the list is *rejected*
100
+ * rather than clamped: clamping would answer a request for thirty days with
101
+ * seven and report success, and an operator who asked for the longer window
102
+ * would believe they got it.
103
+ */
104
+ export const TELEMETRY_PAYLOAD_RETENTION_MINUTES = [15, 30, 60, 360, 720, 1440, 10080] as const;
105
+ /** Payload retention in minutes accepted by the runtime-settings PATCH. */
106
+ export type TelemetryPayloadRetentionMinutes =
107
+ | (typeof TELEMETRY_PAYLOAD_RETENTION_MINUTES)[number]
108
+ | null;
78
109
  export const PRIVACY_MODES = ["masked", "full"] as const;
79
110
  /** Provider/model label privacy accepted by the runtime-settings PATCH. */
80
111
  export type PrivacyMode = (typeof PRIVACY_MODES)[number];
81
- export const PONYTAIL_LEVELS = ["lite", "full", "ultra"] as const;
82
112
  /** PonyTail directive intensity accepted by the runtime-settings PATCH. */
83
113
  export type PonyTailLevel = (typeof PONYTAIL_LEVELS)[number];
84
- export const RTK_LEVELS = ["lite", "full", "ultra"] as const;
85
114
  /** RTK prune strength accepted by the runtime-settings PATCH. */
86
115
  export type RtkLevel = (typeof RTK_LEVELS)[number];
87
116
 
@@ -120,6 +149,19 @@ export function createRuntimeSettingsOperations(deps: RuntimeSettingsConfig) {
120
149
  if (patch.telemetryPayloadDepth !== undefined && !TELEMETRY_PAYLOAD_DEPTHS.includes(patch.telemetryPayloadDepth)) {
121
150
  throw new ConsoleDomainError("invalid_request", 400, "Invalid telemetryPayloadDepth");
122
151
  }
152
+ if (patch.telemetryPayloadRetentionMinutes !== undefined) {
153
+ const minutes = patch.telemetryPayloadRetentionMinutes;
154
+ if (
155
+ minutes !== null &&
156
+ !(TELEMETRY_PAYLOAD_RETENTION_MINUTES as readonly number[]).includes(minutes)
157
+ ) {
158
+ throw new ConsoleDomainError(
159
+ "invalid_request",
160
+ 400,
161
+ "Invalid telemetryPayloadRetentionMinutes",
162
+ );
163
+ }
164
+ }
123
165
  if (patch.privacyMode !== undefined && !PRIVACY_MODES.includes(patch.privacyMode)) {
124
166
  throw new ConsoleDomainError("invalid_request", 400, "Invalid privacyMode");
125
167
  }
@@ -140,6 +182,14 @@ export function createRuntimeSettingsOperations(deps: RuntimeSettingsConfig) {
140
182
  target: a.tenantId,
141
183
  detail: patch as Record<string, unknown>,
142
184
  });
185
+ // Saved preferences are routing changes: `forceDisableCooldown` decides
186
+ // candidate eligibility and `tenantConcurrencyLimit` decides admission,
187
+ // and both are read from the cached route snapshot. Invalidate after the
188
+ // write commits so the next `/v1/*` request uses them instead of the
189
+ // revision that was built before the save. A failure here must not fail
190
+ // the save — the row is already correct, and the snapshot rebuilds on
191
+ // the next read anyway.
192
+ await Promise.resolve(deps.snapshotInvalidator?.invalidate()).catch(() => undefined);
143
193
  return updated;
144
194
  },
145
195
  };
@@ -156,6 +206,12 @@ export const runtimeUpdateBody = t.Object({
156
206
  responsesReasoningSummary: t.Optional(literalUnion(RESPONSES_REASONING_SUMMARIES)),
157
207
  telemetryPayloads: t.Optional(literalUnion(TELEMETRY_PAYLOAD_MODES)),
158
208
  telemetryPayloadDepth: t.Optional(literalUnion(TELEMETRY_PAYLOAD_DEPTHS)),
209
+ telemetryPayloadRetentionMinutes: t.Optional(
210
+ t.Union([
211
+ t.Null(),
212
+ t.Union(TELEMETRY_PAYLOAD_RETENTION_MINUTES.map((minutes) => t.Literal(minutes))),
213
+ ]),
214
+ ),
159
215
  privacyMode: t.Optional(literalUnion(PRIVACY_MODES)),
160
216
  tenantConcurrencyLimit: t.Optional(t.Union([t.Null(), t.Number()])),
161
217
  thinkingNormalizationEnabled: t.Optional(t.Boolean()),
@@ -11,6 +11,7 @@ import {
11
11
  RTK_LEVELS,
12
12
  TELEMETRY_PAYLOAD_DEPTHS,
13
13
  TELEMETRY_PAYLOAD_MODES,
14
+ TELEMETRY_PAYLOAD_RETENTION_MINUTES,
14
15
  type PonyTailLevel,
15
16
  type ResponsesReasoningSummary,
16
17
  type RtkLevel,
@@ -105,6 +106,25 @@ function normalizeImageClampMaxBytes(value: unknown): number | null {
105
106
  return normalizeBoundedNumber(value, 64 * 1024, 100 * 1024 * 1024);
106
107
  }
107
108
 
109
+ /**
110
+ * How long a captured payload frame is kept, in whole minutes.
111
+ *
112
+ * Only one of the offered values survives; anything else reads as "deployment
113
+ * default" rather than being clamped to the nearest one. A stored value from
114
+ * before this list existed would otherwise be silently rewritten to a window
115
+ * the operator never chose, and dropping it to `null` is the same outcome the
116
+ * operator gets by never having set it.
117
+ */
118
+ function normalizePayloadRetentionMinutes(value: unknown): number | null {
119
+ if (value === null || value === undefined) return null;
120
+ const parsed = typeof value === "number" ? value : Number(value);
121
+ if (!Number.isFinite(parsed)) return null;
122
+ const minutes = Math.floor(parsed);
123
+ return (TELEMETRY_PAYLOAD_RETENTION_MINUTES as readonly number[]).includes(minutes)
124
+ ? minutes
125
+ : null;
126
+ }
127
+
108
128
  function mapRuntimeSettingsRow(
109
129
  row: typeof consoleSettings.$inferSelect | undefined,
110
130
  redisBackend: RedisBackend,
@@ -120,6 +140,9 @@ function mapRuntimeSettingsRow(
120
140
  : "detailed",
121
141
  telemetryPayloads: normalizeTelemetryPayloadMode(prefs.telemetryPayloads),
122
142
  telemetryPayloadDepth: normalizeTelemetryPayloadDepth(prefs.telemetryPayloadDepth),
143
+ telemetryPayloadRetentionMinutes: normalizePayloadRetentionMinutes(
144
+ prefs.telemetryPayloadRetentionMinutes,
145
+ ),
123
146
  privacyMode: prefs.privacyMode === "full" ? "full" : "masked",
124
147
  rtkPruneEnabled: prefs.rtkPruneEnabled === true,
125
148
  rtkPruneLevel: normalizeRtkLevel(prefs.rtkPruneLevel),
@@ -127,9 +150,9 @@ function mapRuntimeSettingsRow(
127
150
  // RTK above works. `ponyTailLevel` selects the intensity and is kept while
128
151
  // the feature is off so a re-enable restores it, but a stored level is NOT
129
152
  // consent: the dashboard lets an operator choose a strength with the toggle
130
- // still off, which writes a level and no flag. Reading that as enabled made
131
- // the console report the directive as on while the dispatch gate — which
132
- // requires the flag — never injected it.
153
+ // still off, which writes a level and no flag. Reading that as enabled
154
+ // would report the directive as on while the dispatch gate — which requires
155
+ // the flag — never injects it.
133
156
  ponyTailEnabled: prefs.ponyTailEnabled === true,
134
157
  ponyTailLevel: normalizePonyTailLevel(prefs.ponyTailLevel),
135
158
  forceDisableCooldown: prefs.forceDisableCooldown === true,
@@ -169,6 +192,10 @@ export class DrizzleRuntimeSettingsStore implements RuntimeSettingsStore {
169
192
  patchPrefs.responsesReasoningSummary = patch.responsesReasoningSummary;
170
193
  if (patch.telemetryPayloads !== undefined) patchPrefs.telemetryPayloads = patch.telemetryPayloads;
171
194
  if (patch.telemetryPayloadDepth !== undefined) patchPrefs.telemetryPayloadDepth = patch.telemetryPayloadDepth;
195
+ if (patch.telemetryPayloadRetentionMinutes !== undefined)
196
+ patchPrefs.telemetryPayloadRetentionMinutes = normalizePayloadRetentionMinutes(
197
+ patch.telemetryPayloadRetentionMinutes,
198
+ );
172
199
  if (patch.privacyMode !== undefined) patchPrefs.privacyMode = patch.privacyMode;
173
200
  if (patch.rtkPruneEnabled !== undefined) patchPrefs.rtkPruneEnabled = patch.rtkPruneEnabled;
174
201
  if (patch.rtkPruneLevel !== undefined) patchPrefs.rtkPruneLevel = patch.rtkPruneLevel;
@@ -13,8 +13,8 @@ const decodeCache = new Map<string, unknown>();
13
13
  * Maximum nesting depth a decoded cursor may have.
14
14
  *
15
15
  * `JSON.parse` accepts arbitrary nesting (it is iterative in this runtime), but
16
- * every traversal of the result is not: `deepFreeze` used to recurse once per
17
- * level, and `structuredClone` recurses again. A payload past the stack limit
16
+ * every traversal of the result is not: `structuredClone` recurses once per
17
+ * level. A payload past the stack limit
18
18
  * therefore threw `RangeError: Maximum call stack size exceeded` out of a function
19
19
  * that documents returning `undefined` for a malformed value — failing OPEN into a
20
20
  * 500 where the contract is "fall back to the first page", and leaving a partially
@@ -116,8 +116,8 @@ function copyDecoded<T>(value: unknown): T {
116
116
  // `structuredClone` recurses, so a value past its own budget throws. The
117
117
  // depth limit makes this unreachable for anything `decodeCursor` accepts, but
118
118
  // the documented contract is `undefined` for a value that cannot be decoded,
119
- // and a cache hit on an entry written before the limit existed could still
120
- // reach here.
119
+ // and the documented contract is `undefined` for a value that cannot be
120
+ // decoded.
121
121
  return undefined as T;
122
122
  }
123
123
  }
@@ -133,10 +133,10 @@ export interface DatedCursor {
133
133
  /**
134
134
  * Decodes a `(createdAt, id)` keyset cursor, rejecting anything malformed.
135
135
  *
136
- * Both the audit log and the telemetry event listing paginate on the same pair
137
- * and both had written the same validation locally — a shape check plus a
138
- * parseable timestamp. It lives here so the two listings cannot drift: a cursor
139
- * the audit store accepts must be one the stats store accepts.
136
+ * Both the audit log and the telemetry event listing paginate on the same pair,
137
+ * so the shape check plus parseable-timestamp validation lives here and the two
138
+ * listings cannot drift: a cursor the audit store accepts must be one the stats
139
+ * store accepts.
140
140
  *
141
141
  * Deliberately not generic over `T extends DatedCursor`: a caller could then
142
142
  * instantiate it with a wider type and read fields that were never validated.
@@ -141,9 +141,7 @@ export function requireGlobalAdmin(access: AccessDecision | undefined): AccessDe
141
141
  /**
142
142
  * Builds the console's catch-all error handler for an Elysia route group.
143
143
  *
144
- * Every console route group used to wrap each handler in its own try/catch
145
- * calling `errorResponse` with a group-specific fallback message — 95 copies,
146
- * 22 of them in one file. Elysia's global `error(handler)` hook receives any
144
+ * Elysia's global `error(handler)` hook receives any
147
145
  * throw from the group (including a `.use()`d child), so one hook per group
148
146
  * produces the identical envelope with the identical fallback text.
149
147
  *
@@ -73,10 +73,8 @@ export interface Http2BidiOpenOptions {
73
73
  * The dialer contract a provider adapter is handed.
74
74
  *
75
75
  * Declared here rather than in the provider layer so the dependency runs one
76
- * way: `provider-contracts` imports this module, never the reverse. It used to
77
- * be declared there and re-exported from here, which made the two import each
78
- * other — type-only, so it never deadlocked, but it meant this
79
- * transport-neutral contract had to know about providers.
76
+ * way: `provider-contracts` imports this module, never the reverse, and this
77
+ * transport-neutral contract stays free of any knowledge of providers.
80
78
  */
81
79
  export type ValidatedOutboundHttp2Bidi = (
82
80
  url: URL,