cartethyia 2.7.1 → 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 (263) hide show
  1. package/.env.example +2 -0
  2. package/dist/dashboard/assets/{BannedPage-D-J75hBM.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-QoJ6foxo.js → CombosPage-JEbYJFxw.js} +1 -1
  9. package/dist/dashboard/assets/CombosPage-JEbYJFxw.js.gz +0 -0
  10. package/dist/dashboard/assets/{ConsoleLogPage-DpNRay4C.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-DubdQcvX.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-ATpaxrbl.js → QuotaPage-Ci5O4BnH.js} +1 -1
  29. package/dist/dashboard/assets/QuotaPage-Ci5O4BnH.js.gz +0 -0
  30. package/dist/dashboard/assets/{SettingsPage-CETophpx.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-CAK-EN9z.js → SortableList-BAL5npBC.js} +1 -1
  35. package/dist/dashboard/assets/SortableList-BAL5npBC.js.gz +0 -0
  36. package/dist/dashboard/assets/{StudioPage-B7W2_RhT.js → StudioPage-TP7voAQ0.js} +3 -3
  37. package/dist/dashboard/assets/StudioPage-TP7voAQ0.js.gz +0 -0
  38. package/dist/dashboard/assets/{UsagePage-6T4EFDjW.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-BLBLxX0h.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-mpBPqAQ8.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-D6QzEC_n.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-Cj5Nw43g.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/cli-tools/contracts.ts +10 -4
  133. package/src/console/dashboard-assets.ts +16 -1
  134. package/src/console/domain-registration.ts +2 -0
  135. package/src/console/quota/account-quota-tenant.ts +8 -1
  136. package/src/console/quota/account-quota.ts +4 -1
  137. package/src/console/quota/native-identity.ts +189 -0
  138. package/src/console/remote-support/contracts.ts +12 -6
  139. package/src/console/remote-support/service.ts +2 -2
  140. package/src/console/share/share-router.ts +39 -6
  141. package/src/console/share/share-stats.ts +64 -25
  142. package/src/console/share/share-usage.ts +24 -7
  143. package/src/console/system-prompt/contracts.ts +102 -0
  144. package/src/console/system-prompt/routes.ts +165 -0
  145. package/src/main.ts +11 -0
  146. package/src/network/outbound-fetch.ts +281 -226
  147. package/src/network/pool/agent.ts +7 -0
  148. package/src/observability/event-query.ts +118 -7
  149. package/src/persistence/db-pg.ts +42 -0
  150. package/src/persistence/schema.ts +102 -0
  151. package/src/persistence/stores/api-key-store.ts +753 -581
  152. package/src/persistence/stores/share-store.ts +65 -13
  153. package/src/persistence/stores/telemetry-store.ts +57 -1
  154. package/src/persistence/system-prompt-store.ts +104 -0
  155. package/src/protocol/reasoning.ts +65 -1
  156. package/src/protocol/request/chat.ts +1 -0
  157. package/src/providers/default-registry.ts +1 -0
  158. package/src/providers/integrations/antigravity/antigravity.ts +20 -23
  159. package/src/providers/integrations/buddy/buddy-chat-shared.ts +30 -2
  160. package/src/providers/integrations/buddy/buddy-media.ts +5 -1
  161. package/src/providers/integrations/buddy/codebuddy/provider.ts +5 -1
  162. package/src/providers/integrations/buddy/workbuddy/provider.ts +5 -1
  163. package/src/providers/integrations/cline/cline.ts +7 -0
  164. package/src/providers/integrations/github/github.ts +20 -1
  165. package/src/providers/integrations/hermes.ts +5 -1
  166. package/src/providers/integrations/kilo/kilo.ts +4 -0
  167. package/src/providers/integrations/ollamacloud-models.ts +50 -0
  168. package/src/providers/integrations/openrouter/provider.ts +4 -0
  169. package/src/providers/integrations/qoder/qoder-checkin.ts +224 -51
  170. package/src/providers/integrations/qoder/qoder-native-identity-store.ts +156 -0
  171. package/src/providers/integrations/qoder/qoder-oauth.ts +24 -4
  172. package/src/providers/integrations/qoder/qoder-realm.ts +20 -0
  173. package/src/providers/integrations/qoder/qoder-text-tools.ts +277 -0
  174. package/src/providers/integrations/qoder/qoder-umid.ts +288 -0
  175. package/src/providers/integrations/qoder/qoder.ts +1375 -1641
  176. package/src/providers/operations/account-error-classification.ts +1682 -1660
  177. package/src/providers/operations/account-health-service.ts +619 -621
  178. package/src/providers/operations/client-versions.ts +37 -22
  179. package/src/providers/operations/provider-catalog-service.ts +11 -1
  180. package/src/providers/provider-contracts.ts +15 -2
  181. package/src/security/access-control.ts +19 -0
  182. package/src/security/api-key-auth.ts +14 -1
  183. package/src/security/cli-client-fingerprint.ts +9 -2
  184. package/src/transport/dispatch/empty-stream.ts +224 -224
  185. package/src/transport/dispatch/proxy-request.ts +31 -2
  186. package/src/transport/dispatch/streaming/stream.ts +16 -1
  187. package/src/transport/dispatch/upstream.ts +10 -0
  188. package/src/transport/failure-policy.ts +33 -0
  189. package/src/transport/request/preparer.ts +17 -1
  190. package/src/transport/request/system-prompt-injection.ts +89 -0
  191. package/src/transport/request/tenant-preferences.ts +43 -0
  192. package/src/transport/routing/admission.ts +13 -3
  193. package/src/transport/routing/route-catalog.ts +41 -12
  194. package/src/transport/routing/route-model.ts +53 -0
  195. package/src/transport/routing/router.ts +306 -228
  196. package/src/transport/surface/responses/encode.ts +2 -3
  197. package/src/transport/surface/responses/parse.ts +49 -7
  198. package/src/types/undici.d.ts +33 -0
  199. package/src/workers/daily-checkin.ts +2 -0
  200. package/dist/dashboard/assets/BannedPage-D-J75hBM.js.gz +0 -0
  201. package/dist/dashboard/assets/CliToolDetailPage-CVe6RaBH.js +0 -1
  202. package/dist/dashboard/assets/CliToolDetailPage-CVe6RaBH.js.gz +0 -0
  203. package/dist/dashboard/assets/CliToolsPage-Dd8ZSamp.js +0 -1
  204. package/dist/dashboard/assets/CliToolsPage-Dd8ZSamp.js.gz +0 -0
  205. package/dist/dashboard/assets/CombosPage-QoJ6foxo.js.gz +0 -0
  206. package/dist/dashboard/assets/ConsoleLogPage-DpNRay4C.js.gz +0 -0
  207. package/dist/dashboard/assets/CustomizationPage-CKUFTcW4.js +0 -1
  208. package/dist/dashboard/assets/CustomizationPage-CKUFTcW4.js.gz +0 -0
  209. package/dist/dashboard/assets/HealthEventsModal-CzSLiDzr.js +0 -1
  210. package/dist/dashboard/assets/HealthEventsModal-CzSLiDzr.js.gz +0 -0
  211. package/dist/dashboard/assets/LoginPage-DubdQcvX.js.gz +0 -0
  212. package/dist/dashboard/assets/ModelPicker-BH8Tehdi.js.gz +0 -0
  213. package/dist/dashboard/assets/OverviewPage-DuWzAQFL.js +0 -1
  214. package/dist/dashboard/assets/OverviewPage-DuWzAQFL.js.gz +0 -0
  215. package/dist/dashboard/assets/ProviderDetailPage-BKEKe2Fm.js +0 -4
  216. package/dist/dashboard/assets/ProviderDetailPage-BKEKe2Fm.js.gz +0 -0
  217. package/dist/dashboard/assets/ProvidersPage-Bwotu8UJ.js +0 -1
  218. package/dist/dashboard/assets/ProvidersPage-Bwotu8UJ.js.gz +0 -0
  219. package/dist/dashboard/assets/ProxyPage-D7hpWbg9.js +0 -9
  220. package/dist/dashboard/assets/ProxyPage-D7hpWbg9.js.gz +0 -0
  221. package/dist/dashboard/assets/QuotaPage-ATpaxrbl.js.gz +0 -0
  222. package/dist/dashboard/assets/SettingsPage-CETophpx.js.gz +0 -0
  223. package/dist/dashboard/assets/SetupPage-BdgECjqq.js +0 -1
  224. package/dist/dashboard/assets/SetupPage-BdgECjqq.js.gz +0 -0
  225. package/dist/dashboard/assets/SortableList-CAK-EN9z.js.gz +0 -0
  226. package/dist/dashboard/assets/StudioPage-B7W2_RhT.js.gz +0 -0
  227. package/dist/dashboard/assets/UsagePage-6T4EFDjW.js.gz +0 -0
  228. package/dist/dashboard/assets/api-keys-CUi6UXsR.js.gz +0 -0
  229. package/dist/dashboard/assets/app-KvsUJUMF.css +0 -2
  230. package/dist/dashboard/assets/app-KvsUJUMF.css.gz +0 -0
  231. package/dist/dashboard/assets/assert-67xXH5rf.js.gz +0 -0
  232. package/dist/dashboard/assets/badge-DC2L43Mj.js.gz +0 -0
  233. package/dist/dashboard/assets/banner-BBVcb0J3.js.gz +0 -0
  234. package/dist/dashboard/assets/card-Cca1XC6G.js.gz +0 -0
  235. package/dist/dashboard/assets/cli-tools-KvsWa1Jl.js.gz +0 -0
  236. package/dist/dashboard/assets/clipboard-button-DUzEbynq.js.gz +0 -0
  237. package/dist/dashboard/assets/concurrency-BDF0ok7y.js.gz +0 -0
  238. package/dist/dashboard/assets/dialog-DFvWhwTa.js.gz +0 -0
  239. package/dist/dashboard/assets/drawer-CFM2sMX1.js +0 -1
  240. package/dist/dashboard/assets/drawer-CFM2sMX1.js.gz +0 -0
  241. package/dist/dashboard/assets/entry-BLBLxX0h.js.gz +0 -0
  242. package/dist/dashboard/assets/entry-Bqy8RFvM.js.gz +0 -0
  243. package/dist/dashboard/assets/entry-_xHo5x6r.js.gz +0 -0
  244. package/dist/dashboard/assets/formatters-mpBPqAQ8.js.gz +0 -0
  245. package/dist/dashboard/assets/github-badge-C9e8Ts7e.js.gz +0 -0
  246. package/dist/dashboard/assets/index-D6QzEC_n.js.gz +0 -0
  247. package/dist/dashboard/assets/inline-BFUcvllj.js.gz +0 -0
  248. package/dist/dashboard/assets/input-C_ILAvBb.js.gz +0 -0
  249. package/dist/dashboard/assets/layout-CBIyT6Rt.js.gz +0 -0
  250. package/dist/dashboard/assets/list-row-ebJ_-Nsb.js.gz +0 -0
  251. package/dist/dashboard/assets/live-D0mm--ME.js.gz +0 -0
  252. package/dist/dashboard/assets/mutation-sMD965oO.js.gz +0 -0
  253. package/dist/dashboard/assets/numbers-BO9dKaFr.js.gz +0 -0
  254. package/dist/dashboard/assets/query-keys-CfL_Zdoh.js.gz +0 -0
  255. package/dist/dashboard/assets/routing-BnIpi1IJ.js.gz +0 -0
  256. package/dist/dashboard/assets/select-Cj5Nw43g.js.gz +0 -0
  257. package/dist/dashboard/assets/settings-BAFhDgX8.js.gz +0 -0
  258. package/dist/dashboard/assets/stack-EMrOMt4I.js.gz +0 -0
  259. package/dist/dashboard/assets/state-CWoo7ty5.js.gz +0 -0
  260. package/dist/dashboard/assets/switch-CCjT6IkF.js.gz +0 -0
  261. package/dist/dashboard/assets/useMutation-DjkLHUcE.js.gz +0 -0
  262. /package/dist/dashboard/assets/{app-BaWd0puM.js → app-wvwCi4Eh.js} +0 -0
  263. /package/dist/dashboard/assets/{app-BaWd0puM.js.gz → app-wvwCi4Eh.js.gz} +0 -0
@@ -0,0 +1,102 @@
1
+ // Tenant system-prompt injection contracts.
2
+ //
3
+ // Shared by the console API, the persistence store, and the dispatch-path
4
+ // transform — all Elysia-free so the dashboard bundle can import the types.
5
+
6
+ /**
7
+ * One scope's prompt configuration. `enabled` is the master switch for the
8
+ * scope; `mode` decides how the text meets existing system content.
9
+ */
10
+ export interface PromptSetting {
11
+ readonly enabled: boolean;
12
+ readonly mode: PromptMode;
13
+ readonly text: string;
14
+ }
15
+
16
+ export const PROMPT_MODES = ["append", "override"] as const;
17
+ export type PromptMode = (typeof PROMPT_MODES)[number];
18
+
19
+ /** Per-provider entry: follow the tenant's global prompt, or carry a custom one. */
20
+ export interface ProviderPromptEntry {
21
+ readonly followGlobal: boolean;
22
+ /** Present and used only when `followGlobal` is false. */
23
+ readonly setting?: PromptSetting;
24
+ }
25
+
26
+ /** The full per-tenant configuration, one row in `tenant_system_prompts`. */
27
+ export interface TenantSystemPrompts {
28
+ /** `null` = never configured — distinct from an explicit disabled setting. */
29
+ readonly global: PromptSetting | null;
30
+ /** providerId → entry */
31
+ readonly providers: Readonly<Record<string, ProviderPromptEntry>>;
32
+ /** `${providerId}::${modelId}` → setting */
33
+ readonly models: Readonly<Record<string, PromptSetting>>;
34
+ }
35
+
36
+ /** Empty configuration: nothing configured anywhere. */
37
+ export const EMPTY_TENANT_SYSTEM_PROMPTS: TenantSystemPrompts = {
38
+ global: null,
39
+ providers: {},
40
+ models: {},
41
+ };
42
+
43
+ /** Maximum stored text per scope (5 MB). Enforced at the console API. */
44
+ export const SYSTEM_PROMPT_TEXT_CAP_BYTES = 5_000_000;
45
+
46
+ /**
47
+ * Marker wrapping tenant text injected into a provider wire whose upstream
48
+ * validates the system prompt as a channel identity (the buddy family). The
49
+ * fixed identity prompt stays the opener; the tenant text rides after it
50
+ * inside the same turn, framed so the provider's own parser is not confused.
51
+ */
52
+ export const SYSTEM_PROMPT_WRAPPER_PREFIX = "<cartethyia:system-prompt>";
53
+ export const SYSTEM_PROMPT_WRAPPER_SUFFIX = "</cartethyia:system-prompt>";
54
+
55
+ /** Maximum number of per-provider entries stored for one tenant. */
56
+ export const SYSTEM_PROMPT_PROVIDER_LIMIT = 256;
57
+
58
+ /** Maximum number of per-model entries stored for one tenant. */
59
+ export const SYSTEM_PROMPT_MODEL_LIMIT = 512;
60
+
61
+ /**
62
+ * Providers whose upstream validates the fixed system prompt as a channel
63
+ * identity. Their system turn must keep the fixed prompt as the opener, so
64
+ * tenant text is appended after it inside the same turn, wrapped in the
65
+ * gateway's prefix/suffix markers (see `SYSTEM_PROMPT_WRAPPER_PREFIX`).
66
+ * `override` mode is not honoured for these providers — it would strip the
67
+ * identity and the upstream would refuse the request.
68
+ */
69
+ export const WRAPPED_INJECTION_PROVIDERS: ReadonlySet<string> = new Set([
70
+ "cb", // CodeBuddy International
71
+ "cbcn", // CodeBuddy CN
72
+ "workbuddy",
73
+ ]);
74
+
75
+ /** Whether a provider's tenant text rides wrapped after the fixed identity prompt. */
76
+ export function isWrappedInjectionProvider(providerId: string): boolean {
77
+ return WRAPPED_INJECTION_PROVIDERS.has(providerId);
78
+ }
79
+
80
+ /** Model-scope key for the `models` map. */
81
+ export function systemPromptModelKey(providerId: string, modelId: string): string {
82
+ return `${providerId}::${modelId}`;
83
+ }
84
+
85
+ /** Parses a model-scope key; null when the key is malformed. */
86
+ export function parseSystemPromptModelKey(key: string): { providerId: string; modelId: string } | null {
87
+ const index = key.indexOf("::");
88
+ if (index <= 0 || index === key.length - 2) return null;
89
+ return { providerId: key.slice(0, index), modelId: key.slice(index + 2) };
90
+ }
91
+
92
+ /** True when the value is a well-formed prompt setting (shape only, not size). */
93
+ export function isPromptSetting(value: unknown): value is PromptSetting {
94
+ if (typeof value !== "object" || value === null) return false;
95
+ const candidate = value as Partial<PromptSetting>;
96
+ return (
97
+ typeof candidate.enabled === "boolean" &&
98
+ typeof candidate.mode === "string" &&
99
+ (PROMPT_MODES as readonly string[]).includes(candidate.mode) &&
100
+ typeof candidate.text === "string"
101
+ );
102
+ }
@@ -0,0 +1,165 @@
1
+ // Console API for tenant system-prompt injection.
2
+ //
3
+ // GET /system-prompt → the stored configuration
4
+ // PUT /system-prompt → replace the configuration wholesale
5
+ // DELETE /system-prompt → clear it entirely
6
+ //
7
+ // The PUT replaces the row: the modal edits the whole three-scope shape at
8
+ // once, so a PATCH surface would add states the UI never produces. Text is
9
+ // capped at 5 MB per scope and the provider/model maps are bounded, matching
10
+ // the store's own defensive limits.
11
+ import { Elysia, t } from "elysia";
12
+ import type { ConsoleAccessResolver } from "../auth/access";
13
+ import { ConsoleDomainError, consoleErrorHandler, requireTenantScope } from "../shared/errors";
14
+ import type { AuditSink } from "../audit/contracts";
15
+ import type { CartethyiaDatabase } from "../../persistence/db-handle";
16
+ import {
17
+ clearTenantSystemPrompts,
18
+ readTenantSystemPrompts,
19
+ writeTenantSystemPrompts,
20
+ } from "../../persistence/system-prompt-store";
21
+ import {
22
+ EMPTY_TENANT_SYSTEM_PROMPTS,
23
+ PROMPT_MODES,
24
+ SYSTEM_PROMPT_MODEL_LIMIT,
25
+ SYSTEM_PROMPT_PROVIDER_LIMIT,
26
+ SYSTEM_PROMPT_TEXT_CAP_BYTES,
27
+ isPromptSetting,
28
+ parseSystemPromptModelKey,
29
+ type PromptSetting,
30
+ type ProviderPromptEntry,
31
+ type TenantSystemPrompts,
32
+ } from "./contracts";
33
+
34
+ /** Route-group context injected by the composition root. */
35
+ export interface SystemPromptRoutesConfig {
36
+ readonly db: CartethyiaDatabase;
37
+ readonly accessResolver: ConsoleAccessResolver;
38
+ readonly auditSink?: AuditSink;
39
+ }
40
+
41
+ /** Validates one scope setting, including the 5 MB text cap. */
42
+ function validateSetting(value: unknown, scopeLabel: string): PromptSetting {
43
+ if (!isPromptSetting(value)) {
44
+ throw new ConsoleDomainError("invalid_request", 400, `${scopeLabel} is not a valid prompt setting`);
45
+ }
46
+ const bytes = Buffer.byteLength(value.text, "utf8");
47
+ if (bytes > SYSTEM_PROMPT_TEXT_CAP_BYTES) {
48
+ throw new ConsoleDomainError("payload_too_large", 413, `${scopeLabel} exceeds the 5 MB limit`);
49
+ }
50
+ if (!(PROMPT_MODES as readonly string[]).includes(value.mode)) {
51
+ throw new ConsoleDomainError("invalid_request", 400, `${scopeLabel} has an invalid mode`);
52
+ }
53
+ return value;
54
+ }
55
+
56
+ /** Validates the full PUT body into the stored shape. */
57
+ function validateBody(value: unknown): TenantSystemPrompts {
58
+ if (typeof value !== "object" || value === null) {
59
+ throw new ConsoleDomainError("invalid_request", 400, "Body must be an object");
60
+ }
61
+ const body = value as {
62
+ global?: unknown;
63
+ providers?: unknown;
64
+ models?: unknown;
65
+ };
66
+
67
+ const global =
68
+ body.global === null || body.global === undefined
69
+ ? null
70
+ : validateSetting(body.global, "global");
71
+
72
+ if (body.providers !== undefined && typeof body.providers !== "object") {
73
+ throw new ConsoleDomainError("invalid_request", 400, "providers must be an object");
74
+ }
75
+ const rawProviders = (body.providers ?? {}) as Record<string, unknown>;
76
+ const providerIds = Object.keys(rawProviders);
77
+ if (providerIds.length > SYSTEM_PROMPT_PROVIDER_LIMIT) {
78
+ throw new ConsoleDomainError(
79
+ "invalid_request",
80
+ 400,
81
+ `providers exceeds the ${SYSTEM_PROMPT_PROVIDER_LIMIT} entry limit`,
82
+ );
83
+ }
84
+ const providers: Record<string, ProviderPromptEntry> = {};
85
+ for (const providerId of providerIds) {
86
+ const entry = rawProviders[providerId];
87
+ if (typeof entry !== "object" || entry === null) {
88
+ throw new ConsoleDomainError("invalid_request", 400, `provider ${providerId} entry must be an object`);
89
+ }
90
+ const candidate = entry as Partial<ProviderPromptEntry>;
91
+ if (typeof candidate.followGlobal !== "boolean") {
92
+ throw new ConsoleDomainError("invalid_request", 400, `provider ${providerId} needs followGlobal`);
93
+ }
94
+ if (candidate.followGlobal) {
95
+ providers[providerId] = { followGlobal: true };
96
+ } else {
97
+ providers[providerId] = {
98
+ followGlobal: false,
99
+ setting: validateSetting(candidate.setting, `provider ${providerId}`),
100
+ };
101
+ }
102
+ }
103
+
104
+ if (body.models !== undefined && typeof body.models !== "object") {
105
+ throw new ConsoleDomainError("invalid_request", 400, "models must be an object");
106
+ }
107
+ const rawModels = (body.models ?? {}) as Record<string, unknown>;
108
+ const modelKeys = Object.keys(rawModels);
109
+ if (modelKeys.length > SYSTEM_PROMPT_MODEL_LIMIT) {
110
+ throw new ConsoleDomainError(
111
+ "invalid_request",
112
+ 400,
113
+ `models exceeds the ${SYSTEM_PROMPT_MODEL_LIMIT} entry limit`,
114
+ );
115
+ }
116
+ const models: Record<string, PromptSetting> = {};
117
+ for (const key of modelKeys) {
118
+ if (parseSystemPromptModelKey(key) === null) {
119
+ throw new ConsoleDomainError("invalid_request", 400, `model key ${key} is malformed`);
120
+ }
121
+ models[key] = validateSetting(rawModels[key], `model ${key}`);
122
+ }
123
+
124
+ return { global, providers, models };
125
+ }
126
+
127
+ /**
128
+ * The return type is inferred rather than annotated as `Elysia`: `.error()`
129
+ * widens the instance type with its handler's response envelope, so the
130
+ * annotated base type no longer matches the chained result (same reason the
131
+ * remote-support routes leave it inferred).
132
+ */
133
+ export function createSystemPromptRoutes(config: SystemPromptRoutesConfig) {
134
+ return new Elysia()
135
+ .error(consoleErrorHandler("System prompt settings failed"))
136
+ .get("/system-prompt", async ({ request }) => {
137
+ const access = requireTenantScope(config.accessResolver(request), "dashboard:read");
138
+ return await readTenantSystemPrompts(config.db, access.tenantId);
139
+ })
140
+ .put(
141
+ "/system-prompt",
142
+ { body: t.Any() },
143
+ async ({ request, body }) => {
144
+ const access = requireTenantScope(config.accessResolver(request), "dashboard:write");
145
+ const next = validateBody(body);
146
+ await writeTenantSystemPrompts(config.db, access.tenantId, next);
147
+ await config.auditSink?.record({
148
+ access,
149
+ action: "system_prompt.update",
150
+ target: "tenant_system_prompts",
151
+ detail: {
152
+ global: next.global !== null,
153
+ providers: Object.keys(next.providers).length,
154
+ models: Object.keys(next.models).length,
155
+ },
156
+ });
157
+ return next;
158
+ },
159
+ )
160
+ .delete("/system-prompt", async ({ request }) => {
161
+ const access = requireTenantScope(config.accessResolver(request), "dashboard:write");
162
+ await clearTenantSystemPrompts(config.db, access.tenantId);
163
+ return EMPTY_TENANT_SYSTEM_PROMPTS;
164
+ });
165
+ }
package/src/main.ts CHANGED
@@ -169,6 +169,17 @@ if (boot) {
169
169
  globalThis.__cartethyiaSignalsRegistered = true;
170
170
  process.on("SIGINT", () => shutdown("SIGINT"));
171
171
  process.on("SIGTERM", () => shutdown("SIGTERM"));
172
+ // A rejected promise nothing awaited (a sweep, a cache warm, a read whose
173
+ // parameters a bug elsewhere ballooned) must not kill the listener: the
174
+ // default is fatal exit, which turned one query fault into a systemd
175
+ // restart loop. Logged and absorbed — the failure stays visible, the
176
+ // process stays up.
177
+ process.on("unhandledRejection", (reason) => {
178
+ log.error(
179
+ "[process] unhandled rejection (kept alive)",
180
+ reason instanceof Error ? reason : new Error(String(reason)),
181
+ );
182
+ });
172
183
  // An in-place update signals SIGUSR2 before swapping the image so the old
173
184
  // process drains with the `update` reason and its callers are told the
174
185
  // replacement is seconds away instead of seeing a generic shutdown. Not