cartethyia 2.7.2 → 2.7.3
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 +2 -0
- package/dist/dashboard/assets/{BannedPage-XaUmiBrC.js → BannedPage-2TJD37Hb.js} +1 -1
- package/dist/dashboard/assets/BannedPage-2TJD37Hb.js.gz +0 -0
- package/dist/dashboard/assets/CliToolDetailPage-xefJNRmJ.js +1 -0
- package/dist/dashboard/assets/CliToolDetailPage-xefJNRmJ.js.gz +0 -0
- package/dist/dashboard/assets/CliToolsPage-Rj9iHQ3F.js +1 -0
- package/dist/dashboard/assets/CliToolsPage-Rj9iHQ3F.js.gz +0 -0
- package/dist/dashboard/assets/{CombosPage-CXGS_Ct-.js → CombosPage-JEbYJFxw.js} +1 -1
- package/dist/dashboard/assets/CombosPage-JEbYJFxw.js.gz +0 -0
- package/dist/dashboard/assets/{ConsoleLogPage-DGnGNYnM.js → ConsoleLogPage-C3xx_inM.js} +1 -1
- package/dist/dashboard/assets/ConsoleLogPage-C3xx_inM.js.gz +0 -0
- package/dist/dashboard/assets/CustomizationPage-Dw306gJU.js +1 -0
- package/dist/dashboard/assets/CustomizationPage-Dw306gJU.js.gz +0 -0
- package/dist/dashboard/assets/HealthEventsModal-jaBo1TWV.js +1 -0
- package/dist/dashboard/assets/HealthEventsModal-jaBo1TWV.js.gz +0 -0
- package/dist/dashboard/assets/{LoginPage-BdmRLnq2.js → LoginPage-C4w7vea-.js} +1 -1
- package/dist/dashboard/assets/LoginPage-C4w7vea-.js.gz +0 -0
- package/dist/dashboard/assets/{ModelPicker-BH8Tehdi.js → ModelPicker-BtPcAfot.js} +1 -1
- package/dist/dashboard/assets/ModelPicker-BtPcAfot.js.gz +0 -0
- package/dist/dashboard/assets/OverviewPage-CGC5gNs1.js +1 -0
- package/dist/dashboard/assets/OverviewPage-CGC5gNs1.js.gz +0 -0
- package/dist/dashboard/assets/ProviderDetailPage-DQcthNGt.js +4 -0
- package/dist/dashboard/assets/ProviderDetailPage-DQcthNGt.js.gz +0 -0
- package/dist/dashboard/assets/ProvidersPage-0OsXRGXe.js +1 -0
- package/dist/dashboard/assets/ProvidersPage-0OsXRGXe.js.gz +0 -0
- package/dist/dashboard/assets/ProxyPage-DdPc8slb.js +9 -0
- package/dist/dashboard/assets/ProxyPage-DdPc8slb.js.gz +0 -0
- package/dist/dashboard/assets/{QuotaPage-7P2n--Fv.js → QuotaPage-Ci5O4BnH.js} +1 -1
- package/dist/dashboard/assets/QuotaPage-Ci5O4BnH.js.gz +0 -0
- package/dist/dashboard/assets/{SettingsPage-CxZ1fTkN.js → SettingsPage-CDZjTt87.js} +1 -1
- package/dist/dashboard/assets/SettingsPage-CDZjTt87.js.gz +0 -0
- package/dist/dashboard/assets/SetupPage-BQS0Rx_u.js +1 -0
- package/dist/dashboard/assets/SetupPage-BQS0Rx_u.js.gz +0 -0
- package/dist/dashboard/assets/{SortableList-Dqf4zpS-.js → SortableList-BAL5npBC.js} +1 -1
- package/dist/dashboard/assets/SortableList-BAL5npBC.js.gz +0 -0
- package/dist/dashboard/assets/{StudioPage-Byj2lAEm.js → StudioPage-TP7voAQ0.js} +3 -3
- package/dist/dashboard/assets/StudioPage-TP7voAQ0.js.gz +0 -0
- package/dist/dashboard/assets/{UsagePage-CTkLAuRc.js → UsagePage-Cj_fEzhg.js} +13 -13
- package/dist/dashboard/assets/UsagePage-Cj_fEzhg.js.gz +0 -0
- package/dist/dashboard/assets/{api-keys-CUi6UXsR.js → api-keys-MbI7e7rt.js} +1 -1
- package/dist/dashboard/assets/api-keys-MbI7e7rt.js.gz +0 -0
- package/dist/dashboard/assets/app-B2vyPoII.css +2 -0
- package/dist/dashboard/assets/app-B2vyPoII.css.gz +0 -0
- package/dist/dashboard/assets/arrow-up-59ms1iiT.js +1 -0
- package/dist/dashboard/assets/arrow-up-59ms1iiT.js.gz +0 -0
- package/dist/dashboard/assets/{assert-67xXH5rf.js → assert-C0YYuZJ4.js} +1 -1
- package/dist/dashboard/assets/assert-C0YYuZJ4.js.gz +0 -0
- package/dist/dashboard/assets/{badge-DC2L43Mj.js → badge-B_DMpMJN.js} +1 -1
- package/dist/dashboard/assets/badge-B_DMpMJN.js.gz +0 -0
- package/dist/dashboard/assets/{banner-BBVcb0J3.js → banner-DMrMPmth.js} +1 -1
- package/dist/dashboard/assets/banner-DMrMPmth.js.gz +0 -0
- package/dist/dashboard/assets/{card-Cca1XC6G.js → card-Be5Uzamx.js} +1 -1
- package/dist/dashboard/assets/card-Be5Uzamx.js.gz +0 -0
- package/dist/dashboard/assets/{cli-tools-KvsWa1Jl.js → cli-tools-muFRphDu.js} +1 -1
- package/dist/dashboard/assets/cli-tools-muFRphDu.js.gz +0 -0
- package/dist/dashboard/assets/{clipboard-button-DUzEbynq.js → clipboard-button-DOfrVmsi.js} +1 -1
- package/dist/dashboard/assets/clipboard-button-DOfrVmsi.js.gz +0 -0
- package/dist/dashboard/assets/{dialog-DFvWhwTa.js → dialog-C9omdjQt.js} +1 -1
- package/dist/dashboard/assets/dialog-C9omdjQt.js.gz +0 -0
- package/dist/dashboard/assets/drawer-Bm8WKaGY.js +1 -0
- package/dist/dashboard/assets/drawer-Bm8WKaGY.js.gz +0 -0
- package/dist/dashboard/assets/{entry-_xHo5x6r.js → entry-DS90gUks.js} +1 -1
- package/dist/dashboard/assets/entry-DS90gUks.js.gz +0 -0
- package/dist/dashboard/assets/{entry-ClCMCWWQ.js → entry-j40LJotO.js} +3 -3
- package/dist/dashboard/assets/entry-j40LJotO.js.gz +0 -0
- package/dist/dashboard/assets/{entry-Bqy8RFvM.js → entry-q5HwZ8nN.js} +1 -1
- package/dist/dashboard/assets/entry-q5HwZ8nN.js.gz +0 -0
- package/dist/dashboard/assets/{concurrency-BDF0ok7y.js → fingerprint-pattern-BWuHtbW4.js} +1 -1
- package/dist/dashboard/assets/fingerprint-pattern-BWuHtbW4.js.gz +0 -0
- package/dist/dashboard/assets/{formatters-CWWZ-uCZ.js → formatters-BTScrQpe.js} +1 -1
- package/dist/dashboard/assets/formatters-BTScrQpe.js.gz +0 -0
- package/dist/dashboard/assets/{github-badge-C9e8Ts7e.js → github-badge-Orjuzemr.js} +1 -1
- package/dist/dashboard/assets/github-badge-Orjuzemr.js.gz +0 -0
- package/dist/dashboard/assets/{index-DFVFrnWW.js → index-cPrX5vn6.js} +2 -2
- package/dist/dashboard/assets/index-cPrX5vn6.js.gz +0 -0
- package/dist/dashboard/assets/{inline-BFUcvllj.js → inline-Dvux8w-0.js} +1 -1
- package/dist/dashboard/assets/inline-Dvux8w-0.js.gz +0 -0
- package/dist/dashboard/assets/{input-C_ILAvBb.js → input-CU1Rgf3p.js} +1 -1
- package/dist/dashboard/assets/input-CU1Rgf3p.js.gz +0 -0
- package/dist/dashboard/assets/{layout-CBIyT6Rt.js → layout-C_7A6H55.js} +1 -1
- package/dist/dashboard/assets/layout-C_7A6H55.js.gz +0 -0
- package/dist/dashboard/assets/{list-row-ebJ_-Nsb.js → list-row-CzFvogbj.js} +1 -1
- package/dist/dashboard/assets/list-row-CzFvogbj.js.gz +0 -0
- package/dist/dashboard/assets/{live-D0mm--ME.js → live-KaG15HLQ.js} +1 -1
- package/dist/dashboard/assets/live-KaG15HLQ.js.gz +0 -0
- package/dist/dashboard/assets/{mutation-sMD965oO.js → mutation-JJGQm8TD.js} +1 -1
- package/dist/dashboard/assets/mutation-JJGQm8TD.js.gz +0 -0
- package/dist/dashboard/assets/{numbers-BO9dKaFr.js → numbers-BNDZnsUE.js} +1 -1
- package/dist/dashboard/assets/numbers-BNDZnsUE.js.gz +0 -0
- package/dist/dashboard/assets/{query-keys-CfL_Zdoh.js → query-keys-CLaNz8Xz.js} +1 -1
- package/dist/dashboard/assets/query-keys-CLaNz8Xz.js.gz +0 -0
- package/dist/dashboard/assets/{routing-BnIpi1IJ.js → routing-BEH32QOC.js} +1 -1
- package/dist/dashboard/assets/routing-BEH32QOC.js.gz +0 -0
- package/dist/dashboard/assets/{select-DBNVLdUu.js → select-B3iTetHN.js} +1 -1
- package/dist/dashboard/assets/select-B3iTetHN.js.gz +0 -0
- package/dist/dashboard/assets/settings-2-Ds9DQp0t.js +1 -0
- package/dist/dashboard/assets/settings-2-Ds9DQp0t.js.gz +0 -0
- package/dist/dashboard/assets/{settings-BAFhDgX8.js → settings-Dt3Eqbnq.js} +1 -1
- package/dist/dashboard/assets/settings-Dt3Eqbnq.js.gz +0 -0
- package/dist/dashboard/assets/{stack-EMrOMt4I.js → stack-KrOrm7JZ.js} +1 -1
- package/dist/dashboard/assets/stack-KrOrm7JZ.js.gz +0 -0
- package/dist/dashboard/assets/{state-CWoo7ty5.js → state-DLS7IDMy.js} +1 -1
- package/dist/dashboard/assets/state-DLS7IDMy.js.gz +0 -0
- package/dist/dashboard/assets/{switch-CCjT6IkF.js → switch-42ItvbAs.js} +1 -1
- package/dist/dashboard/assets/switch-42ItvbAs.js.gz +0 -0
- package/dist/dashboard/assets/{useMutation-DjkLHUcE.js → useMutation-Dmnfh1Ue.js} +1 -1
- package/dist/dashboard/assets/useMutation-Dmnfh1Ue.js.gz +0 -0
- package/dist/dashboard/index.html +3 -2
- package/dist/dashboard/index.html.gz +0 -0
- package/dist/dashboard/precompressed-assets.json +117 -115
- package/migrations/full-postgres/0026_add_qoder_native_identity.sql +20 -0
- package/migrations/full-postgres/0027_qoder_identity_genuine_seed.sql +22 -0
- package/migrations/full-postgres/0028_add_family_template_key_telemetry.sql +40 -0
- package/migrations/full-postgres/0029_null_share_child_policy_columns.sql +38 -0
- package/migrations/full-postgres/0030_add_api_key_max_children.sql +10 -0
- package/migrations/full-postgres/0031_tenant_system_prompts.sql +16 -0
- package/migrations/lite-sqlite3/0026_add_qoder_native_identity.sql +15 -0
- package/migrations/lite-sqlite3/0027_qoder_identity_genuine_seed.sql +22 -0
- package/migrations/lite-sqlite3/0028_add_family_template_key_telemetry.sql +40 -0
- package/migrations/lite-sqlite3/0029_null_share_child_policy_columns.sql +38 -0
- package/migrations/lite-sqlite3/0030_add_api_key_max_children.sql +10 -0
- package/migrations/lite-sqlite3/0031_tenant_system_prompts.sql +10 -0
- package/package.json +1 -1
- package/src/config/network.ts +1 -1
- package/src/console/api-keys/bucket-spend.ts +7 -7
- package/src/console/api-keys/contracts.ts +423 -410
- package/src/console/api-keys/routes.ts +118 -19
- package/src/console/backup/export-endpoint.ts +8 -11
- package/src/console/backup/routes.ts +40 -8
- package/src/console/backup/service.ts +11 -6
- package/src/console/backup/store.ts +384 -88
- package/src/console/dashboard-assets.ts +16 -1
- package/src/console/domain-registration.ts +2 -0
- package/src/console/quota/account-quota-tenant.ts +8 -1
- package/src/console/quota/account-quota.ts +4 -1
- package/src/console/quota/native-identity.ts +189 -0
- package/src/console/remote-support/contracts.ts +12 -6
- package/src/console/remote-support/service.ts +2 -2
- package/src/console/share/share-router.ts +39 -6
- package/src/console/share/share-stats.ts +64 -25
- package/src/console/share/share-usage.ts +24 -7
- package/src/console/system-prompt/contracts.ts +102 -0
- package/src/console/system-prompt/routes.ts +165 -0
- package/src/main.ts +11 -0
- package/src/observability/event-query.ts +118 -7
- package/src/persistence/db-pg.ts +42 -0
- package/src/persistence/schema.ts +102 -0
- package/src/persistence/stores/api-key-store.ts +753 -581
- package/src/persistence/stores/share-store.ts +65 -13
- package/src/persistence/stores/telemetry-store.ts +57 -1
- package/src/persistence/system-prompt-store.ts +104 -0
- package/src/protocol/reasoning.ts +65 -1
- package/src/protocol/request/chat.ts +1 -0
- package/src/providers/integrations/antigravity/antigravity.ts +20 -23
- package/src/providers/integrations/buddy/buddy-chat-shared.ts +30 -2
- package/src/providers/integrations/buddy/buddy-media.ts +5 -1
- package/src/providers/integrations/buddy/codebuddy/provider.ts +5 -1
- package/src/providers/integrations/buddy/workbuddy/provider.ts +5 -1
- package/src/providers/integrations/cline/cline.ts +7 -0
- package/src/providers/integrations/github/github.ts +20 -1
- package/src/providers/integrations/hermes.ts +5 -1
- package/src/providers/integrations/kilo/kilo.ts +4 -0
- package/src/providers/integrations/openrouter/provider.ts +4 -0
- package/src/providers/integrations/qoder/qoder-checkin.ts +224 -51
- package/src/providers/integrations/qoder/qoder-native-identity-store.ts +156 -0
- package/src/providers/integrations/qoder/qoder-oauth.ts +12 -3
- package/src/providers/integrations/qoder/qoder-umid.ts +288 -0
- package/src/providers/integrations/qoder/qoder.ts +5 -3
- package/src/providers/operations/account-error-classification.ts +6 -8
- package/src/providers/operations/account-health-service.ts +14 -16
- package/src/providers/operations/client-versions.ts +37 -22
- package/src/providers/provider-contracts.ts +8 -0
- package/src/security/access-control.ts +19 -0
- package/src/security/api-key-auth.ts +14 -1
- package/src/transport/dispatch/proxy-request.ts +31 -2
- package/src/transport/dispatch/streaming/stream.ts +16 -1
- package/src/transport/dispatch/upstream.ts +10 -0
- package/src/transport/request/preparer.ts +17 -1
- package/src/transport/request/system-prompt-injection.ts +89 -0
- package/src/transport/request/tenant-preferences.ts +43 -0
- package/src/transport/routing/admission.ts +13 -3
- package/src/transport/routing/route-catalog.ts +7 -0
- package/src/transport/routing/route-model.ts +53 -0
- package/src/transport/routing/router.ts +306 -256
- package/src/workers/daily-checkin.ts +2 -0
- package/dist/dashboard/assets/BannedPage-XaUmiBrC.js.gz +0 -0
- package/dist/dashboard/assets/CliToolDetailPage-CqD7QiLM.js +0 -1
- package/dist/dashboard/assets/CliToolDetailPage-CqD7QiLM.js.gz +0 -0
- package/dist/dashboard/assets/CliToolsPage-0XUo4UMn.js +0 -1
- package/dist/dashboard/assets/CliToolsPage-0XUo4UMn.js.gz +0 -0
- package/dist/dashboard/assets/CombosPage-CXGS_Ct-.js.gz +0 -0
- package/dist/dashboard/assets/ConsoleLogPage-DGnGNYnM.js.gz +0 -0
- package/dist/dashboard/assets/CustomizationPage-DAG5J9EY.js +0 -1
- package/dist/dashboard/assets/CustomizationPage-DAG5J9EY.js.gz +0 -0
- package/dist/dashboard/assets/HealthEventsModal-BKtAPYVF.js +0 -1
- package/dist/dashboard/assets/HealthEventsModal-BKtAPYVF.js.gz +0 -0
- package/dist/dashboard/assets/LoginPage-BdmRLnq2.js.gz +0 -0
- package/dist/dashboard/assets/ModelPicker-BH8Tehdi.js.gz +0 -0
- package/dist/dashboard/assets/OverviewPage-CqMxibJB.js +0 -1
- package/dist/dashboard/assets/OverviewPage-CqMxibJB.js.gz +0 -0
- package/dist/dashboard/assets/ProviderDetailPage-CqMh2NRX.js +0 -4
- package/dist/dashboard/assets/ProviderDetailPage-CqMh2NRX.js.gz +0 -0
- package/dist/dashboard/assets/ProvidersPage-B9uWu4Nz.js +0 -1
- package/dist/dashboard/assets/ProvidersPage-B9uWu4Nz.js.gz +0 -0
- package/dist/dashboard/assets/ProxyPage-BoWOhpDO.js +0 -9
- package/dist/dashboard/assets/ProxyPage-BoWOhpDO.js.gz +0 -0
- package/dist/dashboard/assets/QuotaPage-7P2n--Fv.js.gz +0 -0
- package/dist/dashboard/assets/SettingsPage-CxZ1fTkN.js.gz +0 -0
- package/dist/dashboard/assets/SetupPage-Co12Zzaw.js +0 -1
- package/dist/dashboard/assets/SetupPage-Co12Zzaw.js.gz +0 -0
- package/dist/dashboard/assets/SortableList-Dqf4zpS-.js.gz +0 -0
- package/dist/dashboard/assets/StudioPage-Byj2lAEm.js.gz +0 -0
- package/dist/dashboard/assets/UsagePage-CTkLAuRc.js.gz +0 -0
- package/dist/dashboard/assets/api-keys-CUi6UXsR.js.gz +0 -0
- package/dist/dashboard/assets/app-KvsUJUMF.css +0 -2
- package/dist/dashboard/assets/app-KvsUJUMF.css.gz +0 -0
- package/dist/dashboard/assets/assert-67xXH5rf.js.gz +0 -0
- package/dist/dashboard/assets/badge-DC2L43Mj.js.gz +0 -0
- package/dist/dashboard/assets/banner-BBVcb0J3.js.gz +0 -0
- package/dist/dashboard/assets/card-Cca1XC6G.js.gz +0 -0
- package/dist/dashboard/assets/cli-tools-KvsWa1Jl.js.gz +0 -0
- package/dist/dashboard/assets/clipboard-button-DUzEbynq.js.gz +0 -0
- package/dist/dashboard/assets/concurrency-BDF0ok7y.js.gz +0 -0
- package/dist/dashboard/assets/dialog-DFvWhwTa.js.gz +0 -0
- package/dist/dashboard/assets/drawer-CFM2sMX1.js +0 -1
- package/dist/dashboard/assets/drawer-CFM2sMX1.js.gz +0 -0
- package/dist/dashboard/assets/entry-Bqy8RFvM.js.gz +0 -0
- package/dist/dashboard/assets/entry-ClCMCWWQ.js.gz +0 -0
- package/dist/dashboard/assets/entry-_xHo5x6r.js.gz +0 -0
- package/dist/dashboard/assets/formatters-CWWZ-uCZ.js.gz +0 -0
- package/dist/dashboard/assets/github-badge-C9e8Ts7e.js.gz +0 -0
- package/dist/dashboard/assets/index-DFVFrnWW.js.gz +0 -0
- package/dist/dashboard/assets/inline-BFUcvllj.js.gz +0 -0
- package/dist/dashboard/assets/input-C_ILAvBb.js.gz +0 -0
- package/dist/dashboard/assets/layout-CBIyT6Rt.js.gz +0 -0
- package/dist/dashboard/assets/list-row-ebJ_-Nsb.js.gz +0 -0
- package/dist/dashboard/assets/live-D0mm--ME.js.gz +0 -0
- package/dist/dashboard/assets/mutation-sMD965oO.js.gz +0 -0
- package/dist/dashboard/assets/numbers-BO9dKaFr.js.gz +0 -0
- package/dist/dashboard/assets/query-keys-CfL_Zdoh.js.gz +0 -0
- package/dist/dashboard/assets/routing-BnIpi1IJ.js.gz +0 -0
- package/dist/dashboard/assets/select-DBNVLdUu.js.gz +0 -0
- package/dist/dashboard/assets/settings-BAFhDgX8.js.gz +0 -0
- package/dist/dashboard/assets/stack-EMrOMt4I.js.gz +0 -0
- package/dist/dashboard/assets/state-CWoo7ty5.js.gz +0 -0
- package/dist/dashboard/assets/switch-CCjT6IkF.js.gz +0 -0
- package/dist/dashboard/assets/useMutation-DjkLHUcE.js.gz +0 -0
- /package/dist/dashboard/assets/{app-BaWd0puM.js → app-wvwCi4Eh.js} +0 -0
- /package/dist/dashboard/assets/{app-BaWd0puM.js.gz → app-wvwCi4Eh.js.gz} +0 -0
|
@@ -0,0 +1,288 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Qoder native machine identity (UMID) — the genuine SG bridge via umidpack.
|
|
3
|
+
*
|
|
4
|
+
* Research record (2026-10-30): the daily-credit campaign gates its CREDITS
|
|
5
|
+
* grant on a *genuine* Alibaba SG-produced identity. Deterministic/generated
|
|
6
|
+
* tokens do not reveal the grant on a fresh account; the `runtime-info`
|
|
7
|
+
* bridge does. Primary path therefore runs the real bridge binary from
|
|
8
|
+
* `vendor/qoder-umid/umidpack/`, auto-picked per host OS.
|
|
9
|
+
*
|
|
10
|
+
* Seed semantics (verified live): the Linux ELF resolves the user home via
|
|
11
|
+
* the passwd entry — it ignores `$HOME`/`XDG_CONFIG_HOME`, so the seed file
|
|
12
|
+
* is always `/home/<user>/.config/.locale_cfg`, one global file per Linux
|
|
13
|
+
* user. Per-account isolation therefore lives in the DATABASE (each account
|
|
14
|
+
* stores its own 350-byte seed) plus a flock-serialized bridge section:
|
|
15
|
+
* mint = wipe → run → capture seed+token; replay = write stored seed → run.
|
|
16
|
+
* Verified cycle (umidpack ELF under WSL): mint A, mint B (distinct),
|
|
17
|
+
* replay A ✓, replay B ✓, replay A again ✓.
|
|
18
|
+
*
|
|
19
|
+
* Platform behavior:
|
|
20
|
+
* win32 — the exe is hardware-bound: one identity per machine, no seed.
|
|
21
|
+
* Still a genuine SG identity (accepted by the campaign surface).
|
|
22
|
+
* linux — direct spawn of the umidpack ELF, serialized seed file.
|
|
23
|
+
* darwin — the Mach-O runs directly; seed path is the macOS user home.
|
|
24
|
+
* win32 + WSL (this repo's primary deployment) — the ELF runs via WSL with
|
|
25
|
+
* flock serialization; the bridge binary is staged into /tmp once.
|
|
26
|
+
*
|
|
27
|
+
* The generated path remains a fallback for hosts with no usable bridge: it
|
|
28
|
+
* produces well-shaped but *not* SG-signed tokens — fine for non-campaign
|
|
29
|
+
* endpoints, will not surface the daily grant.
|
|
30
|
+
*
|
|
31
|
+
* Bridge CLI (donor-verified):
|
|
32
|
+
* linux : runtime-info <env>
|
|
33
|
+
* darwin: printf '{"account":"<uid>"}\n' | runtime-info <env> --account-stdin
|
|
34
|
+
* win32 : runtime-info.exe <env> [--account-stdin]
|
|
35
|
+
*/
|
|
36
|
+
import { spawnSync } from "node:child_process";
|
|
37
|
+
import { createHash } from "node:crypto";
|
|
38
|
+
import { existsSync, mkdirSync, readFileSync, writeFileSync } from "node:fs";
|
|
39
|
+
import { join } from "node:path";
|
|
40
|
+
import { log } from "../../../observability/logger";
|
|
41
|
+
|
|
42
|
+
/** Valid environment codes (kept for validation/console surface). */
|
|
43
|
+
export const QODER_ENV_CODES = ["0", "3", "4", "5", "6", "7"] as const;
|
|
44
|
+
|
|
45
|
+
/** Environment code: an upstream "region/flavor", not a device identity. */
|
|
46
|
+
const ENV_CODE = "3";
|
|
47
|
+
|
|
48
|
+
/** One native identity sample (same shape the bridge prints). */
|
|
49
|
+
export interface QoderNativeIdentity {
|
|
50
|
+
readonly machineToken: string;
|
|
51
|
+
readonly machineType: string;
|
|
52
|
+
readonly machineCode: string;
|
|
53
|
+
readonly isVm: boolean;
|
|
54
|
+
readonly source: "runtime-info" | "generated";
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
/** Length of the base64url payload after the `P1gA` prefix (token total 88). */
|
|
58
|
+
const TOKEN_PAYLOAD_LENGTH = 84;
|
|
59
|
+
/** Hex length of machineType / machineCode in the bridge's output. */
|
|
60
|
+
const MACHINE_TYPE_LENGTH = 18;
|
|
61
|
+
const MACHINE_CODE_LENGTH = 18;
|
|
62
|
+
|
|
63
|
+
/** Where the umidpack binaries live, relative to the repo root. */
|
|
64
|
+
export function umidpackDir(): string {
|
|
65
|
+
return join(process.cwd(), "vendor", "qoder-umid", "umidpack");
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
/** WSL-side staging/seed paths for the Linux bridge on a Windows host. */
|
|
69
|
+
const WSL_BIN = "/tmp/umidpack/runtime-info";
|
|
70
|
+
const WSL_SEED = "/home/risun/.config/.locale_cfg";
|
|
71
|
+
const WSL_LOCK = "/tmp/umidpack/bridge.lock";
|
|
72
|
+
|
|
73
|
+
type Bridge = { kind: "elf" | "macho" | "pe" | "wsl-elf" };
|
|
74
|
+
|
|
75
|
+
/** Picks the bridge for this host; `null` when nothing genuine can run. */
|
|
76
|
+
function pickBridge(): Bridge | null {
|
|
77
|
+
if (process.platform === "win32") {
|
|
78
|
+
// Prefer the WSL ELF (seed-capable → per-account identities); fall back
|
|
79
|
+
// to the Windows exe pair (hardware-bound, one identity per machine).
|
|
80
|
+
const elf = join(umidpackDir(), "elf-x86_64", "runtime-info");
|
|
81
|
+
if (existsSync(elf)) return { kind: "wsl-elf" };
|
|
82
|
+
const exe = join(umidpackDir(), "runtime-info.exe");
|
|
83
|
+
const dll = join(umidpackDir(), "sgsdk.dll");
|
|
84
|
+
if (existsSync(exe) && existsSync(dll)) return { kind: "pe" };
|
|
85
|
+
return null;
|
|
86
|
+
}
|
|
87
|
+
if (process.platform === "darwin") {
|
|
88
|
+
const arch = process.arch === "arm64" ? "macho-arm64" : "macho-x86_64";
|
|
89
|
+
return existsSync(join(umidpackDir(), arch, "runtime-info")) ? { kind: "macho" } : null;
|
|
90
|
+
}
|
|
91
|
+
const arch = process.arch === "arm64" ? "elf-aarch64" : "elf-x86_64";
|
|
92
|
+
if (existsSync(join(umidpackDir(), arch, "runtime-info"))) return { kind: "elf" };
|
|
93
|
+
return existsSync(join(umidpackDir(), "elf-x86_64", "runtime-info")) ? { kind: "elf" } : null;
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
/** True when a genuine bridge can run on this host. */
|
|
97
|
+
export function hasGenuineBridge(): boolean {
|
|
98
|
+
return pickBridge() !== null;
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
/** Generated (software) identity — deterministic per account id, NOT SG-signed. */
|
|
102
|
+
export function mintQoderGeneratedIdentity(accountId: string): QoderNativeIdentity {
|
|
103
|
+
const first = createHash("sha256").update(accountId).digest("base64url");
|
|
104
|
+
const second = createHash("sha256").update(`${accountId}:${first}`).digest("base64url");
|
|
105
|
+
const token = `P1gA${`${first}${second}`.slice(0, TOKEN_PAYLOAD_LENGTH)}`;
|
|
106
|
+
return {
|
|
107
|
+
machineToken: token,
|
|
108
|
+
machineType: createHash("sha256").update(`${accountId}:type`).digest("hex").slice(0, MACHINE_TYPE_LENGTH),
|
|
109
|
+
machineCode: createHash("sha256").update(`${accountId}:code`).digest("hex").slice(0, MACHINE_CODE_LENGTH),
|
|
110
|
+
isVm: false,
|
|
111
|
+
source: "generated",
|
|
112
|
+
};
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
/** Parse one bridge JSON line into an identity; null on any mismatch. */
|
|
116
|
+
function parseBridgeLine(stdout: string): QoderNativeIdentity | null {
|
|
117
|
+
const line = stdout.split(/\r?\n/).find((l) => l.startsWith("{"));
|
|
118
|
+
if (!line) return null;
|
|
119
|
+
try {
|
|
120
|
+
const rec = JSON.parse(line) as Record<string, unknown>;
|
|
121
|
+
const machineToken = String(rec.machineToken ?? "").trim();
|
|
122
|
+
const machineType = String(rec.machineType ?? "").trim();
|
|
123
|
+
const machineCode = String(rec.machineCode ?? "").trim();
|
|
124
|
+
if (!machineToken || !machineType || !machineCode) return null;
|
|
125
|
+
const vmInfo = rec.vmInfo;
|
|
126
|
+
const isVm = typeof vmInfo === "object" && vmInfo !== null && (vmInfo as Record<string, unknown>).isVm === true;
|
|
127
|
+
return { machineToken, machineType, machineCode, isVm, source: "runtime-info" };
|
|
128
|
+
} catch {
|
|
129
|
+
return null;
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
/** Stage the ELF into the WSL filesystem once (idempotent, fast after first). */
|
|
134
|
+
function ensureWslStaged(): void {
|
|
135
|
+
const src = join(umidpackDir(), "elf-x86_64", "runtime-info").replace(/\\/g, "/");
|
|
136
|
+
const drive = /^([A-Za-z]):/.exec(src);
|
|
137
|
+
const wslSrc = drive ? `/mnt/${drive[1]!.toLowerCase()}${src.slice(2)}` : src;
|
|
138
|
+
wsl(
|
|
139
|
+
`mkdir -p /tmp/umidpack && cp "${wslSrc}" ${WSL_BIN} && chmod +x ${WSL_BIN} && ` +
|
|
140
|
+
`mkdir -p $(dirname ${WSL_SEED})`,
|
|
141
|
+
);
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
function wsl(script: string, timeout = 120_000): string {
|
|
145
|
+
const r = spawnSync("wsl", ["-e", "bash", "-lc", script], { encoding: "utf8", timeout, windowsHide: true });
|
|
146
|
+
return String(r.stdout ?? "");
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
/** Run the bridge inside WSL under flock; returns the identity or null. */
|
|
150
|
+
|
|
151
|
+
function seedPath(): string {
|
|
152
|
+
if (process.platform === "win32") return WSL_SEED;
|
|
153
|
+
const home = process.env.HOME ?? "";
|
|
154
|
+
return join(home, ".config", ".locale_cfg");
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
/**
|
|
158
|
+
* Mints a fresh genuine identity: wipe the seed (under the lock), run the
|
|
159
|
+
* bridge, capture the identity AND the seed it wrote. The seed is required
|
|
160
|
+
* for later replay of this same identity. Returns `null` when the bridge
|
|
161
|
+
* fails; falls back to the generated identity only when no bridge exists.
|
|
162
|
+
*/
|
|
163
|
+
export function mintQoderNativeIdentity(accountId: string): { identity: QoderNativeIdentity; seed: string } | null {
|
|
164
|
+
const bridge = pickBridge();
|
|
165
|
+
if (!bridge) {
|
|
166
|
+
log.warn("[qoder] no genuine bridge for this host — generated fallback (campaign grant will not surface)");
|
|
167
|
+
return { identity: mintQoderGeneratedIdentity(accountId), seed: "" };
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
if (bridge.kind === "wsl-elf") {
|
|
171
|
+
ensureWslStaged();
|
|
172
|
+
const out = wsl(
|
|
173
|
+
`exec 9>${WSL_LOCK}; flock 9; rm -f ${WSL_SEED}; ${WSL_BIN} ${ENV_CODE}`,
|
|
174
|
+
);
|
|
175
|
+
const identity = parseBridgeLine(out);
|
|
176
|
+
if (!identity) { log.warn("[qoder] wsl mint failed"); return null; }
|
|
177
|
+
const seed = wsl(`cat ${WSL_SEED}`);
|
|
178
|
+
return { identity, seed };
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
if (bridge.kind === "elf") {
|
|
182
|
+
const exe = join(umidpackDir(), process.arch === "arm64" ? "elf-aarch64" : "elf-x86_64", "runtime-info");
|
|
183
|
+
const seed = seedPath();
|
|
184
|
+
const dir = seed.slice(0, seed.lastIndexOf("/"));
|
|
185
|
+
mkdirSync(dir, { recursive: true });
|
|
186
|
+
try { spawnSync("rm", ["-f", seed]); } catch { /* ignore */ }
|
|
187
|
+
const r = spawnSync(exe, [ENV_CODE], { encoding: "utf8", timeout: 25_000 });
|
|
188
|
+
const identity = parseBridgeLine(String(r.stdout ?? ""));
|
|
189
|
+
if (!identity) { log.warn("[qoder] elf mint failed"); return null; }
|
|
190
|
+
return { identity, seed: existsSync(seed) ? readFileSync(seed, "utf8") : "" };
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
if (bridge.kind === "macho") {
|
|
194
|
+
const arch = process.arch === "arm64" ? "macho-arm64" : "macho-x86_64";
|
|
195
|
+
const exe = join(umidpackDir(), arch, "runtime-info");
|
|
196
|
+
const seed = seedPath();
|
|
197
|
+
const dir = seed.slice(0, seed.lastIndexOf("/"));
|
|
198
|
+
mkdirSync(dir, { recursive: true });
|
|
199
|
+
try { spawnSync("rm", ["-f", seed]); } catch { /* ignore */ }
|
|
200
|
+
const r = spawnSync(exe, [ENV_CODE, "--account-stdin"], {
|
|
201
|
+
input: `${JSON.stringify({ account: accountId })}\n`,
|
|
202
|
+
encoding: "utf8",
|
|
203
|
+
timeout: 25_000,
|
|
204
|
+
});
|
|
205
|
+
const identity = parseBridgeLine(String(r.stdout ?? ""));
|
|
206
|
+
if (!identity) { log.warn("[qoder] macho mint failed"); return null; }
|
|
207
|
+
return { identity, seed: existsSync(seed) ? readFileSync(seed, "utf8") : "" };
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
// pe — hardware-bound; no seed concept on Windows.
|
|
211
|
+
const exe = join(umidpackDir(), "runtime-info.exe");
|
|
212
|
+
const r = spawnSync(exe, [ENV_CODE], { encoding: "utf8", timeout: 30_000, windowsHide: true, cwd: umidpackDir() });
|
|
213
|
+
const identity = parseBridgeLine(String(r.stdout ?? ""));
|
|
214
|
+
if (!identity) { log.warn("[qoder] pe mint failed"); return null; }
|
|
215
|
+
return { identity, seed: "" };
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
/**
|
|
219
|
+
* Replays an identity from a stored seed: materialize the seed at the bridge's
|
|
220
|
+
* fixed path (under the lock) and re-run — the caller verifies the token
|
|
221
|
+
* matches the stored record. Returns `null` when no seed is available, the
|
|
222
|
+
* host cannot replay (Windows exe), or the bridge failed.
|
|
223
|
+
*/
|
|
224
|
+
export function replayQoderNativeIdentity(seed: string, accountId: string): QoderNativeIdentity | null {
|
|
225
|
+
if (!seed) return null;
|
|
226
|
+
const bridge = pickBridge();
|
|
227
|
+
if (!bridge) return null;
|
|
228
|
+
|
|
229
|
+
if (bridge.kind === "wsl-elf") {
|
|
230
|
+
ensureWslStaged();
|
|
231
|
+
const quoted = seed.replace(/'/g, `'\\''`);
|
|
232
|
+
const out = wsl(
|
|
233
|
+
`exec 9>${WSL_LOCK}; flock 9; printf '%s' '${quoted}' > ${WSL_SEED}; ${WSL_BIN} ${ENV_CODE}`,
|
|
234
|
+
);
|
|
235
|
+
return parseBridgeLine(out);
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
if (bridge.kind === "elf") {
|
|
239
|
+
const exe = join(umidpackDir(), process.arch === "arm64" ? "elf-aarch64" : "elf-x86_64", "runtime-info");
|
|
240
|
+
const seed = seedPath();
|
|
241
|
+
writeFileSync(seed, seed, "utf8");
|
|
242
|
+
const r = spawnSync(exe, [ENV_CODE], { encoding: "utf8", timeout: 25_000 });
|
|
243
|
+
return parseBridgeLine(String(r.stdout ?? ""));
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
if (bridge.kind === "macho") {
|
|
247
|
+
const arch = process.arch === "arm64" ? "macho-arm64" : "macho-x86_64";
|
|
248
|
+
const exe = join(umidpackDir(), arch, "runtime-info");
|
|
249
|
+
const seedPathStr = seedPath();
|
|
250
|
+
writeFileSync(seedPathStr, seed, "utf8");
|
|
251
|
+
const r = spawnSync(exe, [ENV_CODE, "--account-stdin"], {
|
|
252
|
+
input: `${JSON.stringify({ account: accountId })}\n`,
|
|
253
|
+
encoding: "utf8",
|
|
254
|
+
timeout: 25_000,
|
|
255
|
+
});
|
|
256
|
+
return parseBridgeLine(String(r.stdout ?? ""));
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
return null; // pe: cannot replay a seed on a hardware-bound identity
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
/** Reads the current seed file content (diagnostics). */
|
|
263
|
+
export function readCurrentQoderSeed(): string {
|
|
264
|
+
const path = seedPath();
|
|
265
|
+
try {
|
|
266
|
+
if (process.platform === "win32") return wsl(`cat ${WSL_SEED} 2>/dev/null`);
|
|
267
|
+
return existsSync(path) ? readFileSync(path, "utf8") : "";
|
|
268
|
+
} catch {
|
|
269
|
+
return "";
|
|
270
|
+
}
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
/** The six native machine headers for one identity. */
|
|
274
|
+
export function qoderMachineHeaders(identity: QoderNativeIdentity): Record<string, string> {
|
|
275
|
+
return {
|
|
276
|
+
"cosy-machineid": identity.machineToken,
|
|
277
|
+
"cosy-machinetoken": identity.machineToken,
|
|
278
|
+
"cosy-machinetype": identity.machineType,
|
|
279
|
+
"cosy-machinecode": identity.machineCode,
|
|
280
|
+
"cosy-machineos": "x86_64_linux",
|
|
281
|
+
"cosy-machinehostname": "DESKTOP-QODER",
|
|
282
|
+
};
|
|
283
|
+
}
|
|
284
|
+
|
|
285
|
+
/** For tests: force re-mint (identities are pure functions of their seed). */
|
|
286
|
+
export function resetQoderNativeIdentityCache(): void {
|
|
287
|
+
// no persistent in-process identity cache remains
|
|
288
|
+
}
|
|
@@ -232,7 +232,9 @@ export function qoderMachineIdForAccount(accountId: string): string {
|
|
|
232
232
|
* directly as the COSY identity's oauth token; the user id and machine id
|
|
233
233
|
* come from the `auth_state` the login persisted. Fails closed with a
|
|
234
234
|
* reconnect prompt when the identity is absent (e.g. an account created
|
|
235
|
-
* before `auth_state` existed
|
|
235
|
+
* before `auth_state` existed, or an import that dropped the field) instead
|
|
236
|
+
* of signing with a guessed id. The message names the missing field so an
|
|
237
|
+
* operator can fix the export instead of re-logging blind.
|
|
236
238
|
*/
|
|
237
239
|
export function qoderOAuthAuth(
|
|
238
240
|
secret: string,
|
|
@@ -240,11 +242,11 @@ export function qoderOAuthAuth(
|
|
|
240
242
|
accountId: string | undefined,
|
|
241
243
|
): QoderAuth {
|
|
242
244
|
const state = parseQoderOAuthState(authState);
|
|
243
|
-
if (state === undefined
|
|
245
|
+
if (state === undefined) {
|
|
244
246
|
throw new GatewayError(
|
|
245
247
|
"authentication_failed",
|
|
246
248
|
401,
|
|
247
|
-
"Qoder OAuth account is missing its user identity; sign in again",
|
|
249
|
+
"Qoder OAuth account is missing its user identity (auth_state.userId/machineId absent or empty — re-login, or re-export with authState included); sign in again",
|
|
248
250
|
);
|
|
249
251
|
}
|
|
250
252
|
return {
|
|
@@ -251,15 +251,13 @@ export interface AccountErrorClassification {
|
|
|
251
251
|
/**
|
|
252
252
|
* True when this write changed a field the routing snapshot actually reads.
|
|
253
253
|
*
|
|
254
|
-
*
|
|
255
|
-
*
|
|
256
|
-
*
|
|
257
|
-
*
|
|
258
|
-
* model-scoped cooldown — is invisible to routing, and invalidating for it
|
|
259
|
-
* forced the next request to rebuild an 11-SELECT snapshot for no reason.
|
|
254
|
+
* Routing reads account-wide `status`/`cooldownUntil` and the per-model
|
|
255
|
+
* `modelCooldowns` map (`route-catalog.ts`). Writes to any of those must drop
|
|
256
|
+
* the snapshot; failure streaks and diagnostic text remain invisible to
|
|
257
|
+
* routing.
|
|
260
258
|
*
|
|
261
|
-
*
|
|
262
|
-
*
|
|
259
|
+
* Only a changed deadline is routing-visible. Re-reporting the same active
|
|
260
|
+
* model cooldown must not rebuild the snapshot again.
|
|
263
261
|
*/
|
|
264
262
|
readonly routesVisible: boolean;
|
|
265
263
|
/**
|
|
@@ -147,13 +147,13 @@ export async function recordAccountFailure(
|
|
|
147
147
|
// Monotonic: never move an in-force window earlier (see `latestDeadlineMs`).
|
|
148
148
|
// A provider-stated deadline wins outright because it is already the later
|
|
149
149
|
// of the two whenever the provider really parked the pair longer.
|
|
150
|
-
const
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
).toISOString();
|
|
150
|
+
const previousDeadline = existing[modelId] === undefined ? undefined : Date.parse(existing[modelId]);
|
|
151
|
+
const nextDeadline = latestDeadlineMs(
|
|
152
|
+
effective.retryAt?.getTime(),
|
|
153
|
+
Date.now() + (unsupportedCooldownMs ?? escalatedCooldownMs),
|
|
154
|
+
previousDeadline,
|
|
155
|
+
);
|
|
156
|
+
const nextUntil = new Date(nextDeadline).toISOString();
|
|
157
157
|
const previousUntil = existing[modelId];
|
|
158
158
|
// A re-statement is not an event: what the audit row reports is the
|
|
159
159
|
// *entry* into a cooling episode, and the deadline itself is not a
|
|
@@ -213,10 +213,10 @@ export async function recordAccountFailure(
|
|
|
213
213
|
createdAt: now,
|
|
214
214
|
});
|
|
215
215
|
}
|
|
216
|
-
//
|
|
217
|
-
//
|
|
218
|
-
//
|
|
219
|
-
return { ...classification, routesVisible:
|
|
216
|
+
// The route catalog projects modelCooldowns and stamps this candidate as
|
|
217
|
+
// model_cooldown. Invalidate when its deadline changes, or the cached
|
|
218
|
+
// snapshot keeps routing this pair until some unrelated write rebuilds it.
|
|
219
|
+
return { ...classification, routesVisible: previousDeadline !== nextDeadline };
|
|
220
220
|
}
|
|
221
221
|
|
|
222
222
|
await client
|
|
@@ -550,11 +550,9 @@ export async function reportAttemptOutcome(
|
|
|
550
550
|
...report.evidence,
|
|
551
551
|
...(report.modelId === undefined ? {} : { modelId: report.modelId }),
|
|
552
552
|
}).catch(() => null);
|
|
553
|
-
// Gated on `routesVisible`, not on `mutatesAccount`: a
|
|
554
|
-
//
|
|
555
|
-
//
|
|
556
|
-
// on the next request, and under a failure wave each rebuild fed the next
|
|
557
|
-
// invalidation.
|
|
553
|
+
// Gated on `routesVisible`, not on `mutatesAccount`: a model cooldown changes
|
|
554
|
+
// routing, while a repeated report with the same deadline or a streak-only
|
|
555
|
+
// write does not.
|
|
558
556
|
if (classification?.routesVisible) await report.snapshotService?.invalidate();
|
|
559
557
|
}
|
|
560
558
|
|
|
@@ -46,6 +46,17 @@ async function traeVersion(response: Response): Promise<string | null> {
|
|
|
46
46
|
/"insert":"((?:January|February|March|April|May|June|July|August|September|October|November|December)\s+\d{1,2},\s*\d{4}\s*\([^)]*\))\\n"/g;
|
|
47
47
|
for (const match of text.matchAll(heading)) {
|
|
48
48
|
const window = text.slice(match.index ?? 0, (match.index ?? 0) + 900);
|
|
49
|
+
// A release line may name a *range* ("TRAE v3.6.1 ~ 3.6.4 are released"),
|
|
50
|
+
// and the newest entry is the newest build in it. Taking the first number
|
|
51
|
+
// reported 3.6.1 while 3.6.4 was already shipping — and on an entry whose
|
|
52
|
+
// heading carried no version at all the old pattern matched nothing, so
|
|
53
|
+
// discovery fell through to the next (older) entry and the pin drifted
|
|
54
|
+
// backwards on every refresh.
|
|
55
|
+
const range = window.match(/(\d+\.\d+\.\d+)\s*[~–-]\s*(\d+\.\d+\.\d+)/);
|
|
56
|
+
if (range) {
|
|
57
|
+
const high = range[2] ?? range[1];
|
|
58
|
+
if (high && isSemverish(high)) return high.trim();
|
|
59
|
+
}
|
|
49
60
|
const found = window.match(/TraeCode\s+v?(\d+\.\d+\.\d+)|v?(\d+\.\d+\.\d+)\s+is\s+released/i);
|
|
50
61
|
const version = found?.[1] ?? found?.[2];
|
|
51
62
|
if (version && isSemverish(version)) return version.trim();
|
|
@@ -221,7 +232,7 @@ const KIRO_NODE_VERSION = "24.18.0";
|
|
|
221
232
|
export const VERSION_SOURCES = {
|
|
222
233
|
antigravity: {
|
|
223
234
|
key: "antigravity",
|
|
224
|
-
fallback: "2.
|
|
235
|
+
fallback: "2.28.2",
|
|
225
236
|
sources: [
|
|
226
237
|
{
|
|
227
238
|
url: "https://antigravity-hub-auto-updater-974169037036.us-central1.run.app/manifest/latest-arm64-mac.yml",
|
|
@@ -231,7 +242,7 @@ export const VERSION_SOURCES = {
|
|
|
231
242
|
},
|
|
232
243
|
qoder: {
|
|
233
244
|
key: "qoder",
|
|
234
|
-
fallback: "1.1.
|
|
245
|
+
fallback: "1.1.81",
|
|
235
246
|
sources: [
|
|
236
247
|
{
|
|
237
248
|
url: "https://registry.npmjs.org/@qoder-ai/qodercli/latest",
|
|
@@ -243,18 +254,21 @@ export const VERSION_SOURCES = {
|
|
|
243
254
|
key: "opencode",
|
|
244
255
|
// The v1 package (`opencode-ai`) stopped at 1.18. The v2 line publishes
|
|
245
256
|
// under `@opencode/cli`, and this feed is what its own installer reads.
|
|
246
|
-
fallback: "2.
|
|
257
|
+
fallback: "2.2.2",
|
|
247
258
|
minVersion: "2.0.0",
|
|
248
259
|
sources: [{ url: "https://opencode.ai/update/api/latest/cli/npm" }],
|
|
249
260
|
},
|
|
250
261
|
commandcode: {
|
|
251
262
|
key: "commandcode",
|
|
252
|
-
|
|
263
|
+
// npm `command-code` latest as of 2026-10-30. The CLI sends this as
|
|
264
|
+
// `x-command-code-version`, so a stale fallback presents a client build
|
|
265
|
+
// the upstream no longer ships — raise it whenever the package moves.
|
|
266
|
+
fallback: "1.88.2",
|
|
253
267
|
sources: [{ url: "https://registry.npmjs.org/command-code/latest" }],
|
|
254
268
|
},
|
|
255
269
|
grok: {
|
|
256
270
|
key: "grok",
|
|
257
|
-
fallback: "1.0.
|
|
271
|
+
fallback: "1.0.70",
|
|
258
272
|
sources: [
|
|
259
273
|
{ url: "https://storage.googleapis.com/grok-build-public-artifacts/cli/stable" },
|
|
260
274
|
{ url: "https://registry.npmjs.org/@xai-official/grok/latest" },
|
|
@@ -262,7 +276,7 @@ export const VERSION_SOURCES = {
|
|
|
262
276
|
},
|
|
263
277
|
clineClient: {
|
|
264
278
|
key: "cline-client",
|
|
265
|
-
fallback: "4.1.
|
|
279
|
+
fallback: "4.1.26",
|
|
266
280
|
minVersion: "4.1.23",
|
|
267
281
|
sources: [
|
|
268
282
|
{
|
|
@@ -273,22 +287,22 @@ export const VERSION_SOURCES = {
|
|
|
273
287
|
},
|
|
274
288
|
clineSdk: {
|
|
275
289
|
key: "cline-sdk",
|
|
276
|
-
fallback: "0.0
|
|
290
|
+
fallback: "0.2.0",
|
|
277
291
|
sources: [{ url: "https://registry.npmjs.org/@cline/sdk/latest", extract: clineSdkVersion }],
|
|
278
292
|
},
|
|
279
293
|
clineCli: {
|
|
280
294
|
key: "cline-cli",
|
|
281
|
-
fallback: "3.0.
|
|
295
|
+
fallback: "3.0.74",
|
|
282
296
|
sources: [{ url: "https://registry.npmjs.org/cline/latest", extract: clineSdkVersion }],
|
|
283
297
|
},
|
|
284
298
|
codex: {
|
|
285
299
|
key: "codex",
|
|
286
|
-
fallback: "0.
|
|
300
|
+
fallback: "0.166.0",
|
|
287
301
|
sources: [{ url: "https://registry.npmjs.org/@openai/codex/latest" }],
|
|
288
302
|
},
|
|
289
303
|
workbuddyClient: {
|
|
290
304
|
key: "workbuddy-client",
|
|
291
|
-
fallback: "5.
|
|
305
|
+
fallback: "5.9.4.42593613",
|
|
292
306
|
sources: [
|
|
293
307
|
{
|
|
294
308
|
url: "https://www.workbuddy.ai/v2/update?platform=workbuddy-win32-x64-user",
|
|
@@ -302,7 +316,7 @@ export const VERSION_SOURCES = {
|
|
|
302
316
|
},
|
|
303
317
|
workbuddyCnClient: {
|
|
304
318
|
key: "workbuddy-cn-client",
|
|
305
|
-
fallback: "5.
|
|
319
|
+
fallback: "5.9.3.42398134",
|
|
306
320
|
sources: [
|
|
307
321
|
{
|
|
308
322
|
url: "https://www.workbuddy.cn/v2/update?platform=workbuddy-win32-x64-user",
|
|
@@ -316,7 +330,7 @@ export const VERSION_SOURCES = {
|
|
|
316
330
|
},
|
|
317
331
|
workbuddyCli: {
|
|
318
332
|
key: "workbuddy",
|
|
319
|
-
fallback: "2.
|
|
333
|
+
fallback: "2.178.1",
|
|
320
334
|
sources: [
|
|
321
335
|
{ url: "https://registry.npmjs.org/@tencent-ai/codebuddy-code/latest" },
|
|
322
336
|
{ url: "https://registry.npmmirror.com/@tencent-ai/codebuddy-code/latest" },
|
|
@@ -329,7 +343,7 @@ export const VERSION_SOURCES = {
|
|
|
329
343
|
},
|
|
330
344
|
kiro: {
|
|
331
345
|
key: "kiro",
|
|
332
|
-
fallback: "1.
|
|
346
|
+
fallback: "1.4.62",
|
|
333
347
|
sources: [
|
|
334
348
|
{
|
|
335
349
|
// Kiro's downloads page exposes no stable version string. Homebrew
|
|
@@ -343,7 +357,7 @@ export const VERSION_SOURCES = {
|
|
|
343
357
|
},
|
|
344
358
|
codebuddy: {
|
|
345
359
|
key: "codebuddy",
|
|
346
|
-
fallback: "2.
|
|
360
|
+
fallback: "2.178.1",
|
|
347
361
|
sources: [
|
|
348
362
|
{ url: "https://registry.npmjs.org/@tencent-ai/codebuddy-code/latest" },
|
|
349
363
|
{ url: "https://registry.npmmirror.com/@tencent-ai/codebuddy-code/latest" },
|
|
@@ -366,7 +380,7 @@ export const VERSION_SOURCES = {
|
|
|
366
380
|
// Current `@anthropic-ai/claude-code` release, so the billing
|
|
367
381
|
// `cc_version=` suffix and the `claude-cli/` User-Agent stay on what
|
|
368
382
|
// upstream ships.
|
|
369
|
-
fallback: "2.1.
|
|
383
|
+
fallback: "2.1.314",
|
|
370
384
|
sources: [{ url: "https://registry.npmjs.org/@anthropic-ai/claude-code/latest" }],
|
|
371
385
|
},
|
|
372
386
|
claudeSdk: {
|
|
@@ -393,20 +407,21 @@ export const VERSION_SOURCES = {
|
|
|
393
407
|
},
|
|
394
408
|
kilo: {
|
|
395
409
|
key: "kilo-code",
|
|
396
|
-
fallback: "7.8.
|
|
410
|
+
fallback: "7.8.25",
|
|
397
411
|
sources: [{ url: "https://open-vsx.org/api/kilocode/kilo-code" }],
|
|
398
412
|
},
|
|
399
413
|
muse: {
|
|
400
414
|
key: "muse-code",
|
|
401
|
-
fallback: "1.4.
|
|
415
|
+
fallback: "1.4.14-R7309.1",
|
|
402
416
|
sources: [{ url: "https://api.meta.ai/muse-code/channels/muse-stable" }],
|
|
403
417
|
},
|
|
404
418
|
trae: {
|
|
405
419
|
key: "trae",
|
|
406
|
-
// Fallback = the
|
|
407
|
-
//
|
|
420
|
+
// Fallback = the newest release the changelog names, which is the *high*
|
|
421
|
+
// end of its newest line ("TRAE v3.6.1 ~ 3.6.4"). Discovery replaces it
|
|
422
|
+
// within one monitor tick; it is only what a cold process sends before
|
|
408
423
|
// the first successful fetch.
|
|
409
|
-
fallback: "3.6.
|
|
424
|
+
fallback: "3.6.4",
|
|
410
425
|
sources: [
|
|
411
426
|
{
|
|
412
427
|
url: "https://docs.trae.ai/ide/changelog?_lang=en",
|
|
@@ -419,7 +434,7 @@ export const VERSION_SOURCES = {
|
|
|
419
434
|
// Fallback = the last observed wire version. Discovery replaces it within
|
|
420
435
|
// one monitor tick; it is only what a cold process sends before the first
|
|
421
436
|
// successful fetch.
|
|
422
|
-
fallback: "cli-
|
|
437
|
+
fallback: "cli-3.27.22-b1aa50c",
|
|
423
438
|
sources: [
|
|
424
439
|
{
|
|
425
440
|
url: "https://cursor.com/api/download?platform=win32-x64-user&releaseTrack=stable",
|
|
@@ -433,7 +448,7 @@ export const VERSION_SOURCES = {
|
|
|
433
448
|
// comment this replaced claimed no semver existed and pinned a date tag
|
|
434
449
|
// (`v2026.9.24`) as the version — a tag, not a release, and not the
|
|
435
450
|
// identity the client sends.
|
|
436
|
-
fallback: "0.22.
|
|
451
|
+
fallback: "0.22.5",
|
|
437
452
|
sources: [
|
|
438
453
|
{
|
|
439
454
|
url: "https://api.github.com/repos/NousResearch/hermes-agent/releases/latest",
|
|
@@ -155,6 +155,14 @@ export interface ProviderDispatchContext {
|
|
|
155
155
|
* and hold no database handle.
|
|
156
156
|
*/
|
|
157
157
|
readonly image_clamp_max_bytes?: number | undefined;
|
|
158
|
+
/**
|
|
159
|
+
* Tenant system-prompt text resolved once per request (append/override
|
|
160
|
+
* injection). The buddy adapters fold it into their fixed system turn after
|
|
161
|
+
* serialization, wrapped in the gateway's marker prefix/suffix, so the
|
|
162
|
+
* upstream's channel-identity validation still sees its prompt as the
|
|
163
|
+
* opener.
|
|
164
|
+
*/
|
|
165
|
+
readonly tenant_system_prompt?: string | undefined;
|
|
158
166
|
/** Opaque identity shared by retries of one logical gateway request. */
|
|
159
167
|
readonly request_identity?: object | undefined;
|
|
160
168
|
/**
|
|
@@ -1,5 +1,24 @@
|
|
|
1
1
|
// Shared authorization primitives: closed scope model and immutable access decisions.
|
|
2
2
|
|
|
3
|
+
/**
|
|
4
|
+
* The single identity of "who is speaking" — resolved once at auth and carried
|
|
5
|
+
* through planning, admission, pool selection, and telemetry so every layer
|
|
6
|
+
* reasons about the same caller.
|
|
7
|
+
*
|
|
8
|
+
* - `apiKeyId` — the credential that authenticated.
|
|
9
|
+
* - `admissionIdentity` — the quota/concurrency owner (the share parent for a
|
|
10
|
+
* child key; otherwise the key itself).
|
|
11
|
+
* - `tenantId` — the tenant the request runs as.
|
|
12
|
+
*
|
|
13
|
+
* Provider/account/model are added by the candidate, not here: the caller
|
|
14
|
+
* identity is stable regardless of what serves the request.
|
|
15
|
+
*/
|
|
16
|
+
export interface CallerIdentity {
|
|
17
|
+
readonly apiKeyId: string;
|
|
18
|
+
readonly admissionIdentity: string;
|
|
19
|
+
readonly tenantId: string;
|
|
20
|
+
}
|
|
21
|
+
|
|
3
22
|
/**
|
|
4
23
|
* Closed set of authorization scopes.
|
|
5
24
|
* - `routing:invoke`: May call only /v1/* gateway routes within holder's tenant.
|
|
@@ -6,7 +6,7 @@ import { DrizzleApiKeyStore } from "../persistence/stores/api-key-store";
|
|
|
6
6
|
import { GatewayError } from "../protocol/gateway-error";
|
|
7
7
|
import { hashSecret } from "./crypto";
|
|
8
8
|
import { AUTH_CACHE_TTL_MS, createApiKeyAuthorizationCache } from "./api-key-auth-cache";
|
|
9
|
-
import type { AccessScope } from "./access-control";
|
|
9
|
+
import type { AccessScope, CallerIdentity } from "./access-control";
|
|
10
10
|
import type { ApiKeyModelAccessMode } from "../persistence/schema";
|
|
11
11
|
|
|
12
12
|
// ── Authorization cache ──────────────────────────────────────────────────────
|
|
@@ -222,6 +222,12 @@ export interface ResolvedApiKey {
|
|
|
222
222
|
readonly modelPrefix?: string;
|
|
223
223
|
/** Share-template id whose remote CLI mappings are inherited by this child. */
|
|
224
224
|
readonly cliMappingOwnerId?: string;
|
|
225
|
+
/**
|
|
226
|
+
* Single "who is speaking" identity derived at auth. Carried through
|
|
227
|
+
* planning, admission, pool selection, and telemetry so every gate reasons
|
|
228
|
+
* about the same caller.
|
|
229
|
+
*/
|
|
230
|
+
readonly caller: CallerIdentity;
|
|
225
231
|
}
|
|
226
232
|
|
|
227
233
|
/**
|
|
@@ -398,6 +404,13 @@ export async function resolveApiKeyAuthorization(
|
|
|
398
404
|
snapshot,
|
|
399
405
|
...(policy.modelPrefix ? { modelPrefix: policy.modelPrefix } : {}),
|
|
400
406
|
...(parent?.keyMode === "share" ? { cliMappingOwnerId: parent.id } : {}),
|
|
407
|
+
caller: {
|
|
408
|
+
apiKeyId: row.id,
|
|
409
|
+
// The admission identity is the family owner for a share child — the
|
|
410
|
+
// same id every counter/lease in the system keys against.
|
|
411
|
+
admissionIdentity: snapshot.admission_identity ?? row.id,
|
|
412
|
+
tenantId: row.tenantId,
|
|
413
|
+
},
|
|
401
414
|
};
|
|
402
415
|
authorizationCache.rememberPositive(hash, value.id, value, Date.now());
|
|
403
416
|
// Shared copy for peer processes, keyed by the token hash so a lookup can
|