@cursor/bdk 0.2.9 → 0.2.10
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/{reference_skills.md.ClkMoQsV.js → reference_skills.md.C0guGcH0.js} +3 -3
- 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 +38 -38
- 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.js +410 -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-DXm_VXAQ.css +1 -0
- package/dist/playground/assets/{index-Co4CUVTQ.js → index-JekUQERK.js} +45 -45
- 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/docs/assets/{reference_skills.md.ClkMoQsV.lean.js → reference_skills.md.C0guGcH0.lean.js} +0 -0
|
@@ -3,15 +3,6 @@
|
|
|
3
3
|
* prompt XOR named handler XOR run, every/cron/delay/at schedules, and
|
|
4
4
|
* until as function XOR prompt.
|
|
5
5
|
*/
|
|
6
|
-
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
|
|
7
|
-
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
|
|
8
|
-
return new (P || (P = Promise))(function (resolve, reject) {
|
|
9
|
-
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
|
|
10
|
-
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
|
|
11
|
-
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
|
|
12
|
-
step((generator = generator.apply(thisArg, _arguments || [])).next());
|
|
13
|
-
});
|
|
14
|
-
};
|
|
15
6
|
import { randomBytes } from "node:crypto";
|
|
16
7
|
import { channelContinuationIdentity, httpContinuationIdentity } from "./continuation-channel.js";
|
|
17
8
|
import { continuationSource, isOneShotContinuationKey } from "./continuation-identity.js";
|
|
@@ -114,11 +105,10 @@ function runReason(result) {
|
|
|
114
105
|
}
|
|
115
106
|
}
|
|
116
107
|
function buildFirePrompt(record) {
|
|
117
|
-
var _a;
|
|
118
108
|
const recurring = record.scheduleKind === "every" || record.scheduleKind === "cron";
|
|
119
109
|
const lines = [
|
|
120
110
|
`[reminder] id=${record.id} purpose=${record.purpose}`,
|
|
121
|
-
|
|
111
|
+
record.prompt ?? "Reminder fired. Check current state and only act if needed.",
|
|
122
112
|
];
|
|
123
113
|
if (record.untilPrompt !== undefined && record.untilPrompt.trim() !== "") {
|
|
124
114
|
lines.push("", `Stop condition: ${record.untilPrompt}`);
|
|
@@ -133,7 +123,6 @@ function buildFirePrompt(record) {
|
|
|
133
123
|
}
|
|
134
124
|
export class ReminderRunner {
|
|
135
125
|
constructor(engine, logger = () => { }, platformTimers, options = {}) {
|
|
136
|
-
var _a;
|
|
137
126
|
this.engine = engine;
|
|
138
127
|
this.logger = logger;
|
|
139
128
|
this.platformTimers = platformTimers;
|
|
@@ -155,7 +144,7 @@ export class ReminderRunner {
|
|
|
155
144
|
this.controlPlane = options.controlPlane;
|
|
156
145
|
this.architecture = options.architecture;
|
|
157
146
|
this.hostedGuest = options.hostedGuest;
|
|
158
|
-
for (const reminder of
|
|
147
|
+
for (const reminder of options.namedHandlers ?? []) {
|
|
159
148
|
this.namedHandlers.set(reminder.name, reminder.definition);
|
|
160
149
|
this.namedHandlerNames.set(reminder.definition, reminder.name);
|
|
161
150
|
}
|
|
@@ -187,178 +176,169 @@ export class ReminderRunner {
|
|
|
187
176
|
* Load reminder documents from the control plane into memory. Does not
|
|
188
177
|
* arm in-process timers — the CP owns clocks.
|
|
189
178
|
*/
|
|
190
|
-
hydrate() {
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
179
|
+
async hydrate() {
|
|
180
|
+
const reader = this.reminderReader;
|
|
181
|
+
if (reader === undefined) {
|
|
182
|
+
return;
|
|
183
|
+
}
|
|
184
|
+
let remote = [];
|
|
185
|
+
try {
|
|
186
|
+
remote = await reader.listReminders();
|
|
187
|
+
}
|
|
188
|
+
catch (error) {
|
|
189
|
+
this.logControlPlaneReadFailure("list", error);
|
|
190
|
+
return;
|
|
191
|
+
}
|
|
192
|
+
this.controlPlaneReadFailureLogged = false;
|
|
193
|
+
this.records.clear();
|
|
194
|
+
for (const record of remote) {
|
|
195
|
+
this.records.set(record.id, record);
|
|
196
|
+
}
|
|
197
|
+
if (this.usesControlPlaneProtocol) {
|
|
198
|
+
// The CP owns status; a fire whose handler is missing reports `stop`
|
|
199
|
+
// from executeFire instead of the guest writing a disarm.
|
|
200
|
+
if (remote.length > 0) {
|
|
201
|
+
this.logger(`[bdk] reminders: hydrated ${remote.length} reminder(s) from control plane`);
|
|
208
202
|
}
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
return;
|
|
203
|
+
return;
|
|
204
|
+
}
|
|
205
|
+
let disarmedOrphan = 0;
|
|
206
|
+
for (const record of [...this.records.values()]) {
|
|
207
|
+
if (record.status !== "armed") {
|
|
208
|
+
continue;
|
|
216
209
|
}
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
continue;
|
|
221
|
-
}
|
|
222
|
-
if (record.handler !== undefined) {
|
|
223
|
-
if (this.namedHandlers.get(record.handler) === undefined) {
|
|
224
|
-
yield this.disarm(record, "handler_missing");
|
|
225
|
-
disarmedOrphan += 1;
|
|
226
|
-
}
|
|
227
|
-
continue;
|
|
228
|
-
}
|
|
229
|
-
if ((record.hasRunHandler === true || record.hasUntilFn === true) &&
|
|
230
|
-
this.handlers.get(record.id) === undefined) {
|
|
231
|
-
yield this.disarm(record, "handler_lost_on_restart");
|
|
210
|
+
if (record.handler !== undefined) {
|
|
211
|
+
if (this.namedHandlers.get(record.handler) === undefined) {
|
|
212
|
+
await this.disarm(record, "handler_missing");
|
|
232
213
|
disarmedOrphan += 1;
|
|
233
214
|
}
|
|
215
|
+
continue;
|
|
234
216
|
}
|
|
235
|
-
if (
|
|
236
|
-
this.
|
|
237
|
-
|
|
217
|
+
if ((record.hasRunHandler === true || record.hasUntilFn === true) &&
|
|
218
|
+
this.handlers.get(record.id) === undefined) {
|
|
219
|
+
await this.disarm(record, "handler_lost_on_restart");
|
|
220
|
+
disarmedOrphan += 1;
|
|
238
221
|
}
|
|
239
|
-
}
|
|
222
|
+
}
|
|
223
|
+
if (remote.length > 0 || disarmedOrphan > 0) {
|
|
224
|
+
this.logger(`[bdk] reminders: hydrated ${remote.length} reminder(s) from control plane` +
|
|
225
|
+
(disarmedOrphan > 0 ? `; disarmed ${disarmedOrphan} without live handlers` : ""));
|
|
226
|
+
}
|
|
240
227
|
}
|
|
241
228
|
/** Start deadline timers for armed reminders (tests / no control plane). */
|
|
242
|
-
start() {
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
if (record.handler
|
|
256
|
-
|
|
257
|
-
yield this.disarm(record, "handler_missing");
|
|
258
|
-
disarmedOrphan += 1;
|
|
259
|
-
continue;
|
|
260
|
-
}
|
|
261
|
-
this.armTimer(record);
|
|
262
|
-
restored += 1;
|
|
263
|
-
continue;
|
|
264
|
-
}
|
|
265
|
-
if ((record.hasRunHandler === true || record.hasUntilFn === true) &&
|
|
266
|
-
this.handlers.get(record.id) === undefined) {
|
|
267
|
-
yield this.disarm(record, "handler_lost_on_restart");
|
|
229
|
+
async start() {
|
|
230
|
+
if (this.started)
|
|
231
|
+
return;
|
|
232
|
+
this.started = true;
|
|
233
|
+
await this.hydrate();
|
|
234
|
+
if (this.platformTimers !== undefined) {
|
|
235
|
+
return;
|
|
236
|
+
}
|
|
237
|
+
const armed = [...this.records.values()].filter(r => r.status === "armed");
|
|
238
|
+
let restored = 0;
|
|
239
|
+
let disarmedOrphan = 0;
|
|
240
|
+
for (const record of armed) {
|
|
241
|
+
if (record.handler !== undefined) {
|
|
242
|
+
if (this.namedHandlers.get(record.handler) === undefined) {
|
|
243
|
+
await this.disarm(record, "handler_missing");
|
|
268
244
|
disarmedOrphan += 1;
|
|
269
245
|
continue;
|
|
270
246
|
}
|
|
271
247
|
this.armTimer(record);
|
|
272
248
|
restored += 1;
|
|
249
|
+
continue;
|
|
273
250
|
}
|
|
274
|
-
if (
|
|
275
|
-
this.
|
|
276
|
-
|
|
251
|
+
if ((record.hasRunHandler === true || record.hasUntilFn === true) &&
|
|
252
|
+
this.handlers.get(record.id) === undefined) {
|
|
253
|
+
await this.disarm(record, "handler_lost_on_restart");
|
|
254
|
+
disarmedOrphan += 1;
|
|
255
|
+
continue;
|
|
277
256
|
}
|
|
278
|
-
|
|
257
|
+
this.armTimer(record);
|
|
258
|
+
restored += 1;
|
|
259
|
+
}
|
|
260
|
+
if (restored > 0 || disarmedOrphan > 0) {
|
|
261
|
+
this.logger(`[bdk] reminders: restored ${restored} armed reminder(s)` +
|
|
262
|
+
(disarmedOrphan > 0 ? `; disarmed ${disarmedOrphan} without live handlers` : ""));
|
|
263
|
+
}
|
|
279
264
|
}
|
|
280
|
-
persist(record) {
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
this.records.set(record.id, record);
|
|
286
|
-
});
|
|
265
|
+
async persist(record) {
|
|
266
|
+
if (this.platformTimers !== undefined) {
|
|
267
|
+
await this.platformTimers.putReminder(record);
|
|
268
|
+
}
|
|
269
|
+
this.records.set(record.id, record);
|
|
287
270
|
}
|
|
288
|
-
getRecord(id) {
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
return undefined;
|
|
298
|
-
}
|
|
299
|
-
this.records.set(id, remote);
|
|
300
|
-
return remote;
|
|
301
|
-
}
|
|
302
|
-
catch (error) {
|
|
303
|
-
this.logControlPlaneReadFailure(`get for ${id}`, error);
|
|
304
|
-
return this.records.get(id);
|
|
271
|
+
async getRecord(id) {
|
|
272
|
+
const reader = this.reminderReader;
|
|
273
|
+
if (reader !== undefined) {
|
|
274
|
+
try {
|
|
275
|
+
const remote = await reader.getReminder(id);
|
|
276
|
+
this.controlPlaneReadFailureLogged = false;
|
|
277
|
+
if (remote === undefined) {
|
|
278
|
+
this.records.delete(id);
|
|
279
|
+
return undefined;
|
|
305
280
|
}
|
|
281
|
+
this.records.set(id, remote);
|
|
282
|
+
return remote;
|
|
306
283
|
}
|
|
307
|
-
|
|
308
|
-
|
|
284
|
+
catch (error) {
|
|
285
|
+
this.logControlPlaneReadFailure(`get for ${id}`, error);
|
|
286
|
+
return this.records.get(id);
|
|
287
|
+
}
|
|
288
|
+
}
|
|
289
|
+
return this.records.get(id);
|
|
309
290
|
}
|
|
310
291
|
/**
|
|
311
292
|
* Under the CP protocol a list failure propagates: the in-memory map is
|
|
312
293
|
* only what this process created, and returning it would let a caller
|
|
313
294
|
* arm duplicates or miss rows to cancel. v1 keeps the best-effort form.
|
|
314
295
|
*/
|
|
315
|
-
allRecords() {
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
this.records.set(record.id, record);
|
|
325
|
-
}
|
|
296
|
+
async allRecords() {
|
|
297
|
+
const reader = this.reminderReader;
|
|
298
|
+
if (reader !== undefined) {
|
|
299
|
+
try {
|
|
300
|
+
const remote = await reader.listReminders();
|
|
301
|
+
this.controlPlaneReadFailureLogged = false;
|
|
302
|
+
this.records.clear();
|
|
303
|
+
for (const record of remote) {
|
|
304
|
+
this.records.set(record.id, record);
|
|
326
305
|
}
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
306
|
+
}
|
|
307
|
+
catch (error) {
|
|
308
|
+
this.logControlPlaneReadFailure("list", error);
|
|
309
|
+
if (this.usesControlPlaneProtocol) {
|
|
310
|
+
throw error;
|
|
332
311
|
}
|
|
333
312
|
}
|
|
334
|
-
|
|
335
|
-
|
|
313
|
+
}
|
|
314
|
+
return [...this.records.values()];
|
|
336
315
|
}
|
|
337
316
|
/**
|
|
338
317
|
* A keyed list asks the control plane for that key. An unfiltered list
|
|
339
318
|
* still uses allRecords. Do not replace the in-memory map with the keyed page.
|
|
340
319
|
*/
|
|
341
|
-
recordsForList(filter) {
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
320
|
+
async recordsForList(filter) {
|
|
321
|
+
if (this.usesControlPlaneProtocol &&
|
|
322
|
+
(filter?.purpose !== undefined || filter?.continuationToken !== undefined)) {
|
|
323
|
+
try {
|
|
324
|
+
const remote = await this.controlPlane.listReminders({
|
|
325
|
+
...(filter.purpose === undefined ? {} : { purpose: filter.purpose }),
|
|
326
|
+
...(filter.continuationToken === undefined
|
|
347
327
|
? {}
|
|
348
|
-
: { continuationToken: filter.continuationToken })
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
return remote;
|
|
354
|
-
}
|
|
355
|
-
catch (error) {
|
|
356
|
-
this.logControlPlaneReadFailure("list", error);
|
|
357
|
-
throw error;
|
|
328
|
+
: { continuationToken: filter.continuationToken }),
|
|
329
|
+
});
|
|
330
|
+
this.controlPlaneReadFailureLogged = false;
|
|
331
|
+
for (const record of remote) {
|
|
332
|
+
this.records.set(record.id, record);
|
|
358
333
|
}
|
|
334
|
+
return remote;
|
|
359
335
|
}
|
|
360
|
-
|
|
361
|
-
|
|
336
|
+
catch (error) {
|
|
337
|
+
this.logControlPlaneReadFailure("list", error);
|
|
338
|
+
throw error;
|
|
339
|
+
}
|
|
340
|
+
}
|
|
341
|
+
return this.allRecords();
|
|
362
342
|
}
|
|
363
343
|
stop() {
|
|
364
344
|
this.started = false;
|
|
@@ -368,204 +348,204 @@ export class ReminderRunner {
|
|
|
368
348
|
this.timers.clear();
|
|
369
349
|
this.idleGates.clear();
|
|
370
350
|
}
|
|
371
|
-
create(input) {
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
if (hasRun) {
|
|
397
|
-
throw new ReminderValidationError("reminders.create: anonymous run handlers are not durable on architecture v2; author reminders/<name>.ts under the project root and pass handler + args");
|
|
398
|
-
}
|
|
399
|
-
if (untilFn !== undefined) {
|
|
400
|
-
throw new ReminderValidationError("reminders.create: function until is not durable on architecture v2; use a string until prompt");
|
|
401
|
-
}
|
|
402
|
-
}
|
|
403
|
-
let handlerName;
|
|
404
|
-
let handlerArgs;
|
|
405
|
-
if (hasHandler) {
|
|
406
|
-
const handler = input.handler;
|
|
407
|
-
const name = typeof handler === "string" ? handler.trim() : this.namedHandlerNames.get(handler);
|
|
408
|
-
if (name === undefined) {
|
|
409
|
-
throw new ReminderValidationError("Unknown reminder handler definition (expected a default export discovered under reminders/)");
|
|
410
|
-
}
|
|
411
|
-
if (name === "") {
|
|
412
|
-
throw new ReminderValidationError("handler name is required");
|
|
413
|
-
}
|
|
414
|
-
// A mounted extension's handler registers as `<ns>__<name>`, a name no
|
|
415
|
-
// authored stem may take.
|
|
416
|
-
const registered = this.namedHandlers.has(name);
|
|
417
|
-
if (!registered && !isValidReminderHandlerName(name)) {
|
|
418
|
-
throw new ReminderValidationError(name.includes("__")
|
|
419
|
-
? `Invalid reminder handler name "${name}": may not contain "__" (reserved for mounted extensions' <ns>__<name> handlers)`
|
|
420
|
-
: `Invalid reminder handler name "${name}": must match ${NAME_PATTERN}`);
|
|
421
|
-
}
|
|
422
|
-
if (!registered) {
|
|
423
|
-
throw new ReminderValidationError(`Unknown reminder handler "${name}" (expected reminders/${name}.ts under the project root)`);
|
|
424
|
-
}
|
|
425
|
-
if (input.args !== undefined && !isJsonObject(input.args)) {
|
|
426
|
-
throw new ReminderValidationError("reminders.create args must be a JSON object");
|
|
427
|
-
}
|
|
428
|
-
handlerName = name;
|
|
429
|
-
handlerArgs = (_a = input.args) !== null && _a !== void 0 ? _a : {};
|
|
430
|
-
assertReminderArgsSize(handlerArgs);
|
|
431
|
-
}
|
|
432
|
-
else if (input.args !== undefined) {
|
|
433
|
-
throw new ReminderValidationError("reminders.create args is only valid with handler");
|
|
434
|
-
}
|
|
435
|
-
const schedule = resolveSchedule(input);
|
|
436
|
-
const busyPolicy = (_b = input.busyPolicy) !== null && _b !== void 0 ? _b : defaultBusyPolicy(schedule.kind);
|
|
437
|
-
const hasRunHandler = input.run !== undefined;
|
|
438
|
-
const hasUntilFn = untilFn !== undefined;
|
|
439
|
-
let record;
|
|
440
|
-
if (this.usesControlPlaneProtocol) {
|
|
441
|
-
const intent = {
|
|
442
|
-
protocolVersion: REMINDER_CP_PROTOCOL_VERSION,
|
|
443
|
-
purpose: input.purpose,
|
|
444
|
-
channelId: input.channelId,
|
|
445
|
-
continuationToken: input.continuationToken,
|
|
446
|
-
target: input.target,
|
|
447
|
-
// CP recalculates nextFireAtMs / intervalMs from the raw schedule.
|
|
448
|
-
schedule: scheduleSpecFromCreateInput(input),
|
|
449
|
-
prompt: input.prompt,
|
|
450
|
-
handler: handlerName,
|
|
451
|
-
args: handlerArgs,
|
|
452
|
-
hasRunHandler,
|
|
453
|
-
hasUntilFn,
|
|
454
|
-
untilPrompt,
|
|
455
|
-
busyPolicy,
|
|
456
|
-
};
|
|
457
|
-
record = yield this.controlPlane.createReminder(intent);
|
|
458
|
-
this.records.set(record.id, record);
|
|
459
|
-
}
|
|
460
|
-
else {
|
|
461
|
-
const now = new Date().toISOString();
|
|
462
|
-
const id = newReminderId();
|
|
463
|
-
record = {
|
|
464
|
-
version: 1,
|
|
465
|
-
id,
|
|
466
|
-
purpose: input.purpose,
|
|
467
|
-
channelId: input.channelId,
|
|
468
|
-
continuationToken: input.continuationToken,
|
|
469
|
-
target: input.target,
|
|
470
|
-
scheduleKind: schedule.kind,
|
|
471
|
-
scheduleValue: schedule.value,
|
|
472
|
-
intervalMs: schedule.intervalMs,
|
|
473
|
-
prompt: input.prompt,
|
|
474
|
-
handler: handlerName,
|
|
475
|
-
args: handlerArgs,
|
|
476
|
-
hasRunHandler,
|
|
477
|
-
hasUntilFn,
|
|
478
|
-
untilPrompt,
|
|
479
|
-
busyPolicy,
|
|
480
|
-
status: "armed",
|
|
481
|
-
fireCount: 0,
|
|
482
|
-
nextFireAtMs: schedule.nextFireAtMs,
|
|
483
|
-
generation: 0,
|
|
484
|
-
createdAt: now,
|
|
485
|
-
updatedAt: now,
|
|
486
|
-
};
|
|
487
|
-
yield this.persist(record);
|
|
351
|
+
async create(input) {
|
|
352
|
+
const hasPrompt = input.prompt !== undefined;
|
|
353
|
+
const hasRun = input.run !== undefined;
|
|
354
|
+
const hasHandler = input.handler !== undefined;
|
|
355
|
+
const formCount = [hasPrompt, hasRun, hasHandler].filter(Boolean).length;
|
|
356
|
+
if (formCount !== 1) {
|
|
357
|
+
throw new ReminderValidationError("reminders.create requires exactly one of prompt, handler, or run");
|
|
358
|
+
}
|
|
359
|
+
if (input.continuationToken.trim() === "") {
|
|
360
|
+
throw new ReminderValidationError("continuationToken is required");
|
|
361
|
+
}
|
|
362
|
+
if (input.channelId.trim() === "") {
|
|
363
|
+
throw new ReminderValidationError("channelId is required");
|
|
364
|
+
}
|
|
365
|
+
if (input.purpose.trim() === "") {
|
|
366
|
+
throw new ReminderValidationError("purpose is required");
|
|
367
|
+
}
|
|
368
|
+
const untilPrompt = typeof input.until === "string" ? input.until : undefined;
|
|
369
|
+
const untilFn = typeof input.until === "function" ? input.until : undefined;
|
|
370
|
+
if (hasHandler && input.until !== undefined) {
|
|
371
|
+
throw new ReminderValidationError("reminders.create: until is not valid with handler; encode the stop condition in args and the handler body");
|
|
372
|
+
}
|
|
373
|
+
if (this.rejectAnonymousHandlers) {
|
|
374
|
+
if (hasRun) {
|
|
375
|
+
throw new ReminderValidationError("reminders.create: anonymous run handlers are not durable on architecture v2; author reminders/<name>.ts under the project root and pass handler + args");
|
|
488
376
|
}
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
untilFn,
|
|
492
|
-
});
|
|
493
|
-
if (this.started && this.platformTimers === undefined && !this.usesControlPlaneProtocol) {
|
|
494
|
-
this.armTimer(record);
|
|
377
|
+
if (untilFn !== undefined) {
|
|
378
|
+
throw new ReminderValidationError("reminders.create: function until is not durable on architecture v2; use a string until prompt");
|
|
495
379
|
}
|
|
496
|
-
|
|
380
|
+
}
|
|
381
|
+
let handlerName;
|
|
382
|
+
let handlerArgs;
|
|
383
|
+
if (hasHandler) {
|
|
384
|
+
const handler = input.handler;
|
|
385
|
+
const name = typeof handler === "string" ? handler.trim() : this.namedHandlerNames.get(handler);
|
|
386
|
+
if (name === undefined) {
|
|
387
|
+
throw new ReminderValidationError("Unknown reminder handler definition (expected a default export discovered under reminders/)");
|
|
388
|
+
}
|
|
389
|
+
if (name === "") {
|
|
390
|
+
throw new ReminderValidationError("handler name is required");
|
|
391
|
+
}
|
|
392
|
+
// A mounted extension's handler registers as `<ns>__<name>`, a name no
|
|
393
|
+
// authored stem may take.
|
|
394
|
+
const registered = this.namedHandlers.has(name);
|
|
395
|
+
if (!registered && !isValidReminderHandlerName(name)) {
|
|
396
|
+
throw new ReminderValidationError(name.includes("__")
|
|
397
|
+
? `Invalid reminder handler name "${name}": may not contain "__" (reserved for mounted extensions' <ns>__<name> handlers)`
|
|
398
|
+
: `Invalid reminder handler name "${name}": must match ${NAME_PATTERN}`);
|
|
399
|
+
}
|
|
400
|
+
if (!registered) {
|
|
401
|
+
throw new ReminderValidationError(`Unknown reminder handler "${name}" (expected reminders/${name}.ts under the project root)`);
|
|
402
|
+
}
|
|
403
|
+
if (input.args !== undefined && !isJsonObject(input.args)) {
|
|
404
|
+
throw new ReminderValidationError("reminders.create args must be a JSON object");
|
|
405
|
+
}
|
|
406
|
+
handlerName = name;
|
|
407
|
+
handlerArgs = input.args ?? {};
|
|
408
|
+
assertReminderArgsSize(handlerArgs);
|
|
409
|
+
}
|
|
410
|
+
else if (input.args !== undefined) {
|
|
411
|
+
throw new ReminderValidationError("reminders.create args is only valid with handler");
|
|
412
|
+
}
|
|
413
|
+
const schedule = resolveSchedule(input);
|
|
414
|
+
const busyPolicy = input.busyPolicy ?? defaultBusyPolicy(schedule.kind);
|
|
415
|
+
const hasRunHandler = input.run !== undefined;
|
|
416
|
+
const hasUntilFn = untilFn !== undefined;
|
|
417
|
+
let record;
|
|
418
|
+
if (this.usesControlPlaneProtocol) {
|
|
419
|
+
const intent = {
|
|
420
|
+
protocolVersion: REMINDER_CP_PROTOCOL_VERSION,
|
|
421
|
+
purpose: input.purpose,
|
|
422
|
+
channelId: input.channelId,
|
|
423
|
+
continuationToken: input.continuationToken,
|
|
424
|
+
target: input.target,
|
|
425
|
+
// CP recalculates nextFireAtMs / intervalMs from the raw schedule.
|
|
426
|
+
schedule: scheduleSpecFromCreateInput(input),
|
|
427
|
+
prompt: input.prompt,
|
|
428
|
+
handler: handlerName,
|
|
429
|
+
args: handlerArgs,
|
|
430
|
+
hasRunHandler,
|
|
431
|
+
hasUntilFn,
|
|
432
|
+
untilPrompt,
|
|
433
|
+
busyPolicy,
|
|
434
|
+
};
|
|
435
|
+
record = await this.controlPlane.createReminder(intent);
|
|
436
|
+
this.records.set(record.id, record);
|
|
437
|
+
}
|
|
438
|
+
else {
|
|
439
|
+
const now = new Date().toISOString();
|
|
440
|
+
const id = newReminderId();
|
|
441
|
+
record = {
|
|
442
|
+
version: 1,
|
|
443
|
+
id,
|
|
444
|
+
purpose: input.purpose,
|
|
445
|
+
channelId: input.channelId,
|
|
446
|
+
continuationToken: input.continuationToken,
|
|
447
|
+
target: input.target,
|
|
448
|
+
scheduleKind: schedule.kind,
|
|
449
|
+
scheduleValue: schedule.value,
|
|
450
|
+
intervalMs: schedule.intervalMs,
|
|
451
|
+
prompt: input.prompt,
|
|
452
|
+
handler: handlerName,
|
|
453
|
+
args: handlerArgs,
|
|
454
|
+
hasRunHandler,
|
|
455
|
+
hasUntilFn,
|
|
456
|
+
untilPrompt,
|
|
457
|
+
busyPolicy,
|
|
458
|
+
status: "armed",
|
|
459
|
+
fireCount: 0,
|
|
460
|
+
nextFireAtMs: schedule.nextFireAtMs,
|
|
461
|
+
generation: 0,
|
|
462
|
+
createdAt: now,
|
|
463
|
+
updatedAt: now,
|
|
464
|
+
};
|
|
465
|
+
await this.persist(record);
|
|
466
|
+
}
|
|
467
|
+
this.handlers.set(record.id, {
|
|
468
|
+
run: input.run,
|
|
469
|
+
untilFn,
|
|
497
470
|
});
|
|
471
|
+
if (this.started && this.platformTimers === undefined && !this.usesControlPlaneProtocol) {
|
|
472
|
+
this.armTimer(record);
|
|
473
|
+
}
|
|
474
|
+
return toInfo(record);
|
|
498
475
|
}
|
|
499
|
-
list(filter) {
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
if (publicStatus !== filter.status) {
|
|
507
|
-
return false;
|
|
508
|
-
}
|
|
509
|
-
}
|
|
510
|
-
if ((filter === null || filter === void 0 ? void 0 : filter.purpose) !== undefined && r.purpose !== filter.purpose) {
|
|
511
|
-
return false;
|
|
512
|
-
}
|
|
513
|
-
if ((filter === null || filter === void 0 ? void 0 : filter.continuationToken) !== undefined &&
|
|
514
|
-
r.continuationToken !== filter.continuationToken) {
|
|
476
|
+
async list(filter) {
|
|
477
|
+
const all = await this.recordsForList(filter);
|
|
478
|
+
return all
|
|
479
|
+
.filter(r => {
|
|
480
|
+
if (filter?.status !== undefined) {
|
|
481
|
+
const publicStatus = r.status === "firing" ? "armed" : r.status;
|
|
482
|
+
if (publicStatus !== filter.status) {
|
|
515
483
|
return false;
|
|
516
484
|
}
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
|
|
485
|
+
}
|
|
486
|
+
if (filter?.purpose !== undefined && r.purpose !== filter.purpose) {
|
|
487
|
+
return false;
|
|
488
|
+
}
|
|
489
|
+
if (filter?.continuationToken !== undefined &&
|
|
490
|
+
r.continuationToken !== filter.continuationToken) {
|
|
491
|
+
return false;
|
|
492
|
+
}
|
|
493
|
+
return true;
|
|
494
|
+
})
|
|
495
|
+
.map(r => {
|
|
496
|
+
const live = this.handlers.get(r.id);
|
|
497
|
+
const info = toInfo(r);
|
|
498
|
+
if (live === undefined)
|
|
499
|
+
return info;
|
|
500
|
+
return {
|
|
501
|
+
...info,
|
|
502
|
+
hasRunHandler: live.run !== undefined || info.hasRunHandler,
|
|
503
|
+
hasUntilFn: live.untilFn !== undefined || info.hasUntilFn,
|
|
504
|
+
};
|
|
526
505
|
});
|
|
527
506
|
}
|
|
528
|
-
cancel(
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
if (
|
|
532
|
-
|
|
533
|
-
if (updated === undefined) {
|
|
534
|
-
this.records.delete(id);
|
|
535
|
-
this.handlers.delete(id);
|
|
536
|
-
return undefined;
|
|
537
|
-
}
|
|
538
|
-
this.records.set(id, updated);
|
|
507
|
+
async cancel(id, reason = "explicit_cancel") {
|
|
508
|
+
if (this.usesControlPlaneProtocol) {
|
|
509
|
+
const updated = await this.controlPlane.cancelReminder(id, reason);
|
|
510
|
+
if (updated === undefined) {
|
|
511
|
+
this.records.delete(id);
|
|
539
512
|
this.handlers.delete(id);
|
|
540
|
-
return toInfo(updated);
|
|
541
|
-
}
|
|
542
|
-
const record = yield this.getRecord(id);
|
|
543
|
-
if (record === undefined) {
|
|
544
513
|
return undefined;
|
|
545
514
|
}
|
|
546
|
-
|
|
547
|
-
return toInfo(record);
|
|
548
|
-
}
|
|
549
|
-
this.clearTimer(id);
|
|
550
|
-
const updated = Object.assign(Object.assign({}, record), { status: "cancelled", disarmReason: reason, generation: ((_a = record.generation) !== null && _a !== void 0 ? _a : 0) + 1, updatedAt: new Date().toISOString() });
|
|
551
|
-
yield this.persist(updated);
|
|
515
|
+
this.records.set(id, updated);
|
|
552
516
|
this.handlers.delete(id);
|
|
553
517
|
return toInfo(updated);
|
|
554
|
-
}
|
|
518
|
+
}
|
|
519
|
+
const record = await this.getRecord(id);
|
|
520
|
+
if (record === undefined) {
|
|
521
|
+
return undefined;
|
|
522
|
+
}
|
|
523
|
+
if (record.status !== "armed" && record.status !== "firing") {
|
|
524
|
+
return toInfo(record);
|
|
525
|
+
}
|
|
526
|
+
this.clearTimer(id);
|
|
527
|
+
const updated = {
|
|
528
|
+
...record,
|
|
529
|
+
status: "cancelled",
|
|
530
|
+
disarmReason: reason,
|
|
531
|
+
generation: (record.generation ?? 0) + 1,
|
|
532
|
+
updatedAt: new Date().toISOString(),
|
|
533
|
+
};
|
|
534
|
+
await this.persist(updated);
|
|
535
|
+
this.handlers.delete(id);
|
|
536
|
+
return toInfo(updated);
|
|
555
537
|
}
|
|
556
538
|
/** Cancel all armed reminders matching a continuation token (e.g. PR closed). */
|
|
557
|
-
cancelForContinuation(continuationToken, reason) {
|
|
558
|
-
|
|
559
|
-
|
|
560
|
-
|
|
561
|
-
|
|
562
|
-
|
|
563
|
-
|
|
564
|
-
n += 1;
|
|
565
|
-
}
|
|
539
|
+
async cancelForContinuation(continuationToken, reason) {
|
|
540
|
+
const armed = (await this.allRecords()).filter(r => r.status === "armed");
|
|
541
|
+
let n = 0;
|
|
542
|
+
for (const record of armed) {
|
|
543
|
+
if (record.continuationToken === continuationToken) {
|
|
544
|
+
await this.cancel(record.id, reason);
|
|
545
|
+
n += 1;
|
|
566
546
|
}
|
|
567
|
-
|
|
568
|
-
|
|
547
|
+
}
|
|
548
|
+
return n;
|
|
569
549
|
}
|
|
570
550
|
/**
|
|
571
551
|
* Re-key armed reminders when a channel rotates a session's continuation
|
|
@@ -573,142 +553,137 @@ export class ReminderRunner {
|
|
|
573
553
|
* this, a reminder armed before the rotation misses its session at fire
|
|
574
554
|
* time and disarms as `session_gone`.
|
|
575
555
|
*/
|
|
576
|
-
rekeyContinuation(channelId, oldToken, newToken) {
|
|
577
|
-
|
|
578
|
-
|
|
579
|
-
|
|
580
|
-
|
|
581
|
-
|
|
582
|
-
|
|
583
|
-
|
|
584
|
-
|
|
585
|
-
|
|
586
|
-
|
|
587
|
-
|
|
588
|
-
|
|
589
|
-
|
|
590
|
-
|
|
591
|
-
|
|
592
|
-
|
|
556
|
+
async rekeyContinuation(channelId, oldToken, newToken) {
|
|
557
|
+
if (this.usesControlPlaneProtocol) {
|
|
558
|
+
return this.controlPlane.rekeyReminders(channelId, oldToken, newToken);
|
|
559
|
+
}
|
|
560
|
+
const armed = (await this.allRecords()).filter(r => r.status === "armed" || r.status === "firing");
|
|
561
|
+
let n = 0;
|
|
562
|
+
for (const record of armed) {
|
|
563
|
+
if (record.channelId !== channelId || record.continuationToken !== oldToken) {
|
|
564
|
+
continue;
|
|
565
|
+
}
|
|
566
|
+
await this.persist({
|
|
567
|
+
...record,
|
|
568
|
+
continuationToken: newToken,
|
|
569
|
+
generation: (record.generation ?? 0) + 1,
|
|
570
|
+
updatedAt: new Date().toISOString(),
|
|
571
|
+
});
|
|
572
|
+
n += 1;
|
|
573
|
+
}
|
|
574
|
+
return n;
|
|
593
575
|
}
|
|
594
576
|
/** Fire one reminder immediately (dev / hillclimb / control plane). */
|
|
595
|
-
dispatch(
|
|
596
|
-
|
|
597
|
-
|
|
598
|
-
|
|
599
|
-
|
|
600
|
-
|
|
601
|
-
|
|
602
|
-
|
|
603
|
-
|
|
604
|
-
|
|
605
|
-
|
|
606
|
-
|
|
607
|
-
|
|
608
|
-
|
|
609
|
-
|
|
610
|
-
return Object.assign({ reminderId }, result);
|
|
611
|
-
});
|
|
577
|
+
async dispatch(reminderId, opts = {}) {
|
|
578
|
+
const record = await this.getRecord(reminderId);
|
|
579
|
+
if (record === undefined) {
|
|
580
|
+
const ids = (await this.allRecords()).map(r => r.id);
|
|
581
|
+
throw new UnknownReminderError(reminderId, ids);
|
|
582
|
+
}
|
|
583
|
+
if (opts.expectedFireCount !== undefined && record.fireCount !== opts.expectedFireCount) {
|
|
584
|
+
return {
|
|
585
|
+
reminderId,
|
|
586
|
+
outcome: "noop",
|
|
587
|
+
reason: "stale_fire_count",
|
|
588
|
+
};
|
|
589
|
+
}
|
|
590
|
+
const result = await this.fire(record.id, { force: true });
|
|
591
|
+
return { reminderId, ...result };
|
|
612
592
|
}
|
|
613
593
|
/**
|
|
614
594
|
* Architecture v2 / CP protocol guest entrypoint: execute an immutable fire
|
|
615
595
|
* descriptor and return an outcome. Does not mint ids or mutate
|
|
616
596
|
* authoritative status/fireCount — the control plane applies the outcome.
|
|
617
597
|
*/
|
|
618
|
-
executeFire(descriptor) {
|
|
619
|
-
|
|
620
|
-
|
|
621
|
-
|
|
622
|
-
|
|
623
|
-
|
|
624
|
-
|
|
625
|
-
|
|
626
|
-
|
|
627
|
-
|
|
628
|
-
// it so the retry loop is visible beside the settled outcomes.
|
|
629
|
-
this.recordFireOutcome(record, {
|
|
630
|
-
kind: "error",
|
|
631
|
-
reason: "fire_threw",
|
|
632
|
-
detail: error instanceof Error ? error.message : String(error),
|
|
633
|
-
});
|
|
634
|
-
throw error;
|
|
635
|
-
}
|
|
598
|
+
async executeFire(descriptor) {
|
|
599
|
+
const record = this.recordFromDescriptor(descriptor);
|
|
600
|
+
this.records.set(record.id, record);
|
|
601
|
+
let fire;
|
|
602
|
+
try {
|
|
603
|
+
fire = await this.runFire(record);
|
|
604
|
+
}
|
|
605
|
+
catch (error) {
|
|
606
|
+
// The hosted admission maps the throw to a retryable failure; count
|
|
607
|
+
// it so the retry loop is visible beside the settled outcomes.
|
|
636
608
|
this.recordFireOutcome(record, {
|
|
637
|
-
kind:
|
|
638
|
-
reason:
|
|
639
|
-
|
|
609
|
+
kind: "error",
|
|
610
|
+
reason: "fire_threw",
|
|
611
|
+
detail: error instanceof Error ? error.message : String(error),
|
|
640
612
|
});
|
|
641
|
-
|
|
613
|
+
throw error;
|
|
614
|
+
}
|
|
615
|
+
this.recordFireOutcome(record, {
|
|
616
|
+
kind: fire.outcome.kind,
|
|
617
|
+
reason: fire.reason,
|
|
618
|
+
turnStatus: fire.turnStatus,
|
|
642
619
|
});
|
|
620
|
+
return fire.outcome;
|
|
643
621
|
}
|
|
644
|
-
runFire(record) {
|
|
645
|
-
|
|
646
|
-
|
|
647
|
-
|
|
648
|
-
|
|
649
|
-
if (this.resolveNamedHandler(record.handler) === undefined) {
|
|
650
|
-
return stopFire("handler_missing");
|
|
651
|
-
}
|
|
652
|
-
}
|
|
653
|
-
else if ((record.hasRunHandler === true || record.hasUntilFn === true) &&
|
|
654
|
-
live.run === undefined &&
|
|
655
|
-
live.untilFn === undefined) {
|
|
656
|
-
return stopFire("handler_lost_on_restart");
|
|
657
|
-
}
|
|
658
|
-
if (live.untilFn !== undefined) {
|
|
659
|
-
try {
|
|
660
|
-
const done = yield live.untilFn(this.buildUntilContext(record));
|
|
661
|
-
if (done) {
|
|
662
|
-
return stopFire("until_fn");
|
|
663
|
-
}
|
|
664
|
-
}
|
|
665
|
-
catch (error) {
|
|
666
|
-
this.logger(`[bdk] reminder "${record.id}" until() failed: ${error instanceof Error ? error.message : String(error)}`);
|
|
667
|
-
}
|
|
668
|
-
}
|
|
669
|
-
if (yield this.isSessionBusy(record.channelId, record.continuationToken)) {
|
|
670
|
-
if (record.busyPolicy === "skipIfBusy") {
|
|
671
|
-
return { outcome: { kind: "skipped" }, reason: "busy" };
|
|
672
|
-
}
|
|
673
|
-
yield this.whenSessionIdle(record.channelId, record.continuationToken);
|
|
622
|
+
async runFire(record) {
|
|
623
|
+
const live = this.handlers.get(record.id) ?? {};
|
|
624
|
+
if (record.handler !== undefined) {
|
|
625
|
+
if (this.resolveNamedHandler(record.handler) === undefined) {
|
|
626
|
+
return stopFire("handler_missing");
|
|
674
627
|
}
|
|
675
|
-
|
|
676
|
-
|
|
677
|
-
|
|
678
|
-
|
|
679
|
-
|
|
680
|
-
|
|
681
|
-
|
|
628
|
+
}
|
|
629
|
+
else if ((record.hasRunHandler === true || record.hasUntilFn === true) &&
|
|
630
|
+
live.run === undefined &&
|
|
631
|
+
live.untilFn === undefined) {
|
|
632
|
+
return stopFire("handler_lost_on_restart");
|
|
633
|
+
}
|
|
634
|
+
if (live.untilFn !== undefined) {
|
|
635
|
+
try {
|
|
636
|
+
const done = await live.untilFn(this.buildUntilContext(record));
|
|
637
|
+
if (done) {
|
|
638
|
+
return stopFire("until_fn");
|
|
682
639
|
}
|
|
683
|
-
result = yield named.run(this.buildFireContext(record));
|
|
684
|
-
reason = runReason(result);
|
|
685
|
-
}
|
|
686
|
-
else if (live.run !== undefined) {
|
|
687
|
-
result = yield live.run(this.buildFireContext(record));
|
|
688
|
-
reason = runReason(result);
|
|
689
|
-
}
|
|
690
|
-
else if (record.prompt !== undefined) {
|
|
691
|
-
const delivery = yield this.deliverPromptFire(record);
|
|
692
|
-
result = delivery.result;
|
|
693
|
-
reason = delivery.reason;
|
|
694
|
-
turnStatus = delivery.turnStatus;
|
|
695
640
|
}
|
|
696
|
-
|
|
697
|
-
|
|
641
|
+
catch (error) {
|
|
642
|
+
this.logger(`[bdk] reminder "${record.id}" until() failed: ${error instanceof Error ? error.message : String(error)}`);
|
|
698
643
|
}
|
|
699
|
-
|
|
700
|
-
|
|
644
|
+
}
|
|
645
|
+
if (await this.isSessionBusy(record.channelId, record.continuationToken)) {
|
|
646
|
+
if (record.busyPolicy === "skipIfBusy") {
|
|
647
|
+
return { outcome: { kind: "skipped" }, reason: "busy" };
|
|
701
648
|
}
|
|
702
|
-
|
|
703
|
-
|
|
704
|
-
|
|
705
|
-
|
|
706
|
-
|
|
707
|
-
|
|
708
|
-
|
|
709
|
-
|
|
710
|
-
|
|
711
|
-
|
|
649
|
+
await this.whenSessionIdle(record.channelId, record.continuationToken);
|
|
650
|
+
}
|
|
651
|
+
let result;
|
|
652
|
+
let reason;
|
|
653
|
+
let turnStatus;
|
|
654
|
+
if (record.handler !== undefined) {
|
|
655
|
+
const named = this.resolveNamedHandler(record.handler);
|
|
656
|
+
if (named === undefined) {
|
|
657
|
+
return stopFire("handler_missing");
|
|
658
|
+
}
|
|
659
|
+
result = await named.run(this.buildFireContext(record));
|
|
660
|
+
reason = runReason(result);
|
|
661
|
+
}
|
|
662
|
+
else if (live.run !== undefined) {
|
|
663
|
+
result = await live.run(this.buildFireContext(record));
|
|
664
|
+
reason = runReason(result);
|
|
665
|
+
}
|
|
666
|
+
else if (record.prompt !== undefined) {
|
|
667
|
+
const delivery = await this.deliverPromptFire(record);
|
|
668
|
+
result = delivery.result;
|
|
669
|
+
reason = delivery.reason;
|
|
670
|
+
turnStatus = delivery.turnStatus;
|
|
671
|
+
}
|
|
672
|
+
else {
|
|
673
|
+
return stopFire("missing_handler");
|
|
674
|
+
}
|
|
675
|
+
if (result.action === "stop") {
|
|
676
|
+
return { outcome: { kind: "stop", reason }, reason };
|
|
677
|
+
}
|
|
678
|
+
const reschedule = result.reschedule !== undefined ? toWireReschedule(result.reschedule) : undefined;
|
|
679
|
+
return {
|
|
680
|
+
outcome: {
|
|
681
|
+
kind: result.action === "skip" ? "skipped" : "delivered",
|
|
682
|
+
reschedule,
|
|
683
|
+
},
|
|
684
|
+
reason,
|
|
685
|
+
turnStatus,
|
|
686
|
+
};
|
|
712
687
|
}
|
|
713
688
|
/**
|
|
714
689
|
* One structured line and one `agent_sdk.reminder.fire_outcome` count per
|
|
@@ -766,19 +741,18 @@ export class ReminderRunner {
|
|
|
766
741
|
};
|
|
767
742
|
}
|
|
768
743
|
armTimer(record) {
|
|
769
|
-
var _a;
|
|
770
744
|
this.clearTimer(record.id);
|
|
771
745
|
if (!this.started || record.status !== "armed")
|
|
772
746
|
return;
|
|
773
747
|
const nextFireAtMs = Number.isFinite(record.nextFireAtMs) ? record.nextFireAtMs : Date.now();
|
|
774
748
|
const delay = Math.min(MAX_TIMER_MS, Math.max(0, nextFireAtMs - Date.now()));
|
|
775
749
|
const timer = setTimeout(() => {
|
|
776
|
-
void this.fire(record.id).catch((error) =>
|
|
750
|
+
void this.fire(record.id).catch(async (error) => {
|
|
777
751
|
this.logger(`[bdk] reminder "${record.id}" fire failed: ${error instanceof Error ? error.message : String(error)}`);
|
|
778
752
|
// Keep recurring reminders alive after unexpected throws.
|
|
779
753
|
// One-shots stay armed so a failed wake can be retried / dispatched.
|
|
780
754
|
try {
|
|
781
|
-
const current =
|
|
755
|
+
const current = await this.getRecord(record.id);
|
|
782
756
|
if (current === undefined || current.status !== "armed")
|
|
783
757
|
return;
|
|
784
758
|
const oneShot = current.scheduleKind === "delay" || current.scheduleKind === "at";
|
|
@@ -786,14 +760,14 @@ export class ReminderRunner {
|
|
|
786
760
|
this.armTimer(current);
|
|
787
761
|
return;
|
|
788
762
|
}
|
|
789
|
-
|
|
763
|
+
await this.afterFire(current, "skip");
|
|
790
764
|
}
|
|
791
765
|
catch (rescheduleError) {
|
|
792
766
|
this.logger(`[bdk] reminder "${record.id}" reschedule after failure failed: ${rescheduleError instanceof Error ? rescheduleError.message : String(rescheduleError)}`);
|
|
793
767
|
}
|
|
794
|
-
})
|
|
768
|
+
});
|
|
795
769
|
}, delay);
|
|
796
|
-
|
|
770
|
+
timer.unref?.();
|
|
797
771
|
this.timers.set(record.id, timer);
|
|
798
772
|
}
|
|
799
773
|
clearTimer(id) {
|
|
@@ -806,130 +780,125 @@ export class ReminderRunner {
|
|
|
806
780
|
resolveNamedHandler(name) {
|
|
807
781
|
return this.namedHandlers.get(name);
|
|
808
782
|
}
|
|
809
|
-
fire(
|
|
810
|
-
|
|
811
|
-
|
|
812
|
-
|
|
813
|
-
|
|
783
|
+
async fire(id, opts = {}) {
|
|
784
|
+
if (this.firing.has(id)) {
|
|
785
|
+
return { outcome: "noop", reason: "already_firing" };
|
|
786
|
+
}
|
|
787
|
+
this.firing.add(id);
|
|
788
|
+
try {
|
|
789
|
+
const record = await this.getRecord(id);
|
|
790
|
+
if (record === undefined || record.status !== "armed") {
|
|
791
|
+
this.clearTimer(id);
|
|
792
|
+
return { outcome: "noop", reason: "not_armed" };
|
|
814
793
|
}
|
|
815
|
-
|
|
816
|
-
|
|
817
|
-
|
|
818
|
-
|
|
819
|
-
|
|
820
|
-
|
|
821
|
-
|
|
822
|
-
if (
|
|
823
|
-
|
|
824
|
-
|
|
825
|
-
return { outcome: "noop", reason: "not_due" };
|
|
826
|
-
}
|
|
827
|
-
const live = (_a = this.handlers.get(id)) !== null && _a !== void 0 ? _a : {};
|
|
828
|
-
if (record.handler !== undefined) {
|
|
829
|
-
if (this.resolveNamedHandler(record.handler) === undefined) {
|
|
830
|
-
yield this.disarm(record, "handler_missing");
|
|
831
|
-
return { outcome: "noop", reason: "handler_missing" };
|
|
832
|
-
}
|
|
833
|
-
}
|
|
834
|
-
else if ((record.hasRunHandler === true || record.hasUntilFn === true) &&
|
|
835
|
-
live.run === undefined &&
|
|
836
|
-
live.untilFn === undefined) {
|
|
837
|
-
yield this.disarm(record, "handler_lost_on_restart");
|
|
838
|
-
return { outcome: "noop", reason: "handler_lost_on_restart" };
|
|
839
|
-
}
|
|
840
|
-
const untilCtx = this.buildUntilContext(record);
|
|
841
|
-
if (live.untilFn !== undefined) {
|
|
842
|
-
try {
|
|
843
|
-
const done = yield live.untilFn(untilCtx);
|
|
844
|
-
if (done) {
|
|
845
|
-
yield this.disarm(record, "until_fn");
|
|
846
|
-
return { outcome: "fired", reason: "until_fn" };
|
|
847
|
-
}
|
|
848
|
-
}
|
|
849
|
-
catch (error) {
|
|
850
|
-
this.logger(`[bdk] reminder "${id}" until() failed: ${error instanceof Error ? error.message : String(error)}`);
|
|
851
|
-
}
|
|
852
|
-
}
|
|
853
|
-
if (yield this.isSessionBusy(record.channelId, record.continuationToken)) {
|
|
854
|
-
if (record.busyPolicy === "skipIfBusy") {
|
|
855
|
-
yield this.afterFire(record, "skip");
|
|
856
|
-
this.logger(`[bdk] reminder "${id}" (${record.purpose}) skipped: session busy`);
|
|
857
|
-
return { outcome: "fired", reason: "skipped_busy" };
|
|
858
|
-
}
|
|
859
|
-
yield this.whenSessionIdle(record.channelId, record.continuationToken);
|
|
860
|
-
const still = yield this.getRecord(id);
|
|
861
|
-
if (still === undefined || still.status !== "armed") {
|
|
862
|
-
return { outcome: "noop", reason: "disarmed_while_waiting" };
|
|
863
|
-
}
|
|
794
|
+
if (!opts.force && record.nextFireAtMs > Date.now() + 50) {
|
|
795
|
+
// Spurious early wake — re-arm.
|
|
796
|
+
this.armTimer(record);
|
|
797
|
+
return { outcome: "noop", reason: "not_due" };
|
|
798
|
+
}
|
|
799
|
+
const live = this.handlers.get(id) ?? {};
|
|
800
|
+
if (record.handler !== undefined) {
|
|
801
|
+
if (this.resolveNamedHandler(record.handler) === undefined) {
|
|
802
|
+
await this.disarm(record, "handler_missing");
|
|
803
|
+
return { outcome: "noop", reason: "handler_missing" };
|
|
864
804
|
}
|
|
865
|
-
|
|
866
|
-
|
|
867
|
-
|
|
868
|
-
|
|
869
|
-
|
|
870
|
-
|
|
805
|
+
}
|
|
806
|
+
else if ((record.hasRunHandler === true || record.hasUntilFn === true) &&
|
|
807
|
+
live.run === undefined &&
|
|
808
|
+
live.untilFn === undefined) {
|
|
809
|
+
await this.disarm(record, "handler_lost_on_restart");
|
|
810
|
+
return { outcome: "noop", reason: "handler_lost_on_restart" };
|
|
811
|
+
}
|
|
812
|
+
const untilCtx = this.buildUntilContext(record);
|
|
813
|
+
if (live.untilFn !== undefined) {
|
|
814
|
+
try {
|
|
815
|
+
const done = await live.untilFn(untilCtx);
|
|
816
|
+
if (done) {
|
|
817
|
+
await this.disarm(record, "until_fn");
|
|
818
|
+
return { outcome: "fired", reason: "until_fn" };
|
|
871
819
|
}
|
|
872
|
-
result = yield named.run(this.buildFireContext(record));
|
|
873
|
-
}
|
|
874
|
-
else if (live.run !== undefined) {
|
|
875
|
-
result = yield live.run(this.buildFireContext(record));
|
|
876
820
|
}
|
|
877
|
-
|
|
878
|
-
|
|
821
|
+
catch (error) {
|
|
822
|
+
this.logger(`[bdk] reminder "${id}" until() failed: ${error instanceof Error ? error.message : String(error)}`);
|
|
879
823
|
}
|
|
880
|
-
|
|
881
|
-
|
|
882
|
-
|
|
824
|
+
}
|
|
825
|
+
if (await this.isSessionBusy(record.channelId, record.continuationToken)) {
|
|
826
|
+
if (record.busyPolicy === "skipIfBusy") {
|
|
827
|
+
await this.afterFire(record, "skip");
|
|
828
|
+
this.logger(`[bdk] reminder "${id}" (${record.purpose}) skipped: session busy`);
|
|
829
|
+
return { outcome: "fired", reason: "skipped_busy" };
|
|
883
830
|
}
|
|
884
|
-
|
|
885
|
-
const
|
|
886
|
-
if (
|
|
887
|
-
|
|
888
|
-
yield this.disarm(latest, "run_stop");
|
|
889
|
-
this.logger(`[bdk] reminder "${id}" (${record.purpose}) disarmed: run_stop`);
|
|
890
|
-
return { outcome: "fired", reason: "run_stop" };
|
|
891
|
-
}
|
|
892
|
-
return {
|
|
893
|
-
outcome: "fired",
|
|
894
|
-
reason: (_b = latest === null || latest === void 0 ? void 0 : latest.disarmReason) !== null && _b !== void 0 ? _b : "run_stop",
|
|
895
|
-
};
|
|
831
|
+
await this.whenSessionIdle(record.channelId, record.continuationToken);
|
|
832
|
+
const still = await this.getRecord(id);
|
|
833
|
+
if (still === undefined || still.status !== "armed") {
|
|
834
|
+
return { outcome: "noop", reason: "disarmed_while_waiting" };
|
|
896
835
|
}
|
|
897
|
-
|
|
898
|
-
|
|
836
|
+
}
|
|
837
|
+
let result;
|
|
838
|
+
if (record.handler !== undefined) {
|
|
839
|
+
const named = this.resolveNamedHandler(record.handler);
|
|
840
|
+
if (named === undefined) {
|
|
841
|
+
await this.disarm(record, "handler_missing");
|
|
842
|
+
return { outcome: "noop", reason: "handler_missing" };
|
|
899
843
|
}
|
|
900
|
-
|
|
901
|
-
|
|
902
|
-
|
|
903
|
-
|
|
844
|
+
result = await named.run(this.buildFireContext(record));
|
|
845
|
+
}
|
|
846
|
+
else if (live.run !== undefined) {
|
|
847
|
+
result = await live.run(this.buildFireContext(record));
|
|
848
|
+
}
|
|
849
|
+
else if (record.prompt !== undefined) {
|
|
850
|
+
result = await this.deliverPrompt(record);
|
|
851
|
+
}
|
|
852
|
+
else {
|
|
853
|
+
await this.disarm(record, "missing_handler");
|
|
854
|
+
return { outcome: "noop", reason: "missing_handler" };
|
|
855
|
+
}
|
|
856
|
+
// Cancel / disarm during the handler must win over reschedule.
|
|
857
|
+
const latest = await this.getRecord(id);
|
|
858
|
+
if (result.action === "stop") {
|
|
859
|
+
if (latest !== undefined && latest.status === "armed") {
|
|
860
|
+
await this.disarm(latest, "run_stop");
|
|
861
|
+
this.logger(`[bdk] reminder "${id}" (${record.purpose}) disarmed: run_stop`);
|
|
862
|
+
return { outcome: "fired", reason: "run_stop" };
|
|
904
863
|
}
|
|
905
|
-
|
|
906
|
-
|
|
907
|
-
|
|
864
|
+
return {
|
|
865
|
+
outcome: "fired",
|
|
866
|
+
reason: latest?.disarmReason ?? "run_stop",
|
|
867
|
+
};
|
|
908
868
|
}
|
|
909
|
-
|
|
910
|
-
|
|
869
|
+
if (latest === undefined || latest.status !== "armed") {
|
|
870
|
+
return { outcome: "noop", reason: "disarmed_during_fire" };
|
|
911
871
|
}
|
|
912
|
-
|
|
872
|
+
if (result.action === "skip") {
|
|
873
|
+
await this.afterFire(latest, "skip", result.reschedule);
|
|
874
|
+
this.logger(`[bdk] reminder "${id}" (${record.purpose}) skipped`);
|
|
875
|
+
return { outcome: "fired", reason: "skipped" };
|
|
876
|
+
}
|
|
877
|
+
await this.afterFire(latest, "delivered", result.reschedule);
|
|
878
|
+
this.logger(`[bdk] reminder "${id}" (${record.purpose}) fired`);
|
|
879
|
+
return { outcome: "fired", reason: "delivered" };
|
|
880
|
+
}
|
|
881
|
+
finally {
|
|
882
|
+
this.firing.delete(id);
|
|
883
|
+
}
|
|
913
884
|
}
|
|
914
|
-
isSessionBusy(channelId, continuationToken) {
|
|
915
|
-
|
|
916
|
-
|
|
917
|
-
|
|
918
|
-
|
|
919
|
-
return this.engine.isTurnRunning(existing.sessionId);
|
|
920
|
-
});
|
|
885
|
+
async isSessionBusy(channelId, continuationToken) {
|
|
886
|
+
const existing = await this.engine.sessions.findByContinuation(channelId, continuationToken);
|
|
887
|
+
if (existing === undefined)
|
|
888
|
+
return false;
|
|
889
|
+
return this.engine.isTurnRunning(existing.sessionId);
|
|
921
890
|
}
|
|
922
891
|
whenSessionIdle(channelId, continuationToken) {
|
|
923
892
|
const sessionKey = `${channelId}:${continuationToken}`;
|
|
924
893
|
const existing = this.idleGates.get(sessionKey);
|
|
925
894
|
if (existing !== undefined)
|
|
926
895
|
return existing;
|
|
927
|
-
const gate = (() =>
|
|
928
|
-
const session =
|
|
896
|
+
const gate = (async () => {
|
|
897
|
+
const session = await this.engine.sessions.findByContinuation(channelId, continuationToken);
|
|
929
898
|
if (session === undefined)
|
|
930
899
|
return;
|
|
931
|
-
|
|
932
|
-
})
|
|
900
|
+
await this.engine.waitForCompletion(session.sessionId);
|
|
901
|
+
})().finally(() => {
|
|
933
902
|
this.idleGates.delete(sessionKey);
|
|
934
903
|
});
|
|
935
904
|
this.idleGates.set(sessionKey, gate);
|
|
@@ -942,10 +911,8 @@ export class ReminderRunner {
|
|
|
942
911
|
boundSession(record) {
|
|
943
912
|
return this.engine.sessions.findByContinuation(record.channelId, record.continuationToken);
|
|
944
913
|
}
|
|
945
|
-
deliverPrompt(record) {
|
|
946
|
-
return
|
|
947
|
-
return (yield this.deliverPromptFire(record)).result;
|
|
948
|
-
});
|
|
914
|
+
async deliverPrompt(record) {
|
|
915
|
+
return (await this.deliverPromptFire(record)).result;
|
|
949
916
|
}
|
|
950
917
|
/**
|
|
951
918
|
* Where a prompt fire joins. The pod's own store first; on a hosted guest
|
|
@@ -954,85 +921,78 @@ export class ReminderRunner {
|
|
|
954
921
|
* keeps its own principal (channel admissions may carry a sender), so the
|
|
955
922
|
* fire joins under that, never under {@link ReminderHostedGuestOptions.fireAuth}.
|
|
956
923
|
*/
|
|
957
|
-
resolveFollowupTarget(record) {
|
|
958
|
-
|
|
959
|
-
|
|
960
|
-
|
|
961
|
-
|
|
962
|
-
|
|
963
|
-
|
|
964
|
-
|
|
965
|
-
|
|
966
|
-
|
|
967
|
-
|
|
968
|
-
|
|
969
|
-
|
|
970
|
-
|
|
971
|
-
|
|
972
|
-
|
|
973
|
-
|
|
974
|
-
|
|
975
|
-
: { auth: session.auth, sessionId: session.sessionId, reached: "session_restored" };
|
|
976
|
-
});
|
|
924
|
+
async resolveFollowupTarget(record) {
|
|
925
|
+
const local = await this.boundSession(record);
|
|
926
|
+
if (local !== undefined) {
|
|
927
|
+
return { auth: local.auth, sessionId: local.sessionId, reached: "session_joined" };
|
|
928
|
+
}
|
|
929
|
+
if (this.hostedGuest === undefined) {
|
|
930
|
+
return { auth: undefined, reached: "session_gone" };
|
|
931
|
+
}
|
|
932
|
+
// Materializes the durable record locally; a storage error propagates
|
|
933
|
+
// and the hosted admission retries the fire.
|
|
934
|
+
const restored = await this.engine.hasContinuationSession(record.channelId, record.continuationToken);
|
|
935
|
+
if (!restored) {
|
|
936
|
+
return { auth: undefined, reached: "session_gone" };
|
|
937
|
+
}
|
|
938
|
+
const session = await this.boundSession(record);
|
|
939
|
+
return session === undefined
|
|
940
|
+
? { auth: undefined, reached: "session_unreadable" }
|
|
941
|
+
: { auth: session.auth, sessionId: session.sessionId, reached: "session_restored" };
|
|
977
942
|
}
|
|
978
|
-
deliverPromptFire(record) {
|
|
979
|
-
|
|
980
|
-
|
|
981
|
-
|
|
982
|
-
|
|
943
|
+
async deliverPromptFire(record) {
|
|
944
|
+
const target = await this.resolveFollowupTarget(record);
|
|
945
|
+
if (target.auth !== undefined) {
|
|
946
|
+
return this.promptDelivery(record, target, target.reached);
|
|
947
|
+
}
|
|
948
|
+
if (target.reached === "session_unreadable") {
|
|
949
|
+
this.logger(`[bdk] reminder "${record.id}" (${record.purpose}) durable session did not materialize locally; not opening a new one`);
|
|
950
|
+
return { result: { action: "stop" }, reason: "session_gone" };
|
|
951
|
+
}
|
|
952
|
+
if (this.hostedGuest !== undefined &&
|
|
953
|
+
this.channelUsesControlPlaneContinuation(record.channelId)) {
|
|
954
|
+
// No session on this pod and none in durable storage. Open one on the
|
|
955
|
+
// stored key the way an inbound event on a fresh key does, instead of
|
|
956
|
+
// consuming the fire with no turn.
|
|
957
|
+
const fireAuth = this.hostedGuest.fireAuth();
|
|
958
|
+
if (fireAuth === undefined) {
|
|
959
|
+
return {
|
|
960
|
+
result: {
|
|
961
|
+
action: "skip",
|
|
962
|
+
reschedule: { delay: REMINDER_SESSION_OPEN_RETRY_DELAY },
|
|
963
|
+
},
|
|
964
|
+
reason: "session_gone",
|
|
965
|
+
};
|
|
983
966
|
}
|
|
984
|
-
|
|
985
|
-
this.
|
|
986
|
-
return { result: { action: "stop" }, reason: "session_gone" };
|
|
967
|
+
try {
|
|
968
|
+
return await this.promptDelivery(record, { auth: fireAuth }, "session_opened");
|
|
987
969
|
}
|
|
988
|
-
|
|
989
|
-
|
|
990
|
-
|
|
991
|
-
// stored key the way an inbound event on a fresh key does, instead of
|
|
992
|
-
// consuming the fire with no turn.
|
|
993
|
-
const fireAuth = this.hostedGuest.fireAuth();
|
|
994
|
-
if (fireAuth === undefined) {
|
|
995
|
-
return {
|
|
996
|
-
result: {
|
|
997
|
-
action: "skip",
|
|
998
|
-
reschedule: { delay: REMINDER_SESSION_OPEN_RETRY_DELAY },
|
|
999
|
-
},
|
|
1000
|
-
reason: "session_gone",
|
|
1001
|
-
};
|
|
1002
|
-
}
|
|
1003
|
-
try {
|
|
1004
|
-
return yield this.promptDelivery(record, { auth: fireAuth }, "session_opened");
|
|
1005
|
-
}
|
|
1006
|
-
catch (error) {
|
|
1007
|
-
if (!(error instanceof SessionAuthError)) {
|
|
1008
|
-
throw error;
|
|
1009
|
-
}
|
|
1010
|
-
// A session landed on this key under another principal between the
|
|
1011
|
-
// lookup and the send. That does not change on retry, so stop
|
|
1012
|
-
// rather than let the control plane re-admit until its attempt cap.
|
|
1013
|
-
this.logger(`[bdk] reminder "${record.id}" (${record.purpose}) cannot join session on its continuation: ${error.message}`);
|
|
1014
|
-
return { result: { action: "stop" }, reason: "session_gone" };
|
|
970
|
+
catch (error) {
|
|
971
|
+
if (!(error instanceof SessionAuthError)) {
|
|
972
|
+
throw error;
|
|
1015
973
|
}
|
|
974
|
+
// A session landed on this key under another principal between the
|
|
975
|
+
// lookup and the send. That does not change on retry, so stop
|
|
976
|
+
// rather than let the control plane re-admit until its attempt cap.
|
|
977
|
+
this.logger(`[bdk] reminder "${record.id}" (${record.purpose}) cannot join session on its continuation: ${error.message}`);
|
|
978
|
+
return { result: { action: "stop" }, reason: "session_gone" };
|
|
1016
979
|
}
|
|
1017
|
-
|
|
1018
|
-
|
|
1019
|
-
|
|
1020
|
-
|
|
1021
|
-
|
|
1022
|
-
|
|
1023
|
-
}
|
|
980
|
+
}
|
|
981
|
+
// Under CP protocol, return stop and let the CP apply/CAS; do not
|
|
982
|
+
// mutate authoritative status from the guest mid-fire.
|
|
983
|
+
if (!this.usesControlPlaneProtocol) {
|
|
984
|
+
await this.disarm(record, "session_gone");
|
|
985
|
+
}
|
|
986
|
+
return { result: { action: "stop" }, reason: "session_gone" };
|
|
1024
987
|
}
|
|
1025
|
-
promptDelivery(record, address, delivered) {
|
|
1026
|
-
|
|
1027
|
-
|
|
1028
|
-
|
|
1029
|
-
|
|
1030
|
-
|
|
1031
|
-
|
|
1032
|
-
|
|
1033
|
-
turnStatus: settle.turnStatus,
|
|
1034
|
-
};
|
|
1035
|
-
});
|
|
988
|
+
async promptDelivery(record, address, delivered) {
|
|
989
|
+
const settle = {};
|
|
990
|
+
const result = await this.sendFollowup(record, buildFirePrompt(record), address, settle);
|
|
991
|
+
return {
|
|
992
|
+
result,
|
|
993
|
+
reason: result.action === "skip" ? (settle.skipReason ?? "busy") : delivered,
|
|
994
|
+
turnStatus: settle.turnStatus,
|
|
995
|
+
};
|
|
1036
996
|
}
|
|
1037
997
|
/**
|
|
1038
998
|
* Join the reminder's existing session. Control-plane (`/v2`) channels
|
|
@@ -1045,9 +1005,13 @@ export class ReminderRunner {
|
|
|
1045
1005
|
followupSendOptions(record, address) {
|
|
1046
1006
|
const { auth, sessionId } = address;
|
|
1047
1007
|
if (this.channelUsesControlPlaneContinuation(record.channelId)) {
|
|
1048
|
-
return
|
|
1049
|
-
|
|
1050
|
-
: {}
|
|
1008
|
+
return {
|
|
1009
|
+
auth,
|
|
1010
|
+
mailboxContinuation: { key: record.continuationToken },
|
|
1011
|
+
...(sessionId !== undefined && isOneShotContinuationKey(record.continuationToken)
|
|
1012
|
+
? { hostedSessionId: sessionId }
|
|
1013
|
+
: {}),
|
|
1014
|
+
};
|
|
1051
1015
|
}
|
|
1052
1016
|
return {
|
|
1053
1017
|
auth,
|
|
@@ -1055,7 +1019,6 @@ export class ReminderRunner {
|
|
|
1055
1019
|
};
|
|
1056
1020
|
}
|
|
1057
1021
|
channelUsesControlPlaneContinuation(channelId) {
|
|
1058
|
-
var _a;
|
|
1059
1022
|
const project = this.engine.project;
|
|
1060
1023
|
// Test doubles may omit `project`; keep the legacy continuationToken path.
|
|
1061
1024
|
if (project === undefined) {
|
|
@@ -1063,7 +1026,7 @@ export class ReminderRunner {
|
|
|
1063
1026
|
}
|
|
1064
1027
|
const identity = channelId === HTTP_CHANNEL_ID
|
|
1065
1028
|
? httpContinuationIdentity(project.httpChannel)
|
|
1066
|
-
: channelContinuationIdentity(
|
|
1029
|
+
: channelContinuationIdentity(project.channels.find(channel => channel.id === channelId)?.definition);
|
|
1067
1030
|
return continuationSource(identity) === "control-plane";
|
|
1068
1031
|
}
|
|
1069
1032
|
/**
|
|
@@ -1072,185 +1035,207 @@ export class ReminderRunner {
|
|
|
1072
1035
|
* because the control plane may stop the pod as soon as the fire's
|
|
1073
1036
|
* outcome is applied, and `delivered` must mean the turn ran.
|
|
1074
1037
|
*/
|
|
1075
|
-
settleFollowup(session, settle) {
|
|
1076
|
-
|
|
1077
|
-
|
|
1078
|
-
|
|
1079
|
-
|
|
1080
|
-
|
|
1081
|
-
|
|
1082
|
-
|
|
1083
|
-
|
|
1084
|
-
|
|
1085
|
-
|
|
1086
|
-
}
|
|
1087
|
-
});
|
|
1038
|
+
async settleFollowup(session, settle) {
|
|
1039
|
+
const completion = this.engine.waitForCompletion(session.id);
|
|
1040
|
+
if (this.hostedGuest === undefined) {
|
|
1041
|
+
this.engine.trackBackground(completion);
|
|
1042
|
+
return;
|
|
1043
|
+
}
|
|
1044
|
+
const outcome = await completion;
|
|
1045
|
+
await this.engine.drainSessionDurability(session.id);
|
|
1046
|
+
if (settle !== undefined) {
|
|
1047
|
+
settle.turnStatus = outcome.status;
|
|
1048
|
+
}
|
|
1088
1049
|
}
|
|
1089
|
-
sendFollowup(record, message, address, settle, extra) {
|
|
1090
|
-
|
|
1091
|
-
|
|
1092
|
-
|
|
1093
|
-
|
|
1094
|
-
|
|
1095
|
-
|
|
1096
|
-
|
|
1097
|
-
|
|
1098
|
-
|
|
1099
|
-
|
|
1100
|
-
|
|
1101
|
-
|
|
1102
|
-
|
|
1103
|
-
|
|
1104
|
-
|
|
1105
|
-
|
|
1106
|
-
|
|
1107
|
-
const session = yield this.engine.send(record.channelId, message, sendOptions);
|
|
1108
|
-
yield this.settleFollowup(session, settle);
|
|
1109
|
-
return { action: "delivered" };
|
|
1050
|
+
async sendFollowup(record, message, address, settle, extra) {
|
|
1051
|
+
const sendOptions = {
|
|
1052
|
+
...this.followupSendOptions(record, address),
|
|
1053
|
+
state: extra?.state,
|
|
1054
|
+
refreshState: extra?.refreshState,
|
|
1055
|
+
title: extra?.title,
|
|
1056
|
+
clearCloudOverride: extra?.clearCloudOverride,
|
|
1057
|
+
};
|
|
1058
|
+
try {
|
|
1059
|
+
const session = await this.engine.send(record.channelId, message, sendOptions);
|
|
1060
|
+
await this.settleFollowup(session, settle);
|
|
1061
|
+
return { action: "delivered" };
|
|
1062
|
+
}
|
|
1063
|
+
catch (error) {
|
|
1064
|
+
if (error instanceof SessionBusyError) {
|
|
1065
|
+
if (record.busyPolicy === "skipIfBusy") {
|
|
1066
|
+
if (settle !== undefined) {
|
|
1067
|
+
settle.skipReason = "busy";
|
|
1110
1068
|
}
|
|
1111
|
-
|
|
1112
|
-
|
|
1113
|
-
|
|
1114
|
-
|
|
1115
|
-
|
|
1116
|
-
|
|
1069
|
+
return { action: "skip" };
|
|
1070
|
+
}
|
|
1071
|
+
await this.whenSessionIdle(record.channelId, record.continuationToken);
|
|
1072
|
+
try {
|
|
1073
|
+
const session = await this.engine.send(record.channelId, message, sendOptions);
|
|
1074
|
+
await this.settleFollowup(session, settle);
|
|
1075
|
+
return { action: "delivered" };
|
|
1076
|
+
}
|
|
1077
|
+
catch (retryError) {
|
|
1078
|
+
this.logger(`[bdk] reminder "${record.id}" followup failed after wait: ${retryError instanceof Error ? retryError.message : String(retryError)}`);
|
|
1079
|
+
if (settle !== undefined) {
|
|
1080
|
+
settle.skipReason = "followup_failed";
|
|
1117
1081
|
}
|
|
1082
|
+
return { action: "skip" };
|
|
1118
1083
|
}
|
|
1119
|
-
throw error;
|
|
1120
1084
|
}
|
|
1121
|
-
|
|
1085
|
+
throw error;
|
|
1086
|
+
}
|
|
1122
1087
|
}
|
|
1123
1088
|
buildFireContext(record) {
|
|
1124
|
-
var _a, _b;
|
|
1125
1089
|
const self = this;
|
|
1126
1090
|
return {
|
|
1127
1091
|
id: record.id,
|
|
1128
1092
|
purpose: record.purpose,
|
|
1129
1093
|
fireCount: record.fireCount,
|
|
1130
|
-
target:
|
|
1131
|
-
|
|
1094
|
+
target: {
|
|
1095
|
+
channelId: record.channelId,
|
|
1096
|
+
continuationToken: record.continuationToken,
|
|
1097
|
+
...(record.target ?? {}),
|
|
1098
|
+
},
|
|
1099
|
+
args: record.args ?? {},
|
|
1132
1100
|
host: this.engine.host,
|
|
1133
1101
|
mcp: this.engine.host.mcp,
|
|
1134
1102
|
appAuth: APP_AUTH,
|
|
1135
1103
|
artifacts: this.engine.artifacts,
|
|
1136
|
-
followup(args) {
|
|
1137
|
-
|
|
1138
|
-
|
|
1139
|
-
|
|
1140
|
-
|
|
1141
|
-
|
|
1142
|
-
|
|
1143
|
-
|
|
1144
|
-
|
|
1145
|
-
|
|
1146
|
-
|
|
1147
|
-
|
|
1148
|
-
|
|
1149
|
-
|
|
1150
|
-
|
|
1151
|
-
|
|
1152
|
-
yield self.disarm(record, "session_gone");
|
|
1153
|
-
}
|
|
1154
|
-
throw new Error(`Reminder "${record.id}" followup: session gone for continuation`);
|
|
1155
|
-
}
|
|
1156
|
-
const result = yield self.sendFollowup(record, args.message, address, undefined, args);
|
|
1157
|
-
if (result.action === "skip") {
|
|
1158
|
-
throw new Error(`Reminder "${record.id}" followup skipped: session busy`);
|
|
1159
|
-
}
|
|
1160
|
-
const existing = yield self.boundSession(record);
|
|
1161
|
-
if (existing === undefined) {
|
|
1162
|
-
throw new Error(`Reminder "${record.id}" followup: session not found for continuation`);
|
|
1163
|
-
}
|
|
1164
|
-
const session = yield self.engine.getChannelSession(record.channelId, existing.sessionId);
|
|
1165
|
-
if (session === null) {
|
|
1166
|
-
throw new Error(`Reminder "${record.id}" followup: channel session missing`);
|
|
1104
|
+
async followup(args) {
|
|
1105
|
+
const target = await self.resolveFollowupTarget(record);
|
|
1106
|
+
let address = target.auth === undefined ? undefined : target;
|
|
1107
|
+
// `openAuth` opens only when nothing exists anywhere: an unreadable
|
|
1108
|
+
// durable session must not be shadowed by a second one on the same
|
|
1109
|
+
// key (prompt-fire parity).
|
|
1110
|
+
if (address === undefined &&
|
|
1111
|
+
target.reached === "session_gone" &&
|
|
1112
|
+
args.openAuth !== undefined) {
|
|
1113
|
+
address = { auth: args.openAuth };
|
|
1114
|
+
}
|
|
1115
|
+
if (address === undefined) {
|
|
1116
|
+
// Under the CP protocol the guest never writes reminder status;
|
|
1117
|
+
// the fire's `stop` outcome carries the reason instead.
|
|
1118
|
+
if (!self.usesControlPlaneProtocol) {
|
|
1119
|
+
await self.disarm(record, "session_gone");
|
|
1167
1120
|
}
|
|
1168
|
-
|
|
1169
|
-
}
|
|
1121
|
+
throw new Error(`Reminder "${record.id}" followup: session gone for continuation`);
|
|
1122
|
+
}
|
|
1123
|
+
const result = await self.sendFollowup(record, args.message, address, undefined, args);
|
|
1124
|
+
if (result.action === "skip") {
|
|
1125
|
+
throw new Error(`Reminder "${record.id}" followup skipped: session busy`);
|
|
1126
|
+
}
|
|
1127
|
+
const existing = await self.boundSession(record);
|
|
1128
|
+
if (existing === undefined) {
|
|
1129
|
+
throw new Error(`Reminder "${record.id}" followup: session not found for continuation`);
|
|
1130
|
+
}
|
|
1131
|
+
const session = await self.engine.getChannelSession(record.channelId, existing.sessionId);
|
|
1132
|
+
if (session === null) {
|
|
1133
|
+
throw new Error(`Reminder "${record.id}" followup: channel session missing`);
|
|
1134
|
+
}
|
|
1135
|
+
return session;
|
|
1170
1136
|
},
|
|
1171
|
-
cancel() {
|
|
1172
|
-
|
|
1173
|
-
yield self.cancel(record.id, reason);
|
|
1174
|
-
});
|
|
1137
|
+
async cancel(reason = "handler_cancel") {
|
|
1138
|
+
await self.cancel(record.id, reason);
|
|
1175
1139
|
},
|
|
1176
1140
|
};
|
|
1177
1141
|
}
|
|
1178
1142
|
buildUntilContext(record) {
|
|
1179
|
-
var _a;
|
|
1180
1143
|
return {
|
|
1181
1144
|
id: record.id,
|
|
1182
1145
|
purpose: record.purpose,
|
|
1183
1146
|
fireCount: record.fireCount,
|
|
1184
|
-
target:
|
|
1147
|
+
target: {
|
|
1148
|
+
channelId: record.channelId,
|
|
1149
|
+
continuationToken: record.continuationToken,
|
|
1150
|
+
...(record.target ?? {}),
|
|
1151
|
+
},
|
|
1185
1152
|
host: this.engine.host,
|
|
1186
1153
|
mcp: this.engine.host.mcp,
|
|
1187
1154
|
};
|
|
1188
1155
|
}
|
|
1189
|
-
disarm(record, reason) {
|
|
1190
|
-
|
|
1191
|
-
|
|
1192
|
-
|
|
1193
|
-
|
|
1194
|
-
|
|
1195
|
-
|
|
1156
|
+
async disarm(record, reason) {
|
|
1157
|
+
this.clearTimer(record.id);
|
|
1158
|
+
const updated = {
|
|
1159
|
+
...record,
|
|
1160
|
+
status: "disarmed",
|
|
1161
|
+
disarmReason: reason,
|
|
1162
|
+
updatedAt: new Date().toISOString(),
|
|
1163
|
+
};
|
|
1164
|
+
await this.persist(updated);
|
|
1165
|
+
this.handlers.delete(record.id);
|
|
1196
1166
|
}
|
|
1197
|
-
afterFire(record, _outcome, reschedule) {
|
|
1198
|
-
|
|
1199
|
-
|
|
1200
|
-
|
|
1201
|
-
|
|
1202
|
-
|
|
1203
|
-
|
|
1204
|
-
|
|
1205
|
-
if (reschedule !== undefined) {
|
|
1206
|
-
let nextFireAtMs;
|
|
1207
|
-
if (reschedule.delay !== undefined) {
|
|
1208
|
-
nextFireAtMs = now + parseDuration(reschedule.delay);
|
|
1209
|
-
}
|
|
1210
|
-
else if (reschedule.at !== undefined) {
|
|
1211
|
-
nextFireAtMs =
|
|
1212
|
-
reschedule.at instanceof Date
|
|
1213
|
-
? reschedule.at.getTime()
|
|
1214
|
-
: Date.parse(String(reschedule.at));
|
|
1215
|
-
if (!Number.isFinite(nextFireAtMs)) {
|
|
1216
|
-
throw new ReminderValidationError(`Invalid reschedule.at for reminder "${current.id}"`);
|
|
1217
|
-
}
|
|
1218
|
-
}
|
|
1219
|
-
else {
|
|
1220
|
-
throw new ReminderValidationError(`reschedule requires delay or at for reminder "${current.id}"`);
|
|
1221
|
-
}
|
|
1222
|
-
const updated = Object.assign(Object.assign({}, current), { fireCount: current.fireCount + 1, nextFireAtMs,
|
|
1223
|
-
// Promote to one-shot-from-now semantics if they rescheduled
|
|
1224
|
-
scheduleKind: reschedule.delay !== undefined ? "delay" : "at", scheduleValue: reschedule.delay !== undefined ? String(reschedule.delay) : String(reschedule.at), updatedAt: new Date().toISOString() });
|
|
1225
|
-
yield this.persist(updated);
|
|
1226
|
-
if (this.platformTimers === undefined) {
|
|
1227
|
-
this.armTimer(updated);
|
|
1228
|
-
}
|
|
1229
|
-
return;
|
|
1230
|
-
}
|
|
1231
|
-
if (oneShot) {
|
|
1232
|
-
this.clearTimer(current.id);
|
|
1233
|
-
const updated = Object.assign(Object.assign({}, current), { fireCount: current.fireCount + 1, status: "completed", disarmReason: "one_shot_complete", updatedAt: new Date().toISOString() });
|
|
1234
|
-
yield this.persist(updated);
|
|
1235
|
-
this.handlers.delete(current.id);
|
|
1236
|
-
return;
|
|
1237
|
-
}
|
|
1238
|
-
// Recurring
|
|
1167
|
+
async afterFire(record, _outcome, reschedule) {
|
|
1168
|
+
const current = await this.getRecord(record.id);
|
|
1169
|
+
if (current === undefined || current.status !== "armed") {
|
|
1170
|
+
return;
|
|
1171
|
+
}
|
|
1172
|
+
const now = Date.now();
|
|
1173
|
+
const oneShot = current.scheduleKind === "delay" || current.scheduleKind === "at";
|
|
1174
|
+
if (reschedule !== undefined) {
|
|
1239
1175
|
let nextFireAtMs;
|
|
1240
|
-
if (
|
|
1241
|
-
nextFireAtMs = now +
|
|
1242
|
-
}
|
|
1243
|
-
else if (
|
|
1244
|
-
nextFireAtMs =
|
|
1176
|
+
if (reschedule.delay !== undefined) {
|
|
1177
|
+
nextFireAtMs = now + parseDuration(reschedule.delay);
|
|
1178
|
+
}
|
|
1179
|
+
else if (reschedule.at !== undefined) {
|
|
1180
|
+
nextFireAtMs =
|
|
1181
|
+
reschedule.at instanceof Date
|
|
1182
|
+
? reschedule.at.getTime()
|
|
1183
|
+
: Date.parse(String(reschedule.at));
|
|
1184
|
+
if (!Number.isFinite(nextFireAtMs)) {
|
|
1185
|
+
throw new ReminderValidationError(`Invalid reschedule.at for reminder "${current.id}"`);
|
|
1186
|
+
}
|
|
1245
1187
|
}
|
|
1246
1188
|
else {
|
|
1247
|
-
throw new
|
|
1248
|
-
}
|
|
1249
|
-
const updated =
|
|
1250
|
-
|
|
1189
|
+
throw new ReminderValidationError(`reschedule requires delay or at for reminder "${current.id}"`);
|
|
1190
|
+
}
|
|
1191
|
+
const updated = {
|
|
1192
|
+
...current,
|
|
1193
|
+
fireCount: current.fireCount + 1,
|
|
1194
|
+
nextFireAtMs,
|
|
1195
|
+
// Promote to one-shot-from-now semantics if they rescheduled
|
|
1196
|
+
scheduleKind: reschedule.delay !== undefined ? "delay" : "at",
|
|
1197
|
+
scheduleValue: reschedule.delay !== undefined ? String(reschedule.delay) : String(reschedule.at),
|
|
1198
|
+
updatedAt: new Date().toISOString(),
|
|
1199
|
+
};
|
|
1200
|
+
await this.persist(updated);
|
|
1251
1201
|
if (this.platformTimers === undefined) {
|
|
1252
1202
|
this.armTimer(updated);
|
|
1253
1203
|
}
|
|
1254
|
-
|
|
1204
|
+
return;
|
|
1205
|
+
}
|
|
1206
|
+
if (oneShot) {
|
|
1207
|
+
this.clearTimer(current.id);
|
|
1208
|
+
const updated = {
|
|
1209
|
+
...current,
|
|
1210
|
+
fireCount: current.fireCount + 1,
|
|
1211
|
+
status: "completed",
|
|
1212
|
+
disarmReason: "one_shot_complete",
|
|
1213
|
+
updatedAt: new Date().toISOString(),
|
|
1214
|
+
};
|
|
1215
|
+
await this.persist(updated);
|
|
1216
|
+
this.handlers.delete(current.id);
|
|
1217
|
+
return;
|
|
1218
|
+
}
|
|
1219
|
+
// Recurring
|
|
1220
|
+
let nextFireAtMs;
|
|
1221
|
+
if (current.scheduleKind === "every" && current.intervalMs !== undefined) {
|
|
1222
|
+
nextFireAtMs = now + current.intervalMs;
|
|
1223
|
+
}
|
|
1224
|
+
else if (current.scheduleKind === "cron") {
|
|
1225
|
+
nextFireAtMs = nextCronFireMs(current.scheduleValue, now);
|
|
1226
|
+
}
|
|
1227
|
+
else {
|
|
1228
|
+
throw new Error(`Reminder "${current.id}" has recurring status with scheduleKind=${current.scheduleKind}`);
|
|
1229
|
+
}
|
|
1230
|
+
const updated = {
|
|
1231
|
+
...current,
|
|
1232
|
+
fireCount: current.fireCount + 1,
|
|
1233
|
+
nextFireAtMs,
|
|
1234
|
+
updatedAt: new Date().toISOString(),
|
|
1235
|
+
};
|
|
1236
|
+
await this.persist(updated);
|
|
1237
|
+
if (this.platformTimers === undefined) {
|
|
1238
|
+
this.armTimer(updated);
|
|
1239
|
+
}
|
|
1255
1240
|
}
|
|
1256
1241
|
}
|