@cursor/bdk 0.2.9 → 0.2.11
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.
- package/dist/artifacts.js +3 -5
- package/dist/bin/agent-serve.js +803 -812
- package/dist/channels/bitbucket/api.js +305 -321
- package/dist/channels/bitbucket/binding.js +97 -61
- package/dist/channels/bitbucket/bitbucket-channel.d.ts +15 -1
- package/dist/channels/bitbucket/bitbucket-channel.d.ts.map +1 -1
- package/dist/channels/bitbucket/bitbucket-channel.js +95 -42
- package/dist/channels/bitbucket/bitbucket-webhook.d.ts +11 -0
- package/dist/channels/bitbucket/bitbucket-webhook.d.ts.map +1 -1
- package/dist/channels/bitbucket/bitbucket-webhook.js +214 -204
- package/dist/channels/bitbucket/checks.js +9 -21
- package/dist/channels/bitbucket/cursor-account.js +1 -1
- package/dist/channels/bitbucket/defaults.js +14 -4
- package/dist/channels/bitbucket/events.js +1 -2
- package/dist/channels/bitbucket/index.d.ts +6 -2
- package/dist/channels/bitbucket/index.d.ts.map +1 -1
- package/dist/channels/bitbucket/index.js +6 -2
- package/dist/channels/bitbucket/replay.js +80 -56
- package/dist/channels/bitbucket/state.js +16 -7
- package/dist/channels/bitbucket/types.d.ts +11 -2
- package/dist/channels/bitbucket/types.d.ts.map +1 -1
- package/dist/channels/checks.js +18 -28
- package/dist/channels/deployments/deployments-channel.js +26 -34
- package/dist/channels/github/api.js +216 -210
- package/dist/channels/github/auth.d.ts +5 -1
- package/dist/channels/github/auth.d.ts.map +1 -1
- package/dist/channels/github/auth.js +157 -174
- package/dist/channels/github/binding.js +48 -39
- package/dist/channels/github/checks.js +9 -14
- package/dist/channels/github/cursor-account.js +6 -11
- package/dist/channels/github/defaults.js +105 -130
- package/dist/channels/github/forward.js +13 -17
- package/dist/channels/github/github-channel.d.ts +43 -2
- package/dist/channels/github/github-channel.d.ts.map +1 -1
- package/dist/channels/github/github-channel.js +1228 -958
- package/dist/channels/github/progress.js +145 -146
- package/dist/channels/github/replay.js +30 -39
- package/dist/channels/github/state.js +40 -16
- package/dist/channels/github/types.d.ts +30 -6
- package/dist/channels/github/types.d.ts.map +1 -1
- package/dist/channels/gitlab/api.js +100 -114
- package/dist/channels/gitlab/binding.js +52 -33
- package/dist/channels/gitlab/checks.js +28 -29
- package/dist/channels/gitlab/cursor-account.js +1 -1
- package/dist/channels/gitlab/defaults.js +9 -3
- package/dist/channels/gitlab/gitlab-channel.d.ts +16 -1
- package/dist/channels/gitlab/gitlab-channel.d.ts.map +1 -1
- package/dist/channels/gitlab/gitlab-channel.js +95 -42
- package/dist/channels/gitlab/gitlab-webhook.d.ts +10 -1
- package/dist/channels/gitlab/gitlab-webhook.d.ts.map +1 -1
- package/dist/channels/gitlab/gitlab-webhook.js +282 -249
- package/dist/channels/gitlab/index.d.ts +6 -2
- package/dist/channels/gitlab/index.d.ts.map +1 -1
- package/dist/channels/gitlab/index.js +6 -2
- package/dist/channels/gitlab/replay.js +50 -27
- package/dist/channels/gitlab/state.js +14 -6
- package/dist/channels/gitlab/types.d.ts +13 -2
- package/dist/channels/gitlab/types.d.ts.map +1 -1
- package/dist/channels/origin/api.js +100 -116
- package/dist/channels/origin/binding.js +15 -16
- package/dist/channels/origin/checks.js +30 -28
- package/dist/channels/origin/defaults.js +50 -65
- package/dist/channels/origin/origin-channel.js +266 -272
- package/dist/channels/origin/origin-webhook.js +70 -88
- package/dist/channels/origin/reactions.js +9 -21
- package/dist/channels/origin/state.js +13 -5
- package/dist/channels/slack/agentic-delivery.js +426 -478
- package/dist/channels/slack/agentic-posted.js +9 -10
- package/dist/channels/slack/agentic-signals.js +2 -3
- package/dist/channels/slack/api.js +417 -427
- package/dist/channels/slack/approvals.js +1 -1
- package/dist/channels/slack/assistant.js +37 -47
- package/dist/channels/slack/attachments.js +188 -206
- package/dist/channels/slack/auth.js +8 -6
- package/dist/channels/slack/bot-mentions.js +31 -45
- package/dist/channels/slack/channel-watch.js +42 -48
- package/dist/channels/slack/cli-project.js +112 -137
- package/dist/channels/slack/code-fences.js +3 -5
- package/dist/channels/slack/constants.js +2 -2
- package/dist/channels/slack/credentials.js +23 -38
- package/dist/channels/slack/defaults.js +292 -331
- package/dist/channels/slack/dispatch.d.ts.map +1 -1
- package/dist/channels/slack/dispatch.js +533 -460
- package/dist/channels/slack/doctor.js +200 -214
- package/dist/channels/slack/eval-directive.js +79 -96
- package/dist/channels/slack/event-id-dedupe.js +1 -1
- package/dist/channels/slack/external-policy.js +26 -39
- package/dist/channels/slack/footer.js +28 -41
- package/dist/channels/slack/inbound.d.ts +1 -0
- package/dist/channels/slack/inbound.d.ts.map +1 -1
- package/dist/channels/slack/inbound.js +50 -31
- package/dist/channels/slack/init.d.ts +1 -5
- package/dist/channels/slack/init.d.ts.map +1 -1
- package/dist/channels/slack/init.js +42 -59
- package/dist/channels/slack/interactive.d.ts +9 -3
- package/dist/channels/slack/interactive.d.ts.map +1 -1
- package/dist/channels/slack/interactive.js +160 -162
- package/dist/channels/slack/links.js +1 -2
- package/dist/channels/slack/live-delivery.js +172 -190
- package/dist/channels/slack/manifest.d.ts.map +1 -1
- package/dist/channels/slack/manifest.js +44 -23
- package/dist/channels/slack/message-body.js +2 -2
- package/dist/channels/slack/nudge-carrier.js +30 -42
- package/dist/channels/slack/nudge-store.js +164 -191
- package/dist/channels/slack/paint-start.js +66 -81
- package/dist/channels/slack/placeholder.js +123 -152
- package/dist/channels/slack/pr-ref.js +1 -1
- package/dist/channels/slack/progress-delivery.js +65 -78
- package/dist/channels/slack/reasoning-card.js +36 -49
- package/dist/channels/slack/recording-sink.js +12 -24
- package/dist/channels/slack/reply-options.js +7 -12
- package/dist/channels/slack/self-identity.js +27 -35
- package/dist/channels/slack/sender-name.d.ts +12 -3
- package/dist/channels/slack/sender-name.d.ts.map +1 -1
- package/dist/channels/slack/sender-name.js +57 -61
- package/dist/channels/slack/setup.js +2 -4
- package/dist/channels/slack/slack-channel.d.ts +6 -4
- package/dist/channels/slack/slack-channel.d.ts.map +1 -1
- package/dist/channels/slack/slack-channel.js +441 -308
- package/dist/channels/slack/socket-mode.js +38 -52
- package/dist/channels/slack/thread-context.js +143 -160
- package/dist/channels/slack/tool-approval-events.js +149 -166
- package/dist/channels/slack/tool-cards.js +6 -11
- package/dist/channels/slack/tool-chip.js +1 -2
- package/dist/channels/slack/types.d.ts +15 -17
- package/dist/channels/slack/types.d.ts.map +1 -1
- package/dist/channels/slack/upload.js +84 -97
- package/dist/channels/slack/user-info.js +27 -41
- package/dist/channels.js +9 -20
- package/dist/dashboard-session-url.js +3 -4
- package/dist/docs/404.html +2 -2
- package/dist/docs/assets/{app.BNhEdGa8.js → app.DXI7bwox.js} +4 -4
- package/dist/docs/assets/{building-with-agents.md.BPjPFxFg.js → building-with-agents.md.Df87rAe1.js} +1 -1
- package/dist/docs/assets/chunks/@localSearchIndexroot.CtLtpWcx.js +1 -0
- package/dist/docs/assets/chunks/{VPLocalSearchBox.DIdUV1Ph.js → VPLocalSearchBox.BrbO5mEe.js} +1 -1
- package/dist/docs/assets/chunks/{arc.lELI4_SR.js → arc.BOG1yms8.js} +1 -1
- package/dist/docs/assets/chunks/{architectureDiagram-Q4EWVU46.iCMdvuoM.js → architectureDiagram-Q4EWVU46.C0hsMZnZ.js} +1 -1
- package/dist/docs/assets/chunks/{baseUniq.C1-nDJBf.js → baseUniq.H044KjKs.js} +1 -1
- package/dist/docs/assets/chunks/{blockDiagram-DXYQGD6D.ClemzBox.js → blockDiagram-DXYQGD6D.BsRW6GoB.js} +1 -1
- package/dist/docs/assets/chunks/{c4Diagram-AHTNJAMY.BqTak6cR.js → c4Diagram-AHTNJAMY.B9HV5PFR.js} +1 -1
- package/dist/docs/assets/chunks/channel.CBwrU9rP.js +1 -0
- package/dist/docs/assets/chunks/{chunk-4BX2VUAB.CUS3qs_R.js → chunk-4BX2VUAB.CPoT_ajl.js} +1 -1
- package/dist/docs/assets/chunks/{chunk-4TB4RGXK.dz5fdHcl.js → chunk-4TB4RGXK.DZBwWuCL.js} +1 -1
- package/dist/docs/assets/chunks/{chunk-55IACEB6.D6BEBmve.js → chunk-55IACEB6.CZ3yTiyq.js} +1 -1
- package/dist/docs/assets/chunks/{chunk-EDXVE4YY.B5uK4ID9.js → chunk-EDXVE4YY.6DfvHSd8.js} +1 -1
- package/dist/docs/assets/chunks/{chunk-FMBD7UC4.PgOJgNQs.js → chunk-FMBD7UC4.CpBj8HfK.js} +1 -1
- package/dist/docs/assets/chunks/{chunk-OYMX7WX6.CvkTrCii.js → chunk-OYMX7WX6.BdKo8YqS.js} +1 -1
- package/dist/docs/assets/chunks/{chunk-QZHKN3VN.BnSfKYsB.js → chunk-QZHKN3VN.BnXNyxJx.js} +1 -1
- package/dist/docs/assets/chunks/{chunk-YZCP3GAM.MZH6v5UC.js → chunk-YZCP3GAM.Cg1OcF8G.js} +1 -1
- package/dist/docs/assets/chunks/classDiagram-6PBFFD2Q.CbC_mrvB.js +1 -0
- package/dist/docs/assets/chunks/classDiagram-v2-HSJHXN6E.CbC_mrvB.js +1 -0
- package/dist/docs/assets/chunks/clone.BTvFyY1a.js +1 -0
- package/dist/docs/assets/chunks/{cose-bilkent-S5V4N54A.BBbw_YZi.js → cose-bilkent-S5V4N54A.1SP8al9c.js} +1 -1
- package/dist/docs/assets/chunks/{dagre-KV5264BT.CNJB2l7E.js → dagre-KV5264BT.DS1L6hJt.js} +1 -1
- package/dist/docs/assets/chunks/{diagram-5BDNPKRD.BQzlnFfq.js → diagram-5BDNPKRD.ChsausbG.js} +1 -1
- package/dist/docs/assets/chunks/{diagram-G4DWMVQ6.DkgqqZMz.js → diagram-G4DWMVQ6.3XsptNWj.js} +1 -1
- package/dist/docs/assets/chunks/{diagram-MMDJMWI5.DIT8L7H_.js → diagram-MMDJMWI5.DPA9Dk_P.js} +1 -1
- package/dist/docs/assets/chunks/{diagram-TYMM5635.3xLPZWGJ.js → diagram-TYMM5635.BVswXWrB.js} +1 -1
- package/dist/docs/assets/chunks/{erDiagram-SMLLAGMA.5_IHz8jF.js → erDiagram-SMLLAGMA.BSak5OSP.js} +1 -1
- package/dist/docs/assets/chunks/{flowDiagram-DWJPFMVM.BGUCXz8A.js → flowDiagram-DWJPFMVM.CQgN4TTz.js} +1 -1
- package/dist/docs/assets/chunks/{ganttDiagram-T4ZO3ILL.DbfFavS1.js → ganttDiagram-T4ZO3ILL.GrT0kA4a.js} +1 -1
- package/dist/docs/assets/chunks/{gitGraphDiagram-UUTBAWPF.D2yKdeNW.js → gitGraphDiagram-UUTBAWPF.CdS7Ucll.js} +1 -1
- package/dist/docs/assets/chunks/{graph.CSCxwHba.js → graph.BVqKRmRo.js} +1 -1
- package/dist/docs/assets/chunks/{infoDiagram-42DDH7IO.FDeb6xHY.js → infoDiagram-42DDH7IO.C3C9FRto.js} +1 -1
- package/dist/docs/assets/chunks/{ishikawaDiagram-UXIWVN3A.DbeYs9VL.js → ishikawaDiagram-UXIWVN3A.2FuAh_Zj.js} +1 -1
- package/dist/docs/assets/chunks/{journeyDiagram-VCZTEJTY.CWjKINpx.js → journeyDiagram-VCZTEJTY.CaHMfK4b.js} +1 -1
- package/dist/docs/assets/chunks/{kanban-definition-6JOO6SKY.B4FrAlwz.js → kanban-definition-6JOO6SKY.CjhIUwcA.js} +1 -1
- package/dist/docs/assets/chunks/{layout.DFGxQ9ab.js → layout.CGU3gfSf.js} +1 -1
- package/dist/docs/assets/chunks/{linear.BmjZ-G59.js → linear.DaihpW_n.js} +1 -1
- package/dist/docs/assets/chunks/{min.DZSxulMW.js → min.BS-j8kHQ.js} +1 -1
- package/dist/docs/assets/chunks/{mindmap-definition-QFDTVHPH.CHLi3-aN.js → mindmap-definition-QFDTVHPH.BVJ1Bbnk.js} +1 -1
- package/dist/docs/assets/chunks/{pieDiagram-DEJITSTG.DteG-CkU.js → pieDiagram-DEJITSTG.Cz1WIv9r.js} +1 -1
- package/dist/docs/assets/chunks/{quadrantDiagram-34T5L4WZ.D458ipN3.js → quadrantDiagram-34T5L4WZ.DKLFxcjy.js} +1 -1
- package/dist/docs/assets/chunks/{requirementDiagram-MS252O5E.DcFgUxst.js → requirementDiagram-MS252O5E.BHegiK7x.js} +1 -1
- package/dist/docs/assets/chunks/{sankeyDiagram-XADWPNL6.BwQAMmSA.js → sankeyDiagram-XADWPNL6.DYkY6yPc.js} +1 -1
- package/dist/docs/assets/chunks/{sequenceDiagram-FGHM5R23.B_Xdsrh9.js → sequenceDiagram-FGHM5R23.DYQ1QUrI.js} +1 -1
- package/dist/docs/assets/chunks/{stateDiagram-FHFEXIEX.CdfDW6-C.js → stateDiagram-FHFEXIEX.Iru8BsFo.js} +1 -1
- package/dist/docs/assets/chunks/stateDiagram-v2-QKLJ7IA2.BYXl4LW6.js +1 -0
- package/dist/docs/assets/chunks/{theme.sfdL3srp.js → theme.DaplWQhQ.js} +2 -2
- package/dist/docs/assets/chunks/{timeline-definition-GMOUNBTQ.56-4hXaC.js → timeline-definition-GMOUNBTQ.BwFA8hiX.js} +1 -1
- package/dist/docs/assets/chunks/{vennDiagram-DHZGUBPP.CvZSQBO7.js → vennDiagram-DHZGUBPP.CZ60W1C_.js} +1 -1
- package/dist/docs/assets/chunks/{wardley-RL74JXVD.C9qc8k39.js → wardley-RL74JXVD.iXazD3Z7.js} +1 -1
- package/dist/docs/assets/chunks/{wardleyDiagram-NUSXRM2D.D9s0nihi.js → wardleyDiagram-NUSXRM2D.B69D2r8-.js} +1 -1
- package/dist/docs/assets/chunks/{xychartDiagram-5P7HB3ND.BVJSpYrC.js → xychartDiagram-5P7HB3ND.CLPZEIU9.js} +1 -1
- package/dist/docs/assets/{deployment.md.BdqTiGdc.js → deployment.md.BYuEcDBv.js} +2 -2
- package/dist/docs/assets/{guides_bitbucket.md.BB6fqD-M.js → guides_bitbucket.md.CKUayYtD.js} +3 -3
- package/dist/docs/assets/{guides_bitbucket.md.BB6fqD-M.lean.js → guides_bitbucket.md.CKUayYtD.lean.js} +1 -1
- package/dist/docs/assets/{guides_github.md.Cr1Xlf-m.js → guides_github.md.TuNiZAOQ.js} +4 -4
- package/dist/docs/assets/{guides_github.md.Cr1Xlf-m.lean.js → guides_github.md.TuNiZAOQ.lean.js} +1 -1
- package/dist/docs/assets/{guides_gitlab.md.CMpmFi4M.js → guides_gitlab.md.CtNI4fSH.js} +4 -4
- package/dist/docs/assets/{guides_gitlab.md.CMpmFi4M.lean.js → guides_gitlab.md.CtNI4fSH.lean.js} +1 -1
- package/dist/docs/assets/guides_migrate.md.sCAvvlC6.js +7 -0
- package/dist/docs/assets/guides_migrate.md.sCAvvlC6.lean.js +1 -0
- package/dist/docs/assets/{reference_cli.md.GwugN3n-.js → reference_cli.md.dYCAL2lB.js} +4 -3
- package/dist/docs/assets/reference_cli.md.dYCAL2lB.lean.js +1 -0
- package/dist/docs/assets/troubleshooting.md.x-GBa7wN.js +1 -0
- package/dist/docs/assets/{troubleshooting.md.Bscj6ODe.lean.js → troubleshooting.md.x-GBa7wN.lean.js} +1 -1
- package/dist/docs/building-with-agents.html +37 -37
- package/dist/docs/building-with-agents.md +2 -0
- package/dist/docs/deployment.html +38 -38
- package/dist/docs/deployment.md +7 -0
- package/dist/docs/evals.html +36 -36
- package/dist/docs/guides/agent-to-agent.html +35 -35
- package/dist/docs/guides/bitbucket.html +38 -38
- package/dist/docs/guides/bitbucket.md +9 -1
- package/dist/docs/guides/cloud-agents.html +35 -35
- package/dist/docs/guides/convert-automation.html +35 -35
- package/dist/docs/guides/github.html +39 -39
- package/dist/docs/guides/github.md +20 -4
- package/dist/docs/guides/gitlab.html +38 -38
- package/dist/docs/guides/gitlab.md +9 -1
- package/dist/docs/guides/grokbot-agents.html +35 -35
- package/dist/docs/guides/hooks.html +35 -35
- package/dist/docs/guides/improve.html +35 -35
- package/dist/docs/guides/jev.html +35 -35
- package/dist/docs/guides/mcp-oauth.html +35 -35
- package/dist/docs/guides/migrate.html +66 -0
- package/dist/docs/guides/migrate.md +122 -0
- package/dist/docs/guides/opentelemetry.html +35 -35
- package/dist/docs/guides/slack.html +35 -35
- package/dist/docs/guides/webhooks.html +35 -35
- package/dist/docs/hashmap.json +1 -1
- package/dist/docs/hillclimbing.html +35 -35
- package/dist/docs/index.html +35 -35
- package/dist/docs/llms-full.txt +214 -6
- package/dist/docs/llms.txt +1 -0
- package/dist/docs/quickstart.html +35 -35
- package/dist/docs/reference/agent-config.html +35 -35
- package/dist/docs/reference/artifacts.html +35 -35
- package/dist/docs/reference/channels.html +35 -35
- package/dist/docs/reference/cli.html +39 -38
- package/dist/docs/reference/cli.md +39 -0
- package/dist/docs/reference/connections.html +35 -35
- package/dist/docs/reference/evals.html +35 -35
- package/dist/docs/reference/extensions.html +35 -35
- package/dist/docs/reference/hooks.html +35 -35
- package/dist/docs/reference/http-api.html +35 -35
- package/dist/docs/reference/instructions.html +35 -35
- package/dist/docs/reference/playground.html +35 -35
- package/dist/docs/reference/project-layout.html +35 -35
- package/dist/docs/reference/prompt.html +35 -35
- package/dist/docs/reference/schedules.html +35 -35
- package/dist/docs/reference/sessions.html +35 -35
- package/dist/docs/reference/skills.html +35 -35
- package/dist/docs/reference/subagents.html +35 -35
- package/dist/docs/reference/tools.html +35 -35
- package/dist/docs/templates/agentic-owners.html +35 -35
- package/dist/docs/templates/grokbot-agents.html +35 -35
- package/dist/docs/templates/security-reviewer.html +35 -35
- package/dist/docs/templates/thermo-quality-review.html +35 -35
- package/dist/docs/templates/thermo-review.html +35 -35
- package/dist/docs/templates/triage.html +35 -35
- package/dist/docs/troubleshooting.html +36 -36
- package/dist/docs/troubleshooting.md +1 -0
- package/dist/evals/assertions.js +20 -34
- package/dist/evals/expect.js +3 -6
- package/dist/evals/judge.js +5 -8
- package/dist/evals/loaders.js +45 -62
- package/dist/evals/matchers.js +2 -3
- package/dist/evals/reporters.js +43 -67
- package/dist/evals/results.js +1 -1
- package/dist/evals/run-facts.js +5 -6
- package/dist/evals.js +27 -15
- package/dist/extensions/cursor-cloud-agents/extension.js +4 -1
- package/dist/extensions/cursor-cloud-agents/lib/backend.js +100 -126
- package/dist/extensions/cursor-cloud-agents/lib/fleet.js +22 -38
- package/dist/extensions/cursor-cloud-agents/lib/repos.js +8 -10
- package/dist/extensions/cursor-cloud-agents/lib/transcript.js +2 -3
- package/dist/extensions/cursor-cloud-agents/lib/watch.js +52 -66
- package/dist/extensions/cursor-cloud-agents/reminders/cloud-agent-stop-watch.js +29 -40
- package/dist/extensions/cursor-cloud-agents/tools/cancel.js +6 -17
- package/dist/extensions/cursor-cloud-agents/tools/dump.js +25 -36
- package/dist/extensions/cursor-cloud-agents/tools/get.js +3 -14
- package/dist/extensions/cursor-cloud-agents/tools/launch.js +13 -21
- package/dist/extensions/cursor-cloud-agents/tools/list.js +8 -19
- package/dist/extensions/cursor-cloud-agents/tools/reply.js +5 -16
- package/dist/extensions/cursor-grokbot-agents/extension.js +4 -1
- package/dist/extensions/cursor-grokbot-agents/lib/agents.js +3 -3
- package/dist/extensions/cursor-grokbot-agents/lib/backend.js +53 -73
- package/dist/extensions/cursor-grokbot-agents/lib/contacts.js +10 -23
- package/dist/extensions/cursor-grokbot-agents/lib/conversation.js +112 -132
- package/dist/extensions/cursor-grokbot-agents/lib/lock.js +1 -2
- package/dist/extensions/cursor-grokbot-agents/tools/ask.js +5 -16
- package/dist/extensions/cursor-grokbot-agents/tools/check.js +4 -15
- package/dist/extensions/cursor-grokbot-agents/tools/interrupt.js +2 -13
- package/dist/extensions/cursor-grokbot-agents/tools/list.js +11 -23
- package/dist/extensions/jev/extension.js +3 -3
- package/dist/extensions/jev/lib/evaluate.js +43 -60
- package/dist/extensions/jev/tools/evaluate.js +6 -17
- package/dist/extensions/slack-communication/tools/post-file.js +1 -1
- package/dist/extensions.js +3 -5
- package/dist/files-backends/cursor-hosted.js +504 -523
- package/dist/files-backends/local-fs.js +72 -134
- package/dist/files.js +3 -3
- package/dist/filesystem.js +562 -861
- package/dist/index.js +16 -35
- package/dist/internal/advertise-tools.js +74 -86
- package/dist/internal/artifacts-store.js +298 -361
- package/dist/internal/as-of.js +2 -4
- package/dist/internal/authored-alias-hooks.js +6 -4
- package/dist/internal/authored-loaders.js +37 -52
- package/dist/internal/bounded-int.js +1 -2
- package/dist/internal/bounded.js +13 -24
- package/dist/internal/brand.js +1 -1
- package/dist/internal/browser-origin-guard.js +4 -4
- package/dist/internal/builtin-tools/artifacts.js +15 -27
- package/dist/internal/builtin-tools/index.js +2 -3
- package/dist/internal/builtin-tools/reminders.js +33 -52
- package/dist/internal/channel-architecture.d.ts +21 -0
- package/dist/internal/channel-architecture.d.ts.map +1 -0
- package/dist/internal/channel-architecture.js +41 -0
- package/dist/internal/channel-dispatch.js +3 -4
- package/dist/internal/channel-route-dispatch.js +40 -51
- package/dist/internal/channel-state.js +1 -1
- package/dist/internal/chat-client.js +319 -340
- package/dist/internal/cli-ax.js +1262 -1294
- package/dist/internal/cli-bitbucket.js +244 -272
- package/dist/internal/cli-convert-automation.js +137 -151
- package/dist/internal/cli-cursor.js +126 -147
- package/dist/internal/cli-deploy.d.ts.map +1 -1
- package/dist/internal/cli-deploy.js +1457 -1507
- package/dist/internal/cli-docs.js +130 -147
- package/dist/internal/cli-github.js +437 -476
- package/dist/internal/cli-gitlab.js +259 -288
- package/dist/internal/cli-images.js +266 -334
- package/dist/internal/cli-manifest.js +25 -37
- package/dist/internal/cli-mcp-oauth.js +228 -247
- package/dist/internal/cli-mcp.js +358 -383
- package/dist/internal/cli-migrate.js +73 -90
- package/dist/internal/cli-skills.js +35 -47
- package/dist/internal/cli-slack-manual.d.ts +0 -1
- package/dist/internal/cli-slack-manual.d.ts.map +1 -1
- package/dist/internal/cli-slack-manual.js +175 -204
- package/dist/internal/cli-slack.d.ts +1 -1
- package/dist/internal/cli-slack.d.ts.map +1 -1
- package/dist/internal/cli-slack.js +416 -456
- package/dist/internal/cloud-merge.js +7 -3
- package/dist/internal/cloud-turn-cost.js +8 -1
- package/dist/internal/connection-env.js +5 -6
- package/dist/internal/continuation-channel.js +10 -7
- package/dist/internal/continuation-identity.js +85 -67
- package/dist/internal/conversation-mirror.js +112 -131
- package/dist/internal/convert-automation/convert-workflow.js +21 -31
- package/dist/internal/cron.js +3 -3
- package/dist/internal/cursor/account-mcp.js +147 -146
- package/dist/internal/cursor/backend-client.js +290 -320
- package/dist/internal/cursor/bound-secret-file.js +3 -3
- package/dist/internal/cursor/credentials.js +127 -142
- package/dist/internal/cursor/cursor-api-transport.js +4 -8
- package/dist/internal/cursor/github-credentials.js +91 -108
- package/dist/internal/cursor/hosted-bound-secrets.js +3 -4
- package/dist/internal/cursor/hosted-deployment-secrets.js +1 -1
- package/dist/internal/cursor/hosted-store-secrets.js +5 -9
- package/dist/internal/cursor/session-token.js +36 -48
- package/dist/internal/cursor/store-api-client.js +77 -92
- package/dist/internal/cursor-account-mcp-auth.js +132 -149
- package/dist/internal/cursor-agent-template.js +1 -1
- package/dist/internal/cursor-deployment-relay.js +33 -54
- package/dist/internal/cursor-event-relay.js +65 -59
- package/dist/internal/cursor-relay-core.js +258 -286
- package/dist/internal/deferred-channel-session.js +3 -12
- package/dist/internal/deploy-client.d.ts.map +1 -1
- package/dist/internal/deploy-client.js +413 -475
- package/dist/internal/deploy-manifest.d.ts +17 -0
- package/dist/internal/deploy-manifest.d.ts.map +1 -1
- package/dist/internal/deploy-manifest.js +71 -21
- package/dist/internal/deploy-source.js +31 -43
- package/dist/internal/discovery/agent-config.js +45 -59
- package/dist/internal/discovery/agent.js +276 -280
- package/dist/internal/discovery/channels.js +67 -80
- package/dist/internal/discovery/connections.js +286 -296
- package/dist/internal/discovery/context.js +2 -13
- package/dist/internal/discovery/cursor-plugin.js +223 -259
- package/dist/internal/discovery/extension-overlay.js +104 -131
- package/dist/internal/discovery/extensions.js +276 -281
- package/dist/internal/discovery/fs.js +56 -79
- package/dist/internal/discovery/hooks.js +39 -50
- package/dist/internal/discovery/info.js +17 -19
- package/dist/internal/discovery/instructions.js +58 -71
- package/dist/internal/discovery/modules.js +45 -58
- package/dist/internal/discovery/project.d.ts.map +1 -1
- package/dist/internal/discovery/project.js +192 -215
- package/dist/internal/discovery/reminders.js +41 -52
- package/dist/internal/discovery/runtime.js +1 -1
- package/dist/internal/discovery/schedules.js +71 -82
- package/dist/internal/discovery/seed-files.js +21 -32
- package/dist/internal/discovery/services.js +74 -96
- package/dist/internal/discovery/skills.js +77 -94
- package/dist/internal/discovery/subagent.js +79 -95
- package/dist/internal/discovery/tools.js +12 -23
- package/dist/internal/distribution.js +2 -2
- package/dist/internal/docs-site.js +30 -45
- package/dist/internal/duration.js +1 -1
- package/dist/internal/env-file.js +48 -64
- package/dist/internal/eval-judge-model.js +17 -28
- package/dist/internal/eval-run-store.js +265 -254
- package/dist/internal/eval-runner.d.ts +8 -0
- package/dist/internal/eval-runner.d.ts.map +1 -1
- package/dist/internal/eval-runner.js +406 -408
- package/dist/internal/eval-transport.d.ts +24 -0
- package/dist/internal/eval-transport.d.ts.map +1 -0
- package/dist/internal/eval-transport.js +22 -0
- package/dist/internal/evals-client.js +242 -246
- package/dist/internal/event-log.js +72 -120
- package/dist/internal/event-mapper.js +30 -31
- package/dist/internal/factory-resource-channel.js +16 -30
- package/dist/internal/filesystem/errors.js +6 -6
- package/dist/internal/filesystem/format.js +10 -13
- package/dist/internal/filesystem/merge.js +52 -94
- package/dist/internal/filesystem/nearest.js +15 -27
- package/dist/internal/filesystem/scratch.js +65 -85
- package/dist/internal/filesystem/tools.js +221 -241
- package/dist/internal/filesystem/walk.js +76 -143
- package/dist/internal/framework-file-storage.js +118 -139
- package/dist/internal/framework-storage-selection.js +21 -13
- package/dist/internal/frontmatter.js +4 -6
- package/dist/internal/github-fanout.js +50 -64
- package/dist/internal/guest-network.js +96 -109
- package/dist/internal/handleAgentServeTrigger.js +121 -126
- package/dist/internal/host-files.js +73 -88
- package/dist/internal/host-kv.js +21 -36
- package/dist/internal/host-otel.js +22 -19
- package/dist/internal/host-platforms.js +49 -63
- package/dist/internal/hosted-catch-protocol.d.ts +1 -0
- package/dist/internal/hosted-catch-protocol.d.ts.map +1 -1
- package/dist/internal/hosted-catch-protocol.js +1 -0
- package/dist/internal/hosted-catch.d.ts +5 -3
- package/dist/internal/hosted-catch.d.ts.map +1 -1
- package/dist/internal/hosted-catch.js +84 -93
- package/dist/internal/hosted-delivery-protocol.js +32 -8
- package/dist/internal/hosted-delivery.d.ts +13 -1
- package/dist/internal/hosted-delivery.d.ts.map +1 -1
- package/dist/internal/hosted-delivery.js +614 -584
- package/dist/internal/hosted-eval-cases.d.ts +21 -0
- package/dist/internal/hosted-eval-cases.d.ts.map +1 -0
- package/dist/internal/hosted-eval-cases.js +162 -0
- package/dist/internal/hosted-execution-diag.js +38 -30
- package/dist/internal/hosted-execution-flush.js +75 -66
- package/dist/internal/hosted-execution-latency.js +16 -8
- package/dist/internal/hosted-managed-automation.js +2 -2
- package/dist/internal/hosting.js +2 -3
- package/dist/internal/http-channel-define.js +4 -1
- package/dist/internal/http-channel.js +101 -112
- package/dist/internal/http-control-plane-session.d.ts +18 -1
- package/dist/internal/http-control-plane-session.d.ts.map +1 -1
- package/dist/internal/http-control-plane-session.js +66 -32
- package/dist/internal/incoming-headers-to-fetch.js +1 -2
- package/dist/internal/init-interview.js +73 -93
- package/dist/internal/init-project.js +182 -214
- package/dist/internal/init-scaffold.js +10 -21
- package/dist/internal/inline-stdio-mcp.js +80 -86
- package/dist/internal/install-cursor-skills.js +231 -273
- package/dist/internal/legacy-command-guard.js +18 -31
- package/dist/internal/listen-port.js +34 -45
- package/dist/internal/local-control-plane.js +293 -307
- package/dist/internal/local-env.js +1 -1
- package/dist/internal/local-ingress.js +15 -9
- package/dist/internal/log-ring.js +1 -2
- package/dist/internal/logs-client.js +207 -227
- package/dist/internal/map-pool.js +24 -38
- package/dist/internal/mcp-endpoint.js +204 -180
- package/dist/internal/mcp-host.js +165 -168
- package/dist/internal/mcp-oauth-on-demand.js +170 -207
- package/dist/internal/mcp-oauth-persist.js +15 -29
- package/dist/internal/mcp-oauth.js +209 -266
- package/dist/internal/named-webhook-secret.d.ts +39 -0
- package/dist/internal/named-webhook-secret.d.ts.map +1 -0
- package/dist/internal/named-webhook-secret.js +47 -0
- package/dist/internal/open-browser.js +2 -3
- package/dist/internal/otel/collector.js +104 -68
- package/dist/internal/otel/config.js +24 -37
- package/dist/internal/otel/http.js +5 -7
- package/dist/internal/otel/mcp.js +3 -12
- package/dist/internal/otel/runtime.js +69 -83
- package/dist/internal/package-inline-stdio.js +57 -65
- package/dist/internal/peer-connections.js +9 -5
- package/dist/internal/platform-schedule-sync.js +39 -54
- package/dist/internal/platform-timers.js +176 -210
- package/dist/internal/playground/static.js +1 -2
- package/dist/internal/playground/toolchain.js +37 -53
- package/dist/internal/playground-proxy.js +230 -270
- package/dist/internal/process-memory-telemetry.js +33 -51
- package/dist/internal/prompt-context.js +3 -6
- package/dist/internal/reminder-control-plane-protocol.js +46 -13
- package/dist/internal/reminder-runner.js +770 -785
- package/dist/internal/reminder-schedule.js +1 -1
- package/dist/internal/reminder-store.js +57 -78
- package/dist/internal/request-error-status.js +1 -3
- package/dist/internal/request-headers.js +2 -2
- package/dist/internal/resolve-prod-target.js +119 -135
- package/dist/internal/resolved-connections.js +16 -6
- package/dist/internal/run-client.js +221 -229
- package/dist/internal/runtime-dispatch-runner.js +18 -43
- package/dist/internal/schedule-runner.js +49 -58
- package/dist/internal/scm/pr-url.js +7 -6
- package/dist/internal/sdk-runner.js +388 -363
- package/dist/internal/serve-restart.js +26 -39
- package/dist/internal/server.d.ts.map +1 -1
- package/dist/internal/server.js +1283 -1244
- package/dist/internal/session-cost.js +7 -9
- package/dist/internal/session-engine.d.ts +3 -1
- package/dist/internal/session-engine.d.ts.map +1 -1
- package/dist/internal/session-engine.js +2801 -2777
- package/dist/internal/session-links.js +12 -16
- package/dist/internal/session-run-log.js +134 -82
- package/dist/internal/session-store.js +133 -164
- package/dist/internal/sessions-client.js +97 -118
- package/dist/internal/skill-catalog.js +18 -23
- package/dist/internal/skills-sync.js +16 -30
- package/dist/internal/slack-provision-client.js +90 -116
- package/dist/internal/start-log-store.js +48 -67
- package/dist/internal/steering.js +1 -2
- package/dist/internal/storage-coordinator.js +160 -196
- package/dist/internal/store-mirror.js +16 -30
- package/dist/internal/stream-progress.js +6 -8
- package/dist/internal/terminal-style.js +3 -4
- package/dist/internal/testing/scm-continuation-fixtures.js +6 -2
- package/dist/internal/testing/slack-continuation-fixtures.d.ts +26 -0
- package/dist/internal/testing/slack-continuation-fixtures.d.ts.map +1 -0
- package/dist/internal/testing/slack-continuation-fixtures.js +136 -0
- package/dist/internal/tool-catalog.js +11 -5
- package/dist/internal/tool-result.js +1 -1
- package/dist/internal/trajectory.js +19 -22
- package/dist/internal/turn-cost.js +52 -62
- package/dist/internal/turn-governor.js +5 -8
- package/dist/internal/turn-lease-store.js +85 -105
- package/dist/internal/turn-outbox.js +28 -43
- package/dist/internal/update-check.js +231 -259
- package/dist/internal/v2-hosting.js +1 -2
- package/dist/internal/workspace-sync.js +25 -41
- package/dist/internal/workspace.js +111 -139
- package/dist/kv/memory-kv.js +27 -51
- package/dist/memory.js +106 -148
- package/dist/otel.js +5 -8
- package/dist/playground/assets/{index-Co4CUVTQ.js → index-Be0-1hw2.js} +45 -45
- package/dist/playground/assets/index-Clz6uHj9.css +1 -0
- package/dist/playground/index.html +2 -2
- package/dist/prompt.js +2 -4
- package/dist/storage-backends/cursor-hosted-v2.js +53 -83
- package/dist/storage-backends/cursor-hosted.js +105 -145
- package/dist/storage-backends/file-kv.js +100 -128
- package/dist/storage-protocol.js +3 -5
- package/dist/storage.js +17 -26
- package/dist/types.d.ts +68 -0
- package/dist/types.d.ts.map +1 -1
- package/dist/types.js +20 -18
- package/docs/building-with-agents.md +2 -0
- package/docs/deployment.md +7 -0
- package/docs/guides/bitbucket.md +9 -1
- package/docs/guides/github.md +20 -4
- package/docs/guides/gitlab.md +9 -1
- package/docs/guides/migrate.md +127 -0
- package/docs/reference/cli.md +39 -0
- package/docs/troubleshooting.md +1 -0
- package/package.json +1 -1
- package/skills/debug/SKILL.md +1 -0
- package/skills/github/SKILL.md +23 -0
- package/skills/migrate-v2/SKILL.md +51 -10
- package/dist/docs/assets/chunks/@localSearchIndexroot.BJ8UZggu.js +0 -1
- package/dist/docs/assets/chunks/channel.BTJ5J9FI.js +0 -1
- package/dist/docs/assets/chunks/classDiagram-6PBFFD2Q.D4BZ3cVV.js +0 -1
- package/dist/docs/assets/chunks/classDiagram-v2-HSJHXN6E.D4BZ3cVV.js +0 -1
- package/dist/docs/assets/chunks/clone.62tX-O-1.js +0 -1
- package/dist/docs/assets/chunks/stateDiagram-v2-QKLJ7IA2.DkTs1aEU.js +0 -1
- package/dist/docs/assets/reference_cli.md.GwugN3n-.lean.js +0 -1
- package/dist/docs/assets/troubleshooting.md.Bscj6ODe.js +0 -1
- package/dist/playground/assets/index-C1g_PTTZ.css +0 -1
- /package/dist/docs/assets/{building-with-agents.md.BPjPFxFg.lean.js → building-with-agents.md.Df87rAe1.lean.js} +0 -0
- /package/dist/docs/assets/{deployment.md.BdqTiGdc.lean.js → deployment.md.BYuEcDBv.lean.js} +0 -0
package/dist/internal/server.js
CHANGED
|
@@ -4,33 +4,6 @@
|
|
|
4
4
|
* routes, custom channel routes (under `/v1/channels/<id>`), and the
|
|
5
5
|
* server-level health / info / dev-dispatch routes.
|
|
6
6
|
*/
|
|
7
|
-
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
|
|
8
|
-
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
|
|
9
|
-
return new (P || (P = Promise))(function (resolve, reject) {
|
|
10
|
-
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
|
|
11
|
-
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
|
|
12
|
-
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
|
|
13
|
-
step((generator = generator.apply(thisArg, _arguments || [])).next());
|
|
14
|
-
});
|
|
15
|
-
};
|
|
16
|
-
var __rest = (this && this.__rest) || function (s, e) {
|
|
17
|
-
var t = {};
|
|
18
|
-
for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p) && e.indexOf(p) < 0)
|
|
19
|
-
t[p] = s[p];
|
|
20
|
-
if (s != null && typeof Object.getOwnPropertySymbols === "function")
|
|
21
|
-
for (var i = 0, p = Object.getOwnPropertySymbols(s); i < p.length; i++) {
|
|
22
|
-
if (e.indexOf(p[i]) < 0 && Object.prototype.propertyIsEnumerable.call(s, p[i]))
|
|
23
|
-
t[p[i]] = s[p[i]];
|
|
24
|
-
}
|
|
25
|
-
return t;
|
|
26
|
-
};
|
|
27
|
-
var __asyncValues = (this && this.__asyncValues) || function (o) {
|
|
28
|
-
if (!Symbol.asyncIterator) throw new TypeError("Symbol.asyncIterator is not defined.");
|
|
29
|
-
var m = o[Symbol.asyncIterator], i;
|
|
30
|
-
return m ? m.call(o) : (o = typeof __values === "function" ? __values(o) : o[Symbol.iterator](), i = {}, verb("next"), verb("throw"), verb("return"), i[Symbol.asyncIterator] = function () { return this; }, i);
|
|
31
|
-
function verb(n) { i[n] = o[n] && function (v) { return new Promise(function (resolve, reject) { v = o[n](v), settle(resolve, reject, v.done, v.value); }); }; }
|
|
32
|
-
function settle(resolve, reject, d, v) { Promise.resolve(v).then(function(v) { resolve({ value: v, done: d }); }, reject); }
|
|
33
|
-
};
|
|
34
7
|
import { Buffer } from "node:buffer";
|
|
35
8
|
import { once } from "node:events";
|
|
36
9
|
import { createServer } from "node:http";
|
|
@@ -58,6 +31,7 @@ import { DeploymentEventRelay, fanOutDeploymentEvent, mergeResolvedSubscriptions
|
|
|
58
31
|
import { CursorEventRelay, formatCursorScmDispatchOutcome, formatCursorScmEventLog, scmEventToGitHubPayload, scmMetadataToOriginPayload, } from "./cursor-event-relay.js";
|
|
59
32
|
import { AGENT_SERVE_GENERATION_ENV_VAR } from "./cursor-relay-core.js";
|
|
60
33
|
import { buildAgentDeployManifest } from "./deploy-manifest.js";
|
|
34
|
+
import { describeError } from "./describe-error.js";
|
|
61
35
|
import { formatDiagnostics, hasErrorDiagnostics, projectInfo } from "./discovery.js";
|
|
62
36
|
import { packageVersion } from "./distribution.js";
|
|
63
37
|
import { docsSiteExists, ensureDocsSiteBundle, renderDocsFile } from "./docs-site.js";
|
|
@@ -66,6 +40,7 @@ import { GUEST_NETWORK_PATH, guestNetworkReadinessResponse } from "./guest-netwo
|
|
|
66
40
|
import { collectGitHubTriggerTargets, collectOriginTriggerTargets, handleAgentServeTrigger, } from "./handleAgentServeTrigger.js";
|
|
67
41
|
import { createHostPlatforms, warmupHostGitHubAuth } from "./host-platforms.js";
|
|
68
42
|
import { registerHostedDeliveryRoute } from "./hosted-delivery.js";
|
|
43
|
+
import { hostedEvalCatalog, hostedSendStart } from "./hosted-eval-cases.js";
|
|
69
44
|
import { HOSTED_DELIVERY_HEADER, HOSTED_GUEST_RETRY_TERMINAL, hostedGuestErrorResponse, } from "./hosted-delivery-protocol.js";
|
|
70
45
|
import { hostedErrorMessageOnly, hostedErrorStackSnippet } from "./hosted-execution-diag.js";
|
|
71
46
|
import { flushHostedExecutionProcessDeath } from "./hosted-execution-flush.js";
|
|
@@ -116,8 +91,7 @@ function localControlPlaneRoute(runtimes) {
|
|
|
116
91
|
return undefined;
|
|
117
92
|
}
|
|
118
93
|
function firstLocalControlPlane(runtimes) {
|
|
119
|
-
|
|
120
|
-
return (_a = runtimes.find(runtime => runtime.controlPlane !== undefined)) === null || _a === void 0 ? void 0 : _a.controlPlane;
|
|
94
|
+
return runtimes.find(runtime => runtime.controlPlane !== undefined)?.controlPlane;
|
|
121
95
|
}
|
|
122
96
|
function isV2Project(project) {
|
|
123
97
|
return project.agent.architecture === "v2";
|
|
@@ -127,181 +101,155 @@ function isV2Project(project) {
|
|
|
127
101
|
* empty slug serves at `/v1/*` and `/playground`; multiple agents each serve
|
|
128
102
|
* under `/<slug>/v1/*` and `/<slug>/playground`, with an index at `/`.
|
|
129
103
|
*/
|
|
130
|
-
export function startServer(
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
104
|
+
export async function startServer(mounts, options = {}) {
|
|
105
|
+
if (mounts.length === 0) {
|
|
106
|
+
throw new Error("startServer requires at least one agent mount");
|
|
107
|
+
}
|
|
108
|
+
const multi = mounts.length > 1 || mounts[0]?.slug !== "";
|
|
109
|
+
for (const { slug, project } of mounts) {
|
|
110
|
+
if (hasErrorDiagnostics(project)) {
|
|
111
|
+
const label = slug === "" ? project.name : `${slug} (${project.name})`;
|
|
112
|
+
throw new Error(`Refusing to serve agent ${label} — it has errors:\n${formatDiagnostics(project.diagnostics.filter(d => d.severity === "error"))}`);
|
|
137
113
|
}
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
114
|
+
}
|
|
115
|
+
const logRing = createLogRing({ capacity: 2000 });
|
|
116
|
+
const logger = logRing.wrap(options.logger ?? ((line) => console.log(line)));
|
|
117
|
+
const dev = options.dev ?? false;
|
|
118
|
+
const playground = options.playground ?? true;
|
|
119
|
+
const hosted = options;
|
|
120
|
+
const noControlPlane = hosted.noControlPlane === true;
|
|
121
|
+
const scmProxyRequested = hosted.cursorScmProxy === true;
|
|
122
|
+
const runSchedules = options.schedules ?? !dev;
|
|
123
|
+
const runReminders = options.reminders ?? !dev;
|
|
124
|
+
// CURSOR_AGENTKIT_VERSION is still the env the SDK reads when building
|
|
125
|
+
// its client-version header (wire prefix remains `agentkit-`). Setting it
|
|
126
|
+
// here makes the internal-only agentkit_enabled gate apply to every turn.
|
|
127
|
+
process.env.CURSOR_AGENTKIT_VERSION ??= packageVersion();
|
|
128
|
+
const authoredOtel = pickAuthoredOtel(mounts.map(m => ({
|
|
129
|
+
slug: m.slug,
|
|
130
|
+
name: m.project.name,
|
|
131
|
+
otel: m.project.otel,
|
|
132
|
+
})));
|
|
133
|
+
const resolveOtelExport = () => resolveOtelConfig({
|
|
134
|
+
authored: authoredOtel,
|
|
135
|
+
serve: options.otel,
|
|
136
|
+
});
|
|
137
|
+
const otelRuntime = createOtelRuntime(resolveOtelExport(), logger, {
|
|
138
|
+
resolveExport: resolveOtelExport,
|
|
139
|
+
});
|
|
140
|
+
const cursorGithubTargets = collectCursorGithubTargets(mounts);
|
|
141
|
+
const cursorOriginTargets = collectCursorOriginTargets(mounts);
|
|
142
|
+
const cursorGithubConfig = resolveCursorGithubEventConfig({
|
|
143
|
+
configured: options.cursorEvents,
|
|
144
|
+
targets: cursorGithubTargets,
|
|
145
|
+
});
|
|
146
|
+
const cursorOriginScope = resolveCursorOriginRepoScope({
|
|
147
|
+
explicitRepos: options.cursorEvents?.repos ?? [],
|
|
148
|
+
targets: cursorOriginTargets,
|
|
149
|
+
});
|
|
150
|
+
const effectiveCursorEvents = options.evals === true
|
|
151
|
+
? undefined
|
|
152
|
+
: (cursorGithubConfig.cursorEvents ??
|
|
153
|
+
(cursorOriginScope === undefined ? undefined : { repos: [...cursorOriginScope.repos] }));
|
|
154
|
+
// One resolution for every Cursor credential consumer (SDK runner, account
|
|
155
|
+
// MCP bridge, SCM event relay, GitHub credentials): explicit option →
|
|
156
|
+
// CURSOR_API_KEY → CURSOR_API_KEY_FILE (or the hosted default path) →
|
|
157
|
+
// CURSOR_SERVICE_ACCOUNT_KEY → stored `agent-sdk login`. SDK / GitHub /
|
|
158
|
+
// account MCP re-read later; Slack and deployment-event relays capture
|
|
159
|
+
// the key at start.
|
|
160
|
+
const accountKey = await resolveApiKey(options.apiKey === undefined ? {} : { apiKey: options.apiKey });
|
|
161
|
+
// First present source wins (we define the order): option → env → file →
|
|
162
|
+
// service account → stored login. Re-read so bind can drop env or file
|
|
163
|
+
// onto a listening process.
|
|
164
|
+
const liveCursorApiKey = () => resolveApiKeySync(options.apiKey === undefined ? {} : { apiKey: options.apiKey })?.apiKey;
|
|
165
|
+
// The SCM relay reads the event stream as the signed-in user, so
|
|
166
|
+
// `cursorEvents` cannot work signed out. Require the credential before any
|
|
167
|
+
// slow or stateful startup work (playground build, channel onStart, cron).
|
|
168
|
+
// Not on a hosted v2 guest: under `--no-control-plane` with no v1 mount
|
|
169
|
+
// this process never polls `/v0/scm-events` (the hosted control plane
|
|
170
|
+
// owns ingest), and a persistent slot starts before its first credential
|
|
171
|
+
// is bound, so demanding one here would refuse a guest that never needed it.
|
|
172
|
+
const hostedControlPlaneOwnsIngest = noControlPlane && mounts.every(mount => isV2Project(mount.project));
|
|
173
|
+
const cursorEventsAuth = effectiveCursorEvents === undefined || hostedControlPlaneOwnsIngest
|
|
174
|
+
? undefined
|
|
175
|
+
: requireCursorEventsAuth(effectiveCursorEvents, accountKey);
|
|
176
|
+
// Two architectures + local CP + cursor-events would register two
|
|
177
|
+
// /v0/scm-events consumers (engine offset vs control-plane offset).
|
|
178
|
+
// Fail before engines or relays start. `--no-control-plane` mixed is one
|
|
179
|
+
// July consumer (v1 engine); hosted CP owns v2.
|
|
180
|
+
assertSingleCursorEventsLane({
|
|
181
|
+
noControlPlane,
|
|
182
|
+
config: effectiveCursorEvents,
|
|
183
|
+
auth: cursorEventsAuth,
|
|
184
|
+
v1Mounts: mounts.filter(mount => !isV2Project(mount.project)),
|
|
185
|
+
v2Mounts: mounts.filter(mount => isV2Project(mount.project)),
|
|
186
|
+
githubRepos: cursorGithubConfig.cursorEvents?.repos ?? [],
|
|
187
|
+
originRepos: cursorOriginScope?.repos ?? [],
|
|
188
|
+
});
|
|
189
|
+
let cursorGithubCredentials;
|
|
190
|
+
// Proxy-wired GitHub token resolver for `ctx.host.github` when no startup
|
|
191
|
+
// token mint runs (empty repo scope): host Octokit calls must route
|
|
192
|
+
// through github-proxy exactly like channel Octokit calls.
|
|
193
|
+
let hostGithubProxyResolver;
|
|
194
|
+
if (cursorGithubTargets.length > 0) {
|
|
195
|
+
const githubProxyRequested = scmProxyRequested;
|
|
196
|
+
const needsGithubAuth = cursorGithubConfig.scope !== undefined || githubProxyRequested;
|
|
197
|
+
if (needsGithubAuth) {
|
|
198
|
+
const currentGithubApiKey = () => {
|
|
199
|
+
const fromEvents = cursorEventsAuth?.apiKey.trim();
|
|
200
|
+
if (fromEvents !== undefined && fromEvents !== "") {
|
|
201
|
+
return fromEvents;
|
|
202
|
+
}
|
|
203
|
+
return liveCursorApiKey();
|
|
204
|
+
};
|
|
205
|
+
const githubApiKeyNow = currentGithubApiKey();
|
|
206
|
+
if (githubApiKeyNow === undefined && options.allowAnonymous !== true) {
|
|
207
|
+
throw new Error("githubChannel({ cursorAccount: true }) could not resolve Cursor event authentication.");
|
|
143
208
|
}
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
const dev = (_h = options.dev) !== null && _h !== void 0 ? _h : false;
|
|
148
|
-
const playground = (_j = options.playground) !== null && _j !== void 0 ? _j : true;
|
|
149
|
-
const hosted = options;
|
|
150
|
-
const noControlPlane = hosted.noControlPlane === true;
|
|
151
|
-
const scmProxyRequested = hosted.cursorScmProxy === true;
|
|
152
|
-
const runSchedules = (_k = options.schedules) !== null && _k !== void 0 ? _k : !dev;
|
|
153
|
-
const runReminders = (_l = options.reminders) !== null && _l !== void 0 ? _l : !dev;
|
|
154
|
-
// CURSOR_AGENTKIT_VERSION is still the env the SDK reads when building
|
|
155
|
-
// its client-version header (wire prefix remains `agentkit-`). Setting it
|
|
156
|
-
// here makes the internal-only agentkit_enabled gate apply to every turn.
|
|
157
|
-
(_m = (_7 = process.env).CURSOR_AGENTKIT_VERSION) !== null && _m !== void 0 ? _m : (_7.CURSOR_AGENTKIT_VERSION = packageVersion());
|
|
158
|
-
const authoredOtel = pickAuthoredOtel(mounts.map(m => ({
|
|
159
|
-
slug: m.slug,
|
|
160
|
-
name: m.project.name,
|
|
161
|
-
otel: m.project.otel,
|
|
162
|
-
})));
|
|
163
|
-
const resolveOtelExport = () => resolveOtelConfig({
|
|
164
|
-
authored: authoredOtel,
|
|
165
|
-
serve: options.otel,
|
|
166
|
-
});
|
|
167
|
-
const otelRuntime = createOtelRuntime(resolveOtelExport(), logger, {
|
|
168
|
-
resolveExport: resolveOtelExport,
|
|
169
|
-
});
|
|
170
|
-
const cursorGithubTargets = collectCursorGithubTargets(mounts);
|
|
171
|
-
const cursorOriginTargets = collectCursorOriginTargets(mounts);
|
|
172
|
-
const cursorGithubConfig = resolveCursorGithubEventConfig({
|
|
173
|
-
configured: options.cursorEvents,
|
|
174
|
-
targets: cursorGithubTargets,
|
|
175
|
-
});
|
|
176
|
-
const cursorOriginScope = resolveCursorOriginRepoScope({
|
|
177
|
-
explicitRepos: (_o = (_b = options.cursorEvents) === null || _b === void 0 ? void 0 : _b.repos) !== null && _o !== void 0 ? _o : [],
|
|
178
|
-
targets: cursorOriginTargets,
|
|
179
|
-
});
|
|
180
|
-
const effectiveCursorEvents = options.evals === true
|
|
181
|
-
? undefined
|
|
182
|
-
: ((_p = cursorGithubConfig.cursorEvents) !== null && _p !== void 0 ? _p : (cursorOriginScope === undefined ? undefined : { repos: [...cursorOriginScope.repos] }));
|
|
183
|
-
// One resolution for every Cursor credential consumer (SDK runner, account
|
|
184
|
-
// MCP bridge, SCM event relay, GitHub credentials): explicit option →
|
|
185
|
-
// CURSOR_API_KEY → CURSOR_API_KEY_FILE (or the hosted default path) →
|
|
186
|
-
// CURSOR_SERVICE_ACCOUNT_KEY → stored `agent-sdk login`. SDK / GitHub /
|
|
187
|
-
// account MCP re-read later; Slack and deployment-event relays capture
|
|
188
|
-
// the key at start.
|
|
189
|
-
const accountKey = yield resolveApiKey(options.apiKey === undefined ? {} : { apiKey: options.apiKey });
|
|
190
|
-
// First present source wins (we define the order): option → env → file →
|
|
191
|
-
// service account → stored login. Re-read so bind can drop env or file
|
|
192
|
-
// onto a listening process.
|
|
193
|
-
const liveCursorApiKey = () => { var _a; return (_a = resolveApiKeySync(options.apiKey === undefined ? {} : { apiKey: options.apiKey })) === null || _a === void 0 ? void 0 : _a.apiKey; };
|
|
194
|
-
// The SCM relay reads the event stream as the signed-in user, so
|
|
195
|
-
// `cursorEvents` cannot work signed out. Require the credential before any
|
|
196
|
-
// slow or stateful startup work (playground build, channel onStart, cron).
|
|
197
|
-
// Not on a hosted v2 guest: under `--no-control-plane` with no v1 mount
|
|
198
|
-
// this process never polls `/v0/scm-events` (the hosted control plane
|
|
199
|
-
// owns ingest), and a persistent slot starts before its first credential
|
|
200
|
-
// is bound, so demanding one here would refuse a guest that never needed it.
|
|
201
|
-
const hostedControlPlaneOwnsIngest = noControlPlane && mounts.every(mount => isV2Project(mount.project));
|
|
202
|
-
const cursorEventsAuth = effectiveCursorEvents === undefined || hostedControlPlaneOwnsIngest
|
|
203
|
-
? undefined
|
|
204
|
-
: requireCursorEventsAuth(effectiveCursorEvents, accountKey);
|
|
205
|
-
// Two architectures + local CP + cursor-events would register two
|
|
206
|
-
// /v0/scm-events consumers (engine offset vs control-plane offset).
|
|
207
|
-
// Fail before engines or relays start. `--no-control-plane` mixed is one
|
|
208
|
-
// July consumer (v1 engine); hosted CP owns v2.
|
|
209
|
-
assertSingleCursorEventsLane({
|
|
210
|
-
noControlPlane,
|
|
211
|
-
config: effectiveCursorEvents,
|
|
212
|
-
auth: cursorEventsAuth,
|
|
213
|
-
v1Mounts: mounts.filter(mount => !isV2Project(mount.project)),
|
|
214
|
-
v2Mounts: mounts.filter(mount => isV2Project(mount.project)),
|
|
215
|
-
githubRepos: (_q = (_c = cursorGithubConfig.cursorEvents) === null || _c === void 0 ? void 0 : _c.repos) !== null && _q !== void 0 ? _q : [],
|
|
216
|
-
originRepos: (_r = cursorOriginScope === null || cursorOriginScope === void 0 ? void 0 : cursorOriginScope.repos) !== null && _r !== void 0 ? _r : [],
|
|
217
|
-
});
|
|
218
|
-
let cursorGithubCredentials;
|
|
219
|
-
// Proxy-wired GitHub token resolver for `ctx.host.github` when no startup
|
|
220
|
-
// token mint runs (empty repo scope): host Octokit calls must route
|
|
221
|
-
// through github-proxy exactly like channel Octokit calls.
|
|
222
|
-
let hostGithubProxyResolver;
|
|
223
|
-
if (cursorGithubTargets.length > 0) {
|
|
224
|
-
const githubProxyRequested = scmProxyRequested;
|
|
225
|
-
const needsGithubAuth = cursorGithubConfig.scope !== undefined || githubProxyRequested;
|
|
226
|
-
if (needsGithubAuth) {
|
|
227
|
-
const currentGithubApiKey = () => {
|
|
228
|
-
const fromEvents = cursorEventsAuth === null || cursorEventsAuth === void 0 ? void 0 : cursorEventsAuth.apiKey.trim();
|
|
229
|
-
if (fromEvents !== undefined && fromEvents !== "") {
|
|
230
|
-
return fromEvents;
|
|
231
|
-
}
|
|
232
|
-
return liveCursorApiKey();
|
|
233
|
-
};
|
|
234
|
-
const githubApiKeyNow = currentGithubApiKey();
|
|
235
|
-
if (githubApiKeyNow === undefined && options.allowAnonymous !== true) {
|
|
236
|
-
throw new Error("githubChannel({ cursorAccount: true }) could not resolve Cursor event authentication.");
|
|
209
|
+
if (options.allowAnonymous === true) {
|
|
210
|
+
if (options.allowAnonymousCursorGithub !== true) {
|
|
211
|
+
throw new Error("githubChannel({ cursorAccount: true }) cannot be served with --allow-anonymous because admitted callers could use the signed-in account's GitHub access. If every admitted caller is trusted (e.g. behind an SSO proxy), opt in with --allow-anonymous-cursor-github.");
|
|
237
212
|
}
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
}
|
|
242
|
-
logger("[bdk] warning: --allow-anonymous-cursor-github — every admitted HTTP caller can drive sessions holding the signed-in account's repo-scoped GitHub credential; serve this only behind a trusted boundary (e.g. an SSO proxy)");
|
|
243
|
-
if (githubApiKeyNow === undefined) {
|
|
244
|
-
logger("[bdk] GitHub auth: listening unsigned; credentials bind at task via CURSOR_API_KEY or CURSOR_API_KEY_FILE");
|
|
245
|
-
}
|
|
213
|
+
logger("[bdk] warning: --allow-anonymous-cursor-github — every admitted HTTP caller can drive sessions holding the signed-in account's repo-scoped GitHub credential; serve this only behind a trusted boundary (e.g. an SSO proxy)");
|
|
214
|
+
if (githubApiKeyNow === undefined) {
|
|
215
|
+
logger("[bdk] GitHub auth: listening unsigned; credentials bind at task via CURSOR_API_KEY or CURSOR_API_KEY_FILE");
|
|
246
216
|
}
|
|
247
|
-
|
|
248
|
-
|
|
217
|
+
}
|
|
218
|
+
if (cursorGithubConfig.permissions === "contents-write") {
|
|
219
|
+
logger('[bdk] warning: Cursor GitHub credentials include contents:write — the host holds a push-capable token shared with model-driven tools and untrusted webhook content; prefer permissions: "pr-write" unless this agent must push code');
|
|
220
|
+
}
|
|
221
|
+
const githubAuthBaseUrl = cursorExternalApiUrl();
|
|
222
|
+
const requireGithubApiKey = async () => {
|
|
223
|
+
const apiKey = currentGithubApiKey();
|
|
224
|
+
if (apiKey === undefined) {
|
|
225
|
+
throw new Error(`githubChannel({ cursorAccount: true }) has no Cursor credential yet. ${SIGN_IN_HINT}`);
|
|
249
226
|
}
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
if (cursorGithubConfig.scope !== undefined && githubApiKeyNow !== undefined) {
|
|
262
|
-
const provider = new CursorGithubCredentialProvider({
|
|
263
|
-
client: new CursorBackendClient({
|
|
264
|
-
backendUrl: githubAuthBaseUrl,
|
|
265
|
-
apiKey: githubApiKeyNow,
|
|
266
|
-
getApiKey: () => { var _a; return (_a = currentGithubApiKey()) !== null && _a !== void 0 ? _a : ""; },
|
|
267
|
-
logger,
|
|
268
|
-
}),
|
|
269
|
-
scope: cursorGithubConfig.scope,
|
|
227
|
+
return apiKey;
|
|
228
|
+
};
|
|
229
|
+
const githubProxyUrl = githubProxyRequested
|
|
230
|
+
? resolveCursorGithubProxyUrl(githubAuthBaseUrl)
|
|
231
|
+
: undefined;
|
|
232
|
+
if (cursorGithubConfig.scope !== undefined && githubApiKeyNow !== undefined) {
|
|
233
|
+
const provider = new CursorGithubCredentialProvider({
|
|
234
|
+
client: new CursorBackendClient({
|
|
235
|
+
backendUrl: githubAuthBaseUrl,
|
|
236
|
+
apiKey: githubApiKeyNow,
|
|
237
|
+
getApiKey: () => currentGithubApiKey() ?? "",
|
|
270
238
|
logger,
|
|
271
|
-
})
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
"x-cursor-github-repos": cursorGithubConfig.scope.repos.join(","),
|
|
282
|
-
"x-cursor-github-permissions": cursorGithubConfig.permissions,
|
|
283
|
-
},
|
|
284
|
-
};
|
|
285
|
-
for (const target of cursorGithubTargets) {
|
|
286
|
-
target.accountRuntime.credentials.bind(requireGithubApiKey);
|
|
287
|
-
target.accountRuntime.credentials.bindProxyTransport(proxyTransport);
|
|
288
|
-
}
|
|
289
|
-
logger(`[bdk] GitHub auth: channel API calls route through ${githubProxyUrl}`);
|
|
290
|
-
}
|
|
291
|
-
else {
|
|
292
|
-
for (const target of cursorGithubTargets) {
|
|
293
|
-
target.accountRuntime.credentials.bind(() => provider.getToken());
|
|
294
|
-
}
|
|
295
|
-
}
|
|
296
|
-
}
|
|
297
|
-
else if (githubProxyUrl !== undefined) {
|
|
298
|
-
// `cursorAccount: true` with no `--repo`: skip the startup token mint
|
|
299
|
-
// (it requires a repo list) and let github-proxy take the repo from
|
|
300
|
-
// each Octokit call. Hosted freeze bakes this flag into the template
|
|
301
|
-
// so restore is GitHub-ready once Bind drops the key.
|
|
239
|
+
}),
|
|
240
|
+
scope: cursorGithubConfig.scope,
|
|
241
|
+
logger,
|
|
242
|
+
});
|
|
243
|
+
cursorGithubCredentials = provider;
|
|
244
|
+
if (githubProxyUrl !== undefined) {
|
|
245
|
+
// Proxy mode: channel Octokit calls carry the Cursor API key to the
|
|
246
|
+
// backend forwarder, which swaps in the installation token server-side
|
|
247
|
+
// and logs every call with per-deployment attribution. The provider
|
|
248
|
+
// still runs so child `gh` processes get a `GH_TOKEN` lease (v1).
|
|
302
249
|
const proxyTransport = {
|
|
303
250
|
apiBaseUrl: githubProxyUrl,
|
|
304
251
|
headers: {
|
|
252
|
+
"x-cursor-github-repos": cursorGithubConfig.scope.repos.join(","),
|
|
305
253
|
"x-cursor-github-permissions": cursorGithubConfig.permissions,
|
|
306
254
|
},
|
|
307
255
|
};
|
|
@@ -309,268 +257,311 @@ export function startServer(mounts_1) {
|
|
|
309
257
|
target.accountRuntime.credentials.bind(requireGithubApiKey);
|
|
310
258
|
target.accountRuntime.credentials.bindProxyTransport(proxyTransport);
|
|
311
259
|
}
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
});
|
|
319
|
-
logger(`[bdk] GitHub auth: channel and host API calls route through ${githubProxyUrl} (repo scope per request)`);
|
|
260
|
+
logger(`[bdk] GitHub auth: channel API calls route through ${githubProxyUrl}`);
|
|
261
|
+
}
|
|
262
|
+
else {
|
|
263
|
+
for (const target of cursorGithubTargets) {
|
|
264
|
+
target.accountRuntime.credentials.bind(() => provider.getToken());
|
|
265
|
+
}
|
|
320
266
|
}
|
|
321
267
|
}
|
|
268
|
+
else if (githubProxyUrl !== undefined) {
|
|
269
|
+
// `cursorAccount: true` with no `--repo`: skip the startup token mint
|
|
270
|
+
// (it requires a repo list) and let github-proxy take the repo from
|
|
271
|
+
// each Octokit call. Hosted freeze bakes this flag into the template
|
|
272
|
+
// so restore is GitHub-ready once Bind drops the key.
|
|
273
|
+
const proxyTransport = {
|
|
274
|
+
apiBaseUrl: githubProxyUrl,
|
|
275
|
+
headers: {
|
|
276
|
+
"x-cursor-github-permissions": cursorGithubConfig.permissions,
|
|
277
|
+
},
|
|
278
|
+
};
|
|
279
|
+
for (const target of cursorGithubTargets) {
|
|
280
|
+
target.accountRuntime.credentials.bind(requireGithubApiKey);
|
|
281
|
+
target.accountRuntime.credentials.bindProxyTransport(proxyTransport);
|
|
282
|
+
}
|
|
283
|
+
// `ctx.host.github` gets the same proxy transport; without this it
|
|
284
|
+
// would fall back to ambient auth (env token / `gh`) or an
|
|
285
|
+
// unauthenticated Octokit, and every host GitHub call would 404.
|
|
286
|
+
hostGithubProxyResolver = createCursorGithubProxyHostResolver({
|
|
287
|
+
getApiKey: requireGithubApiKey,
|
|
288
|
+
transport: proxyTransport,
|
|
289
|
+
});
|
|
290
|
+
logger(`[bdk] GitHub auth: channel and host API calls route through ${githubProxyUrl} (repo scope per request)`);
|
|
291
|
+
}
|
|
322
292
|
}
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
293
|
+
}
|
|
294
|
+
// GitLab `cursorAccount` channels: `ctx.gitlab` routes through the SCM
|
|
295
|
+
// proxy in proxy mode. Without `--cursor-scm-proxy` the runtime stays
|
|
296
|
+
// unbound and the handle falls back to direct `GITLAB_TOKEN` access.
|
|
297
|
+
if (scmProxyRequested) {
|
|
298
|
+
const gitlabProxyUrl = resolveCursorGitLabProxyUrl(cursorExternalApiUrl());
|
|
299
|
+
let gitlabTargets = 0;
|
|
300
|
+
for (const mount of mounts) {
|
|
301
|
+
for (const channel of mount.project.channels) {
|
|
302
|
+
const runtime = getGitLabCursorAccountRuntime(channel.definition);
|
|
303
|
+
if (runtime === undefined) {
|
|
304
|
+
continue;
|
|
305
|
+
}
|
|
306
|
+
gitlabTargets += 1;
|
|
307
|
+
const transport = {
|
|
308
|
+
apiBaseUrl: gitlabProxyUrl,
|
|
309
|
+
...(runtime.scope === undefined
|
|
337
310
|
? {}
|
|
338
311
|
: {
|
|
339
312
|
headers: {
|
|
340
313
|
[CURSOR_GITLAB_PROJECTS_HEADER]: runtime.scope.projects.join(","),
|
|
341
314
|
},
|
|
342
|
-
})
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
}
|
|
348
|
-
return apiKey;
|
|
349
|
-
}));
|
|
350
|
-
runtime.credentials.bindProxyTransport(transport);
|
|
351
|
-
}
|
|
352
|
-
}
|
|
353
|
-
if (gitlabTargets > 0) {
|
|
354
|
-
logger(`[bdk] GitLab auth: channel API calls route through ${gitlabProxyUrl}`);
|
|
355
|
-
}
|
|
356
|
-
// Bitbucket `cursorAccount` channels: same shape, `/v0/bitbucket-proxy`
|
|
357
|
-
// with `x-cursor-bitbucket-repos` scope. The proxy picks Cloud 2.0 or
|
|
358
|
-
// the DC host's 1.0 upstream per tenant; the pack picks the dialect
|
|
359
|
-
// from the delivery payload.
|
|
360
|
-
const bitbucketProxyUrl = resolveCursorBitbucketProxyUrl(cursorExternalApiUrl());
|
|
361
|
-
let bitbucketTargets = 0;
|
|
362
|
-
for (const mount of mounts) {
|
|
363
|
-
for (const channel of mount.project.channels) {
|
|
364
|
-
const runtime = getBitbucketCursorAccountRuntime(channel.definition);
|
|
365
|
-
if (runtime === undefined) {
|
|
366
|
-
continue;
|
|
315
|
+
}),
|
|
316
|
+
};
|
|
317
|
+
runtime.credentials.bind(async () => {
|
|
318
|
+
const apiKey = liveCursorApiKey();
|
|
319
|
+
if (apiKey === undefined) {
|
|
320
|
+
throw new Error(`gitlabChannel({ cursorAccount }) has no Cursor credential yet. ${SIGN_IN_HINT}`);
|
|
367
321
|
}
|
|
368
|
-
|
|
369
|
-
|
|
322
|
+
return apiKey;
|
|
323
|
+
});
|
|
324
|
+
runtime.credentials.bindProxyTransport(transport);
|
|
325
|
+
}
|
|
326
|
+
}
|
|
327
|
+
if (gitlabTargets > 0) {
|
|
328
|
+
logger(`[bdk] GitLab auth: channel API calls route through ${gitlabProxyUrl}`);
|
|
329
|
+
}
|
|
330
|
+
// Bitbucket `cursorAccount` channels: same shape, `/v0/bitbucket-proxy`
|
|
331
|
+
// with `x-cursor-bitbucket-repos` scope. The proxy picks Cloud 2.0 or
|
|
332
|
+
// the DC host's 1.0 upstream per tenant; the pack picks the dialect
|
|
333
|
+
// from the delivery payload.
|
|
334
|
+
const bitbucketProxyUrl = resolveCursorBitbucketProxyUrl(cursorExternalApiUrl());
|
|
335
|
+
let bitbucketTargets = 0;
|
|
336
|
+
for (const mount of mounts) {
|
|
337
|
+
for (const channel of mount.project.channels) {
|
|
338
|
+
const runtime = getBitbucketCursorAccountRuntime(channel.definition);
|
|
339
|
+
if (runtime === undefined) {
|
|
340
|
+
continue;
|
|
341
|
+
}
|
|
342
|
+
bitbucketTargets += 1;
|
|
343
|
+
const transport = {
|
|
344
|
+
apiBaseUrl: bitbucketProxyUrl,
|
|
345
|
+
...(runtime.scope === undefined
|
|
370
346
|
? {}
|
|
371
347
|
: {
|
|
372
348
|
headers: {
|
|
373
349
|
[CURSOR_BITBUCKET_REPOS_HEADER]: runtime.scope.repos.join(","),
|
|
374
350
|
},
|
|
375
|
-
})
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
}
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
}
|
|
386
|
-
if (bitbucketTargets > 0) {
|
|
387
|
-
logger(`[bdk] Bitbucket auth: channel API calls route through ${bitbucketProxyUrl}`);
|
|
351
|
+
}),
|
|
352
|
+
};
|
|
353
|
+
runtime.credentials.bind(async () => {
|
|
354
|
+
const apiKey = liveCursorApiKey();
|
|
355
|
+
if (apiKey === undefined) {
|
|
356
|
+
throw new Error(`bitbucketChannel({ cursorAccount }) has no Cursor credential yet. ${SIGN_IN_HINT}`);
|
|
357
|
+
}
|
|
358
|
+
return apiKey;
|
|
359
|
+
});
|
|
360
|
+
runtime.credentials.bindProxyTransport(transport);
|
|
388
361
|
}
|
|
389
362
|
}
|
|
390
|
-
|
|
391
|
-
|
|
363
|
+
if (bitbucketTargets > 0) {
|
|
364
|
+
logger(`[bdk] Bitbucket auth: channel API calls route through ${bitbucketProxyUrl}`);
|
|
365
|
+
}
|
|
366
|
+
}
|
|
367
|
+
const resolvedCursorGithubCredentials = cursorGithubCredentials;
|
|
368
|
+
const hostGithubToken = hostGithubProxyResolver ??
|
|
369
|
+
(resolvedCursorGithubCredentials === undefined
|
|
392
370
|
? undefined
|
|
393
371
|
: createCursorAccountGithubTokenResolver(() => resolvedCursorGithubCredentials.getToken()));
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
372
|
+
const hostPlatforms = createHostPlatforms(hostGithubToken === undefined ? {} : { githubCredentials: { token: hostGithubToken } });
|
|
373
|
+
if (playground) {
|
|
374
|
+
// Auto-build dist/playground when missing (monorepo / source checkout).
|
|
375
|
+
// Published installs already ship the bundle; vite may be absent there.
|
|
376
|
+
await ensurePlaygroundBundle(logger);
|
|
377
|
+
}
|
|
378
|
+
const docsSite = options.docs ?? true;
|
|
379
|
+
if (docsSite) {
|
|
380
|
+
// Same pattern for the documentation site at /docs.
|
|
381
|
+
await ensureDocsSiteBundle(logger);
|
|
382
|
+
}
|
|
383
|
+
if (options.allowAnonymous === true) {
|
|
384
|
+
logger("[bdk] warning: --allow-anonymous admits every HTTP caller as the same principal; use only on a trusted network (e.g. Tailscale) and prefer --bearer-token for shared hosts");
|
|
385
|
+
}
|
|
386
|
+
// v2 hosted engines mirror the SDK conversation store to the deployment's
|
|
387
|
+
// shared file storage so a fresh pod resumes threads instead of forking
|
|
388
|
+
// them. Hosted serve is single-agent; a multi-mount serve (dev) has no
|
|
389
|
+
// single agent name to mint keys under and does not mirror.
|
|
390
|
+
const conversationMirror = mounts.length === 1 && mounts[0] !== undefined
|
|
391
|
+
? selectConversationMirror({ project: mounts[0].project, logger })
|
|
392
|
+
: undefined;
|
|
393
|
+
const sessionRunLog = options.sessionRunLog ??
|
|
394
|
+
(mounts.length === 1 && mounts[0] !== undefined
|
|
416
395
|
? selectSessionRunLogStore({
|
|
417
396
|
project: mounts[0].project,
|
|
418
397
|
logger,
|
|
419
398
|
snapshotLogs: () => logRing.since(),
|
|
420
399
|
})
|
|
421
400
|
: undefined);
|
|
422
|
-
|
|
401
|
+
const startLogStore = options.startLogStore ??
|
|
402
|
+
(mounts.length === 1 && mounts[0] !== undefined
|
|
423
403
|
? selectStartLogStore({
|
|
424
404
|
project: mounts[0].project,
|
|
425
405
|
logger,
|
|
426
406
|
})
|
|
427
407
|
: undefined);
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
}
|
|
436
|
-
yield flushLocalStartLog({ store: startLogStore, startLogId });
|
|
437
|
-
});
|
|
438
|
-
if (sessionRunLog !== undefined) {
|
|
439
|
-
registerSessionRunFatalFlush((error) => __awaiter(this, void 0, void 0, function* () {
|
|
440
|
-
yield flushHostedExecutionProcessDeath({
|
|
441
|
-
flushProcessDeath: (reason, flushError) => sessionRunLog.flushProcessDeath(reason, flushError),
|
|
442
|
-
flushStartLog,
|
|
443
|
-
startLogId: lastStartLogId,
|
|
444
|
-
reason: "uncaught_exception",
|
|
445
|
-
error,
|
|
446
|
-
});
|
|
447
|
-
}));
|
|
448
|
-
}
|
|
449
|
-
const sdkRunner = (_v = options.runner) !== null && _v !== void 0 ? _v : new CursorSdkRunner(Object.assign(Object.assign(Object.assign({}, (options.apiKey === undefined || options.apiKey.trim() === ""
|
|
450
|
-
? {}
|
|
451
|
-
: { apiKey: options.apiKey })), (conversationMirror === undefined ? {} : { conversationMirror })), { logger }));
|
|
452
|
-
// Grok Bot turns run on Cursor's hosted Grok Bot (Sand) harness through
|
|
453
|
-
// the /v0/grokbot session API; the SDK side is a thin remote client. The
|
|
454
|
-
// dispatching runner is only interposed when a mount needs it. An
|
|
455
|
-
// injected test runner receives every turn, grokbot included.
|
|
456
|
-
const hasGrokBotMounts = mounts.some(mount => mount.project.agent.runtime === "grokbot");
|
|
457
|
-
// Fail closed like cursor-account GitHub/MCP: grokbot turns execute on the
|
|
458
|
-
// signed-in account's hosted computer, so anonymous admission needs its
|
|
459
|
-
// own opt-in.
|
|
460
|
-
if (hasGrokBotMounts && options.allowAnonymous === true) {
|
|
461
|
-
if (options.allowAnonymousGrokbot !== true) {
|
|
462
|
-
throw new Error('runtime: "grokbot" cannot be served with --allow-anonymous because admitted callers could drive the signed-in account\'s hosted Grok Bot computer. If every admitted caller is trusted (e.g. behind an SSO proxy), opt in with --allow-anonymous-grokbot.');
|
|
463
|
-
}
|
|
464
|
-
logger("[bdk] warning: --allow-anonymous-grokbot — every admitted HTTP caller can drive turns on the signed-in account's hosted Grok Bot computer; serve this only behind a trusted boundary (e.g. an SSO proxy)");
|
|
408
|
+
// The running claim stamps this before the turn; process-death flush
|
|
409
|
+
// reuses it so the tee snapshot lands under the same ses_ key.
|
|
410
|
+
let lastStartLogId;
|
|
411
|
+
const flushStartLog = async (startLogId) => {
|
|
412
|
+
lastStartLogId = startLogId;
|
|
413
|
+
if (startLogStore === undefined) {
|
|
414
|
+
return;
|
|
465
415
|
}
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
grokbot: new GrokBotRunner({
|
|
477
|
-
apiKey: options.apiKey,
|
|
478
|
-
// `/v0/grokbot` lives on the external API (api.cursor.com), not
|
|
479
|
-
// api2. Prefer that default over the account key's backendUrl.
|
|
480
|
-
// Do not pass stored login as apiKey: that would pin it as an
|
|
481
|
-
// option and beat a later CURSOR_API_KEY_FILE bind.
|
|
482
|
-
backendUrl: cursorExternalApiUrl(),
|
|
483
|
-
}),
|
|
416
|
+
await flushLocalStartLog({ store: startLogStore, startLogId });
|
|
417
|
+
};
|
|
418
|
+
if (sessionRunLog !== undefined) {
|
|
419
|
+
registerSessionRunFatalFlush(async (error) => {
|
|
420
|
+
await flushHostedExecutionProcessDeath({
|
|
421
|
+
flushProcessDeath: (reason, flushError) => sessionRunLog.flushProcessDeath(reason, flushError),
|
|
422
|
+
flushStartLog,
|
|
423
|
+
startLogId: lastStartLogId,
|
|
424
|
+
reason: "uncaught_exception",
|
|
425
|
+
error,
|
|
484
426
|
});
|
|
485
|
-
|
|
427
|
+
});
|
|
428
|
+
}
|
|
429
|
+
const sdkRunner = options.runner ??
|
|
430
|
+
new CursorSdkRunner({
|
|
431
|
+
...(options.apiKey === undefined || options.apiKey.trim() === ""
|
|
432
|
+
? {}
|
|
433
|
+
: { apiKey: options.apiKey }),
|
|
434
|
+
...(conversationMirror === undefined ? {} : { conversationMirror }),
|
|
435
|
+
logger,
|
|
436
|
+
});
|
|
437
|
+
// Grok Bot turns run on Cursor's hosted Grok Bot (Sand) harness through
|
|
438
|
+
// the /v0/grokbot session API; the SDK side is a thin remote client. The
|
|
439
|
+
// dispatching runner is only interposed when a mount needs it. An
|
|
440
|
+
// injected test runner receives every turn, grokbot included.
|
|
441
|
+
const hasGrokBotMounts = mounts.some(mount => mount.project.agent.runtime === "grokbot");
|
|
442
|
+
// Fail closed like cursor-account GitHub/MCP: grokbot turns execute on the
|
|
443
|
+
// signed-in account's hosted computer, so anonymous admission needs its
|
|
444
|
+
// own opt-in.
|
|
445
|
+
if (hasGrokBotMounts && options.allowAnonymous === true) {
|
|
446
|
+
if (options.allowAnonymousGrokbot !== true) {
|
|
447
|
+
throw new Error('runtime: "grokbot" cannot be served with --allow-anonymous because admitted callers could drive the signed-in account\'s hosted Grok Bot computer. If every admitted caller is trusted (e.g. behind an SSO proxy), opt in with --allow-anonymous-grokbot.');
|
|
486
448
|
}
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
//
|
|
492
|
-
// mount
|
|
493
|
-
//
|
|
494
|
-
const
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
449
|
+
logger("[bdk] warning: --allow-anonymous-grokbot — every admitted HTTP caller can drive turns on the signed-in account's hosted Grok Bot computer; serve this only behind a trusted boundary (e.g. an SSO proxy)");
|
|
450
|
+
}
|
|
451
|
+
let runner = sdkRunner;
|
|
452
|
+
if (hasGrokBotMounts && options.runner === undefined) {
|
|
453
|
+
// Cursor-internal client, left out of the npm tarball (`files`); loaded
|
|
454
|
+
// only when a mount needs it, so a published install fails here, before
|
|
455
|
+
// listen, rather than on the first turn.
|
|
456
|
+
const { GrokBotRunner } = await import("./grokbot/runner.js").catch(() => {
|
|
457
|
+
throw new Error('runtime: "grokbot" is not available in the published @cursor/bdk package');
|
|
458
|
+
});
|
|
459
|
+
runner = new RuntimeDispatchingRunner({
|
|
460
|
+
sdkRunner,
|
|
461
|
+
grokbot: new GrokBotRunner({
|
|
462
|
+
apiKey: options.apiKey,
|
|
463
|
+
// `/v0/grokbot` lives on the external API (api.cursor.com), not
|
|
464
|
+
// api2. Prefer that default over the account key's backendUrl.
|
|
465
|
+
// Do not pass stored login as apiKey: that would pin it as an
|
|
466
|
+
// option and beat a later CURSOR_API_KEY_FILE bind.
|
|
467
|
+
backendUrl: cursorExternalApiUrl(),
|
|
468
|
+
}),
|
|
504
469
|
});
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
|
|
470
|
+
logger("[bdk] grokbot runtime: remote client (turns run on the hosted Grok Bot harness)");
|
|
471
|
+
}
|
|
472
|
+
const router = new Router();
|
|
473
|
+
const runtimes = [];
|
|
474
|
+
const channelStopHooks = [];
|
|
475
|
+
let githubAuthWarmed = false;
|
|
476
|
+
// Validate peer MCP connections (defineConnection({ agent })) against the
|
|
477
|
+
// mount set before anything starts: unknown slugs and self-references are
|
|
478
|
+
// authoring errors. URLs resolve after listen, once the port is known.
|
|
479
|
+
const peerRefsBySlug = collectPeerConnections(mounts);
|
|
480
|
+
// Cursor account MCP connections (defineConnection({ cursorAccount: true }))
|
|
481
|
+
// need a signed-in host: their tools execute on the Cursor backend with
|
|
482
|
+
// the account's stored OAuth credentials. Fail fast at serve start instead
|
|
483
|
+
// of at the first turn.
|
|
484
|
+
const accountRefsBySlug = collectCursorAccountConnections(mounts);
|
|
485
|
+
assertAccountConnectionsNotAnonymous({
|
|
486
|
+
allowAnonymous: options.allowAnonymous,
|
|
487
|
+
allowAnonymousCursorAccountMcp: options.allowAnonymousCursorAccountMcp,
|
|
488
|
+
hasAccountConnections: accountRefsBySlug.size > 0,
|
|
489
|
+
});
|
|
490
|
+
if (options.allowAnonymous === true &&
|
|
491
|
+
options.allowAnonymousCursorAccountMcp === true &&
|
|
492
|
+
accountRefsBySlug.size > 0) {
|
|
493
|
+
logger("[bdk] warning: --allow-anonymous-cursor-account-mcp — every admitted HTTP caller can drive Cursor account MCP connectors; serve this only behind a trusted boundary (e.g. an SSO proxy or hosted alias token)");
|
|
494
|
+
}
|
|
495
|
+
let accountBridge;
|
|
496
|
+
if (accountRefsBySlug.size > 0) {
|
|
497
|
+
if (accountKey === undefined && options.allowAnonymous !== true) {
|
|
498
|
+
throw new Error(`This project has Cursor account MCP connections (defineConnection({ cursorAccount: true })) but the host is not signed in to Cursor. ${SIGN_IN_HINT}`);
|
|
509
499
|
}
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
if (accountKey === undefined && options.allowAnonymous !== true) {
|
|
513
|
-
throw new Error(`This project has Cursor account MCP connections (defineConnection({ cursorAccount: true })) but the host is not signed in to Cursor. ${SIGN_IN_HINT}`);
|
|
514
|
-
}
|
|
515
|
-
if (accountKey === undefined) {
|
|
516
|
-
logger("[bdk] Cursor account MCP: listening unsigned; credentials bind at task via CURSOR_API_KEY or CURSOR_API_KEY_FILE");
|
|
517
|
-
}
|
|
518
|
-
accountBridge = new CursorAccountMcpBridge({
|
|
519
|
-
client: new CursorBackendClient({
|
|
520
|
-
backendUrl: (_w = accountKey === null || accountKey === void 0 ? void 0 : accountKey.backendUrl) !== null && _w !== void 0 ? _w : cursorBackendUrl(),
|
|
521
|
-
apiKey: (_x = accountKey === null || accountKey === void 0 ? void 0 : accountKey.apiKey) !== null && _x !== void 0 ? _x : "",
|
|
522
|
-
getApiKey: () => { var _a; return (_a = liveCursorApiKey()) !== null && _a !== void 0 ? _a : ""; },
|
|
523
|
-
logger,
|
|
524
|
-
}),
|
|
525
|
-
logger,
|
|
526
|
-
// Cloud turns reach the bridge through the serve publicUrl; without
|
|
527
|
-
// one, the bridge admits loopback callers only.
|
|
528
|
-
allowRemoteCallers: options.publicUrl !== undefined,
|
|
529
|
-
});
|
|
500
|
+
if (accountKey === undefined) {
|
|
501
|
+
logger("[bdk] Cursor account MCP: listening unsigned; credentials bind at task via CURSOR_API_KEY or CURSOR_API_KEY_FILE");
|
|
530
502
|
}
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
|
|
535
|
-
|
|
536
|
-
? undefined
|
|
537
|
-
: createOtelCollector(otelRuntime, project.name, logger);
|
|
538
|
-
const engine = new SessionEngine({
|
|
539
|
-
project,
|
|
540
|
-
stateRoot,
|
|
541
|
-
runner,
|
|
503
|
+
accountBridge = new CursorAccountMcpBridge({
|
|
504
|
+
client: new CursorBackendClient({
|
|
505
|
+
backendUrl: accountKey?.backendUrl ?? cursorBackendUrl(),
|
|
506
|
+
apiKey: accountKey?.apiKey ?? "",
|
|
507
|
+
getApiKey: () => liveCursorApiKey() ?? "",
|
|
542
508
|
logger,
|
|
543
|
-
|
|
544
|
-
|
|
545
|
-
|
|
509
|
+
}),
|
|
510
|
+
logger,
|
|
511
|
+
// Cloud turns reach the bridge through the serve publicUrl; without
|
|
512
|
+
// one, the bridge admits loopback callers only.
|
|
513
|
+
allowRemoteCallers: options.publicUrl !== undefined,
|
|
514
|
+
});
|
|
515
|
+
}
|
|
516
|
+
for (const { slug, project } of mounts) {
|
|
517
|
+
// Each agent gets an isolated state root so their sessions never mix.
|
|
518
|
+
const baseStateRoot = options.stateRoot ?? join(project.rootDir, ".agent-serve");
|
|
519
|
+
const stateRoot = slug === "" ? baseStateRoot : join(baseStateRoot, slug);
|
|
520
|
+
const otelCollector = otelRuntime === undefined
|
|
521
|
+
? undefined
|
|
522
|
+
: createOtelCollector(otelRuntime, project.name, logger);
|
|
523
|
+
const engine = new SessionEngine({
|
|
524
|
+
project,
|
|
525
|
+
stateRoot,
|
|
526
|
+
runner,
|
|
527
|
+
logger,
|
|
528
|
+
platforms: hostPlatforms,
|
|
529
|
+
otel: otelCollector,
|
|
530
|
+
otelMeter: otelRuntime?.meter,
|
|
531
|
+
});
|
|
532
|
+
engine.oauthCallbackHub.secrets = options.mcpOauthSecrets;
|
|
533
|
+
engine.oauthCallbackHub.secretsBind = {
|
|
534
|
+
slug: slug === "" ? undefined : slug,
|
|
535
|
+
cloudToolsUrl: options.cloudToolsUrl,
|
|
536
|
+
};
|
|
537
|
+
engine.oauthCallbackHub.logger = logger;
|
|
538
|
+
// Rehydrate sessions from the authored storage sink before any
|
|
539
|
+
// channel or route can address them (continuation tokens must resolve).
|
|
540
|
+
await engine.restoreStoredSessions();
|
|
541
|
+
// Resolve a shared harness workspace in the background: on a large repo
|
|
542
|
+
// that takes seconds, and a deployment coming online should absorb it
|
|
543
|
+
// rather than whoever sends the first message. No-op unless the agent
|
|
544
|
+
// pins `local.workspaceDir`.
|
|
545
|
+
void engine.prewarmSharedWorkspace();
|
|
546
|
+
if (!githubAuthWarmed) {
|
|
547
|
+
// Soft ambient GitHub auth check before channel onStart (which may call
|
|
548
|
+
// host.github). Missing auth is a warning only — serve still starts.
|
|
549
|
+
await warmupHostGitHubAuth({
|
|
550
|
+
logger,
|
|
551
|
+
github: engine.host.github,
|
|
552
|
+
...(cursorGithubCredentials === undefined ? {} : { authSource: "cursor-account" }),
|
|
546
553
|
});
|
|
547
|
-
|
|
548
|
-
|
|
549
|
-
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
|
|
553
|
-
|
|
554
|
-
|
|
555
|
-
|
|
556
|
-
|
|
557
|
-
|
|
558
|
-
// rather than whoever sends the first message. No-op unless the agent
|
|
559
|
-
// pins `local.workspaceDir`.
|
|
560
|
-
void engine.prewarmSharedWorkspace();
|
|
561
|
-
if (!githubAuthWarmed) {
|
|
562
|
-
// Soft ambient GitHub auth check before channel onStart (which may call
|
|
563
|
-
// host.github). Missing auth is a warning only — serve still starts.
|
|
564
|
-
yield warmupHostGitHubAuth(Object.assign({ logger, github: engine.host.github }, (cursorGithubCredentials === undefined ? {} : { authSource: "cursor-account" })));
|
|
565
|
-
githubAuthWarmed = true;
|
|
566
|
-
}
|
|
567
|
-
const scheduleRunner = new ScheduleRunner(engine, project, logger);
|
|
568
|
-
let reminderRunner;
|
|
569
|
-
const architecture = project.agent.architecture;
|
|
570
|
-
const useReminderCpProtocol = architecture === "v2";
|
|
571
|
-
const localControlPlane = noControlPlane
|
|
572
|
-
? undefined
|
|
573
|
-
: yield createLocalControlPlane(Object.assign(Object.assign({ fireSchedule: name => scheduleRunner.dispatch(name) }, (useReminderCpProtocol
|
|
554
|
+
githubAuthWarmed = true;
|
|
555
|
+
}
|
|
556
|
+
const scheduleRunner = new ScheduleRunner(engine, project, logger);
|
|
557
|
+
let reminderRunner;
|
|
558
|
+
const architecture = project.agent.architecture;
|
|
559
|
+
const useReminderCpProtocol = architecture === "v2";
|
|
560
|
+
const localControlPlane = noControlPlane
|
|
561
|
+
? undefined
|
|
562
|
+
: await createLocalControlPlane({
|
|
563
|
+
fireSchedule: name => scheduleRunner.dispatch(name),
|
|
564
|
+
...(useReminderCpProtocol
|
|
574
565
|
? {
|
|
575
566
|
executeReminder: descriptor => reminderRunner.executeFire(descriptor),
|
|
576
567
|
}
|
|
@@ -578,498 +569,540 @@ export function startServer(mounts_1) {
|
|
|
578
569
|
fireReminder: (id, fireCount) => reminderRunner.dispatch(id, {
|
|
579
570
|
expectedFireCount: fireCount,
|
|
580
571
|
}),
|
|
581
|
-
})
|
|
582
|
-
|
|
583
|
-
|
|
584
|
-
|
|
585
|
-
: undefined;
|
|
586
|
-
const reminderControlPlane = useReminderCpProtocol
|
|
587
|
-
? (localControlPlane !== null && localControlPlane !== void 0 ? localControlPlane : hostedReminderControlPlane)
|
|
588
|
-
: undefined;
|
|
589
|
-
reminderRunner = new ReminderRunner(engine, logger, timerClient, {
|
|
590
|
-
namedHandlers: (_z = project.reminders) !== null && _z !== void 0 ? _z : [],
|
|
591
|
-
// Architecture v2 rejects anonymous run/until everywhere (local + hosted).
|
|
592
|
-
rejectAnonymousHandlers: rejectAnonymousReminderHandlers({
|
|
593
|
-
noControlPlane,
|
|
594
|
-
architecture,
|
|
595
|
-
}),
|
|
596
|
-
architecture,
|
|
597
|
-
controlPlane: reminderControlPlane,
|
|
598
|
-
// Hosted admissions build their channel args with no request
|
|
599
|
-
// principal (`buildChannelHandlerArgs` → `auth: null`), so a fire that
|
|
600
|
-
// opens a session on a fresh pod uses the same one and a session
|
|
601
|
-
// restored from durable storage still matches.
|
|
602
|
-
hostedGuest: hostedReminderControlPlane === undefined ? undefined : { fireAuth: () => null },
|
|
603
|
-
});
|
|
604
|
-
const evalRuns = new EvalRunStore(project.rootDir, logger,
|
|
605
|
-
// Playground eval history mirrors onto the defineStorage evals table.
|
|
606
|
-
(_d = engine.storage) === null || _d === void 0 ? void 0 : _d.evalRuns());
|
|
607
|
-
// Eval history restores with the same startup step as sessions (reads
|
|
608
|
-
// also hydrate lazily, but doing it here surfaces store problems at
|
|
609
|
-
// boot and makes /v1/dev/evals complete from the first request).
|
|
610
|
-
yield evalRuns.hydrate();
|
|
611
|
-
engine.setReminders({
|
|
612
|
-
create: input => reminderRunner.create(input),
|
|
613
|
-
list: filter => reminderRunner.list(filter),
|
|
614
|
-
cancel: (id, reason) => reminderRunner.cancel(id, reason),
|
|
615
|
-
cancelForContinuation: (token, reason) => reminderRunner.cancelForContinuation(token, reason),
|
|
616
|
-
rekeyContinuation: (channelId, oldToken, newToken) => reminderRunner.rekeyContinuation(channelId, oldToken, newToken),
|
|
617
|
-
dispatch: id => reminderRunner.dispatch(id),
|
|
618
|
-
});
|
|
619
|
-
engine.setEvals({
|
|
620
|
-
list: () => evalRuns.list(),
|
|
621
|
-
get: runId => evalRuns.get(runId),
|
|
622
|
-
listRunsWithActive: () => evalRuns.listRunsWithActive(),
|
|
623
|
-
start: input => evalRuns.start(input),
|
|
624
|
-
cancel: runId => evalRuns.cancel(runId),
|
|
572
|
+
}),
|
|
573
|
+
stateDir: join(stateRoot, "control-plane"),
|
|
574
|
+
armReminderClocks: runReminders,
|
|
575
|
+
logger,
|
|
625
576
|
});
|
|
626
|
-
|
|
627
|
-
|
|
628
|
-
|
|
629
|
-
|
|
630
|
-
|
|
631
|
-
|
|
632
|
-
|
|
633
|
-
|
|
634
|
-
|
|
635
|
-
|
|
577
|
+
const timerClient = noControlPlane ? createCursorHostedPlatformTimers() : localControlPlane;
|
|
578
|
+
const hostedReminderControlPlane = noControlPlane && useReminderCpProtocol
|
|
579
|
+
? createCursorHostedReminderControlPlane()
|
|
580
|
+
: undefined;
|
|
581
|
+
const reminderControlPlane = useReminderCpProtocol
|
|
582
|
+
? (localControlPlane ?? hostedReminderControlPlane)
|
|
583
|
+
: undefined;
|
|
584
|
+
reminderRunner = new ReminderRunner(engine, logger, timerClient, {
|
|
585
|
+
namedHandlers: project.reminders ?? [],
|
|
586
|
+
// Architecture v2 rejects anonymous run/until everywhere (local + hosted).
|
|
587
|
+
rejectAnonymousHandlers: rejectAnonymousReminderHandlers({
|
|
636
588
|
noControlPlane,
|
|
637
|
-
|
|
638
|
-
|
|
639
|
-
|
|
640
|
-
|
|
589
|
+
architecture,
|
|
590
|
+
}),
|
|
591
|
+
architecture,
|
|
592
|
+
controlPlane: reminderControlPlane,
|
|
593
|
+
// Hosted admissions build their channel args with no request
|
|
594
|
+
// principal (`buildChannelHandlerArgs` → `auth: null`), so a fire that
|
|
595
|
+
// opens a session on a fresh pod uses the same one and a session
|
|
596
|
+
// restored from durable storage still matches.
|
|
597
|
+
hostedGuest: hostedReminderControlPlane === undefined ? undefined : { fireAuth: () => null },
|
|
598
|
+
});
|
|
599
|
+
const evalRuns = new EvalRunStore(project.rootDir, logger,
|
|
600
|
+
// Playground eval history mirrors onto the defineStorage evals table.
|
|
601
|
+
engine.storage?.evalRuns());
|
|
602
|
+
// Eval history restores with the same startup step as sessions (reads
|
|
603
|
+
// also hydrate lazily, but doing it here surfaces store problems at
|
|
604
|
+
// boot and makes /v1/dev/evals complete from the first request).
|
|
605
|
+
await evalRuns.hydrate();
|
|
606
|
+
engine.setReminders({
|
|
607
|
+
create: input => reminderRunner.create(input),
|
|
608
|
+
list: filter => reminderRunner.list(filter),
|
|
609
|
+
cancel: (id, reason) => reminderRunner.cancel(id, reason),
|
|
610
|
+
cancelForContinuation: (token, reason) => reminderRunner.cancelForContinuation(token, reason),
|
|
611
|
+
rekeyContinuation: (channelId, oldToken, newToken) => reminderRunner.rekeyContinuation(channelId, oldToken, newToken),
|
|
612
|
+
dispatch: id => reminderRunner.dispatch(id),
|
|
613
|
+
});
|
|
614
|
+
engine.setEvals({
|
|
615
|
+
list: () => evalRuns.list(),
|
|
616
|
+
get: runId => evalRuns.get(runId),
|
|
617
|
+
listRunsWithActive: () => evalRuns.listRunsWithActive(),
|
|
618
|
+
start: input => evalRuns.start(input),
|
|
619
|
+
cancel: runId => evalRuns.cancel(runId),
|
|
620
|
+
});
|
|
621
|
+
const runtime = {
|
|
622
|
+
slug,
|
|
623
|
+
project,
|
|
624
|
+
engine,
|
|
625
|
+
scheduleRunner,
|
|
626
|
+
reminderRunner,
|
|
627
|
+
evalRuns,
|
|
628
|
+
...(localControlPlane === undefined ? {} : { controlPlane: localControlPlane }),
|
|
629
|
+
...(reminderControlPlane === undefined ? {} : { reminderControlPlane }),
|
|
630
|
+
};
|
|
631
|
+
runtimes.push(runtime);
|
|
632
|
+
const accountRefs = accountRefsBySlug.get(slug);
|
|
633
|
+
mountAgentRoutes(router, runtime, {
|
|
634
|
+
dev,
|
|
635
|
+
playground,
|
|
636
|
+
noControlPlane,
|
|
637
|
+
multi,
|
|
638
|
+
logger,
|
|
639
|
+
logRing,
|
|
640
|
+
diagStateRoot: baseStateRoot,
|
|
641
|
+
sessionRunLog,
|
|
642
|
+
flushStartLog,
|
|
643
|
+
flushOtel: () => forceFlushOtelRuntime(otelRuntime),
|
|
644
|
+
...(options.authToken === undefined ? {} : { authToken: options.authToken }),
|
|
645
|
+
...(options.allowAnonymous === true ? { allowAnonymous: true } : {}),
|
|
646
|
+
...(accountRefs !== undefined && accountBridge !== undefined
|
|
641
647
|
? { accountBridge, accountRefs }
|
|
642
|
-
: {})
|
|
648
|
+
: {}),
|
|
649
|
+
...(sdkRunner instanceof CursorSdkRunner
|
|
643
650
|
? {
|
|
644
651
|
evictSdkSession: (sessionId) => {
|
|
645
652
|
sdkRunner.evictSession(sessionId);
|
|
646
653
|
},
|
|
647
654
|
}
|
|
648
|
-
: {})
|
|
649
|
-
|
|
650
|
-
|
|
651
|
-
|
|
652
|
-
|
|
653
|
-
|
|
654
|
-
if (definition.onStart !== undefined) {
|
|
655
|
-
const startArgs = {
|
|
656
|
-
send: (message, sendOptions) => engine.send(channel.id, message, sendOptions),
|
|
657
|
-
architecture: project.agent.architecture,
|
|
658
|
-
getSession: sessionId => engine.getChannelSession(channel.id, sessionId),
|
|
659
|
-
receive: (target, input) => engine.receive(target, input),
|
|
660
|
-
callTool: (toolName, input, callOptions) => engine.callTool(toolName, input, callOptions),
|
|
661
|
-
emitAssistantMessage: (sessionId, text) => engine.emitAssistantMessage(sessionId, text),
|
|
662
|
-
host: engine.host,
|
|
663
|
-
waitUntil: promise => {
|
|
664
|
-
engine.trackBackground(promise);
|
|
665
|
-
},
|
|
666
|
-
resolveApproval: (sessionId, callId, decision, auth) => engine.resolveApproval(sessionId, callId, decision, auth),
|
|
667
|
-
resolveAuth: (sessionId, callId, decision, auth) => engine.resolveAuth(sessionId, callId, decision, auth),
|
|
668
|
-
hasContinuationSession: continuationToken => engine.hasContinuationSession(channel.id, continuationToken),
|
|
669
|
-
isContinuationBusy: continuationToken => engine.isContinuationBusy(channel.id, continuationToken),
|
|
670
|
-
getContinuationLastBotMessageTs: continuationToken => engine.getContinuationLastBotMessageTs(channel.id, continuationToken),
|
|
671
|
-
interruptContinuation: continuationToken => engine.interruptContinuation(channel.id, continuationToken),
|
|
672
|
-
logger,
|
|
673
|
-
artifacts: engine.artifacts,
|
|
674
|
-
};
|
|
675
|
-
try {
|
|
676
|
-
yield definition.onStart(startArgs);
|
|
677
|
-
}
|
|
678
|
-
catch (error) {
|
|
679
|
-
logger(`[bdk] channel "${channel.id}" onStart failed: ${error instanceof Error ? ((_0 = error.stack) !== null && _0 !== void 0 ? _0 : error.message) : String(error)}`);
|
|
680
|
-
}
|
|
681
|
-
}
|
|
655
|
+
: {}),
|
|
656
|
+
});
|
|
657
|
+
for (const channel of project.channels) {
|
|
658
|
+
const definition = channel.definition;
|
|
659
|
+
if (definition.onStop !== undefined) {
|
|
660
|
+
channelStopHooks.push(() => definition.onStop?.());
|
|
682
661
|
}
|
|
683
|
-
if (
|
|
684
|
-
|
|
685
|
-
|
|
686
|
-
|
|
687
|
-
|
|
662
|
+
if (definition.onStart !== undefined) {
|
|
663
|
+
const startArgs = {
|
|
664
|
+
send: (message, sendOptions) => engine.send(channel.id, message, sendOptions),
|
|
665
|
+
architecture: project.agent.architecture,
|
|
666
|
+
getSession: sessionId => engine.getChannelSession(channel.id, sessionId),
|
|
667
|
+
receive: (target, input) => engine.receive(target, input),
|
|
668
|
+
callTool: (toolName, input, callOptions) => engine.callTool(toolName, input, callOptions),
|
|
669
|
+
emitAssistantMessage: (sessionId, text) => engine.emitAssistantMessage(sessionId, text),
|
|
670
|
+
host: engine.host,
|
|
671
|
+
waitUntil: promise => {
|
|
672
|
+
engine.trackBackground(promise);
|
|
673
|
+
},
|
|
674
|
+
resolveApproval: (sessionId, callId, decision, auth) => engine.resolveApproval(sessionId, callId, decision, auth),
|
|
675
|
+
resolveAuth: (sessionId, callId, decision, auth) => engine.resolveAuth(sessionId, callId, decision, auth),
|
|
676
|
+
hasContinuationSession: continuationToken => engine.hasContinuationSession(channel.id, continuationToken),
|
|
677
|
+
isContinuationBusy: continuationToken => engine.isContinuationBusy(channel.id, continuationToken),
|
|
678
|
+
getContinuationLastBotMessageTs: continuationToken => engine.getContinuationLastBotMessageTs(channel.id, continuationToken),
|
|
679
|
+
interruptContinuation: continuationToken => engine.interruptContinuation(channel.id, continuationToken),
|
|
688
680
|
logger,
|
|
689
|
-
|
|
690
|
-
|
|
691
|
-
|
|
692
|
-
|
|
681
|
+
artifacts: engine.artifacts,
|
|
682
|
+
};
|
|
683
|
+
try {
|
|
684
|
+
await definition.onStart(startArgs);
|
|
685
|
+
}
|
|
686
|
+
catch (error) {
|
|
687
|
+
logger(`[bdk] channel "${channel.id}" onStart failed: ${error instanceof Error ? (error.stack ?? error.message) : String(error)}`);
|
|
688
|
+
}
|
|
693
689
|
}
|
|
694
|
-
localControlPlane === null || localControlPlane === void 0 ? void 0 : localControlPlane.startClocks();
|
|
695
690
|
}
|
|
696
|
-
if (
|
|
697
|
-
|
|
698
|
-
|
|
699
|
-
|
|
700
|
-
|
|
701
|
-
|
|
702
|
-
pattern: "/docs",
|
|
703
|
-
auth: "none",
|
|
704
|
-
handler: () => renderDocsFile(""),
|
|
705
|
-
});
|
|
706
|
-
router.add({
|
|
707
|
-
method: "GET",
|
|
708
|
-
pattern: "/docs/*path",
|
|
709
|
-
auth: "none",
|
|
710
|
-
handler: (_request, ctx) => { var _a; return renderDocsFile((_a = ctx.params.path) !== null && _a !== void 0 ? _a : ""); },
|
|
691
|
+
if (timerClient !== undefined && runSchedules) {
|
|
692
|
+
await syncHostedPlatformSchedules({
|
|
693
|
+
timers: timerClient,
|
|
694
|
+
schedules: project.schedules,
|
|
695
|
+
catalogPath: join(stateRoot, PLATFORM_SCHEDULE_CATALOG_FILENAME),
|
|
696
|
+
logger,
|
|
711
697
|
});
|
|
712
698
|
}
|
|
699
|
+
if (timerClient !== undefined) {
|
|
700
|
+
await reminderRunner.hydrate();
|
|
701
|
+
}
|
|
702
|
+
localControlPlane?.startClocks();
|
|
703
|
+
}
|
|
704
|
+
if (docsSite) {
|
|
705
|
+
// The framework documentation site, mounted at /docs in both layouts
|
|
706
|
+
// ("docs" is a reserved slug). Static, no secrets; unauthenticated like
|
|
707
|
+
// the playground assets.
|
|
708
|
+
router.add({
|
|
709
|
+
method: "GET",
|
|
710
|
+
pattern: "/docs",
|
|
711
|
+
auth: "none",
|
|
712
|
+
handler: () => renderDocsFile(""),
|
|
713
|
+
});
|
|
714
|
+
router.add({
|
|
715
|
+
method: "GET",
|
|
716
|
+
pattern: "/docs/*path",
|
|
717
|
+
auth: "none",
|
|
718
|
+
handler: (_request, ctx) => renderDocsFile(ctx.params.path ?? ""),
|
|
719
|
+
});
|
|
720
|
+
}
|
|
721
|
+
if (multi) {
|
|
722
|
+
// Host-level liveness for ALB / ECS health checks. Per-agent health stays
|
|
723
|
+
// at `/<slug>/v1/health`; without this, multi-mode has no `/v1/health`.
|
|
724
|
+
router.add({
|
|
725
|
+
method: "GET",
|
|
726
|
+
pattern: "/v1/health",
|
|
727
|
+
auth: "none",
|
|
728
|
+
handler: () => jsonResponse({
|
|
729
|
+
ok: true,
|
|
730
|
+
agents: runtimes.map(({ slug, project }) => ({
|
|
731
|
+
slug,
|
|
732
|
+
name: project.name,
|
|
733
|
+
})),
|
|
734
|
+
...memoryHealthFields(runtimes.map(runtime => runtime.engine)),
|
|
735
|
+
}),
|
|
736
|
+
});
|
|
737
|
+
}
|
|
738
|
+
// Orienting `/` for every layout (single empty-slug and multi).
|
|
739
|
+
// Lists playground links plus live routes (playground, health, docs).
|
|
740
|
+
if (playground) {
|
|
741
|
+
const entries = buildAgentIndexEntries(runtimes);
|
|
742
|
+
const indexHtml = renderAgentsIndexHtml(entries, {
|
|
743
|
+
docs: docsSite,
|
|
744
|
+
multi,
|
|
745
|
+
});
|
|
746
|
+
router.add({
|
|
747
|
+
method: "GET",
|
|
748
|
+
pattern: "/",
|
|
749
|
+
auth: "none",
|
|
750
|
+
handler: () => new Response(indexHtml, {
|
|
751
|
+
headers: { "content-type": "text/html; charset=utf-8" },
|
|
752
|
+
}),
|
|
753
|
+
});
|
|
713
754
|
if (multi) {
|
|
714
|
-
// Host-level liveness for ALB / ECS health checks. Per-agent health stays
|
|
715
|
-
// at `/<slug>/v1/health`; without this, multi-mode has no `/v1/health`.
|
|
716
755
|
router.add({
|
|
717
756
|
method: "GET",
|
|
718
|
-
pattern: "/v1/
|
|
757
|
+
pattern: "/v1/agents",
|
|
719
758
|
auth: "none",
|
|
720
|
-
handler: () => jsonResponse(
|
|
721
|
-
slug,
|
|
722
|
-
name: project.name,
|
|
723
|
-
})) }, memoryHealthFields(runtimes.map(runtime => runtime.engine)))),
|
|
759
|
+
handler: () => jsonResponse({ agents: entries }),
|
|
724
760
|
});
|
|
725
761
|
}
|
|
726
|
-
|
|
727
|
-
|
|
728
|
-
|
|
729
|
-
|
|
730
|
-
|
|
731
|
-
|
|
732
|
-
|
|
733
|
-
|
|
734
|
-
|
|
735
|
-
|
|
736
|
-
|
|
737
|
-
|
|
738
|
-
|
|
739
|
-
|
|
740
|
-
|
|
762
|
+
}
|
|
763
|
+
// Shared GitHub webhook ingress: one URL fans out to every mounted agent
|
|
764
|
+
// that declared a github channel. Signature verification is the caller's
|
|
765
|
+
// job (backend `/github_webhook` already verified); we only admit loopback
|
|
766
|
+
// so this route is not a public unsigned endpoint. Agents decide whether
|
|
767
|
+
// to run via their onPullRequest / onComment / … hooks.
|
|
768
|
+
const collectGitHubTargetsFrom = (selected) => collectGitHubTriggerTargets({
|
|
769
|
+
mounts: selected.map(runtime => ({
|
|
770
|
+
slug: runtime.slug,
|
|
771
|
+
channels: runtime.project.channels,
|
|
772
|
+
buildHandlerArgs: channelId => buildChannelHandlerArgs({ runtime, channelId }),
|
|
773
|
+
})),
|
|
774
|
+
});
|
|
775
|
+
const collectOriginTargetsFrom = (selected) => collectOriginTriggerTargets({
|
|
776
|
+
mounts: selected.map(runtime => ({
|
|
777
|
+
slug: runtime.slug,
|
|
778
|
+
channels: runtime.project.channels,
|
|
779
|
+
buildHandlerArgs: channelId => buildChannelHandlerArgs({ runtime, channelId }),
|
|
780
|
+
})),
|
|
781
|
+
});
|
|
782
|
+
const collectGitHubTargets = () => collectGitHubTargetsFrom(runtimes);
|
|
783
|
+
const v1Runtimes = runtimes.filter(runtime => !isV2Project(runtime.project));
|
|
784
|
+
const v2Runtimes = runtimes.filter(runtime => isV2Project(runtime.project));
|
|
785
|
+
const v1Mounts = mounts.filter(mount => !isV2Project(mount.project));
|
|
786
|
+
const v2Mounts = mounts.filter(mount => isV2Project(mount.project));
|
|
787
|
+
router.add({
|
|
788
|
+
method: "POST",
|
|
789
|
+
pattern: "/v1/webhooks/github",
|
|
790
|
+
auth: [localDevStrict()],
|
|
791
|
+
handler: async (request) => {
|
|
792
|
+
const bodyText = await request.text();
|
|
793
|
+
const routeContinuation = localControlPlaneRoute(runtimes);
|
|
794
|
+
const result = await handleAgentServeTrigger({
|
|
795
|
+
type: "github",
|
|
796
|
+
bodyText,
|
|
797
|
+
eventName: request.headers.get("x-github-event"),
|
|
798
|
+
deliveryId: request.headers.get("x-github-delivery") ?? undefined,
|
|
799
|
+
targets: collectGitHubTargets(),
|
|
800
|
+
...(routeContinuation === undefined ? {} : { routeContinuation }),
|
|
741
801
|
});
|
|
742
|
-
|
|
743
|
-
|
|
744
|
-
|
|
745
|
-
|
|
746
|
-
|
|
747
|
-
|
|
748
|
-
|
|
749
|
-
|
|
750
|
-
|
|
751
|
-
|
|
752
|
-
|
|
753
|
-
// job (backend `/github_webhook` already verified); we only admit loopback
|
|
754
|
-
// so this route is not a public unsigned endpoint. Agents decide whether
|
|
755
|
-
// to run via their onPullRequest / onComment / … hooks.
|
|
756
|
-
const collectGitHubTargetsFrom = (selected) => collectGitHubTriggerTargets({
|
|
757
|
-
mounts: selected.map(runtime => ({
|
|
758
|
-
slug: runtime.slug,
|
|
759
|
-
channels: runtime.project.channels,
|
|
760
|
-
buildHandlerArgs: channelId => buildChannelHandlerArgs({ runtime, channelId }),
|
|
761
|
-
})),
|
|
762
|
-
});
|
|
763
|
-
const collectOriginTargetsFrom = (selected) => collectOriginTriggerTargets({
|
|
764
|
-
mounts: selected.map(runtime => ({
|
|
765
|
-
slug: runtime.slug,
|
|
766
|
-
channels: runtime.project.channels,
|
|
767
|
-
buildHandlerArgs: channelId => buildChannelHandlerArgs({ runtime, channelId }),
|
|
768
|
-
})),
|
|
769
|
-
});
|
|
770
|
-
const collectGitHubTargets = () => collectGitHubTargetsFrom(runtimes);
|
|
771
|
-
const v1Runtimes = runtimes.filter(runtime => !isV2Project(runtime.project));
|
|
772
|
-
const v2Runtimes = runtimes.filter(runtime => isV2Project(runtime.project));
|
|
773
|
-
const v1Mounts = mounts.filter(mount => !isV2Project(mount.project));
|
|
774
|
-
const v2Mounts = mounts.filter(mount => isV2Project(mount.project));
|
|
775
|
-
router.add({
|
|
776
|
-
method: "POST",
|
|
777
|
-
pattern: "/v1/webhooks/github",
|
|
778
|
-
auth: [localDevStrict()],
|
|
779
|
-
handler: (request) => __awaiter(this, void 0, void 0, function* () {
|
|
780
|
-
var _a;
|
|
781
|
-
var _b, _c;
|
|
782
|
-
const bodyText = yield request.text();
|
|
783
|
-
const routeContinuation = localControlPlaneRoute(runtimes);
|
|
784
|
-
const result = yield handleAgentServeTrigger(Object.assign({ type: "github", bodyText, eventName: request.headers.get("x-github-event"), deliveryId: (_b = request.headers.get("x-github-delivery")) !== null && _b !== void 0 ? _b : undefined, targets: collectGitHubTargets() }, (routeContinuation === undefined ? {} : { routeContinuation })));
|
|
785
|
-
const status = result.ok ? 202 : ((_c = (_a = result.targets[0]) === null || _a === void 0 ? void 0 : _a.status) !== null && _c !== void 0 ? _c : 500);
|
|
786
|
-
return jsonResponse(result, { status });
|
|
787
|
-
}),
|
|
802
|
+
const status = result.ok ? 202 : (result.targets[0]?.status ?? 500);
|
|
803
|
+
return jsonResponse(result, { status });
|
|
804
|
+
},
|
|
805
|
+
});
|
|
806
|
+
for (const { slug, project } of mounts) {
|
|
807
|
+
validateMountConnectionsConfig({
|
|
808
|
+
slug,
|
|
809
|
+
project,
|
|
810
|
+
accountRefs: accountRefsBySlug.get(slug),
|
|
811
|
+
...(options.publicUrl === undefined ? {} : { publicUrl: options.publicUrl }),
|
|
812
|
+
cloudToolsBridge: options.cloudToolsUrl !== undefined,
|
|
788
813
|
});
|
|
789
|
-
|
|
790
|
-
|
|
791
|
-
|
|
792
|
-
|
|
793
|
-
|
|
794
|
-
|
|
795
|
-
|
|
796
|
-
catch (error) {
|
|
797
|
-
if (options.evals !== true) {
|
|
798
|
-
throw error;
|
|
799
|
-
}
|
|
800
|
-
logger(`[bdk] GitHub auth: no Cursor account credential (${error instanceof Error ? error.message : String(error)}); GitHub calls from eval sessions will fail`);
|
|
814
|
+
}
|
|
815
|
+
try {
|
|
816
|
+
await cursorGithubCredentials?.start();
|
|
817
|
+
}
|
|
818
|
+
catch (error) {
|
|
819
|
+
if (options.evals !== true) {
|
|
820
|
+
throw error;
|
|
801
821
|
}
|
|
802
|
-
|
|
803
|
-
|
|
804
|
-
|
|
805
|
-
|
|
806
|
-
|
|
807
|
-
|
|
808
|
-
|
|
809
|
-
|
|
810
|
-
|
|
811
|
-
|
|
812
|
-
|
|
813
|
-
|
|
814
|
-
|
|
815
|
-
|
|
816
|
-
|
|
817
|
-
|
|
818
|
-
|
|
819
|
-
|
|
820
|
-
|
|
821
|
-
|
|
822
|
-
|
|
823
|
-
|
|
824
|
-
|
|
822
|
+
logger(`[bdk] GitHub auth: no Cursor account credential (${error instanceof Error ? error.message : String(error)}); GitHub calls from eval sessions will fail`);
|
|
823
|
+
}
|
|
824
|
+
// Cursor SCM events: v1 stays on the engine poll. v2 ingest is local-CP
|
|
825
|
+
// only — hosted `--no-control-plane` skips the July poll (hosted CP wakes).
|
|
826
|
+
const routeContinuation = localControlPlaneRoute(runtimes);
|
|
827
|
+
const cursorEventsCommon = {
|
|
828
|
+
config: effectiveCursorEvents,
|
|
829
|
+
githubRepos: cursorGithubConfig.cursorEvents?.repos ?? [],
|
|
830
|
+
originRepos: cursorOriginScope?.repos ?? [],
|
|
831
|
+
stateRoot: options.stateRoot,
|
|
832
|
+
auth: cursorEventsAuth,
|
|
833
|
+
logger,
|
|
834
|
+
cursorAccountOnly: options.cursorEvents === undefined &&
|
|
835
|
+
(cursorGithubTargets.length > 0 || cursorOriginTargets.length > 0),
|
|
836
|
+
};
|
|
837
|
+
const cursorEventRelay = startCursorEventRelay({
|
|
838
|
+
...cursorEventsCommon,
|
|
839
|
+
mounts: v1Mounts,
|
|
840
|
+
collectGitHubTargets: () => collectGitHubTargetsFrom(v1Runtimes),
|
|
841
|
+
collectOriginTargets: () => collectOriginTargetsFrom(v1Runtimes),
|
|
842
|
+
lane: "engine",
|
|
843
|
+
skippedV2: v2Mounts.length > 0,
|
|
844
|
+
});
|
|
845
|
+
const controlPlaneScmRelay = startCursorEventRelay({
|
|
846
|
+
...cursorEventsCommon,
|
|
847
|
+
mounts: v2Mounts,
|
|
848
|
+
collectGitHubTargets: () => collectGitHubTargetsFrom(v2Runtimes),
|
|
849
|
+
collectOriginTargets: () => collectOriginTargetsFrom(v2Runtimes),
|
|
850
|
+
...(routeContinuation === undefined ? {} : { routeContinuation }),
|
|
851
|
+
lane: "control-plane",
|
|
852
|
+
start: false,
|
|
853
|
+
stateDirName: join("control-plane", "cursor-events"),
|
|
854
|
+
});
|
|
855
|
+
const localCp = firstLocalControlPlane(runtimes);
|
|
856
|
+
if (controlPlaneScmRelay !== undefined) {
|
|
857
|
+
if (localCp !== undefined) {
|
|
858
|
+
localCp.attachScmEvents(controlPlaneScmRelay);
|
|
825
859
|
}
|
|
826
|
-
else
|
|
827
|
-
// No credential was required above, so no relay object was built; the
|
|
828
|
-
// outcome is the same skip.
|
|
860
|
+
else {
|
|
829
861
|
logger("[bdk] cursor-events: skipping v2 scm-events poll (--no-control-plane); hosted CP owns ingest");
|
|
830
862
|
}
|
|
831
|
-
|
|
832
|
-
|
|
833
|
-
|
|
834
|
-
|
|
835
|
-
|
|
836
|
-
|
|
837
|
-
|
|
838
|
-
|
|
839
|
-
|
|
840
|
-
|
|
841
|
-
|
|
842
|
-
|
|
843
|
-
|
|
844
|
-
|
|
863
|
+
}
|
|
864
|
+
else if (hostedControlPlaneOwnsIngest && effectiveCursorEvents !== undefined) {
|
|
865
|
+
// No credential was required above, so no relay object was built; the
|
|
866
|
+
// outcome is the same skip.
|
|
867
|
+
logger("[bdk] cursor-events: skipping v2 scm-events poll (--no-control-plane); hosted CP owns ingest");
|
|
868
|
+
}
|
|
869
|
+
// Cursor deployment-events relay: automatic when a mounted agent authored
|
|
870
|
+
// `deploymentsChannel({ ... })`.
|
|
871
|
+
const cursorDeploymentRelay = options.evals === true
|
|
872
|
+
? undefined
|
|
873
|
+
: startCursorDeploymentRelay({
|
|
874
|
+
options,
|
|
875
|
+
runtimes,
|
|
876
|
+
accountKey,
|
|
877
|
+
logger,
|
|
845
878
|
});
|
|
846
|
-
|
|
847
|
-
|
|
848
|
-
|
|
849
|
-
|
|
850
|
-
|
|
851
|
-
|
|
852
|
-
|
|
853
|
-
|
|
854
|
-
|
|
855
|
-
|
|
856
|
-
|
|
857
|
-
|
|
879
|
+
// Trust --public-url's host when a reverse proxy rewrites Host.
|
|
880
|
+
const browserOriginGuard = browserOriginGuardOptionsForPublicUrl(options.publicUrl);
|
|
881
|
+
const server = createServer((req, res) => {
|
|
882
|
+
void instrumentHttp(otelRuntime, req, res, () => handleRequest(router, req, res, logger, browserOriginGuard));
|
|
883
|
+
});
|
|
884
|
+
const host = options.host ?? "127.0.0.1";
|
|
885
|
+
const portWasExplicit = options.port !== undefined;
|
|
886
|
+
const requestedPort = options.port ?? DEFAULT_SERVE_PORT;
|
|
887
|
+
let boundPort;
|
|
888
|
+
try {
|
|
889
|
+
boundPort = await listenHttpServer({
|
|
890
|
+
server,
|
|
891
|
+
host,
|
|
892
|
+
port: requestedPort,
|
|
893
|
+
portWasExplicit,
|
|
894
|
+
logger,
|
|
895
|
+
});
|
|
896
|
+
}
|
|
897
|
+
catch (error) {
|
|
898
|
+
await cursorDeploymentRelay?.stop();
|
|
899
|
+
await cursorEventRelay?.stop();
|
|
900
|
+
await controlPlaneScmRelay?.stop();
|
|
901
|
+
cursorGithubCredentials?.stop();
|
|
902
|
+
await shutdownOtelRuntime(otelRuntime);
|
|
903
|
+
throw error;
|
|
904
|
+
}
|
|
905
|
+
const address = server.address();
|
|
906
|
+
const displayHost = host === "0.0.0.0" || host === "::" ? "127.0.0.1" : host;
|
|
907
|
+
const url = `http://${displayHost}:${boundPort}`;
|
|
908
|
+
const oauthOrigin = (options.publicUrl ?? url).replace(/\/$/, "");
|
|
909
|
+
// Resolve peer + Cursor account MCP connections now that the listen port is
|
|
910
|
+
// known (see resolved-connections.ts for the URL/runtime rules).
|
|
911
|
+
for (const runtime of runtimes) {
|
|
912
|
+
const peerRefs = peerRefsBySlug.get(runtime.slug);
|
|
913
|
+
const accountRefs = accountRefsBySlug.get(runtime.slug);
|
|
914
|
+
const resolved = resolveMountConnections({
|
|
915
|
+
slug: runtime.slug,
|
|
916
|
+
project: runtime.project,
|
|
917
|
+
serverUrl: url,
|
|
918
|
+
...(peerRefs === undefined ? {} : { peerRefs }),
|
|
919
|
+
...(accountRefs !== undefined && accountBridge !== undefined
|
|
920
|
+
? { accountRefs, accountBridge }
|
|
921
|
+
: {}),
|
|
922
|
+
...(options.publicUrl === undefined ? {} : { publicUrl: options.publicUrl }),
|
|
923
|
+
...(options.authToken === undefined ? {} : { authToken: options.authToken }),
|
|
924
|
+
cloudToolsBridge: options.cloudToolsUrl !== undefined,
|
|
925
|
+
logger,
|
|
926
|
+
});
|
|
927
|
+
if (resolved.length > 0) {
|
|
928
|
+
runtime.engine.setResolvedConnections(resolved);
|
|
858
929
|
}
|
|
859
|
-
|
|
860
|
-
|
|
861
|
-
|
|
862
|
-
|
|
863
|
-
|
|
864
|
-
|
|
865
|
-
|
|
930
|
+
const oauthBase = runtime.slug === "" ? "" : `/${runtime.slug}`;
|
|
931
|
+
runtime.engine.setMcpOauthCallbackUrl(mcpOauthServeCallbackUrl(oauthOrigin, oauthBase));
|
|
932
|
+
if (accountBridge !== undefined && accountRefs !== undefined) {
|
|
933
|
+
const bridge = accountBridge;
|
|
934
|
+
runtime.engine.setCursorAccountAuthProbe(async (connection) => {
|
|
935
|
+
const ref = accountRefs.find(entry => entry.name === connection);
|
|
936
|
+
if (ref === undefined) {
|
|
937
|
+
return undefined;
|
|
938
|
+
}
|
|
939
|
+
return await bridge.probeAuthUrl(ref);
|
|
940
|
+
});
|
|
866
941
|
}
|
|
867
|
-
const
|
|
868
|
-
|
|
869
|
-
|
|
870
|
-
const
|
|
871
|
-
|
|
872
|
-
|
|
873
|
-
|
|
874
|
-
const
|
|
875
|
-
|
|
876
|
-
const resolved = resolveMountConnections(Object.assign(Object.assign(Object.assign(Object.assign(Object.assign({ slug: runtime.slug, project: runtime.project, serverUrl: url }, (peerRefs === undefined ? {} : { peerRefs })), (accountRefs !== undefined && accountBridge !== undefined
|
|
877
|
-
? { accountRefs, accountBridge }
|
|
878
|
-
: {})), (options.publicUrl === undefined ? {} : { publicUrl: options.publicUrl })), (options.authToken === undefined ? {} : { authToken: options.authToken })), { cloudToolsBridge: options.cloudToolsUrl !== undefined, logger }));
|
|
879
|
-
if (resolved.length > 0) {
|
|
880
|
-
runtime.engine.setResolvedConnections(resolved);
|
|
881
|
-
}
|
|
882
|
-
const oauthBase = runtime.slug === "" ? "" : `/${runtime.slug}`;
|
|
883
|
-
runtime.engine.setMcpOauthCallbackUrl(mcpOauthServeCallbackUrl(oauthOrigin, oauthBase));
|
|
884
|
-
if (accountBridge !== undefined && accountRefs !== undefined) {
|
|
885
|
-
const bridge = accountBridge;
|
|
886
|
-
runtime.engine.setCursorAccountAuthProbe((connection) => __awaiter(this, void 0, void 0, function* () {
|
|
887
|
-
const ref = accountRefs.find(entry => entry.name === connection);
|
|
888
|
-
if (ref === undefined) {
|
|
889
|
-
return undefined;
|
|
890
|
-
}
|
|
891
|
-
return yield bridge.probeAuthUrl(ref);
|
|
892
|
-
}));
|
|
893
|
-
}
|
|
894
|
-
const hasServerTools = runtime.project.agent.tools.some(tool => tool.execution === "server");
|
|
895
|
-
// Host-bridged connections ride the same endpoint as server tools, so
|
|
896
|
-
// the cloud tools connection is set whenever either needs it.
|
|
897
|
-
const bridgeableNames = runtime.engine.cloudBridgeableConnectionNames();
|
|
898
|
-
const cloudCapable = runtime.project.agent.runtime === "cloud" || runtime.project.agent.cloud !== undefined;
|
|
899
|
-
if ((hasServerTools || bridgeableNames.length > 0) && cloudCapable) {
|
|
900
|
-
const basePath = runtime.slug === "" ? "" : `/${runtime.slug}`;
|
|
901
|
-
const publicToolsUrl = (_6 = options.cloudToolsUrl) !== null && _6 !== void 0 ? _6 : (options.publicUrl === undefined
|
|
942
|
+
const hasServerTools = runtime.project.agent.tools.some(tool => tool.execution === "server");
|
|
943
|
+
// Host-bridged connections ride the same endpoint as server tools, so
|
|
944
|
+
// the cloud tools connection is set whenever either needs it.
|
|
945
|
+
const bridgeableNames = runtime.engine.cloudBridgeableConnectionNames();
|
|
946
|
+
const cloudCapable = runtime.project.agent.runtime === "cloud" || runtime.project.agent.cloud !== undefined;
|
|
947
|
+
if ((hasServerTools || bridgeableNames.length > 0) && cloudCapable) {
|
|
948
|
+
const basePath = runtime.slug === "" ? "" : `/${runtime.slug}`;
|
|
949
|
+
const publicToolsUrl = options.cloudToolsUrl ??
|
|
950
|
+
(options.publicUrl === undefined
|
|
902
951
|
? undefined
|
|
903
952
|
: `${options.publicUrl.replace(/\/$/, "")}${basePath}${TOOL_MCP_ENDPOINT_PATH}`);
|
|
904
|
-
|
|
905
|
-
|
|
906
|
-
|
|
907
|
-
}
|
|
953
|
+
if (publicToolsUrl === undefined) {
|
|
954
|
+
if (hasServerTools) {
|
|
955
|
+
logger(`[bdk] warning: agent "${runtime.project.name}" has server tools but no --public-url or --cloud-tools-url; cloud turns omit those tools.`);
|
|
908
956
|
}
|
|
909
|
-
|
|
910
|
-
|
|
957
|
+
}
|
|
958
|
+
else {
|
|
959
|
+
const resolveCloudToolsHeaders = options.cloudToolsUrl === undefined
|
|
960
|
+
? undefined
|
|
961
|
+
: () => {
|
|
962
|
+
const apiKey = liveCursorApiKey();
|
|
963
|
+
return apiKey === undefined ? undefined : { authorization: `Bearer ${apiKey}` };
|
|
964
|
+
};
|
|
965
|
+
const headers = options.cloudToolsUrl !== undefined
|
|
966
|
+
? resolveCloudToolsHeaders?.()
|
|
967
|
+
: options.authToken === undefined
|
|
911
968
|
? undefined
|
|
912
|
-
:
|
|
913
|
-
|
|
914
|
-
|
|
915
|
-
};
|
|
916
|
-
const headers = options.cloudToolsUrl !== undefined
|
|
917
|
-
? resolveCloudToolsHeaders === null || resolveCloudToolsHeaders === void 0 ? void 0 : resolveCloudToolsHeaders()
|
|
918
|
-
: options.authToken === undefined
|
|
919
|
-
? undefined
|
|
920
|
-
: { authorization: `Bearer ${options.authToken}` };
|
|
921
|
-
if (options.cloudToolsUrl !== undefined && headers === undefined) {
|
|
922
|
-
if (options.allowAnonymous !== true) {
|
|
923
|
-
throw new Error(`Agent "${runtime.project.name}" needs a Cursor API key to authenticate --cloud-tools-url. ${SIGN_IN_HINT}`);
|
|
924
|
-
}
|
|
925
|
-
logger(`[bdk] --cloud-tools-url: listening unsigned; Bearer resolves after task-time credential binding`);
|
|
969
|
+
: { authorization: `Bearer ${options.authToken}` };
|
|
970
|
+
if (options.cloudToolsUrl !== undefined && headers === undefined) {
|
|
971
|
+
if (options.allowAnonymous !== true) {
|
|
972
|
+
throw new Error(`Agent "${runtime.project.name}" needs a Cursor API key to authenticate --cloud-tools-url. ${SIGN_IN_HINT}`);
|
|
926
973
|
}
|
|
927
|
-
|
|
974
|
+
logger(`[bdk] --cloud-tools-url: listening unsigned; Bearer resolves after task-time credential binding`);
|
|
928
975
|
}
|
|
976
|
+
runtime.engine.setCloudServerToolsConnection({
|
|
977
|
+
type: "http",
|
|
978
|
+
url: publicToolsUrl,
|
|
979
|
+
...(headers === undefined ? {} : { headers }),
|
|
980
|
+
}, resolveCloudToolsHeaders);
|
|
929
981
|
}
|
|
930
982
|
}
|
|
931
|
-
|
|
932
|
-
|
|
933
|
-
|
|
934
|
-
|
|
935
|
-
}
|
|
936
|
-
|
|
937
|
-
|
|
938
|
-
}
|
|
939
|
-
|
|
940
|
-
|
|
941
|
-
|
|
942
|
-
|
|
943
|
-
|
|
944
|
-
|
|
945
|
-
|
|
946
|
-
|
|
947
|
-
|
|
948
|
-
|
|
949
|
-
|
|
950
|
-
|
|
951
|
-
|
|
952
|
-
|
|
953
|
-
}
|
|
954
|
-
|
|
955
|
-
|
|
956
|
-
|
|
957
|
-
|
|
958
|
-
|
|
959
|
-
|
|
960
|
-
|
|
961
|
-
|
|
962
|
-
|
|
963
|
-
|
|
964
|
-
|
|
965
|
-
|
|
966
|
-
|
|
967
|
-
|
|
968
|
-
|
|
969
|
-
|
|
970
|
-
|
|
971
|
-
|
|
972
|
-
|
|
973
|
-
|
|
974
|
-
|
|
975
|
-
|
|
976
|
-
|
|
977
|
-
|
|
978
|
-
}
|
|
979
|
-
|
|
980
|
-
|
|
981
|
-
|
|
982
|
-
|
|
983
|
-
|
|
984
|
-
|
|
985
|
-
|
|
986
|
-
|
|
987
|
-
|
|
988
|
-
|
|
989
|
-
|
|
990
|
-
|
|
991
|
-
|
|
992
|
-
|
|
993
|
-
|
|
994
|
-
|
|
995
|
-
|
|
996
|
-
|
|
997
|
-
|
|
998
|
-
|
|
999
|
-
|
|
1000
|
-
|
|
1001
|
-
|
|
1002
|
-
|
|
1003
|
-
|
|
1004
|
-
|
|
1005
|
-
|
|
1006
|
-
|
|
1007
|
-
|
|
1008
|
-
|
|
1009
|
-
|
|
1010
|
-
|
|
1011
|
-
|
|
1012
|
-
|
|
1013
|
-
|
|
1014
|
-
|
|
1015
|
-
|
|
1016
|
-
|
|
1017
|
-
|
|
1018
|
-
|
|
1019
|
-
|
|
1020
|
-
|
|
1021
|
-
|
|
1022
|
-
|
|
1023
|
-
|
|
1024
|
-
|
|
1025
|
-
|
|
1026
|
-
|
|
1027
|
-
reason: "process_exit",
|
|
1028
|
-
});
|
|
1029
|
-
unregisterSessionRunFatalFlush();
|
|
1030
|
-
}
|
|
1031
|
-
playgroundDev === null || playgroundDev === void 0 ? void 0 : playgroundDev.stop();
|
|
1032
|
-
yield cursorDeploymentRelay === null || cursorDeploymentRelay === void 0 ? void 0 : cursorDeploymentRelay.stop();
|
|
1033
|
-
yield cursorEventRelay === null || cursorEventRelay === void 0 ? void 0 : cursorEventRelay.stop();
|
|
1034
|
-
yield controlPlaneScmRelay === null || controlPlaneScmRelay === void 0 ? void 0 : controlPlaneScmRelay.stop();
|
|
1035
|
-
for (const runtime of runtimes) {
|
|
1036
|
-
(_a = runtime.controlPlane) === null || _a === void 0 ? void 0 : _a.stop();
|
|
1037
|
-
runtime.scheduleRunner.stop();
|
|
1038
|
-
runtime.reminderRunner.stop();
|
|
1039
|
-
// Before the engines close their storage coordinators, so the
|
|
1040
|
-
// interrupted-batch write can still reach the sink.
|
|
1041
|
-
runtime.evalRuns.close();
|
|
1042
|
-
}
|
|
1043
|
-
for (const stop of channelStopHooks) {
|
|
1044
|
-
try {
|
|
1045
|
-
yield stop();
|
|
1046
|
-
}
|
|
1047
|
-
catch (error) {
|
|
1048
|
-
logger(`[bdk] channel onStop failed: ${error instanceof Error ? ((_b = error.stack) !== null && _b !== void 0 ? _b : error.message) : String(error)}`);
|
|
1049
|
-
}
|
|
1050
|
-
}
|
|
1051
|
-
server.close();
|
|
1052
|
-
server.closeAllConnections();
|
|
983
|
+
}
|
|
984
|
+
const agents = runtimes.map(({ slug, project }) => ({
|
|
985
|
+
slug,
|
|
986
|
+
project,
|
|
987
|
+
url: slug === "" ? url : `${url}/${slug}`,
|
|
988
|
+
}));
|
|
989
|
+
for (const runtime of runtimes) {
|
|
990
|
+
runtime.evalRuns.loopbackUrl = runtime.slug === "" ? url : `${url}/${runtime.slug}`;
|
|
991
|
+
}
|
|
992
|
+
const runtimeBySlug = new Map(runtimes.map(r => [r.slug, r]));
|
|
993
|
+
// --dev: spawn Vite HMR. Single-agent infers the slug base; multi-agent
|
|
994
|
+
// serves `/<slug>/playground` on the HMR origin (optional AGENT_SERVE_BASE
|
|
995
|
+
// pins one slug at the HMR root instead).
|
|
996
|
+
let playgroundDev;
|
|
997
|
+
if (playground && dev) {
|
|
998
|
+
const pinnedBase = mounts.length === 1 ? playgroundDevBasePath(mounts) : playgroundDevBaseFromEnv();
|
|
999
|
+
const multiAgent = mounts.length > 1 && pinnedBase === undefined;
|
|
1000
|
+
playgroundDev = startPlaygroundDevServer({
|
|
1001
|
+
targetUrl: url,
|
|
1002
|
+
basePath: pinnedBase,
|
|
1003
|
+
multiAgent,
|
|
1004
|
+
logger,
|
|
1005
|
+
});
|
|
1006
|
+
}
|
|
1007
|
+
const handle = {
|
|
1008
|
+
url,
|
|
1009
|
+
port: address.port,
|
|
1010
|
+
project: runtimes[0]?.project,
|
|
1011
|
+
agents,
|
|
1012
|
+
dispatchSchedule: (name, slug = runtimes[0]?.slug ?? "") => {
|
|
1013
|
+
const runtime = runtimeBySlug.get(slug);
|
|
1014
|
+
if (runtime === undefined) {
|
|
1015
|
+
throw new Error(`Unknown agent slug "${slug}"`);
|
|
1016
|
+
}
|
|
1017
|
+
return runtime.scheduleRunner.dispatch(name);
|
|
1018
|
+
},
|
|
1019
|
+
createReminder: (input, slug = runtimes[0]?.slug ?? "") => {
|
|
1020
|
+
const runtime = runtimeBySlug.get(slug);
|
|
1021
|
+
if (runtime === undefined) {
|
|
1022
|
+
throw new Error(`Unknown agent slug "${slug}"`);
|
|
1023
|
+
}
|
|
1024
|
+
return runtime.reminderRunner.create(input);
|
|
1025
|
+
},
|
|
1026
|
+
listReminders: (filter, slug = runtimes[0]?.slug ?? "") => {
|
|
1027
|
+
const runtime = runtimeBySlug.get(slug);
|
|
1028
|
+
if (runtime === undefined) {
|
|
1029
|
+
throw new Error(`Unknown agent slug "${slug}"`);
|
|
1030
|
+
}
|
|
1031
|
+
return runtime.reminderRunner.list(filter);
|
|
1032
|
+
},
|
|
1033
|
+
cancelReminder: (id, options) => {
|
|
1034
|
+
const slug = options?.slug ?? runtimes[0]?.slug ?? "";
|
|
1035
|
+
const runtime = runtimeBySlug.get(slug);
|
|
1036
|
+
if (runtime === undefined) {
|
|
1037
|
+
throw new Error(`Unknown agent slug "${slug}"`);
|
|
1038
|
+
}
|
|
1039
|
+
return runtime.reminderRunner.cancel(id, options?.reason);
|
|
1040
|
+
},
|
|
1041
|
+
dispatchReminder: (id, slug = runtimes[0]?.slug ?? "") => {
|
|
1042
|
+
const runtime = runtimeBySlug.get(slug);
|
|
1043
|
+
if (runtime === undefined) {
|
|
1044
|
+
throw new Error(`Unknown agent slug "${slug}"`);
|
|
1045
|
+
}
|
|
1046
|
+
return runtime.reminderRunner.dispatch(id);
|
|
1047
|
+
},
|
|
1048
|
+
callTool: (toolName, input, options) => {
|
|
1049
|
+
const { slug, ...callOptions } = options ?? {};
|
|
1050
|
+
const runtime = runtimeBySlug.get(slug ?? runtimes[0]?.slug ?? "");
|
|
1051
|
+
if (runtime === undefined) {
|
|
1052
|
+
throw new Error(`Unknown agent slug "${slug}"`);
|
|
1053
|
+
}
|
|
1054
|
+
return runtime.engine.callTool(toolName, input, callOptions);
|
|
1055
|
+
},
|
|
1056
|
+
close: async () => {
|
|
1057
|
+
if (sessionRunLog !== undefined) {
|
|
1058
|
+
await flushHostedExecutionProcessDeath({
|
|
1059
|
+
flushProcessDeath: reason => sessionRunLog.flushProcessDeath(reason),
|
|
1060
|
+
flushStartLog,
|
|
1061
|
+
startLogId: lastStartLogId,
|
|
1062
|
+
reason: "process_exit",
|
|
1063
|
+
});
|
|
1064
|
+
unregisterSessionRunFatalFlush();
|
|
1065
|
+
}
|
|
1066
|
+
playgroundDev?.stop();
|
|
1067
|
+
await cursorDeploymentRelay?.stop();
|
|
1068
|
+
await cursorEventRelay?.stop();
|
|
1069
|
+
await controlPlaneScmRelay?.stop();
|
|
1070
|
+
for (const runtime of runtimes) {
|
|
1071
|
+
runtime.controlPlane?.stop();
|
|
1072
|
+
runtime.scheduleRunner.stop();
|
|
1073
|
+
runtime.reminderRunner.stop();
|
|
1074
|
+
// Before the engines close their storage coordinators, so the
|
|
1075
|
+
// interrupted-batch write can still reach the sink.
|
|
1076
|
+
runtime.evalRuns.close();
|
|
1077
|
+
}
|
|
1078
|
+
for (const stop of channelStopHooks) {
|
|
1053
1079
|
try {
|
|
1054
|
-
|
|
1080
|
+
await stop();
|
|
1055
1081
|
}
|
|
1056
|
-
|
|
1057
|
-
|
|
1058
|
-
yield shutdownOtelRuntime(otelRuntime);
|
|
1082
|
+
catch (error) {
|
|
1083
|
+
logger(`[bdk] channel onStop failed: ${error instanceof Error ? (error.stack ?? error.message) : String(error)}`);
|
|
1059
1084
|
}
|
|
1060
|
-
}
|
|
1061
|
-
|
|
1062
|
-
|
|
1063
|
-
|
|
1064
|
-
|
|
1065
|
-
|
|
1066
|
-
|
|
1067
|
-
|
|
1068
|
-
|
|
1069
|
-
|
|
1085
|
+
}
|
|
1086
|
+
server.close();
|
|
1087
|
+
server.closeAllConnections();
|
|
1088
|
+
try {
|
|
1089
|
+
await Promise.all(runtimes.map(runtime => runtime.engine.close()));
|
|
1090
|
+
}
|
|
1091
|
+
finally {
|
|
1092
|
+
cursorGithubCredentials?.stop();
|
|
1093
|
+
await shutdownOtelRuntime(otelRuntime);
|
|
1094
|
+
}
|
|
1095
|
+
},
|
|
1096
|
+
};
|
|
1097
|
+
if (playgroundDev?.url !== undefined) {
|
|
1098
|
+
handle.playgroundDevUrl = playgroundDev.url;
|
|
1099
|
+
}
|
|
1100
|
+
if (docsSite && docsSiteExists()) {
|
|
1101
|
+
handle.docsUrl = `${handle.url}/docs`;
|
|
1102
|
+
}
|
|
1103
|
+
return handle;
|
|
1070
1104
|
}
|
|
1071
1105
|
function mountAgentRoutes(router, runtime, args) {
|
|
1072
|
-
var _a, _b, _c;
|
|
1073
1106
|
const { slug, project, engine, scheduleRunner, reminderRunner, reminderControlPlane } = runtime;
|
|
1074
1107
|
const { dev, playground, noControlPlane, multi, logger, logRing, sessionRunLog, flushStartLog } = args;
|
|
1075
1108
|
// "" (single) -> "/v1/..."; "my-agent" (multi) -> "/my-agent/v1/...".
|
|
@@ -1084,10 +1117,9 @@ function mountAgentRoutes(router, runtime, args) {
|
|
|
1084
1117
|
pattern: `${base}${cursorAccountMcpPath(":connection")}`,
|
|
1085
1118
|
auth: [bridge.authPolicy()],
|
|
1086
1119
|
handler: (request, ctx) => {
|
|
1087
|
-
|
|
1088
|
-
const ref = refByName.get((_a = ctx.params.connection) !== null && _a !== void 0 ? _a : "");
|
|
1120
|
+
const ref = refByName.get(ctx.params.connection ?? "");
|
|
1089
1121
|
if (ref === undefined) {
|
|
1090
|
-
return errorResponse(404, "not_found", `No Cursor account MCP connection named "${
|
|
1122
|
+
return errorResponse(404, "not_found", `No Cursor account MCP connection named "${ctx.params.connection ?? ""}" on this agent.`);
|
|
1091
1123
|
}
|
|
1092
1124
|
return bridge.handle(request, ref, appendSlackQuietHint);
|
|
1093
1125
|
},
|
|
@@ -1098,7 +1130,7 @@ function mountAgentRoutes(router, runtime, args) {
|
|
|
1098
1130
|
: args.allowAnonymous === true
|
|
1099
1131
|
? [allowAll()]
|
|
1100
1132
|
: [localDevStrict()];
|
|
1101
|
-
const httpAuth =
|
|
1133
|
+
const httpAuth = project.httpChannel.auth ?? defaultAuth;
|
|
1102
1134
|
// Temporal posts this marker on a direct pod port-token fetch. The alias
|
|
1103
1135
|
// proxy strips it, so webhook/playground tokens cannot present it.
|
|
1104
1136
|
const platformTimerFireAuth = request => {
|
|
@@ -1120,24 +1152,28 @@ function mountAgentRoutes(router, runtime, args) {
|
|
|
1120
1152
|
// Local dev, self-hosted serve, and hosted v1 keep the engine mounts.
|
|
1121
1153
|
const hostedV2Guest = noControlPlane === true && isV2Project(project);
|
|
1122
1154
|
if (hostedV2Guest) {
|
|
1123
|
-
registerHostedDeliveryRoute(
|
|
1124
|
-
|
|
1155
|
+
registerHostedDeliveryRoute({
|
|
1156
|
+
router,
|
|
1157
|
+
base,
|
|
1125
1158
|
// The built-in http channel is not in `project.channels`; the control
|
|
1126
1159
|
// plane's session start (`agentsdk.session`) is delivered to it.
|
|
1127
|
-
channels: [...project.channels, { id: HTTP_CHANNEL_ID, definition: project.httpChannel }],
|
|
1160
|
+
channels: [...project.channels, { id: HTTP_CHANNEL_ID, definition: project.httpChannel }],
|
|
1161
|
+
buildHandlerArgs: (channelId, hostedSessionId) => buildChannelHandlerArgs({
|
|
1128
1162
|
runtime,
|
|
1129
1163
|
channelId,
|
|
1130
1164
|
hostedSessionId,
|
|
1131
|
-
}),
|
|
1165
|
+
}),
|
|
1166
|
+
fireReminder: (reminderId, fireCount) => reminderRunner.dispatch(reminderId, { expectedFireCount: fireCount }),
|
|
1167
|
+
executeReminderDescriptor: async (descriptor) => {
|
|
1132
1168
|
if (reminderControlPlane === undefined) {
|
|
1133
1169
|
throw new Error("hosted reminder CP client is not configured");
|
|
1134
1170
|
}
|
|
1135
1171
|
const fireDescriptor = descriptor;
|
|
1136
|
-
const outcome =
|
|
1172
|
+
const outcome = await reminderRunner.executeFire(fireDescriptor);
|
|
1137
1173
|
// applyReminderOutcome is idempotent and internally retries transient
|
|
1138
1174
|
// CP failures. Handler execution is at-least-once across Temporal
|
|
1139
1175
|
// re-admission if apply ultimately fails.
|
|
1140
|
-
const apply =
|
|
1176
|
+
const apply = await reminderControlPlane.applyReminderOutcome(claimFromDescriptor(fireDescriptor), outcome);
|
|
1141
1177
|
// Cancel/rekey winning the race is a successful no-op from the guest's
|
|
1142
1178
|
// perspective (fired work may have run; lifecycle authority stayed CP).
|
|
1143
1179
|
if (!apply.applied && apply.reason === "missing") {
|
|
@@ -1151,14 +1187,22 @@ function mountAgentRoutes(router, runtime, args) {
|
|
|
1151
1187
|
reminderId: descriptor.id,
|
|
1152
1188
|
outcome: "fired",
|
|
1153
1189
|
};
|
|
1154
|
-
}
|
|
1155
|
-
|
|
1156
|
-
:
|
|
1157
|
-
|
|
1158
|
-
|
|
1190
|
+
},
|
|
1191
|
+
drainSessionDurability: sessionId => engine.drainSessionDurability(sessionId),
|
|
1192
|
+
flushOtel: args.flushOtel,
|
|
1193
|
+
...(args.diagStateRoot === undefined ? {} : { stateRoot: args.diagStateRoot }),
|
|
1194
|
+
...(sessionRunLog === undefined
|
|
1195
|
+
? {}
|
|
1196
|
+
: {
|
|
1197
|
+
persistSessionRun: async (document) => {
|
|
1198
|
+
await sessionRunLog.persist(document);
|
|
1199
|
+
},
|
|
1200
|
+
flushStartLog,
|
|
1159
1201
|
}),
|
|
1160
|
-
|
|
1161
|
-
|
|
1202
|
+
snapshotLogs: () => logRing.since(),
|
|
1203
|
+
...(args.evictSdkSession === undefined ? {} : { evictSdkSession: args.evictSdkSession }),
|
|
1204
|
+
startSend: hostedSendStart({ engine, projectRoot: project.rootDir, logger }),
|
|
1205
|
+
});
|
|
1162
1206
|
}
|
|
1163
1207
|
// Playground lists/opens Slack (and other channel) sessions across
|
|
1164
1208
|
// principals when `--dev` (loopback) or `--allow-anonymous` (trusted
|
|
@@ -1184,7 +1228,12 @@ function mountAgentRoutes(router, runtime, args) {
|
|
|
1184
1228
|
// Temporal consumes this response before promoting a newly deployed pod.
|
|
1185
1229
|
// Include the framework build there so deploy observability gets the
|
|
1186
1230
|
// version from the process that will serve turns, with no second request.
|
|
1187
|
-
handler: () => jsonResponse(
|
|
1231
|
+
handler: () => jsonResponse({
|
|
1232
|
+
ok: true,
|
|
1233
|
+
name: project.name,
|
|
1234
|
+
version: packageVersion(),
|
|
1235
|
+
...memoryHealthFields([engine]),
|
|
1236
|
+
}),
|
|
1188
1237
|
});
|
|
1189
1238
|
// TAS polls this after `/v1/health` and before the hosted POST. Auth none
|
|
1190
1239
|
// mirrors health: the engine is reachable only through the anyrun port
|
|
@@ -1201,11 +1250,16 @@ function mountAgentRoutes(router, runtime, args) {
|
|
|
1201
1250
|
method: "GET",
|
|
1202
1251
|
pattern: `${base}/v1/meta`,
|
|
1203
1252
|
auth: "none",
|
|
1204
|
-
handler: () => jsonResponse(
|
|
1253
|
+
handler: () => jsonResponse({
|
|
1254
|
+
name: project.name,
|
|
1255
|
+
dev,
|
|
1256
|
+
basePath: base,
|
|
1205
1257
|
// Framework version, for SPA/engine skew observability now that the
|
|
1206
1258
|
// hosted playground shell is served centrally (backend deploys)
|
|
1207
1259
|
// while engines stay on their install-time version.
|
|
1208
|
-
version: packageVersion()
|
|
1260
|
+
version: packageVersion(),
|
|
1261
|
+
...(multi ? { indexPath: "/" } : {}),
|
|
1262
|
+
}),
|
|
1209
1263
|
});
|
|
1210
1264
|
router.add({
|
|
1211
1265
|
method: "GET",
|
|
@@ -1226,7 +1280,18 @@ function mountAgentRoutes(router, runtime, args) {
|
|
|
1226
1280
|
method: "GET",
|
|
1227
1281
|
pattern: `${base}/v1/manifest`,
|
|
1228
1282
|
auth: "none",
|
|
1229
|
-
handler: () =>
|
|
1283
|
+
handler: async () => {
|
|
1284
|
+
const manifest = buildAgentDeployManifest(project);
|
|
1285
|
+
// A v2 release runs eval cases as its own sessions, so the control
|
|
1286
|
+
// plane lists them from the manifest it freezes.
|
|
1287
|
+
if (isV2Project(project)) {
|
|
1288
|
+
manifest.info.evals = await hostedEvalCatalog(project.rootDir).catch(error => {
|
|
1289
|
+
logger(`[bdk] eval discovery failed; release lists no evals: ${describeError(error)}`);
|
|
1290
|
+
return undefined;
|
|
1291
|
+
});
|
|
1292
|
+
}
|
|
1293
|
+
return jsonResponse(manifest);
|
|
1294
|
+
},
|
|
1230
1295
|
});
|
|
1231
1296
|
}
|
|
1232
1297
|
// Playground Logs tab: recent ServeOptions.logger lines (channels, sessions,
|
|
@@ -1236,8 +1301,6 @@ function mountAgentRoutes(router, runtime, args) {
|
|
|
1236
1301
|
pattern: `${base}/v1/logs`,
|
|
1237
1302
|
auth: httpAuth,
|
|
1238
1303
|
handler: request => {
|
|
1239
|
-
var _a;
|
|
1240
|
-
var _b;
|
|
1241
1304
|
const url = new URL(request.url);
|
|
1242
1305
|
const afterRaw = url.searchParams.get("after");
|
|
1243
1306
|
const after = afterRaw === null || afterRaw === "" ? -1 : Number.parseInt(afterRaw, 10);
|
|
@@ -1245,7 +1308,7 @@ function mountAgentRoutes(router, runtime, args) {
|
|
|
1245
1308
|
const entries = logRing.since(cursor);
|
|
1246
1309
|
return jsonResponse({
|
|
1247
1310
|
entries,
|
|
1248
|
-
nextAfter: entries.length === 0 ? cursor : (
|
|
1311
|
+
nextAfter: entries.length === 0 ? cursor : (entries[entries.length - 1]?.index ?? cursor),
|
|
1249
1312
|
});
|
|
1250
1313
|
},
|
|
1251
1314
|
});
|
|
@@ -1256,7 +1319,7 @@ function mountAgentRoutes(router, runtime, args) {
|
|
|
1256
1319
|
method: "GET",
|
|
1257
1320
|
pattern: `${base}/v1/sessions`,
|
|
1258
1321
|
auth: httpAuth,
|
|
1259
|
-
handler: (request, ctx) =>
|
|
1322
|
+
handler: async (request, ctx) => {
|
|
1260
1323
|
// Same gate as requireSessionOwner / relaxSessionReadAuth: never with
|
|
1261
1324
|
// bearer or custom channel auth (strict per-principal isolation).
|
|
1262
1325
|
// `--allow-anonymous` alone is a shared-principal trust boundary (corp
|
|
@@ -1268,13 +1331,13 @@ function mountAgentRoutes(router, runtime, args) {
|
|
|
1268
1331
|
// binding a default session do not need the cost of a full listing.
|
|
1269
1332
|
const latest = new URL(request.url).searchParams.get("latest");
|
|
1270
1333
|
const listed = latest === "1" || latest === "true"
|
|
1271
|
-
?
|
|
1272
|
-
:
|
|
1334
|
+
? await engine.latestSessionWithSource(ctx.auth, { includeAll })
|
|
1335
|
+
: await engine.listSessionsWithSource(ctx.auth, { includeAll });
|
|
1273
1336
|
return jsonResponse({
|
|
1274
1337
|
sessions: listed.sessions,
|
|
1275
1338
|
source: listed.source,
|
|
1276
1339
|
});
|
|
1277
|
-
}
|
|
1340
|
+
},
|
|
1278
1341
|
});
|
|
1279
1342
|
}
|
|
1280
1343
|
// Durable artifacts (ctx.artifacts / tag_artifact), newest-updated first.
|
|
@@ -1285,8 +1348,7 @@ function mountAgentRoutes(router, runtime, args) {
|
|
|
1285
1348
|
method: "GET",
|
|
1286
1349
|
pattern: `${base}/v1/artifacts`,
|
|
1287
1350
|
auth: httpAuth,
|
|
1288
|
-
handler: (request, ctx) =>
|
|
1289
|
-
var _a, _b, _c;
|
|
1351
|
+
handler: async (request, ctx) => {
|
|
1290
1352
|
const url = new URL(request.url);
|
|
1291
1353
|
const limitRaw = url.searchParams.get("limit");
|
|
1292
1354
|
let limit = ARTIFACTS_PAGE_DEFAULT_LIMIT;
|
|
@@ -1297,9 +1359,9 @@ function mountAgentRoutes(router, runtime, args) {
|
|
|
1297
1359
|
}
|
|
1298
1360
|
limit = Math.min(parsed, ARTIFACTS_PAGE_MAX_LIMIT);
|
|
1299
1361
|
}
|
|
1300
|
-
const kind =
|
|
1301
|
-
const sessionId =
|
|
1302
|
-
const pageToken =
|
|
1362
|
+
const kind = url.searchParams.get("kind") ?? undefined;
|
|
1363
|
+
const sessionId = url.searchParams.get("sessionId") ?? undefined;
|
|
1364
|
+
const pageToken = url.searchParams.get("pageToken") ?? undefined;
|
|
1303
1365
|
// Same includeAll gate as /v1/sessions: never with bearer or custom
|
|
1304
1366
|
// channel auth (strict per-principal isolation); otherwise session
|
|
1305
1367
|
// ownership applies (see SessionEngine.pageArtifacts).
|
|
@@ -1308,7 +1370,7 @@ function mountAgentRoutes(router, runtime, args) {
|
|
|
1308
1370
|
(typeof ctx.requestIp === "string" && isLoopbackAddress(ctx.requestIp)));
|
|
1309
1371
|
let page;
|
|
1310
1372
|
try {
|
|
1311
|
-
page =
|
|
1373
|
+
page = await engine.pageArtifacts(ctx.auth, {
|
|
1312
1374
|
includeAll,
|
|
1313
1375
|
filter: { kind, sessionId },
|
|
1314
1376
|
limit,
|
|
@@ -1325,20 +1387,18 @@ function mountAgentRoutes(router, runtime, args) {
|
|
|
1325
1387
|
artifacts: page.records,
|
|
1326
1388
|
nextPageToken: page.nextCursor,
|
|
1327
1389
|
});
|
|
1328
|
-
}
|
|
1390
|
+
},
|
|
1329
1391
|
});
|
|
1330
1392
|
// File/blob payload of one artifact (`tag({ contents })`).
|
|
1331
1393
|
router.add({
|
|
1332
1394
|
method: "GET",
|
|
1333
1395
|
pattern: `${base}/v1/artifacts/:id/content`,
|
|
1334
1396
|
auth: httpAuth,
|
|
1335
|
-
handler: (_request, ctx) =>
|
|
1336
|
-
var _a;
|
|
1337
|
-
var _b, _c;
|
|
1397
|
+
handler: async (_request, ctx) => {
|
|
1338
1398
|
const includeAll = relaxSessionReadAuth &&
|
|
1339
1399
|
(relaxSessionReadAuthBeyondLoopback ||
|
|
1340
1400
|
(typeof ctx.requestIp === "string" && isLoopbackAddress(ctx.requestIp)));
|
|
1341
|
-
const found =
|
|
1401
|
+
const found = await engine.getArtifactContent(ctx.auth, ctx.params.id ?? "", { includeAll });
|
|
1342
1402
|
if (found === undefined) {
|
|
1343
1403
|
return errorResponse(404, "not_found", "No such artifact, or it carries no content.");
|
|
1344
1404
|
}
|
|
@@ -1348,12 +1408,12 @@ function mountAgentRoutes(router, runtime, args) {
|
|
|
1348
1408
|
// this origin: force download, never render or sniff.
|
|
1349
1409
|
return new Response(found.body, {
|
|
1350
1410
|
headers: {
|
|
1351
|
-
"content-type":
|
|
1411
|
+
"content-type": found.record.content?.contentType ?? "application/octet-stream",
|
|
1352
1412
|
"content-disposition": "attachment",
|
|
1353
1413
|
"x-content-type-options": "nosniff",
|
|
1354
1414
|
},
|
|
1355
1415
|
});
|
|
1356
|
-
}
|
|
1416
|
+
},
|
|
1357
1417
|
});
|
|
1358
1418
|
// The agent's MCP surface: peers and external MCP clients delegate to
|
|
1359
1419
|
// this agent (ask / check / call_tool) over streamable HTTP. Stateless —
|
|
@@ -1383,13 +1443,12 @@ function mountAgentRoutes(router, runtime, args) {
|
|
|
1383
1443
|
// answer. `session` / `continuationToken` query parameters bind the
|
|
1384
1444
|
// listing to a session identity (advertised inventories can be
|
|
1385
1445
|
// tenant-scoped and resolved per session).
|
|
1386
|
-
const resolveCatalogBinding = (request, ctx) =>
|
|
1387
|
-
var _a, _b;
|
|
1446
|
+
const resolveCatalogBinding = async (request, ctx) => {
|
|
1388
1447
|
const url = new URL(request.url);
|
|
1389
|
-
const sessionId =
|
|
1390
|
-
const continuationToken =
|
|
1448
|
+
const sessionId = url.searchParams.get("session") ?? undefined;
|
|
1449
|
+
const continuationToken = url.searchParams.get("continuationToken") ?? undefined;
|
|
1391
1450
|
if (sessionId !== undefined) {
|
|
1392
|
-
const record =
|
|
1451
|
+
const record = await engine.sessions.get(sessionId);
|
|
1393
1452
|
if (record === undefined) {
|
|
1394
1453
|
return {
|
|
1395
1454
|
errorResponse: errorResponse(404, "session_not_found", `Unknown session ${sessionId}.`),
|
|
@@ -1405,18 +1464,21 @@ function mountAgentRoutes(router, runtime, args) {
|
|
|
1405
1464
|
}
|
|
1406
1465
|
}
|
|
1407
1466
|
return { sessionId, continuationToken };
|
|
1408
|
-
}
|
|
1467
|
+
};
|
|
1409
1468
|
router.add({
|
|
1410
1469
|
method: "GET",
|
|
1411
1470
|
pattern: `${base}/v1/tools`,
|
|
1412
1471
|
auth: httpAuth,
|
|
1413
|
-
handler: (request, ctx) =>
|
|
1414
|
-
const binding =
|
|
1472
|
+
handler: async (request, ctx) => {
|
|
1473
|
+
const binding = await resolveCatalogBinding(request, ctx);
|
|
1415
1474
|
if ("errorResponse" in binding) {
|
|
1416
1475
|
return binding.errorResponse;
|
|
1417
1476
|
}
|
|
1418
1477
|
try {
|
|
1419
|
-
const catalog =
|
|
1478
|
+
const catalog = await engine.listCatalogTools({
|
|
1479
|
+
auth: ctx.auth,
|
|
1480
|
+
...binding,
|
|
1481
|
+
});
|
|
1420
1482
|
return jsonResponse({
|
|
1421
1483
|
tools: catalog.tools.map(catalogListEntry),
|
|
1422
1484
|
// JSON.stringify drops the key when there is nothing to report.
|
|
@@ -1429,21 +1491,23 @@ function mountAgentRoutes(router, runtime, args) {
|
|
|
1429
1491
|
}
|
|
1430
1492
|
throw error;
|
|
1431
1493
|
}
|
|
1432
|
-
}
|
|
1494
|
+
},
|
|
1433
1495
|
});
|
|
1434
1496
|
router.add({
|
|
1435
1497
|
method: "GET",
|
|
1436
1498
|
pattern: `${base}/v1/tools/:toolName`,
|
|
1437
1499
|
auth: httpAuth,
|
|
1438
|
-
handler: (request, ctx) =>
|
|
1439
|
-
|
|
1440
|
-
const
|
|
1441
|
-
const binding = yield resolveCatalogBinding(request, ctx);
|
|
1500
|
+
handler: async (request, ctx) => {
|
|
1501
|
+
const toolName = ctx.params.toolName ?? "";
|
|
1502
|
+
const binding = await resolveCatalogBinding(request, ctx);
|
|
1442
1503
|
if ("errorResponse" in binding) {
|
|
1443
1504
|
return binding.errorResponse;
|
|
1444
1505
|
}
|
|
1445
1506
|
try {
|
|
1446
|
-
const described =
|
|
1507
|
+
const described = await engine.describeCatalogTool(toolName, {
|
|
1508
|
+
auth: ctx.auth,
|
|
1509
|
+
...binding,
|
|
1510
|
+
});
|
|
1447
1511
|
if (described.tool === undefined) {
|
|
1448
1512
|
return jsonResponse({
|
|
1449
1513
|
ok: false,
|
|
@@ -1460,7 +1524,7 @@ function mountAgentRoutes(router, runtime, args) {
|
|
|
1460
1524
|
}
|
|
1461
1525
|
throw error;
|
|
1462
1526
|
}
|
|
1463
|
-
}
|
|
1527
|
+
},
|
|
1464
1528
|
});
|
|
1465
1529
|
// Deterministic tool invocation: run an authored server tool directly,
|
|
1466
1530
|
// without a model turn deciding to call it.
|
|
@@ -1468,10 +1532,9 @@ function mountAgentRoutes(router, runtime, args) {
|
|
|
1468
1532
|
method: "POST",
|
|
1469
1533
|
pattern: `${base}/v1/tools/:toolName`,
|
|
1470
1534
|
auth: httpAuth,
|
|
1471
|
-
handler: (request, ctx) =>
|
|
1472
|
-
|
|
1473
|
-
const
|
|
1474
|
-
const body = yield readJsonObject(request);
|
|
1535
|
+
handler: async (request, ctx) => {
|
|
1536
|
+
const toolName = ctx.params.toolName ?? "";
|
|
1537
|
+
const body = await readJsonObject(request);
|
|
1475
1538
|
if (body === undefined) {
|
|
1476
1539
|
return errorResponse(400, "invalid_request", 'Expected a JSON object body, e.g. {"input": {...}}.');
|
|
1477
1540
|
}
|
|
@@ -1484,7 +1547,7 @@ function mountAgentRoutes(router, runtime, args) {
|
|
|
1484
1547
|
const continuationToken = body.continuationToken;
|
|
1485
1548
|
const sessionId = body.sessionId;
|
|
1486
1549
|
if (sessionId !== undefined) {
|
|
1487
|
-
const record =
|
|
1550
|
+
const record = await engine.sessions.get(sessionId);
|
|
1488
1551
|
if (record === undefined) {
|
|
1489
1552
|
return errorResponse(404, "session_not_found", `Unknown session ${sessionId}.`);
|
|
1490
1553
|
}
|
|
@@ -1498,12 +1561,12 @@ function mountAgentRoutes(router, runtime, args) {
|
|
|
1498
1561
|
}
|
|
1499
1562
|
}
|
|
1500
1563
|
try {
|
|
1501
|
-
const outcome =
|
|
1564
|
+
const outcome = await engine.callTool(toolName, body.input ?? {}, {
|
|
1502
1565
|
auth: ctx.auth,
|
|
1503
1566
|
sessionId,
|
|
1504
1567
|
continuationToken,
|
|
1505
1568
|
});
|
|
1506
|
-
return jsonResponse(
|
|
1569
|
+
return jsonResponse({ ok: !outcome.isError, ...outcome });
|
|
1507
1570
|
}
|
|
1508
1571
|
catch (error) {
|
|
1509
1572
|
if (error instanceof UnknownToolError) {
|
|
@@ -1528,7 +1591,7 @@ function mountAgentRoutes(router, runtime, args) {
|
|
|
1528
1591
|
}
|
|
1529
1592
|
throw error;
|
|
1530
1593
|
}
|
|
1531
|
-
}
|
|
1594
|
+
},
|
|
1532
1595
|
});
|
|
1533
1596
|
// Deterministic skill reads: the body the harness would receive, plus
|
|
1534
1597
|
// resolved sibling files. Read-only, so plain GETs with no session binding.
|
|
@@ -1536,21 +1599,18 @@ function mountAgentRoutes(router, runtime, args) {
|
|
|
1536
1599
|
method: "GET",
|
|
1537
1600
|
pattern: `${base}/v1/skills`,
|
|
1538
1601
|
auth: httpAuth,
|
|
1539
|
-
handler: () =>
|
|
1540
|
-
|
|
1541
|
-
skills: project.agent.skills.map(skillListEntry),
|
|
1542
|
-
});
|
|
1602
|
+
handler: async () => jsonResponse({
|
|
1603
|
+
skills: project.agent.skills.map(skillListEntry),
|
|
1543
1604
|
}),
|
|
1544
1605
|
});
|
|
1545
1606
|
router.add({
|
|
1546
1607
|
method: "GET",
|
|
1547
1608
|
pattern: `${base}/v1/skills/:skillName`,
|
|
1548
1609
|
auth: httpAuth,
|
|
1549
|
-
handler: (_request, ctx) =>
|
|
1550
|
-
|
|
1551
|
-
const skillName = (_a = ctx.params.skillName) !== null && _a !== void 0 ? _a : "";
|
|
1610
|
+
handler: async (_request, ctx) => {
|
|
1611
|
+
const skillName = ctx.params.skillName ?? "";
|
|
1552
1612
|
try {
|
|
1553
|
-
return jsonResponse(
|
|
1613
|
+
return jsonResponse(await readSkillDetail(project.agent.skills, skillName));
|
|
1554
1614
|
}
|
|
1555
1615
|
catch (error) {
|
|
1556
1616
|
if (error instanceof UnknownSkillError) {
|
|
@@ -1563,7 +1623,7 @@ function mountAgentRoutes(router, runtime, args) {
|
|
|
1563
1623
|
}
|
|
1564
1624
|
throw error;
|
|
1565
1625
|
}
|
|
1566
|
-
}
|
|
1626
|
+
},
|
|
1567
1627
|
});
|
|
1568
1628
|
if (playground) {
|
|
1569
1629
|
// Serve the built Vite SPA. `<base href>` is injected so the SPA's relative
|
|
@@ -1583,7 +1643,7 @@ function mountAgentRoutes(router, runtime, args) {
|
|
|
1583
1643
|
method: "GET",
|
|
1584
1644
|
pattern: `${base}/playground/assets/:file`,
|
|
1585
1645
|
auth: "none",
|
|
1586
|
-
handler: (_request, ctx) =>
|
|
1646
|
+
handler: (_request, ctx) => renderPlaygroundAsset(ctx.params.file ?? ""),
|
|
1587
1647
|
});
|
|
1588
1648
|
}
|
|
1589
1649
|
// The built-in HTTP session channel (`/v1/session*`, including the
|
|
@@ -1612,32 +1672,38 @@ function mountAgentRoutes(router, runtime, args) {
|
|
|
1612
1672
|
method: "GET",
|
|
1613
1673
|
pattern: `${base}/v1/dev/evals`,
|
|
1614
1674
|
auth: httpAuth,
|
|
1615
|
-
handler: () =>
|
|
1675
|
+
handler: async () => jsonResponse(await runtime.evalRuns.list()),
|
|
1616
1676
|
});
|
|
1617
1677
|
router.add({
|
|
1618
1678
|
method: "GET",
|
|
1619
1679
|
pattern: `${base}/v1/dev/evals/runs`,
|
|
1620
1680
|
auth: httpAuth,
|
|
1621
|
-
handler: () =>
|
|
1622
|
-
return jsonResponse(
|
|
1623
|
-
}
|
|
1681
|
+
handler: async () => {
|
|
1682
|
+
return jsonResponse(await runtime.evalRuns.listRunsWithActive());
|
|
1683
|
+
},
|
|
1624
1684
|
});
|
|
1625
1685
|
router.add({
|
|
1626
1686
|
method: "POST",
|
|
1627
1687
|
pattern: `${base}/v1/dev/evals/runs`,
|
|
1628
1688
|
auth: httpAuth,
|
|
1629
|
-
handler: (request) =>
|
|
1630
|
-
const body =
|
|
1631
|
-
const filterIds = asStringArray(body
|
|
1632
|
-
const tags = asStringArray(body
|
|
1633
|
-
const timeoutMs = asPositiveInt(body
|
|
1634
|
-
const verbose =
|
|
1689
|
+
handler: async (request) => {
|
|
1690
|
+
const body = await readJsonObject(request);
|
|
1691
|
+
const filterIds = asStringArray(body?.filterIds);
|
|
1692
|
+
const tags = asStringArray(body?.tags);
|
|
1693
|
+
const timeoutMs = asPositiveInt(body?.timeoutMs);
|
|
1694
|
+
const verbose = body?.verbose === true;
|
|
1635
1695
|
const authHeader = request.headers.get("authorization");
|
|
1636
|
-
const bearerToken =
|
|
1696
|
+
const bearerToken = authHeader?.toLowerCase().startsWith("bearer ")
|
|
1637
1697
|
? authHeader.slice("bearer ".length).trim()
|
|
1638
1698
|
: undefined;
|
|
1639
1699
|
try {
|
|
1640
|
-
const snap =
|
|
1700
|
+
const snap = await runtime.evalRuns.start({
|
|
1701
|
+
...(filterIds === undefined ? {} : { filterIds }),
|
|
1702
|
+
...(tags === undefined ? {} : { tags }),
|
|
1703
|
+
...(bearerToken === undefined || bearerToken === "" ? {} : { bearerToken }),
|
|
1704
|
+
...(timeoutMs === undefined ? {} : { timeoutMs }),
|
|
1705
|
+
...(verbose ? { verbose: true } : {}),
|
|
1706
|
+
});
|
|
1641
1707
|
return jsonResponse(snap, { status: 202 });
|
|
1642
1708
|
}
|
|
1643
1709
|
catch (error) {
|
|
@@ -1654,31 +1720,29 @@ function mountAgentRoutes(router, runtime, args) {
|
|
|
1654
1720
|
const message = error instanceof Error ? error.message : String(error);
|
|
1655
1721
|
return jsonResponse({ ok: false, error: "eval_start_failed", message }, { status: 400 });
|
|
1656
1722
|
}
|
|
1657
|
-
}
|
|
1723
|
+
},
|
|
1658
1724
|
});
|
|
1659
1725
|
router.add({
|
|
1660
1726
|
method: "GET",
|
|
1661
1727
|
pattern: `${base}/v1/dev/evals/runs/:runId`,
|
|
1662
1728
|
auth: httpAuth,
|
|
1663
|
-
handler: (_request, ctx) =>
|
|
1664
|
-
|
|
1665
|
-
const
|
|
1666
|
-
const snap = yield runtime.evalRuns.get(runId);
|
|
1729
|
+
handler: async (_request, ctx) => {
|
|
1730
|
+
const runId = ctx.params.runId ?? "";
|
|
1731
|
+
const snap = await runtime.evalRuns.get(runId);
|
|
1667
1732
|
if (snap === undefined) {
|
|
1668
1733
|
return jsonResponse({ ok: false, error: "unknown_eval_run" }, { status: 404 });
|
|
1669
1734
|
}
|
|
1670
1735
|
return jsonResponse(snap);
|
|
1671
|
-
}
|
|
1736
|
+
},
|
|
1672
1737
|
});
|
|
1673
1738
|
router.add({
|
|
1674
1739
|
method: "POST",
|
|
1675
1740
|
pattern: `${base}/v1/dev/evals/runs/:runId/cancel`,
|
|
1676
1741
|
auth: httpAuth,
|
|
1677
|
-
handler: (_request, ctx) =>
|
|
1678
|
-
|
|
1679
|
-
const runId = (_a = ctx.params.runId) !== null && _a !== void 0 ? _a : "";
|
|
1742
|
+
handler: async (_request, ctx) => {
|
|
1743
|
+
const runId = ctx.params.runId ?? "";
|
|
1680
1744
|
try {
|
|
1681
|
-
const snap =
|
|
1745
|
+
const snap = await runtime.evalRuns.cancel(runId);
|
|
1682
1746
|
return jsonResponse(snap);
|
|
1683
1747
|
}
|
|
1684
1748
|
catch (error) {
|
|
@@ -1697,18 +1761,17 @@ function mountAgentRoutes(router, runtime, args) {
|
|
|
1697
1761
|
const message = error instanceof Error ? error.message : String(error);
|
|
1698
1762
|
return jsonResponse({ ok: false, error: "eval_cancel_failed", message }, { status: 400 });
|
|
1699
1763
|
}
|
|
1700
|
-
}
|
|
1764
|
+
},
|
|
1701
1765
|
});
|
|
1702
1766
|
if (noControlPlane === true) {
|
|
1703
1767
|
router.add({
|
|
1704
1768
|
method: "POST",
|
|
1705
1769
|
pattern: `${base}/v1/internal/schedules/:scheduleId/fire`,
|
|
1706
1770
|
auth: [platformTimerFireAuth],
|
|
1707
|
-
handler: (_request, ctx) =>
|
|
1708
|
-
|
|
1709
|
-
const scheduleId = (_a = ctx.params.scheduleId) !== null && _a !== void 0 ? _a : "";
|
|
1771
|
+
handler: async (_request, ctx) => {
|
|
1772
|
+
const scheduleId = ctx.params.scheduleId ?? "";
|
|
1710
1773
|
try {
|
|
1711
|
-
const { sessionIds } =
|
|
1774
|
+
const { sessionIds } = await scheduleRunner.dispatch(scheduleId);
|
|
1712
1775
|
return jsonResponse({ scheduleId, sessionIds });
|
|
1713
1776
|
}
|
|
1714
1777
|
catch (error) {
|
|
@@ -1721,19 +1784,18 @@ function mountAgentRoutes(router, runtime, args) {
|
|
|
1721
1784
|
}
|
|
1722
1785
|
throw error;
|
|
1723
1786
|
}
|
|
1724
|
-
}
|
|
1787
|
+
},
|
|
1725
1788
|
});
|
|
1726
1789
|
router.add({
|
|
1727
1790
|
method: "POST",
|
|
1728
1791
|
pattern: `${base}/v1/internal/reminders/:reminderId/fire`,
|
|
1729
1792
|
auth: [platformTimerFireAuth],
|
|
1730
|
-
handler: (request, ctx) =>
|
|
1731
|
-
|
|
1732
|
-
const reminderId = (_a = ctx.params.reminderId) !== null && _a !== void 0 ? _a : "";
|
|
1793
|
+
handler: async (request, ctx) => {
|
|
1794
|
+
const reminderId = ctx.params.reminderId ?? "";
|
|
1733
1795
|
const fireCountRaw = new URL(request.url).searchParams.get("fireCount");
|
|
1734
1796
|
const expectedFireCount = fireCountRaw === null ? undefined : Number(fireCountRaw);
|
|
1735
1797
|
try {
|
|
1736
|
-
const result =
|
|
1798
|
+
const result = await reminderRunner.dispatch(reminderId, expectedFireCount !== undefined && Number.isInteger(expectedFireCount)
|
|
1737
1799
|
? { expectedFireCount }
|
|
1738
1800
|
: {});
|
|
1739
1801
|
return jsonResponse(result);
|
|
@@ -1751,7 +1813,7 @@ function mountAgentRoutes(router, runtime, args) {
|
|
|
1751
1813
|
}
|
|
1752
1814
|
throw error;
|
|
1753
1815
|
}
|
|
1754
|
-
}
|
|
1816
|
+
},
|
|
1755
1817
|
});
|
|
1756
1818
|
}
|
|
1757
1819
|
if (dev) {
|
|
@@ -1759,11 +1821,10 @@ function mountAgentRoutes(router, runtime, args) {
|
|
|
1759
1821
|
method: "POST",
|
|
1760
1822
|
pattern: `${base}/v1/dev/schedules/:scheduleId`,
|
|
1761
1823
|
auth: httpAuth,
|
|
1762
|
-
handler: (_request, ctx) =>
|
|
1763
|
-
|
|
1764
|
-
const scheduleId = (_a = ctx.params.scheduleId) !== null && _a !== void 0 ? _a : "";
|
|
1824
|
+
handler: async (_request, ctx) => {
|
|
1825
|
+
const scheduleId = ctx.params.scheduleId ?? "";
|
|
1765
1826
|
try {
|
|
1766
|
-
const { sessionIds } =
|
|
1827
|
+
const { sessionIds } = await scheduleRunner.dispatch(scheduleId);
|
|
1767
1828
|
return jsonResponse({ scheduleId, sessionIds });
|
|
1768
1829
|
}
|
|
1769
1830
|
catch (error) {
|
|
@@ -1776,23 +1837,22 @@ function mountAgentRoutes(router, runtime, args) {
|
|
|
1776
1837
|
}
|
|
1777
1838
|
throw error;
|
|
1778
1839
|
}
|
|
1779
|
-
}
|
|
1840
|
+
},
|
|
1780
1841
|
});
|
|
1781
1842
|
router.add({
|
|
1782
1843
|
method: "POST",
|
|
1783
1844
|
pattern: `${base}/v1/dev/reminders/:reminderId`,
|
|
1784
1845
|
auth: httpAuth,
|
|
1785
|
-
handler: (_request, ctx) =>
|
|
1786
|
-
|
|
1787
|
-
const reminderId = (_a = ctx.params.reminderId) !== null && _a !== void 0 ? _a : "";
|
|
1846
|
+
handler: async (_request, ctx) => {
|
|
1847
|
+
const reminderId = ctx.params.reminderId ?? "";
|
|
1788
1848
|
try {
|
|
1789
|
-
const listed =
|
|
1849
|
+
const listed = await reminderRunner.list();
|
|
1790
1850
|
const reminder = listed.find(r => r.id === reminderId);
|
|
1791
1851
|
if (reminder === undefined) {
|
|
1792
1852
|
const ids = listed.map(r => r.id);
|
|
1793
1853
|
throw new UnknownReminderError(reminderId, ids);
|
|
1794
1854
|
}
|
|
1795
|
-
const ownership =
|
|
1855
|
+
const ownership = await assertReminderReadable({
|
|
1796
1856
|
engine,
|
|
1797
1857
|
reminder,
|
|
1798
1858
|
auth: ctx.auth,
|
|
@@ -1803,7 +1863,7 @@ function mountAgentRoutes(router, runtime, args) {
|
|
|
1803
1863
|
if (ownership !== undefined) {
|
|
1804
1864
|
return ownership;
|
|
1805
1865
|
}
|
|
1806
|
-
const result =
|
|
1866
|
+
const result = await reminderRunner.dispatch(reminderId);
|
|
1807
1867
|
return jsonResponse(result);
|
|
1808
1868
|
}
|
|
1809
1869
|
catch (error) {
|
|
@@ -1819,17 +1879,17 @@ function mountAgentRoutes(router, runtime, args) {
|
|
|
1819
1879
|
}
|
|
1820
1880
|
throw error;
|
|
1821
1881
|
}
|
|
1822
|
-
}
|
|
1882
|
+
},
|
|
1823
1883
|
});
|
|
1824
1884
|
router.add({
|
|
1825
1885
|
method: "GET",
|
|
1826
1886
|
pattern: `${base}/v1/dev/reminders`,
|
|
1827
1887
|
auth: httpAuth,
|
|
1828
|
-
handler: (_request, ctx) =>
|
|
1829
|
-
const all =
|
|
1888
|
+
handler: async (_request, ctx) => {
|
|
1889
|
+
const all = await reminderRunner.list();
|
|
1830
1890
|
const reminders = [];
|
|
1831
1891
|
for (const reminder of all) {
|
|
1832
|
-
const denied =
|
|
1892
|
+
const denied = await assertReminderReadable({
|
|
1833
1893
|
engine,
|
|
1834
1894
|
reminder,
|
|
1835
1895
|
auth: ctx.auth,
|
|
@@ -1842,12 +1902,12 @@ function mountAgentRoutes(router, runtime, args) {
|
|
|
1842
1902
|
}
|
|
1843
1903
|
}
|
|
1844
1904
|
return jsonResponse({ reminders });
|
|
1845
|
-
}
|
|
1905
|
+
},
|
|
1846
1906
|
});
|
|
1847
1907
|
}
|
|
1848
1908
|
for (const channel of project.channels) {
|
|
1849
|
-
const channelAuth =
|
|
1850
|
-
for (const channelRoute of
|
|
1909
|
+
const channelAuth = channel.definition.auth ?? defaultAuth;
|
|
1910
|
+
for (const channelRoute of channel.definition.routes ?? []) {
|
|
1851
1911
|
router.add({
|
|
1852
1912
|
method: channelRoute.method,
|
|
1853
1913
|
pattern: `${base}/v1/channels/${channel.id}${channelRoute.path}`,
|
|
@@ -1873,8 +1933,7 @@ function mountAgentRoutes(router, runtime, args) {
|
|
|
1873
1933
|
* serve instead of starting with a relay that could never run.
|
|
1874
1934
|
*/
|
|
1875
1935
|
function requireCursorEventsAuth(config, accountKey) {
|
|
1876
|
-
|
|
1877
|
-
const apiKey = ((_b = (_a = config.apiKey) !== null && _a !== void 0 ? _a : accountKey === null || accountKey === void 0 ? void 0 : accountKey.apiKey) !== null && _b !== void 0 ? _b : "").trim();
|
|
1936
|
+
const apiKey = (config.apiKey ?? accountKey?.apiKey ?? "").trim();
|
|
1878
1937
|
if (apiKey === "") {
|
|
1879
1938
|
throw new Error(`[bdk] --cursor-events (serve({ cursorEvents })) requires a signed-in Cursor account: the SCM event stream is read as the host's user. ${SIGN_IN_HINT}`);
|
|
1880
1939
|
}
|
|
@@ -1883,7 +1942,7 @@ function requireCursorEventsAuth(config, accountKey) {
|
|
|
1883
1942
|
// `/v0/scm-events/*` lives on the external API (api.cursor.com), not
|
|
1884
1943
|
// api2. Prefer that default over the account key's backendUrl, which
|
|
1885
1944
|
// follows cursorBackendUrl() (api2) when CURSOR_API_KEY is set.
|
|
1886
|
-
baseUrl:
|
|
1945
|
+
baseUrl: config.baseUrl ?? cursorExternalApiUrl(),
|
|
1887
1946
|
};
|
|
1888
1947
|
}
|
|
1889
1948
|
/**
|
|
@@ -1937,15 +1996,13 @@ function assertSingleCursorEventsLane(input) {
|
|
|
1937
1996
|
githubRepos: input.githubRepos,
|
|
1938
1997
|
originRepos: input.originRepos,
|
|
1939
1998
|
};
|
|
1940
|
-
if (collectCursorEventSubscriptions(
|
|
1941
|
-
collectCursorEventSubscriptions(
|
|
1999
|
+
if (collectCursorEventSubscriptions({ mounts: input.v1Mounts, ...shared }).length === 0 ||
|
|
2000
|
+
collectCursorEventSubscriptions({ mounts: input.v2Mounts, ...shared }).length === 0) {
|
|
1942
2001
|
return;
|
|
1943
2002
|
}
|
|
1944
2003
|
throw new Error("[bdk] cursor-events: refusing to serve v1 and v2 github/origin agents in one process — they would open two /v0/scm-events consumers. Serve one architecture per process.");
|
|
1945
2004
|
}
|
|
1946
2005
|
function startCursorEventRelay(input) {
|
|
1947
|
-
var _a;
|
|
1948
|
-
var _b, _c;
|
|
1949
2006
|
const { config, stateRoot, mounts, auth, logger } = input;
|
|
1950
2007
|
if (config === undefined || auth === undefined) {
|
|
1951
2008
|
return undefined;
|
|
@@ -1971,14 +2028,14 @@ function startCursorEventRelay(input) {
|
|
|
1971
2028
|
}
|
|
1972
2029
|
return undefined;
|
|
1973
2030
|
}
|
|
1974
|
-
const baseStateRoot = stateRoot
|
|
2031
|
+
const baseStateRoot = stateRoot ?? join(mounts[0]?.project.rootDir ?? process.cwd(), ".agent-serve");
|
|
1975
2032
|
const relay = new CursorEventRelay({
|
|
1976
2033
|
baseUrl: auth.baseUrl,
|
|
1977
2034
|
apiKey: auth.apiKey,
|
|
1978
2035
|
subscriptions,
|
|
1979
|
-
stateDir: join(baseStateRoot,
|
|
2036
|
+
stateDir: join(baseStateRoot, input.stateDirName ?? "cursor-events"),
|
|
1980
2037
|
logger,
|
|
1981
|
-
dispatch: (event) =>
|
|
2038
|
+
dispatch: async (event) => {
|
|
1982
2039
|
if (event.provider === "origin") {
|
|
1983
2040
|
const targets = input.collectOriginTargets().filter(target => {
|
|
1984
2041
|
const cursorAccount = target.options.cursorAccount;
|
|
@@ -1991,9 +2048,14 @@ function startCursorEventRelay(input) {
|
|
|
1991
2048
|
? true
|
|
1992
2049
|
: cursorAccount.repos.some(repo => repo.toLowerCase() === event.repo.toLowerCase());
|
|
1993
2050
|
});
|
|
1994
|
-
const result =
|
|
1995
|
-
|
|
1996
|
-
:
|
|
2051
|
+
const result = await handleAgentServeTrigger({
|
|
2052
|
+
type: "origin",
|
|
2053
|
+
bodyText: JSON.stringify(scmMetadataToOriginPayload(event)),
|
|
2054
|
+
targets,
|
|
2055
|
+
...(input.routeContinuation === undefined
|
|
2056
|
+
? {}
|
|
2057
|
+
: { routeContinuation: input.routeContinuation }),
|
|
2058
|
+
});
|
|
1997
2059
|
logCursorScmDispatch(logger, event, result);
|
|
1998
2060
|
return;
|
|
1999
2061
|
}
|
|
@@ -2010,11 +2072,18 @@ function startCursorEventRelay(input) {
|
|
|
2010
2072
|
? true
|
|
2011
2073
|
: cursorAccount.repos.some(repo => repo.toLowerCase() === event.repo.toLowerCase());
|
|
2012
2074
|
});
|
|
2013
|
-
const result =
|
|
2014
|
-
|
|
2015
|
-
:
|
|
2075
|
+
const result = await handleAgentServeTrigger({
|
|
2076
|
+
type: "github",
|
|
2077
|
+
bodyText: JSON.stringify(scmEventToGitHubPayload(event)),
|
|
2078
|
+
eventName: event.eventName,
|
|
2079
|
+
deliveryId: event.deliveryId,
|
|
2080
|
+
targets,
|
|
2081
|
+
...(input.routeContinuation === undefined
|
|
2082
|
+
? {}
|
|
2083
|
+
: { routeContinuation: input.routeContinuation }),
|
|
2084
|
+
});
|
|
2016
2085
|
logCursorScmDispatch(logger, event, result);
|
|
2017
|
-
}
|
|
2086
|
+
},
|
|
2018
2087
|
});
|
|
2019
2088
|
if (input.start === false) {
|
|
2020
2089
|
return relay;
|
|
@@ -2048,16 +2117,13 @@ function collectCursorGithubTargets(mounts) {
|
|
|
2048
2117
|
return targets;
|
|
2049
2118
|
}
|
|
2050
2119
|
function cursorGithubPermissionsForTarget(target) {
|
|
2051
|
-
var _a;
|
|
2052
|
-
var _b;
|
|
2053
2120
|
const authored = typeof target.accountRuntime.options.cursorAccount === "object"
|
|
2054
2121
|
? target.accountRuntime.options.cursorAccount.permissions
|
|
2055
2122
|
: undefined;
|
|
2056
|
-
return (
|
|
2123
|
+
return (target.accountRuntime.scope?.permissions ??
|
|
2124
|
+
cursorGithubPermissionsForChannel(target.accountRuntime.options, authored));
|
|
2057
2125
|
}
|
|
2058
2126
|
function resolveCursorGithubEventConfig(input) {
|
|
2059
|
-
var _a;
|
|
2060
|
-
var _b;
|
|
2061
2127
|
if (input.targets.length === 0) {
|
|
2062
2128
|
return {
|
|
2063
2129
|
cursorEvents: input.configured,
|
|
@@ -2066,8 +2132,8 @@ function resolveCursorGithubEventConfig(input) {
|
|
|
2066
2132
|
};
|
|
2067
2133
|
}
|
|
2068
2134
|
const repos = [
|
|
2069
|
-
...(
|
|
2070
|
-
...input.targets.flatMap(target =>
|
|
2135
|
+
...(input.configured?.repos ?? []),
|
|
2136
|
+
...input.targets.flatMap(target => [...(target.accountRuntime.scope?.repos ?? [])]),
|
|
2071
2137
|
];
|
|
2072
2138
|
const permissions = resolveCursorGithubPermissions(input.targets.map(cursorGithubPermissionsForTarget));
|
|
2073
2139
|
const scope = resolveCursorGithubCredentialScope(repos, permissions);
|
|
@@ -2079,7 +2145,10 @@ function resolveCursorGithubEventConfig(input) {
|
|
|
2079
2145
|
};
|
|
2080
2146
|
}
|
|
2081
2147
|
return {
|
|
2082
|
-
cursorEvents:
|
|
2148
|
+
cursorEvents: {
|
|
2149
|
+
...input.configured,
|
|
2150
|
+
repos: [...scope.repos],
|
|
2151
|
+
},
|
|
2083
2152
|
scope,
|
|
2084
2153
|
permissions,
|
|
2085
2154
|
};
|
|
@@ -2111,7 +2180,7 @@ function resolveCursorOriginRepoScope(input) {
|
|
|
2111
2180
|
const wantsHostRepos = input.targets.some(target => target.accountRuntime.options.cursorAccount === true);
|
|
2112
2181
|
const repos = [
|
|
2113
2182
|
...(wantsHostRepos ? input.explicitRepos : []),
|
|
2114
|
-
...input.targets.flatMap(target =>
|
|
2183
|
+
...input.targets.flatMap(target => target.accountRuntime.scope?.repos ?? []),
|
|
2115
2184
|
];
|
|
2116
2185
|
if (repos.length === 0) {
|
|
2117
2186
|
return undefined;
|
|
@@ -2120,15 +2189,13 @@ function resolveCursorOriginRepoScope(input) {
|
|
|
2120
2189
|
}
|
|
2121
2190
|
function buildAgentIndexEntries(runtimes) {
|
|
2122
2191
|
return runtimes.map(({ slug, project }) => {
|
|
2123
|
-
|
|
2124
|
-
var _b;
|
|
2125
|
-
const hasSlack = project.channels.some(channel => { var _a; return ((_a = channel.definition.meta) === null || _a === void 0 ? void 0 : _a.kind) === "slack"; });
|
|
2192
|
+
const hasSlack = project.channels.some(channel => channel.definition.meta?.kind === "slack");
|
|
2126
2193
|
return {
|
|
2127
2194
|
slug,
|
|
2128
2195
|
name: project.name,
|
|
2129
2196
|
model: typeof project.agent.model === "string"
|
|
2130
2197
|
? project.agent.model
|
|
2131
|
-
: (
|
|
2198
|
+
: (project.agent.model?.id ?? ""),
|
|
2132
2199
|
tools: project.agent.tools.length,
|
|
2133
2200
|
channels: project.channels.length + 1,
|
|
2134
2201
|
slack: hasSlack ? { kind: "socket" } : undefined,
|
|
@@ -2164,7 +2231,6 @@ function liveDeploymentSubscriptionUnion(targets) {
|
|
|
2164
2231
|
* `startCursorEventRelay` has.
|
|
2165
2232
|
*/
|
|
2166
2233
|
function startCursorDeploymentRelay(input) {
|
|
2167
|
-
var _a;
|
|
2168
2234
|
const { options, runtimes, accountKey, logger } = input;
|
|
2169
2235
|
const targets = collectCursorDeploymentTargets(runtimes);
|
|
2170
2236
|
if (targets.length === 0) {
|
|
@@ -2188,30 +2254,28 @@ function startCursorDeploymentRelay(input) {
|
|
|
2188
2254
|
}
|
|
2189
2255
|
// Same host split as scm-events: `/v0/deployment-events/*` is on
|
|
2190
2256
|
// api.cursor.com, not api2.
|
|
2191
|
-
const baseStateRoot =
|
|
2257
|
+
const baseStateRoot = options.stateRoot ?? join(targets[0].runtime.project.rootDir, ".agent-serve");
|
|
2192
2258
|
const relay = new DeploymentEventRelay({
|
|
2193
2259
|
baseUrl: cursorExternalApiUrl(),
|
|
2194
2260
|
apiKey: accountKey.apiKey,
|
|
2195
2261
|
stateDir: join(baseStateRoot, "deployment-events"),
|
|
2196
2262
|
subscriptions: () => liveDeploymentSubscriptionUnion(targets),
|
|
2197
2263
|
logger,
|
|
2198
|
-
dispatch: (event) =>
|
|
2199
|
-
|
|
2200
|
-
|
|
2201
|
-
|
|
2202
|
-
|
|
2203
|
-
|
|
2204
|
-
|
|
2205
|
-
|
|
2206
|
-
|
|
2207
|
-
|
|
2208
|
-
|
|
2209
|
-
|
|
2210
|
-
channelId: target.channelId,
|
|
2211
|
-
}),
|
|
2264
|
+
dispatch: async (event) => fanOutDeploymentEvent({
|
|
2265
|
+
event,
|
|
2266
|
+
logger,
|
|
2267
|
+
targets: targets.map(target => ({
|
|
2268
|
+
name: `${target.runtime.slug || target.runtime.project.name}/${target.channelId}`,
|
|
2269
|
+
subscriptions: resolveDeploymentSubscriptions(target.options),
|
|
2270
|
+
deliver: delivered => dispatchDeploymentEvent({
|
|
2271
|
+
options: target.options,
|
|
2272
|
+
event: delivered,
|
|
2273
|
+
args: buildChannelHandlerArgs({
|
|
2274
|
+
runtime: target.runtime,
|
|
2275
|
+
channelId: target.channelId,
|
|
2212
2276
|
}),
|
|
2213
|
-
})
|
|
2214
|
-
})
|
|
2277
|
+
}),
|
|
2278
|
+
})),
|
|
2215
2279
|
}),
|
|
2216
2280
|
});
|
|
2217
2281
|
relay.start();
|
|
@@ -2219,17 +2283,20 @@ function startCursorDeploymentRelay(input) {
|
|
|
2219
2283
|
return relay;
|
|
2220
2284
|
}
|
|
2221
2285
|
function buildChannelHandlerArgs(input) {
|
|
2222
|
-
var _a, _b, _c;
|
|
2223
2286
|
const { runtime, channelId } = input;
|
|
2224
2287
|
const base = runtime.slug === "" ? "" : `/${runtime.slug}`;
|
|
2225
|
-
const auth =
|
|
2288
|
+
const auth = input.auth ?? null;
|
|
2226
2289
|
return {
|
|
2227
|
-
params:
|
|
2228
|
-
requestIp:
|
|
2290
|
+
params: input.params ?? {},
|
|
2291
|
+
requestIp: input.requestIp ?? null,
|
|
2229
2292
|
auth,
|
|
2230
2293
|
host: runtime.engine.host,
|
|
2231
2294
|
artifacts: runtime.engine.artifacts,
|
|
2232
|
-
send: (message, sendOptions = {}) => runtime.engine.send(channelId, message,
|
|
2295
|
+
send: (message, sendOptions = {}) => runtime.engine.send(channelId, message, {
|
|
2296
|
+
...sendOptions,
|
|
2297
|
+
auth: sendOptions.auth !== undefined ? sendOptions.auth : auth,
|
|
2298
|
+
hostedSessionId: input.hostedSessionId,
|
|
2299
|
+
}),
|
|
2233
2300
|
getSession: sessionId => runtime.engine.getChannelSession(channelId, sessionId),
|
|
2234
2301
|
receive: (target, inputReceive) => runtime.engine.receive(target, inputReceive),
|
|
2235
2302
|
callTool: (toolName, toolInput, callOptions) => runtime.engine.callTool(toolName, toolInput, callOptions),
|
|
@@ -2243,200 +2310,174 @@ function buildChannelHandlerArgs(input) {
|
|
|
2243
2310
|
interruptContinuation: continuationToken => runtime.engine.interruptContinuation(channelId, continuationToken),
|
|
2244
2311
|
};
|
|
2245
2312
|
}
|
|
2246
|
-
function handleRequest(router, req, res, logger, browserOriginGuard) {
|
|
2247
|
-
|
|
2248
|
-
|
|
2249
|
-
|
|
2250
|
-
|
|
2251
|
-
const
|
|
2252
|
-
|
|
2253
|
-
|
|
2254
|
-
|
|
2255
|
-
|
|
2256
|
-
|
|
2257
|
-
|
|
2258
|
-
|
|
2259
|
-
|
|
2260
|
-
|
|
2261
|
-
|
|
2262
|
-
|
|
2263
|
-
|
|
2264
|
-
|
|
2265
|
-
if (!isTrustedBrowserMutation(request, browserOriginGuard)) {
|
|
2266
|
-
response = requestErrorResponse(403, "forbidden_origin", "Cross-origin browser mutations are not allowed.");
|
|
2267
|
-
}
|
|
2268
|
-
else if (match === undefined) {
|
|
2269
|
-
response = router.hasPath(url.pathname)
|
|
2270
|
-
? requestErrorResponse(405, "method_not_allowed", `${request.method} is not supported on ${url.pathname}.`)
|
|
2271
|
-
: requestErrorResponse(404, "not_found", `No route for ${request.method} ${url.pathname}.`);
|
|
2272
|
-
}
|
|
2273
|
-
else {
|
|
2274
|
-
const requestIp = (_b = req.socket.remoteAddress) !== null && _b !== void 0 ? _b : null;
|
|
2275
|
-
let auth = null;
|
|
2276
|
-
let unauthorized = false;
|
|
2277
|
-
if (match.route.auth !== "none") {
|
|
2278
|
-
auth = yield resolveAuth(match.route.auth, request, requestIp);
|
|
2279
|
-
unauthorized = auth === null;
|
|
2280
|
-
}
|
|
2281
|
-
response = unauthorized
|
|
2282
|
-
? requestErrorResponse(401, "unauthorized", "No auth policy admitted this request. In production, configure channel auth (e.g. bearerAuth) — the default localDevStrict() policy only admits loopback callers with a loopback Host.")
|
|
2283
|
-
: yield match.route.handler(request, {
|
|
2284
|
-
params: match.params,
|
|
2285
|
-
requestIp,
|
|
2286
|
-
auth,
|
|
2287
|
-
});
|
|
2288
|
-
}
|
|
2313
|
+
async function handleRequest(router, req, res, logger, browserOriginGuard) {
|
|
2314
|
+
let response;
|
|
2315
|
+
let hostedDeliveryRequest = false;
|
|
2316
|
+
const method = req.method ?? "GET";
|
|
2317
|
+
try {
|
|
2318
|
+
const request = await toFetchRequest(req);
|
|
2319
|
+
hostedDeliveryRequest = request.headers.get(HOSTED_DELIVERY_HEADER) === "1";
|
|
2320
|
+
const requestErrorResponse = (status, error, message) => hostedDeliveryRequest
|
|
2321
|
+
? hostedGuestErrorResponse({
|
|
2322
|
+
status,
|
|
2323
|
+
error,
|
|
2324
|
+
retry: HOSTED_GUEST_RETRY_TERMINAL,
|
|
2325
|
+
message,
|
|
2326
|
+
})
|
|
2327
|
+
: errorResponse(status, error, message);
|
|
2328
|
+
const url = new URL(request.url);
|
|
2329
|
+
const match = router.match(request.method, url.pathname);
|
|
2330
|
+
if (!isTrustedBrowserMutation(request, browserOriginGuard)) {
|
|
2331
|
+
response = requestErrorResponse(403, "forbidden_origin", "Cross-origin browser mutations are not allowed.");
|
|
2289
2332
|
}
|
|
2290
|
-
|
|
2291
|
-
|
|
2292
|
-
|
|
2293
|
-
|
|
2294
|
-
|
|
2295
|
-
|
|
2296
|
-
|
|
2297
|
-
|
|
2298
|
-
|
|
2299
|
-
|
|
2300
|
-
|
|
2301
|
-
|
|
2302
|
-
else {
|
|
2303
|
-
logger(`[bdk] request failed: ${error instanceof Error ? ((_c = error.stack) !== null && _c !== void 0 ? _c : error.message) : String(error)}`);
|
|
2304
|
-
const snippet = hostedErrorStackSnippet(error);
|
|
2305
|
-
const message = hostedDeliveryRequest
|
|
2306
|
-
? hostedErrorMessageOnly(error)
|
|
2307
|
-
: snippet.length > 0
|
|
2308
|
-
? `Internal server error: ${snippet}`
|
|
2309
|
-
: "Internal server error.";
|
|
2310
|
-
response = hostedDeliveryRequest
|
|
2311
|
-
? hostedGuestErrorResponse({
|
|
2312
|
-
status: 500,
|
|
2313
|
-
error: "internal_error",
|
|
2314
|
-
retry: HOSTED_GUEST_RETRY_TERMINAL,
|
|
2315
|
-
message,
|
|
2316
|
-
})
|
|
2317
|
-
: errorResponse(500, "internal_error", message);
|
|
2333
|
+
else if (match === undefined) {
|
|
2334
|
+
response = router.hasPath(url.pathname)
|
|
2335
|
+
? requestErrorResponse(405, "method_not_allowed", `${request.method} is not supported on ${url.pathname}.`)
|
|
2336
|
+
: requestErrorResponse(404, "not_found", `No route for ${request.method} ${url.pathname}.`);
|
|
2337
|
+
}
|
|
2338
|
+
else {
|
|
2339
|
+
const requestIp = req.socket.remoteAddress ?? null;
|
|
2340
|
+
let auth = null;
|
|
2341
|
+
let unauthorized = false;
|
|
2342
|
+
if (match.route.auth !== "none") {
|
|
2343
|
+
auth = await resolveAuth(match.route.auth, request, requestIp);
|
|
2344
|
+
unauthorized = auth === null;
|
|
2318
2345
|
}
|
|
2346
|
+
response = unauthorized
|
|
2347
|
+
? requestErrorResponse(401, "unauthorized", "No auth policy admitted this request. In production, configure channel auth (e.g. bearerAuth) — the default localDevStrict() policy only admits loopback callers with a loopback Host.")
|
|
2348
|
+
: await match.route.handler(request, {
|
|
2349
|
+
params: match.params,
|
|
2350
|
+
requestIp,
|
|
2351
|
+
auth,
|
|
2352
|
+
});
|
|
2319
2353
|
}
|
|
2320
|
-
|
|
2321
|
-
|
|
2354
|
+
}
|
|
2355
|
+
catch (error) {
|
|
2356
|
+
const mapped = mapThrownRequestError(error);
|
|
2357
|
+
if (mapped !== undefined) {
|
|
2358
|
+
response = hostedDeliveryRequest
|
|
2359
|
+
? hostedGuestErrorResponse({
|
|
2360
|
+
status: mapped.status,
|
|
2361
|
+
error: mapped.code,
|
|
2362
|
+
retry: mapped.retry,
|
|
2363
|
+
message: mapped.message,
|
|
2364
|
+
})
|
|
2365
|
+
: errorResponse(mapped.status, mapped.code, mapped.message);
|
|
2322
2366
|
}
|
|
2323
|
-
|
|
2324
|
-
logger(`[bdk]
|
|
2325
|
-
|
|
2367
|
+
else {
|
|
2368
|
+
logger(`[bdk] request failed: ${error instanceof Error ? (error.stack ?? error.message) : String(error)}`);
|
|
2369
|
+
const snippet = hostedErrorStackSnippet(error);
|
|
2370
|
+
const message = hostedDeliveryRequest
|
|
2371
|
+
? hostedErrorMessageOnly(error)
|
|
2372
|
+
: snippet.length > 0
|
|
2373
|
+
? `Internal server error: ${snippet}`
|
|
2374
|
+
: "Internal server error.";
|
|
2375
|
+
response = hostedDeliveryRequest
|
|
2376
|
+
? hostedGuestErrorResponse({
|
|
2377
|
+
status: 500,
|
|
2378
|
+
error: "internal_error",
|
|
2379
|
+
retry: HOSTED_GUEST_RETRY_TERMINAL,
|
|
2380
|
+
message,
|
|
2381
|
+
})
|
|
2382
|
+
: errorResponse(500, "internal_error", message);
|
|
2326
2383
|
}
|
|
2327
|
-
}
|
|
2384
|
+
}
|
|
2385
|
+
try {
|
|
2386
|
+
await writeFetchResponse(response, res, method);
|
|
2387
|
+
}
|
|
2388
|
+
catch (error) {
|
|
2389
|
+
logger(`[bdk] response write failed: ${error instanceof Error ? error.message : String(error)}`);
|
|
2390
|
+
res.destroy();
|
|
2391
|
+
}
|
|
2328
2392
|
}
|
|
2329
|
-
function resolveAuth(policies, request, requestIp) {
|
|
2330
|
-
|
|
2331
|
-
|
|
2332
|
-
|
|
2333
|
-
|
|
2334
|
-
return result;
|
|
2335
|
-
}
|
|
2393
|
+
async function resolveAuth(policies, request, requestIp) {
|
|
2394
|
+
for (const policy of policies) {
|
|
2395
|
+
const result = await policy(request, { requestIp });
|
|
2396
|
+
if (result !== null) {
|
|
2397
|
+
return result;
|
|
2336
2398
|
}
|
|
2337
|
-
|
|
2399
|
+
}
|
|
2400
|
+
return null;
|
|
2401
|
+
}
|
|
2402
|
+
async function toFetchRequest(req) {
|
|
2403
|
+
const method = req.method ?? "GET";
|
|
2404
|
+
const hostHeader = incomingRequestHost(req.headers);
|
|
2405
|
+
const url = new URL(req.url ?? "/", `http://${hostHeader}`);
|
|
2406
|
+
// Drop HTTP/2 pseudo-headers (`:method`, …) — Fetch `Headers` rejects them.
|
|
2407
|
+
const headers = incomingHeadersToFetch(req.headers);
|
|
2408
|
+
if (method === "GET" || method === "HEAD") {
|
|
2409
|
+
return new Request(url, { method, headers });
|
|
2410
|
+
}
|
|
2411
|
+
const body = await readBody(req);
|
|
2412
|
+
// Buffer is a Uint8Array at runtime; TS's BodyInit wants ArrayBuffer-backed
|
|
2413
|
+
// typed arrays, so re-view without copying.
|
|
2414
|
+
return new Request(url, {
|
|
2415
|
+
method,
|
|
2416
|
+
headers,
|
|
2417
|
+
body: new Uint8Array(body.buffer, body.byteOffset, body.byteLength),
|
|
2338
2418
|
});
|
|
2339
2419
|
}
|
|
2340
|
-
function
|
|
2341
|
-
|
|
2342
|
-
|
|
2343
|
-
|
|
2344
|
-
const
|
|
2345
|
-
|
|
2346
|
-
|
|
2347
|
-
|
|
2348
|
-
if (method === "GET" || method === "HEAD") {
|
|
2349
|
-
return new Request(url, { method, headers });
|
|
2420
|
+
async function readBody(req) {
|
|
2421
|
+
const chunks = [];
|
|
2422
|
+
let total = 0;
|
|
2423
|
+
for await (const chunk of req) {
|
|
2424
|
+
const buffer = Buffer.isBuffer(chunk) ? chunk : Buffer.from(chunk);
|
|
2425
|
+
total += buffer.length;
|
|
2426
|
+
if (total > MAX_BODY_BYTES) {
|
|
2427
|
+
throw new Error("Request body too large");
|
|
2350
2428
|
}
|
|
2351
|
-
|
|
2352
|
-
|
|
2353
|
-
|
|
2354
|
-
return new Request(url, {
|
|
2355
|
-
method,
|
|
2356
|
-
headers,
|
|
2357
|
-
body: new Uint8Array(body.buffer, body.byteOffset, body.byteLength),
|
|
2358
|
-
});
|
|
2359
|
-
});
|
|
2429
|
+
chunks.push(buffer);
|
|
2430
|
+
}
|
|
2431
|
+
return Buffer.concat(chunks);
|
|
2360
2432
|
}
|
|
2361
|
-
function
|
|
2362
|
-
|
|
2363
|
-
|
|
2364
|
-
|
|
2365
|
-
|
|
2366
|
-
|
|
2367
|
-
|
|
2368
|
-
|
|
2369
|
-
|
|
2370
|
-
|
|
2371
|
-
|
|
2372
|
-
|
|
2373
|
-
|
|
2374
|
-
|
|
2375
|
-
throw new Error("Request body too large");
|
|
2376
|
-
}
|
|
2377
|
-
chunks.push(buffer);
|
|
2378
|
-
}
|
|
2433
|
+
export async function writeFetchResponse(response, res, method = "GET") {
|
|
2434
|
+
const headers = {};
|
|
2435
|
+
response.headers.forEach((value, key) => {
|
|
2436
|
+
headers[key] = value;
|
|
2437
|
+
});
|
|
2438
|
+
res.writeHead(response.status, headers);
|
|
2439
|
+
// HEAD responses must not include a body (RFC 9110 §9.3.2). Cancel any
|
|
2440
|
+
// handler-produced stream so long-lived GET handlers (e.g. session SSE)
|
|
2441
|
+
// still unsubscribe when served via the HEAD→GET fallback. A client that
|
|
2442
|
+
// left while the handler ran gets no body either: its `close` has already
|
|
2443
|
+
// fired, so the loop below would wait on a drain that never comes.
|
|
2444
|
+
if (method === "HEAD" || response.body === null || res.destroyed) {
|
|
2445
|
+
if (response.body !== null) {
|
|
2446
|
+
void response.body.cancel().catch(() => { });
|
|
2379
2447
|
}
|
|
2380
|
-
|
|
2381
|
-
|
|
2382
|
-
|
|
2383
|
-
|
|
2448
|
+
res.end();
|
|
2449
|
+
return;
|
|
2450
|
+
}
|
|
2451
|
+
const reader = response.body.getReader();
|
|
2452
|
+
let clientGone = false;
|
|
2453
|
+
const closed = new Promise(resolve => {
|
|
2454
|
+
res.once("close", () => {
|
|
2455
|
+
if (!res.writableFinished) {
|
|
2456
|
+
clientGone = true;
|
|
2457
|
+
void reader.cancel().catch(() => { });
|
|
2384
2458
|
}
|
|
2385
|
-
|
|
2386
|
-
}
|
|
2387
|
-
return Buffer.concat(chunks);
|
|
2388
|
-
});
|
|
2389
|
-
}
|
|
2390
|
-
export function writeFetchResponse(response_1, res_1) {
|
|
2391
|
-
return __awaiter(this, arguments, void 0, function* (response, res, method = "GET") {
|
|
2392
|
-
const headers = {};
|
|
2393
|
-
response.headers.forEach((value, key) => {
|
|
2394
|
-
headers[key] = value;
|
|
2459
|
+
resolve();
|
|
2395
2460
|
});
|
|
2396
|
-
|
|
2397
|
-
|
|
2398
|
-
|
|
2399
|
-
|
|
2400
|
-
|
|
2401
|
-
|
|
2402
|
-
if (method === "HEAD" || response.body === null || res.destroyed) {
|
|
2403
|
-
if (response.body !== null) {
|
|
2404
|
-
void response.body.cancel().catch(() => { });
|
|
2461
|
+
});
|
|
2462
|
+
try {
|
|
2463
|
+
while (!clientGone) {
|
|
2464
|
+
const { done, value } = await reader.read();
|
|
2465
|
+
if (done) {
|
|
2466
|
+
break;
|
|
2405
2467
|
}
|
|
2406
|
-
|
|
2407
|
-
|
|
2408
|
-
|
|
2409
|
-
|
|
2410
|
-
|
|
2411
|
-
const closed = new Promise(resolve => {
|
|
2412
|
-
res.once("close", () => {
|
|
2413
|
-
if (!res.writableFinished) {
|
|
2414
|
-
clientGone = true;
|
|
2415
|
-
void reader.cancel().catch(() => { });
|
|
2416
|
-
}
|
|
2417
|
-
resolve();
|
|
2418
|
-
});
|
|
2419
|
-
});
|
|
2420
|
-
try {
|
|
2421
|
-
while (!clientGone) {
|
|
2422
|
-
const { done, value } = yield reader.read();
|
|
2423
|
-
if (done) {
|
|
2424
|
-
break;
|
|
2425
|
-
}
|
|
2426
|
-
if (value !== undefined && value.length > 0) {
|
|
2427
|
-
if (!res.write(value) && !clientGone) {
|
|
2428
|
-
// Race against close so a vanished client can't wedge the loop
|
|
2429
|
-
// waiting for a drain that will never come.
|
|
2430
|
-
yield Promise.race([once(res, "drain"), closed]);
|
|
2431
|
-
}
|
|
2468
|
+
if (value !== undefined && value.length > 0) {
|
|
2469
|
+
if (!res.write(value) && !clientGone) {
|
|
2470
|
+
// Race against close so a vanished client can't wedge the loop
|
|
2471
|
+
// waiting for a drain that will never come.
|
|
2472
|
+
await Promise.race([once(res, "drain"), closed]);
|
|
2432
2473
|
}
|
|
2433
2474
|
}
|
|
2434
2475
|
}
|
|
2435
|
-
|
|
2436
|
-
|
|
2437
|
-
}
|
|
2438
|
-
|
|
2439
|
-
|
|
2476
|
+
}
|
|
2477
|
+
catch {
|
|
2478
|
+
void reader.cancel().catch(() => { });
|
|
2479
|
+
}
|
|
2480
|
+
res.end();
|
|
2440
2481
|
}
|
|
2441
2482
|
/**
|
|
2442
2483
|
* Dev reminder routes may only inspect/fire reminders whose bound session
|
|
@@ -2463,25 +2504,23 @@ function asPositiveInt(value) {
|
|
|
2463
2504
|
const n = Math.floor(value);
|
|
2464
2505
|
return n > 0 ? n : undefined;
|
|
2465
2506
|
}
|
|
2466
|
-
function assertReminderReadable(input) {
|
|
2467
|
-
|
|
2468
|
-
|
|
2469
|
-
|
|
2470
|
-
|
|
2471
|
-
|
|
2472
|
-
|
|
2473
|
-
(input.
|
|
2474
|
-
|
|
2475
|
-
|
|
2476
|
-
return undefined;
|
|
2477
|
-
}
|
|
2478
|
-
// Without a session principal to compare, deny non-relaxed callers.
|
|
2479
|
-
return errorResponse(403, "forbidden", "This reminder is bound to a session that is not visible to the caller.");
|
|
2507
|
+
async function assertReminderReadable(input) {
|
|
2508
|
+
const existing = await input.engine.sessions.findByContinuation(input.reminder.channelId, input.reminder.continuationToken);
|
|
2509
|
+
if (existing === undefined) {
|
|
2510
|
+
// No live session — still gate on relax/loopback so a shared-dev caller
|
|
2511
|
+
// cannot enumerate orphan reminder metadata across principals.
|
|
2512
|
+
const relaxOwnership = input.relax === true &&
|
|
2513
|
+
(input.beyondLoopback === true ||
|
|
2514
|
+
(typeof input.requestIp === "string" && isLoopbackAddress(input.requestIp)));
|
|
2515
|
+
if (relaxOwnership) {
|
|
2516
|
+
return undefined;
|
|
2480
2517
|
}
|
|
2481
|
-
|
|
2482
|
-
|
|
2483
|
-
|
|
2484
|
-
|
|
2485
|
-
|
|
2518
|
+
// Without a session principal to compare, deny non-relaxed callers.
|
|
2519
|
+
return errorResponse(403, "forbidden", "This reminder is bound to a session that is not visible to the caller.");
|
|
2520
|
+
}
|
|
2521
|
+
return requireSessionOwner(existing, input.auth, {
|
|
2522
|
+
relax: input.relax,
|
|
2523
|
+
beyondLoopback: input.beyondLoopback,
|
|
2524
|
+
requestIp: input.requestIp,
|
|
2486
2525
|
});
|
|
2487
2526
|
}
|