cartethyia 2.5.2 → 2.5.4

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 (277) hide show
  1. package/dist/dashboard/assets/{BannedPage-MsQaeQTs.js → BannedPage-C2orBsI2.js} +1 -1
  2. package/dist/dashboard/assets/BannedPage-C2orBsI2.js.gz +0 -0
  3. package/dist/dashboard/assets/CliToolDetailPage-DUcZ6pMB.js +1 -0
  4. package/dist/dashboard/assets/CliToolDetailPage-DUcZ6pMB.js.gz +0 -0
  5. package/dist/dashboard/assets/{CliToolsPage-DfWHTJ7v.js → CliToolsPage-BV6pxVQo.js} +1 -1
  6. package/dist/dashboard/assets/CliToolsPage-BV6pxVQo.js.gz +0 -0
  7. package/dist/dashboard/assets/{CombosPage-BrCBQDAc.js → CombosPage-Bsan4679.js} +1 -1
  8. package/dist/dashboard/assets/CombosPage-Bsan4679.js.gz +0 -0
  9. package/dist/dashboard/assets/{ConsoleLogPage-C6Uh33er.js → ConsoleLogPage-CW-wPmJ6.js} +1 -1
  10. package/dist/dashboard/assets/ConsoleLogPage-CW-wPmJ6.js.gz +0 -0
  11. package/dist/dashboard/assets/{CustomizationPage-8aS9hyJu.js → CustomizationPage-0p8uX7vB.js} +1 -1
  12. package/dist/dashboard/assets/CustomizationPage-0p8uX7vB.js.gz +0 -0
  13. package/dist/dashboard/assets/HealthEventsModal-BjqSMfko.js +1 -0
  14. package/dist/dashboard/assets/HealthEventsModal-BjqSMfko.js.gz +0 -0
  15. package/dist/dashboard/assets/{LoginPage-C8hKyXBe.js → LoginPage-BvzCx8Kr.js} +1 -1
  16. package/dist/dashboard/assets/LoginPage-BvzCx8Kr.js.gz +0 -0
  17. package/dist/dashboard/assets/{ModelPicker-CWkTUtpX.js → ModelPicker-BY1aKEkP.js} +1 -1
  18. package/dist/dashboard/assets/ModelPicker-BY1aKEkP.js.gz +0 -0
  19. package/dist/dashboard/assets/OverviewPage-DOQpy1y0.js +1 -0
  20. package/dist/dashboard/assets/OverviewPage-DOQpy1y0.js.gz +0 -0
  21. package/dist/dashboard/assets/ProviderDetailPage-ecxyUMzt.js +3 -0
  22. package/dist/dashboard/assets/ProviderDetailPage-ecxyUMzt.js.gz +0 -0
  23. package/dist/dashboard/assets/ProvidersPage-Js9jUPSb.js +1 -0
  24. package/dist/dashboard/assets/ProvidersPage-Js9jUPSb.js.gz +0 -0
  25. package/dist/dashboard/assets/ProxyPage-J3TBi3Kx.js +7 -0
  26. package/dist/dashboard/assets/ProxyPage-J3TBi3Kx.js.gz +0 -0
  27. package/dist/dashboard/assets/QuotaPage-C1gZS6xr.js +1 -0
  28. package/dist/dashboard/assets/QuotaPage-C1gZS6xr.js.gz +0 -0
  29. package/dist/dashboard/assets/SettingsPage-CS86ho5X.js +1 -0
  30. package/dist/dashboard/assets/SettingsPage-CS86ho5X.js.gz +0 -0
  31. package/dist/dashboard/assets/{SetupPage-CXjUlfqr.js → SetupPage-DDbMQsoo.js} +1 -1
  32. package/dist/dashboard/assets/SetupPage-DDbMQsoo.js.gz +0 -0
  33. package/dist/dashboard/assets/{SortableList-C-_oD3F2.js → SortableList-CB3Z7VTf.js} +1 -1
  34. package/dist/dashboard/assets/SortableList-CB3Z7VTf.js.gz +0 -0
  35. package/dist/dashboard/assets/{StudioPage-u9IxmYOI.js → StudioPage-D95UCPHI.js} +3 -3
  36. package/dist/dashboard/assets/StudioPage-D95UCPHI.js.gz +0 -0
  37. package/dist/dashboard/assets/{UsagePage-BEVLVrwP.js → UsagePage-Cnae9SWA.js} +13 -13
  38. package/dist/dashboard/assets/UsagePage-Cnae9SWA.js.gz +0 -0
  39. package/dist/dashboard/assets/{api-keys-uNywYlfv.js → api-keys-VY68Mhd1.js} +1 -1
  40. package/dist/dashboard/assets/api-keys-VY68Mhd1.js.gz +0 -0
  41. package/dist/dashboard/assets/app-COlK95oi.css +2 -0
  42. package/dist/dashboard/assets/app-COlK95oi.css.gz +0 -0
  43. package/dist/dashboard/assets/{assert-NHwYq95t.js → assert-Chi7pCnt.js} +1 -1
  44. package/dist/dashboard/assets/assert-Chi7pCnt.js.gz +0 -0
  45. package/dist/dashboard/assets/{badge-6uzDDvqs.js → badge-D4pQapKj.js} +1 -1
  46. package/dist/dashboard/assets/badge-D4pQapKj.js.gz +0 -0
  47. package/dist/dashboard/assets/{banner-4_WNg2WZ.js → banner-DH6cg-_B.js} +1 -1
  48. package/dist/dashboard/assets/banner-DH6cg-_B.js.gz +0 -0
  49. package/dist/dashboard/assets/{card--AtFedAq.js → card-BKqlUOe4.js} +1 -1
  50. package/dist/dashboard/assets/card-BKqlUOe4.js.gz +0 -0
  51. package/dist/dashboard/assets/{cli-tools-B3e7dx3N.js → cli-tools-fTgljTGA.js} +1 -1
  52. package/dist/dashboard/assets/cli-tools-fTgljTGA.js.gz +0 -0
  53. package/dist/dashboard/assets/{clipboard-button-NreUx6c8.js → clipboard-button-D5Ys_sM0.js} +1 -1
  54. package/dist/dashboard/assets/clipboard-button-D5Ys_sM0.js.gz +0 -0
  55. package/dist/dashboard/assets/database-Dz9N3zGd.js +1 -0
  56. package/dist/dashboard/assets/database-Dz9N3zGd.js.gz +0 -0
  57. package/dist/dashboard/assets/{dialog-BtpsJU98.js → dialog-BYj-8WuK.js} +1 -1
  58. package/dist/dashboard/assets/dialog-BYj-8WuK.js.gz +0 -0
  59. package/dist/dashboard/assets/{drawer-WwwtzihF.js → drawer-CmXIZ6H1.js} +1 -1
  60. package/dist/dashboard/assets/drawer-CmXIZ6H1.js.gz +0 -0
  61. package/dist/dashboard/assets/entry-CBg5zeu8.js +1 -0
  62. package/dist/dashboard/assets/entry-CBg5zeu8.js.gz +0 -0
  63. package/dist/dashboard/assets/entry-CQBr6U9c.js +1 -0
  64. package/dist/dashboard/assets/entry-CQBr6U9c.js.gz +0 -0
  65. package/dist/dashboard/assets/entry-CgdSAzEV.js +4 -0
  66. package/dist/dashboard/assets/entry-CgdSAzEV.js.gz +0 -0
  67. package/dist/dashboard/assets/{github-badge-D_eM3IgG.js → github-badge-BlptL8Uf.js} +1 -1
  68. package/dist/dashboard/assets/github-badge-BlptL8Uf.js.gz +0 -0
  69. package/dist/dashboard/assets/{index-DCBtROoK.js → index-js6TJvdr.js} +2 -2
  70. package/dist/dashboard/assets/index-js6TJvdr.js.gz +0 -0
  71. package/dist/dashboard/assets/{inline-BcNGF8Gr.js → inline-DUWDSmDV.js} +1 -1
  72. package/dist/dashboard/assets/inline-DUWDSmDV.js.gz +0 -0
  73. package/dist/dashboard/assets/{input-BG41et9G.js → input-LVjgZq4f.js} +1 -1
  74. package/dist/dashboard/assets/input-LVjgZq4f.js.gz +0 -0
  75. package/dist/dashboard/assets/{layout-C8mer_Ak.js → layout-CPfxpsfy.js} +1 -1
  76. package/dist/dashboard/assets/layout-CPfxpsfy.js.gz +0 -0
  77. package/dist/dashboard/assets/{list-row-Gh_W4OR_.js → list-row-DupvLfDx.js} +1 -1
  78. package/dist/dashboard/assets/list-row-DupvLfDx.js.gz +0 -0
  79. package/dist/dashboard/assets/{live-BJGYRBnR.js → live-C4pEJUqI.js} +1 -1
  80. package/dist/dashboard/assets/live-C4pEJUqI.js.gz +0 -0
  81. package/dist/dashboard/assets/model-bans-BRiuufkB.js +1 -0
  82. package/dist/dashboard/assets/model-bans-BRiuufkB.js.gz +0 -0
  83. package/dist/dashboard/assets/{mutation-BkoLM5Ba.js → mutation-B4ROwk8L.js} +1 -1
  84. package/dist/dashboard/assets/mutation-B4ROwk8L.js.gz +0 -0
  85. package/dist/dashboard/assets/numbers-YNcHvRY7.js +2 -0
  86. package/dist/dashboard/assets/numbers-YNcHvRY7.js.gz +0 -0
  87. package/dist/dashboard/assets/{query-keys-D7wX2vM5.js → query-keys-gmjzC3RS.js} +1 -1
  88. package/dist/dashboard/assets/{query-keys-D7wX2vM5.js.gz → query-keys-gmjzC3RS.js.gz} +0 -0
  89. package/dist/dashboard/assets/{routing-CcusqXA7.js → routing-C7tBT5o-.js} +1 -1
  90. package/dist/dashboard/assets/routing-C7tBT5o-.js.gz +0 -0
  91. package/dist/dashboard/assets/{select-DsX5QT8K.js → select-WUCXorXw.js} +2 -2
  92. package/dist/dashboard/assets/select-WUCXorXw.js.gz +0 -0
  93. package/dist/dashboard/assets/{settings-mpmXTgUa.js → settings-C_zKl1GS.js} +1 -1
  94. package/dist/dashboard/assets/settings-C_zKl1GS.js.gz +0 -0
  95. package/dist/dashboard/assets/{stack-DwLmjUFK.js → stack-_3O0F8Xn.js} +1 -1
  96. package/dist/dashboard/assets/stack-_3O0F8Xn.js.gz +0 -0
  97. package/dist/dashboard/assets/{state-DYdQmdAl.js → state-BgTxHcJS.js} +1 -1
  98. package/dist/dashboard/assets/state-BgTxHcJS.js.gz +0 -0
  99. package/dist/dashboard/assets/switch-DEjTtOWf.js +1 -0
  100. package/dist/dashboard/assets/switch-DEjTtOWf.js.gz +0 -0
  101. package/dist/dashboard/assets/terminal-0_IjPWTk.js +1 -0
  102. package/dist/dashboard/assets/terminal-0_IjPWTk.js.gz +0 -0
  103. package/dist/dashboard/assets/{useMutation-w4u4CVue.js → useMutation-BWSC0UAS.js} +1 -1
  104. package/dist/dashboard/assets/useMutation-BWSC0UAS.js.gz +0 -0
  105. package/dist/dashboard/assets/zap-C4EeVW4q.js +1 -0
  106. package/dist/dashboard/assets/zap-C4EeVW4q.js.gz +0 -0
  107. package/dist/dashboard/donation/qris-risun-marke.png +0 -0
  108. package/dist/dashboard/index.html +1 -1
  109. package/dist/dashboard/index.html.gz +0 -0
  110. package/dist/dashboard/precompressed-assets.json +54 -54
  111. package/package.json +1 -1
  112. package/scripts/cli/autostart.ts +237 -0
  113. package/scripts/cli/command/console-menu.ts +244 -246
  114. package/scripts/cli/command/tray.ts +208 -204
  115. package/scripts/cli/command/update.ts +27 -10
  116. package/scripts/cli/main.ts +28 -1
  117. package/src/boot/app.ts +693 -693
  118. package/src/boot/dependencies.ts +646 -616
  119. package/src/config/spec.ts +81 -2
  120. package/src/config/telemetry.ts +46 -0
  121. package/src/console/api-keys/bucket-spend.ts +15 -4
  122. package/src/console/backup/contracts.ts +15 -2
  123. package/src/console/backup/routes.ts +52 -1
  124. package/src/console/backup/service.ts +60 -4
  125. package/src/console/console-router.ts +8 -0
  126. package/src/console/data-management/contracts.ts +93 -0
  127. package/src/console/data-management/operations.ts +229 -0
  128. package/src/console/data-management/routes.ts +104 -0
  129. package/src/console/domain-registration.ts +39 -0
  130. package/src/console/observability/contracts.ts +524 -485
  131. package/src/console/observability/store.ts +1115 -1018
  132. package/src/console/providers/catalog/contracts.ts +6 -0
  133. package/src/console/providers/catalog/provider-operations.ts +20 -0
  134. package/src/console/providers/catalog/routes.ts +7 -0
  135. package/src/console/providers/catalog/store.ts +22 -1
  136. package/src/console/quota/account-quota-shared.ts +52 -39
  137. package/src/console/quota/refresh.ts +441 -429
  138. package/src/console/routing/pools/import-parse.ts +206 -0
  139. package/src/console/routing/pools/operations.ts +543 -501
  140. package/src/console/routing/pools/routes.ts +181 -164
  141. package/src/console/settings/contracts.ts +37 -0
  142. package/src/console/settings/store.ts +51 -0
  143. package/src/console/share/share-router.ts +6 -9
  144. package/src/console/updates/routes.ts +16 -0
  145. package/src/main.ts +10 -0
  146. package/src/observability/event-compaction.ts +315 -0
  147. package/src/observability/event-day-index.ts +442 -0
  148. package/src/observability/event-log.ts +152 -3
  149. package/src/observability/payload-capture.ts +373 -344
  150. package/src/observability/payload-store.ts +440 -366
  151. package/src/observability/telemetry-status.ts +28 -16
  152. package/src/persistence/health-event-flush.ts +36 -0
  153. package/src/persistence/schema.ts +1402 -1360
  154. package/src/persistence/stores/api-key-store.ts +6 -0
  155. package/src/persistence/stores/share-store.ts +583 -556
  156. package/src/persistence/stores/telemetry-store.ts +237 -214
  157. package/src/protocol/codecs.ts +1 -2
  158. package/src/protocol/image-clamp.ts +19 -1
  159. package/src/protocol/registry.ts +8 -0
  160. package/src/protocol/request/gemini.ts +9 -2
  161. package/src/protocol/request/messages.ts +14 -2
  162. package/src/protocol/response/gemini.ts +18 -4
  163. package/src/providers/adapters/compatible-adapter.ts +3 -0
  164. package/src/providers/bundled-discovery-providers.ts +122 -119
  165. package/src/providers/configured-openai-providers.ts +136 -134
  166. package/src/providers/default-registry.ts +5 -4
  167. package/src/providers/discovery/openai-model-discovery.ts +5 -0
  168. package/src/providers/discovery/openrouter-free-roster.ts +15 -17
  169. package/src/providers/integrations/antigravity/antigravity-quota.ts +11 -3
  170. package/src/providers/integrations/buddy/buddy-catalog-shared.ts +4 -4
  171. package/src/providers/integrations/buddy/workbuddy/shared.ts +201 -200
  172. package/src/providers/integrations/cline/cline-quota.ts +132 -128
  173. package/src/providers/integrations/codex/codex-headers.ts +25 -39
  174. package/src/providers/integrations/codex/codex.ts +2 -29
  175. package/src/providers/integrations/cursor/cursor-quota.ts +310 -0
  176. package/src/providers/integrations/cursor/cursor-request.ts +616 -471
  177. package/src/providers/integrations/cursor/cursor-session-manager.ts +319 -281
  178. package/src/providers/integrations/cursor/cursor.ts +1485 -960
  179. package/src/providers/integrations/cursor/protocol/messages.ts +25 -0
  180. package/src/providers/integrations/factory/factory.ts +8 -1
  181. package/src/providers/integrations/gemini.ts +6 -1
  182. package/src/providers/integrations/grok/grok-quota.ts +279 -177
  183. package/src/providers/integrations/grok/grok-ratelimit-store.ts +73 -0
  184. package/src/providers/integrations/grok/grok-ratelimit.ts +218 -0
  185. package/src/providers/integrations/opencode/provider.ts +531 -532
  186. package/src/providers/integrations/qoder/qoder-quota.ts +192 -0
  187. package/src/providers/integrations/tokenharbor.ts +2 -2
  188. package/src/providers/integrations/trae/trae-catalog.ts +5 -3
  189. package/src/providers/integrations/trae/trae-quota.ts +384 -0
  190. package/src/providers/operations/account-health-service.ts +1523 -1457
  191. package/src/providers/operations/client-versions.ts +9 -2
  192. package/src/providers/provider-contracts.ts +7 -0
  193. package/src/providers/provider-metadata.ts +13 -1
  194. package/src/providers/quota/quota-support.ts +8 -0
  195. package/src/security/api-key-auth.ts +504 -494
  196. package/src/transport/dispatch/attempt-finalize.ts +46 -5
  197. package/src/transport/dispatch/generation.ts +6 -0
  198. package/src/transport/dispatch/proxy-request.ts +33 -1
  199. package/src/transport/dispatch/ratelimit-capture.ts +37 -0
  200. package/src/transport/dispatch/streaming/stream.ts +30 -2
  201. package/src/transport/dispatch/upstream.ts +9 -0
  202. package/src/transport/request/preparer.ts +7 -6
  203. package/src/transport/request/state.ts +9 -0
  204. package/src/transport/request/tenant-preferences.ts +60 -0
  205. package/src/transport/routing/route-catalog.ts +854 -799
  206. package/dist/dashboard/assets/BannedPage-MsQaeQTs.js.gz +0 -0
  207. package/dist/dashboard/assets/CliToolDetailPage-BO9nKg9w.js +0 -1
  208. package/dist/dashboard/assets/CliToolDetailPage-BO9nKg9w.js.gz +0 -0
  209. package/dist/dashboard/assets/CliToolsPage-DfWHTJ7v.js.gz +0 -0
  210. package/dist/dashboard/assets/CombosPage-BrCBQDAc.js.gz +0 -0
  211. package/dist/dashboard/assets/ConsoleLogPage-C6Uh33er.js.gz +0 -0
  212. package/dist/dashboard/assets/CustomizationPage-8aS9hyJu.js.gz +0 -0
  213. package/dist/dashboard/assets/HealthEventsModal-qomXThxX.js +0 -1
  214. package/dist/dashboard/assets/HealthEventsModal-qomXThxX.js.gz +0 -0
  215. package/dist/dashboard/assets/LoginPage-C8hKyXBe.js.gz +0 -0
  216. package/dist/dashboard/assets/ModelPicker-CWkTUtpX.js.gz +0 -0
  217. package/dist/dashboard/assets/OverviewPage-C-ExCy1V.js +0 -1
  218. package/dist/dashboard/assets/OverviewPage-C-ExCy1V.js.gz +0 -0
  219. package/dist/dashboard/assets/ProviderDetailPage-tr811uaF.js +0 -3
  220. package/dist/dashboard/assets/ProviderDetailPage-tr811uaF.js.gz +0 -0
  221. package/dist/dashboard/assets/ProvidersPage-BS9IEpvv.js +0 -1
  222. package/dist/dashboard/assets/ProvidersPage-BS9IEpvv.js.gz +0 -0
  223. package/dist/dashboard/assets/ProxyPage-Rn_qXScb.js +0 -7
  224. package/dist/dashboard/assets/ProxyPage-Rn_qXScb.js.gz +0 -0
  225. package/dist/dashboard/assets/QuotaPage-BihkgQSq.js +0 -1
  226. package/dist/dashboard/assets/QuotaPage-BihkgQSq.js.gz +0 -0
  227. package/dist/dashboard/assets/SettingsPage-DhiEjm6T.js +0 -1
  228. package/dist/dashboard/assets/SettingsPage-DhiEjm6T.js.gz +0 -0
  229. package/dist/dashboard/assets/SetupPage-CXjUlfqr.js.gz +0 -0
  230. package/dist/dashboard/assets/SortableList-C-_oD3F2.js.gz +0 -0
  231. package/dist/dashboard/assets/StudioPage-u9IxmYOI.js.gz +0 -0
  232. package/dist/dashboard/assets/UsagePage-BEVLVrwP.js.gz +0 -0
  233. package/dist/dashboard/assets/api-keys-uNywYlfv.js.gz +0 -0
  234. package/dist/dashboard/assets/app-DxivpfYG.css +0 -2
  235. package/dist/dashboard/assets/app-DxivpfYG.css.gz +0 -0
  236. package/dist/dashboard/assets/assert-NHwYq95t.js.gz +0 -0
  237. package/dist/dashboard/assets/badge-6uzDDvqs.js.gz +0 -0
  238. package/dist/dashboard/assets/banner-4_WNg2WZ.js.gz +0 -0
  239. package/dist/dashboard/assets/card--AtFedAq.js.gz +0 -0
  240. package/dist/dashboard/assets/cli-tools-B3e7dx3N.js.gz +0 -0
  241. package/dist/dashboard/assets/clipboard-button-NreUx6c8.js.gz +0 -0
  242. package/dist/dashboard/assets/dialog-BtpsJU98.js.gz +0 -0
  243. package/dist/dashboard/assets/download-A0SW9OCW.js +0 -1
  244. package/dist/dashboard/assets/download-A0SW9OCW.js.gz +0 -0
  245. package/dist/dashboard/assets/drawer-WwwtzihF.js.gz +0 -0
  246. package/dist/dashboard/assets/entry-Dj9AR0gY.js +0 -4
  247. package/dist/dashboard/assets/entry-Dj9AR0gY.js.gz +0 -0
  248. package/dist/dashboard/assets/entry-TJIye_jT.js +0 -1
  249. package/dist/dashboard/assets/entry-TJIye_jT.js.gz +0 -0
  250. package/dist/dashboard/assets/entry-jy9_4-Ke.js +0 -1
  251. package/dist/dashboard/assets/entry-jy9_4-Ke.js.gz +0 -0
  252. package/dist/dashboard/assets/github-badge-D_eM3IgG.js.gz +0 -0
  253. package/dist/dashboard/assets/index-DCBtROoK.js.gz +0 -0
  254. package/dist/dashboard/assets/inline-BcNGF8Gr.js.gz +0 -0
  255. package/dist/dashboard/assets/input-BG41et9G.js.gz +0 -0
  256. package/dist/dashboard/assets/layout-C8mer_Ak.js.gz +0 -0
  257. package/dist/dashboard/assets/list-row-Gh_W4OR_.js.gz +0 -0
  258. package/dist/dashboard/assets/live-BJGYRBnR.js.gz +0 -0
  259. package/dist/dashboard/assets/model-bans-DeVLDA7I.js +0 -1
  260. package/dist/dashboard/assets/model-bans-DeVLDA7I.js.gz +0 -0
  261. package/dist/dashboard/assets/mutation-BkoLM5Ba.js.gz +0 -0
  262. package/dist/dashboard/assets/numbers-Bs4LOOuf.js +0 -2
  263. package/dist/dashboard/assets/numbers-Bs4LOOuf.js.gz +0 -0
  264. package/dist/dashboard/assets/routing-CcusqXA7.js.gz +0 -0
  265. package/dist/dashboard/assets/select-DsX5QT8K.js.gz +0 -0
  266. package/dist/dashboard/assets/settings-mpmXTgUa.js.gz +0 -0
  267. package/dist/dashboard/assets/sparkles-BRaRUxAU.js +0 -1
  268. package/dist/dashboard/assets/sparkles-BRaRUxAU.js.gz +0 -0
  269. package/dist/dashboard/assets/stack-DwLmjUFK.js.gz +0 -0
  270. package/dist/dashboard/assets/state-DYdQmdAl.js.gz +0 -0
  271. package/dist/dashboard/assets/switch-CidSfbG7.js +0 -1
  272. package/dist/dashboard/assets/switch-CidSfbG7.js.gz +0 -0
  273. package/dist/dashboard/assets/terminal-Bd4ee8TA.js +0 -1
  274. package/dist/dashboard/assets/terminal-Bd4ee8TA.js.gz +0 -0
  275. package/dist/dashboard/assets/useMutation-w4u4CVue.js.gz +0 -0
  276. /package/dist/dashboard/assets/{app-CafWtVXA.js → app-DCQZRmHY.js} +0 -0
  277. /package/dist/dashboard/assets/{app-CafWtVXA.js.gz → app-DCQZRmHY.js.gz} +0 -0
@@ -1,281 +1,319 @@
1
- // Live Cursor Run sessions, and the tool-call continuation they make possible.
2
- //
3
- // Cursor's tool loop spans *two* client HTTP requests: the first returns a tool
4
- // call, the second submits that tool's result. The upstream Run must be the same
5
- // Run, and it must stay open between them, so the Run cannot be owned by either
6
- // request — it is owned here.
7
- //
8
- // This manager is process-local by construction. A Run is an HTTP/2 socket; it
9
- // cannot survive a restart, and it is not shared across processes. After a
10
- // restart the adapter rebases from the complete request history instead (see
11
- // `cursor.ts`), which is why an orphan tool result with no history fails
12
- // explicitly rather than silently starting a new Run.
13
- //
14
- // Everything the manager holds is bounded: one idle TTL, one release per
15
- // session, one frame pump per Run, and one terminal path that closes the stream
16
- // and releases the borrowed lease exactly once.
17
- import { GatewayError } from "../../../protocol/gateway-error";
18
- import type { UsageRecord } from "../../../protocol/canonical-model";
19
- import type { AttemptLeaseOwnership } from "../../provider-contracts";
20
- import { RUN_IDLE_TTL_MS } from "./cursor-shared";
21
- import type { CursorToolDescriptor } from "./cursor-request";
22
-
23
- /** Identity a later request must present to claim a pending tool call. */
24
- export interface CursorContinuationScope {
25
- readonly tenantId: string;
26
- readonly apiKeyId: string;
27
- readonly providerId: string;
28
- readonly providerAccountId: string;
29
- }
30
-
31
- /** One pending external tool call awaiting the client's result. */
32
- export interface CursorPendingToolCall {
33
- /** Unguessable call id handed to the client. */
34
- readonly callId: string;
35
- /** Cursor's exec message id the result must echo. */
36
- readonly execId: number;
37
- /** Bare function name the client declared. */
38
- readonly toolName: string;
39
- }
40
-
41
- /** Everything one live Run session owns. */
42
- export interface CursorRunSession {
43
- readonly runId: string;
44
- readonly conversationId: string;
45
- readonly scope: CursorContinuationScope;
46
- /** Declared tools for this Run; an inbound call outside them is refused. */
47
- readonly descriptors: readonly CursorToolDescriptor[];
48
- /** Cursor exec id → pending call id. */
49
- readonly pending: Map<number, string>;
50
- /** Pending call id → Cursor exec id and name. */
51
- readonly calls: Map<string, CursorPendingToolCall>;
52
- /** Usage reported by the Run's `turn_ended`, when it has arrived. */
53
- usage: UsageRecord | undefined;
54
- /** Terminal reason this Run ended, once it has. */
55
- failure: GatewayError | undefined;
56
- }
57
-
58
- /** Registry entry: one live Run, its expiry, and its borrowed lease release. */
59
- interface LiveRun {
60
- readonly session: CursorRunSession;
61
- /**
62
- * The borrowed account/network-pool lease release. Optional and mutable: a
63
- * continuation request that re-enters an already-registered Run hands over the
64
- * lease it took, and `adoptLease` refuses when one is already stored — so the
65
- * slot is released exactly once however many requests touched the Run.
66
- */
67
- releaseLease?: (() => void) | undefined;
68
- readonly close: (reason?: string) => void;
69
- expiryTimer: ReturnType<typeof setTimeout>;
70
- expiresAt: number;
71
- }
72
-
73
- /**
74
- * Bounded, tenant/API-key-scoped registry of live Cursor Runs.
75
- *
76
- * A continuation is authorized by *server-recorded* affinity only: a call id is
77
- * unguessable, and resolving one additionally requires the same tenant, API key,
78
- * provider, and account that created it. A caller that guesses or replays a call
79
- * id from another key resolves nothing, so one tenant's tool result can never be
80
- * written onto another tenant's Run.
81
- */
82
- export class CursorRunSessionManager {
83
- /** runId → live Run. */
84
- private readonly runs = new Map<string, LiveRun>();
85
- /** Pending call id → runId. Second index: resolution is by call id. */
86
- private readonly callOwners = new Map<string, string>();
87
-
88
- /** Live Run count, for tests and runtime assertions. */
89
- get size(): number {
90
- return this.runs.size;
91
- }
92
-
93
- /**
94
- * Registers one live Run.
95
- *
96
- * `releaseLease` is the borrowed account/network-pool lease the manager now
97
- * owns: the request that opened the Run transferred it instead of releasing it
98
- * at response time. It is called exactly once, on the Run's single terminal
99
- * path.
100
- */
101
- register(input: {
102
- readonly session: CursorRunSession;
103
- readonly releaseLease?: (() => void) | undefined;
104
- readonly close: (reason?: string) => void;
105
- readonly idleTtlMs?: number;
106
- }): void {
107
- const idleTtlMs = input.idleTtlMs ?? RUN_IDLE_TTL_MS;
108
- const runId = input.session.runId;
109
- const entry: LiveRun = {
110
- session: input.session,
111
- ...(input.releaseLease === undefined ? {} : { releaseLease: input.releaseLease }),
112
- close: input.close,
113
- expiresAt: Date.now() + idleTtlMs,
114
- expiryTimer: setTimeout(() => {
115
- this.terminate(runId, "Cursor Run idle-expired waiting for a tool result");
116
- }, idleTtlMs),
117
- };
118
- entry.expiryTimer.unref?.();
119
- this.runs.set(runId, entry);
120
- }
121
-
122
- /** Ends one Run: closes its stream and releases what it borrowed, once. */
123
- terminate(runId: string, reason?: string): void {
124
- const entry = this.runs.get(runId);
125
- if (entry === undefined) return;
126
- this.runs.delete(runId);
127
- for (const callId of entry.session.pending.values()) {
128
- if (this.callOwners.get(callId) === runId) this.callOwners.delete(callId);
129
- }
130
- for (const call of entry.session.calls.values()) {
131
- if (this.callOwners.get(call.callId) === runId) this.callOwners.delete(call.callId);
132
- }
133
- clearTimeout(entry.expiryTimer);
134
- try {
135
- entry.close(reason);
136
- } catch {
137
- // Closing a dead stream must not skip the lease release below.
138
- }
139
- try {
140
- entry.releaseLease?.();
141
- } catch {
142
- // A failed release must not propagate out of teardown.
143
- }
144
- }
145
-
146
- /** Registers one pending external tool call on a live Run. */
147
- registerPendingCall(runId: string, call: CursorPendingToolCall): boolean {
148
- const entry = this.runs.get(runId);
149
- if (entry === undefined) return false;
150
- entry.session.pending.set(call.execId, call.callId);
151
- entry.session.calls.set(call.callId, call);
152
- this.callOwners.set(call.callId, runId);
153
- return true;
154
- }
155
-
156
- /**
157
- * Resolves one pending call id to its live Run, scoped to the caller.
158
- *
159
- * Returns the Run only when the id is pending *and* every scope field matches
160
- * the Run that created it. A mismatch is a hard refusal, not a fallback:
161
- * routing one account's tool result onto another account's Run would corrupt
162
- * both conversations and leak the first account's context into the second.
163
- */
164
- resolveCall(
165
- callId: string,
166
- scope: CursorContinuationScope,
167
- ): { session: CursorRunSession; call: CursorPendingToolCall } | undefined {
168
- const runId = this.callOwners.get(callId);
169
- if (runId === undefined) return undefined;
170
- const entry = this.runs.get(runId);
171
- if (entry === undefined) return undefined;
172
- const { session } = entry;
173
- if (
174
- session.scope.tenantId !== scope.tenantId ||
175
- session.scope.apiKeyId !== scope.apiKeyId ||
176
- session.scope.providerId !== scope.providerId ||
177
- session.scope.providerAccountId !== scope.providerAccountId
178
- ) {
179
- return undefined;
180
- }
181
- const call = session.calls.get(callId);
182
- if (call === undefined) return undefined;
183
- return { session, call };
184
- }
185
-
186
- /** Clears one pending call after its result has been written to the Run. */
187
- clearPendingCall(runId: string, callId: string): void {
188
- const entry = this.runs.get(runId);
189
- if (entry === undefined) return;
190
- const call = entry.session.calls.get(callId);
191
- if (call !== undefined) entry.session.pending.delete(call.execId);
192
- entry.session.calls.delete(callId);
193
- this.callOwners.delete(callId);
194
- this.touch(runId);
195
- }
196
-
197
- /** Extends one Run's idle expiry: the client answered, so the Run is live. */
198
- touch(runId: string, idleTtlMs = RUN_IDLE_TTL_MS): void {
199
- const entry = this.runs.get(runId);
200
- if (entry === undefined) return;
201
- clearTimeout(entry.expiryTimer);
202
- entry.expiresAt = Date.now() + idleTtlMs;
203
- entry.expiryTimer = setTimeout(() => {
204
- this.terminate(runId, "Cursor Run idle-expired waiting for a tool result");
205
- }, idleTtlMs);
206
- entry.expiryTimer.unref?.();
207
- }
208
-
209
- /** Records the Run's authoritative terminal usage before it is closed. */
210
- recordUsage(runId: string, usage: UsageRecord): void {
211
- const entry = this.runs.get(runId);
212
- if (entry === undefined) return;
213
- entry.session.usage = usage;
214
- }
215
-
216
- /** Records why a Run failed, so the next request reports it rather than hanging. */
217
- recordFailure(runId: string, failure: GatewayError): void {
218
- const entry = this.runs.get(runId);
219
- if (entry === undefined) return;
220
- entry.session.failure = failure;
221
- }
222
-
223
- /** Reads one live Run by id, for the frame continuation path. */
224
- get(runId: string): CursorRunSession | undefined {
225
- return this.runs.get(runId)?.session;
226
- }
227
-
228
- /**
229
- * Describes one pending call by id and caller identity.
230
- *
231
- * The routing layer needs the owning account *before* it knows which provider
232
- * a candidate belongs to, so this reads the same server-recorded scope the
233
- * adapter will later re-check: tenant and API key here, provider and account
234
- * in the answer. A call id from another tenant or key resolves nothing.
235
- */
236
- describePendingCall(
237
- callId: string,
238
- scope: { readonly tenantId: string; readonly apiKeyId: string },
239
- ): { readonly providerId: string; readonly providerAccountId: string } | undefined {
240
- const runId = this.callOwners.get(callId);
241
- if (runId === undefined) return undefined;
242
- const entry = this.runs.get(runId);
243
- if (entry === undefined) return undefined;
244
- const { session } = entry;
245
- if (
246
- session.scope.tenantId !== scope.tenantId ||
247
- session.scope.apiKeyId !== scope.apiKeyId
248
- ) {
249
- return undefined;
250
- }
251
- if (!session.calls.has(callId)) return undefined;
252
- return {
253
- providerId: session.scope.providerId,
254
- providerAccountId: session.scope.providerAccountId,
255
- };
256
- }
257
-
258
- /**
259
- * Takes over the borrowed lease for a Run that is already registered.
260
- *
261
- * Idempotent: a second transfer stores nothing, so a retry of the same
262
- * continuation request cannot double-release the slot.
263
- */
264
- adoptLease(runId: string, ownership: AttemptLeaseOwnership | undefined): void {
265
- if (ownership === undefined) return;
266
- const entry = this.runs.get(runId);
267
- if (entry === undefined || entry.releaseLease !== undefined) return;
268
- const release = ownership.transferToProviderSession();
269
- if (release === undefined) return;
270
- entry.releaseLease = release;
271
- }
272
-
273
- /** Drops every live Run. Shutdown and test seam. */
274
- terminateAll(reason?: string): void {
275
- for (const runId of [...this.runs.keys()]) this.terminate(runId, reason);
276
- this.callOwners.clear();
277
- }
278
- }
279
-
280
- /** Process-local manager: one active Run set per gateway process. */
281
- export const cursorRunSessions = new CursorRunSessionManager();
1
+ // Live Cursor Run sessions, and the tool-call continuation they make possible.
2
+ //
3
+ // Cursor's tool loop spans *two* client HTTP requests: the first returns a tool
4
+ // call, the second submits that tool's result. The upstream Run must be the same
5
+ // Run, and it must stay open between them, so the Run cannot be owned by either
6
+ // request — it is owned here.
7
+ //
8
+ // This manager is process-local by construction. A Run is an HTTP/2 socket; it
9
+ // cannot survive a restart, and it is not shared across processes. After a
10
+ // restart the adapter rebases from the complete request history instead (see
11
+ // `cursor.ts`), which is why an orphan tool result with no history fails
12
+ // explicitly rather than silently starting a new Run.
13
+ //
14
+ // Everything the manager holds is bounded: one idle TTL, one release per
15
+ // session, one frame pump per Run, and one terminal path that closes the stream
16
+ // and releases the borrowed lease exactly once.
17
+ import { GatewayError } from "../../../protocol/gateway-error";
18
+ import type { UsageRecord } from "../../../protocol/canonical-model";
19
+ import type { AttemptLeaseOwnership } from "../../provider-contracts";
20
+ import { RUN_IDLE_TTL_MS } from "./cursor-shared";
21
+ import type { CursorToolDescriptor } from "./cursor-request";
22
+
23
+ /** Identity a later request must present to claim a pending tool call. */
24
+ export interface CursorContinuationScope {
25
+ readonly tenantId: string;
26
+ readonly apiKeyId: string;
27
+ readonly providerId: string;
28
+ readonly providerAccountId: string;
29
+ }
30
+
31
+ /**
32
+ * The host exec frame a pending call forwards: Cursor asked its executor for a
33
+ * native host tool (shell, write, read) and the canonical client is executing
34
+ * it locally, so the result leg must answer the variant's typed result frame —
35
+ * not the generic `mcp_result` the MCP channel waits on.
36
+ */
37
+ export interface CursorHostExecDetail {
38
+ readonly variant: "shell" | "write" | "read";
39
+ /** The shell command Cursor's executor re-reports in the result echo. */
40
+ readonly command?: string;
41
+ /** The shell working directory echo, when the args carried one. */
42
+ readonly workingDirectory?: string;
43
+ /** The target path echo for write/read frames. */
44
+ readonly path?: string;
45
+ /** Line count of the written content, computed from the call's args. */
46
+ readonly lines?: number;
47
+ /** Byte size of the written content, computed from the call's args. */
48
+ readonly fileSize?: number;
49
+ }
50
+
51
+ /** One pending external tool call awaiting the client's result. */
52
+ export interface CursorPendingToolCall {
53
+ /** Unguessable call id handed to the client. */
54
+ readonly callId: string;
55
+ /** Cursor's exec message id the result must echo. */
56
+ readonly execId: number;
57
+ /**
58
+ * Cursor's exec UUID for the frame, when the build carries one.
59
+ * Newer edges pair exec replies on it as well as the numeric id.
60
+ */
61
+ readonly execIdString?: string;
62
+ /** Bare function name the client declared. */
63
+ readonly toolName: string;
64
+ /** The forwarded host exec, when this call is one. */
65
+ readonly host?: CursorHostExecDetail;
66
+ }
67
+
68
+ /** Everything one live Run session owns. */
69
+ export interface CursorRunSession {
70
+ readonly runId: string;
71
+ readonly conversationId: string;
72
+ readonly scope: CursorContinuationScope;
73
+ /** Declared tools for this Run; an inbound call outside them is refused. */
74
+ readonly descriptors: readonly CursorToolDescriptor[];
75
+ /** Cursor exec id → pending call id. */
76
+ readonly pending: Map<number, string>;
77
+ /** Pending call id → Cursor exec id and name. */
78
+ readonly calls: Map<string, CursorPendingToolCall>;
79
+ /** Usage reported by the Run's `turn_ended`, when it has arrived. */
80
+ usage: UsageRecord | undefined;
81
+ /** Terminal reason this Run ended, once it has. */
82
+ failure: GatewayError | undefined;
83
+ }
84
+
85
+ /** Registry entry: one live Run, its expiry, and its borrowed lease release. */
86
+ interface LiveRun {
87
+ readonly session: CursorRunSession;
88
+ /**
89
+ * The borrowed account/network-pool lease release. Optional and mutable: a
90
+ * continuation request that re-enters an already-registered Run hands over the
91
+ * lease it took, and `adoptLease` refuses when one is already stored — so the
92
+ * slot is released exactly once however many requests touched the Run.
93
+ */
94
+ releaseLease?: (() => void) | undefined;
95
+ readonly close: (reason?: string) => void;
96
+ expiryTimer: ReturnType<typeof setTimeout>;
97
+ expiresAt: number;
98
+ }
99
+
100
+ /**
101
+ * Bounded, tenant/API-key-scoped registry of live Cursor Runs.
102
+ *
103
+ * A continuation is authorized by *server-recorded* affinity only: a call id is
104
+ * unguessable, and resolving one additionally requires the same tenant, API key,
105
+ * provider, and account that created it. A caller that guesses or replays a call
106
+ * id from another key resolves nothing, so one tenant's tool result can never be
107
+ * written onto another tenant's Run.
108
+ */
109
+ export class CursorRunSessionManager {
110
+ /** runId → live Run. */
111
+ private readonly runs = new Map<string, LiveRun>();
112
+ /** Pending call id → runId. Second index: resolution is by call id. */
113
+ private readonly callOwners = new Map<string, string>();
114
+
115
+ /** Live Run count, for tests and runtime assertions. */
116
+ get size(): number {
117
+ return this.runs.size;
118
+ }
119
+
120
+ /**
121
+ * Registers one live Run.
122
+ *
123
+ * `releaseLease` is the borrowed account/network-pool lease the manager now
124
+ * owns: the request that opened the Run transferred it instead of releasing it
125
+ * at response time. It is called exactly once, on the Run's single terminal
126
+ * path.
127
+ */
128
+ register(input: {
129
+ readonly session: CursorRunSession;
130
+ readonly releaseLease?: (() => void) | undefined;
131
+ readonly close: (reason?: string) => void;
132
+ readonly idleTtlMs?: number;
133
+ }): void {
134
+ const idleTtlMs = input.idleTtlMs ?? RUN_IDLE_TTL_MS;
135
+ const runId = input.session.runId;
136
+ const entry: LiveRun = {
137
+ session: input.session,
138
+ ...(input.releaseLease === undefined ? {} : { releaseLease: input.releaseLease }),
139
+ close: input.close,
140
+ expiresAt: Date.now() + idleTtlMs,
141
+ expiryTimer: setTimeout(() => {
142
+ this.terminate(runId, "Cursor Run idle-expired waiting for a tool result");
143
+ }, idleTtlMs),
144
+ };
145
+ entry.expiryTimer.unref?.();
146
+ this.runs.set(runId, entry);
147
+ }
148
+
149
+ /** Ends one Run: closes its stream and releases what it borrowed, once. */
150
+ terminate(runId: string, reason?: string): void {
151
+ const entry = this.runs.get(runId);
152
+ if (entry === undefined) return;
153
+ this.runs.delete(runId);
154
+ for (const callId of entry.session.pending.values()) {
155
+ if (this.callOwners.get(callId) === runId) this.callOwners.delete(callId);
156
+ }
157
+ for (const call of entry.session.calls.values()) {
158
+ if (this.callOwners.get(call.callId) === runId) this.callOwners.delete(call.callId);
159
+ }
160
+ clearTimeout(entry.expiryTimer);
161
+ try {
162
+ entry.close(reason);
163
+ } catch {
164
+ // Closing a dead stream must not skip the lease release below.
165
+ }
166
+ try {
167
+ entry.releaseLease?.();
168
+ } catch {
169
+ // A failed release must not propagate out of teardown.
170
+ }
171
+ }
172
+
173
+ /** Registers one pending external tool call on a live Run. */
174
+ registerPendingCall(runId: string, call: CursorPendingToolCall): boolean {
175
+ const entry = this.runs.get(runId);
176
+ if (entry === undefined) return false;
177
+ entry.session.pending.set(call.execId, call.callId);
178
+ entry.session.calls.set(call.callId, call);
179
+ this.callOwners.set(call.callId, runId);
180
+ return true;
181
+ }
182
+
183
+ /**
184
+ * Resolves one pending call id to its live Run, scoped to the caller.
185
+ *
186
+ * Returns the Run only when the id is pending *and* every scope field matches
187
+ * the Run that created it. A mismatch is a hard refusal, not a fallback:
188
+ * routing one account's tool result onto another account's Run would corrupt
189
+ * both conversations and leak the first account's context into the second.
190
+ */
191
+ resolveCall(
192
+ callId: string,
193
+ scope: CursorContinuationScope,
194
+ ): { session: CursorRunSession; call: CursorPendingToolCall } | undefined {
195
+ const runId = this.callOwners.get(callId);
196
+ if (runId === undefined) return undefined;
197
+ const entry = this.runs.get(runId);
198
+ if (entry === undefined) return undefined;
199
+ const { session } = entry;
200
+ if (
201
+ session.scope.tenantId !== scope.tenantId ||
202
+ session.scope.apiKeyId !== scope.apiKeyId ||
203
+ session.scope.providerId !== scope.providerId ||
204
+ session.scope.providerAccountId !== scope.providerAccountId
205
+ ) {
206
+ return undefined;
207
+ }
208
+ const call = session.calls.get(callId);
209
+ if (call === undefined) return undefined;
210
+ return { session, call };
211
+ }
212
+
213
+ /**
214
+ * Whether the call id is held by a live Run in this process, under any
215
+ * scope. A foreign caller carrying a live call id must stay refused; the
216
+ * scopeless check distinguishes that case from a post-restart replay whose
217
+ * call exists nowhere anymore.
218
+ */
219
+ hasAnyCall(callId: string): boolean {
220
+ const runId = this.callOwners.get(callId);
221
+ return runId !== undefined && this.runs.get(runId) !== undefined;
222
+ }
223
+
224
+ /** Clears one pending call after its result has been written to the Run. */
225
+ clearPendingCall(runId: string, callId: string): void {
226
+ const entry = this.runs.get(runId);
227
+ if (entry === undefined) return;
228
+ const call = entry.session.calls.get(callId);
229
+ if (call !== undefined) entry.session.pending.delete(call.execId);
230
+ entry.session.calls.delete(callId);
231
+ this.callOwners.delete(callId);
232
+ this.touch(runId);
233
+ }
234
+
235
+ /** Extends one Run's idle expiry: the client answered, so the Run is live. */
236
+ touch(runId: string, idleTtlMs = RUN_IDLE_TTL_MS): void {
237
+ const entry = this.runs.get(runId);
238
+ if (entry === undefined) return;
239
+ clearTimeout(entry.expiryTimer);
240
+ entry.expiresAt = Date.now() + idleTtlMs;
241
+ entry.expiryTimer = setTimeout(() => {
242
+ this.terminate(runId, "Cursor Run idle-expired waiting for a tool result");
243
+ }, idleTtlMs);
244
+ entry.expiryTimer.unref?.();
245
+ }
246
+
247
+ /** Records the Run's authoritative terminal usage before it is closed. */
248
+ recordUsage(runId: string, usage: UsageRecord): void {
249
+ const entry = this.runs.get(runId);
250
+ if (entry === undefined) return;
251
+ entry.session.usage = usage;
252
+ }
253
+
254
+ /** Records why a Run failed, so the next request reports it rather than hanging. */
255
+ recordFailure(runId: string, failure: GatewayError): void {
256
+ const entry = this.runs.get(runId);
257
+ if (entry === undefined) return;
258
+ entry.session.failure = failure;
259
+ }
260
+
261
+ /** Reads one live Run by id, for the frame continuation path. */
262
+ get(runId: string): CursorRunSession | undefined {
263
+ return this.runs.get(runId)?.session;
264
+ }
265
+
266
+ /**
267
+ * Describes one pending call by id and caller identity.
268
+ *
269
+ * The routing layer needs the owning account *before* it knows which provider
270
+ * a candidate belongs to, so this reads the same server-recorded scope the
271
+ * adapter will later re-check: tenant and API key here, provider and account
272
+ * in the answer. A call id from another tenant or key resolves nothing.
273
+ */
274
+ describePendingCall(
275
+ callId: string,
276
+ scope: { readonly tenantId: string; readonly apiKeyId: string },
277
+ ): { readonly providerId: string; readonly providerAccountId: string } | undefined {
278
+ const runId = this.callOwners.get(callId);
279
+ if (runId === undefined) return undefined;
280
+ const entry = this.runs.get(runId);
281
+ if (entry === undefined) return undefined;
282
+ const { session } = entry;
283
+ if (
284
+ session.scope.tenantId !== scope.tenantId ||
285
+ session.scope.apiKeyId !== scope.apiKeyId
286
+ ) {
287
+ return undefined;
288
+ }
289
+ if (!session.calls.has(callId)) return undefined;
290
+ return {
291
+ providerId: session.scope.providerId,
292
+ providerAccountId: session.scope.providerAccountId,
293
+ };
294
+ }
295
+
296
+ /**
297
+ * Takes over the borrowed lease for a Run that is already registered.
298
+ *
299
+ * Idempotent: a second transfer stores nothing, so a retry of the same
300
+ * continuation request cannot double-release the slot.
301
+ */
302
+ adoptLease(runId: string, ownership: AttemptLeaseOwnership | undefined): void {
303
+ if (ownership === undefined) return;
304
+ const entry = this.runs.get(runId);
305
+ if (entry === undefined || entry.releaseLease !== undefined) return;
306
+ const release = ownership.transferToProviderSession();
307
+ if (release === undefined) return;
308
+ entry.releaseLease = release;
309
+ }
310
+
311
+ /** Drops every live Run. Shutdown and test seam. */
312
+ terminateAll(reason?: string): void {
313
+ for (const runId of [...this.runs.keys()]) this.terminate(runId, reason);
314
+ this.callOwners.clear();
315
+ }
316
+ }
317
+
318
+ /** Process-local manager: one active Run set per gateway process. */
319
+ export const cursorRunSessions = new CursorRunSessionManager();