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.
Files changed (249) hide show
  1. package/.env.example +2 -0
  2. package/dist/dashboard/assets/{BannedPage-XaUmiBrC.js → BannedPage-2TJD37Hb.js} +1 -1
  3. package/dist/dashboard/assets/BannedPage-2TJD37Hb.js.gz +0 -0
  4. package/dist/dashboard/assets/CliToolDetailPage-xefJNRmJ.js +1 -0
  5. package/dist/dashboard/assets/CliToolDetailPage-xefJNRmJ.js.gz +0 -0
  6. package/dist/dashboard/assets/CliToolsPage-Rj9iHQ3F.js +1 -0
  7. package/dist/dashboard/assets/CliToolsPage-Rj9iHQ3F.js.gz +0 -0
  8. package/dist/dashboard/assets/{CombosPage-CXGS_Ct-.js → CombosPage-JEbYJFxw.js} +1 -1
  9. package/dist/dashboard/assets/CombosPage-JEbYJFxw.js.gz +0 -0
  10. package/dist/dashboard/assets/{ConsoleLogPage-DGnGNYnM.js → ConsoleLogPage-C3xx_inM.js} +1 -1
  11. package/dist/dashboard/assets/ConsoleLogPage-C3xx_inM.js.gz +0 -0
  12. package/dist/dashboard/assets/CustomizationPage-Dw306gJU.js +1 -0
  13. package/dist/dashboard/assets/CustomizationPage-Dw306gJU.js.gz +0 -0
  14. package/dist/dashboard/assets/HealthEventsModal-jaBo1TWV.js +1 -0
  15. package/dist/dashboard/assets/HealthEventsModal-jaBo1TWV.js.gz +0 -0
  16. package/dist/dashboard/assets/{LoginPage-BdmRLnq2.js → LoginPage-C4w7vea-.js} +1 -1
  17. package/dist/dashboard/assets/LoginPage-C4w7vea-.js.gz +0 -0
  18. package/dist/dashboard/assets/{ModelPicker-BH8Tehdi.js → ModelPicker-BtPcAfot.js} +1 -1
  19. package/dist/dashboard/assets/ModelPicker-BtPcAfot.js.gz +0 -0
  20. package/dist/dashboard/assets/OverviewPage-CGC5gNs1.js +1 -0
  21. package/dist/dashboard/assets/OverviewPage-CGC5gNs1.js.gz +0 -0
  22. package/dist/dashboard/assets/ProviderDetailPage-DQcthNGt.js +4 -0
  23. package/dist/dashboard/assets/ProviderDetailPage-DQcthNGt.js.gz +0 -0
  24. package/dist/dashboard/assets/ProvidersPage-0OsXRGXe.js +1 -0
  25. package/dist/dashboard/assets/ProvidersPage-0OsXRGXe.js.gz +0 -0
  26. package/dist/dashboard/assets/ProxyPage-DdPc8slb.js +9 -0
  27. package/dist/dashboard/assets/ProxyPage-DdPc8slb.js.gz +0 -0
  28. package/dist/dashboard/assets/{QuotaPage-7P2n--Fv.js → QuotaPage-Ci5O4BnH.js} +1 -1
  29. package/dist/dashboard/assets/QuotaPage-Ci5O4BnH.js.gz +0 -0
  30. package/dist/dashboard/assets/{SettingsPage-CxZ1fTkN.js → SettingsPage-CDZjTt87.js} +1 -1
  31. package/dist/dashboard/assets/SettingsPage-CDZjTt87.js.gz +0 -0
  32. package/dist/dashboard/assets/SetupPage-BQS0Rx_u.js +1 -0
  33. package/dist/dashboard/assets/SetupPage-BQS0Rx_u.js.gz +0 -0
  34. package/dist/dashboard/assets/{SortableList-Dqf4zpS-.js → SortableList-BAL5npBC.js} +1 -1
  35. package/dist/dashboard/assets/SortableList-BAL5npBC.js.gz +0 -0
  36. package/dist/dashboard/assets/{StudioPage-Byj2lAEm.js → StudioPage-TP7voAQ0.js} +3 -3
  37. package/dist/dashboard/assets/StudioPage-TP7voAQ0.js.gz +0 -0
  38. package/dist/dashboard/assets/{UsagePage-CTkLAuRc.js → UsagePage-Cj_fEzhg.js} +13 -13
  39. package/dist/dashboard/assets/UsagePage-Cj_fEzhg.js.gz +0 -0
  40. package/dist/dashboard/assets/{api-keys-CUi6UXsR.js → api-keys-MbI7e7rt.js} +1 -1
  41. package/dist/dashboard/assets/api-keys-MbI7e7rt.js.gz +0 -0
  42. package/dist/dashboard/assets/app-B2vyPoII.css +2 -0
  43. package/dist/dashboard/assets/app-B2vyPoII.css.gz +0 -0
  44. package/dist/dashboard/assets/arrow-up-59ms1iiT.js +1 -0
  45. package/dist/dashboard/assets/arrow-up-59ms1iiT.js.gz +0 -0
  46. package/dist/dashboard/assets/{assert-67xXH5rf.js → assert-C0YYuZJ4.js} +1 -1
  47. package/dist/dashboard/assets/assert-C0YYuZJ4.js.gz +0 -0
  48. package/dist/dashboard/assets/{badge-DC2L43Mj.js → badge-B_DMpMJN.js} +1 -1
  49. package/dist/dashboard/assets/badge-B_DMpMJN.js.gz +0 -0
  50. package/dist/dashboard/assets/{banner-BBVcb0J3.js → banner-DMrMPmth.js} +1 -1
  51. package/dist/dashboard/assets/banner-DMrMPmth.js.gz +0 -0
  52. package/dist/dashboard/assets/{card-Cca1XC6G.js → card-Be5Uzamx.js} +1 -1
  53. package/dist/dashboard/assets/card-Be5Uzamx.js.gz +0 -0
  54. package/dist/dashboard/assets/{cli-tools-KvsWa1Jl.js → cli-tools-muFRphDu.js} +1 -1
  55. package/dist/dashboard/assets/cli-tools-muFRphDu.js.gz +0 -0
  56. package/dist/dashboard/assets/{clipboard-button-DUzEbynq.js → clipboard-button-DOfrVmsi.js} +1 -1
  57. package/dist/dashboard/assets/clipboard-button-DOfrVmsi.js.gz +0 -0
  58. package/dist/dashboard/assets/{dialog-DFvWhwTa.js → dialog-C9omdjQt.js} +1 -1
  59. package/dist/dashboard/assets/dialog-C9omdjQt.js.gz +0 -0
  60. package/dist/dashboard/assets/drawer-Bm8WKaGY.js +1 -0
  61. package/dist/dashboard/assets/drawer-Bm8WKaGY.js.gz +0 -0
  62. package/dist/dashboard/assets/{entry-_xHo5x6r.js → entry-DS90gUks.js} +1 -1
  63. package/dist/dashboard/assets/entry-DS90gUks.js.gz +0 -0
  64. package/dist/dashboard/assets/{entry-ClCMCWWQ.js → entry-j40LJotO.js} +3 -3
  65. package/dist/dashboard/assets/entry-j40LJotO.js.gz +0 -0
  66. package/dist/dashboard/assets/{entry-Bqy8RFvM.js → entry-q5HwZ8nN.js} +1 -1
  67. package/dist/dashboard/assets/entry-q5HwZ8nN.js.gz +0 -0
  68. package/dist/dashboard/assets/{concurrency-BDF0ok7y.js → fingerprint-pattern-BWuHtbW4.js} +1 -1
  69. package/dist/dashboard/assets/fingerprint-pattern-BWuHtbW4.js.gz +0 -0
  70. package/dist/dashboard/assets/{formatters-CWWZ-uCZ.js → formatters-BTScrQpe.js} +1 -1
  71. package/dist/dashboard/assets/formatters-BTScrQpe.js.gz +0 -0
  72. package/dist/dashboard/assets/{github-badge-C9e8Ts7e.js → github-badge-Orjuzemr.js} +1 -1
  73. package/dist/dashboard/assets/github-badge-Orjuzemr.js.gz +0 -0
  74. package/dist/dashboard/assets/{index-DFVFrnWW.js → index-cPrX5vn6.js} +2 -2
  75. package/dist/dashboard/assets/index-cPrX5vn6.js.gz +0 -0
  76. package/dist/dashboard/assets/{inline-BFUcvllj.js → inline-Dvux8w-0.js} +1 -1
  77. package/dist/dashboard/assets/inline-Dvux8w-0.js.gz +0 -0
  78. package/dist/dashboard/assets/{input-C_ILAvBb.js → input-CU1Rgf3p.js} +1 -1
  79. package/dist/dashboard/assets/input-CU1Rgf3p.js.gz +0 -0
  80. package/dist/dashboard/assets/{layout-CBIyT6Rt.js → layout-C_7A6H55.js} +1 -1
  81. package/dist/dashboard/assets/layout-C_7A6H55.js.gz +0 -0
  82. package/dist/dashboard/assets/{list-row-ebJ_-Nsb.js → list-row-CzFvogbj.js} +1 -1
  83. package/dist/dashboard/assets/list-row-CzFvogbj.js.gz +0 -0
  84. package/dist/dashboard/assets/{live-D0mm--ME.js → live-KaG15HLQ.js} +1 -1
  85. package/dist/dashboard/assets/live-KaG15HLQ.js.gz +0 -0
  86. package/dist/dashboard/assets/{mutation-sMD965oO.js → mutation-JJGQm8TD.js} +1 -1
  87. package/dist/dashboard/assets/mutation-JJGQm8TD.js.gz +0 -0
  88. package/dist/dashboard/assets/{numbers-BO9dKaFr.js → numbers-BNDZnsUE.js} +1 -1
  89. package/dist/dashboard/assets/numbers-BNDZnsUE.js.gz +0 -0
  90. package/dist/dashboard/assets/{query-keys-CfL_Zdoh.js → query-keys-CLaNz8Xz.js} +1 -1
  91. package/dist/dashboard/assets/query-keys-CLaNz8Xz.js.gz +0 -0
  92. package/dist/dashboard/assets/{routing-BnIpi1IJ.js → routing-BEH32QOC.js} +1 -1
  93. package/dist/dashboard/assets/routing-BEH32QOC.js.gz +0 -0
  94. package/dist/dashboard/assets/{select-DBNVLdUu.js → select-B3iTetHN.js} +1 -1
  95. package/dist/dashboard/assets/select-B3iTetHN.js.gz +0 -0
  96. package/dist/dashboard/assets/settings-2-Ds9DQp0t.js +1 -0
  97. package/dist/dashboard/assets/settings-2-Ds9DQp0t.js.gz +0 -0
  98. package/dist/dashboard/assets/{settings-BAFhDgX8.js → settings-Dt3Eqbnq.js} +1 -1
  99. package/dist/dashboard/assets/settings-Dt3Eqbnq.js.gz +0 -0
  100. package/dist/dashboard/assets/{stack-EMrOMt4I.js → stack-KrOrm7JZ.js} +1 -1
  101. package/dist/dashboard/assets/stack-KrOrm7JZ.js.gz +0 -0
  102. package/dist/dashboard/assets/{state-CWoo7ty5.js → state-DLS7IDMy.js} +1 -1
  103. package/dist/dashboard/assets/state-DLS7IDMy.js.gz +0 -0
  104. package/dist/dashboard/assets/{switch-CCjT6IkF.js → switch-42ItvbAs.js} +1 -1
  105. package/dist/dashboard/assets/switch-42ItvbAs.js.gz +0 -0
  106. package/dist/dashboard/assets/{useMutation-DjkLHUcE.js → useMutation-Dmnfh1Ue.js} +1 -1
  107. package/dist/dashboard/assets/useMutation-Dmnfh1Ue.js.gz +0 -0
  108. package/dist/dashboard/index.html +3 -2
  109. package/dist/dashboard/index.html.gz +0 -0
  110. package/dist/dashboard/precompressed-assets.json +117 -115
  111. package/migrations/full-postgres/0026_add_qoder_native_identity.sql +20 -0
  112. package/migrations/full-postgres/0027_qoder_identity_genuine_seed.sql +22 -0
  113. package/migrations/full-postgres/0028_add_family_template_key_telemetry.sql +40 -0
  114. package/migrations/full-postgres/0029_null_share_child_policy_columns.sql +38 -0
  115. package/migrations/full-postgres/0030_add_api_key_max_children.sql +10 -0
  116. package/migrations/full-postgres/0031_tenant_system_prompts.sql +16 -0
  117. package/migrations/lite-sqlite3/0026_add_qoder_native_identity.sql +15 -0
  118. package/migrations/lite-sqlite3/0027_qoder_identity_genuine_seed.sql +22 -0
  119. package/migrations/lite-sqlite3/0028_add_family_template_key_telemetry.sql +40 -0
  120. package/migrations/lite-sqlite3/0029_null_share_child_policy_columns.sql +38 -0
  121. package/migrations/lite-sqlite3/0030_add_api_key_max_children.sql +10 -0
  122. package/migrations/lite-sqlite3/0031_tenant_system_prompts.sql +10 -0
  123. package/package.json +1 -1
  124. package/src/config/network.ts +1 -1
  125. package/src/console/api-keys/bucket-spend.ts +7 -7
  126. package/src/console/api-keys/contracts.ts +423 -410
  127. package/src/console/api-keys/routes.ts +118 -19
  128. package/src/console/backup/export-endpoint.ts +8 -11
  129. package/src/console/backup/routes.ts +40 -8
  130. package/src/console/backup/service.ts +11 -6
  131. package/src/console/backup/store.ts +384 -88
  132. package/src/console/dashboard-assets.ts +16 -1
  133. package/src/console/domain-registration.ts +2 -0
  134. package/src/console/quota/account-quota-tenant.ts +8 -1
  135. package/src/console/quota/account-quota.ts +4 -1
  136. package/src/console/quota/native-identity.ts +189 -0
  137. package/src/console/remote-support/contracts.ts +12 -6
  138. package/src/console/remote-support/service.ts +2 -2
  139. package/src/console/share/share-router.ts +39 -6
  140. package/src/console/share/share-stats.ts +64 -25
  141. package/src/console/share/share-usage.ts +24 -7
  142. package/src/console/system-prompt/contracts.ts +102 -0
  143. package/src/console/system-prompt/routes.ts +165 -0
  144. package/src/main.ts +11 -0
  145. package/src/observability/event-query.ts +118 -7
  146. package/src/persistence/db-pg.ts +42 -0
  147. package/src/persistence/schema.ts +102 -0
  148. package/src/persistence/stores/api-key-store.ts +753 -581
  149. package/src/persistence/stores/share-store.ts +65 -13
  150. package/src/persistence/stores/telemetry-store.ts +57 -1
  151. package/src/persistence/system-prompt-store.ts +104 -0
  152. package/src/protocol/reasoning.ts +65 -1
  153. package/src/protocol/request/chat.ts +1 -0
  154. package/src/providers/integrations/antigravity/antigravity.ts +20 -23
  155. package/src/providers/integrations/buddy/buddy-chat-shared.ts +30 -2
  156. package/src/providers/integrations/buddy/buddy-media.ts +5 -1
  157. package/src/providers/integrations/buddy/codebuddy/provider.ts +5 -1
  158. package/src/providers/integrations/buddy/workbuddy/provider.ts +5 -1
  159. package/src/providers/integrations/cline/cline.ts +7 -0
  160. package/src/providers/integrations/github/github.ts +20 -1
  161. package/src/providers/integrations/hermes.ts +5 -1
  162. package/src/providers/integrations/kilo/kilo.ts +4 -0
  163. package/src/providers/integrations/openrouter/provider.ts +4 -0
  164. package/src/providers/integrations/qoder/qoder-checkin.ts +224 -51
  165. package/src/providers/integrations/qoder/qoder-native-identity-store.ts +156 -0
  166. package/src/providers/integrations/qoder/qoder-oauth.ts +12 -3
  167. package/src/providers/integrations/qoder/qoder-umid.ts +288 -0
  168. package/src/providers/integrations/qoder/qoder.ts +5 -3
  169. package/src/providers/operations/account-error-classification.ts +6 -8
  170. package/src/providers/operations/account-health-service.ts +14 -16
  171. package/src/providers/operations/client-versions.ts +37 -22
  172. package/src/providers/provider-contracts.ts +8 -0
  173. package/src/security/access-control.ts +19 -0
  174. package/src/security/api-key-auth.ts +14 -1
  175. package/src/transport/dispatch/proxy-request.ts +31 -2
  176. package/src/transport/dispatch/streaming/stream.ts +16 -1
  177. package/src/transport/dispatch/upstream.ts +10 -0
  178. package/src/transport/request/preparer.ts +17 -1
  179. package/src/transport/request/system-prompt-injection.ts +89 -0
  180. package/src/transport/request/tenant-preferences.ts +43 -0
  181. package/src/transport/routing/admission.ts +13 -3
  182. package/src/transport/routing/route-catalog.ts +7 -0
  183. package/src/transport/routing/route-model.ts +53 -0
  184. package/src/transport/routing/router.ts +306 -256
  185. package/src/workers/daily-checkin.ts +2 -0
  186. package/dist/dashboard/assets/BannedPage-XaUmiBrC.js.gz +0 -0
  187. package/dist/dashboard/assets/CliToolDetailPage-CqD7QiLM.js +0 -1
  188. package/dist/dashboard/assets/CliToolDetailPage-CqD7QiLM.js.gz +0 -0
  189. package/dist/dashboard/assets/CliToolsPage-0XUo4UMn.js +0 -1
  190. package/dist/dashboard/assets/CliToolsPage-0XUo4UMn.js.gz +0 -0
  191. package/dist/dashboard/assets/CombosPage-CXGS_Ct-.js.gz +0 -0
  192. package/dist/dashboard/assets/ConsoleLogPage-DGnGNYnM.js.gz +0 -0
  193. package/dist/dashboard/assets/CustomizationPage-DAG5J9EY.js +0 -1
  194. package/dist/dashboard/assets/CustomizationPage-DAG5J9EY.js.gz +0 -0
  195. package/dist/dashboard/assets/HealthEventsModal-BKtAPYVF.js +0 -1
  196. package/dist/dashboard/assets/HealthEventsModal-BKtAPYVF.js.gz +0 -0
  197. package/dist/dashboard/assets/LoginPage-BdmRLnq2.js.gz +0 -0
  198. package/dist/dashboard/assets/ModelPicker-BH8Tehdi.js.gz +0 -0
  199. package/dist/dashboard/assets/OverviewPage-CqMxibJB.js +0 -1
  200. package/dist/dashboard/assets/OverviewPage-CqMxibJB.js.gz +0 -0
  201. package/dist/dashboard/assets/ProviderDetailPage-CqMh2NRX.js +0 -4
  202. package/dist/dashboard/assets/ProviderDetailPage-CqMh2NRX.js.gz +0 -0
  203. package/dist/dashboard/assets/ProvidersPage-B9uWu4Nz.js +0 -1
  204. package/dist/dashboard/assets/ProvidersPage-B9uWu4Nz.js.gz +0 -0
  205. package/dist/dashboard/assets/ProxyPage-BoWOhpDO.js +0 -9
  206. package/dist/dashboard/assets/ProxyPage-BoWOhpDO.js.gz +0 -0
  207. package/dist/dashboard/assets/QuotaPage-7P2n--Fv.js.gz +0 -0
  208. package/dist/dashboard/assets/SettingsPage-CxZ1fTkN.js.gz +0 -0
  209. package/dist/dashboard/assets/SetupPage-Co12Zzaw.js +0 -1
  210. package/dist/dashboard/assets/SetupPage-Co12Zzaw.js.gz +0 -0
  211. package/dist/dashboard/assets/SortableList-Dqf4zpS-.js.gz +0 -0
  212. package/dist/dashboard/assets/StudioPage-Byj2lAEm.js.gz +0 -0
  213. package/dist/dashboard/assets/UsagePage-CTkLAuRc.js.gz +0 -0
  214. package/dist/dashboard/assets/api-keys-CUi6UXsR.js.gz +0 -0
  215. package/dist/dashboard/assets/app-KvsUJUMF.css +0 -2
  216. package/dist/dashboard/assets/app-KvsUJUMF.css.gz +0 -0
  217. package/dist/dashboard/assets/assert-67xXH5rf.js.gz +0 -0
  218. package/dist/dashboard/assets/badge-DC2L43Mj.js.gz +0 -0
  219. package/dist/dashboard/assets/banner-BBVcb0J3.js.gz +0 -0
  220. package/dist/dashboard/assets/card-Cca1XC6G.js.gz +0 -0
  221. package/dist/dashboard/assets/cli-tools-KvsWa1Jl.js.gz +0 -0
  222. package/dist/dashboard/assets/clipboard-button-DUzEbynq.js.gz +0 -0
  223. package/dist/dashboard/assets/concurrency-BDF0ok7y.js.gz +0 -0
  224. package/dist/dashboard/assets/dialog-DFvWhwTa.js.gz +0 -0
  225. package/dist/dashboard/assets/drawer-CFM2sMX1.js +0 -1
  226. package/dist/dashboard/assets/drawer-CFM2sMX1.js.gz +0 -0
  227. package/dist/dashboard/assets/entry-Bqy8RFvM.js.gz +0 -0
  228. package/dist/dashboard/assets/entry-ClCMCWWQ.js.gz +0 -0
  229. package/dist/dashboard/assets/entry-_xHo5x6r.js.gz +0 -0
  230. package/dist/dashboard/assets/formatters-CWWZ-uCZ.js.gz +0 -0
  231. package/dist/dashboard/assets/github-badge-C9e8Ts7e.js.gz +0 -0
  232. package/dist/dashboard/assets/index-DFVFrnWW.js.gz +0 -0
  233. package/dist/dashboard/assets/inline-BFUcvllj.js.gz +0 -0
  234. package/dist/dashboard/assets/input-C_ILAvBb.js.gz +0 -0
  235. package/dist/dashboard/assets/layout-CBIyT6Rt.js.gz +0 -0
  236. package/dist/dashboard/assets/list-row-ebJ_-Nsb.js.gz +0 -0
  237. package/dist/dashboard/assets/live-D0mm--ME.js.gz +0 -0
  238. package/dist/dashboard/assets/mutation-sMD965oO.js.gz +0 -0
  239. package/dist/dashboard/assets/numbers-BO9dKaFr.js.gz +0 -0
  240. package/dist/dashboard/assets/query-keys-CfL_Zdoh.js.gz +0 -0
  241. package/dist/dashboard/assets/routing-BnIpi1IJ.js.gz +0 -0
  242. package/dist/dashboard/assets/select-DBNVLdUu.js.gz +0 -0
  243. package/dist/dashboard/assets/settings-BAFhDgX8.js.gz +0 -0
  244. package/dist/dashboard/assets/stack-EMrOMt4I.js.gz +0 -0
  245. package/dist/dashboard/assets/state-CWoo7ty5.js.gz +0 -0
  246. package/dist/dashboard/assets/switch-CCjT6IkF.js.gz +0 -0
  247. package/dist/dashboard/assets/useMutation-DjkLHUcE.js.gz +0 -0
  248. /package/dist/dashboard/assets/{app-BaWd0puM.js → app-wvwCi4Eh.js} +0 -0
  249. /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) instead of signing with a guessed id.
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 || !state.userId) {
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
- * The snapshot projects `provider_accounts.status` and
255
- * `.cooldownUntil` and nothing else (`route-catalog.ts:286-287`), so only a
256
- * write that moves one of those has to drop it. Everything else a failure
257
- * persists — the failure streak, the error text, the category, a
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
- * That made a failure wave self-sustaining: N failed attempts produced N
262
- * invalidations, and every following request paid the full rebuild.
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 nextUntil = new Date(
151
- latestDeadlineMs(
152
- effective.retryAt?.getTime(),
153
- Date.now() + (unsupportedCooldownMs ?? escalatedCooldownMs),
154
- existing[modelId] ? Date.parse(existing[modelId]!) : undefined,
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
- // `status` and `cooldownUntil` are untouched on this path — only the
217
- // (account, model) pair backed off — so the snapshot is still accurate
218
- // and must not be dropped.
219
- return { ...classification, routesVisible: false };
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 failure that only
554
- // cooled one (account, model) pair, or only advanced the streak, changed
555
- // nothing routing reads. Invalidating for those rebuilt an 11-SELECT snapshot
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.24.0",
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.74",
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.0.32",
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
- fallback: "1.84.0",
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.62",
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.25",
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.95",
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.73",
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.163.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.8.4.41366630",
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.8.3.41334564",
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.169.0",
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.3.11",
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.169.0",
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.302",
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.14",
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.10-R6427.1",
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 donor's last observed client version. Discovery replaces
407
- // it within one monitor tick; it is only what a cold process sends before
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.2",
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-2026.10.19-6bbf2c4",
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.2",
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