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.
- package/.env.example +13 -4
- package/README.md +2 -2
- package/dist/dashboard/assets/{BannedPage-C2orBsI2.js → BannedPage-DE2607Tb.js} +1 -1
- package/dist/dashboard/assets/BannedPage-DE2607Tb.js.gz +0 -0
- package/dist/dashboard/assets/CliToolDetailPage-Duv6mfrl.js +1 -0
- package/dist/dashboard/assets/CliToolDetailPage-Duv6mfrl.js.gz +0 -0
- package/dist/dashboard/assets/{CliToolsPage-BV6pxVQo.js → CliToolsPage-C2PNYWzk.js} +1 -1
- package/dist/dashboard/assets/CliToolsPage-C2PNYWzk.js.gz +0 -0
- package/dist/dashboard/assets/CombosPage-TXP9OfAM.js +1 -0
- package/dist/dashboard/assets/CombosPage-TXP9OfAM.js.gz +0 -0
- package/dist/dashboard/assets/{ConsoleLogPage-CW-wPmJ6.js → ConsoleLogPage-B-9xnAQN.js} +1 -1
- package/dist/dashboard/assets/ConsoleLogPage-B-9xnAQN.js.gz +0 -0
- package/dist/dashboard/assets/{CustomizationPage-0p8uX7vB.js → CustomizationPage-xeax9nJt.js} +1 -1
- package/dist/dashboard/assets/CustomizationPage-xeax9nJt.js.gz +0 -0
- package/dist/dashboard/assets/{HealthEventsModal-BjqSMfko.js → HealthEventsModal-Df4jjaHJ.js} +1 -1
- package/dist/dashboard/assets/HealthEventsModal-Df4jjaHJ.js.gz +0 -0
- package/dist/dashboard/assets/{LoginPage-BvzCx8Kr.js → LoginPage-Dyojsm84.js} +1 -1
- package/dist/dashboard/assets/LoginPage-Dyojsm84.js.gz +0 -0
- package/dist/dashboard/assets/ModelPicker-C7pDb41R.js +1 -0
- package/dist/dashboard/assets/ModelPicker-C7pDb41R.js.gz +0 -0
- package/dist/dashboard/assets/OverviewPage-BJfg4ow6.js +1 -0
- package/dist/dashboard/assets/OverviewPage-BJfg4ow6.js.gz +0 -0
- package/dist/dashboard/assets/ProviderDetailPage-ChCX1gmT.js +3 -0
- package/dist/dashboard/assets/ProviderDetailPage-ChCX1gmT.js.gz +0 -0
- package/dist/dashboard/assets/{ProvidersPage-Js9jUPSb.js → ProvidersPage-DeWSOEUO.js} +1 -1
- package/dist/dashboard/assets/ProvidersPage-DeWSOEUO.js.gz +0 -0
- package/dist/dashboard/assets/ProxyPage-CkpWln2S.js +7 -0
- package/dist/dashboard/assets/ProxyPage-CkpWln2S.js.gz +0 -0
- package/dist/dashboard/assets/QuotaPage-B3VOgQmt.js +1 -0
- package/dist/dashboard/assets/QuotaPage-B3VOgQmt.js.gz +0 -0
- package/dist/dashboard/assets/SettingsPage-C_klIZom.js +1 -0
- package/dist/dashboard/assets/SettingsPage-C_klIZom.js.gz +0 -0
- package/dist/dashboard/assets/{SetupPage-DDbMQsoo.js → SetupPage-BYph-InK.js} +1 -1
- package/dist/dashboard/assets/SetupPage-BYph-InK.js.gz +0 -0
- package/dist/dashboard/assets/{SortableList-CB3Z7VTf.js → SortableList-CvyQCYWJ.js} +1 -1
- package/dist/dashboard/assets/SortableList-CvyQCYWJ.js.gz +0 -0
- package/dist/dashboard/assets/{StudioPage-D95UCPHI.js → StudioPage-Cm1JYilI.js} +3 -3
- package/dist/dashboard/assets/StudioPage-Cm1JYilI.js.gz +0 -0
- package/dist/dashboard/assets/UsagePage-DlFnc3_S.js +56 -0
- package/dist/dashboard/assets/UsagePage-DlFnc3_S.js.gz +0 -0
- package/dist/dashboard/assets/api-keys-DQYLQxpW.js +1 -0
- package/dist/dashboard/assets/api-keys-DQYLQxpW.js.gz +0 -0
- package/dist/dashboard/assets/app-CX_Z8cMs.css +2 -0
- package/dist/dashboard/assets/app-CX_Z8cMs.css.gz +0 -0
- package/dist/dashboard/assets/{assert-Chi7pCnt.js → assert-C9FD_JwJ.js} +1 -1
- package/dist/dashboard/assets/assert-C9FD_JwJ.js.gz +0 -0
- package/dist/dashboard/assets/{badge-D4pQapKj.js → badge-CR72dasT.js} +1 -1
- package/dist/dashboard/assets/badge-CR72dasT.js.gz +0 -0
- package/dist/dashboard/assets/{banner-DH6cg-_B.js → banner-CI9_eSfO.js} +1 -1
- package/dist/dashboard/assets/banner-CI9_eSfO.js.gz +0 -0
- package/dist/dashboard/assets/{card-BKqlUOe4.js → card-DS7CQEsO.js} +1 -1
- package/dist/dashboard/assets/card-DS7CQEsO.js.gz +0 -0
- package/dist/dashboard/assets/{cli-tools-fTgljTGA.js → cli-tools-CNqzrfdb.js} +1 -1
- package/dist/dashboard/assets/cli-tools-CNqzrfdb.js.gz +0 -0
- package/dist/dashboard/assets/{clipboard-button-D5Ys_sM0.js → clipboard-button-611pSsaX.js} +1 -1
- package/dist/dashboard/assets/clipboard-button-611pSsaX.js.gz +0 -0
- package/dist/dashboard/assets/{fingerprint-pattern-BWuHtbW4.js → concurrency-BDF0ok7y.js} +1 -1
- package/dist/dashboard/assets/concurrency-BDF0ok7y.js.gz +0 -0
- package/dist/dashboard/assets/dialog-k37exjNt.js +1 -0
- package/dist/dashboard/assets/dialog-k37exjNt.js.gz +0 -0
- package/dist/dashboard/assets/drawer-CX7N8ucR.js +1 -0
- package/dist/dashboard/assets/drawer-CX7N8ucR.js.gz +0 -0
- package/dist/dashboard/assets/{entry-CQBr6U9c.js → entry-B9Xvtnfj.js} +1 -1
- package/dist/dashboard/assets/entry-B9Xvtnfj.js.gz +0 -0
- package/dist/dashboard/assets/entry-Bog0p925.js +4 -0
- package/dist/dashboard/assets/entry-Bog0p925.js.gz +0 -0
- package/dist/dashboard/assets/entry-CTP8AyiS.js +1 -0
- package/dist/dashboard/assets/entry-CTP8AyiS.js.gz +0 -0
- package/dist/dashboard/assets/{github-badge-BlptL8Uf.js → github-badge-sltk1qUi.js} +1 -1
- package/dist/dashboard/assets/github-badge-sltk1qUi.js.gz +0 -0
- package/dist/dashboard/assets/{index-js6TJvdr.js → index-CZQIrdoX.js} +2 -2
- package/dist/dashboard/assets/index-CZQIrdoX.js.gz +0 -0
- package/dist/dashboard/assets/{inline-DUWDSmDV.js → inline-BhY2sSnO.js} +1 -1
- package/dist/dashboard/assets/inline-BhY2sSnO.js.gz +0 -0
- package/dist/dashboard/assets/{input-LVjgZq4f.js → input-V1umFiBk.js} +1 -1
- package/dist/dashboard/assets/input-V1umFiBk.js.gz +0 -0
- package/dist/dashboard/assets/layout-BBOYycyS.js +1 -0
- package/dist/dashboard/assets/layout-BBOYycyS.js.gz +0 -0
- package/dist/dashboard/assets/{list-row-DupvLfDx.js → list-row-1zzZYM0B.js} +1 -1
- package/dist/dashboard/assets/list-row-1zzZYM0B.js.gz +0 -0
- package/dist/dashboard/assets/{live-C4pEJUqI.js → live-BivGfuUs.js} +1 -1
- package/dist/dashboard/assets/live-BivGfuUs.js.gz +0 -0
- package/dist/dashboard/assets/{model-bans-BRiuufkB.js → model-bans-CmffjLuG.js} +1 -1
- package/dist/dashboard/assets/model-bans-CmffjLuG.js.gz +0 -0
- package/dist/dashboard/assets/{mutation-B4ROwk8L.js → mutation-46P0oWMe.js} +1 -1
- package/dist/dashboard/assets/mutation-46P0oWMe.js.gz +0 -0
- package/dist/dashboard/assets/numbers-DkfMR5Qs.js +2 -0
- package/dist/dashboard/assets/numbers-DkfMR5Qs.js.gz +0 -0
- package/dist/dashboard/assets/{query-keys-gmjzC3RS.js → query-keys-BFhGpwR1.js} +1 -1
- package/dist/dashboard/assets/query-keys-BFhGpwR1.js.gz +0 -0
- package/dist/dashboard/assets/{routing-C7tBT5o-.js → routing-CAb4F37R.js} +1 -1
- package/dist/dashboard/assets/routing-CAb4F37R.js.gz +0 -0
- package/dist/dashboard/assets/{select-WUCXorXw.js → select-BPpSsBgL.js} +1 -1
- package/dist/dashboard/assets/select-BPpSsBgL.js.gz +0 -0
- package/dist/dashboard/assets/{settings-C_zKl1GS.js → settings-DNX9sydi.js} +1 -1
- package/dist/dashboard/assets/settings-DNX9sydi.js.gz +0 -0
- package/dist/dashboard/assets/{stack-_3O0F8Xn.js → stack-BHTfXdz_.js} +1 -1
- package/dist/dashboard/assets/stack-BHTfXdz_.js.gz +0 -0
- package/dist/dashboard/assets/{state-BgTxHcJS.js → state-mPDRlQkl.js} +1 -1
- package/dist/dashboard/assets/state-mPDRlQkl.js.gz +0 -0
- package/dist/dashboard/assets/{switch-DEjTtOWf.js → switch-DOmJxg7H.js} +1 -1
- package/dist/dashboard/assets/switch-DOmJxg7H.js.gz +0 -0
- package/dist/dashboard/assets/tokens-COzario9.js +1 -0
- package/dist/dashboard/assets/tokens-COzario9.js.gz +0 -0
- package/dist/dashboard/assets/use-modal-focus-By7oJoGd.js +1 -0
- package/dist/dashboard/assets/use-modal-focus-By7oJoGd.js.gz +0 -0
- package/dist/dashboard/assets/{useMutation-BWSC0UAS.js → useMutation-Dem0lXQj.js} +1 -1
- package/dist/dashboard/assets/useMutation-Dem0lXQj.js.gz +0 -0
- package/dist/dashboard/index.html +1 -1
- package/dist/dashboard/index.html.gz +0 -0
- package/dist/dashboard/precompressed-assets.json +54 -54
- package/migrations/README.md +52 -52
- package/migrations/lite-sqlite3/0000_baseline.sql +2 -2
- package/package.json +95 -95
- package/scripts/cli/admin.ts +125 -0
- package/scripts/cli/{ui/background.ts → background.ts} +1 -1
- package/scripts/cli/{command/console-menu.ts → console-menu.ts} +275 -244
- package/scripts/cli/doctor.ts +37 -0
- package/scripts/cli/{support/env.ts → env.ts} +99 -99
- package/scripts/cli/{gateway/spawn.ts → gateway.ts} +112 -24
- package/scripts/cli/instance.ts +224 -0
- package/scripts/cli/main.ts +61 -18
- package/scripts/cli/{support/platform.ts → platform.ts} +64 -64
- package/scripts/cli/port.ts +100 -0
- package/scripts/cli/{support/services.ts → services.ts} +88 -88
- package/scripts/cli/{command/setup.ts → setup.ts} +35 -34
- package/scripts/cli/{command/start.ts → start.ts} +10 -5
- package/scripts/cli/{support/startup-signal.ts → startup-signal.ts} +23 -23
- package/scripts/cli/tray/index.ts +62 -12
- package/scripts/cli/{command/tray.ts → tray.ts} +208 -208
- package/scripts/cli/{command/update.ts → update.ts} +39 -39
- package/scripts/cli/version.ts +67 -0
- package/scripts/cli/{ui/wordmark.ts → wordmark.ts} +58 -58
- package/src/boot/app.ts +697 -693
- package/src/boot/dependencies.ts +647 -646
- package/src/boot/timeout.ts +2 -13
- package/src/config/cooldowns.ts +186 -138
- package/src/config/network.ts +8 -14
- package/src/config/providers.ts +7 -10
- package/src/config/runtime.ts +76 -76
- package/src/config/security.ts +15 -0
- package/src/config/spec.ts +20 -30
- package/src/config/telemetry.ts +0 -20
- package/src/config/timeouts.ts +0 -6
- package/src/console/admin/model-abuse/contracts.ts +28 -6
- package/src/console/admin/model-abuse/routes.ts +56 -3
- package/src/console/api-keys/contracts.ts +410 -414
- package/src/console/api-keys/routes.ts +881 -880
- package/src/console/auth/credentials.ts +3 -3
- package/src/console/auth/setup-service.ts +2 -2
- package/src/console/backup/export-endpoint.ts +6 -2
- package/src/console/backup/nine-router.ts +2 -5
- package/src/console/backup/service.ts +4 -0
- package/src/console/backup/store.ts +192 -29
- package/src/console/backup/validate.ts +3 -45
- package/src/console/cli-tools/fs-ops.ts +487 -487
- package/src/console/console-router-guards.ts +1 -1
- package/src/console/data-management/contracts.ts +13 -6
- package/src/console/data-management/operations.ts +23 -18
- package/src/console/data-management/routes.ts +0 -1
- package/src/console/domain-registration.ts +10 -5
- package/src/console/observability/live.ts +22 -0
- package/src/console/observability/store.ts +39 -16
- package/src/console/observability/usage-dimensions.ts +4 -4
- package/src/console/providers/catalog/catalog-projections.ts +6 -7
- package/src/console/providers/catalog/model-operations.ts +5 -2
- package/src/console/providers/catalog/provider-operations.ts +4 -4
- package/src/console/providers/catalog/public-model-store.ts +8 -11
- package/src/console/providers/catalog/store.ts +1553 -1553
- package/src/console/providers/oauth/routes.ts +8 -12
- package/src/console/quota/account-quota-tenant.ts +6 -6
- package/src/console/quota/quota-view.ts +1 -1
- package/src/console/remote-support/contracts.ts +61 -0
- package/src/console/remote-support/routes.ts +65 -0
- package/src/console/remote-support/service.ts +207 -0
- package/src/console/response-compression.ts +0 -3
- package/src/console/routing/pools/probe-result.ts +3 -2
- package/src/console/routing/pools/probe-verdict.ts +132 -132
- package/src/console/routing/pools/store.ts +8 -8
- package/src/console/settings/contracts.ts +58 -2
- package/src/console/settings/store.ts +30 -3
- package/src/console/shared/cursor.ts +8 -8
- package/src/console/shared/errors.ts +1 -3
- package/src/network/http2-bidi.ts +2 -4
- package/src/network/pool/agent.ts +478 -478
- package/src/network/pool/selector.ts +5 -4
- package/src/network/retry.ts +1 -2
- package/src/network/ssrf.ts +5 -4
- package/src/observability/event-compaction.ts +80 -78
- package/src/observability/event-day-index.ts +13 -15
- package/src/observability/event-log.ts +4 -4
- package/src/observability/logger.ts +6 -6
- package/src/observability/payload-capture.ts +151 -33
- package/src/observability/payload-store.ts +397 -440
- package/src/observability/redaction.ts +5 -6
- package/src/observability/request-notify.ts +43 -0
- package/src/observability/telemetry-status.ts +8 -9
- package/src/observability/token-speed.ts +5 -4
- package/src/persistence/db-mode.ts +7 -7
- package/src/persistence/readiness.ts +1 -1
- package/src/persistence/schema.ts +1406 -1402
- package/src/persistence/shared-cache.ts +1 -2
- package/src/persistence/stores/api-key-store.ts +18 -6
- package/src/persistence/stores/share-store.ts +4 -3
- package/src/persistence/stores/telemetry-store.ts +236 -237
- package/src/protocol/canonical-model.ts +11 -3
- package/src/protocol/codecs.ts +138 -4
- package/src/protocol/gateway-error.ts +6 -4
- package/src/protocol/request/chat.ts +21 -1
- package/src/protocol/request/codex.ts +10 -1
- package/src/protocol/request/messages.ts +4 -5
- package/src/protocol/request/responses.ts +9 -1
- package/src/protocol/response/chat.ts +34 -4
- package/src/protocol/response/codex.ts +7 -4
- package/src/protocol/response/dsml.ts +140 -28
- package/src/protocol/response/responses.ts +11 -5
- package/src/protocol/stream-error-frames.ts +4 -5
- package/src/providers/authentication/jwt-validator.ts +9 -10
- package/src/providers/authentication/oauth-refresh-service.ts +9 -11
- package/src/providers/configured-openai-providers.ts +134 -136
- package/src/providers/discovery/catalog/catalog-blob.ts +18 -18
- package/src/providers/discovery/catalog/catalog-format.ts +3 -6
- package/src/providers/discovery/models-dev-catalog.ts +354 -354
- package/src/providers/discovery/probe-phases.ts +4 -4
- package/src/providers/discovery/probe-wire.ts +6 -13
- package/src/providers/discovery/probing-service.ts +1117 -1045
- package/src/providers/integrations/alibabacp/alibabacp.ts +1 -1
- package/src/providers/integrations/antigravity/antigravity-quota.ts +5 -7
- package/src/providers/integrations/buddy/buddy-chat-shared.ts +23 -2
- package/src/providers/integrations/buddy/buddy-checkin.ts +1 -4
- package/src/providers/integrations/buddy/buddy-cli-headers.ts +23 -6
- package/src/providers/integrations/buddy/buddy-conversation-headers.ts +0 -1
- package/src/providers/integrations/buddy/buddy-identity.ts +52 -0
- package/src/providers/integrations/buddy/buddy-media.ts +2 -2
- package/src/providers/integrations/buddy/buddy-usage-store.ts +3 -4
- package/src/providers/integrations/buddy/codebuddy/quota.ts +2 -13
- package/src/providers/integrations/buddy/codebuddy/shared.ts +118 -115
- package/src/providers/integrations/buddy/workbuddy/quota.ts +0 -11
- package/src/providers/integrations/buddy/workbuddy/shared.ts +5 -2
- package/src/providers/integrations/claude/claude.ts +608 -608
- package/src/providers/integrations/cline/cline-quota.ts +132 -132
- package/src/providers/integrations/cline/cline.ts +537 -539
- package/src/providers/integrations/codex/codex-oauth.ts +1 -1
- package/src/providers/integrations/commandcode/commandcode.ts +674 -675
- package/src/providers/integrations/commandcode/discovery.ts +117 -116
- package/src/providers/integrations/cursor/cursor-auth.ts +418 -418
- package/src/providers/integrations/cursor/cursor-quota.ts +309 -310
- package/src/providers/integrations/cursor/cursor.ts +11 -163
- package/src/providers/integrations/factory/factory-models.ts +3 -5
- package/src/providers/integrations/grok/grok-install-id.ts +2 -3
- package/src/providers/integrations/grok/grok.ts +6 -7
- package/src/providers/integrations/hermes.ts +14 -18
- package/src/providers/integrations/kiro/kiro-discovery.ts +1 -1
- package/src/providers/integrations/kiro/kiro.ts +1 -1
- package/src/providers/integrations/mistral.ts +6 -6
- package/src/providers/integrations/opencode/fingerprint.ts +3 -3
- package/src/providers/integrations/opencode/provider.ts +8 -8
- package/src/providers/integrations/qoder/qoder-catalog.ts +450 -450
- package/src/providers/integrations/qoder/qoder-oauth.ts +388 -310
- package/src/providers/integrations/qoder/qoder.ts +884 -865
- package/src/providers/integrations/shared/connect.ts +6 -6
- package/src/providers/integrations/trae/trae-catalog.ts +1 -2
- package/src/providers/integrations/trae/trae.ts +2 -2
- package/src/providers/integrations/zcode/oauth.ts +6 -1
- package/src/providers/operations/account-health-service.ts +1528 -1523
- package/src/providers/operations/account-reset-service.ts +1 -1
- package/src/providers/operations/client-versions.ts +6 -6
- package/src/providers/operations/custom-cli-headers.ts +3 -4
- package/src/providers/operations/provider-catalog-seeder.ts +95 -95
- package/src/providers/operations/provider-catalog-service.ts +3 -4
- package/src/providers/provider-metadata.ts +4 -8
- package/src/providers/provider-registry.ts +6 -7
- package/src/providers/provider-usage.ts +1 -1
- package/src/security/abuse.ts +15 -10
- package/src/security/access-control.ts +144 -146
- package/src/security/admission/contracts.ts +11 -0
- package/src/security/admission/in-memory-store.ts +4 -3
- package/src/security/admission/lease-sweep.ts +6 -6
- package/src/security/admission/reasons.ts +5 -1
- package/src/security/admission/redis-store.ts +211 -210
- package/src/security/admission/service.ts +4 -0
- package/src/security/api-key-auth.ts +504 -504
- package/src/security/ip-boundary.ts +155 -15
- package/src/security/model-abuse.ts +366 -94
- package/src/security/outbound-headers.ts +3 -4
- package/src/shared/sleep.ts +4 -0
- package/src/transport/dispatch/attempt-finalize.ts +47 -5
- package/src/transport/dispatch/attempt-loop.ts +531 -530
- package/src/transport/dispatch/stream-bridge.ts +1 -3
- package/src/transport/dispatch/streaming/finalize.ts +1 -1
- package/src/transport/dispatch/streaming/stream.ts +5 -6
- package/src/transport/failure-policy.ts +162 -8
- package/src/transport/guards/auth-guard.ts +7 -4
- package/src/transport/guards/concurrency-guard.ts +0 -5
- package/src/transport/guards/error-lifecycle.ts +13 -6
- package/src/transport/guards/pipeline.ts +15 -3
- package/src/transport/guards/request-context.ts +8 -2
- package/src/transport/request/inflight.ts +5 -6
- package/src/transport/request/preparer.ts +2 -2
- package/src/transport/request/state.ts +12 -8
- package/src/transport/routing/admission.ts +4 -0
- package/src/transport/routing/route-catalog.ts +8 -1
- package/src/transport/routing/route-model.ts +46 -14
- package/src/transport/routing/router.ts +21 -14
- package/src/transport/surface/completion.ts +2 -3
- package/src/transport/surface/dialects.ts +5 -6
- package/src/transport/surface/responses/encode.ts +38 -0
- package/src/transport/surface/responses/parse.ts +4 -0
- package/src/transport/translation/capabilities.ts +2 -3
- package/src/transport/translation/quirks.ts +3 -3
- package/src/workers/buddy-growth-tasks.ts +5 -10
- package/src/workers/daily-checkin.ts +17 -23
- package/src/workers/quota-refresh-sweep.ts +26 -26
- package/dist/dashboard/assets/BannedPage-C2orBsI2.js.gz +0 -0
- package/dist/dashboard/assets/CliToolDetailPage-DUcZ6pMB.js +0 -1
- package/dist/dashboard/assets/CliToolDetailPage-DUcZ6pMB.js.gz +0 -0
- package/dist/dashboard/assets/CliToolsPage-BV6pxVQo.js.gz +0 -0
- package/dist/dashboard/assets/CombosPage-Bsan4679.js +0 -1
- package/dist/dashboard/assets/CombosPage-Bsan4679.js.gz +0 -0
- package/dist/dashboard/assets/ConsoleLogPage-CW-wPmJ6.js.gz +0 -0
- package/dist/dashboard/assets/CustomizationPage-0p8uX7vB.js.gz +0 -0
- package/dist/dashboard/assets/HealthEventsModal-BjqSMfko.js.gz +0 -0
- package/dist/dashboard/assets/LoginPage-BvzCx8Kr.js.gz +0 -0
- package/dist/dashboard/assets/ModelPicker-BY1aKEkP.js +0 -1
- package/dist/dashboard/assets/ModelPicker-BY1aKEkP.js.gz +0 -0
- package/dist/dashboard/assets/OverviewPage-DOQpy1y0.js +0 -1
- package/dist/dashboard/assets/OverviewPage-DOQpy1y0.js.gz +0 -0
- package/dist/dashboard/assets/ProviderDetailPage-ecxyUMzt.js +0 -3
- package/dist/dashboard/assets/ProviderDetailPage-ecxyUMzt.js.gz +0 -0
- package/dist/dashboard/assets/ProvidersPage-Js9jUPSb.js.gz +0 -0
- package/dist/dashboard/assets/ProxyPage-J3TBi3Kx.js +0 -7
- package/dist/dashboard/assets/ProxyPage-J3TBi3Kx.js.gz +0 -0
- package/dist/dashboard/assets/QuotaPage-C1gZS6xr.js +0 -1
- package/dist/dashboard/assets/QuotaPage-C1gZS6xr.js.gz +0 -0
- package/dist/dashboard/assets/SettingsPage-CS86ho5X.js +0 -1
- package/dist/dashboard/assets/SettingsPage-CS86ho5X.js.gz +0 -0
- package/dist/dashboard/assets/SetupPage-DDbMQsoo.js.gz +0 -0
- package/dist/dashboard/assets/SortableList-CB3Z7VTf.js.gz +0 -0
- package/dist/dashboard/assets/StudioPage-D95UCPHI.js.gz +0 -0
- package/dist/dashboard/assets/UsagePage-Cnae9SWA.js +0 -56
- package/dist/dashboard/assets/UsagePage-Cnae9SWA.js.gz +0 -0
- package/dist/dashboard/assets/api-keys-VY68Mhd1.js +0 -1
- package/dist/dashboard/assets/api-keys-VY68Mhd1.js.gz +0 -0
- package/dist/dashboard/assets/app-COlK95oi.css +0 -2
- package/dist/dashboard/assets/app-COlK95oi.css.gz +0 -0
- package/dist/dashboard/assets/assert-Chi7pCnt.js.gz +0 -0
- package/dist/dashboard/assets/badge-D4pQapKj.js.gz +0 -0
- package/dist/dashboard/assets/banner-DH6cg-_B.js.gz +0 -0
- package/dist/dashboard/assets/card-BKqlUOe4.js.gz +0 -0
- package/dist/dashboard/assets/cli-tools-fTgljTGA.js.gz +0 -0
- package/dist/dashboard/assets/clipboard-button-D5Ys_sM0.js.gz +0 -0
- package/dist/dashboard/assets/dialog-BYj-8WuK.js +0 -1
- package/dist/dashboard/assets/dialog-BYj-8WuK.js.gz +0 -0
- package/dist/dashboard/assets/drawer-CmXIZ6H1.js +0 -1
- package/dist/dashboard/assets/drawer-CmXIZ6H1.js.gz +0 -0
- package/dist/dashboard/assets/entry-CBg5zeu8.js +0 -1
- package/dist/dashboard/assets/entry-CBg5zeu8.js.gz +0 -0
- package/dist/dashboard/assets/entry-CQBr6U9c.js.gz +0 -0
- package/dist/dashboard/assets/entry-CgdSAzEV.js +0 -4
- package/dist/dashboard/assets/entry-CgdSAzEV.js.gz +0 -0
- package/dist/dashboard/assets/fingerprint-pattern-BWuHtbW4.js.gz +0 -0
- package/dist/dashboard/assets/github-badge-BlptL8Uf.js.gz +0 -0
- package/dist/dashboard/assets/index-js6TJvdr.js.gz +0 -0
- package/dist/dashboard/assets/inline-DUWDSmDV.js.gz +0 -0
- package/dist/dashboard/assets/input-LVjgZq4f.js.gz +0 -0
- package/dist/dashboard/assets/layout-CPfxpsfy.js +0 -1
- package/dist/dashboard/assets/layout-CPfxpsfy.js.gz +0 -0
- package/dist/dashboard/assets/list-row-DupvLfDx.js.gz +0 -0
- package/dist/dashboard/assets/live-C4pEJUqI.js.gz +0 -0
- package/dist/dashboard/assets/model-bans-BRiuufkB.js.gz +0 -0
- package/dist/dashboard/assets/mutation-B4ROwk8L.js.gz +0 -0
- package/dist/dashboard/assets/numbers-YNcHvRY7.js +0 -2
- package/dist/dashboard/assets/numbers-YNcHvRY7.js.gz +0 -0
- package/dist/dashboard/assets/query-keys-gmjzC3RS.js.gz +0 -0
- package/dist/dashboard/assets/routing-C7tBT5o-.js.gz +0 -0
- package/dist/dashboard/assets/select-WUCXorXw.js.gz +0 -0
- package/dist/dashboard/assets/settings-C_zKl1GS.js.gz +0 -0
- package/dist/dashboard/assets/stack-_3O0F8Xn.js.gz +0 -0
- package/dist/dashboard/assets/state-BgTxHcJS.js.gz +0 -0
- package/dist/dashboard/assets/switch-DEjTtOWf.js.gz +0 -0
- package/dist/dashboard/assets/tokens-DDxMbSRL.js +0 -1
- package/dist/dashboard/assets/tokens-DDxMbSRL.js.gz +0 -0
- package/dist/dashboard/assets/use-presence-BSZEBx7U.js +0 -1
- package/dist/dashboard/assets/use-presence-BSZEBx7U.js.gz +0 -0
- package/dist/dashboard/assets/useMutation-BWSC0UAS.js.gz +0 -0
- package/scripts/cli/command/admin.ts +0 -65
- package/scripts/cli/command/doctor.ts +0 -257
- package/scripts/cli/gateway/drain-token.ts +0 -78
- package/scripts/cli/gateway/instance-lock.ts +0 -105
- package/scripts/cli/gateway/stop.ts +0 -51
- package/scripts/cli/support/bun.ts +0 -27
- package/scripts/cli/support/version.ts +0 -37
- /package/dist/dashboard/assets/{app-DCQZRmHY.js → app-BIt446yE.js} +0 -0
- /package/dist/dashboard/assets/{app-DCQZRmHY.js.gz → app-BIt446yE.js.gz} +0 -0
- /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/
|
|
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
|
|
170
|
-
//
|
|
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
|
|
354
|
-
*
|
|
355
|
-
*
|
|
356
|
-
*
|
|
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
|
|
462
|
-
//
|
|
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
|
|
131
|
-
//
|
|
132
|
-
//
|
|
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: `
|
|
17
|
-
* level
|
|
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
|
|
120
|
-
//
|
|
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
|
-
*
|
|
138
|
-
*
|
|
139
|
-
*
|
|
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
|
-
*
|
|
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
|
|
77
|
-
*
|
|
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,
|