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,92 +1,70 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Remote-support tunnel service.
|
|
3
3
|
*
|
|
4
|
-
*
|
|
5
|
-
*
|
|
6
|
-
*
|
|
7
|
-
*
|
|
8
|
-
*
|
|
4
|
+
* Launches `cloudflared` or `tailscale` detached from the gateway process, so
|
|
5
|
+
* a gateway restart, a hot reload, or a Ctrl-C in the owning terminal cannot
|
|
6
|
+
* kill an operator's live remote-support session. Earlier revisions shelled
|
|
7
|
+
* out to a `.cmd` + `.ps1` launcher pair; those were Windows-only, had to be
|
|
8
|
+
* carried through the npm tarball, and failed with "Launcher not found" the
|
|
9
|
+
* day the whitelist missed them. This module does the same work with
|
|
10
|
+
* `child_process.spawn` directly and runs on every host Node runs on.
|
|
9
11
|
*
|
|
10
12
|
* This service never installs anything. A missing binary is reported as a
|
|
11
13
|
* blocker with install guidance; downloading a third-party agent on the
|
|
12
14
|
* operator's behalf is out of scope.
|
|
13
15
|
*/
|
|
14
|
-
import { spawn } from "node:child_process";
|
|
15
|
-
import { existsSync } from "node:fs";
|
|
16
|
+
import { spawn, spawnSync } from "node:child_process";
|
|
17
|
+
import { closeSync, existsSync, mkdirSync, openSync, readFileSync, rmSync, writeFileSync } from "node:fs";
|
|
16
18
|
import { join } from "node:path";
|
|
17
|
-
import {
|
|
19
|
+
import { resolveUserConfigDir } from "../../config/package-paths";
|
|
18
20
|
import {
|
|
19
21
|
REMOTE_SUPPORT_INSTALL_HINT,
|
|
22
|
+
REMOTE_SUPPORT_TOOLS,
|
|
20
23
|
type RemoteSupportBlocker,
|
|
21
24
|
type RemoteSupportStatus,
|
|
22
25
|
type RemoteSupportTool,
|
|
23
26
|
} from "./contracts";
|
|
24
27
|
|
|
25
|
-
/** How long the
|
|
26
|
-
const
|
|
28
|
+
/** How long to tail the publisher's log before giving up on a URL. */
|
|
29
|
+
const URL_WAIT_MS = 25_000;
|
|
30
|
+
/** How often to re-scan the log file while waiting for the URL. */
|
|
31
|
+
const URL_POLL_MS = 500;
|
|
27
32
|
|
|
28
|
-
/** Cached install detection so we don't spawn `where` on every
|
|
33
|
+
/** Cached install detection so we don't spawn `where`/`which` on every poll. */
|
|
29
34
|
const installedCache = new Map<RemoteSupportTool, boolean>();
|
|
30
35
|
|
|
31
36
|
export interface RemoteSupportServiceConfig {
|
|
32
37
|
/** Defaults to the gateway's configured port. */
|
|
33
38
|
readonly port: number;
|
|
34
|
-
/** Overridable for tests
|
|
35
|
-
readonly
|
|
36
|
-
}
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
// that code reach us as the child's status.
|
|
58
|
-
const command = isWindows ? "cmd.exe" : "powershell";
|
|
59
|
-
const fileName = isWindows ? "remote-support.cmd" : "remote-support.ps1";
|
|
60
|
-
const file = scriptPath(config, fileName);
|
|
61
|
-
const spawnArgs = isWindows
|
|
62
|
-
? ["/d", "/s", "/c", file, ...args]
|
|
63
|
-
: ["-NoProfile", "-ExecutionPolicy", "Bypass", "-File", file, ...args];
|
|
64
|
-
const child = spawn(command, spawnArgs, {
|
|
65
|
-
stdio: ["ignore", "pipe", "pipe"],
|
|
66
|
-
windowsHide: true,
|
|
67
|
-
});
|
|
68
|
-
let stdout = "";
|
|
69
|
-
let stderr = "";
|
|
70
|
-
child.stdout.setEncoding("utf8");
|
|
71
|
-
child.stderr.setEncoding("utf8");
|
|
72
|
-
child.stdout.on("data", (chunk: string) => {
|
|
73
|
-
stdout += chunk;
|
|
74
|
-
});
|
|
75
|
-
child.stderr.on("data", (chunk: string) => {
|
|
76
|
-
stderr += chunk;
|
|
77
|
-
});
|
|
78
|
-
const timer = setTimeout(() => child.kill(), LAUNCH_TIMEOUT_MS);
|
|
79
|
-
child.on("error", reject);
|
|
80
|
-
child.on("close", (status) => {
|
|
81
|
-
clearTimeout(timer);
|
|
82
|
-
resolve({ status: status ?? 1, stdout: `${stdout}${stderr}`.trim() });
|
|
83
|
-
});
|
|
84
|
-
return promise;
|
|
39
|
+
/** Overridable for tests. Defaults to a subdir of the user config dir. */
|
|
40
|
+
readonly stateDir?: string;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
function stateDirOf(config: RemoteSupportServiceConfig): string {
|
|
44
|
+
return config.stateDir ?? join(resolveUserConfigDir(), "remote-support");
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
function ensureStateDir(dir: string): void {
|
|
48
|
+
if (!existsSync(dir)) mkdirSync(dir, { recursive: true });
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
function pidPath(dir: string, tool: RemoteSupportTool): string {
|
|
52
|
+
return join(dir, `${tool}.pid`);
|
|
53
|
+
}
|
|
54
|
+
function urlPath(dir: string, tool: RemoteSupportTool): string {
|
|
55
|
+
return join(dir, `${tool}.url`);
|
|
56
|
+
}
|
|
57
|
+
function logPath(dir: string, tool: RemoteSupportTool): string {
|
|
58
|
+
return join(dir, `${tool}.log`);
|
|
59
|
+
}
|
|
60
|
+
function errPath(dir: string, tool: RemoteSupportTool): string {
|
|
61
|
+
return join(dir, `${tool}.err`);
|
|
85
62
|
}
|
|
86
63
|
|
|
87
64
|
/**
|
|
88
|
-
* `where
|
|
89
|
-
*
|
|
65
|
+
* `where` / `which` lookup, cached per process. Shelled out rather than coded
|
|
66
|
+
* because PATHEXT, Spotlight PATH injections, and nvm's shims make the
|
|
67
|
+
* resolved location non-trivial — the OS's own resolver gets it right.
|
|
90
68
|
*/
|
|
91
69
|
function toolAvailable(tool: RemoteSupportTool): Promise<boolean> {
|
|
92
70
|
const cached = installedCache.get(tool);
|
|
@@ -106,6 +84,179 @@ function toolAvailable(tool: RemoteSupportTool): Promise<boolean> {
|
|
|
106
84
|
return promise;
|
|
107
85
|
}
|
|
108
86
|
|
|
87
|
+
/** Non-blocking check: is `pid` still alive on this host? Cross-OS. */
|
|
88
|
+
function pidAlive(pid: number): boolean {
|
|
89
|
+
try {
|
|
90
|
+
process.kill(pid, 0);
|
|
91
|
+
return true;
|
|
92
|
+
} catch {
|
|
93
|
+
return false;
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
/**
|
|
98
|
+
* Reads the recorded PID file, returning `null` if the file is missing, empty,
|
|
99
|
+
* malformed, or names a process that is no longer alive. Dead files are
|
|
100
|
+
* cleaned up so a later start does not race against a stale record.
|
|
101
|
+
*/
|
|
102
|
+
function readLivePid(dir: string, tool: RemoteSupportTool): number | null {
|
|
103
|
+
const file = pidPath(dir, tool);
|
|
104
|
+
if (!existsSync(file)) return null;
|
|
105
|
+
const raw = readFileSync(file, "utf8").trim();
|
|
106
|
+
const pid = Number.parseInt(raw, 10);
|
|
107
|
+
if (!Number.isFinite(pid) || pid <= 0) {
|
|
108
|
+
rmSync(file, { force: true });
|
|
109
|
+
return null;
|
|
110
|
+
}
|
|
111
|
+
if (!pidAlive(pid)) {
|
|
112
|
+
rmSync(file, { force: true });
|
|
113
|
+
return null;
|
|
114
|
+
}
|
|
115
|
+
return pid;
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
/**
|
|
119
|
+
* Terminates `pid` and its descendants. On Windows the tree has to be killed
|
|
120
|
+
* explicitly via `taskkill /T /F` — plain `process.kill` only signals the
|
|
121
|
+
* top-level process, and cloudflared under Windows spawns a worker that keeps
|
|
122
|
+
* the port open after the parent exits. On POSIX, SIGTERM to the group covers
|
|
123
|
+
* descendants spawned with `detached: true`.
|
|
124
|
+
*/
|
|
125
|
+
function killTree(pid: number): void {
|
|
126
|
+
if (process.platform === "win32") {
|
|
127
|
+
spawnSync("taskkill", ["/PID", String(pid), "/T", "/F"], { stdio: "ignore", windowsHide: true });
|
|
128
|
+
return;
|
|
129
|
+
}
|
|
130
|
+
try {
|
|
131
|
+
process.kill(-pid, "SIGTERM");
|
|
132
|
+
} catch {
|
|
133
|
+
try {
|
|
134
|
+
process.kill(pid, "SIGTERM");
|
|
135
|
+
} catch {
|
|
136
|
+
/* already dead */
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
/** Arguments for the detached publisher launch. Shape mirrors the old script. */
|
|
142
|
+
function launchArgsFor(tool: RemoteSupportTool, port: number): readonly string[] {
|
|
143
|
+
if (tool === "cloudflared") {
|
|
144
|
+
// `--url` prints the fresh *.trycloudflare.com hostname (to stderr).
|
|
145
|
+
return ["tunnel", "--url", `http://127.0.0.1:${port}`, "--no-autoupdate"];
|
|
146
|
+
}
|
|
147
|
+
// `serve --bg` returns immediately; the funnel listens on 443.
|
|
148
|
+
return ["serve", "--bg", "--https=443", `http://127.0.0.1:${port}`];
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
/** The pattern anchors to the tunnel's own hostname so Cloudflare's startup
|
|
152
|
+
* banner (which references `cloudflare.com`) is not mistaken for the URL. */
|
|
153
|
+
function urlPatternFor(tool: RemoteSupportTool): RegExp {
|
|
154
|
+
return tool === "cloudflared"
|
|
155
|
+
? /https:\/\/[A-Za-z0-9-]+\.trycloudflare\.com/
|
|
156
|
+
: /https:\/\/[A-Za-z0-9.-]+\.ts\.net/;
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
/**
|
|
160
|
+
* Tails the publisher's stdout + stderr log files for the URL regex. Resolves
|
|
161
|
+
* with the first match or `null` after the overall deadline.
|
|
162
|
+
*/
|
|
163
|
+
async function waitForUrl(dir: string, tool: RemoteSupportTool): Promise<string | null> {
|
|
164
|
+
const pattern = urlPatternFor(tool);
|
|
165
|
+
const deadline = Date.now() + URL_WAIT_MS;
|
|
166
|
+
const log = logPath(dir, tool);
|
|
167
|
+
const err = errPath(dir, tool);
|
|
168
|
+
while (Date.now() < deadline) {
|
|
169
|
+
for (const path of [log, err]) {
|
|
170
|
+
if (!existsSync(path)) continue;
|
|
171
|
+
const text = readFileSync(path, "utf8");
|
|
172
|
+
const match = pattern.exec(text);
|
|
173
|
+
if (match) return match[0];
|
|
174
|
+
}
|
|
175
|
+
const { promise, resolve } = Promise.withResolvers<void>();
|
|
176
|
+
setTimeout(resolve, URL_POLL_MS);
|
|
177
|
+
await promise;
|
|
178
|
+
}
|
|
179
|
+
return null;
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
/**
|
|
183
|
+
* Last-resort hostname lookup for Tailscale.
|
|
184
|
+
*
|
|
185
|
+
* `tailscale serve --bg` prints nothing useful once it has backgrounded; the
|
|
186
|
+
* live hostname comes from the daemon's own status. The JSON shape has drifted
|
|
187
|
+
* across releases (`DNSName` at the root in some, nested under `Self` in
|
|
188
|
+
* others), so both locations are probed and the first hit wins.
|
|
189
|
+
*/
|
|
190
|
+
function tailscaleUrlFromDaemon(): string | null {
|
|
191
|
+
const result = spawnSync("tailscale", ["serve", "status", "--json"], { encoding: "utf8", windowsHide: true });
|
|
192
|
+
if (result.status !== 0) return null;
|
|
193
|
+
let parsed: unknown;
|
|
194
|
+
try {
|
|
195
|
+
parsed = JSON.parse(result.stdout);
|
|
196
|
+
} catch {
|
|
197
|
+
return null;
|
|
198
|
+
}
|
|
199
|
+
const bag: Record<string, unknown> =
|
|
200
|
+
typeof parsed === "object" && parsed !== null ? (parsed as Record<string, unknown>) : {};
|
|
201
|
+
const direct = typeof bag["DNSName"] === "string" ? (bag["DNSName"] as string) : null;
|
|
202
|
+
const self =
|
|
203
|
+
typeof bag["Self"] === "object" && bag["Self"] !== null ? (bag["Self"] as Record<string, unknown>) : null;
|
|
204
|
+
const nested = self && typeof self["DNSName"] === "string" ? (self["DNSName"] as string) : null;
|
|
205
|
+
const hostname = (direct ?? nested)?.trim().replace(/\/+$/, "");
|
|
206
|
+
return hostname && hostname.length > 0 ? `https://${hostname}` : null;
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
/**
|
|
210
|
+
* Starts `tool` detached, writes its logs to the state dir, records the pid,
|
|
211
|
+
* and waits briefly for the URL. The caller never becomes the publisher's
|
|
212
|
+
* parent — `detached` + `unref` + `ignore`-stdin cuts the inheritance chain.
|
|
213
|
+
*/
|
|
214
|
+
async function launchDetached(
|
|
215
|
+
dir: string,
|
|
216
|
+
tool: RemoteSupportTool,
|
|
217
|
+
port: number,
|
|
218
|
+
): Promise<{ pid: number; url: string | null; error: string | null }> {
|
|
219
|
+
const resolved = spawnSync(process.platform === "win32" ? "where" : "which", [tool], {
|
|
220
|
+
encoding: "utf8",
|
|
221
|
+
windowsHide: true,
|
|
222
|
+
});
|
|
223
|
+
if (resolved.status !== 0) {
|
|
224
|
+
return { pid: 0, url: null, error: `${tool} is not on PATH` };
|
|
225
|
+
}
|
|
226
|
+
const binary = resolved.stdout.split(/\r?\n/).map((line) => line.trim()).find((line) => line.length > 0);
|
|
227
|
+
if (!binary) {
|
|
228
|
+
return { pid: 0, url: null, error: `${tool} is not on PATH` };
|
|
229
|
+
}
|
|
230
|
+
ensureStateDir(dir);
|
|
231
|
+
rmSync(urlPath(dir, tool), { force: true });
|
|
232
|
+
rmSync(logPath(dir, tool), { force: true });
|
|
233
|
+
rmSync(errPath(dir, tool), { force: true });
|
|
234
|
+
const outFd = openSync(logPath(dir, tool), "a");
|
|
235
|
+
const errFd = openSync(errPath(dir, tool), "a");
|
|
236
|
+
try {
|
|
237
|
+
const child = spawn(binary, launchArgsFor(tool, port), {
|
|
238
|
+
detached: true,
|
|
239
|
+
windowsHide: true,
|
|
240
|
+
stdio: ["ignore", outFd, errFd],
|
|
241
|
+
});
|
|
242
|
+
const pid = child.pid;
|
|
243
|
+
if (pid === undefined) {
|
|
244
|
+
return { pid: 0, url: null, error: `failed to launch ${tool}` };
|
|
245
|
+
}
|
|
246
|
+
writeFileSync(pidPath(dir, tool), String(pid), "utf8");
|
|
247
|
+
child.unref();
|
|
248
|
+
let url = await waitForUrl(dir, tool);
|
|
249
|
+
if (!url && tool === "tailscale") {
|
|
250
|
+
url = tailscaleUrlFromDaemon();
|
|
251
|
+
}
|
|
252
|
+
if (url) writeFileSync(urlPath(dir, tool), url, "utf8");
|
|
253
|
+
return { pid, url, error: null };
|
|
254
|
+
} finally {
|
|
255
|
+
closeSync(outFd);
|
|
256
|
+
closeSync(errFd);
|
|
257
|
+
}
|
|
258
|
+
}
|
|
259
|
+
|
|
109
260
|
export interface RemoteSupportService {
|
|
110
261
|
status(): Promise<RemoteSupportStatus>;
|
|
111
262
|
start(tool: RemoteSupportTool, port?: number): Promise<RemoteSupportStatus>;
|
|
@@ -114,37 +265,56 @@ export interface RemoteSupportService {
|
|
|
114
265
|
|
|
115
266
|
export function createRemoteSupportService(config: RemoteSupportServiceConfig): RemoteSupportService {
|
|
116
267
|
const port = config.port;
|
|
268
|
+
const dir = stateDirOf(config);
|
|
117
269
|
|
|
118
|
-
/**
|
|
119
|
-
* Install state for both publishers, cached per process.
|
|
120
|
-
*
|
|
121
|
-
* Every status carries it so the card can show "not installed" without the
|
|
122
|
-
* browser probing on its own — but the probe is `where`/`which`, which is a
|
|
123
|
-
* process spawn, so it is memoised here rather than run on each poll.
|
|
124
|
-
*/
|
|
125
270
|
async function detectInstalled(): Promise<Readonly<Record<RemoteSupportTool, boolean>>> {
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
271
|
+
const [cloudflared, tailscale] = await Promise.all([
|
|
272
|
+
toolAvailable("cloudflared"),
|
|
273
|
+
toolAvailable("tailscale"),
|
|
274
|
+
]);
|
|
275
|
+
return { cloudflared, tailscale } as const;
|
|
130
276
|
}
|
|
131
277
|
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
const
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
:
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
const
|
|
143
|
-
|
|
144
|
-
|
|
278
|
+
/** Which publisher, if any, is currently alive on this host. */
|
|
279
|
+
function detectActive(): { tool: RemoteSupportTool | null; url: string | null } {
|
|
280
|
+
for (const tool of REMOTE_SUPPORT_TOOLS) {
|
|
281
|
+
// Tailscale runs as a system service — its serve daemon owns its own
|
|
282
|
+
// lifecycle, so a pid file is not reliable. Ask the daemon.
|
|
283
|
+
if (tool === "tailscale") {
|
|
284
|
+
const url = tailscaleUrlFromDaemon();
|
|
285
|
+
if (url) return { tool: "tailscale", url };
|
|
286
|
+
continue;
|
|
287
|
+
}
|
|
288
|
+
const pid = readLivePid(dir, tool);
|
|
289
|
+
if (pid === null) continue;
|
|
290
|
+
const recorded = existsSync(urlPath(dir, tool)) ? readFileSync(urlPath(dir, tool), "utf8").trim() : "";
|
|
291
|
+
return { tool, url: recorded.length > 0 ? recorded : null };
|
|
145
292
|
}
|
|
293
|
+
return { tool: null, url: null };
|
|
294
|
+
}
|
|
295
|
+
|
|
296
|
+
async function readStatus(
|
|
297
|
+
blocker: RemoteSupportBlocker | null = null,
|
|
298
|
+
message: string | null = null,
|
|
299
|
+
): Promise<RemoteSupportStatus> {
|
|
300
|
+
const { tool, url } = detectActive();
|
|
146
301
|
const installed = await detectInstalled();
|
|
147
|
-
return { active, url, blocker, message, port, installed };
|
|
302
|
+
return { active: tool, url, blocker, message, port, installed };
|
|
303
|
+
}
|
|
304
|
+
|
|
305
|
+
async function stopTool(tool: RemoteSupportTool): Promise<void> {
|
|
306
|
+
if (tool === "tailscale") {
|
|
307
|
+
// The daemon owns the serve lifecycle; `serve reset` is the sanctioned
|
|
308
|
+
// teardown and leaves no orphan process behind.
|
|
309
|
+
spawnSync("tailscale", ["serve", "reset"], { stdio: "ignore", windowsHide: true });
|
|
310
|
+
rmSync(urlPath(dir, tool), { force: true });
|
|
311
|
+
rmSync(pidPath(dir, tool), { force: true });
|
|
312
|
+
return;
|
|
313
|
+
}
|
|
314
|
+
const pid = readLivePid(dir, tool);
|
|
315
|
+
if (pid !== null) killTree(pid);
|
|
316
|
+
rmSync(pidPath(dir, tool), { force: true });
|
|
317
|
+
rmSync(urlPath(dir, tool), { force: true });
|
|
148
318
|
}
|
|
149
319
|
|
|
150
320
|
return {
|
|
@@ -153,27 +323,6 @@ export function createRemoteSupportService(config: RemoteSupportServiceConfig):
|
|
|
153
323
|
},
|
|
154
324
|
|
|
155
325
|
async start(tool, requestedPort) {
|
|
156
|
-
if (process.platform !== "win32") {
|
|
157
|
-
return {
|
|
158
|
-
active: null,
|
|
159
|
-
url: null,
|
|
160
|
-
blocker: "unsupported_platform",
|
|
161
|
-
message: "Remote support is currently scripted for Windows hosts only.",
|
|
162
|
-
port,
|
|
163
|
-
installed: { cloudflared: false, tailscale: false },
|
|
164
|
-
};
|
|
165
|
-
}
|
|
166
|
-
const launcher = scriptPath(config, "remote-support.cmd");
|
|
167
|
-
if (!existsSync(launcher)) {
|
|
168
|
-
return {
|
|
169
|
-
active: null,
|
|
170
|
-
url: null,
|
|
171
|
-
blocker: "launch_failed",
|
|
172
|
-
message: `Launcher not found: ${launcher}`,
|
|
173
|
-
port,
|
|
174
|
-
installed: await detectInstalled(),
|
|
175
|
-
};
|
|
176
|
-
}
|
|
177
326
|
if (!(await toolAvailable(tool))) {
|
|
178
327
|
return {
|
|
179
328
|
active: null,
|
|
@@ -184,14 +333,17 @@ export function createRemoteSupportService(config: RemoteSupportServiceConfig):
|
|
|
184
333
|
installed: await detectInstalled(),
|
|
185
334
|
};
|
|
186
335
|
}
|
|
336
|
+
// Replace any stale run of the same tool before launching a new one, so
|
|
337
|
+
// two instances never fight for the same local port.
|
|
338
|
+
await stopTool(tool);
|
|
187
339
|
const target = requestedPort ?? port;
|
|
188
|
-
const result = await
|
|
189
|
-
if (result.
|
|
340
|
+
const result = await launchDetached(dir, tool, target);
|
|
341
|
+
if (result.error) {
|
|
190
342
|
return {
|
|
191
343
|
active: null,
|
|
192
344
|
url: null,
|
|
193
345
|
blocker: "launch_failed",
|
|
194
|
-
message: result.
|
|
346
|
+
message: result.error,
|
|
195
347
|
port,
|
|
196
348
|
installed: await detectInstalled(),
|
|
197
349
|
};
|
|
@@ -200,7 +352,11 @@ export function createRemoteSupportService(config: RemoteSupportServiceConfig):
|
|
|
200
352
|
},
|
|
201
353
|
|
|
202
354
|
async stop(tool) {
|
|
203
|
-
|
|
355
|
+
if (tool === undefined) {
|
|
356
|
+
for (const name of REMOTE_SUPPORT_TOOLS) await stopTool(name);
|
|
357
|
+
} else {
|
|
358
|
+
await stopTool(tool);
|
|
359
|
+
}
|
|
204
360
|
return await readStatus();
|
|
205
361
|
},
|
|
206
362
|
};
|