@cursor/bdk 0.2.3 → 0.2.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 (577) hide show
  1. package/dist/bin/agent-serve.js +9 -1
  2. package/dist/channels/slack/attachments.d.ts +2 -1
  3. package/dist/channels/slack/attachments.d.ts.map +1 -1
  4. package/dist/channels/slack/attachments.js +19 -9
  5. package/dist/channels/slack/dispatch.d.ts.map +1 -1
  6. package/dist/channels/slack/dispatch.js +26 -24
  7. package/dist/channels/slack/thread-context.d.ts +21 -11
  8. package/dist/channels/slack/thread-context.d.ts.map +1 -1
  9. package/dist/channels/slack/thread-context.js +75 -27
  10. package/dist/docs/404.html +2 -2
  11. package/dist/docs/assets/{app.BOIHhrKi.js → app.4kQ8Db_q.js} +4 -4
  12. package/dist/docs/assets/chunks/@localSearchIndexroot.CvuAJ0My.js +1 -0
  13. package/dist/docs/assets/chunks/{VPLocalSearchBox.D49zVZqM.js → VPLocalSearchBox.DY1RwB8Q.js} +1 -1
  14. package/dist/docs/assets/chunks/{arc.hmjb-sc4.js → arc.CxE_fSPK.js} +1 -1
  15. package/dist/docs/assets/chunks/{architectureDiagram-Q4EWVU46.CxEI1KqM.js → architectureDiagram-Q4EWVU46.CPX426ui.js} +1 -1
  16. package/dist/docs/assets/chunks/{baseUniq.DHHvBWoV.js → baseUniq.CB52JrKG.js} +1 -1
  17. package/dist/docs/assets/chunks/{blockDiagram-DXYQGD6D.DnXW-3bu.js → blockDiagram-DXYQGD6D.J0cNaQCL.js} +1 -1
  18. package/dist/docs/assets/chunks/{c4Diagram-AHTNJAMY.e-47O2jM.js → c4Diagram-AHTNJAMY.DJn3vEGT.js} +1 -1
  19. package/dist/docs/assets/chunks/channel.BoNycAoN.js +1 -0
  20. package/dist/docs/assets/chunks/{chunk-4BX2VUAB.3ry4pYXB.js → chunk-4BX2VUAB.Dao618F6.js} +1 -1
  21. package/dist/docs/assets/chunks/{chunk-4TB4RGXK.DGIZFRx6.js → chunk-4TB4RGXK.QW8Rhjai.js} +1 -1
  22. package/dist/docs/assets/chunks/{chunk-55IACEB6.DKBnqg9U.js → chunk-55IACEB6.DfRLLKF_.js} +1 -1
  23. package/dist/docs/assets/chunks/{chunk-EDXVE4YY.BIU8Ue5k.js → chunk-EDXVE4YY.CIV2ApKQ.js} +1 -1
  24. package/dist/docs/assets/chunks/{chunk-FMBD7UC4.DP-TFhjI.js → chunk-FMBD7UC4.BEheSuxp.js} +1 -1
  25. package/dist/docs/assets/chunks/{chunk-OYMX7WX6.Ux0OYj5v.js → chunk-OYMX7WX6.CUN88ENB.js} +1 -1
  26. package/dist/docs/assets/chunks/{chunk-QZHKN3VN.BzHMwYox.js → chunk-QZHKN3VN.CgqCs8b_.js} +1 -1
  27. package/dist/docs/assets/chunks/{chunk-YZCP3GAM.D4OGAsSP.js → chunk-YZCP3GAM.iCH0nnTJ.js} +1 -1
  28. package/dist/docs/assets/chunks/classDiagram-6PBFFD2Q.C4x07Gkj.js +1 -0
  29. package/dist/docs/assets/chunks/classDiagram-v2-HSJHXN6E.C4x07Gkj.js +1 -0
  30. package/dist/docs/assets/chunks/clone.Db22cH_-.js +1 -0
  31. package/dist/docs/assets/chunks/{cose-bilkent-S5V4N54A.Jq_7BUNx.js → cose-bilkent-S5V4N54A.BLcUxhWE.js} +1 -1
  32. package/dist/docs/assets/chunks/{dagre-KV5264BT.Di-skR2x.js → dagre-KV5264BT.Ep7LofOm.js} +1 -1
  33. package/dist/docs/assets/chunks/{diagram-5BDNPKRD.DeREHIdH.js → diagram-5BDNPKRD.Bzf4GSQ4.js} +1 -1
  34. package/dist/docs/assets/chunks/{diagram-G4DWMVQ6.C7Kcqxro.js → diagram-G4DWMVQ6.DD6Jh-eM.js} +1 -1
  35. package/dist/docs/assets/chunks/{diagram-MMDJMWI5.BK3eohyo.js → diagram-MMDJMWI5.oOIl8bWd.js} +1 -1
  36. package/dist/docs/assets/chunks/{diagram-TYMM5635.C6TyymXT.js → diagram-TYMM5635.DHs85Ade.js} +1 -1
  37. package/dist/docs/assets/chunks/{erDiagram-SMLLAGMA.DYGG-gGx.js → erDiagram-SMLLAGMA.BIb5ghCk.js} +1 -1
  38. package/dist/docs/assets/chunks/{flowDiagram-DWJPFMVM.C9nBcSV4.js → flowDiagram-DWJPFMVM.DV4paY9k.js} +1 -1
  39. package/dist/docs/assets/chunks/{ganttDiagram-T4ZO3ILL.wAg_ymsp.js → ganttDiagram-T4ZO3ILL.AyEKA6_v.js} +1 -1
  40. package/dist/docs/assets/chunks/{gitGraphDiagram-UUTBAWPF.B87j9Oeg.js → gitGraphDiagram-UUTBAWPF.BltiCDFo.js} +1 -1
  41. package/dist/docs/assets/chunks/{graph.CuFMj31w.js → graph.DzuEYGdC.js} +1 -1
  42. package/dist/docs/assets/chunks/{infoDiagram-42DDH7IO.P_mLGumP.js → infoDiagram-42DDH7IO.BV2l981J.js} +1 -1
  43. package/dist/docs/assets/chunks/{ishikawaDiagram-UXIWVN3A.DyuL_MsT.js → ishikawaDiagram-UXIWVN3A.BA4Izf-m.js} +1 -1
  44. package/dist/docs/assets/chunks/{journeyDiagram-VCZTEJTY.CtP09IoJ.js → journeyDiagram-VCZTEJTY.DjaFbFS3.js} +1 -1
  45. package/dist/docs/assets/chunks/{kanban-definition-6JOO6SKY.BoeSYPZo.js → kanban-definition-6JOO6SKY.CfTFieDs.js} +1 -1
  46. package/dist/docs/assets/chunks/{layout.B0lMMtNo.js → layout.DzfWqTBV.js} +1 -1
  47. package/dist/docs/assets/chunks/{linear.HxRBiAFX.js → linear.BO6mpulV.js} +1 -1
  48. package/dist/docs/assets/chunks/{min.BGlFFHwS.js → min.BGgTadgT.js} +1 -1
  49. package/dist/docs/assets/chunks/{mindmap-definition-QFDTVHPH.JyLBD9M8.js → mindmap-definition-QFDTVHPH.DPuIXVWY.js} +1 -1
  50. package/dist/docs/assets/chunks/{pieDiagram-DEJITSTG.DnSmNZF8.js → pieDiagram-DEJITSTG.BOnl3bBg.js} +1 -1
  51. package/dist/docs/assets/chunks/{quadrantDiagram-34T5L4WZ.CUCnbfCa.js → quadrantDiagram-34T5L4WZ.DNB6Ckdc.js} +1 -1
  52. package/dist/docs/assets/chunks/{requirementDiagram-MS252O5E.py0IVUn5.js → requirementDiagram-MS252O5E.D5-Qo7vb.js} +1 -1
  53. package/dist/docs/assets/chunks/{sankeyDiagram-XADWPNL6.Bkc-HWYp.js → sankeyDiagram-XADWPNL6.8t65cesf.js} +1 -1
  54. package/dist/docs/assets/chunks/{sequenceDiagram-FGHM5R23.Dqn8Mlv2.js → sequenceDiagram-FGHM5R23.CaG07BdS.js} +1 -1
  55. package/dist/docs/assets/chunks/{stateDiagram-FHFEXIEX.JikkoQUU.js → stateDiagram-FHFEXIEX.TKOweTB6.js} +1 -1
  56. package/dist/docs/assets/chunks/stateDiagram-v2-QKLJ7IA2.CuqmyQc4.js +1 -0
  57. package/dist/docs/assets/chunks/{theme.b6HQ6zG_.js → theme.Dx-JhpTB.js} +2 -2
  58. package/dist/docs/assets/chunks/{timeline-definition-GMOUNBTQ.BvpSXiBj.js → timeline-definition-GMOUNBTQ.fE53zQ2s.js} +1 -1
  59. package/dist/docs/assets/chunks/{vennDiagram-DHZGUBPP.CU7MVnZx.js → vennDiagram-DHZGUBPP.DJsZZaJg.js} +1 -1
  60. package/dist/docs/assets/chunks/{wardley-RL74JXVD.BKwC4ctG.js → wardley-RL74JXVD.844kyrjl.js} +1 -1
  61. package/dist/docs/assets/chunks/{wardleyDiagram-NUSXRM2D.BfaF6Uhe.js → wardleyDiagram-NUSXRM2D.CMZ-Ru_t.js} +1 -1
  62. package/dist/docs/assets/chunks/{xychartDiagram-5P7HB3ND.Bg5dcpj4.js → xychartDiagram-5P7HB3ND.XXrhBc7a.js} +1 -1
  63. package/dist/docs/assets/{deployment.md.BJ8i7Q6p.js → deployment.md.UhnJIMr-.js} +2 -2
  64. package/dist/docs/assets/{deployment.md.BJ8i7Q6p.lean.js → deployment.md.UhnJIMr-.lean.js} +1 -1
  65. package/dist/docs/assets/{guides_jev.md.BNDm14Gy.js → guides_jev.md.CRcgmjDb.js} +2 -2
  66. package/dist/docs/assets/{guides_jev.md.BNDm14Gy.lean.js → guides_jev.md.CRcgmjDb.lean.js} +1 -1
  67. package/dist/docs/assets/{reference_agent-config.md.DzGI777n.js → reference_agent-config.md.Cj5uBKp4.js} +1 -1
  68. package/dist/docs/assets/{reference_extensions.md.CPPrkWlJ.js → reference_extensions.md.pa2tN-e4.js} +4 -3
  69. package/dist/docs/assets/{reference_extensions.md.CPPrkWlJ.lean.js → reference_extensions.md.pa2tN-e4.lean.js} +1 -1
  70. package/dist/docs/building-with-agents.html +35 -35
  71. package/dist/docs/deployment.html +37 -37
  72. package/dist/docs/deployment.md +4 -1
  73. package/dist/docs/evals.html +35 -35
  74. package/dist/docs/guides/agent-to-agent.html +35 -35
  75. package/dist/docs/guides/bitbucket.html +35 -35
  76. package/dist/docs/guides/cloud-agents.html +35 -35
  77. package/dist/docs/guides/convert-automation.html +35 -35
  78. package/dist/docs/guides/github.html +35 -35
  79. package/dist/docs/guides/gitlab.html +35 -35
  80. package/dist/docs/guides/grokbot-agents.html +35 -35
  81. package/dist/docs/guides/hooks.html +35 -35
  82. package/dist/docs/guides/improve.html +35 -35
  83. package/dist/docs/guides/jev.html +37 -37
  84. package/dist/docs/guides/jev.md +7 -0
  85. package/dist/docs/guides/mcp-oauth.html +35 -35
  86. package/dist/docs/guides/opentelemetry.html +35 -35
  87. package/dist/docs/guides/slack.html +35 -35
  88. package/dist/docs/guides/webhooks.html +35 -35
  89. package/dist/docs/hashmap.json +1 -1
  90. package/dist/docs/hillclimbing.html +35 -35
  91. package/dist/docs/index.html +35 -35
  92. package/dist/docs/llms-full.txt +25 -2
  93. package/dist/docs/quickstart.html +35 -35
  94. package/dist/docs/reference/agent-config.html +37 -37
  95. package/dist/docs/reference/agent-config.md +1 -1
  96. package/dist/docs/reference/artifacts.html +35 -35
  97. package/dist/docs/reference/channels.html +35 -35
  98. package/dist/docs/reference/cli.html +35 -35
  99. package/dist/docs/reference/connections.html +35 -35
  100. package/dist/docs/reference/evals.html +35 -35
  101. package/dist/docs/reference/extensions.html +39 -38
  102. package/dist/docs/reference/extensions.md +13 -0
  103. package/dist/docs/reference/hooks.html +35 -35
  104. package/dist/docs/reference/http-api.html +35 -35
  105. package/dist/docs/reference/instructions.html +35 -35
  106. package/dist/docs/reference/playground.html +35 -35
  107. package/dist/docs/reference/project-layout.html +35 -35
  108. package/dist/docs/reference/prompt.html +35 -35
  109. package/dist/docs/reference/schedules.html +35 -35
  110. package/dist/docs/reference/sessions.html +35 -35
  111. package/dist/docs/reference/skills.html +35 -35
  112. package/dist/docs/reference/subagents.html +35 -35
  113. package/dist/docs/reference/tools.html +35 -35
  114. package/dist/docs/templates/agentic-owners.html +35 -35
  115. package/dist/docs/templates/security-reviewer.html +35 -35
  116. package/dist/docs/templates/thermo-quality-review.html +35 -35
  117. package/dist/docs/templates/thermo-review.html +35 -35
  118. package/dist/docs/templates/triage.html +35 -35
  119. package/dist/docs/troubleshooting.html +35 -35
  120. package/dist/extensions/jev/extension.d.ts +3 -0
  121. package/dist/extensions/jev/extension.d.ts.map +1 -1
  122. package/dist/extensions/jev/extension.js +10 -1
  123. package/dist/extensions/jev/lib/evaluate.d.ts +4 -0
  124. package/dist/extensions/jev/lib/evaluate.d.ts.map +1 -1
  125. package/dist/extensions/jev/lib/evaluate.js +6 -2
  126. package/dist/extensions.d.ts +4 -2
  127. package/dist/extensions.d.ts.map +1 -1
  128. package/dist/extensions.js +5 -2
  129. package/dist/internal/cli-deploy.d.ts.map +1 -1
  130. package/dist/internal/cli-deploy.js +19 -5
  131. package/dist/internal/discovery/agent-config.d.ts +6 -5
  132. package/dist/internal/discovery/agent-config.d.ts.map +1 -1
  133. package/dist/internal/discovery/agent-config.js +29 -28
  134. package/dist/internal/discovery/agent.js +1 -1
  135. package/dist/internal/discovery/extensions.d.ts.map +1 -1
  136. package/dist/internal/discovery/extensions.js +12 -0
  137. package/dist/internal/hosting.d.ts +8 -0
  138. package/dist/internal/hosting.d.ts.map +1 -1
  139. package/dist/internal/hosting.js +24 -0
  140. package/dist/types.d.ts +18 -2
  141. package/dist/types.d.ts.map +1 -1
  142. package/docs/deployment.md +4 -1
  143. package/docs/guides/jev.md +7 -0
  144. package/docs/reference/agent-config.md +1 -1
  145. package/docs/reference/extensions.md +13 -0
  146. package/package.json +1 -9
  147. package/dist/docs/assets/chunks/@localSearchIndexroot.D5L-b34G.js +0 -1
  148. package/dist/docs/assets/chunks/channel.BbOGaJQP.js +0 -1
  149. package/dist/docs/assets/chunks/classDiagram-6PBFFD2Q.Db58HCb1.js +0 -1
  150. package/dist/docs/assets/chunks/classDiagram-v2-HSJHXN6E.Db58HCb1.js +0 -1
  151. package/dist/docs/assets/chunks/clone.Co4MBflG.js +0 -1
  152. package/dist/docs/assets/chunks/stateDiagram-v2-QKLJ7IA2.CMsKhrW0.js +0 -1
  153. package/src/artifacts.ts +0 -68
  154. package/src/bin/agent-serve.ts +0 -1854
  155. package/src/channels/bitbucket/api.ts +0 -891
  156. package/src/channels/bitbucket/binding.ts +0 -417
  157. package/src/channels/bitbucket/bitbucket-channel.ts +0 -162
  158. package/src/channels/bitbucket/bitbucket-webhook.ts +0 -519
  159. package/src/channels/bitbucket/checks.ts +0 -146
  160. package/src/channels/bitbucket/cursor-account.ts +0 -106
  161. package/src/channels/bitbucket/defaults.ts +0 -100
  162. package/src/channels/bitbucket/events.ts +0 -74
  163. package/src/channels/bitbucket/index.ts +0 -51
  164. package/src/channels/bitbucket/replay.ts +0 -421
  165. package/src/channels/bitbucket/state.ts +0 -59
  166. package/src/channels/bitbucket/types.ts +0 -261
  167. package/src/channels/change-monitors/change-monitors-channel.ts +0 -63
  168. package/src/channels/change-monitors/index.ts +0 -8
  169. package/src/channels/change-monitors/types.ts +0 -42
  170. package/src/channels/checks.ts +0 -185
  171. package/src/channels/deployments/deployments-channel.ts +0 -224
  172. package/src/channels/deployments/index.ts +0 -25
  173. package/src/channels/deployments/types.ts +0 -92
  174. package/src/channels/github/api.ts +0 -526
  175. package/src/channels/github/auth.ts +0 -293
  176. package/src/channels/github/binding.ts +0 -233
  177. package/src/channels/github/checks.ts +0 -145
  178. package/src/channels/github/cursor-account.ts +0 -303
  179. package/src/channels/github/defaults.ts +0 -168
  180. package/src/channels/github/events.ts +0 -67
  181. package/src/channels/github/forward.ts +0 -359
  182. package/src/channels/github/github-channel.ts +0 -2334
  183. package/src/channels/github/index.ts +0 -54
  184. package/src/channels/github/progress.ts +0 -365
  185. package/src/channels/github/replay.ts +0 -372
  186. package/src/channels/github/state.ts +0 -124
  187. package/src/channels/github/types.ts +0 -590
  188. package/src/channels/gitlab/api.ts +0 -329
  189. package/src/channels/gitlab/binding.ts +0 -231
  190. package/src/channels/gitlab/checks.ts +0 -228
  191. package/src/channels/gitlab/cursor-account.ts +0 -105
  192. package/src/channels/gitlab/defaults.ts +0 -80
  193. package/src/channels/gitlab/events.ts +0 -37
  194. package/src/channels/gitlab/gitlab-channel.ts +0 -160
  195. package/src/channels/gitlab/gitlab-webhook.ts +0 -509
  196. package/src/channels/gitlab/index.ts +0 -44
  197. package/src/channels/gitlab/replay.ts +0 -328
  198. package/src/channels/gitlab/state.ts +0 -55
  199. package/src/channels/gitlab/types.ts +0 -247
  200. package/src/channels/issues/index.ts +0 -9
  201. package/src/channels/issues/issues-channel.ts +0 -45
  202. package/src/channels/issues/types.ts +0 -38
  203. package/src/channels/origin/api.ts +0 -507
  204. package/src/channels/origin/binding.ts +0 -114
  205. package/src/channels/origin/checks.ts +0 -250
  206. package/src/channels/origin/cursor-account.ts +0 -58
  207. package/src/channels/origin/defaults.ts +0 -140
  208. package/src/channels/origin/events.ts +0 -35
  209. package/src/channels/origin/index.ts +0 -35
  210. package/src/channels/origin/origin-channel.ts +0 -815
  211. package/src/channels/origin/origin-webhook.ts +0 -296
  212. package/src/channels/origin/reactions.ts +0 -72
  213. package/src/channels/origin/state.ts +0 -54
  214. package/src/channels/origin/types.ts +0 -295
  215. package/src/channels/slack/agentic-delivery.ts +0 -634
  216. package/src/channels/slack/agentic-posted.ts +0 -135
  217. package/src/channels/slack/agentic-signals.ts +0 -231
  218. package/src/channels/slack/api.ts +0 -761
  219. package/src/channels/slack/approvals.ts +0 -279
  220. package/src/channels/slack/assistant.ts +0 -76
  221. package/src/channels/slack/attachments.ts +0 -596
  222. package/src/channels/slack/auth.ts +0 -127
  223. package/src/channels/slack/blocks.ts +0 -131
  224. package/src/channels/slack/bot-mentions.ts +0 -318
  225. package/src/channels/slack/channel-watch.ts +0 -314
  226. package/src/channels/slack/cli-project.ts +0 -249
  227. package/src/channels/slack/code-fences.ts +0 -171
  228. package/src/channels/slack/constants.ts +0 -38
  229. package/src/channels/slack/credentials.ts +0 -88
  230. package/src/channels/slack/defaults.ts +0 -714
  231. package/src/channels/slack/dispatch.ts +0 -1060
  232. package/src/channels/slack/doctor.ts +0 -338
  233. package/src/channels/slack/eval-directive.ts +0 -306
  234. package/src/channels/slack/event-id-dedupe.ts +0 -30
  235. package/src/channels/slack/external-policy.ts +0 -75
  236. package/src/channels/slack/footer.ts +0 -106
  237. package/src/channels/slack/inbound.ts +0 -416
  238. package/src/channels/slack/index.ts +0 -53
  239. package/src/channels/slack/init.ts +0 -169
  240. package/src/channels/slack/interactive.ts +0 -406
  241. package/src/channels/slack/links.ts +0 -23
  242. package/src/channels/slack/live-delivery.ts +0 -489
  243. package/src/channels/slack/log.ts +0 -70
  244. package/src/channels/slack/manifest.ts +0 -240
  245. package/src/channels/slack/message-body.ts +0 -82
  246. package/src/channels/slack/nudge-carrier.ts +0 -224
  247. package/src/channels/slack/nudge-store.ts +0 -334
  248. package/src/channels/slack/paint-start.ts +0 -93
  249. package/src/channels/slack/placeholder.ts +0 -358
  250. package/src/channels/slack/pr-ref.ts +0 -74
  251. package/src/channels/slack/progress-delivery.ts +0 -138
  252. package/src/channels/slack/reasoning-card.ts +0 -125
  253. package/src/channels/slack/reasoning-chip.ts +0 -41
  254. package/src/channels/slack/recording-sink.ts +0 -167
  255. package/src/channels/slack/redact.ts +0 -6
  256. package/src/channels/slack/reply-options.ts +0 -279
  257. package/src/channels/slack/setup.ts +0 -125
  258. package/src/channels/slack/slack-channel.ts +0 -744
  259. package/src/channels/slack/socket-mode.ts +0 -140
  260. package/src/channels/slack/stop.ts +0 -25
  261. package/src/channels/slack/stream.ts +0 -66
  262. package/src/channels/slack/thread-context.ts +0 -485
  263. package/src/channels/slack/tool-approval-events.ts +0 -233
  264. package/src/channels/slack/tool-cards.ts +0 -198
  265. package/src/channels/slack/tool-chip.ts +0 -34
  266. package/src/channels/slack/types.ts +0 -787
  267. package/src/channels/slack/upload.ts +0 -137
  268. package/src/channels.ts +0 -543
  269. package/src/connections.ts +0 -153
  270. package/src/dashboard-session-url.ts +0 -30
  271. package/src/evals/assertions.ts +0 -688
  272. package/src/evals/expect.ts +0 -250
  273. package/src/evals/judge.ts +0 -329
  274. package/src/evals/loaders.ts +0 -111
  275. package/src/evals/matchers.ts +0 -223
  276. package/src/evals/reporters.ts +0 -257
  277. package/src/evals/results.ts +0 -130
  278. package/src/evals/run-facts.ts +0 -306
  279. package/src/evals.ts +0 -709
  280. package/src/extensions/cursor-cloud-agents/extension.ts +0 -131
  281. package/src/extensions/cursor-cloud-agents/lib/backend.ts +0 -386
  282. package/src/extensions/cursor-cloud-agents/lib/channels.ts +0 -11
  283. package/src/extensions/cursor-cloud-agents/lib/fleet.ts +0 -98
  284. package/src/extensions/cursor-cloud-agents/lib/inputs.ts +0 -5
  285. package/src/extensions/cursor-cloud-agents/lib/repos.ts +0 -80
  286. package/src/extensions/cursor-cloud-agents/lib/transcript.ts +0 -19
  287. package/src/extensions/cursor-cloud-agents/skills/handoff.md +0 -37
  288. package/src/extensions/cursor-cloud-agents/tools/cancel.ts +0 -21
  289. package/src/extensions/cursor-cloud-agents/tools/dump.ts +0 -47
  290. package/src/extensions/cursor-cloud-agents/tools/get.ts +0 -18
  291. package/src/extensions/cursor-cloud-agents/tools/launch.ts +0 -62
  292. package/src/extensions/cursor-cloud-agents/tools/list.ts +0 -31
  293. package/src/extensions/cursor-cloud-agents/tools/reply.ts +0 -36
  294. package/src/extensions/cursor-grokbot-agents/extension.ts +0 -132
  295. package/src/extensions/cursor-grokbot-agents/lib/agents.ts +0 -42
  296. package/src/extensions/cursor-grokbot-agents/lib/backend.ts +0 -208
  297. package/src/extensions/cursor-grokbot-agents/lib/contacts.ts +0 -74
  298. package/src/extensions/cursor-grokbot-agents/lib/conversation.ts +0 -260
  299. package/src/extensions/cursor-grokbot-agents/lib/inputs.ts +0 -21
  300. package/src/extensions/cursor-grokbot-agents/lib/lock.ts +0 -26
  301. package/src/extensions/cursor-grokbot-agents/skills/consult.md +0 -40
  302. package/src/extensions/cursor-grokbot-agents/tools/ask.ts +0 -32
  303. package/src/extensions/cursor-grokbot-agents/tools/check.ts +0 -25
  304. package/src/extensions/cursor-grokbot-agents/tools/interrupt.ts +0 -17
  305. package/src/extensions/cursor-grokbot-agents/tools/list.ts +0 -26
  306. package/src/extensions/improve/extension.ts +0 -69
  307. package/src/extensions/improve/skills/yourself.ts +0 -50
  308. package/src/extensions/jev/extension.ts +0 -95
  309. package/src/extensions/jev/lib/evaluate.ts +0 -266
  310. package/src/extensions/jev/skills/gated-write.md +0 -25
  311. package/src/extensions/jev/skills/questions.md +0 -33
  312. package/src/extensions/jev/tools/evaluate.ts +0 -82
  313. package/src/extensions/slack-communication/evals/harness.ts +0 -705
  314. package/src/extensions/slack-communication/evals/slack-communication.eval.ts +0 -830
  315. package/src/extensions/slack-communication/evals/slack-placeholder.eval.ts +0 -172
  316. package/src/extensions/slack-communication/extension.ts +0 -44
  317. package/src/extensions/slack-communication/skills/slack-voice.ts +0 -56
  318. package/src/extensions/slack-communication/tools/delete-message.ts +0 -33
  319. package/src/extensions/slack-communication/tools/edit-message.ts +0 -42
  320. package/src/extensions/slack-communication/tools/post-card.ts +0 -28
  321. package/src/extensions/slack-communication/tools/post-file.ts +0 -82
  322. package/src/extensions/slack-communication/tools/post-update.ts +0 -40
  323. package/src/extensions/slack-communication/tools/react.ts +0 -30
  324. package/src/extensions/slack-communication/tools/set-status.ts +0 -21
  325. package/src/extensions.ts +0 -179
  326. package/src/files-backends/agent-store-presigned-url.ts +0 -11
  327. package/src/files-backends/cursor-hosted.ts +0 -1212
  328. package/src/files-backends/local-fs.ts +0 -163
  329. package/src/files.ts +0 -293
  330. package/src/filesystem.ts +0 -2005
  331. package/src/hooks.ts +0 -30
  332. package/src/index.ts +0 -259
  333. package/src/internal/advertise-tools.ts +0 -370
  334. package/src/internal/approval-gate.ts +0 -103
  335. package/src/internal/artifacts-store.ts +0 -836
  336. package/src/internal/as-of.ts +0 -257
  337. package/src/internal/auth.ts +0 -46
  338. package/src/internal/authored-alias-hooks.ts +0 -138
  339. package/src/internal/authored-loaders.ts +0 -188
  340. package/src/internal/bounded-int.ts +0 -24
  341. package/src/internal/bounded.ts +0 -25
  342. package/src/internal/brand.ts +0 -28
  343. package/src/internal/browser-origin-guard.ts +0 -67
  344. package/src/internal/builtin-tools/artifacts.ts +0 -62
  345. package/src/internal/builtin-tools/index.ts +0 -53
  346. package/src/internal/builtin-tools/reminders.ts +0 -145
  347. package/src/internal/channel-dispatch.ts +0 -129
  348. package/src/internal/channel-route-dispatch.ts +0 -58
  349. package/src/internal/channel-state.ts +0 -83
  350. package/src/internal/chat-client.ts +0 -594
  351. package/src/internal/cli-ax.ts +0 -2318
  352. package/src/internal/cli-bitbucket.ts +0 -438
  353. package/src/internal/cli-convert-automation.ts +0 -335
  354. package/src/internal/cli-cursor.ts +0 -282
  355. package/src/internal/cli-deploy.ts +0 -2519
  356. package/src/internal/cli-docs.ts +0 -192
  357. package/src/internal/cli-github.ts +0 -806
  358. package/src/internal/cli-gitlab.ts +0 -452
  359. package/src/internal/cli-images.ts +0 -672
  360. package/src/internal/cli-manifest.ts +0 -56
  361. package/src/internal/cli-mcp-oauth.ts +0 -396
  362. package/src/internal/cli-mcp.ts +0 -808
  363. package/src/internal/cli-skills.ts +0 -98
  364. package/src/internal/cli-slack-manual.ts +0 -418
  365. package/src/internal/cli-slack.ts +0 -874
  366. package/src/internal/cloud-merge.ts +0 -51
  367. package/src/internal/cloud-turn-cost.ts +0 -159
  368. package/src/internal/connection-env.ts +0 -106
  369. package/src/internal/continuation-channel.ts +0 -233
  370. package/src/internal/continuation-identity.ts +0 -935
  371. package/src/internal/conversation-mirror.ts +0 -423
  372. package/src/internal/convert-automation/convert-workflow.ts +0 -1377
  373. package/src/internal/convert-automation/slug.ts +0 -29
  374. package/src/internal/convert-automation/types.ts +0 -212
  375. package/src/internal/cron.ts +0 -76
  376. package/src/internal/cursor/account-mcp.ts +0 -692
  377. package/src/internal/cursor/backend-client.ts +0 -984
  378. package/src/internal/cursor/bound-secret-file.ts +0 -101
  379. package/src/internal/cursor/credentials.ts +0 -560
  380. package/src/internal/cursor/cursor-api-transport.ts +0 -65
  381. package/src/internal/cursor/github-credentials.ts +0 -228
  382. package/src/internal/cursor/hosted-bound-secrets.ts +0 -71
  383. package/src/internal/cursor/hosted-deployment-secrets.ts +0 -100
  384. package/src/internal/cursor/hosted-store-secrets.ts +0 -152
  385. package/src/internal/cursor/session-token.ts +0 -111
  386. package/src/internal/cursor/store-api-client.ts +0 -323
  387. package/src/internal/cursor-account-mcp-auth.ts +0 -303
  388. package/src/internal/cursor-agent-template.ts +0 -50
  389. package/src/internal/cursor-deployment-relay.ts +0 -315
  390. package/src/internal/cursor-event-relay.ts +0 -675
  391. package/src/internal/cursor-relay-core.ts +0 -563
  392. package/src/internal/deferred-channel-session.ts +0 -61
  393. package/src/internal/deploy-client.ts +0 -1574
  394. package/src/internal/deploy-manifest.ts +0 -361
  395. package/src/internal/deploy-source.ts +0 -144
  396. package/src/internal/describe-error.ts +0 -32
  397. package/src/internal/dev-mode.ts +0 -20
  398. package/src/internal/discovery/agent-config.ts +0 -288
  399. package/src/internal/discovery/agent.ts +0 -381
  400. package/src/internal/discovery/channels.ts +0 -100
  401. package/src/internal/discovery/connections.ts +0 -516
  402. package/src/internal/discovery/context.ts +0 -38
  403. package/src/internal/discovery/cursor-plugin.ts +0 -628
  404. package/src/internal/discovery/extension-overlay.ts +0 -247
  405. package/src/internal/discovery/extensions.ts +0 -490
  406. package/src/internal/discovery/fs.ts +0 -103
  407. package/src/internal/discovery/hooks.ts +0 -63
  408. package/src/internal/discovery/info.ts +0 -174
  409. package/src/internal/discovery/instructions.ts +0 -98
  410. package/src/internal/discovery/modules.ts +0 -107
  411. package/src/internal/discovery/names.ts +0 -45
  412. package/src/internal/discovery/project.ts +0 -319
  413. package/src/internal/discovery/reminders.ts +0 -65
  414. package/src/internal/discovery/runtime.ts +0 -158
  415. package/src/internal/discovery/schedules.ts +0 -120
  416. package/src/internal/discovery/seed-files.ts +0 -34
  417. package/src/internal/discovery/services.ts +0 -151
  418. package/src/internal/discovery/skills.ts +0 -174
  419. package/src/internal/discovery/subagent.ts +0 -138
  420. package/src/internal/discovery/tools.ts +0 -170
  421. package/src/internal/discovery.ts +0 -27
  422. package/src/internal/distribution.ts +0 -126
  423. package/src/internal/docs-site.ts +0 -248
  424. package/src/internal/duration.ts +0 -37
  425. package/src/internal/env-file.ts +0 -83
  426. package/src/internal/eval-judge-model.ts +0 -142
  427. package/src/internal/eval-run-store.ts +0 -585
  428. package/src/internal/eval-runner.ts +0 -823
  429. package/src/internal/evals-client.ts +0 -475
  430. package/src/internal/event-log.ts +0 -342
  431. package/src/internal/event-mapper.ts +0 -811
  432. package/src/internal/expected-delivery-failure.ts +0 -34
  433. package/src/internal/factory-resource-channel.ts +0 -103
  434. package/src/internal/filesystem/errors.ts +0 -207
  435. package/src/internal/filesystem/format.ts +0 -253
  436. package/src/internal/filesystem/merge.ts +0 -192
  437. package/src/internal/filesystem/nearest.ts +0 -112
  438. package/src/internal/filesystem/scratch.ts +0 -239
  439. package/src/internal/filesystem/sdk-schemas.ts +0 -204
  440. package/src/internal/filesystem/tools.ts +0 -804
  441. package/src/internal/filesystem/walk.ts +0 -366
  442. package/src/internal/fixtures/units-server.ts +0 -52
  443. package/src/internal/framework-file-storage.ts +0 -357
  444. package/src/internal/framework-storage-selection.ts +0 -156
  445. package/src/internal/frontmatter.ts +0 -108
  446. package/src/internal/github-fanout.ts +0 -144
  447. package/src/internal/guest-network.ts +0 -391
  448. package/src/internal/handleAgentServeTrigger.ts +0 -286
  449. package/src/internal/host-files.ts +0 -341
  450. package/src/internal/host-kv.ts +0 -81
  451. package/src/internal/host-otel.ts +0 -231
  452. package/src/internal/host-platforms.ts +0 -170
  453. package/src/internal/hosted-admission-adapter.ts +0 -47
  454. package/src/internal/hosted-admission-context.ts +0 -37
  455. package/src/internal/hosted-catch-protocol.ts +0 -112
  456. package/src/internal/hosted-catch.ts +0 -179
  457. package/src/internal/hosted-delivery-protocol.ts +0 -824
  458. package/src/internal/hosted-delivery.ts +0 -1410
  459. package/src/internal/hosted-execution-diag.ts +0 -291
  460. package/src/internal/hosted-execution-flush.ts +0 -136
  461. package/src/internal/hosted-execution-latency.ts +0 -301
  462. package/src/internal/hosted-managed-automation.ts +0 -116
  463. package/src/internal/hosting.ts +0 -114
  464. package/src/internal/http-channel-define.ts +0 -25
  465. package/src/internal/http-channel.ts +0 -669
  466. package/src/internal/http-control-plane-session.ts +0 -110
  467. package/src/internal/ids.ts +0 -39
  468. package/src/internal/incoming-headers-to-fetch.ts +0 -45
  469. package/src/internal/init-interview.ts +0 -360
  470. package/src/internal/init-project.ts +0 -711
  471. package/src/internal/init-scaffold.ts +0 -182
  472. package/src/internal/inline-stdio-define-stub.ts +0 -4
  473. package/src/internal/inline-stdio-mcp.ts +0 -230
  474. package/src/internal/install-cursor-skills.ts +0 -604
  475. package/src/internal/is-record.ts +0 -3
  476. package/src/internal/json-schema.ts +0 -95
  477. package/src/internal/legacy-command-guard.ts +0 -66
  478. package/src/internal/listen-port.ts +0 -85
  479. package/src/internal/local-control-plane.ts +0 -442
  480. package/src/internal/local-env.ts +0 -59
  481. package/src/internal/local-ingress.ts +0 -90
  482. package/src/internal/log-ring.ts +0 -59
  483. package/src/internal/logs-client.ts +0 -477
  484. package/src/internal/map-pool.ts +0 -39
  485. package/src/internal/mcp-endpoint.ts +0 -747
  486. package/src/internal/mcp-host.ts +0 -509
  487. package/src/internal/mcp-oauth-on-demand.ts +0 -337
  488. package/src/internal/mcp-oauth-persist.ts +0 -118
  489. package/src/internal/mcp-oauth.ts +0 -549
  490. package/src/internal/net.ts +0 -29
  491. package/src/internal/open-browser.ts +0 -41
  492. package/src/internal/otel/attrs.ts +0 -16
  493. package/src/internal/otel/collector.ts +0 -937
  494. package/src/internal/otel/config.ts +0 -287
  495. package/src/internal/otel/http.ts +0 -122
  496. package/src/internal/otel/mcp.ts +0 -56
  497. package/src/internal/otel/propagation.ts +0 -17
  498. package/src/internal/otel/runtime.ts +0 -441
  499. package/src/internal/package-inline-stdio.ts +0 -114
  500. package/src/internal/peer-connections.ts +0 -96
  501. package/src/internal/platform-schedule-sync.ts +0 -75
  502. package/src/internal/platform-timers.ts +0 -365
  503. package/src/internal/playground/static.ts +0 -278
  504. package/src/internal/playground/toolchain.ts +0 -270
  505. package/src/internal/playground-proxy.ts +0 -564
  506. package/src/internal/process-memory-telemetry.ts +0 -334
  507. package/src/internal/project-layout.ts +0 -31
  508. package/src/internal/prompt-context.ts +0 -116
  509. package/src/internal/reminder-control-plane-protocol.ts +0 -482
  510. package/src/internal/reminder-runner.ts +0 -1559
  511. package/src/internal/reminder-schedule.ts +0 -104
  512. package/src/internal/reminder-store.ts +0 -176
  513. package/src/internal/request-error-status.ts +0 -95
  514. package/src/internal/request-headers.ts +0 -21
  515. package/src/internal/resolve-prod-target.ts +0 -334
  516. package/src/internal/resolved-connections.ts +0 -190
  517. package/src/internal/router.ts +0 -143
  518. package/src/internal/run-client.ts +0 -354
  519. package/src/internal/runtime-dispatch-runner.ts +0 -50
  520. package/src/internal/sanitize-mcp-tool-name.ts +0 -31
  521. package/src/internal/schedule-runner.ts +0 -154
  522. package/src/internal/scm/pr-url.ts +0 -245
  523. package/src/internal/sdk-runner.ts +0 -959
  524. package/src/internal/serve-restart.ts +0 -113
  525. package/src/internal/server.ts +0 -3147
  526. package/src/internal/session-cost.ts +0 -208
  527. package/src/internal/session-engine.ts +0 -5622
  528. package/src/internal/session-links.ts +0 -84
  529. package/src/internal/session-run-log.ts +0 -535
  530. package/src/internal/session-store.ts +0 -307
  531. package/src/internal/session-urls.ts +0 -32
  532. package/src/internal/sessions-client.ts +0 -239
  533. package/src/internal/skill-catalog.ts +0 -66
  534. package/src/internal/skills-sync.ts +0 -55
  535. package/src/internal/slack-provision-client.ts +0 -544
  536. package/src/internal/slack-thread-key.ts +0 -11
  537. package/src/internal/sleep.ts +0 -3
  538. package/src/internal/start-log-store.ts +0 -164
  539. package/src/internal/steering.ts +0 -17
  540. package/src/internal/storage-coordinator.ts +0 -800
  541. package/src/internal/storage-roles.ts +0 -86
  542. package/src/internal/storage.ts +0 -1
  543. package/src/internal/store-api-protocol.ts +0 -205
  544. package/src/internal/store-mirror.ts +0 -73
  545. package/src/internal/stream-progress.ts +0 -294
  546. package/src/internal/terminal-style.ts +0 -133
  547. package/src/internal/testing/factory-resource-fixtures.ts +0 -84
  548. package/src/internal/testing/scm-continuation-fixtures.ts +0 -285
  549. package/src/internal/tool-catalog.ts +0 -104
  550. package/src/internal/tool-policy.ts +0 -222
  551. package/src/internal/tool-result.ts +0 -50
  552. package/src/internal/trajectory.ts +0 -418
  553. package/src/internal/turn-cost.ts +0 -136
  554. package/src/internal/turn-governor.ts +0 -145
  555. package/src/internal/turn-lease-store.ts +0 -144
  556. package/src/internal/turn-outbox.ts +0 -77
  557. package/src/internal/update-check.ts +0 -517
  558. package/src/internal/v2-hosting.ts +0 -52
  559. package/src/internal/workspace-sync.ts +0 -114
  560. package/src/internal/workspace.ts +0 -505
  561. package/src/kv/memory-kv.ts +0 -94
  562. package/src/kv.ts +0 -18
  563. package/src/memory.ts +0 -487
  564. package/src/otel-public.ts +0 -24
  565. package/src/otel.ts +0 -320
  566. package/src/prompt.ts +0 -80
  567. package/src/reminders.ts +0 -65
  568. package/src/schedules.ts +0 -31
  569. package/src/skills.ts +0 -27
  570. package/src/storage-backends/cursor-hosted-v2.ts +0 -204
  571. package/src/storage-backends/cursor-hosted.ts +0 -388
  572. package/src/storage-backends/file-kv.ts +0 -276
  573. package/src/storage-protocol.ts +0 -261
  574. package/src/storage.ts +0 -408
  575. package/src/tools.ts +0 -166
  576. package/src/types.ts +0 -3776
  577. /package/dist/docs/assets/{reference_agent-config.md.DzGI777n.lean.js → reference_agent-config.md.Cj5uBKp4.lean.js} +0 -0
@@ -1,1212 +0,0 @@
1
- /**
2
- * Cursor-managed durable files for hosted agents — the backend that puts
3
- * `ctx.host.files` bytes in an Agent Store on S3.
4
- *
5
- * One credential throughout: the engine's pod `CURSOR_API_KEY`. It authorizes
6
- * every call, and the bytes then move between this process and presigned S3
7
- * URLs. File contents never transit the control plane, so a large artifact
8
- * costs S3 bandwidth rather than backend memory.
9
- *
10
- * By default everything goes through the public API under
11
- * `/v0/agent-serve-files`, the same surface any API-key holder gets — a
12
- * self-hosted deployment, a CI job, or a customer script can drive the same
13
- * store with the same requests. Cursor-managed v2 pods can instead be pinned
14
- * (per deployment, at task-time bind) onto the engine-facing factory store API
15
- * under `/internal/agentsdk/store`: same host, same credential, same stores
16
- * and S3 objects, but flush-shaped — one control-plane request settles a whole
17
- * `putMany` batch or a conditional delete, where `/v0` costs a presign POST
18
- * plus stat GETs and a delete POST. The pin fails safe: an absent or
19
- * unrecognized protocol value, or a source id that maps to neither deployment
20
- * store role, keeps `/v0`.
21
- *
22
- * Two things follow. There is no Agent Store token: a token narrows an identity
23
- * to one store and one capability, which is what a caller that cannot hold a
24
- * credential needs (an in-pod FUSE daemon). This process holds the credential
25
- * the token would be minted from, so a token bought a round trip, an expiry to
26
- * manage, and a second auth path, and no privilege reduction. And there is no
27
- * direct call to the store service: the API layer owns the policy, and the
28
- * engine never addresses it.
29
- *
30
- * There is also no store lookup. Every request names this deployment's file
31
- * store by the `agentStoreSourceId` the deploy pipeline pinned into the pod
32
- * env as `AGENT_SERVE_STORE_SOURCE_ID` — provisioning the store at the same
33
- * time, so by boot the store exists and the id resolves. The id is an
34
- * address, not a credential: the server maps it to a store and authorizes
35
- * every call against the deployment's own live service account, so holding
36
- * it grants nothing, and this process never learns a store id it could use
37
- * to ask for anyone else's.
38
- *
39
- * Locally, point `CURSOR_API_BASE_URL` at a backend that serves
40
- * `/v0/agent-serve-files` and use a deployment SA key; off hosting the
41
- * local-disk sink is the default instead.
42
- *
43
- * Writes are conditional. The store rejects unconditional writes, so every
44
- * `put` first learns the current etag: `new` for a path we believe is absent,
45
- * `etag` to replace what we last observed. A raced write comes back 412 and
46
- * surfaces as {@link FileConflictError}, which is why this backend reports
47
- * `etags: true` and `host.files.capabilities.conflictDetection` is honestly
48
- * true.
49
- */
50
-
51
- import { createHash } from "node:crypto";
52
- import {
53
- FileConflictError,
54
- type FileSink,
55
- type FileSinkBody,
56
- type FileSinkEntry,
57
- type FileSinkPutResult,
58
- } from "../files.js";
59
- import { mapBounded } from "../internal/bounded.js";
60
- import {
61
- type CursorApiTransportOptions,
62
- resolveCursorApiBaseUrl,
63
- resolveCursorApiFetch,
64
- resolveCursorApiKey,
65
- } from "../internal/cursor/cursor-api-transport.js";
66
- import {
67
- resolveDeploymentStoreSourceIdSync,
68
- resolveFrameworkStoreSourceIdSync,
69
- resolveStoreProtocolSync,
70
- storeProtocolEnablesFactoryFlush,
71
- } from "../internal/cursor/hosted-store-secrets.js";
72
- import { createAgentStorePresignedUrlValidatorForBackend } from "./agent-store-presigned-url.js";
73
-
74
- const AGENT_SERVE_FILES_PATH = "/v0/agent-serve-files";
75
-
76
- /**
77
- * Engine-facing store API on the factory service — same host, different path
78
- * family. Selected per pod by the task-bound protocol pin
79
- * ({@link resolveStoreProtocolSync}); the server resolves the deployment and
80
- * its stores from the pod credential, so requests carry a store *role*
81
- * (`deployment` | `framework`), never a store id. One flush request replaces
82
- * the presign POST plus the stat GETs and delete POST a turn costs on `/v0`.
83
- */
84
- const AGENTSDK_STORE_PATH = "/internal/agentsdk/store";
85
-
86
- /**
87
- * Pinned by the deploy pipeline for every Cursor-managed deployment — which
88
- * provisions the file store it names in the same step — and reserved from
89
- * user secrets. Its presence is the signal that a durable store exists and
90
- * the hosted control plane is reachable; a deployment without one (no
91
- * service account yet) has no store, and this backend is not selected.
92
- */
93
- export const AGENT_SERVE_STORE_SOURCE_ID_ENV_VAR = "AGENT_SERVE_STORE_SOURCE_ID";
94
-
95
- /**
96
- * The deployment's **framework-level** file store, distinct from the
97
- * user-level store above. The user store carries author-facing bytes
98
- * (`ctx.host.files`, skills, memory, artifacts) and is browsable and
99
- * mountable; the framework store carries engine-owned session state —
100
- * session records, event transcripts, hosted-execution run logs,
101
- * continuation index, and the SDK conversation mirror — and is never
102
- * exposed to authors or models.
103
- * Provisioned by the v2 deploy pipeline alongside the user store; v1
104
- * deployments have only the user store.
105
- */
106
- export const AGENT_SERVE_FRAMEWORK_STORE_SOURCE_ID_ENV_VAR =
107
- "AGENT_SERVE_FRAMEWORK_STORE_SOURCE_ID";
108
-
109
- /** Cap on the per-process etag cache; a miss costs a lookup, not correctness. */
110
- const MAX_REMEMBERED_ETAGS = 1_000;
111
-
112
- /** Bounded directory page for point lookups. */
113
- const DIRECTORY_PAGE_SIZE = 1_000;
114
- /**
115
- * Paths per presign call in {@link FileSink.getMany}, matching the factory
116
- * `/read` route's server-side `MAX_READ_PATHS`. The whole point of the batch:
117
- * the factory wire's per-deployment rate limiter counts requests, not files,
118
- * so a restore that used to spend N `/read` calls now spends ceil(N/100).
119
- */
120
- const READ_PRESIGN_BATCH_MAX = 100;
121
- /**
122
- * Concurrent presigned-URL downloads inside one {@link FileSink.getMany}
123
- * batch — the S3 fan-out, not the control-plane call count. Matches the
124
- * conversation mirror's historical `HYDRATE_DOWNLOAD_CONCURRENCY`.
125
- */
126
- const GET_MANY_DOWNLOAD_CONCURRENCY = 8;
127
- /** Stop paging a pathological directory rather than scanning forever. */
128
- const MAX_DIRECTORY_PAGES = 20;
129
- /**
130
- * Total page budget for a full `list()` walk of a store the unscoped listing
131
- * could not carry (~1M entries at the page size above). Past it, fail loudly:
132
- * a silently partial listing would be mistaken for the whole scope.
133
- */
134
- const MAX_LIST_WALK_PAGES = 1_000;
135
-
136
- /**
137
- * True on Cursor-managed hosting with a provisioned file store.
138
- *
139
- * Keyed on the store source id alone, deliberately: the credential is not part
140
- * of the test. If it were, a missing or regressed `CURSOR_API_KEY` would
141
- * silently select pod-local disk on a hosted pod and accept writes that vanish
142
- * with the pod. Instead the store is detected, and a missing credential fails
143
- * loudly when the sink is used.
144
- */
145
- export function isCursorHostedFilesAvailable(env: NodeJS.ProcessEnv = process.env): boolean {
146
- return resolveDeploymentStoreSourceIdSync(env) !== undefined;
147
- }
148
-
149
- /**
150
- * Local `serve`/`run` with a personal `CURSOR_API_KEY` can address
151
- * `/v0/agent-serve-files/user`. Hosted deployments have a store source id
152
- * and must not take this path — they own the deployment store instead.
153
- */
154
- export function isCursorUserStoreFilesAvailable(env: NodeJS.ProcessEnv = process.env): boolean {
155
- if (isCursorHostedFilesAvailable(env)) {
156
- return false;
157
- }
158
- // Vitest inherits the developer's shell. User-store sync writes durable
159
- // keys; skip it in the test runner so `pnpm test` cannot poison
160
- // `agent-serve/<agent>/skills/` on the caller's USER store.
161
- if (env.VITEST === "true") {
162
- return false;
163
- }
164
- const key = env.CURSOR_API_KEY?.trim();
165
- return key !== undefined && key !== "";
166
- }
167
-
168
- /**
169
- * Base URL / credential / fetch resolution is the shared
170
- * {@link CursorApiTransportOptions} (`internal/cursor/cursor-api-transport.ts`),
171
- * so this sink and the domain-lane store API client resolve identically.
172
- */
173
- export interface CursorHostedFilesOptions extends CursorApiTransportOptions {
174
- /** File store to address (default: `AGENT_SERVE_STORE_SOURCE_ID`). */
175
- storeSourceId?: string;
176
- /**
177
- * Lazy store source id, re-resolved on every call and preferred over
178
- * {@link storeSourceId} — freeze bind drops ids into `/run/cursor/secrets`
179
- * after serve start without restarting July.
180
- */
181
- resolveStoreSourceId?: () => string | undefined;
182
- /**
183
- * Lazy store protocol pin (default: env, then the task-bound file). When it
184
- * enables the factory flush wire (`factory-flush-v1`, or the superset
185
- * domain-API pin `factory-api-v1`) and the source id maps to a deployment
186
- * store role, requests ride the factory store API instead of
187
- * `/v0/agent-serve-files`. Any other value keeps `/v0`.
188
- */
189
- resolveStoreProtocol?: () => string | undefined;
190
- }
191
-
192
- /**
193
- * What a file operation needs: where this deployment's files live, and which
194
- * wire reaches them. `v0` addresses the public API by store source id; `flush`
195
- * addresses the factory store API by role and lets the server resolve.
196
- */
197
- type FilesTarget =
198
- | { wire: "v0"; path(suffix?: string): string }
199
- | { wire: "flush"; store: "deployment" | "framework" };
200
-
201
- /**
202
- * Wire shapes from `/v0/agent-serve-files`. Declared loosely on purpose: this is a
203
- * versioned public API, and a client that hard-required every field would break
204
- * on an additive change it could otherwise ignore.
205
- */
206
- interface ApiFile {
207
- path?: string;
208
- sizeBytes?: number;
209
- etag?: string;
210
- updatedAt?: string;
211
- }
212
-
213
- interface ApiFileListing {
214
- files?: ApiFile[];
215
- complete?: boolean;
216
- /** Immediate subdirectories; only returned by the `?path=` form. */
217
- directories?: string[];
218
- nextPageToken?: string;
219
- }
220
-
221
- interface ApiPresignResponse {
222
- files?: Array<{
223
- path?: string;
224
- url?: string;
225
- headers?: Record<string, string>;
226
- }>;
227
- }
228
-
229
- interface ApiDeleteResponse {
230
- files?: Array<{ path?: string; status?: string; etag?: string }>;
231
- }
232
-
233
- /**
234
- * Per-op outcome from the factory store API's `flush` route, index-aligned
235
- * with the request ops. A put answers `url`/`headers`; a delete answers
236
- * `status`; declared loosely for the same additive-change reason as the
237
- * `/v0` shapes above.
238
- */
239
- interface FlushWireOpOutcome {
240
- path?: string;
241
- url?: string;
242
- headers?: Record<string, string>;
243
- error?: string;
244
- status?: string;
245
- etag?: string;
246
- }
247
-
248
- interface FlushWireResponse {
249
- ops?: FlushWireOpOutcome[];
250
- }
251
-
252
- type HostedFilesMethods = Omit<FileSink, "get" | "getMany"> & {
253
- /** Narrower than {@link FileSink.get}: this backend always reports the etag. */
254
- get(key: string): Promise<FileSinkBody | undefined>;
255
- /** Always present here (and narrowed like {@link get}); optional on {@link FileSink}. */
256
- getMany(keys: readonly string[]): Promise<Array<FileSinkBody | undefined>>;
257
- };
258
-
259
- /**
260
- * Cursor-hosted {@link FileSink}, selected on Cursor-managed hosting.
261
- *
262
- * Stateless apart from the etag cache below: every request names this
263
- * deployment's store by its pinned source id, so there is nothing to
264
- * resolve, hold, or invalidate.
265
- */
266
- export function cursorHostedFiles(options: CursorHostedFilesOptions = {}): HostedFilesMethods {
267
- /**
268
- * Etags observed per key, so a replace can carry a precondition without
269
- * re-reading. Bounded: a resident engine is long-lived and touches an
270
- * unbounded number of keys over its lifetime. Oldest-out is fine — a miss
271
- * costs one lookup, not correctness.
272
- */
273
- const observedEtags = new Map<string, string>();
274
- const rememberEtag = (key: string, etag: string): void => {
275
- if (observedEtags.size >= MAX_REMEMBERED_ETAGS) {
276
- const oldest = observedEtags.keys().next();
277
- if (!oldest.done) {
278
- observedEtags.delete(oldest.value);
279
- }
280
- }
281
- observedEtags.set(key, etag);
282
- };
283
-
284
- /**
285
- * Run `call` against this deployment's files.
286
- *
287
- * No network call and nothing cached: the store source id and protocol pin
288
- * come from the environment (or task-bound files) and the server maps them
289
- * on every request. Resolved per call rather than once at construction so a
290
- * misconfigured environment fails where the operation is, not where the
291
- * sink was created — and so a protocol pin bound after serve start takes
292
- * effect without a restart.
293
- */
294
- async function withFiles<T>(call: (files: FilesTarget) => Promise<T>): Promise<T> {
295
- const sourceId = resolveStoreSourceId(options);
296
- const protocol = options.resolveStoreProtocol?.() ?? resolveStoreProtocolSync();
297
- if (storeProtocolEnablesFactoryFlush(protocol)) {
298
- // The factory API addresses stores by role, so map the pinned source id
299
- // back to the role it was pinned from. A source id matching neither (a
300
- // local-dev override) has no role there and stays on `/v0`, as does any
301
- // protocol value this runtime does not recognize.
302
- if (sourceId === resolveFrameworkStoreSourceIdSync()) {
303
- return await call({ wire: "flush", store: "framework" });
304
- }
305
- if (sourceId === resolveDeploymentStoreSourceIdSync()) {
306
- return await call({ wire: "flush", store: "deployment" });
307
- }
308
- }
309
- return await call({
310
- wire: "v0",
311
- path: (suffix = "") => `/${encodeURIComponent(sourceId)}${suffix}`,
312
- });
313
- }
314
-
315
- /**
316
- * Fetch one presigned read URL, shared by `get` and `getMany`. A 404 on
317
- * the object GET is absence (a delete raced the presign).
318
- *
319
- * A 403 here is deliberately *not* absence. Absence was already settled
320
- * by the presign: the store refuses to presign a path with no live
321
- * object, so reaching this point means the object existed moments ago.
322
- * What produces a 403 after a good presign is an expired or clock-skewed
323
- * signature, a policy change, or a delete that raced the GET — all
324
- * transient or retryable, none of them "definitively gone".
325
- *
326
- * Reporting them as absence is how a store blip becomes data loss:
327
- * `undefined` on a continuation read is taken as "unknown token", and
328
- * the engine mints a fresh session under it, forking the thread. The
329
- * delete race resolves itself on retry, where the presign returns 404
330
- * and this correctly reads as a miss.
331
- */
332
- async function downloadPresigned(key: string, url: string): Promise<FileSinkBody | undefined> {
333
- const response = await resolveCursorApiFetch(options)(assertPresignedUrlSafe(options, url), {
334
- method: "GET",
335
- redirect: "manual",
336
- });
337
- if (response.status === 404) {
338
- return undefined;
339
- }
340
- if (!response.ok) {
341
- throw new Error(`cursorHostedFiles: download of "${key}" failed (HTTP ${response.status})`);
342
- }
343
- const etag = normalizeEtag(response.headers.get("etag"));
344
- if (etag !== undefined) {
345
- rememberEtag(key, etag);
346
- }
347
- return {
348
- body: new Uint8Array(await response.arrayBuffer()),
349
- ...(etag === undefined ? {} : { etag }),
350
- };
351
- }
352
-
353
- /** One page of the paginated per-directory listing. */
354
- async function directoryPage(
355
- files: FilesTarget,
356
- directory: string,
357
- pageToken: string | undefined,
358
- ): Promise<ApiFileListing> {
359
- if (files.wire === "flush") {
360
- return await flushPost<ApiFileListing>(options, "/list", {
361
- store: files.store,
362
- path: directory,
363
- limit: DIRECTORY_PAGE_SIZE,
364
- ...(pageToken === undefined ? {} : { pageToken }),
365
- });
366
- }
367
- const query = new URLSearchParams({
368
- path: directory,
369
- limit: String(DIRECTORY_PAGE_SIZE),
370
- });
371
- if (pageToken !== undefined) {
372
- query.set("pageToken", pageToken);
373
- }
374
- return await storeGet<ApiFileListing>(options, files.path(`?${query.toString()}`));
375
- }
376
-
377
- /** The unscoped whole-store listing, on whichever wire the target rides. */
378
- async function unscopedListing(files: FilesTarget): Promise<ApiFileListing> {
379
- if (files.wire === "flush") {
380
- return await flushPost<ApiFileListing>(options, "/list", {
381
- store: files.store,
382
- });
383
- }
384
- return await storeGet<ApiFileListing>(options, files.path());
385
- }
386
-
387
- async function listEntries(prefix: string): Promise<FileSinkEntry[]> {
388
- return await withFiles(async files => {
389
- const raw = await unscopedListing(files);
390
- if (raw.complete === true) {
391
- const out: FileSinkEntry[] = [];
392
- for (const file of raw.files ?? []) {
393
- if (typeof file.path !== "string" || !file.path.startsWith(prefix)) {
394
- continue;
395
- }
396
- out.push(toEntry(file, file.path));
397
- }
398
- // The FileSink contract promises key order, and transcript-chunk
399
- // consumers depend on it (chunk keys are zero-padded so key order
400
- // is append order). The server happens to scan in order today; do
401
- // not let that be the only thing holding the contract.
402
- return out.sort((a, b) => (a.key < b.key ? -1 : a.key > b.key ? 1 : 0));
403
- }
404
- // The unscoped listing was truncated (`complete: false`) and has no
405
- // continuation to follow. `list()` promises the whole scope, so rather
406
- // than lie or fail, walk the paginated per-directory form, which does
407
- // paginate and reports immediate subdirectories to recurse into.
408
- return await walkDirectoryTree(files, prefix);
409
- });
410
- }
411
-
412
- /**
413
- * Every entry under `prefix`, via the paginated directory listing.
414
- *
415
- * Starts at the deepest directory the prefix fully names and descends only
416
- * into subdirectories that can still hold matches. Bounded so a runaway
417
- * store fails loudly rather than returning a silently partial answer the
418
- * caller would mistake for the whole scope.
419
- */
420
- async function walkDirectoryTree(files: FilesTarget, prefix: string): Promise<FileSinkEntry[]> {
421
- const slash = prefix.lastIndexOf("/");
422
- const pending: string[] = [slash === -1 ? "" : prefix.slice(0, slash)];
423
- const queued = new Set<string>(pending);
424
- const out: FileSinkEntry[] = [];
425
- let pageBudget = MAX_LIST_WALK_PAGES;
426
- while (pending.length > 0) {
427
- const directory = pending.pop() as string;
428
- let pageToken: string | undefined;
429
- do {
430
- if (pageBudget <= 0) {
431
- throw new Error(
432
- "cursorHostedFiles: the store is too large to list; scope the listing with a narrower prefix",
433
- );
434
- }
435
- pageBudget -= 1;
436
- const page = await directoryPage(files, directory, pageToken);
437
- for (const file of page.files ?? []) {
438
- if (typeof file.path === "string" && file.path.startsWith(prefix)) {
439
- out.push(toEntry(file, file.path));
440
- }
441
- }
442
- // Every page can surface different subdirectories, so collect from
443
- // all of them, deduped — and descend only into branches that can
444
- // still hold matches: a descendant of the prefix, or (at the first
445
- // level) a directory the prefix's basename component narrows to.
446
- for (const subdir of page.directories ?? []) {
447
- if (queued.has(subdir)) {
448
- continue;
449
- }
450
- if (`${subdir}/`.startsWith(prefix) || prefix.startsWith(`${subdir}/`)) {
451
- queued.add(subdir);
452
- pending.push(subdir);
453
- }
454
- }
455
- pageToken =
456
- page.complete === false && page.nextPageToken !== undefined
457
- ? page.nextPageToken
458
- : undefined;
459
- } while (pageToken !== undefined);
460
- }
461
- return out.sort((a, b) => (a.key < b.key ? -1 : a.key > b.key ? 1 : 0));
462
- }
463
-
464
- /** Shared decode: remember the etag as a side effect, then shape the entry. */
465
- function toEntry(file: ApiFile, key: string): FileSinkEntry {
466
- if (typeof file.etag === "string" && file.etag !== "") {
467
- rememberEtag(key, file.etag);
468
- }
469
- const mtimeMs = typeof file.updatedAt === "string" ? Date.parse(file.updatedAt) : undefined;
470
- return {
471
- key,
472
- size: typeof file.sizeBytes === "number" ? file.sizeBytes : 0,
473
- ...(typeof file.etag === "string" && file.etag !== "" ? { etag: file.etag } : {}),
474
- ...(mtimeMs === undefined || Number.isNaN(mtimeMs) ? {} : { mtimeMs }),
475
- };
476
- }
477
-
478
- /**
479
- * One key's current entry, via the bounded directory listing.
480
- *
481
- * The unscoped listing has no prefix parameter, so using it for a point lookup
482
- * would walk the whole store on the hot write path and cost more the fuller
483
- * the store got. `?path=` is scoped to one directory and paginates, and the
484
- * scan stops as soon as the key appears.
485
- */
486
- async function statKey(key: string): Promise<FileSinkEntry | undefined> {
487
- const slash = key.lastIndexOf("/");
488
- const directory = slash === -1 ? "" : key.slice(0, slash);
489
- return await withFiles(async files => {
490
- let pageToken: string | undefined;
491
- for (let page = 0; page < MAX_DIRECTORY_PAGES; page += 1) {
492
- const raw = await directoryPage(files, directory, pageToken);
493
- for (const file of raw.files ?? []) {
494
- if (file.path === key) {
495
- return toEntry(file, key);
496
- }
497
- }
498
- // `complete` is false exactly when another page is waiting.
499
- if (raw.complete !== false || raw.nextPageToken === undefined) {
500
- return undefined;
501
- }
502
- pageToken = raw.nextPageToken;
503
- }
504
- return undefined;
505
- });
506
- }
507
-
508
- /** One key's share of a (possibly multi-file) upload attempt. */
509
- type UploadSpec = {
510
- key: string;
511
- body: Uint8Array;
512
- precondition: { etag: string } | { new: true };
513
- };
514
- type UploadOutcome =
515
- | { status: "ok"; result: FileSinkPutResult }
516
- | { status: "conflict" }
517
- | { status: "error"; error: unknown };
518
-
519
- /**
520
- * The shared presign for one upload attempt: a `/v0` upload POST, or one
521
- * flush request whose put outcomes are reshaped into the same instruction
522
- * list (an outcome without a `url` — an oversize refusal — surfaces exactly
523
- * like a `/v0` multipart answer: no single-part instruction for that key).
524
- */
525
- async function presignUploads(
526
- files: FilesTarget,
527
- specs: UploadSpec[],
528
- ): Promise<ApiPresignResponse> {
529
- if (files.wire === "flush") {
530
- const response = await flushPost<FlushWireResponse>(options, "/flush", {
531
- store: files.store,
532
- ops: specs.map(spec => ({
533
- op: "put",
534
- path: spec.key,
535
- sha: createHash("sha256").update(spec.body).digest("hex"),
536
- size: spec.body.byteLength,
537
- ...spec.precondition,
538
- })),
539
- });
540
- // Index-aligned with the request ops, entries without a `url` included:
541
- // a refused op (oversize) must keep its slot so the by-path/positional
542
- // instruction matching below cannot hand its sibling's presign to it,
543
- // and the missing url surfaces as that key's own error.
544
- return {
545
- files: (response.ops ?? []).map(op => ({
546
- path: op.path,
547
- ...(typeof op.url === "string" && op.url !== "" ? { url: op.url } : {}),
548
- headers: op.headers,
549
- })),
550
- };
551
- }
552
- return await storePost<ApiPresignResponse>(options, files.path("/upload"), {
553
- files: specs.map(spec => ({
554
- path: spec.key,
555
- sha: createHash("sha256").update(spec.body).digest("hex"),
556
- size: spec.body.byteLength,
557
- ...spec.precondition,
558
- })),
559
- });
560
- }
561
-
562
- /**
563
- * One presign call covering every spec, then parallel presigned PUTs.
564
- * Outcomes are per spec: a 412 ("conflict") or a failed PUT for one key
565
- * never disturbs its batch siblings. Only the presign itself is shared —
566
- * a presign error throws and fails the whole attempt.
567
- */
568
- async function attemptUploads(files: FilesTarget, specs: UploadSpec[]): Promise<UploadOutcome[]> {
569
- const presigned = await presignUploads(files, specs);
570
- return await Promise.all(
571
- specs.map(async (spec, index): Promise<UploadOutcome> => {
572
- // Match by path, falling back to position for servers that answer
573
- // in request order without echoing the path.
574
- const instruction =
575
- presigned.files?.find(file => file.path === spec.key) ?? presigned.files?.[index];
576
- if (instruction?.url === undefined) {
577
- // Files past the single-part threshold come back with a multipart
578
- // plan instead of a url; this backend does not implement multipart.
579
- return {
580
- status: "error",
581
- error: new Error(
582
- `cursorHostedFiles: no single-part upload url for "${spec.key}" (too large?)`,
583
- ),
584
- };
585
- }
586
- try {
587
- const response = await resolveCursorApiFetch(options)(
588
- assertPresignedUrlSafe(options, instruction.url),
589
- {
590
- method: "PUT",
591
- headers: instruction.headers ?? {},
592
- // A view over bytes is a valid fetch body; the DOM lib types
593
- // are narrower than the runtime, and copying into a Blob would
594
- // double the memory for every upload.
595
- body: spec.body as unknown as BodyInit,
596
- // A redirect would re-send the body somewhere the presign did
597
- // not authorize.
598
- redirect: "manual",
599
- },
600
- );
601
- if (response.status === 412) {
602
- observedEtags.delete(spec.key);
603
- return { status: "conflict" };
604
- }
605
- if (!response.ok) {
606
- return {
607
- status: "error",
608
- error: new Error(
609
- `cursorHostedFiles: upload of "${spec.key}" failed (HTTP ${response.status})`,
610
- ),
611
- };
612
- }
613
- const etag = normalizeEtag(response.headers.get("etag"));
614
- if (etag !== undefined) {
615
- rememberEtag(spec.key, etag);
616
- }
617
- return { status: "ok", result: etag === undefined ? {} : { etag } };
618
- } catch (error) {
619
- return { status: "error", error };
620
- }
621
- }),
622
- );
623
- }
624
-
625
- /**
626
- * The full single-key write flow: precondition resolution, one shared
627
- * (already-performed) first attempt outcome, and the conflict handling
628
- * `put` promises — including the one statKey-and-retry when an optimistic
629
- * create loses to an existing object.
630
- */
631
- async function settlePut(
632
- files: FilesTarget,
633
- entry: {
634
- key: string;
635
- body: Uint8Array;
636
- options?: { ifMatch?: string | null };
637
- cachedEtagUsed?: boolean;
638
- staleEtag?: string;
639
- },
640
- first: UploadOutcome,
641
- ): Promise<FileSinkPutResult> {
642
- if (first.status === "error") {
643
- throw first.error;
644
- }
645
- if (first.status === "ok") {
646
- return first.result;
647
- }
648
- // Conflict. Explicit `ifMatch` (etag or create-only) and cached-etag
649
- // writes are single-attempt: the lost precondition is the caller's.
650
- const requested = entry.options?.ifMatch;
651
- if (requested !== undefined) {
652
- throw new FileConflictError(entry.key, {
653
- storedEtag: requested ?? undefined,
654
- });
655
- }
656
- if (entry.cachedEtagUsed === true) {
657
- // Someone else wrote between our etag observation and this PUT.
658
- throw new FileConflictError(entry.key, { storedEtag: entry.staleEtag });
659
- }
660
- // The optimistic create lost: learn the current etag once and replace.
661
- const found = await statKey(entry.key);
662
- if (found?.etag === undefined) {
663
- // Existed a moment ago (the create lost) but stat sees nothing: a
664
- // delete raced this write. Surface the conflict; a retry from the
665
- // caller lands on the fresh create-only path.
666
- throw new FileConflictError(entry.key, {});
667
- }
668
- const [replaced] = await attemptUploads(files, [
669
- { key: entry.key, body: entry.body, precondition: { etag: found.etag } },
670
- ]);
671
- if (replaced === undefined || replaced.status === "conflict") {
672
- throw new FileConflictError(entry.key, { storedEtag: found?.etag });
673
- }
674
- if (replaced.status === "error") {
675
- throw replaced.error;
676
- }
677
- return replaced.result;
678
- }
679
-
680
- /**
681
- * Precondition for a write, and whether it came from the etag cache (a
682
- * cached-etag conflict is terminal; an optimistic-create conflict earns
683
- * one stat-and-retry).
684
- */
685
- function preconditionFor(entry: { key: string; options?: { ifMatch?: string | null } }): {
686
- precondition: { etag: string } | { new: true };
687
- cachedEtagUsed: boolean;
688
- staleEtag?: string;
689
- } {
690
- const requested = entry.options?.ifMatch;
691
- // The store rejects unconditional writes, so every put carries a
692
- // precondition. `null` is create-only and an explicit etag is a caller
693
- // assertion.
694
- if (requested !== undefined) {
695
- return {
696
- precondition: requested === null ? { new: true } : { etag: requested },
697
- cachedEtagUsed: false,
698
- };
699
- }
700
- // Unconditional write with a known etag: replace what we saw last.
701
- const cached = observedEtags.get(entry.key);
702
- if (cached !== undefined) {
703
- return {
704
- precondition: { etag: cached },
705
- cachedEtagUsed: true,
706
- staleEtag: cached,
707
- };
708
- }
709
- // Unconditional write to a key this process has never observed:
710
- // create-only first, learning the current etag only on conflict. Most
711
- // such writes are brand-new keys (session-event chunks mint a fresh key
712
- // per flush), and the old stat-before-every-put shape made the
713
- // precondition lookup the single largest source of `agent-serve-files`
714
- // list traffic — every fresh pod re-listed a directory per key before
715
- // its first write.
716
- return { precondition: { new: true }, cachedEtagUsed: false };
717
- }
718
-
719
- return {
720
- // No local copy: bytes live in the store, so `materializedPath()` is
721
- // `undefined` and the harness reaches files through `host.files`.
722
- capabilities: { etags: true },
723
-
724
- async put(key, body, putOptions): Promise<FileSinkPutResult> {
725
- return await withFiles(async files => {
726
- const entry = { key, body, options: putOptions };
727
- const resolved = preconditionFor(entry);
728
- const [first] = await attemptUploads(files, [
729
- { key, body, precondition: resolved.precondition },
730
- ]);
731
- if (first === undefined) {
732
- throw new Error(`cursorHostedFiles: upload of "${key}" vanished`);
733
- }
734
- return await settlePut(
735
- files,
736
- {
737
- ...entry,
738
- cachedEtagUsed: resolved.cachedEtagUsed,
739
- staleEtag: resolved.staleEtag,
740
- },
741
- first,
742
- );
743
- });
744
- },
745
-
746
- async putMany(entries) {
747
- if (entries.length === 0) {
748
- return [];
749
- }
750
- return await withFiles(async files => {
751
- const resolved = entries.map(entry => ({
752
- entry,
753
- ...preconditionFor(entry),
754
- }));
755
- let firsts: UploadOutcome[];
756
- try {
757
- // One presign call for the whole batch — the point of putMany.
758
- firsts = await attemptUploads(
759
- files,
760
- resolved.map(({ entry, precondition }) => ({
761
- key: entry.key,
762
- body: entry.body,
763
- precondition,
764
- })),
765
- );
766
- } catch (error) {
767
- // The shared presign failed: every entry failed the same way.
768
- return entries.map(() => ({
769
- status: "rejected" as const,
770
- reason: error,
771
- }));
772
- }
773
- return await Promise.all(
774
- resolved.map(
775
- async (
776
- { entry, cachedEtagUsed, staleEtag },
777
- index,
778
- ): Promise<PromiseSettledResult<FileSinkPutResult>> => {
779
- const first = firsts[index];
780
- if (first === undefined) {
781
- return {
782
- status: "rejected",
783
- reason: new Error(`cursorHostedFiles: upload of "${entry.key}" vanished`),
784
- };
785
- }
786
- try {
787
- return {
788
- status: "fulfilled",
789
- value: await settlePut(files, { ...entry, cachedEtagUsed, staleEtag }, first),
790
- };
791
- } catch (error) {
792
- return { status: "rejected", reason: error };
793
- }
794
- },
795
- ),
796
- );
797
- });
798
- },
799
-
800
- async get(key): Promise<FileSinkBody | undefined> {
801
- return await withFiles(async files => {
802
- // Presigning a read of a path the store has no live object for — never
803
- // written, or deleted and now holding a tombstone — is a 404 on `/v0`
804
- // and an absent entry on the flush wire. Either is absence, which
805
- // `read()` reports as undefined, not a failure.
806
- let presigned: ApiPresignResponse;
807
- try {
808
- presigned =
809
- files.wire === "flush"
810
- ? await flushPost<ApiPresignResponse>(options, "/read", {
811
- store: files.store,
812
- paths: [key],
813
- })
814
- : await storePost<ApiPresignResponse>(options, files.path("/download"), {
815
- paths: [key],
816
- });
817
- } catch (error) {
818
- if (error instanceof StoreApiError && error.status === 404) {
819
- return undefined;
820
- }
821
- throw error;
822
- }
823
- const url = presigned.files?.[0]?.url;
824
- if (url === undefined) {
825
- return undefined;
826
- }
827
- return await downloadPresigned(key, url);
828
- });
829
- },
830
-
831
- async getMany(keys): Promise<Array<FileSinkBody | undefined>> {
832
- if (keys.length === 0) {
833
- return [];
834
- }
835
- const bodies = await withFiles(async files => {
836
- if (files.wire !== "flush") {
837
- // `/v0` presigns 404 the whole batch when any path is absent, so a
838
- // batched call cannot tell one missing key from a failed request.
839
- // Bounded per-key gets keep the `/v0` absence semantics exact; the
840
- // batch win belongs to the flush wire, whose contract is "an absent
841
- // path is an absent entry" — and whose per-deployment rate limiter
842
- // is the reason to spend fewer requests in the first place.
843
- return await mapBounded([...keys], GET_MANY_DOWNLOAD_CONCURRENCY, async key => {
844
- const presigned = await storePost<ApiPresignResponse>(
845
- options,
846
- files.path("/download"),
847
- { paths: [key] },
848
- ).catch((error: unknown) => {
849
- if (error instanceof StoreApiError && error.status === 404) {
850
- return undefined;
851
- }
852
- throw error;
853
- });
854
- const url = presigned?.files?.[0]?.url;
855
- if (url === undefined) {
856
- return undefined;
857
- }
858
- return await downloadPresigned(key, url);
859
- });
860
- }
861
- const urls = new Map<string, string>();
862
- for (let from = 0; from < keys.length; from += READ_PRESIGN_BATCH_MAX) {
863
- const chunk = keys.slice(from, from + READ_PRESIGN_BATCH_MAX);
864
- let presigned: ApiPresignResponse;
865
- try {
866
- presigned = await flushPost<ApiPresignResponse>(options, "/read", {
867
- store: files.store,
868
- paths: chunk,
869
- });
870
- } catch (error) {
871
- if (error instanceof StoreApiError && error.status === 404) {
872
- // The read route never 404s per path (absent paths are
873
- // omitted entries); a 404 is the store itself — not
874
- // provisioned, or deleted under us — which get() reads as
875
- // absence for every key.
876
- return keys.map(() => undefined);
877
- }
878
- throw error;
879
- }
880
- for (const entry of presigned.files ?? []) {
881
- if (entry.path !== undefined && entry.url !== undefined) {
882
- urls.set(entry.path, entry.url);
883
- }
884
- }
885
- }
886
- return await mapBounded([...keys], GET_MANY_DOWNLOAD_CONCURRENCY, async key => {
887
- const url = urls.get(key);
888
- if (url !== undefined) {
889
- return await downloadPresigned(key, url);
890
- }
891
- // A miss in the batch is NOT yet a definitive absence: the
892
- // server echoes each path *normalized* (trimmed, backslashes
893
- // folded, empty/"." segments dropped), so a key whose
894
- // normalized form differs from what was sent never matches the
895
- // echo even though its object exists. `get()` is immune (it
896
- // reads the response positionally), so settle every batch miss
897
- // through the single-path read — keeping getMany's absence
898
- // semantics byte-identical to get()'s. Genuine absences pay one
899
- // extra request; every batch caller reads keys it just listed
900
- // or was told exist, so real misses are rare (delete races,
901
- // store loss).
902
- const presigned = await flushPost<ApiPresignResponse>(options, "/read", {
903
- store: files.store,
904
- paths: [key],
905
- }).catch((error: unknown) => {
906
- // Store-level 404 (deleted between the batch and this read):
907
- // absence, same as get().
908
- if (error instanceof StoreApiError && error.status === 404) {
909
- return undefined;
910
- }
911
- throw error;
912
- });
913
- const singleUrl = presigned?.files?.[0]?.url;
914
- if (singleUrl === undefined) {
915
- return undefined;
916
- }
917
- return await downloadPresigned(key, singleUrl);
918
- });
919
- });
920
- return bodies;
921
- },
922
-
923
- async delete(key): Promise<void> {
924
- const handled = await withFiles(async files => {
925
- if (files.wire !== "flush") {
926
- return false;
927
- }
928
- // With an observed etag the flush delete is If-Match on that version.
929
- // Without one, the server tombstones whatever live object occupies
930
- // the key (no directory walk here) and an absent key settles as
931
- // `already_deleted`.
932
- const cached = observedEtags.get(key);
933
- const response = await flushPost<FlushWireResponse>(options, "/flush", {
934
- store: files.store,
935
- ops: [
936
- {
937
- op: "delete",
938
- path: key,
939
- ...(cached === undefined ? {} : { etag: cached }),
940
- },
941
- ],
942
- });
943
- const outcome = response.ops?.[0];
944
- if (outcome?.status === "conflict") {
945
- observedEtags.delete(key);
946
- throw new FileConflictError(key, {
947
- ...(outcome.etag === undefined
948
- ? {}
949
- : { storedEtag: normalizeEtag(outcome.etag) ?? outcome.etag }),
950
- });
951
- }
952
- observedEtags.delete(key);
953
- return true;
954
- });
955
- if (handled) {
956
- return;
957
- }
958
- if (observedEtags.get(key) === undefined) {
959
- // Deletes are conditional too, so learn the current etag first. A key
960
- // we have never seen is already absent as far as the caller cares.
961
- const found = await statKey(key);
962
- if (found?.etag === undefined) {
963
- return;
964
- }
965
- }
966
- const etag = observedEtags.get(key);
967
- if (etag === undefined) {
968
- return;
969
- }
970
- await withFiles(async files => {
971
- if (files.wire === "flush") {
972
- // The protocol pin appeared between the two withFiles calls; the
973
- // cached etag is enough for the conditional flush delete.
974
- const response = await flushPost<FlushWireResponse>(options, "/flush", {
975
- store: files.store,
976
- ops: [{ op: "delete", path: key, etag }],
977
- });
978
- const outcome = response.ops?.[0];
979
- if (outcome?.status === "conflict") {
980
- observedEtags.delete(key);
981
- throw new FileConflictError(key, {
982
- ...(outcome.etag === undefined
983
- ? {}
984
- : { storedEtag: normalizeEtag(outcome.etag) ?? outcome.etag }),
985
- });
986
- }
987
- return;
988
- }
989
- const raw = await storePost<ApiDeleteResponse>(options, files.path("/delete"), {
990
- files: [{ path: key, etag }],
991
- });
992
- // The API answers 200 with a per-file status, so a lost race would look
993
- // like success unless the outcome is read.
994
- const outcome = (raw.files ?? []).find(entry => entry.path === key);
995
- if (outcome?.status === "conflict") {
996
- observedEtags.delete(key);
997
- throw new FileConflictError(key, {
998
- ...(outcome.etag === undefined
999
- ? {}
1000
- : { storedEtag: normalizeEtag(outcome.etag) ?? outcome.etag }),
1001
- });
1002
- }
1003
- });
1004
- observedEtags.delete(key);
1005
- },
1006
-
1007
- list: listEntries,
1008
-
1009
- async stat(key): Promise<FileSinkEntry | undefined> {
1010
- return await statKey(key);
1011
- },
1012
- };
1013
- }
1014
-
1015
- /**
1016
- * Refuse a presigned URL that does not target this backend's agent-stores
1017
- * bucket.
1018
- *
1019
- * The URL arrives from BCS over TLS, so this is defense in depth rather than
1020
- * a live hole — but the engine pod can reach link-local metadata and other
1021
- * allowlisted egress hosts, so a BCS bug or compromise returning a foreign
1022
- * address would otherwise steer file bytes somewhere the caller never chose.
1023
- * Same pin as the sibling sync client: production backends allow only the
1024
- * agent-stores bucket hosts, playground its bucket, and a local dev backend
1025
- * additionally admits loopback http for LocalStack.
1026
- *
1027
- * `CURSOR_HOSTED_FILES_DEV_PRESIGN_HOSTS` (comma-separated hostnames) admits
1028
- * extra https hosts. A laptop backend hosting agents on a shared cluster
1029
- * fronts its LocalStack with a public tunnel and exports this in the pod's
1030
- * start script; production start scripts never set it. Read once at module
1031
- * load, so authored agent code sharing this process cannot widen the list
1032
- * by mutating `process.env` later.
1033
- */
1034
- function assertPresignedUrlSafe(options: CursorHostedFilesOptions, raw: string): string {
1035
- let url: URL;
1036
- try {
1037
- url = new URL(raw);
1038
- } catch {
1039
- throw new Error("cursorHostedFiles: presigned URL is not a valid URL");
1040
- }
1041
- if (
1042
- url.protocol === "https:" &&
1043
- url.username === "" &&
1044
- url.password === "" &&
1045
- DEV_PRESIGN_HOSTS.has(url.hostname.toLowerCase())
1046
- ) {
1047
- return url.toString();
1048
- }
1049
- try {
1050
- createAgentStorePresignedUrlValidatorForBackend(resolveCursorApiBaseUrl(options))(url);
1051
- } catch (error) {
1052
- throw new Error(
1053
- `cursorHostedFiles: refused presigned URL: ${
1054
- error instanceof Error ? error.message : String(error)
1055
- }`,
1056
- );
1057
- }
1058
- return url.toString();
1059
- }
1060
-
1061
- /**
1062
- * The file store this engine's deployment owns. Every request names it, so
1063
- * the server resolves the store row and this process never handles a store id.
1064
- *
1065
- * Fails loudly rather than defaulting: with no source id there is no store to
1066
- * address, and writing somewhere else silently is worse than not writing.
1067
- */
1068
- function resolveStoreSourceId(options: CursorHostedFilesOptions): string {
1069
- const id = (
1070
- options.resolveStoreSourceId?.() ??
1071
- options.storeSourceId ??
1072
- resolveDeploymentStoreSourceIdSync(process.env)
1073
- )?.trim();
1074
- if (id === undefined || id === "") {
1075
- throw new Error(
1076
- `cursorHostedFiles requires ${AGENT_SERVE_STORE_SOURCE_ID_ENV_VAR} (set on Cursor-managed deployments)`,
1077
- );
1078
- }
1079
- return id;
1080
- }
1081
-
1082
- const DEV_PRESIGN_HOSTS: ReadonlySet<string> = new Set(
1083
- (process.env.CURSOR_HOSTED_FILES_DEV_PRESIGN_HOSTS ?? "")
1084
- .split(",")
1085
- .map(host => host.trim().toLowerCase())
1086
- .filter(host => host.length > 0),
1087
- );
1088
-
1089
- /**
1090
- * Public agent-serve-files API call. The pod credential is the whole authorization:
1091
- * the API forwards this caller's identity to the store service, which checks it
1092
- * against the store's live owner. There is no second, narrower credential.
1093
- */
1094
- async function storePost<T>(
1095
- options: CursorHostedFilesOptions,
1096
- path: string,
1097
- body: Record<string, unknown>,
1098
- ): Promise<T> {
1099
- return await postJson<T>(
1100
- options,
1101
- `${resolveCursorApiBaseUrl(options)}${AGENT_SERVE_FILES_PATH}${path}`,
1102
- body,
1103
- );
1104
- }
1105
-
1106
- async function storeGet<T>(options: CursorHostedFilesOptions, path: string): Promise<T> {
1107
- return await sendJson<T>(
1108
- options,
1109
- `${resolveCursorApiBaseUrl(options)}${AGENT_SERVE_FILES_PATH}${path}`,
1110
- "GET",
1111
- );
1112
- }
1113
-
1114
- /**
1115
- * Factory store API call — same base URL and pod credential as
1116
- * {@link storePost}, different path family. The server resolves the
1117
- * deployment (and the addressed store role) from the credential. The
1118
- * domain-call lane one path family deeper is NOT here: it belongs to the
1119
- * store API client (`../internal/cursor/store-api-client.ts`).
1120
- */
1121
- async function flushPost<T>(
1122
- options: CursorHostedFilesOptions,
1123
- path: string,
1124
- body: Record<string, unknown>,
1125
- ): Promise<T> {
1126
- return await postJson<T>(
1127
- options,
1128
- `${resolveCursorApiBaseUrl(options)}${AGENTSDK_STORE_PATH}${path}`,
1129
- body,
1130
- );
1131
- }
1132
-
1133
- async function postJson<T>(
1134
- options: CursorHostedFilesOptions,
1135
- url: string,
1136
- body: Record<string, unknown>,
1137
- ): Promise<T> {
1138
- return await sendJson<T>(options, url, "POST", body);
1139
- }
1140
-
1141
- async function sendJson<T>(
1142
- options: CursorHostedFilesOptions,
1143
- url: string,
1144
- method: "GET" | "POST",
1145
- body?: Record<string, unknown>,
1146
- ): Promise<T> {
1147
- const response = await resolveCursorApiFetch(options)(url, {
1148
- method,
1149
- headers: {
1150
- Authorization: `Bearer ${resolveCursorApiKey(options)}`,
1151
- ...(body === undefined ? {} : { "Content-Type": "application/json" }),
1152
- },
1153
- ...(body === undefined ? {} : { body: JSON.stringify(body) }),
1154
- });
1155
- const text = await response.text();
1156
- let parsed: unknown;
1157
- try {
1158
- parsed = text.length === 0 ? {} : JSON.parse(text);
1159
- } catch {
1160
- throw new Error(`cursorHostedFiles ${url} returned non-JSON (${response.status})`);
1161
- }
1162
- if (!response.ok) {
1163
- throw new StoreApiError(
1164
- response.status,
1165
- `cursorHostedFiles ${url}: ${describeError(parsed, response.status)}`,
1166
- );
1167
- }
1168
- return parsed as T;
1169
- }
1170
-
1171
- /**
1172
- * A non-2xx answer from the files API, carrying the status so a caller can
1173
- * tell "absent" apart from a failure it must not swallow.
1174
- */
1175
- class StoreApiError extends Error {
1176
- constructor(
1177
- readonly status: number,
1178
- message: string,
1179
- ) {
1180
- super(message);
1181
- this.name = "StoreApiError";
1182
- }
1183
- }
1184
-
1185
- function describeError(parsed: unknown, status: number): string {
1186
- if (
1187
- parsed !== null &&
1188
- typeof parsed === "object" &&
1189
- "error" in parsed &&
1190
- typeof (parsed as { error: unknown }).error === "string"
1191
- ) {
1192
- return (parsed as { error: string }).error;
1193
- }
1194
- if (
1195
- parsed !== null &&
1196
- typeof parsed === "object" &&
1197
- "message" in parsed &&
1198
- typeof (parsed as { message: unknown }).message === "string"
1199
- ) {
1200
- return (parsed as { message: string }).message;
1201
- }
1202
- return `HTTP ${status}`;
1203
- }
1204
-
1205
- /** S3 returns quoted etags; the store compares them unquoted. */
1206
- function normalizeEtag(raw: string | null): string | undefined {
1207
- if (raw === null) {
1208
- return undefined;
1209
- }
1210
- const trimmed = raw.trim().replace(/^"+|"+$/g, "");
1211
- return trimmed === "" ? undefined : trimmed;
1212
- }