cartethyia 2.5.9 → 2.6.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.env.example +15 -42
- package/README.md +11 -10
- package/dist/dashboard/assets/{BannedPage-Brvuj_Eb.js → BannedPage-l6XMnfr9.js} +1 -1
- package/dist/dashboard/assets/BannedPage-l6XMnfr9.js.gz +0 -0
- package/dist/dashboard/assets/{CliToolDetailPage-CqfXtI3y.js → CliToolDetailPage-DwOzAW45.js} +1 -1
- package/dist/dashboard/assets/CliToolDetailPage-DwOzAW45.js.gz +0 -0
- package/dist/dashboard/assets/{CliToolsPage-GQeS91bg.js → CliToolsPage-BT3Up3QA.js} +1 -1
- package/dist/dashboard/assets/CliToolsPage-BT3Up3QA.js.gz +0 -0
- package/dist/dashboard/assets/{CombosPage-BDRsTXBB.js → CombosPage-6kYIUpzR.js} +1 -1
- package/dist/dashboard/assets/CombosPage-6kYIUpzR.js.gz +0 -0
- package/dist/dashboard/assets/{ConsoleLogPage-zP8AFHX0.js → ConsoleLogPage-zRnj1mlR.js} +1 -1
- package/dist/dashboard/assets/ConsoleLogPage-zRnj1mlR.js.gz +0 -0
- package/dist/dashboard/assets/{CustomizationPage-BmfrNZ14.js → CustomizationPage-C7m-H1Rz.js} +1 -1
- package/dist/dashboard/assets/CustomizationPage-C7m-H1Rz.js.gz +0 -0
- package/dist/dashboard/assets/HealthEventsModal-C8MR_BdB.js +1 -0
- package/dist/dashboard/assets/HealthEventsModal-C8MR_BdB.js.gz +0 -0
- package/dist/dashboard/assets/{LoginPage-DoreIgm8.js → LoginPage-CbphLvjg.js} +1 -1
- package/dist/dashboard/assets/LoginPage-CbphLvjg.js.gz +0 -0
- package/dist/dashboard/assets/{ModelPicker-C7pDb41R.js → ModelPicker-DVny4S5_.js} +1 -1
- package/dist/dashboard/assets/ModelPicker-DVny4S5_.js.gz +0 -0
- package/dist/dashboard/assets/OverviewPage-DABu2Wgt.js +1 -0
- package/dist/dashboard/assets/OverviewPage-DABu2Wgt.js.gz +0 -0
- package/dist/dashboard/assets/ProviderDetailPage-C15x9q13.js +3 -0
- package/dist/dashboard/assets/ProviderDetailPage-C15x9q13.js.gz +0 -0
- package/dist/dashboard/assets/{ProvidersPage-BnoPgA7w.js → ProvidersPage-Dfb2896L.js} +1 -1
- package/dist/dashboard/assets/ProvidersPage-Dfb2896L.js.gz +0 -0
- package/dist/dashboard/assets/{ProxyPage-Bq_TGgdb.js → ProxyPage-BaCIdAUt.js} +1 -1
- package/dist/dashboard/assets/ProxyPage-BaCIdAUt.js.gz +0 -0
- package/dist/dashboard/assets/QuotaPage-BsdoCIw1.js +1 -0
- package/dist/dashboard/assets/QuotaPage-BsdoCIw1.js.gz +0 -0
- package/dist/dashboard/assets/SettingsPage-KVJFh3Ip.js +1 -0
- package/dist/dashboard/assets/SettingsPage-KVJFh3Ip.js.gz +0 -0
- package/dist/dashboard/assets/{SetupPage-Day3mCOn.js → SetupPage-D4g6vOGc.js} +1 -1
- package/dist/dashboard/assets/SetupPage-D4g6vOGc.js.gz +0 -0
- package/dist/dashboard/assets/{SortableList-DQxllnB5.js → SortableList-C93i14BD.js} +1 -1
- package/dist/dashboard/assets/SortableList-C93i14BD.js.gz +0 -0
- package/dist/dashboard/assets/{StudioPage-uMExoWu1.js → StudioPage-SgQpymjm.js} +1 -1
- package/dist/dashboard/assets/{StudioPage-uMExoWu1.js.gz → StudioPage-SgQpymjm.js.gz} +0 -0
- package/dist/dashboard/assets/{UsagePage-DXa1oR-4.js → UsagePage-BFME-eba.js} +2 -2
- package/dist/dashboard/assets/UsagePage-BFME-eba.js.gz +0 -0
- package/dist/dashboard/assets/{api-keys-DQYLQxpW.js → api-keys-C23S9sgp.js} +1 -1
- package/dist/dashboard/assets/api-keys-C23S9sgp.js.gz +0 -0
- package/dist/dashboard/assets/{app-BIt446yE.js → app-2KhtHCv9.js} +1 -1
- package/dist/dashboard/assets/{app-BIt446yE.js.gz → app-2KhtHCv9.js.gz} +0 -0
- package/dist/dashboard/assets/app-CEeaMHOy.css +2 -0
- package/dist/dashboard/assets/app-CEeaMHOy.css.gz +0 -0
- package/dist/dashboard/assets/{assert-C9FD_JwJ.js → assert-DU6qrMA_.js} +1 -1
- package/dist/dashboard/assets/assert-DU6qrMA_.js.gz +0 -0
- package/dist/dashboard/assets/{badge-CR72dasT.js → badge-CKsF123d.js} +1 -1
- package/dist/dashboard/assets/badge-CKsF123d.js.gz +0 -0
- package/dist/dashboard/assets/{banner-CI9_eSfO.js → banner-8j7uChA9.js} +1 -1
- package/dist/dashboard/assets/banner-8j7uChA9.js.gz +0 -0
- package/dist/dashboard/assets/{card-DS7CQEsO.js → card-CdsSlWA3.js} +1 -1
- package/dist/dashboard/assets/card-CdsSlWA3.js.gz +0 -0
- package/dist/dashboard/assets/{cli-tools-CNqzrfdb.js → cli-tools-BaFUuFFF.js} +1 -1
- package/dist/dashboard/assets/cli-tools-BaFUuFFF.js.gz +0 -0
- package/dist/dashboard/assets/{clipboard-button-611pSsaX.js → clipboard-button-uL5aYLfW.js} +1 -1
- package/dist/dashboard/assets/clipboard-button-uL5aYLfW.js.gz +0 -0
- package/dist/dashboard/assets/{dialog-k37exjNt.js → dialog-C07hr5_G.js} +1 -1
- package/dist/dashboard/assets/dialog-C07hr5_G.js.gz +0 -0
- package/dist/dashboard/assets/{drawer-CX7N8ucR.js → drawer-BPO1TcKH.js} +1 -1
- package/dist/dashboard/assets/drawer-BPO1TcKH.js.gz +0 -0
- package/dist/dashboard/assets/{entry-CTP8AyiS.js → entry-Bz_CtDfC.js} +1 -1
- package/dist/dashboard/assets/entry-Bz_CtDfC.js.gz +0 -0
- package/dist/dashboard/assets/entry-CwPRKk0d.js +4 -0
- package/dist/dashboard/assets/entry-CwPRKk0d.js.gz +0 -0
- package/dist/dashboard/assets/entry-GJRbmrpA.js +1 -0
- package/dist/dashboard/assets/entry-GJRbmrpA.js.gz +0 -0
- package/dist/dashboard/assets/{github-badge-sltk1qUi.js → github-badge-DmQreSDd.js} +1 -1
- package/dist/dashboard/assets/github-badge-DmQreSDd.js.gz +0 -0
- package/dist/dashboard/assets/{index-z3zDOSMM.js → index-Bblu6erR.js} +2 -2
- package/dist/dashboard/assets/index-Bblu6erR.js.gz +0 -0
- package/dist/dashboard/assets/{inline-BhY2sSnO.js → inline-C9dCYaBZ.js} +1 -1
- package/dist/dashboard/assets/inline-C9dCYaBZ.js.gz +0 -0
- package/dist/dashboard/assets/{input-V1umFiBk.js → input-CjCnXweJ.js} +1 -1
- package/dist/dashboard/assets/input-CjCnXweJ.js.gz +0 -0
- package/dist/dashboard/assets/{layout-BBOYycyS.js → layout-V6y8DAkl.js} +1 -1
- package/dist/dashboard/assets/layout-V6y8DAkl.js.gz +0 -0
- package/dist/dashboard/assets/{list-row-1zzZYM0B.js → list-row-CM1AaG_y.js} +1 -1
- package/dist/dashboard/assets/list-row-CM1AaG_y.js.gz +0 -0
- package/dist/dashboard/assets/{live-BivGfuUs.js → live-ta6GMb-8.js} +1 -1
- package/dist/dashboard/assets/live-ta6GMb-8.js.gz +0 -0
- package/dist/dashboard/assets/{model-bans-CmffjLuG.js → model-bans-BmYTmbAk.js} +1 -1
- package/dist/dashboard/assets/model-bans-BmYTmbAk.js.gz +0 -0
- package/dist/dashboard/assets/{mutation-46P0oWMe.js → mutation-g_fduiNw.js} +1 -1
- package/dist/dashboard/assets/mutation-g_fduiNw.js.gz +0 -0
- package/dist/dashboard/assets/{numbers-DkfMR5Qs.js → numbers-BOevJzdz.js} +1 -1
- package/dist/dashboard/assets/numbers-BOevJzdz.js.gz +0 -0
- package/dist/dashboard/assets/{query-keys-BFhGpwR1.js → query-keys-UETqfytp.js} +1 -1
- package/dist/dashboard/assets/query-keys-UETqfytp.js.gz +0 -0
- package/dist/dashboard/assets/{routing-CAb4F37R.js → routing-BPUo-W8d.js} +1 -1
- package/dist/dashboard/assets/routing-BPUo-W8d.js.gz +0 -0
- package/dist/dashboard/assets/{select-C6MMiaio.js → select-C980c4cz.js} +1 -1
- package/dist/dashboard/assets/select-C980c4cz.js.gz +0 -0
- package/dist/dashboard/assets/{settings-DNX9sydi.js → settings--3hxSyry.js} +1 -1
- package/dist/dashboard/assets/settings--3hxSyry.js.gz +0 -0
- package/dist/dashboard/assets/{stack-BHTfXdz_.js → stack-_gfTdLVB.js} +1 -1
- package/dist/dashboard/assets/stack-_gfTdLVB.js.gz +0 -0
- package/dist/dashboard/assets/{state-mPDRlQkl.js → state-2c5V4zo7.js} +1 -1
- package/dist/dashboard/assets/state-2c5V4zo7.js.gz +0 -0
- package/dist/dashboard/assets/{switch-DOmJxg7H.js → switch-Dkke7Naf.js} +1 -1
- package/dist/dashboard/assets/switch-Dkke7Naf.js.gz +0 -0
- package/dist/dashboard/assets/{useMutation-Dem0lXQj.js → useMutation-EDFRkn1i.js} +1 -1
- package/dist/dashboard/assets/useMutation-EDFRkn1i.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 +51 -51
- package/migrations/README.md +52 -52
- package/migrations/full-postgres/0020_add_telemetry_event_analytics.sql +77 -0
- package/migrations/full-postgres/0021_add_api_key_concurrency_tolerance.sql +13 -0
- package/migrations/full-postgres/0022_add_inferhub_config_key.sql +17 -0
- package/migrations/full-postgres/0023_drop_api_key_concurrency_tolerance.sql +13 -0
- package/migrations/lite-sqlite3/0020_add_telemetry_event_analytics.sql +69 -0
- package/migrations/lite-sqlite3/0021_add_api_key_concurrency_tolerance.sql +13 -0
- package/migrations/lite-sqlite3/0022_add_inferhub_config_key.sql +17 -0
- package/migrations/lite-sqlite3/0023_drop_api_key_concurrency_tolerance.sql +13 -0
- package/package.json +96 -97
- package/scripts/cli/admin.ts +126 -125
- package/scripts/cli/autostart.ts +237 -237
- package/scripts/cli/background.ts +120 -110
- package/scripts/cli/bin/carte.cjs +8 -8
- package/scripts/cli/bin/cartethyia.cjs +167 -167
- package/scripts/cli/console-menu.ts +308 -275
- package/scripts/cli/controller.ts +145 -0
- package/scripts/cli/doctor.ts +1 -1
- package/scripts/cli/env.ts +99 -99
- package/scripts/cli/gateway.ts +264 -261
- package/scripts/{health.ts → cli/health.ts} +4 -4
- package/scripts/cli/instance.ts +234 -224
- package/scripts/cli/main.ts +188 -164
- package/scripts/cli/platform.ts +64 -64
- package/scripts/cli/prompt.ts +275 -107
- package/scripts/cli/services.ts +88 -88
- package/scripts/cli/setup.ts +445 -445
- package/scripts/cli/start.ts +85 -82
- package/scripts/cli/startup-signal.ts +23 -23
- package/scripts/cli/telemetry.ts +70 -0
- package/scripts/cli/tray/index.ts +263 -263
- package/scripts/cli/tray/windows.ps1 +107 -107
- package/scripts/cli/tray.ts +192 -208
- package/scripts/cli/update.ts +39 -39
- package/scripts/cli/version.ts +67 -67
- package/scripts/cli/wordmark.ts +58 -58
- package/src/boot/app.ts +16 -1
- package/src/boot/dependencies.ts +128 -25
- package/src/config/package-paths.ts +24 -0
- package/src/config/resources.ts +137 -0
- package/src/config/runtime.ts +133 -76
- package/src/config/spec.ts +30 -29
- package/src/config/telemetry.ts +0 -25
- package/src/console/api-keys/bucket-spend.ts +14 -17
- package/src/console/api-keys/contracts.ts +410 -410
- package/src/console/api-keys/routes.ts +12 -0
- package/src/console/audit/store.ts +2 -3
- package/src/console/auth/setup-service.ts +8 -1
- package/src/console/backup/routes.ts +7 -0
- package/src/console/backup/service.ts +463 -463
- package/src/console/backup/store.ts +16 -11
- package/src/console/backup/validate.ts +1 -4
- package/src/console/cli-tools/fs-ops.ts +18 -2
- package/src/console/data-management/contracts.ts +1 -8
- package/src/console/data-management/operations.ts +61 -48
- package/src/console/data-management/routes.ts +4 -2
- package/src/console/domain-registration.ts +25 -3
- package/src/console/inferhub/client.ts +139 -0
- package/src/console/inferhub/key-store.ts +129 -0
- package/src/console/inferhub/normalize.ts +347 -0
- package/src/console/inferhub/routes.ts +411 -0
- package/src/console/inferhub/store.ts +109 -0
- package/src/console/inferhub/types.ts +183 -0
- package/src/console/observability/contracts.ts +2 -41
- package/src/console/observability/store.ts +176 -371
- package/src/console/providers/catalog/public-model-store.ts +0 -0
- package/src/console/providers/catalog/store.ts +23 -36
- package/src/console/quota/account-quota-shared.ts +52 -52
- package/src/console/quota/account-quota-tenant.ts +16 -24
- package/src/console/quota/refresh.ts +441 -441
- package/src/console/remote-support/service.ts +270 -114
- package/src/console/routing/pools/import-parse.ts +206 -206
- package/src/console/routing/pools/operations.ts +543 -543
- package/src/console/routing/pools/probe-verdict.ts +132 -132
- package/src/console/routing/pools/routes.ts +181 -181
- package/src/console/routing/pools/store.ts +1 -1
- package/src/console/settings/contracts.ts +42 -40
- package/src/console/settings/store.ts +25 -25
- package/src/console/share/share-stats.ts +22 -4
- package/src/console/share/share-usage.ts +30 -4
- package/src/console/shared/cursor.ts +15 -0
- package/src/console/updates/repository-updates.ts +40 -39
- package/src/console/updates/routes.ts +12 -0
- package/src/main.ts +13 -5
- package/src/network/pool/resolver.ts +1104 -1104
- package/src/observability/client-name.ts +22 -0
- package/src/observability/event-log.ts +5 -29
- package/src/observability/event-query.ts +690 -0
- package/src/observability/log-ring.ts +26 -2
- package/src/observability/log-sanitize.ts +118 -0
- package/src/observability/logger.ts +17 -13
- package/src/observability/metrics.ts +35 -0
- package/src/observability/payload-capture.ts +114 -41
- package/src/observability/payload-store.ts +249 -24
- package/src/observability/raw-event-writer.ts +118 -0
- package/src/observability/runtime-metrics.ts +71 -1
- package/src/observability/telemetry-buffer.ts +100 -17
- package/src/observability/telemetry-reconciliation.ts +417 -0
- package/src/persistence/boot.ts +20 -9
- package/src/persistence/db-pg.ts +13 -0
- package/src/persistence/db-sqlite.ts +569 -497
- package/src/persistence/dialect.ts +3 -2
- package/src/persistence/migrate.ts +181 -181
- package/src/persistence/schema.ts +1496 -1406
- package/src/persistence/shared-cache.ts +5 -1
- package/src/persistence/stores/api-key-store.ts +581 -581
- package/src/persistence/stores/telemetry-store.ts +124 -63
- package/src/protocol/reasoning-text.ts +30 -0
- package/src/protocol/registry.ts +2 -0
- package/src/protocol/response/chat.ts +33 -7
- package/src/protocol/response/codex.ts +14 -2
- package/src/protocol/response/messages.ts +5 -2
- package/src/providers/authentication/oauth-flow-store.ts +3 -10
- package/src/providers/bundled-discovery-providers.ts +122 -122
- package/src/providers/default-registry.ts +14 -0
- package/src/providers/discovery/catalog/catalog-blob.ts +18 -18
- package/src/providers/discovery/models-dev-catalog.ts +354 -354
- package/src/providers/discovery/probe-phases.ts +43 -7
- package/src/providers/discovery/probing-service.ts +70 -32
- package/src/providers/integrations/antigravity/antigravity.ts +9 -2
- package/src/providers/integrations/buddy/buddy-catalog-shared.ts +85 -17
- package/src/providers/integrations/buddy/buddy-chat-shared.ts +934 -934
- package/src/providers/integrations/buddy/buddy-checkin.ts +5 -9
- package/src/providers/integrations/buddy/buddy-discovery-shared.ts +3 -7
- package/src/providers/integrations/buddy/buddy-quota-shared.ts +3 -1
- package/src/providers/integrations/buddy/capabilities.ts +4 -0
- package/src/providers/integrations/buddy/codebuddy/cn-provider.ts +57 -49
- package/src/providers/integrations/buddy/codebuddy/provider.ts +180 -180
- package/src/providers/integrations/buddy/workbuddy/provider.ts +206 -206
- package/src/providers/integrations/buddy/workbuddy/quota.ts +161 -161
- package/src/providers/integrations/claude/claude-quota.ts +2 -2
- package/src/providers/integrations/claude/claude-user-profiles.ts +3 -2
- package/src/providers/integrations/claude/claude.ts +3 -4
- package/src/providers/integrations/cline/cline.ts +94 -8
- package/src/providers/integrations/codex/codex.ts +61 -37
- package/src/providers/integrations/cursor/cursor-catalog.ts +368 -368
- package/src/providers/integrations/cursor/cursor-http2.ts +300 -300
- package/src/providers/integrations/cursor/cursor-identity.ts +2 -4
- package/src/providers/integrations/cursor/cursor-request.ts +11 -0
- package/src/providers/integrations/cursor/cursor.ts +29 -17
- package/src/providers/integrations/cursor/protocol/framing.ts +129 -129
- package/src/providers/integrations/cursor/protocol/messages.ts +2380 -2380
- package/src/providers/integrations/cursor/protocol/struct.ts +305 -305
- package/src/providers/integrations/cursor/protocol/tool-canonical.ts +275 -0
- package/src/providers/integrations/github/github.ts +3 -1
- package/src/providers/integrations/grok/grok-quota.ts +279 -279
- package/src/providers/integrations/grok/grok-ratelimit-store.ts +73 -73
- package/src/providers/integrations/grok/grok-ratelimit.ts +218 -218
- package/src/providers/integrations/hermes.ts +7 -1
- package/src/providers/integrations/inferhub.ts +201 -73
- package/src/providers/integrations/kilo/kilo.ts +152 -152
- package/src/providers/integrations/kiro/kiro-machine-id.ts +2 -4
- package/src/providers/integrations/opencode/provider.ts +2 -0
- package/src/providers/integrations/openrouter/provider.ts +129 -129
- package/src/providers/integrations/qoder/qoder-quota.ts +193 -192
- package/src/providers/integrations/qoder/qoder-realm.ts +106 -106
- package/src/providers/integrations/trae/trae-headers.ts +17 -48
- package/src/providers/integrations/trae/trae-oauth.ts +4 -26
- package/src/providers/integrations/trae/trae-quota.ts +389 -384
- package/src/providers/integrations/trae/trae-realm.ts +15 -2
- package/src/providers/integrations/trae/trae.ts +22 -24
- package/src/providers/integrations/xiaomi-mimo/think-stream.ts +100 -24
- package/src/providers/integrations/zcode/oauth.ts +1 -1
- package/src/providers/operations/account-health-service.ts +605 -279
- package/src/providers/provider-metadata.ts +12 -1
- package/src/providers/provider-model-definition.ts +373 -373
- package/src/providers/provider-probe.ts +3 -0
- package/src/providers/quota/quota-contracts.ts +3 -7
- package/src/providers/search/search-provider.ts +2 -6
- package/src/security/admission/contracts.ts +10 -0
- package/src/security/admission/in-memory-store.ts +89 -14
- package/src/security/admission/lua.ts +191 -182
- package/src/security/admission/redis-store.ts +30 -0
- package/src/security/admission/service.ts +18 -0
- package/src/security/api-key-auth-cache.ts +96 -0
- package/src/security/api-key-auth.ts +11 -100
- package/src/security/client-router-fingerprint.ts +3 -7
- package/src/security/ip-boundary.ts +7 -3
- package/src/shared/coerce.ts +67 -0
- package/src/shared/hash.ts +6 -0
- package/src/shared/time.ts +7 -0
- package/src/transport/dispatch/attempt-finalize.ts +26 -138
- package/src/transport/dispatch/attempt-loop.ts +103 -81
- package/src/transport/dispatch/capture-policy.ts +78 -0
- package/src/transport/dispatch/proxy-request.ts +4 -2
- package/src/transport/dispatch/streaming/encoder.ts +111 -5
- package/src/transport/dispatch/streaming/stream.ts +19 -2
- package/src/transport/dispatch/streaming/thinking-loop.ts +300 -0
- package/src/transport/failure-policy.ts +27 -0
- package/src/transport/guards/abuse-guard.ts +2 -2
- package/src/transport/guards/auth-guard.ts +17 -0
- package/src/transport/guards/body-policy.ts +3 -2
- package/src/transport/guards/concurrency-guard.ts +89 -13
- package/src/transport/guards/early-rejection-capture.ts +43 -3
- package/src/transport/guards/error-lifecycle.ts +10 -2
- package/src/transport/guards/guard-errors.ts +23 -0
- package/src/transport/guards/headroom-guard.ts +41 -7
- package/src/transport/guards/pipeline.ts +24 -0
- package/src/transport/guards/request-context.ts +2 -1
- package/src/transport/routing/admission.ts +51 -9
- package/src/transport/routing/bounded-lru.ts +49 -0
- package/src/transport/routing/candidate-index.ts +88 -0
- package/src/transport/routing/reservations.ts +121 -7
- package/src/transport/routing/round-robin.ts +10 -22
- package/src/transport/routing/route-catalog.ts +26 -13
- package/src/transport/routing/route-model.ts +22 -2
- package/src/transport/routing/router.ts +53 -31
- package/src/transport/surface/chat/encode.ts +2 -3
- package/src/transport/surface/chat/parse.ts +2 -1
- package/src/transport/surface/dialects.ts +11 -3
- package/src/transport/surface/messages/encode.ts +2 -1
- package/src/transport/surface/messages/parse.ts +3 -1
- package/src/transport/surface/messages/stream.ts +2 -6
- package/src/workers/maintenance-lease.ts +200 -0
- package/src/workers/scheduler.ts +101 -1
- package/dist/dashboard/assets/BannedPage-Brvuj_Eb.js.gz +0 -0
- package/dist/dashboard/assets/CliToolDetailPage-CqfXtI3y.js.gz +0 -0
- package/dist/dashboard/assets/CliToolsPage-GQeS91bg.js.gz +0 -0
- package/dist/dashboard/assets/CombosPage-BDRsTXBB.js.gz +0 -0
- package/dist/dashboard/assets/ConsoleLogPage-zP8AFHX0.js.gz +0 -0
- package/dist/dashboard/assets/CustomizationPage-BmfrNZ14.js.gz +0 -0
- package/dist/dashboard/assets/HealthEventsModal-Dn3M6WZc.js +0 -1
- package/dist/dashboard/assets/HealthEventsModal-Dn3M6WZc.js.gz +0 -0
- package/dist/dashboard/assets/LoginPage-DoreIgm8.js.gz +0 -0
- package/dist/dashboard/assets/ModelPicker-C7pDb41R.js.gz +0 -0
- package/dist/dashboard/assets/OverviewPage-CvvD1Ugk.js +0 -1
- package/dist/dashboard/assets/OverviewPage-CvvD1Ugk.js.gz +0 -0
- package/dist/dashboard/assets/ProviderDetailPage-Clbn1gsW.js +0 -3
- package/dist/dashboard/assets/ProviderDetailPage-Clbn1gsW.js.gz +0 -0
- package/dist/dashboard/assets/ProvidersPage-BnoPgA7w.js.gz +0 -0
- package/dist/dashboard/assets/ProxyPage-Bq_TGgdb.js.gz +0 -0
- package/dist/dashboard/assets/QuotaPage-D9DHXvom.js +0 -1
- package/dist/dashboard/assets/QuotaPage-D9DHXvom.js.gz +0 -0
- package/dist/dashboard/assets/SettingsPage-7xV8WkWL.js +0 -1
- package/dist/dashboard/assets/SettingsPage-7xV8WkWL.js.gz +0 -0
- package/dist/dashboard/assets/SetupPage-Day3mCOn.js.gz +0 -0
- package/dist/dashboard/assets/SortableList-DQxllnB5.js.gz +0 -0
- package/dist/dashboard/assets/UsagePage-DXa1oR-4.js.gz +0 -0
- package/dist/dashboard/assets/api-keys-DQYLQxpW.js.gz +0 -0
- package/dist/dashboard/assets/app-CX_Z8cMs.css +0 -2
- package/dist/dashboard/assets/app-CX_Z8cMs.css.gz +0 -0
- package/dist/dashboard/assets/assert-C9FD_JwJ.js.gz +0 -0
- package/dist/dashboard/assets/badge-CR72dasT.js.gz +0 -0
- package/dist/dashboard/assets/banner-CI9_eSfO.js.gz +0 -0
- package/dist/dashboard/assets/card-DS7CQEsO.js.gz +0 -0
- package/dist/dashboard/assets/cli-tools-CNqzrfdb.js.gz +0 -0
- package/dist/dashboard/assets/clipboard-button-611pSsaX.js.gz +0 -0
- package/dist/dashboard/assets/dialog-k37exjNt.js.gz +0 -0
- package/dist/dashboard/assets/drawer-CX7N8ucR.js.gz +0 -0
- package/dist/dashboard/assets/entry-B9Xvtnfj.js +0 -1
- package/dist/dashboard/assets/entry-B9Xvtnfj.js.gz +0 -0
- package/dist/dashboard/assets/entry-ByMGkR3C.js +0 -4
- package/dist/dashboard/assets/entry-ByMGkR3C.js.gz +0 -0
- package/dist/dashboard/assets/entry-CTP8AyiS.js.gz +0 -0
- package/dist/dashboard/assets/github-badge-sltk1qUi.js.gz +0 -0
- package/dist/dashboard/assets/index-z3zDOSMM.js.gz +0 -0
- package/dist/dashboard/assets/inline-BhY2sSnO.js.gz +0 -0
- package/dist/dashboard/assets/input-V1umFiBk.js.gz +0 -0
- package/dist/dashboard/assets/layout-BBOYycyS.js.gz +0 -0
- package/dist/dashboard/assets/list-row-1zzZYM0B.js.gz +0 -0
- package/dist/dashboard/assets/live-BivGfuUs.js.gz +0 -0
- package/dist/dashboard/assets/model-bans-CmffjLuG.js.gz +0 -0
- package/dist/dashboard/assets/mutation-46P0oWMe.js.gz +0 -0
- package/dist/dashboard/assets/numbers-DkfMR5Qs.js.gz +0 -0
- package/dist/dashboard/assets/query-keys-BFhGpwR1.js.gz +0 -0
- package/dist/dashboard/assets/routing-CAb4F37R.js.gz +0 -0
- package/dist/dashboard/assets/select-C6MMiaio.js.gz +0 -0
- package/dist/dashboard/assets/settings-DNX9sydi.js.gz +0 -0
- package/dist/dashboard/assets/stack-BHTfXdz_.js.gz +0 -0
- package/dist/dashboard/assets/state-mPDRlQkl.js.gz +0 -0
- package/dist/dashboard/assets/switch-DOmJxg7H.js.gz +0 -0
- package/dist/dashboard/assets/useMutation-Dem0lXQj.js.gz +0 -0
- package/scripts/build/aot.ts +0 -143
- package/scripts/build/build.ts +0 -24
- package/scripts/build/compile.ts +0 -88
- package/scripts/build/restart.ts +0 -120
- package/scripts/build/start.ts +0 -25
- package/src/observability/event-compaction.ts +0 -317
- package/src/observability/event-day-index.ts +0 -440
|
@@ -1,107 +1,107 @@
|
|
|
1
|
-
# Cartethyia tray icon for Windows using NotifyIcon.
|
|
2
|
-
#
|
|
3
|
-
# Why PowerShell instead of a Node tray library: `systray2` and friends bundle a
|
|
4
|
-
# helper binary and still leave the spawning process attached to a console, so
|
|
5
|
-
# Windows Terminal shows an empty tab for the tray's whole lifetime. A
|
|
6
|
-
# PowerShell NotifyIcon owns no console of its own when started with
|
|
7
|
-
# -WindowStyle Hidden, and the protocol here is plain JSON lines on
|
|
8
|
-
# stdin/stdout, so the parent stays in charge.
|
|
9
|
-
#
|
|
10
|
-
# Protocol: the parent writes one JSON object per line.
|
|
11
|
-
# {"action":"add-item","index":0,"title":"...","enabled":true}
|
|
12
|
-
# {"action":"update-item","index":0,"title":"...","enabled":true}
|
|
13
|
-
# {"action":"set-tooltip","text":"..."}
|
|
14
|
-
# {"action":"kill"}
|
|
15
|
-
# This script writes one JSON object per line:
|
|
16
|
-
# {"type":"started"} {"type":"ready"} {"type":"click","index":0}
|
|
17
|
-
param([string]$IconPath, [string]$Tooltip)
|
|
18
|
-
|
|
19
|
-
$ErrorActionPreference = "Stop"
|
|
20
|
-
|
|
21
|
-
Add-Type -AssemblyName System.Windows.Forms
|
|
22
|
-
Add-Type -AssemblyName System.Drawing
|
|
23
|
-
|
|
24
|
-
[Console]::OutputEncoding = [System.Text.Encoding]::UTF8
|
|
25
|
-
[Console]::InputEncoding = [System.Text.Encoding]::UTF8
|
|
26
|
-
$OutputEncoding = [System.Text.Encoding]::UTF8
|
|
27
|
-
|
|
28
|
-
[System.Windows.Forms.Application]::EnableVisualStyles()
|
|
29
|
-
[System.Windows.Forms.Application]::SetCompatibleTextRenderingDefault($false)
|
|
30
|
-
|
|
31
|
-
$script:notifyIcon = New-Object System.Windows.Forms.NotifyIcon
|
|
32
|
-
$script:notifyIcon.Icon = New-Object System.Drawing.Icon($IconPath)
|
|
33
|
-
$script:notifyIcon.Text = $Tooltip
|
|
34
|
-
$script:notifyIcon.Visible = $true
|
|
35
|
-
|
|
36
|
-
$script:menu = New-Object System.Windows.Forms.ContextMenuStrip
|
|
37
|
-
$script:notifyIcon.ContextMenuStrip = $script:menu
|
|
38
|
-
$script:items = @()
|
|
39
|
-
|
|
40
|
-
function Write-Event($obj) {
|
|
41
|
-
$json = $obj | ConvertTo-Json -Compress
|
|
42
|
-
[Console]::Out.WriteLine($json)
|
|
43
|
-
[Console]::Out.Flush()
|
|
44
|
-
}
|
|
45
|
-
|
|
46
|
-
function Add-MenuItem($index, $title, $enabled) {
|
|
47
|
-
$item = New-Object System.Windows.Forms.ToolStripMenuItem
|
|
48
|
-
$item.Text = $title
|
|
49
|
-
$item.Enabled = $enabled
|
|
50
|
-
$idx = $index
|
|
51
|
-
$item.Add_Click({ Write-Event @{ type = "click"; index = $idx } }.GetNewClosure())
|
|
52
|
-
$script:menu.Items.Add($item) | Out-Null
|
|
53
|
-
$script:items += $item
|
|
54
|
-
}
|
|
55
|
-
|
|
56
|
-
# A real ToolStripSeparator draws the divider. Sending a "-" title instead
|
|
57
|
-
# renders a literal dash row, which is what the menu showed before.
|
|
58
|
-
function Add-MenuSeparator($index) {
|
|
59
|
-
$item = New-Object System.Windows.Forms.ToolStripSeparator
|
|
60
|
-
$script:menu.Items.Add($item) | Out-Null
|
|
61
|
-
$script:items += $item
|
|
62
|
-
}
|
|
63
|
-
|
|
64
|
-
function Update-MenuItem($index, $title, $enabled) {
|
|
65
|
-
if ($index -lt $script:items.Count) {
|
|
66
|
-
$script:items[$index].Text = $title
|
|
67
|
-
$script:items[$index].Enabled = $enabled
|
|
68
|
-
}
|
|
69
|
-
}
|
|
70
|
-
|
|
71
|
-
function Set-Tooltip($text) {
|
|
72
|
-
# NotifyIcon.Text is capped at 63 characters.
|
|
73
|
-
if ($text.Length -gt 63) { $text = $text.Substring(0, 63) }
|
|
74
|
-
$script:notifyIcon.Text = $text
|
|
75
|
-
}
|
|
76
|
-
|
|
77
|
-
# Stdin is polled from the UI thread: a background thread reading Console.In
|
|
78
|
-
# races the message loop and deadlocks when the menu is open.
|
|
79
|
-
$script:timer = New-Object System.Windows.Forms.Timer
|
|
80
|
-
$script:timer.Interval = 100
|
|
81
|
-
$script:timer.Add_Tick({
|
|
82
|
-
try {
|
|
83
|
-
while ([Console]::In.Peek() -ne -1) {
|
|
84
|
-
$line = [Console]::In.ReadLine()
|
|
85
|
-
if ([string]::IsNullOrWhiteSpace($line)) { continue }
|
|
86
|
-
$cmd = $line | ConvertFrom-Json
|
|
87
|
-
switch ($cmd.action) {
|
|
88
|
-
"add-item" { Add-MenuItem $cmd.index $cmd.title $cmd.enabled }
|
|
89
|
-
"add-separator" { Add-MenuSeparator $cmd.index }
|
|
90
|
-
"update-item" { Update-MenuItem $cmd.index $cmd.title $cmd.enabled }
|
|
91
|
-
"set-tooltip" { Set-Tooltip $cmd.text }
|
|
92
|
-
"ready" { Write-Event @{ type = "ready" } }
|
|
93
|
-
"kill" {
|
|
94
|
-
$script:notifyIcon.Visible = $false
|
|
95
|
-
$script:notifyIcon.Dispose()
|
|
96
|
-
[System.Windows.Forms.Application]::Exit()
|
|
97
|
-
}
|
|
98
|
-
}
|
|
99
|
-
}
|
|
100
|
-
} catch {
|
|
101
|
-
Write-Event @{ type = "error"; message = $_.Exception.Message }
|
|
102
|
-
}
|
|
103
|
-
})
|
|
104
|
-
$script:timer.Start()
|
|
105
|
-
|
|
106
|
-
Write-Event @{ type = "started" }
|
|
107
|
-
[System.Windows.Forms.Application]::Run()
|
|
1
|
+
# Cartethyia tray icon for Windows using NotifyIcon.
|
|
2
|
+
#
|
|
3
|
+
# Why PowerShell instead of a Node tray library: `systray2` and friends bundle a
|
|
4
|
+
# helper binary and still leave the spawning process attached to a console, so
|
|
5
|
+
# Windows Terminal shows an empty tab for the tray's whole lifetime. A
|
|
6
|
+
# PowerShell NotifyIcon owns no console of its own when started with
|
|
7
|
+
# -WindowStyle Hidden, and the protocol here is plain JSON lines on
|
|
8
|
+
# stdin/stdout, so the parent stays in charge.
|
|
9
|
+
#
|
|
10
|
+
# Protocol: the parent writes one JSON object per line.
|
|
11
|
+
# {"action":"add-item","index":0,"title":"...","enabled":true}
|
|
12
|
+
# {"action":"update-item","index":0,"title":"...","enabled":true}
|
|
13
|
+
# {"action":"set-tooltip","text":"..."}
|
|
14
|
+
# {"action":"kill"}
|
|
15
|
+
# This script writes one JSON object per line:
|
|
16
|
+
# {"type":"started"} {"type":"ready"} {"type":"click","index":0}
|
|
17
|
+
param([string]$IconPath, [string]$Tooltip)
|
|
18
|
+
|
|
19
|
+
$ErrorActionPreference = "Stop"
|
|
20
|
+
|
|
21
|
+
Add-Type -AssemblyName System.Windows.Forms
|
|
22
|
+
Add-Type -AssemblyName System.Drawing
|
|
23
|
+
|
|
24
|
+
[Console]::OutputEncoding = [System.Text.Encoding]::UTF8
|
|
25
|
+
[Console]::InputEncoding = [System.Text.Encoding]::UTF8
|
|
26
|
+
$OutputEncoding = [System.Text.Encoding]::UTF8
|
|
27
|
+
|
|
28
|
+
[System.Windows.Forms.Application]::EnableVisualStyles()
|
|
29
|
+
[System.Windows.Forms.Application]::SetCompatibleTextRenderingDefault($false)
|
|
30
|
+
|
|
31
|
+
$script:notifyIcon = New-Object System.Windows.Forms.NotifyIcon
|
|
32
|
+
$script:notifyIcon.Icon = New-Object System.Drawing.Icon($IconPath)
|
|
33
|
+
$script:notifyIcon.Text = $Tooltip
|
|
34
|
+
$script:notifyIcon.Visible = $true
|
|
35
|
+
|
|
36
|
+
$script:menu = New-Object System.Windows.Forms.ContextMenuStrip
|
|
37
|
+
$script:notifyIcon.ContextMenuStrip = $script:menu
|
|
38
|
+
$script:items = @()
|
|
39
|
+
|
|
40
|
+
function Write-Event($obj) {
|
|
41
|
+
$json = $obj | ConvertTo-Json -Compress
|
|
42
|
+
[Console]::Out.WriteLine($json)
|
|
43
|
+
[Console]::Out.Flush()
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
function Add-MenuItem($index, $title, $enabled) {
|
|
47
|
+
$item = New-Object System.Windows.Forms.ToolStripMenuItem
|
|
48
|
+
$item.Text = $title
|
|
49
|
+
$item.Enabled = $enabled
|
|
50
|
+
$idx = $index
|
|
51
|
+
$item.Add_Click({ Write-Event @{ type = "click"; index = $idx } }.GetNewClosure())
|
|
52
|
+
$script:menu.Items.Add($item) | Out-Null
|
|
53
|
+
$script:items += $item
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
# A real ToolStripSeparator draws the divider. Sending a "-" title instead
|
|
57
|
+
# renders a literal dash row, which is what the menu showed before.
|
|
58
|
+
function Add-MenuSeparator($index) {
|
|
59
|
+
$item = New-Object System.Windows.Forms.ToolStripSeparator
|
|
60
|
+
$script:menu.Items.Add($item) | Out-Null
|
|
61
|
+
$script:items += $item
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
function Update-MenuItem($index, $title, $enabled) {
|
|
65
|
+
if ($index -lt $script:items.Count) {
|
|
66
|
+
$script:items[$index].Text = $title
|
|
67
|
+
$script:items[$index].Enabled = $enabled
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
function Set-Tooltip($text) {
|
|
72
|
+
# NotifyIcon.Text is capped at 63 characters.
|
|
73
|
+
if ($text.Length -gt 63) { $text = $text.Substring(0, 63) }
|
|
74
|
+
$script:notifyIcon.Text = $text
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
# Stdin is polled from the UI thread: a background thread reading Console.In
|
|
78
|
+
# races the message loop and deadlocks when the menu is open.
|
|
79
|
+
$script:timer = New-Object System.Windows.Forms.Timer
|
|
80
|
+
$script:timer.Interval = 100
|
|
81
|
+
$script:timer.Add_Tick({
|
|
82
|
+
try {
|
|
83
|
+
while ([Console]::In.Peek() -ne -1) {
|
|
84
|
+
$line = [Console]::In.ReadLine()
|
|
85
|
+
if ([string]::IsNullOrWhiteSpace($line)) { continue }
|
|
86
|
+
$cmd = $line | ConvertFrom-Json
|
|
87
|
+
switch ($cmd.action) {
|
|
88
|
+
"add-item" { Add-MenuItem $cmd.index $cmd.title $cmd.enabled }
|
|
89
|
+
"add-separator" { Add-MenuSeparator $cmd.index }
|
|
90
|
+
"update-item" { Update-MenuItem $cmd.index $cmd.title $cmd.enabled }
|
|
91
|
+
"set-tooltip" { Set-Tooltip $cmd.text }
|
|
92
|
+
"ready" { Write-Event @{ type = "ready" } }
|
|
93
|
+
"kill" {
|
|
94
|
+
$script:notifyIcon.Visible = $false
|
|
95
|
+
$script:notifyIcon.Dispose()
|
|
96
|
+
[System.Windows.Forms.Application]::Exit()
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
} catch {
|
|
101
|
+
Write-Event @{ type = "error"; message = $_.Exception.Message }
|
|
102
|
+
}
|
|
103
|
+
})
|
|
104
|
+
$script:timer.Start()
|
|
105
|
+
|
|
106
|
+
Write-Event @{ type = "started" }
|
|
107
|
+
[System.Windows.Forms.Application]::Run()
|
package/scripts/cli/tray.ts
CHANGED
|
@@ -1,208 +1,192 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import {
|
|
3
|
-
import {
|
|
4
|
-
import {
|
|
5
|
-
import {
|
|
6
|
-
import {
|
|
7
|
-
import {
|
|
8
|
-
import {
|
|
9
|
-
import {
|
|
10
|
-
import {
|
|
11
|
-
import {
|
|
12
|
-
import {
|
|
13
|
-
|
|
14
|
-
/**
|
|
15
|
-
* Runs the gateway with a tray icon. The tray is platform-specific (`tray/`):
|
|
16
|
-
* on Windows it is a PowerShell NotifyIcon, because a Node tray library leaves
|
|
17
|
-
* an empty console tab open for the tray's whole lifetime, and that window is
|
|
18
|
-
* owned by Windows Terminal — not by this process, so it cannot be found and
|
|
19
|
-
* hidden from inside.
|
|
20
|
-
*
|
|
21
|
-
* The gateway it starts is detached, which is what makes the health poll below
|
|
22
|
-
* necessary — see {@link TRAY_HEALTH_POLL_MS}.
|
|
23
|
-
*/
|
|
24
|
-
|
|
25
|
-
/**
|
|
26
|
-
* How often the tray asks whether the gateway is still answering.
|
|
27
|
-
*
|
|
28
|
-
* 10 s: often enough that an operator who stops the gateway by hand sees the
|
|
29
|
-
* icon go away, and far below the cost of a request. A detached gateway can no
|
|
30
|
-
* longer be waited on, so this poll is the only signal left.
|
|
31
|
-
*/
|
|
32
|
-
const TRAY_HEALTH_POLL_MS = 10_000;
|
|
33
|
-
|
|
34
|
-
/**
|
|
35
|
-
* Consecutive misses to forgive before a dead gateway closes the tray.
|
|
36
|
-
*
|
|
37
|
-
* One refusal is not a stopped gateway — a probe can lose a race with startup
|
|
38
|
-
* or a slow request — and closing the icon on one would make startup feel
|
|
39
|
-
* random.
|
|
40
|
-
*/
|
|
41
|
-
const TRAY_HEALTH_MISSES_BEFORE_CLOSE = 3;
|
|
42
|
-
|
|
43
|
-
export async function startTray(): Promise<void> {
|
|
44
|
-
const
|
|
45
|
-
const
|
|
46
|
-
const
|
|
47
|
-
const
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
await close();
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
//
|
|
167
|
-
//
|
|
168
|
-
//
|
|
169
|
-
//
|
|
170
|
-
//
|
|
171
|
-
//
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
}).then((response) => response.ok).catch(() => false);
|
|
194
|
-
if (alive) {
|
|
195
|
-
misses = 0;
|
|
196
|
-
continue;
|
|
197
|
-
}
|
|
198
|
-
misses += 1;
|
|
199
|
-
if (misses < TRAY_HEALTH_MISSES_BEFORE_CLOSE) continue;
|
|
200
|
-
gateway = null;
|
|
201
|
-
console.error("Cartethyia stopped answering; the tray is closing.");
|
|
202
|
-
await close(1);
|
|
203
|
-
}
|
|
204
|
-
} catch (error) {
|
|
205
|
-
await close(1);
|
|
206
|
-
throw error;
|
|
207
|
-
}
|
|
208
|
-
}
|
|
1
|
+
import { resolvePackagePath } from "../../src/config/package-paths";
|
|
2
|
+
import { resolvePort } from "../../src/config/runtime";
|
|
3
|
+
import { ensureDrainToken, attachGateway, stopAttachedGateway, type RunningGateway } from "./instance";
|
|
4
|
+
import { startGateway } from "./gateway";
|
|
5
|
+
import { stopGateway } from "./gateway";
|
|
6
|
+
import { readInstalledVersion } from "./version";
|
|
7
|
+
import { reportTrayStartup } from "./startup-signal";
|
|
8
|
+
import { createTray } from "./tray/index";
|
|
9
|
+
import { openUrl } from "./background";
|
|
10
|
+
import { exportBackup, resetConsoleAccount } from "./admin";
|
|
11
|
+
import { acquireController, inheritedControllerHandoff } from "./controller";
|
|
12
|
+
import { restoreControllerAfterFailedHandoff } from "./controller";
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* Runs the gateway with a tray icon. The tray is platform-specific (`tray/`):
|
|
16
|
+
* on Windows it is a PowerShell NotifyIcon, because a Node tray library leaves
|
|
17
|
+
* an empty console tab open for the tray's whole lifetime, and that window is
|
|
18
|
+
* owned by Windows Terminal — not by this process, so it cannot be found and
|
|
19
|
+
* hidden from inside.
|
|
20
|
+
*
|
|
21
|
+
* The gateway it starts is detached, which is what makes the health poll below
|
|
22
|
+
* necessary — see {@link TRAY_HEALTH_POLL_MS}.
|
|
23
|
+
*/
|
|
24
|
+
|
|
25
|
+
/**
|
|
26
|
+
* How often the tray asks whether the gateway is still answering.
|
|
27
|
+
*
|
|
28
|
+
* 10 s: often enough that an operator who stops the gateway by hand sees the
|
|
29
|
+
* icon go away, and far below the cost of a request. A detached gateway can no
|
|
30
|
+
* longer be waited on, so this poll is the only signal left.
|
|
31
|
+
*/
|
|
32
|
+
const TRAY_HEALTH_POLL_MS = 10_000;
|
|
33
|
+
|
|
34
|
+
/**
|
|
35
|
+
* Consecutive misses to forgive before a dead gateway closes the tray.
|
|
36
|
+
*
|
|
37
|
+
* One refusal is not a stopped gateway — a probe can lose a race with startup
|
|
38
|
+
* or a slow request — and closing the icon on one would make startup feel
|
|
39
|
+
* random.
|
|
40
|
+
*/
|
|
41
|
+
const TRAY_HEALTH_MISSES_BEFORE_CLOSE = 3;
|
|
42
|
+
|
|
43
|
+
export async function startTray(): Promise<void> {
|
|
44
|
+
const handoff = inheritedControllerHandoff();
|
|
45
|
+
const controller = acquireController("tray", handoff);
|
|
46
|
+
const releaseController = (): void => controller.release();
|
|
47
|
+
const failedHandoff = (): void => {
|
|
48
|
+
if (handoff) restoreControllerAfterFailedHandoff(handoff);
|
|
49
|
+
};
|
|
50
|
+
process.once("exit", failedHandoff);
|
|
51
|
+
process.once("exit", releaseController);
|
|
52
|
+
try {
|
|
53
|
+
await runTray();
|
|
54
|
+
} finally {
|
|
55
|
+
process.removeListener("exit", failedHandoff);
|
|
56
|
+
controller.release();
|
|
57
|
+
process.removeListener("exit", releaseController);
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
async function runTray(): Promise<void> {
|
|
62
|
+
const port = resolvePort();
|
|
63
|
+
const drainToken = ensureDrainToken();
|
|
64
|
+
const dashboardUrl = `http://127.0.0.1:${port}/console`;
|
|
65
|
+
const releasesUrl = "https://github.com/risunCodex/Cartethyia/releases";
|
|
66
|
+
const currentVersion = await readInstalledVersion();
|
|
67
|
+
|
|
68
|
+
let gateway: Bun.Subprocess | null = null;
|
|
69
|
+
let adoptedGateway: RunningGateway | null = null;
|
|
70
|
+
let closing = false;
|
|
71
|
+
let resetArmed = false;
|
|
72
|
+
let closePromise: Promise<void> | undefined;
|
|
73
|
+
|
|
74
|
+
const tray = createTray({
|
|
75
|
+
iconPath: resolvePackagePath(
|
|
76
|
+
"scripts",
|
|
77
|
+
"cli",
|
|
78
|
+
"tray",
|
|
79
|
+
"assets",
|
|
80
|
+
process.platform === "win32" ? "cartethyia.ico" : "cartethyia.png",
|
|
81
|
+
),
|
|
82
|
+
tooltip: `Cartethyia ${currentVersion}`,
|
|
83
|
+
items: [
|
|
84
|
+
// Version row is informational; clicking it opens the releases page so
|
|
85
|
+
// the operator can decide for themselves. No background check: the tray
|
|
86
|
+
// used to poll the registry on open and the result was a cue the user
|
|
87
|
+
// did not ask for.
|
|
88
|
+
{ kind: "action", id: "version", title: `Cartethyia ${currentVersion}`, enabled: true },
|
|
89
|
+
{ kind: "separator" },
|
|
90
|
+
{ kind: "action", id: "console", title: `Open Console \u00b7 ${dashboardUrl}` },
|
|
91
|
+
{ kind: "action", id: "reset", title: "Reset console password" },
|
|
92
|
+
{ kind: "action", id: "backup", title: "Backup" },
|
|
93
|
+
{ kind: "separator" },
|
|
94
|
+
{ kind: "action", id: "exit", title: "Exit" },
|
|
95
|
+
],
|
|
96
|
+
onSelect: (id: string) => void handleSelect(id),
|
|
97
|
+
onExit: () => void close(1),
|
|
98
|
+
});
|
|
99
|
+
|
|
100
|
+
const close = (exitCode = 0): Promise<void> => {
|
|
101
|
+
if (closePromise) return closePromise;
|
|
102
|
+
closing = true;
|
|
103
|
+
closePromise = (async () => {
|
|
104
|
+
const current: Bun.Subprocess | null = gateway;
|
|
105
|
+
gateway = null;
|
|
106
|
+
if (current) await stopGateway(current, port, drainToken);
|
|
107
|
+
else if (adoptedGateway !== null) await stopAttachedGateway(adoptedGateway, drainToken);
|
|
108
|
+
await tray.close();
|
|
109
|
+
process.exitCode = exitCode;
|
|
110
|
+
})();
|
|
111
|
+
return closePromise;
|
|
112
|
+
};
|
|
113
|
+
|
|
114
|
+
async function handleSelect(id: string): Promise<void> {
|
|
115
|
+
if (closing) return;
|
|
116
|
+
if (id === "exit") return void (await close());
|
|
117
|
+
if (id === "console") return void (await openUrl(dashboardUrl));
|
|
118
|
+
if (id === "version") return void (await openUrl(releasesUrl));
|
|
119
|
+
if (id === "backup") {
|
|
120
|
+
const result = await exportBackup(port, drainToken);
|
|
121
|
+
if ("error" in result) {
|
|
122
|
+
console.error(result.error);
|
|
123
|
+
} else {
|
|
124
|
+
console.log(
|
|
125
|
+
`Backup written to ${result.path} (${(result.bytes / 1024).toFixed(1)} KiB). Telemetry is not included.`,
|
|
126
|
+
);
|
|
127
|
+
}
|
|
128
|
+
return;
|
|
129
|
+
}
|
|
130
|
+
if (id === "reset") {
|
|
131
|
+
// A tray click cannot ask for the typed confirmation the menu uses, so
|
|
132
|
+
// the first click arms the action and the second one runs it.
|
|
133
|
+
if (!resetArmed) {
|
|
134
|
+
resetArmed = true;
|
|
135
|
+
tray.update("reset", "Reset console password \u00b7 click again to delete", true);
|
|
136
|
+
return;
|
|
137
|
+
}
|
|
138
|
+
resetArmed = false;
|
|
139
|
+
tray.update("reset", "Reset console password", true);
|
|
140
|
+
await resetConsoleAccount(port, drainToken);
|
|
141
|
+
return;
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
process.once("SIGINT", () => void close());
|
|
146
|
+
process.once("SIGTERM", () => void close());
|
|
147
|
+
|
|
148
|
+
try {
|
|
149
|
+
await tray.ready();
|
|
150
|
+
// Detached, unlike every other caller: the gateway must outlive this
|
|
151
|
+
// process. Attached, it dies with the tray — and the tray dies with the
|
|
152
|
+
// terminal that launched it, so closing that terminal took the gateway
|
|
153
|
+
// down too and "Hide to tray" left nothing serving. `stopGateway` drains
|
|
154
|
+
// over HTTP before it ever reaches for `kill()`, so detaching costs the
|
|
155
|
+
// exit path nothing: the tray can still stop the gateway on Exit.
|
|
156
|
+
adoptedGateway = await attachGateway(port, drainToken);
|
|
157
|
+
if (adoptedGateway === null) gateway = await startGateway(port, drainToken, { detached: true });
|
|
158
|
+
// The launcher that spawned this tray is blocked on a signal file: it
|
|
159
|
+
// cannot read our stdio, because the tray is detached. Without this write
|
|
160
|
+
// it waits out its 60 s deadline and reports a failure for a tray that is
|
|
161
|
+
// running fine.
|
|
162
|
+
reportTrayStartup({ status: "ready", url: dashboardUrl });
|
|
163
|
+
console.log(`Cartethyia is running at ${dashboardUrl}`);
|
|
164
|
+
|
|
165
|
+
let misses = 0;
|
|
166
|
+
// A detached child's `exited` never settles on Windows, so the old
|
|
167
|
+
// `await current.exited` would have parked here forever and the tray would
|
|
168
|
+
// never notice the gateway going away. Poll its health endpoint instead:
|
|
169
|
+
// that answers for a process we no longer own, which is the whole point of
|
|
170
|
+
// detaching it. The probe is the same one startup used, so a gateway that
|
|
171
|
+
// is up reads as up regardless of who spawned it.
|
|
172
|
+
while (!closing) {
|
|
173
|
+
await Bun.sleep(TRAY_HEALTH_POLL_MS);
|
|
174
|
+
if (closing || gateway === null && adoptedGateway === null) continue;
|
|
175
|
+
const alive = await fetch(`http://127.0.0.1:${port}/health`, {
|
|
176
|
+
signal: AbortSignal.timeout(2_000),
|
|
177
|
+
}).then((response) => response.ok).catch(() => false);
|
|
178
|
+
if (alive) {
|
|
179
|
+
misses = 0;
|
|
180
|
+
continue;
|
|
181
|
+
}
|
|
182
|
+
misses += 1;
|
|
183
|
+
if (misses < TRAY_HEALTH_MISSES_BEFORE_CLOSE) continue;
|
|
184
|
+
gateway = null;
|
|
185
|
+
console.error("Cartethyia stopped answering; the tray is closing.");
|
|
186
|
+
await close(1);
|
|
187
|
+
}
|
|
188
|
+
} catch (error) {
|
|
189
|
+
await close(1);
|
|
190
|
+
throw error;
|
|
191
|
+
}
|
|
192
|
+
}
|