cartethyia 2.7.8 → 2.7.9

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 (371) hide show
  1. package/.env.example +0 -1
  2. package/README.md +29 -59
  3. package/dist/dashboard/assets/BannedPage-BxoG1MrL.js +1 -0
  4. package/dist/dashboard/assets/BannedPage-BxoG1MrL.js.gz +0 -0
  5. package/dist/dashboard/assets/{CliToolDetailPage-BRWKPQPf.js → CliToolDetailPage-ixwgKk53.js} +1 -1
  6. package/dist/dashboard/assets/CliToolDetailPage-ixwgKk53.js.gz +0 -0
  7. package/dist/dashboard/assets/{CliToolsPage-BP-Y5iHN.js → CliToolsPage-BmttNV3U.js} +1 -1
  8. package/dist/dashboard/assets/CliToolsPage-BmttNV3U.js.gz +0 -0
  9. package/dist/dashboard/assets/CombosPage-BE3qwwfC.js +1 -0
  10. package/dist/dashboard/assets/CombosPage-BE3qwwfC.js.gz +0 -0
  11. package/dist/dashboard/assets/ConsoleLogPage-CRZdmMIX.js +1 -0
  12. package/dist/dashboard/assets/ConsoleLogPage-CRZdmMIX.js.gz +0 -0
  13. package/dist/dashboard/assets/CustomizationPage-Dv_FJ6Er.js +1 -0
  14. package/dist/dashboard/assets/CustomizationPage-Dv_FJ6Er.js.gz +0 -0
  15. package/dist/dashboard/assets/HealthEventsModal-CoVrw2sM.js +1 -0
  16. package/dist/dashboard/assets/HealthEventsModal-CoVrw2sM.js.gz +0 -0
  17. package/dist/dashboard/assets/{InjectSystemPromptPanel-Cv-2Ol_K.js → InjectSystemPromptPanel-BOoO8rpz.js} +2 -2
  18. package/dist/dashboard/assets/InjectSystemPromptPanel-BOoO8rpz.js.gz +0 -0
  19. package/dist/dashboard/assets/{LoginPage-bKfz_b1K.js → LoginPage-ChJuNNVN.js} +1 -1
  20. package/dist/dashboard/assets/LoginPage-ChJuNNVN.js.gz +0 -0
  21. package/dist/dashboard/assets/{ModelPicker-B-WQxx_H.js → ModelPicker-BZAJtPg1.js} +1 -1
  22. package/dist/dashboard/assets/ModelPicker-BZAJtPg1.js.gz +0 -0
  23. package/dist/dashboard/assets/OverviewPage-BE40s3u5.js +1 -0
  24. package/dist/dashboard/assets/OverviewPage-BE40s3u5.js.gz +0 -0
  25. package/dist/dashboard/assets/ProviderDetailPage-sfLoQtPV.js +8 -0
  26. package/dist/dashboard/assets/ProviderDetailPage-sfLoQtPV.js.gz +0 -0
  27. package/dist/dashboard/assets/ProviderIcon-CQ6bPUf_.js +1 -0
  28. package/dist/dashboard/assets/ProviderIcon-CQ6bPUf_.js.gz +0 -0
  29. package/dist/dashboard/assets/ProvidersPage-DcepmPWo.js +1 -0
  30. package/dist/dashboard/assets/ProvidersPage-DcepmPWo.js.gz +0 -0
  31. package/dist/dashboard/assets/{ProxyPage-emlTuxz6.js → ProxyPage-BJ87Pp0U.js} +1 -1
  32. package/dist/dashboard/assets/ProxyPage-BJ87Pp0U.js.gz +0 -0
  33. package/dist/dashboard/assets/{QuotaPage-CMJKNYWb.js → QuotaPage-gkSyRAKa.js} +1 -1
  34. package/dist/dashboard/assets/QuotaPage-gkSyRAKa.js.gz +0 -0
  35. package/dist/dashboard/assets/SettingsPage-C5HPWqEv.js +1 -0
  36. package/dist/dashboard/assets/SettingsPage-C5HPWqEv.js.gz +0 -0
  37. package/dist/dashboard/assets/{SetupPage-CUZrKs9r.js → SetupPage-CFNcATJJ.js} +1 -1
  38. package/dist/dashboard/assets/SetupPage-CFNcATJJ.js.gz +0 -0
  39. package/dist/dashboard/assets/{StudioPage-jWmUdDpy.js → StudioPage-YoD4tY5l.js} +1 -1
  40. package/dist/dashboard/assets/StudioPage-YoD4tY5l.js.gz +0 -0
  41. package/dist/dashboard/assets/UsagePage-DxjaAhb_.js +56 -0
  42. package/dist/dashboard/assets/UsagePage-DxjaAhb_.js.gz +0 -0
  43. package/dist/dashboard/assets/api-keys-BTlkq_Uk.js +1 -0
  44. package/dist/dashboard/assets/api-keys-BTlkq_Uk.js.gz +0 -0
  45. package/dist/dashboard/assets/app-BX2nnKqs.css +2 -0
  46. package/dist/dashboard/assets/app-BX2nnKqs.css.gz +0 -0
  47. package/dist/dashboard/assets/assert-CndthXFQ.js +1 -0
  48. package/dist/dashboard/assets/assert-CndthXFQ.js.gz +0 -0
  49. package/dist/dashboard/assets/{badge-DyI_mJfi.js → badge-DdbT_7Am.js} +1 -1
  50. package/dist/dashboard/assets/badge-DdbT_7Am.js.gz +0 -0
  51. package/dist/dashboard/assets/ban-durations-B58FyRxC.js +1 -0
  52. package/dist/dashboard/assets/ban-durations-B58FyRxC.js.gz +0 -0
  53. package/dist/dashboard/assets/{banner-B-yXtCqb.js → banner-CsGN-nbm.js} +1 -1
  54. package/dist/dashboard/assets/banner-CsGN-nbm.js.gz +0 -0
  55. package/dist/dashboard/assets/brand/bansos_page.webp +0 -0
  56. package/dist/dashboard/assets/brand/denia_bansos.webp +0 -0
  57. package/dist/dashboard/assets/brand/favicon.webp +0 -0
  58. package/dist/dashboard/assets/brand/login_register.webp +0 -0
  59. package/dist/dashboard/assets/brand/login_register_initial.webp +0 -0
  60. package/dist/dashboard/assets/brand/og_bansos.webp +0 -0
  61. package/dist/dashboard/assets/brand/og_image.webp +0 -0
  62. package/dist/dashboard/assets/brand/sidebar_ico.webp +0 -0
  63. package/dist/dashboard/assets/{card-_HwAEqFF.js → card-mgZ_TLLZ.js} +1 -1
  64. package/dist/dashboard/assets/card-mgZ_TLLZ.js.gz +0 -0
  65. package/dist/dashboard/assets/cli-tools-A1-v8-03.js +1 -0
  66. package/dist/dashboard/assets/cli-tools-A1-v8-03.js.gz +0 -0
  67. package/dist/dashboard/assets/{clipboard-button-ByjCk0o9.js → clipboard-button-CJ4Zty89.js} +1 -1
  68. package/dist/dashboard/assets/clipboard-button-CJ4Zty89.js.gz +0 -0
  69. package/dist/dashboard/assets/{dialog-BZhOlfz-.js → dialog-B5L2jPQ_.js} +1 -1
  70. package/dist/dashboard/assets/dialog-B5L2jPQ_.js.gz +0 -0
  71. package/dist/dashboard/assets/{drawer-BADqIRYP.js → drawer-DkjUouAj.js} +1 -1
  72. package/dist/dashboard/assets/drawer-DkjUouAj.js.gz +0 -0
  73. package/dist/dashboard/assets/{entry-COaC-irN.js → entry-B-LVLV6o.js} +1 -1
  74. package/dist/dashboard/assets/entry-B-LVLV6o.js.gz +0 -0
  75. package/dist/dashboard/assets/entry-CAhdYl3B.js +1 -0
  76. package/dist/dashboard/assets/entry-CAhdYl3B.js.gz +0 -0
  77. package/dist/dashboard/assets/entry-D2xD0Tmd.js +4 -0
  78. package/dist/dashboard/assets/entry-D2xD0Tmd.js.gz +0 -0
  79. package/dist/dashboard/assets/{formatters-DcHJyRiu.js → formatters-CrsdLVsn.js} +1 -1
  80. package/dist/dashboard/assets/formatters-CrsdLVsn.js.gz +0 -0
  81. package/dist/dashboard/assets/{index-CgKmFngv.js → index-BU64Lqvp.js} +2 -2
  82. package/dist/dashboard/assets/index-BU64Lqvp.js.gz +0 -0
  83. package/dist/dashboard/assets/{inline-B2BirKry.js → inline-B2aDLyyg.js} +1 -1
  84. package/dist/dashboard/assets/inline-B2aDLyyg.js.gz +0 -0
  85. package/dist/dashboard/assets/{input-D-qV9dKY.js → input-BCVkdR9d.js} +1 -1
  86. package/dist/dashboard/assets/input-BCVkdR9d.js.gz +0 -0
  87. package/dist/dashboard/assets/{layout-B7tVWhtV.js → layout-DNcAdMK-.js} +1 -1
  88. package/dist/dashboard/assets/layout-DNcAdMK-.js.gz +0 -0
  89. package/dist/dashboard/assets/{list-row-CtkGE3bF.js → list-row-DgrIH0hw.js} +1 -1
  90. package/dist/dashboard/assets/list-row-DgrIH0hw.js.gz +0 -0
  91. package/dist/dashboard/assets/{live-DGizF06-.js → live-Cae01iu8.js} +1 -1
  92. package/dist/dashboard/assets/live-Cae01iu8.js.gz +0 -0
  93. package/dist/dashboard/assets/{mutation-GU7XdEjz.js → mutation-BQ0ccH_A.js} +1 -1
  94. package/dist/dashboard/assets/mutation-BQ0ccH_A.js.gz +0 -0
  95. package/dist/dashboard/assets/{network-C9UE-bOr.js → network-CRib7H_2.js} +1 -1
  96. package/dist/dashboard/assets/network-CRib7H_2.js.gz +0 -0
  97. package/dist/dashboard/assets/{numbers-BnAd22a_.js → numbers-BKQE8Xpz.js} +1 -1
  98. package/dist/dashboard/assets/numbers-BKQE8Xpz.js.gz +0 -0
  99. package/dist/dashboard/assets/{pagination-BAywyMzS.js → pagination-ecPvG_8g.js} +1 -1
  100. package/dist/dashboard/assets/pagination-ecPvG_8g.js.gz +0 -0
  101. package/dist/dashboard/assets/providers/devin.svg +9 -0
  102. package/dist/dashboard/assets/providers/devin.svg.gz +0 -0
  103. package/dist/dashboard/assets/providers/windsurf.svg +10 -0
  104. package/dist/dashboard/assets/providers/windsurf.svg.gz +0 -0
  105. package/dist/dashboard/assets/{query-keys-YSoQOUh6.js → query-keys-IKLh0YFd.js} +1 -1
  106. package/dist/dashboard/assets/query-keys-IKLh0YFd.js.gz +0 -0
  107. package/dist/dashboard/assets/routing-Ckm1UXuC.js +1 -0
  108. package/dist/dashboard/assets/routing-Ckm1UXuC.js.gz +0 -0
  109. package/dist/dashboard/assets/{select-BCOcv8WK.js → select-ChoFWyKz.js} +1 -1
  110. package/dist/dashboard/assets/select-ChoFWyKz.js.gz +0 -0
  111. package/dist/dashboard/assets/settings-CKjFPlKW.js +1 -0
  112. package/dist/dashboard/assets/settings-CKjFPlKW.js.gz +0 -0
  113. package/dist/dashboard/assets/{stack-ByKt3Q1g.js → stack-DWCwU3fh.js} +1 -1
  114. package/dist/dashboard/assets/stack-DWCwU3fh.js.gz +0 -0
  115. package/dist/dashboard/assets/{state-DXbGTx7N.js → state-Cg3VL5J_.js} +1 -1
  116. package/dist/dashboard/assets/state-Cg3VL5J_.js.gz +0 -0
  117. package/dist/dashboard/assets/story/25817331.webp +0 -0
  118. package/dist/dashboard/assets/story/Shorekeeper.webp +0 -0
  119. package/dist/dashboard/assets/story/cartethyia-god.webp +0 -0
  120. package/dist/dashboard/assets/story/fleurdelys_plus.webp +0 -0
  121. package/dist/dashboard/assets/story/jinhsi-blossom.webp +0 -0
  122. package/dist/dashboard/assets/story/phrolova-a.webp +0 -0
  123. package/dist/dashboard/assets/story/requestdeniawokkjpg.webp +0 -0
  124. package/dist/dashboard/assets/story/wuthering-waves-hiyuki-aemeath-hiyuki-rover.webp +0 -0
  125. package/dist/dashboard/assets/{switch-ZeTsW623.js → switch-Db70BMix.js} +1 -1
  126. package/dist/dashboard/assets/switch-Db70BMix.js.gz +0 -0
  127. package/dist/dashboard/assets/{useMutation-CbCP4w1o.js → useMutation-BqdE0LuM.js} +1 -1
  128. package/dist/dashboard/assets/useMutation-BqdE0LuM.js.gz +0 -0
  129. package/dist/dashboard/console-backgrounds.webp +0 -0
  130. package/dist/dashboard/console-backgrounds_initial.webp +0 -0
  131. package/dist/dashboard/donation/qris-risun-marke.webp +0 -0
  132. package/dist/dashboard/index.html +3 -3
  133. package/dist/dashboard/index.html.gz +0 -0
  134. package/dist/dashboard/precompressed-assets.json +57 -55
  135. package/migrations/full-postgres/0044_add_models_warning_metadata.sql +10 -0
  136. package/migrations/full-postgres/0045_migrate_workbuddy_to_codebuddy.sql +156 -0
  137. package/migrations/lite-sqlite3/0044_add_models_warning_metadata.sql +11 -0
  138. package/migrations/lite-sqlite3/0045_migrate_workbuddy_to_codebuddy.sql +156 -0
  139. package/package.json +1 -1
  140. package/src/boot/dependencies.ts +11 -3
  141. package/src/config/cooldowns.ts +1 -1
  142. package/src/config/spec.ts +0 -6
  143. package/src/console/api-keys/routes.ts +11 -4
  144. package/src/console/console-router.ts +7 -0
  145. package/src/console/dashboard-assets.ts +5 -5
  146. package/src/console/domain-registration.ts +27 -0
  147. package/src/console/ip-bans/routes.ts +99 -0
  148. package/src/console/providers/catalog/catalog-projections.ts +5 -2
  149. package/src/console/providers/catalog/contracts.ts +123 -0
  150. package/src/console/providers/catalog/model-operations.ts +122 -10
  151. package/src/console/providers/catalog/provider-operations.ts +116 -8
  152. package/src/console/providers/catalog/public-model-store.ts +0 -0
  153. package/src/console/providers/catalog/routes.ts +64 -4
  154. package/src/console/providers/catalog/store-helpers.ts +30 -1
  155. package/src/console/providers/catalog/store.ts +243 -3
  156. package/src/console/quota/account-quota-global.ts +89 -1
  157. package/src/console/quota/native-identity.ts +2 -0
  158. package/src/console/settings/contracts.ts +18 -0
  159. package/src/console/settings/store.ts +32 -0
  160. package/src/console/system-prompt/contracts.ts +0 -1
  161. package/src/main.ts +1 -0
  162. package/src/observability/event-query.ts +18 -4
  163. package/src/persistence/dialect.ts +12 -0
  164. package/src/persistence/schema.ts +20 -2
  165. package/src/protocol/dsml.ts +1 -1
  166. package/src/protocol/gateway-error.ts +1 -0
  167. package/src/protocol/reasoning.ts +1 -1
  168. package/src/protocol/request/chat.ts +4 -4
  169. package/src/protocol/request/messages.ts +1 -1
  170. package/src/protocol/response/gemini.ts +6 -1
  171. package/src/protocol/sse-decoder.ts +25 -8
  172. package/src/protocol/stream-error-frames.ts +13 -3
  173. package/src/protocol/translation/quirks.ts +1 -1
  174. package/src/protocol/translation/tool-repair.ts +2 -2
  175. package/src/protocol/upstream/send-claude-messages.ts +15 -4
  176. package/src/protocol/upstream-parsing.ts +1 -1
  177. package/src/providers/adapters/compatible-adapter.ts +7 -5
  178. package/src/providers/authentication/oauth-refresh-service.ts +149 -36
  179. package/src/providers/default-registry.ts +68 -3
  180. package/src/providers/discovery/discovery-types.ts +12 -0
  181. package/src/providers/discovery/probe-phases.ts +61 -30
  182. package/src/providers/discovery/probing-service.ts +279 -164
  183. package/src/providers/integrations/antigravity/antigravity-protocol.ts +117 -2
  184. package/src/providers/integrations/antigravity/antigravity.ts +13 -2
  185. package/src/providers/integrations/buddy/buddy-catalog-shared.ts +72 -34
  186. package/src/providers/integrations/buddy/buddy-chat-shared.ts +18 -18
  187. package/src/providers/integrations/buddy/buddy-checkin.ts +17 -39
  188. package/src/providers/integrations/buddy/buddy-conversation-headers.ts +2 -2
  189. package/src/providers/integrations/buddy/buddy-discovery-shared.ts +5 -5
  190. package/src/providers/integrations/buddy/buddy-identity.ts +70 -0
  191. package/src/providers/integrations/buddy/buddy-media.ts +4 -5
  192. package/src/providers/integrations/buddy/buddy-oauth-shared.ts +2 -2
  193. package/src/providers/integrations/buddy/buddy-quota-shared.ts +1 -1
  194. package/src/providers/integrations/buddy/buddy-usage-shared.ts +1 -3
  195. package/src/providers/integrations/buddy/capabilities.ts +4 -41
  196. package/src/providers/integrations/buddy/codebuddy/cn-catalog.ts +2 -1
  197. package/src/providers/integrations/buddy/codebuddy/cn-provider.ts +20 -1
  198. package/src/providers/integrations/buddy/codebuddy/provider.ts +2 -2
  199. package/src/providers/integrations/buddy/codebuddy/shared.ts +6 -10
  200. package/src/providers/integrations/cursor/cursor-catalog.ts +2 -0
  201. package/src/providers/integrations/cursor/cursor-exec.ts +738 -0
  202. package/src/providers/integrations/cursor/cursor-http2.ts +2 -0
  203. package/src/providers/integrations/cursor/cursor-request.ts +9 -8
  204. package/src/providers/integrations/cursor/cursor-shared.ts +19 -1
  205. package/src/providers/integrations/cursor/cursor.ts +50 -30
  206. package/src/providers/integrations/cursor/protocol/tool-canonical.ts +26 -100
  207. package/src/providers/integrations/devin/devin-auth.ts +353 -0
  208. package/src/providers/integrations/devin/devin-catalog.ts +207 -0
  209. package/src/providers/integrations/devin/devin-http.ts +91 -0
  210. package/src/providers/integrations/devin/devin-quota.ts +214 -0
  211. package/src/providers/integrations/devin/devin-shared.ts +111 -0
  212. package/src/providers/integrations/devin/devin.ts +539 -0
  213. package/src/providers/integrations/devin/protocol/devin-proto.ts +584 -0
  214. package/src/providers/integrations/devin/protocol/framing.ts +124 -0
  215. package/src/providers/integrations/factory/factory.ts +2 -2
  216. package/src/providers/integrations/freepi/freepi-catalog.ts +60 -0
  217. package/src/providers/integrations/freepi/freepi-discovery.ts +82 -0
  218. package/src/providers/integrations/freepi/freepi-headers.ts +21 -0
  219. package/src/providers/integrations/freepi/freepi-oauth.ts +142 -0
  220. package/src/providers/integrations/freepi/freepi-protocol.ts +242 -0
  221. package/src/providers/integrations/freepi/freepi-quota.ts +126 -0
  222. package/src/providers/integrations/freepi/freepi-realm.ts +35 -0
  223. package/src/providers/integrations/freepi/freepi.ts +292 -0
  224. package/src/providers/integrations/gemini.ts +2 -2
  225. package/src/providers/integrations/hermes/hermes-oauth.ts +94 -0
  226. package/src/providers/integrations/hermes.ts +4 -12
  227. package/src/providers/integrations/opencode/fingerprint.ts +1 -1
  228. package/src/providers/integrations/perch/perch-catalog.ts +105 -0
  229. package/src/providers/integrations/perch/perch-oauth.ts +170 -0
  230. package/src/providers/integrations/perch/perch-protocol.ts +211 -0
  231. package/src/providers/integrations/perch/perch-quota.ts +137 -0
  232. package/src/providers/integrations/perch/perch-realm.ts +82 -0
  233. package/src/providers/integrations/perch/perch-system-prompt.ts +13 -0
  234. package/src/providers/integrations/perch/perch.ts +315 -0
  235. package/src/providers/integrations/perch/saffron-system-prompt.txt +8 -0
  236. package/src/providers/integrations/qoder/chat-proto.ts +328 -328
  237. package/src/providers/integrations/qoder/claim-qoder-credits.ts +1 -1
  238. package/src/providers/integrations/qoder/qoder-adapter.ts +89 -28
  239. package/src/providers/integrations/qoder/qoder-cache.ts +32 -0
  240. package/src/providers/integrations/qoder/qoder-catalog.ts +6 -1
  241. package/src/providers/integrations/qoder/qoder-checkin.ts +2 -2
  242. package/src/providers/integrations/qoder/qoder-grpc.ts +210 -6
  243. package/src/providers/integrations/qoder/qoder-ide.ts +5 -1
  244. package/src/providers/integrations/qoder/qoder-identity-cache.ts +184 -0
  245. package/src/providers/integrations/qoder/qoder-native-identity-store.ts +83 -18
  246. package/src/providers/integrations/qoder/qoder-realm.ts +9 -6
  247. package/src/providers/integrations/qoder/qoder-umid.ts +109 -342
  248. package/src/providers/integrations/qoder/qoder-virtual.ts +33 -1
  249. package/src/providers/integrations/qoder/qoder.ts +3 -6
  250. package/src/providers/integrations/trae/trae-catalog.ts +9 -14
  251. package/src/providers/integrations/trae/trae-headers.ts +36 -11
  252. package/src/providers/integrations/trae/trae-oauth.ts +14 -17
  253. package/src/providers/integrations/trae/trae-protocol.ts +3 -6
  254. package/src/providers/integrations/trae/trae-quota.ts +103 -21
  255. package/src/providers/integrations/trae/trae-realm.ts +36 -15
  256. package/src/providers/integrations/trae/trae.ts +97 -7
  257. package/src/providers/operations/account-error-classification.ts +66 -1
  258. package/src/providers/operations/account-health-service.ts +192 -26
  259. package/src/providers/operations/account-refresh-status.ts +284 -0
  260. package/src/providers/operations/account-tier.ts +67 -0
  261. package/src/providers/operations/client-versions.ts +37 -99
  262. package/src/providers/operations/cooldown-rules.ts +61 -0
  263. package/src/providers/operations/provider-catalog-seeder.ts +4 -0
  264. package/src/providers/provider-contracts.ts +29 -0
  265. package/src/providers/provider-metadata.ts +59 -4
  266. package/src/providers/provider-model-definition.ts +36 -0
  267. package/src/providers/quota/refresh.ts +21 -0
  268. package/src/security/abuse.ts +71 -0
  269. package/src/transport/dispatch/proxy-request.ts +1 -0
  270. package/src/transport/dispatch/streaming/stream.ts +1 -0
  271. package/src/transport/dispatch/upstream.ts +4 -1
  272. package/src/transport/failure-policy.ts +113 -7
  273. package/src/transport/routing/router.ts +25 -1
  274. package/src/transport/surface/messages/adapter.ts +2 -1
  275. package/src/transport/surface/responses/encode.ts +12 -1
  276. package/src/workers/daily-checkin.ts +1 -1
  277. package/src/workers/quota-refresh-sweep.ts +1 -1
  278. package/src/workers/scheduler.ts +22 -4
  279. package/dist/dashboard/assets/BannedPage-DeIbIiBU.js +0 -1
  280. package/dist/dashboard/assets/BannedPage-DeIbIiBU.js.gz +0 -0
  281. package/dist/dashboard/assets/CliToolDetailPage-BRWKPQPf.js.gz +0 -0
  282. package/dist/dashboard/assets/CliToolsPage-BP-Y5iHN.js.gz +0 -0
  283. package/dist/dashboard/assets/CombosPage-B7JHozVa.js +0 -1
  284. package/dist/dashboard/assets/CombosPage-B7JHozVa.js.gz +0 -0
  285. package/dist/dashboard/assets/ConsoleLogPage-GX7iGGcu.js +0 -1
  286. package/dist/dashboard/assets/ConsoleLogPage-GX7iGGcu.js.gz +0 -0
  287. package/dist/dashboard/assets/CustomizationPage-HVonbGEa.js +0 -1
  288. package/dist/dashboard/assets/CustomizationPage-HVonbGEa.js.gz +0 -0
  289. package/dist/dashboard/assets/HealthEventsModal-xaut4OJ9.js +0 -1
  290. package/dist/dashboard/assets/HealthEventsModal-xaut4OJ9.js.gz +0 -0
  291. package/dist/dashboard/assets/InjectSystemPromptPanel-Cv-2Ol_K.js.gz +0 -0
  292. package/dist/dashboard/assets/LoginPage-bKfz_b1K.js.gz +0 -0
  293. package/dist/dashboard/assets/ModelPicker-B-WQxx_H.js.gz +0 -0
  294. package/dist/dashboard/assets/OverviewPage-Z9pJZFo8.js +0 -1
  295. package/dist/dashboard/assets/OverviewPage-Z9pJZFo8.js.gz +0 -0
  296. package/dist/dashboard/assets/ProviderDetailPage-CMeDBxR1.js +0 -7
  297. package/dist/dashboard/assets/ProviderDetailPage-CMeDBxR1.js.gz +0 -0
  298. package/dist/dashboard/assets/ProviderIcon-jjqXJDA3.js +0 -1
  299. package/dist/dashboard/assets/ProviderIcon-jjqXJDA3.js.gz +0 -0
  300. package/dist/dashboard/assets/ProvidersPage-BXPdOpG4.js +0 -1
  301. package/dist/dashboard/assets/ProvidersPage-BXPdOpG4.js.gz +0 -0
  302. package/dist/dashboard/assets/ProxyPage-emlTuxz6.js.gz +0 -0
  303. package/dist/dashboard/assets/QuotaPage-CMJKNYWb.js.gz +0 -0
  304. package/dist/dashboard/assets/SettingsPage-BSy7EwGo.js +0 -1
  305. package/dist/dashboard/assets/SettingsPage-BSy7EwGo.js.gz +0 -0
  306. package/dist/dashboard/assets/SetupPage-CUZrKs9r.js.gz +0 -0
  307. package/dist/dashboard/assets/StudioPage-jWmUdDpy.js.gz +0 -0
  308. package/dist/dashboard/assets/UsagePage-5F9tGVGG.js +0 -56
  309. package/dist/dashboard/assets/UsagePage-5F9tGVGG.js.gz +0 -0
  310. package/dist/dashboard/assets/api-keys-KvX83mRe.js +0 -1
  311. package/dist/dashboard/assets/api-keys-KvX83mRe.js.gz +0 -0
  312. package/dist/dashboard/assets/app-BEsU8hKs.css +0 -2
  313. package/dist/dashboard/assets/app-BEsU8hKs.css.gz +0 -0
  314. package/dist/dashboard/assets/assert-CidQVoNo.js +0 -1
  315. package/dist/dashboard/assets/assert-CidQVoNo.js.gz +0 -0
  316. package/dist/dashboard/assets/badge-DyI_mJfi.js.gz +0 -0
  317. package/dist/dashboard/assets/banner-B-yXtCqb.js.gz +0 -0
  318. package/dist/dashboard/assets/brand/bansos_page.png +0 -0
  319. package/dist/dashboard/assets/brand/denia_bansos.jpg +0 -0
  320. package/dist/dashboard/assets/brand/favicon.jpg +0 -0
  321. package/dist/dashboard/assets/brand/login_register.jpg +0 -0
  322. package/dist/dashboard/assets/brand/login_register_initial.jpg +0 -0
  323. package/dist/dashboard/assets/brand/og_bansos.jpg +0 -0
  324. package/dist/dashboard/assets/brand/og_image.jpg +0 -0
  325. package/dist/dashboard/assets/brand/sidebar_ico.jpg +0 -0
  326. package/dist/dashboard/assets/card-_HwAEqFF.js.gz +0 -0
  327. package/dist/dashboard/assets/cli-tools-Bwv5-zGZ.js +0 -1
  328. package/dist/dashboard/assets/cli-tools-Bwv5-zGZ.js.gz +0 -0
  329. package/dist/dashboard/assets/clipboard-button-ByjCk0o9.js.gz +0 -0
  330. package/dist/dashboard/assets/dialog-BZhOlfz-.js.gz +0 -0
  331. package/dist/dashboard/assets/drawer-BADqIRYP.js.gz +0 -0
  332. package/dist/dashboard/assets/entry-COaC-irN.js.gz +0 -0
  333. package/dist/dashboard/assets/entry-Ca7LSCet.js +0 -4
  334. package/dist/dashboard/assets/entry-Ca7LSCet.js.gz +0 -0
  335. package/dist/dashboard/assets/entry-PDd27uCd.js +0 -1
  336. package/dist/dashboard/assets/entry-PDd27uCd.js.gz +0 -0
  337. package/dist/dashboard/assets/formatters-DcHJyRiu.js.gz +0 -0
  338. package/dist/dashboard/assets/index-CgKmFngv.js.gz +0 -0
  339. package/dist/dashboard/assets/inline-B2BirKry.js.gz +0 -0
  340. package/dist/dashboard/assets/input-D-qV9dKY.js.gz +0 -0
  341. package/dist/dashboard/assets/layout-B7tVWhtV.js.gz +0 -0
  342. package/dist/dashboard/assets/list-row-CtkGE3bF.js.gz +0 -0
  343. package/dist/dashboard/assets/live-DGizF06-.js.gz +0 -0
  344. package/dist/dashboard/assets/minimize-2-BTmMTK4o.js +0 -1
  345. package/dist/dashboard/assets/minimize-2-BTmMTK4o.js.gz +0 -0
  346. package/dist/dashboard/assets/mutation-GU7XdEjz.js.gz +0 -0
  347. package/dist/dashboard/assets/network-C9UE-bOr.js.gz +0 -0
  348. package/dist/dashboard/assets/numbers-BnAd22a_.js.gz +0 -0
  349. package/dist/dashboard/assets/pagination-BAywyMzS.js.gz +0 -0
  350. package/dist/dashboard/assets/query-keys-YSoQOUh6.js.gz +0 -0
  351. package/dist/dashboard/assets/routing-fnCsxgNO.js +0 -1
  352. package/dist/dashboard/assets/routing-fnCsxgNO.js.gz +0 -0
  353. package/dist/dashboard/assets/select-BCOcv8WK.js.gz +0 -0
  354. package/dist/dashboard/assets/settings-BxslsErn.js +0 -1
  355. package/dist/dashboard/assets/settings-BxslsErn.js.gz +0 -0
  356. package/dist/dashboard/assets/stack-ByKt3Q1g.js.gz +0 -0
  357. package/dist/dashboard/assets/state-DXbGTx7N.js.gz +0 -0
  358. package/dist/dashboard/assets/switch-ZeTsW623.js.gz +0 -0
  359. package/dist/dashboard/assets/useMutation-CbCP4w1o.js.gz +0 -0
  360. package/dist/dashboard/console-backgrounds.jpg +0 -0
  361. package/dist/dashboard/console-backgrounds_initial.jpg +0 -0
  362. package/dist/dashboard/donation/qris-risun-marke.png +0 -0
  363. package/src/providers/integrations/buddy/turing-device-token.ts +0 -262
  364. package/src/providers/integrations/buddy/workbuddy/cn-oauth.ts +0 -30
  365. package/src/providers/integrations/buddy/workbuddy/cn-provider.ts +0 -58
  366. package/src/providers/integrations/buddy/workbuddy/oauth.ts +0 -31
  367. package/src/providers/integrations/buddy/workbuddy/provider.ts +0 -209
  368. package/src/providers/integrations/buddy/workbuddy/quota.ts +0 -161
  369. package/src/providers/integrations/buddy/workbuddy/shared.ts +0 -285
  370. /package/dist/dashboard/assets/{app-CTxoqyYB.js → app-B7kIWFm5.js} +0 -0
  371. /package/dist/dashboard/assets/{app-CTxoqyYB.js.gz → app-B7kIWFm5.js.gz} +0 -0
@@ -10,6 +10,7 @@
10
10
  import { isRecord } from "../../protocol/codecs";
11
11
  import { GatewayError } from "../../protocol/gateway-error";
12
12
  import { BUDDY_PROVIDER_IDS } from "../provider-metadata";
13
+ import type { AccountTier } from "./account-tier";
13
14
  import { parseProviderResetDuration, parseUpstreamBackoff } from "../../protocol/upstream-parsing";
14
15
  import {
15
16
  resolveAccountAuthRejectedCooldownMs,
@@ -23,6 +24,12 @@ import {
23
24
  resolveAccountUnclassifiedCooldownMs,
24
25
  } from "../../config/cooldowns";
25
26
  const DEFAULT_RATE_LIMIT_COOLDOWN_MS = resolveAccountRateLimitCooldownMs;
27
+ /**
28
+ * Perch's `turn_rate_limited` window. The upstream does not state a reset and
29
+ * answers the same on every ticket mint while the window is exhausted, so a
30
+ * day matches the practical cadence and stops the redial loop.
31
+ */
32
+ const PERCH_TURN_RATE_LIMIT_COOLDOWN_MS = 24 * 60 * 60 * 1000;
26
33
  /**
27
34
  * Inception's per-minute limit window (1 minute).
28
35
  *
@@ -83,7 +90,7 @@ const QODER_QUOTA_COOLDOWN_MS = 24 * 60 * 60 * 1000;
83
90
  * to be two constants, 24h for the content block and 6h for the channel one,
84
91
  * both applied on the first observation. That was wrong in the direction that
85
92
  * costs most: `11140` is also what a wrong desktop `User-Agent` brand answers
86
- * (`workbuddy/shared.ts`), so after we fixed a header regression every buddy
93
+ * (`codebuddy/shared.ts`), so after we fixed a header regression every buddy
87
94
  * account stayed dead for up to a further day, and one prompt that failed a
88
95
  * safety review cost a whole account a day. As a base it escalates through the
89
96
  * consecutive-failure ladder, which tells the two causes apart by what they do
@@ -259,6 +266,12 @@ export interface AccountFailureEvidence {
259
266
  * limit the provider said resets in 23 days.
260
267
  */
261
268
  readonly upstreamBody?: unknown;
269
+ /**
270
+ * The account's tier, derived from its plan (`mapPlanToTier`). Read only by
271
+ * the writer when it resolves tier-aware cooldown rules; the classifier
272
+ * itself does not branch on it.
273
+ */
274
+ readonly accountTier?: AccountTier;
262
275
  }
263
276
 
264
277
  export interface AccountErrorClassification {
@@ -299,6 +312,14 @@ export interface AccountErrorClassification {
299
312
  * apart — it needs to know whether to count the hit and escalate the window.
300
313
  */
301
314
  readonly modelUnsupported?: boolean;
315
+ /**
316
+ * Where the cooldown duration came from (D18). A duration resolved from a
317
+ * tier/provider rule (`provider-rule`) is a policy decision and must NOT go
318
+ * through `escalateFallbackCooldown` — the 1h escalation cap would cut the
319
+ * 24h Trae rule down to an hour on the first failure. Only a pure fallback
320
+ * (`fallback`) escalates.
321
+ */
322
+ readonly ruleSource?: "provider-rule" | "fallback";
302
323
  }
303
324
 
304
325
  /**
@@ -344,6 +365,26 @@ export function escalateFallbackCooldown(
344
365
  return Math.min(cooldownMs * 2 ** level, resolveAccountCooldownEscalationMaxMs());
345
366
  }
346
367
 
368
+ /**
369
+ * Returns a cooldown duration with the D18 escalation split applied.
370
+ *
371
+ * A duration that came from a tier/provider rule (`ruleSource === "provider-rule"`)
372
+ * is a policy decision and must never pass through
373
+ * {@link escalateFallbackCooldown}: the 1h cap would truncate the 24h Trae
374
+ * free-tier rule to one hour on the first failure. Only a pure fallback
375
+ * (`ruleSource === "fallback"`, or unset for callers that predate the split)
376
+ * escalates.
377
+ */
378
+ export function applyRuleEscalation(
379
+ cooldownMs: number,
380
+ providerStated: boolean,
381
+ consecutiveFailures: number,
382
+ ruleSource: "provider-rule" | "fallback" | undefined,
383
+ ): number {
384
+ if (ruleSource === "provider-rule") return cooldownMs;
385
+ return escalateFallbackCooldown(cooldownMs, providerStated, consecutiveFailures);
386
+ }
387
+
347
388
  /**
348
389
  * The later of a candidate deadline and the one already recorded.
349
390
  *
@@ -1594,6 +1635,30 @@ const ACCOUNT_ERROR_RULES: readonly AccountErrorRule[] = [
1594
1635
  );
1595
1636
  },
1596
1637
  },
1638
+ {
1639
+ // Perch's `turn_rate_limited` is a per-turn API window, not a per-request
1640
+ // 429: the account keeps answering but every call mints another ticket
1641
+ // into the same exhausted window. The stated remedy ("contact us for
1642
+ // direct API access") implies the window is long — park the account for a
1643
+ // day so routing picks a sibling instead of hammering the same key.
1644
+ name: "perch-turn-rate-limit",
1645
+ match: (s) =>
1646
+ s.origin === "upstream" &&
1647
+ s.providerId === "perch" &&
1648
+ (s.providerCode === "turn_rate_limited" ||
1649
+ (s.statusCode === 429 && s.lower.includes("turn rate"))),
1650
+ decide: (s) => {
1651
+ const cooldownMs = s.statedCooldownMs ?? PERCH_TURN_RATE_LIMIT_COOLDOWN_MS;
1652
+ return s.result(
1653
+ "rate_limit_transient",
1654
+ "cooldown",
1655
+ cooldownMs,
1656
+ new Date(Date.now() + cooldownMs),
1657
+ s.reason("Perch turn rate limit"),
1658
+ s.origin === "upstream",
1659
+ );
1660
+ },
1661
+ },
1597
1662
  {
1598
1663
  name: "rate-limit",
1599
1664
  match: (s) =>
@@ -1,18 +1,66 @@
1
1
  import { randomUUID } from "node:crypto";
2
2
  import { healthEventBuffer } from "../../persistence/health-event-flush";
3
- import { and, count, desc, eq, inArray, isNotNull, lte, sql } from "drizzle-orm";
3
+ import { and, count, desc, eq, inArray, isNotNull, like, lte, or, sql } from "drizzle-orm";
4
4
  import { affectedRows, pruneExpiredCooldowns } from "../../persistence/dialect";
5
5
  import type { CartethyiaDatabase } from "../../persistence/db-handle";
6
- import { healthEvents, providerAccounts } from "../../persistence/schema";
6
+ import { consoleSettings, healthEvents, providerAccounts } from "../../persistence/schema";
7
7
  import {
8
8
  COOLDOWN_RECOVERY_BATCH,
9
9
  accountDeadlineMs,
10
+ applyRuleEscalation,
10
11
  classifyAccountError,
11
- escalateFallbackCooldown,
12
12
  latestDeadlineMs,
13
13
  unsupportedModelCooldownMs,
14
14
  } from "./account-error-classification";
15
+ import { resolveAccountDisabledRecoveryMs } from "../../config/cooldowns";
15
16
  import type { AccountErrorClassification, AccountFailureEvidence } from "./account-error-classification";
17
+ import { resolveCooldownRule } from "./cooldown-rules";
18
+ import { mapPlanToTier } from "./account-tier";
19
+
20
+ /**
21
+ * Categories the auto-disable toggle (area 01) targets (D01). Model-scoped
22
+ * verdicts (`model_unreachable`) are not here — they belong to the tier rules
23
+ * (area 03, R-C).
24
+ */
25
+ const AUTO_DISABLE_TARGET_CATEGORIES: ReadonlySet<string> = new Set([
26
+ "quota_exhausted",
27
+ "rate_limit_transient",
28
+ ]);
29
+
30
+ /** Prefix marking an auto-disable written by the rate-limited intercept (D03/R-B). */
31
+ export const AUTO_DISABLE_MARKER_PREFIX = "auto-disable (rate-limited): ";
32
+
33
+ /** An upstream-stated window shorter than this is not auto-disabled (D04). */
34
+ const AUTO_DISABLE_STATED_MIN_MS = 5 * 60 * 1000;
35
+
36
+ /**
37
+ * Whether the auto-disable preference is on for a tenant (and for shared
38
+ * accounts — `tenantId === null` — when any tenant has it on, D06).
39
+ *
40
+ * The preference lives in `console_settings.preferences` (the C2 cluster added
41
+ * the typed field to `ConsoleSettingsPreferences`). Reading is fail-safe (D01
42
+ * §3.1): a missing row, a failed read, or a preference that is not exactly
43
+ * `true` all mean off — a settings failure must never break a health write.
44
+ */
45
+ export async function autoDisableRateLimitedEnabled(
46
+ db: CartethyiaDatabase,
47
+ tenantId: string | null,
48
+ ): Promise<boolean> {
49
+ try {
50
+ const rows = await db
51
+ .select({ tenantId: consoleSettings.tenantId, preferences: consoleSettings.preferences })
52
+ .from(consoleSettings);
53
+ return rows.some((row) => {
54
+ if (tenantId !== null && row.tenantId !== tenantId) return false;
55
+ const prefs = row.preferences as {
56
+ readonly autoDisableRateLimited?: boolean;
57
+ } | null | undefined;
58
+ return prefs?.autoDisableRateLimited === true;
59
+ });
60
+ } catch {
61
+ return false;
62
+ }
63
+ }
16
64
 
17
65
  export { classifyAccountError, unsupportedModelCooldownMs } from "./account-error-classification";
18
66
  export type {
@@ -110,6 +158,11 @@ export async function recordAccountFailure(
110
158
  // counted", which is the common case and must stay cheap.
111
159
  modelBlockHits: providerAccounts.modelBlockHits,
112
160
  cooldownUntil: providerAccounts.cooldownUntil,
161
+ // Required by the auto-disable toggle (tenant-level preference read)
162
+ // and by the tier-aware cooldown rules (plan → tier).
163
+ providerId: providerAccounts.providerId,
164
+ tenantId: providerAccounts.tenantId,
165
+ quotaPlan: providerAccounts.quotaPlan,
113
166
  })
114
167
  .from(providerAccounts)
115
168
  .where(eq(providerAccounts.id, accountId))
@@ -128,6 +181,42 @@ export async function recordAccountFailure(
128
181
  const fromStatus = account.status;
129
182
  const now = new Date();
130
183
 
184
+ // ---- Auto-disable intercept (area 01, D01–D06) -------------------------
185
+ // Evaluated after classification and before the model-scoped/account-wide
186
+ // branch, so a 429/quota verdict carrying a modelId is rewritten to an
187
+ // account-wide disable instead of sinking into a per-model cooldown (D02).
188
+ //
189
+ // Only fires when every condition holds:
190
+ // - the tenant's toggle is on (fail-safe read: off on any error),
191
+ // - the classification mutates the account and the origin is upstream,
192
+ // - the category is a target (quota_exhausted / rate_limit_transient),
193
+ // - the verdict is a cooldown (rule-level `disabled` verdicts — bans,
194
+ // revoked credentials — are untouched),
195
+ // - the provider did not state a sub-5-minute window (D04: Inception's
196
+ // 1m limit must keep its short cooldown, not park the account for 24h).
197
+ const autoDisableTarget =
198
+ AUTO_DISABLE_TARGET_CATEGORIES.has(effective.category) &&
199
+ effective.status === "cooldown" &&
200
+ effective.mutatesAccount &&
201
+ options?.origin === "upstream";
202
+ const statedTooShort =
203
+ effective.providerStated === true &&
204
+ effective.cooldownMs > 0 &&
205
+ effective.cooldownMs < AUTO_DISABLE_STATED_MIN_MS;
206
+ let autoDisableActive = false;
207
+ if (autoDisableTarget && !statedTooShort) {
208
+ autoDisableActive = await autoDisableRateLimitedEnabled(client, account.tenantId);
209
+ }
210
+ // Rewritten classification used by the write paths below.
211
+ const interception = autoDisableActive
212
+ ? {
213
+ ...effective,
214
+ status: "disabled" as const,
215
+ cooldownMs: resolveAccountDisabledRecoveryMs(),
216
+ ruleSource: "fallback" as const,
217
+ }
218
+ : effective;
219
+
131
220
  const modelId = options?.modelId;
132
221
  // Every category here is a verdict about one (account, model) pair rather
133
222
  // than about the credential, so all three back off that pair and leave the
@@ -138,16 +227,56 @@ export async function recordAccountFailure(
138
227
  effective.category === "rate_limit_transient" ||
139
228
  effective.category === "model_capacity" ||
140
229
  effective.category === "model_unreachable";
141
- const shouldModelCooldown = modelId !== undefined && isThrottle;
230
+ // D02: when the auto-disable intercept fired, the target category must not
231
+ // sink into a model-scoped cooldown — the outcome is an account-wide
232
+ // disable.
233
+ const shouldModelCooldown = !autoDisableActive && modelId !== undefined && isThrottle;
142
234
  // The account-wide streak, escalated for a provider-stated window. Read once
143
235
  // so the model-scoped and account-wide paths below cannot disagree about it:
144
236
  // this is the count *before* this failure, which the UPDATE below increments.
145
237
  const streak = Math.max(0, account.consecutiveFailures ?? 0);
146
- const escalatedCooldownMs = escalateFallbackCooldown(
147
- effective.cooldownMs,
148
- effective.providerStated === true,
149
- streak,
150
- );
238
+ // Tier-aware rule resolution (area 03, D15–D19): the account's plan is
239
+ // projected on this row, mapped to a tier, and — when a rule exists for
240
+ // the (provider, category, tier) combination — replaces the classifier's
241
+ // duration. The rule duration is a policy decision and never escalates
242
+ // (D18); escalation applies only to the fallback.
243
+ const accountTier =
244
+ account.quotaPlan === null || account.quotaPlan === undefined
245
+ ? null
246
+ : mapPlanToTier(account.providerId, account.quotaPlan);
247
+ // A provider-stated window always wins over a tier rule (D18), so the rule
248
+ // is only consulted when the upstream named nothing.
249
+ const ruleCooldownMs =
250
+ interception.providerStated === true
251
+ ? undefined
252
+ : resolveCooldownRule({
253
+ providerId: account.providerId,
254
+ category: effective.category,
255
+ tier: accountTier,
256
+ });
257
+ const ruleSource = interception.ruleSource;
258
+ const sourceCooldownMs =
259
+ ruleCooldownMs !== undefined ? ruleCooldownMs : interception.cooldownMs;
260
+ const sourceRuleKind: "provider-rule" | "fallback" | undefined =
261
+ ruleCooldownMs !== undefined ? "provider-rule" : ruleSource;
262
+ // The auto-disable recovery window is a policy duration like a tier rule:
263
+ // it must never pass through the fallback escalation ladder, or the 1h cap
264
+ // would truncate the default 24h recovery on the first unstated 429 (D18).
265
+ const resolvedCooldownMs = autoDisableActive
266
+ ? interception.cooldownMs
267
+ : applyRuleEscalation(
268
+ sourceCooldownMs,
269
+ interception.providerStated === true,
270
+ streak,
271
+ sourceRuleKind,
272
+ );
273
+ // When a rule replaces the duration, the classifier's `retryAt` (computed
274
+ // from the old constant/fallback) is stale: a shorter rule would lose to it
275
+ // in the monotonic-max deadline. Recompute from the rule duration instead.
276
+ const retryAtMs =
277
+ ruleCooldownMs !== undefined
278
+ ? Date.now() + ruleCooldownMs
279
+ : effective.retryAt?.getTime();
151
280
 
152
281
  // The unsupported-model ladder, computed before the branch: the classifier
153
282
  // could only state the base window because the authoritative hit count
@@ -173,8 +302,8 @@ export async function recordAccountFailure(
173
302
  // of the two whenever the provider really parked the pair longer.
174
303
  const previousDeadline = existing[modelId] === undefined ? undefined : Date.parse(existing[modelId]);
175
304
  const nextDeadline = latestDeadlineMs(
176
- effective.retryAt?.getTime(),
177
- Date.now() + (unsupportedCooldownMs ?? escalatedCooldownMs),
305
+ retryAtMs,
306
+ Date.now() + (unsupportedCooldownMs ?? resolvedCooldownMs),
178
307
  previousDeadline,
179
308
  );
180
309
  const nextUntil = new Date(nextDeadline).toISOString();
@@ -240,15 +369,35 @@ export async function recordAccountFailure(
240
369
  // The route catalog projects modelCooldowns and stamps this candidate as
241
370
  // model_cooldown. Invalidate when its deadline changes, or the cached
242
371
  // snapshot keeps routing this pair until some unrelated write rebuilds it.
243
- return { ...classification, routesVisible: previousDeadline !== nextDeadline };
372
+ return {
373
+ ...classification,
374
+ routesVisible: previousDeadline !== nextDeadline,
375
+ ...(sourceRuleKind === undefined ? {} : { ruleSource: sourceRuleKind }),
376
+ };
244
377
  }
245
378
 
379
+ // When the intercept fired, the reason is prefixed with the auto-disable
380
+ // marker — `sweepExpiredCooldowns` (D03/R-B) uses the prefix to tell an
381
+ // auto-disable apart from an operator disable, which stays terminal.
382
+ const writtenReason = autoDisableActive
383
+ ? `${AUTO_DISABLE_MARKER_PREFIX}${effective.reason}`
384
+ : effective.reason;
385
+ // A disabled account's deadline is the auto-disable recovery window — the
386
+ // classifier's `retryAt`/cooldown duration must not leak in, or a terminal
387
+ // disable (`recovery = 0`) would gain a deadline and be swept back.
388
+ const deadlineCandidates = autoDisableActive
389
+ ? [interception.cooldownMs > 0 ? Date.now() + interception.cooldownMs : undefined]
390
+ : [
391
+ retryAtMs,
392
+ resolvedCooldownMs > 0 ? Date.now() + resolvedCooldownMs : undefined,
393
+ account.cooldownUntil?.getTime?.(),
394
+ ];
246
395
  await client
247
396
  .update(providerAccounts)
248
397
  .set({
249
- status: effective.status,
398
+ status: interception.status,
250
399
  consecutiveFailures: sql`${providerAccounts.consecutiveFailures} + 1`,
251
- lastError: effective.reason,
400
+ lastError: writtenReason,
252
401
  lastErrorCategory: effective.category,
253
402
  lastErrorAt: now,
254
403
  // Monotonic against the account's own in-force deadline, for the same
@@ -259,11 +408,7 @@ export async function recordAccountFailure(
259
408
  // (see `accountDeadlineMs`): the one state that asks for it is a
260
409
  // terminal automatic disable, and an elapsed deadline there would hand
261
410
  // the row straight back to the recovery sweep.
262
- cooldownUntil: accountDeadlineMs([
263
- effective.retryAt?.getTime(),
264
- escalatedCooldownMs > 0 ? Date.now() + escalatedCooldownMs : undefined,
265
- account.cooldownUntil?.getTime?.(),
266
- ]),
411
+ cooldownUntil: accountDeadlineMs(deadlineCandidates),
267
412
  })
268
413
  .where(eq(providerAccounts.id, accountId));
269
414
 
@@ -272,8 +417,8 @@ export async function recordAccountFailure(
272
417
  entityKind: "account",
273
418
  accountId,
274
419
  fromStatus: fromStatus as "active" | "cooldown" | "disabled",
275
- toStatus: effective.status,
276
- reason: effective.reason,
420
+ toStatus: interception.status,
421
+ reason: writtenReason,
277
422
  errorCategory: effective.category,
278
423
  modelId: modelId ?? null,
279
424
  createdAt: now,
@@ -281,7 +426,11 @@ export async function recordAccountFailure(
281
426
  }
282
427
  // This path wrote `status` and `cooldownUntil`, both of which the snapshot
283
428
  // projects, so routing has to see it.
284
- return { ...classification, routesVisible: true };
429
+ return {
430
+ ...classification,
431
+ routesVisible: true,
432
+ ...(sourceRuleKind === undefined ? {} : { ruleSource: sourceRuleKind }),
433
+ };
285
434
  };
286
435
 
287
436
  return withTransaction(db, persist, null);
@@ -414,11 +563,28 @@ export async function sweepExpiredCooldowns(db: CartethyiaDatabase): Promise<num
414
563
  })
415
564
  .from(providerAccounts)
416
565
  .where(
417
- // An ordinary cooldown, recovered as soon as its window elapses.
566
+ // An ordinary cooldown, recovered as soon as its window elapses; plus
567
+ // (D03/R-B) an account the *auto-disable* intercept disabled — only
568
+ // when its category is a target AND the reason carries the auto-disable
569
+ // marker AND its recovery deadline has elapsed. An auto-disable is the
570
+ // machine's own verdict, so the sweep may undo it; an operator disable
571
+ // (no marker) stays terminal.
418
572
  and(
419
- eq(providerAccounts.status, "cooldown"),
420
- isNotNull(providerAccounts.cooldownUntil),
421
- lte(providerAccounts.cooldownUntil, now),
573
+ or(
574
+ and(
575
+ eq(providerAccounts.status, "cooldown"),
576
+ isNotNull(providerAccounts.cooldownUntil),
577
+ lte(providerAccounts.cooldownUntil, now),
578
+ ),
579
+ and(
580
+ eq(providerAccounts.status, "disabled"),
581
+ inArray(providerAccounts.lastErrorCategory, [...AUTO_DISABLE_TARGET_CATEGORIES]),
582
+ isNotNull(providerAccounts.lastError),
583
+ like(providerAccounts.lastError, `${AUTO_DISABLE_MARKER_PREFIX}%`),
584
+ isNotNull(providerAccounts.cooldownUntil),
585
+ lte(providerAccounts.cooldownUntil, now),
586
+ ),
587
+ ),
422
588
  ),
423
589
  )
424
590
  // One pass recovers a bounded batch. The sweep runs every 30s, so a