@desplega.ai/agent-swarm 2.4.5 → 2.5.0
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/README.md +4 -2
- package/dist/{acp-adapter-2wdkktv6.js → acp-adapter-11gp67nr.js} +8 -6
- package/dist/{actions-c40ebcm4.js → actions-8p2kze8j.js} +23 -21
- package/dist/{amp-adapter-wb67m95z.js → amp-adapter-z4qvhacj.js} +2 -2
- package/dist/{anthropic-messages-g6pqmqk4.js → anthropic-messages-txs190dd.js} +17 -20
- package/dist/{app-353vre8g.js → app-0qp0a2gk.js} +3 -3
- package/dist/{assistant-sj1njh23.js → assistant-jedd3ktm.js} +24 -22
- package/dist/{azure-openai-responses-9dbmj346.js → azure-openai-responses-9kfwhmn7.js} +18 -13
- package/dist/{boot-reembed-qdkvxeem.js → boot-reembed-4xwq5tg0.js} +1 -1
- package/dist/{boot-reembed-b9c3az3c.js → boot-reembed-n4wq5dd2.js} +1 -1
- package/dist/{boot-scrub-sweep-m3wh8eks.js → boot-scrub-sweep-69h90xcq.js} +4 -4
- package/dist/{boot-seeds-j2273s54.js → boot-seeds-3vn0m9sg.js} +4 -4
- package/dist/{claude-adapter-m7q0a085.js → claude-adapter-221mvad5.js} +8 -6
- package/dist/{claude-managed-adapter-5ptb6tzs.js → claude-managed-adapter-gt6pa35d.js} +2 -2
- package/dist/{claude-managed-setup-smrqk5j5.js → claude-managed-setup-c3hp36ph.js} +4 -4
- package/dist/{claude-sdk-session-38xnz21e.js → claude-sdk-session-8thjsk5e.js} +9012 -8928
- package/dist/{cli-b9fcxxev.js → cli-007cnhm1.js} +50 -4
- package/dist/{cli-j4rqdf2n.js → cli-03vpq45j.js} +2 -2
- package/dist/{cli-dk44dxec.js → cli-0y6f0ckq.js} +1 -1
- package/dist/{cli-kpw04va0.js → cli-0z3vjrpm.js} +1 -1
- package/dist/{cli-94gwyg7m.js → cli-179xcyky.js} +9 -9
- package/dist/{cli-3r8samv3.js → cli-1f0gp9vz.js} +3 -2
- package/dist/{cli-4zfqgjc5.js → cli-1pr672hz.js} +2 -2
- package/dist/{cli-0bqteszy.js → cli-2nf3b08t.js} +328 -25
- package/dist/{cli-43x64hv3.js → cli-36j4jnk3.js} +4 -4
- package/dist/{cli-k8g16gz3.js → cli-3qv8naq8.js} +115 -119
- package/dist/cli-3zwye9sn.js +1193 -0
- package/dist/{cli-60cgh1rv.js → cli-459fk97x.js} +7 -7
- package/dist/{cli-v8fjakwf.js → cli-4w24f7z3.js} +1 -1
- package/dist/{cli-n72rg53p.js → cli-5kzk3472.js} +812 -418
- package/dist/{cli-h7gc0b4r.js → cli-5m84zd2k.js} +55 -18
- package/dist/{cli-j99azg3g.js → cli-5rvg1ddk.js} +5 -5
- package/dist/{cli-dvkmp9bt.js → cli-6ctwrf1z.js} +20 -10
- package/dist/{cli-848hxdwj.js → cli-6rg2jn2y.js} +2 -1
- package/dist/{cli-fv5hepyy.js → cli-7bwtc6yx.js} +3 -3
- package/dist/{cli-ms85309d.js → cli-7g40g8hq.js} +11 -11
- package/dist/{cli-xpa64rek.js → cli-92mqenkc.js} +2362 -159
- package/dist/{cli-018r4jq6.js → cli-9ce2xw98.js} +8 -8
- package/dist/{cli-1syn2xy1.js → cli-bgdtv62r.js} +32 -18
- package/dist/{cli-wxbn3yfx.js → cli-cz5nmdxt.js} +2 -2
- package/dist/{cli-wt645v7q.js → cli-dabehxhp.js} +1 -1
- package/dist/{cli-vfha21c9.js → cli-e5bxnjps.js} +2 -2
- package/dist/{cli-wt0jf9hm.js → cli-fcc5qe3f.js} +553 -193
- package/dist/{cli-ftc1g5tj.js → cli-fes51ya2.js} +3 -3
- package/dist/{cli-3a45zcgm.js → cli-ftmq4nvk.js} +2 -2
- package/dist/{cli-37j04k05.js → cli-fy1jyyym.js} +13 -13
- package/dist/{cli-s95dknxh.js → cli-g1cw7xtt.js} +1 -1
- package/dist/{cli-74c1wpm0.js → cli-gpv60278.js} +1 -1
- package/dist/{cli-pemywhfq.js → cli-ha9rmwgq.js} +2 -1
- package/dist/{cli-g35m2xwm.js → cli-j1eghqfm.js} +2 -2
- package/dist/{cli-qfxc84gh.js → cli-jpxsrxcb.js} +1 -1
- package/dist/{cli-ejs3m1sx.js → cli-kxpfx2xr.js} +1 -1
- package/dist/{cli-p14m2cnp.js → cli-mbfj11fq.js} +1 -1
- package/dist/{cli-pnp2j5zg.js → cli-mmhj8vd7.js} +2 -2
- package/dist/{cli-xf234ws8.js → cli-n4c8e3s5.js} +2 -0
- package/dist/{cli-f01h25yt.js → cli-n6kmqngw.js} +2 -2
- package/dist/{cli-v0yrfmdh.js → cli-nnvjb17s.js} +3 -3
- package/dist/{cli-1dmcq2eh.js → cli-nzfnt2gs.js} +1 -1
- package/dist/{cli-625tb7aq.js → cli-p859hcc8.js} +2 -2
- package/dist/{cli-smvcw9z5.js → cli-pasyvjmm.js} +1 -1
- package/dist/cli-pkaqk57w.js +128 -0
- package/dist/{cli-8nyhf7za.js → cli-qqzmjxkf.js} +129 -105
- package/dist/{cli-9yqgm9nd.js → cli-r25qpaxt.js} +161 -63
- package/dist/{cli-eww120g8.js → cli-rh5smt8t.js} +18 -4
- package/dist/{cli-qwkcwjdn.js → cli-rycdaj0a.js} +1 -1
- package/dist/{cli-a92r1127.js → cli-s4sntwt4.js} +2 -2
- package/dist/{cli-be2161h1.js → cli-segs7hx9.js} +6 -6
- package/dist/{cli-36ckw0wx.js → cli-stxdrqs2.js} +2 -2
- package/dist/{cli-z7d29rw9.js → cli-sy8f9705.js} +4 -4
- package/dist/{cli-1m17dc9d.js → cli-ta8bd7th.js} +2 -2
- package/dist/{cli-st8aw1m6.js → cli-we0ra057.js} +4 -4
- package/dist/{cli-7c9qs530.js → cli-x21dqn18.js} +5 -5
- package/dist/{cli-1sp861s4.js → cli-x6vf4sad.js} +11 -11
- package/dist/{cli-d2mb5s8a.js → cli-x77gen2p.js} +680 -440
- package/dist/{cli-tw4p90jb.js → cli-xawhpgc9.js} +5 -5
- package/dist/{cli-f0g3pgvf.js → cli-xd4eqtr2.js} +2 -2
- package/dist/{cli-1f3qzw6y.js → cli-xt768r5m.js} +2 -2
- package/dist/{cli-cxxe61f6.js → cli-y4x8ww6s.js} +2 -2
- package/dist/{cli-bnes4z6b.js → cli-y9jvxfhk.js} +3 -3
- package/dist/{cli-qwhy2qma.js → cli-z63a269p.js} +2 -2
- package/dist/{cli-sm8rses9.js → cli-zfdp01hn.js} +8 -5
- package/dist/cli.js +29 -27
- package/dist/{cloudflare-workers-ai-system-one-ch2wxfcp.js → cloudflare-workers-ai-system-one-43v9qkjk.js} +7 -2
- package/dist/{codex-adapter-tn6k5mkg.js → codex-adapter-tcg1q3jk.js} +6 -4
- package/dist/{codex-login-m819c41r.js → codex-login-rn7c5gxt.js} +1 -1
- package/dist/{codex-session-runner-7vjt7jbw.js → codex-session-runner-xfx4wd9x.js} +7 -5
- package/dist/{commands-t7rz9146.js → commands-ew83nj70.js} +2 -2
- package/dist/{controller-m2jasma5.js → controller-gnq1k1mf.js} +4 -4
- package/dist/{controllers-connect-9ftpex99.js → controllers-connect-tw1dan9r.js} +25 -23
- package/dist/{cursor-adapter-mjv79e4z.js → cursor-adapter-yq5xgfcg.js} +1 -1
- package/dist/{db-6sfrn9g3.js → db-7gbc53fd.js} +14 -2
- package/dist/{db-rn673r06.js → db-t86xrnes.js} +3 -3
- package/dist/{devin-adapter-zgqadvnm.js → devin-adapter-4ycy4vrf.js} +1 -1
- package/dist/{dispatcher-c155xz0p.js → dispatcher-64bdyhps.js} +22 -20
- package/dist/{docker-fkgtr75t.js → docker-nfy2gc8t.js} +2 -2
- package/dist/{dsh-adapter-3mmszc1x.js → dsh-adapter-tvhc4k2d.js} +1 -1
- package/dist/{e2b-16d6ch39.js → e2b-9gkdhm3c.js} +2 -2
- package/dist/{e2b-zn0b4w5w.js → e2b-cjtf8k1z.js} +2 -2
- package/dist/{easter-egg-3d-5cgynaja.js → easter-egg-3d-e4qv8dxj.js} +1 -1
- package/dist/{event-bus-9vpk8gmq.js → event-bus-3z78ep5n.js} +1 -1
- package/dist/{execute-t4e6m3rw.js → execute-0xpz8950.js} +85 -23
- package/dist/{external-channel-35k6tkv8.js → external-channel-e9nw24zv.js} +2 -2
- package/dist/{google-generative-ai-bmq8gkf3.js → google-generative-ai-mk9vcpe1.js} +15 -15
- package/dist/{google-vertex-b1rvp52s.js → google-vertex-q580kz8k.js} +20 -20
- package/dist/{grok-adapter-tfrq7tg1.js → grok-adapter-v82srhrx.js} +9 -7
- package/dist/handlers-c9ajzhbn.js +41 -0
- package/dist/{heartbeat-dyg4npkp.js → heartbeat-2vqs1319.js} +25 -23
- package/dist/{hook-x26b31bg.js → hook-01nk13wx.js} +7 -5
- package/dist/{hook-drspbwpe.js → hook-jzpheegg.js} +9 -7
- package/dist/{http-pk972xyw.js → http-fc1k6ec8.js} +544 -76
- package/dist/index-07ef7nkm.js +42 -0
- package/dist/{index-g6tbb6ve.js → index-31ceyf42.js} +1 -1
- package/dist/index-7yts8n9s.js +43 -0
- package/dist/index-q3t40ryv.js +46 -0
- package/dist/{index-arytgj5q.js → index-sepja052.js} +1 -1
- package/dist/{index-jwxkkex2.js → index-zdycv3ja.js} +13 -11
- package/dist/{index-g216q6jr.js → index-zsyx51ap.js} +1 -4
- package/dist/{keepalive-7yrng7px.js → keepalive-5nh2rhmr.js} +4 -4
- package/dist/lead-aayq64jn.js +79 -0
- package/dist/{llama-cpp-classify-vv43gswy.js → llama-cpp-classify-ee7vmd7q.js} +4 -1
- package/dist/{maintenance-e6cemdqt.js → maintenance-hjhzdt3k.js} +1 -1
- package/dist/{mistral-conversations-1medfwv7.js → mistral-conversations-sd0xf1tn.js} +13 -13
- package/dist/{node-de6akczr.js → node-jzjj7r9x.js} +1 -1
- package/dist/{oauth-refresh-sweep-yw3xdbcj.js → oauth-refresh-sweep-y3n1knmd.js} +3 -3
- package/dist/{onboard-0a9y5yrc.js → onboard-1zq3m7db.js} +2 -2
- package/dist/{openai-codex-responses-de9pvs4d.js → openai-codex-responses-79s8xnbh.js} +29 -23
- package/dist/{openai-completions-mtqge3nf.js → openai-completions-tdbyts0x.js} +21 -20
- package/dist/{openai-decisions-ygk4kkxd.js → openai-decisions-bqhnjyfz.js} +2 -1
- package/dist/{openai-responses-dkyng71g.js → openai-responses-dm3t5vqp.js} +26 -17
- package/dist/{opencode-adapter-x4tqzhkh.js → opencode-adapter-e56086m1.js} +7 -5
- package/dist/{openrouter-images-wvv7camj.js → openrouter-images-ssttr1y4.js} +6 -3
- package/dist/{otel-impl-trjpkfb7.js → otel-impl-h9yyza47.js} +2 -2
- package/dist/{pi-messages-nhh1pkz2.js → pi-messages-3nf8bq1a.js} +6 -4
- package/dist/{pi-mono-adapter-mcfkn9vf.js → pi-mono-adapter-tzps8f38.js} +9 -37
- package/dist/{pricing-refresh-d2b5my0y.js → pricing-refresh-nevhat8h.js} +3 -3
- package/dist/{rbac-roles-nxh5ahwk.js → rbac-roles-4y6ngvzq.js} +1 -1
- package/dist/{rbac-roles-hey3yv9d.js → rbac-roles-exbg8xb6.js} +2 -2
- package/dist/{render-v2-rr3p2atg.js → render-v2-cfsh13z3.js} +3 -3
- package/dist/{resume-2efkf1s1.js → resume-5s2t0dbs.js} +10 -8
- package/dist/{runtime-xe3aqy4x.js → runtime-bt5pkpm1.js} +1 -1
- package/dist/{session-access-s2mttxpw.js → session-access-dnbyp3cn.js} +6 -4
- package/dist/{setup-vypma5yt.js → setup-1wj1y77j.js} +1 -1
- package/dist/{typesafe-system-one-2hgpayzy.js → typesafe-system-one-bre38hvs.js} +4 -2
- package/dist/{virtual-modules-4r3zkc20.js → virtual-modules-mnfbh5mr.js} +8 -2
- package/dist/{work-objects-rvhckdkp.js → work-objects-7mg6216g.js} +25 -23
- package/dist/worker-nwb789mg.js +79 -0
- package/dist/{x-y45mthwn.js → x-pbpezr0s.js} +2 -2
- package/openapi.json +785 -5
- package/package.json +9 -9
- package/src/assets/key.ts +17 -0
- package/src/azure-devops/handlers.ts +1 -1
- package/src/be/db/approvals.ts +18 -0
- package/src/be/db/session-moves.ts +164 -0
- package/src/be/db/session-tree.ts +384 -0
- package/src/be/db/tasks/read.ts +40 -4
- package/src/be/db/workflow-runs.ts +96 -0
- package/src/be/db.ts +80 -236
- package/src/be/onboarding.ts +3 -0
- package/src/be/skill-sync.ts +43 -2
- package/src/be/swarm-config-guard.ts +1 -0
- package/src/bitbucket/api.ts +81 -0
- package/src/bitbucket/auth.ts +64 -0
- package/src/bitbucket/handlers.ts +434 -0
- package/src/bitbucket/index.ts +28 -0
- package/src/bitbucket/templates.ts +134 -0
- package/src/bitbucket/types.ts +65 -0
- package/src/commands/runner.ts +55 -3
- package/src/e2b/env.ts +1 -0
- package/src/extensions/contract-types.generated.ts +1 -1
- package/src/extensions/contract.ts +2 -2
- package/src/extensions/post-bridge.ts +1 -0
- package/src/github/handlers.ts +6 -6
- package/src/gitlab/handlers.ts +5 -5
- package/src/http/assets.ts +14 -2
- package/src/http/core.ts +12 -0
- package/src/http/index.ts +4 -0
- package/src/http/onboarding.ts +1 -0
- package/src/http/sessions.ts +366 -12
- package/src/http/skills.ts +48 -1
- package/src/http/tasks.ts +1 -1
- package/src/http/users.ts +1 -0
- package/src/http/webhooks.ts +141 -0
- package/src/integrations/chat-sdk/adapters.ts +19 -3
- package/src/integrations/chat-sdk/facts.ts +30 -12
- package/src/integrations/chat-sdk/outbound.ts +38 -1
- package/src/providers/acp-targets.ts +1 -1
- package/src/providers/amp-adapter.ts +1 -1
- package/src/providers/pi-mono-adapter.ts +5 -53
- package/src/scripts-runtime/types/swarm-extension.d.ts +6 -4
- package/src/slack/handlers.test.ts +158 -0
- package/src/slack/handlers.ts +19 -28
- package/src/tasks/context-key.ts +61 -1
- package/src/tests/bitbucket-auth.test.ts +156 -0
- package/src/tests/bitbucket-handlers.test.ts +425 -0
- package/src/tests/bitbucket-webhook.test.ts +220 -0
- package/src/tests/chat-sdk-inbound.test.ts +39 -0
- package/src/tests/chat-sdk-telegram-markdown.test.ts +92 -0
- package/src/tests/context-key.test.ts +50 -0
- package/src/tests/core-auth.test.ts +5 -0
- package/src/tests/db-task-read-contract.test.ts +7 -0
- package/src/tests/db-task-write-contract.test.ts +6 -0
- package/src/tests/docs-index-footprint.test.js +3 -3
- package/src/tests/fixtures/bitbucket/pullrequest-comment-created-inline.json +174 -0
- package/src/tests/fixtures/bitbucket/pullrequest-comment-created.json +170 -0
- package/src/tests/fixtures/bitbucket/pullrequest-created-mention.json +132 -0
- package/src/tests/fixtures/bitbucket/pullrequest-created-reviewer.json +140 -0
- package/src/tests/fixtures/bitbucket/pullrequest-fulfilled.json +134 -0
- package/src/tests/fixtures/bitbucket/pullrequest-rejected.json +133 -0
- package/src/tests/generate-mcp-docs.test.ts +101 -0
- package/src/tests/gitlab-handlers.test.ts +3 -3
- package/src/tests/gitlab-vcs-db.test.ts +4 -4
- package/src/tests/integration-disable-flags.test.ts +36 -0
- package/src/tests/mcp-tools.test.ts +1 -1
- package/src/tests/onboarding.test.ts +12 -0
- package/src/tests/pi-codemode.test.ts +8 -2
- package/src/tests/preload-env.test.ts +57 -0
- package/src/tests/preload.ts +21 -0
- package/src/tests/prompt-template-remaining.test.ts +38 -0
- package/src/tests/run-root-coverage.test.ts +39 -0
- package/src/tests/runner-bitbucket-vcs.test.ts +191 -0
- package/src/tests/runner-skills-refresh.test.ts +58 -0
- package/src/tests/secret-scrubber.test.ts +15 -0
- package/src/tests/sessions-move.test.ts +576 -0
- package/src/tests/sessions-tree.test.ts +493 -0
- package/src/tests/skill-files-http.test.ts +50 -0
- package/src/tests/skills-bundle.test.ts +99 -0
- package/src/tests/swarm-config-export-to-env.test.ts +2 -1
- package/src/tests/vcs-bitbucket.test.ts +91 -0
- package/src/tests/vcs-provider-isolation.test.ts +123 -0
- package/src/tests/vcs-provider.test.ts +16 -1
- package/src/tests/vcs-tracking.test.ts +3 -3
- package/src/tests/workflow-foreach-bodies.test.ts +637 -0
- package/src/tests/workflow-foreach-concurrency.test.ts +342 -0
- package/src/tests/workflow-foreach-retry.test.ts +537 -0
- package/src/tests/workflow-foreach.test.ts +44 -23
- package/src/tests/workflow-step-claims.test.ts +6 -6
- package/src/tests/workflow-tla-races.test.ts +2 -2
- package/src/tools/script-delete.ts +1 -1
- package/src/tools/workflows/create-workflow.ts +5 -3
- package/src/types.ts +3 -2
- package/src/utils/config-env-export.ts +1 -0
- package/src/utils/secret-scrubber.ts +4 -0
- package/src/utils/skills-refresh.ts +83 -43
- package/src/vcs/bitbucket.ts +58 -0
- package/src/vcs/index.ts +2 -0
- package/src/vcs/types.ts +2 -2
- package/src/workflows/approval-resolution.ts +4 -1
- package/src/workflows/checkpoint.ts +9 -1
- package/src/workflows/constants.ts +3 -0
- package/src/workflows/definition.ts +48 -17
- package/src/workflows/engine.ts +25 -15
- package/src/workflows/executors/foreach.ts +45 -99
- package/src/workflows/foreach-body.ts +146 -0
- package/src/workflows/foreach-driver.ts +552 -0
- package/src/workflows/foreach-join.ts +206 -7
- package/src/workflows/node-timeout.ts +21 -0
- package/src/workflows/recovery.ts +68 -3
- package/src/workflows/resume.ts +221 -43
- package/src/workflows/retry-poller.ts +39 -24
- package/src/workflows/step-input.ts +59 -0
- package/src/workflows/task-step-routing.ts +114 -56
- package/templates/skills/slack-interaction/content.md +5 -0
- package/templates/skills/work-on-task/SKILL.md +1 -1
- package/templates/skills/work-on-task/content.md +1 -1
- package/dist/cli-9hstj09h.js +0 -2657
- package/dist/cli-yw91rnmw.js +0 -13
- package/dist/handlers-7gw64dxw.js +0 -39
- package/dist/index-bgdyvcnv.js +0 -40
- package/dist/index-gabbcdn2.js +0 -43
- package/dist/index-tfb26zhy.js +0 -41
- package/dist/lead-7e02z839.js +0 -77
- package/dist/worker-5snytpbd.js +0 -77
|
@@ -0,0 +1,1193 @@
|
|
|
1
|
+
import {
|
|
2
|
+
getDbClient2,
|
|
3
|
+
getTaskById2,
|
|
4
|
+
cancelTask2,
|
|
5
|
+
getChildWorkflowRunId,
|
|
6
|
+
getWorkflowRunLineage,
|
|
7
|
+
createForeachAttemptSteps,
|
|
8
|
+
init_workflow_runs,
|
|
9
|
+
getWorkflow2,
|
|
10
|
+
getWorkflowRun2,
|
|
11
|
+
updateWorkflowRun2,
|
|
12
|
+
getWorkflowRunStep2,
|
|
13
|
+
updateWorkflowRunStep2,
|
|
14
|
+
getWorkflowRunStepsByRunId2,
|
|
15
|
+
getCompletedStepNodeIds2,
|
|
16
|
+
getTaskByWorkflowRunStepId2,
|
|
17
|
+
detachTaskFromWorkflowRunStep2,
|
|
18
|
+
cancelPendingApprovalRequestsForRun2,
|
|
19
|
+
listCancelledApprovalRequestsForRun2,
|
|
20
|
+
getWaitStateById2,
|
|
21
|
+
getPendingEventWaitNames2,
|
|
22
|
+
getPendingWaitsByEvent2,
|
|
23
|
+
resolveWaitState2,
|
|
24
|
+
init_db3
|
|
25
|
+
} from "./cli-fcc5qe3f.js";
|
|
26
|
+
import {
|
|
27
|
+
BaseExecutor,
|
|
28
|
+
postApprovalCancellationUpdates,
|
|
29
|
+
checkpointStep,
|
|
30
|
+
checkpointStepFailure,
|
|
31
|
+
FAILED_TASK_OUTPUT_PREFIX,
|
|
32
|
+
loadCompletedStepRouting,
|
|
33
|
+
getSecretInputKeys,
|
|
34
|
+
getMaxWorkflowStepsPerRun,
|
|
35
|
+
findWorkflowReadinessProblems,
|
|
36
|
+
formatReadinessProblems,
|
|
37
|
+
startWorkflowExecution2,
|
|
38
|
+
failRunOnUnreadableReplay,
|
|
39
|
+
walkGraph,
|
|
40
|
+
hasRunningStep,
|
|
41
|
+
findReadyNodes,
|
|
42
|
+
startForeachDrive,
|
|
43
|
+
FOREACH_TERMINAL_STEP_STATUSES,
|
|
44
|
+
parseSyntheticNodeId,
|
|
45
|
+
resolveForeachParent,
|
|
46
|
+
settleForeachChild,
|
|
47
|
+
resolveStepNode,
|
|
48
|
+
stepFailureMode,
|
|
49
|
+
getNextTargets,
|
|
50
|
+
getSuccessors
|
|
51
|
+
} from "./cli-92mqenkc.js";
|
|
52
|
+
import {
|
|
53
|
+
workflowEventBus2,
|
|
54
|
+
init_event_bus
|
|
55
|
+
} from "./cli-dabehxhp.js";
|
|
56
|
+
import {
|
|
57
|
+
RetryPolicySchema,
|
|
58
|
+
SUPERSEDED_WORKFLOW_TASK_REASON,
|
|
59
|
+
init_types
|
|
60
|
+
} from "./cli-rh5smt8t.js";
|
|
61
|
+
import {
|
|
62
|
+
init_zod
|
|
63
|
+
} from "./cli-4chz1hye.js";
|
|
64
|
+
import {
|
|
65
|
+
string2,
|
|
66
|
+
number2,
|
|
67
|
+
unknown,
|
|
68
|
+
object2,
|
|
69
|
+
union2,
|
|
70
|
+
discriminatedUnion,
|
|
71
|
+
record2,
|
|
72
|
+
_enum2,
|
|
73
|
+
literal2
|
|
74
|
+
} from "./cli-d9f7gzeq.js";
|
|
75
|
+
import {
|
|
76
|
+
scrubSecrets,
|
|
77
|
+
init_secret_scrubber
|
|
78
|
+
} from "./cli-n4c8e3s5.js";
|
|
79
|
+
|
|
80
|
+
// src/workflows/approval-resolution.ts
|
|
81
|
+
function shapeApprovalResolution(registry, definition, nodeId, parkedOutput, approval) {
|
|
82
|
+
const node = resolveStepNode(definition, nodeId);
|
|
83
|
+
if (node && registry.has(node.type)) {
|
|
84
|
+
const shaped = registry.get(node.type).resolveApproval(parkedOutput, approval);
|
|
85
|
+
if (shaped)
|
|
86
|
+
return shaped;
|
|
87
|
+
}
|
|
88
|
+
return {
|
|
89
|
+
output: {
|
|
90
|
+
requestId: approval.requestId,
|
|
91
|
+
status: approval.status,
|
|
92
|
+
responses: approval.responses
|
|
93
|
+
},
|
|
94
|
+
nextPort: approval.status === "timeout" ? "timeout" : approval.status === "rejected" ? "rejected" : "approved"
|
|
95
|
+
};
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
// src/workflows/resume.ts
|
|
99
|
+
init_db3();
|
|
100
|
+
init_workflow_runs();
|
|
101
|
+
init_secret_scrubber();
|
|
102
|
+
init_event_bus();
|
|
103
|
+
|
|
104
|
+
// src/workflows/executors/sub-workflow.ts
|
|
105
|
+
init_zod();
|
|
106
|
+
init_workflow_runs();
|
|
107
|
+
var SubWorkflowConfigSchema = object2({
|
|
108
|
+
workflowId: string2().min(1).describe("Workflow to run as a child"),
|
|
109
|
+
inputs: record2(string2(), unknown()).optional().describe("Trigger data for the child run, validated against its triggerSchema")
|
|
110
|
+
});
|
|
111
|
+
var SubWorkflowOutputSchema = object2({
|
|
112
|
+
runId: string2(),
|
|
113
|
+
outputs: record2(string2(), unknown()),
|
|
114
|
+
error: string2().optional()
|
|
115
|
+
});
|
|
116
|
+
var MAX_SUB_WORKFLOW_DEPTH = 16;
|
|
117
|
+
var NON_NODE_CONTEXT_KEYS = new Set(["trigger", "input", "swarm", "workflow"]);
|
|
118
|
+
function childRunOutcome(child) {
|
|
119
|
+
if (child.status === "running" || child.status === "waiting")
|
|
120
|
+
return null;
|
|
121
|
+
if (child.status !== "completed") {
|
|
122
|
+
const reason = child.error ? `: ${child.error}` : "";
|
|
123
|
+
return { error: `Child workflow run ${child.id} ${child.status}${reason}` };
|
|
124
|
+
}
|
|
125
|
+
const outputs = {};
|
|
126
|
+
for (const [key, value] of Object.entries(child.context ?? {})) {
|
|
127
|
+
if (!NON_NODE_CONTEXT_KEYS.has(key))
|
|
128
|
+
outputs[key] = value;
|
|
129
|
+
}
|
|
130
|
+
return { output: { runId: child.id, outputs, error: child.error } };
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
class SubWorkflowExecutor extends BaseExecutor {
|
|
134
|
+
registry;
|
|
135
|
+
type = "sub-workflow";
|
|
136
|
+
mode = "async";
|
|
137
|
+
configSchema = SubWorkflowConfigSchema;
|
|
138
|
+
outputSchema = SubWorkflowOutputSchema;
|
|
139
|
+
constructor(deps, registry) {
|
|
140
|
+
super(deps);
|
|
141
|
+
this.registry = registry;
|
|
142
|
+
}
|
|
143
|
+
async execute(config, _context, meta) {
|
|
144
|
+
const { db } = this.deps;
|
|
145
|
+
const existingId = await getChildWorkflowRunId(meta.stepId);
|
|
146
|
+
const existing = existingId ? await db.getWorkflowRun(existingId) : null;
|
|
147
|
+
if (existing)
|
|
148
|
+
return this.settle(existing);
|
|
149
|
+
const lineage = await getWorkflowRunLineage(meta.runId, MAX_SUB_WORKFLOW_DEPTH);
|
|
150
|
+
if (lineage.length > MAX_SUB_WORKFLOW_DEPTH) {
|
|
151
|
+
return {
|
|
152
|
+
status: "failed",
|
|
153
|
+
error: `Sub-workflow nesting exceeds ${MAX_SUB_WORKFLOW_DEPTH} levels`
|
|
154
|
+
};
|
|
155
|
+
}
|
|
156
|
+
if (config.workflowId === meta.workflowId || lineage.includes(config.workflowId)) {
|
|
157
|
+
return {
|
|
158
|
+
status: "failed",
|
|
159
|
+
error: `Sub-workflow recursion: workflow ${config.workflowId} is already running in this run's ancestry`
|
|
160
|
+
};
|
|
161
|
+
}
|
|
162
|
+
const workflow = await db.getWorkflow(config.workflowId);
|
|
163
|
+
if (!workflow)
|
|
164
|
+
return { status: "failed", error: `Workflow ${config.workflowId} not found` };
|
|
165
|
+
if (!workflow.enabled) {
|
|
166
|
+
return { status: "failed", error: `Workflow ${config.workflowId} is disabled` };
|
|
167
|
+
}
|
|
168
|
+
const childRunId = await startWorkflowExecution2(workflow, config.inputs ?? {}, this.registry, {
|
|
169
|
+
requestedByUserId: meta.requestedByUserId,
|
|
170
|
+
parentStepId: meta.stepId
|
|
171
|
+
});
|
|
172
|
+
const child = await db.getWorkflowRun(childRunId);
|
|
173
|
+
if (!child)
|
|
174
|
+
return { status: "failed", error: `Child workflow run ${childRunId} not found` };
|
|
175
|
+
return this.settle(child);
|
|
176
|
+
}
|
|
177
|
+
settle(child) {
|
|
178
|
+
const outcome = childRunOutcome(child);
|
|
179
|
+
if (!outcome) {
|
|
180
|
+
return {
|
|
181
|
+
status: "success",
|
|
182
|
+
async: true,
|
|
183
|
+
waitFor: "workflow.child.finished",
|
|
184
|
+
correlationId: child.id
|
|
185
|
+
};
|
|
186
|
+
}
|
|
187
|
+
if ("error" in outcome)
|
|
188
|
+
return { status: "failed", error: outcome.error };
|
|
189
|
+
return { status: "success", output: outcome.output, nextPort: "success" };
|
|
190
|
+
}
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
// src/workflows/executors/wait.ts
|
|
194
|
+
init_zod();
|
|
195
|
+
|
|
196
|
+
// src/workflows/wait-filter.ts
|
|
197
|
+
var PARAM_SHADOW_KEYS = [
|
|
198
|
+
"require",
|
|
199
|
+
"process",
|
|
200
|
+
"Bun",
|
|
201
|
+
"globalThis",
|
|
202
|
+
"global",
|
|
203
|
+
"fetch",
|
|
204
|
+
"setTimeout",
|
|
205
|
+
"setInterval"
|
|
206
|
+
];
|
|
207
|
+
var PARAM_SHADOW_VALUES = PARAM_SHADOW_KEYS.map(() => {
|
|
208
|
+
return;
|
|
209
|
+
});
|
|
210
|
+
var BODY_SHADOW_KEYS = ["eval", "Function", "AsyncFunction"];
|
|
211
|
+
var BODY_SHADOW_PREAMBLE = BODY_SHADOW_KEYS.map((k) => `var ${k} = undefined;`).join(" ");
|
|
212
|
+
var FILTER_TIMEOUT_MS = 50;
|
|
213
|
+
function getByDotPath(obj, path) {
|
|
214
|
+
if (obj === null || typeof obj !== "object") {
|
|
215
|
+
return { found: false, value: undefined };
|
|
216
|
+
}
|
|
217
|
+
const segments = path.split(".");
|
|
218
|
+
let cursor = obj;
|
|
219
|
+
for (const seg of segments) {
|
|
220
|
+
if (cursor === null || typeof cursor !== "object") {
|
|
221
|
+
return { found: false, value: undefined };
|
|
222
|
+
}
|
|
223
|
+
const rec = cursor;
|
|
224
|
+
if (!(seg in rec)) {
|
|
225
|
+
return { found: false, value: undefined };
|
|
226
|
+
}
|
|
227
|
+
cursor = rec[seg];
|
|
228
|
+
}
|
|
229
|
+
return { found: true, value: cursor };
|
|
230
|
+
}
|
|
231
|
+
function deepEqual(a, b) {
|
|
232
|
+
if (a === b)
|
|
233
|
+
return true;
|
|
234
|
+
if (typeof a !== typeof b)
|
|
235
|
+
return false;
|
|
236
|
+
if (a === null || b === null)
|
|
237
|
+
return false;
|
|
238
|
+
if (Array.isArray(a)) {
|
|
239
|
+
if (!Array.isArray(b))
|
|
240
|
+
return false;
|
|
241
|
+
if (a.length !== b.length)
|
|
242
|
+
return false;
|
|
243
|
+
for (let i = 0;i < a.length; i++) {
|
|
244
|
+
if (!deepEqual(a[i], b[i]))
|
|
245
|
+
return false;
|
|
246
|
+
}
|
|
247
|
+
return true;
|
|
248
|
+
}
|
|
249
|
+
if (typeof a === "object" && typeof b === "object") {
|
|
250
|
+
const aKeys = Object.keys(a);
|
|
251
|
+
const bKeys = Object.keys(b);
|
|
252
|
+
if (aKeys.length !== bKeys.length)
|
|
253
|
+
return false;
|
|
254
|
+
for (const k of aKeys) {
|
|
255
|
+
if (!deepEqual(a[k], b[k])) {
|
|
256
|
+
return false;
|
|
257
|
+
}
|
|
258
|
+
}
|
|
259
|
+
return true;
|
|
260
|
+
}
|
|
261
|
+
return false;
|
|
262
|
+
}
|
|
263
|
+
function matchObjectFilter(payload, filter) {
|
|
264
|
+
for (const [key, expected] of Object.entries(filter)) {
|
|
265
|
+
const { found, value } = getByDotPath(payload, key);
|
|
266
|
+
if (!found)
|
|
267
|
+
return false;
|
|
268
|
+
if (!deepEqual(value, expected))
|
|
269
|
+
return false;
|
|
270
|
+
}
|
|
271
|
+
return true;
|
|
272
|
+
}
|
|
273
|
+
async function runWithTimeout(fn) {
|
|
274
|
+
return new Promise((resolve) => {
|
|
275
|
+
let settled = false;
|
|
276
|
+
const timer = setTimeout(() => {
|
|
277
|
+
if (settled)
|
|
278
|
+
return;
|
|
279
|
+
settled = true;
|
|
280
|
+
resolve(null);
|
|
281
|
+
}, FILTER_TIMEOUT_MS);
|
|
282
|
+
queueMicrotask(() => {
|
|
283
|
+
if (settled)
|
|
284
|
+
return;
|
|
285
|
+
try {
|
|
286
|
+
const v = fn();
|
|
287
|
+
const then = v !== null && typeof v === "object" ? v.then : undefined;
|
|
288
|
+
if (typeof then === "function") {
|
|
289
|
+
then.call(v, undefined, () => {});
|
|
290
|
+
settled = true;
|
|
291
|
+
clearTimeout(timer);
|
|
292
|
+
resolve({ __asyncRejected: true });
|
|
293
|
+
return;
|
|
294
|
+
}
|
|
295
|
+
if (settled)
|
|
296
|
+
return;
|
|
297
|
+
settled = true;
|
|
298
|
+
clearTimeout(timer);
|
|
299
|
+
resolve(v);
|
|
300
|
+
} catch {
|
|
301
|
+
if (settled)
|
|
302
|
+
return;
|
|
303
|
+
settled = true;
|
|
304
|
+
clearTimeout(timer);
|
|
305
|
+
resolve(null);
|
|
306
|
+
}
|
|
307
|
+
});
|
|
308
|
+
});
|
|
309
|
+
}
|
|
310
|
+
var ASYNC_SENTINEL = "__asyncRejected";
|
|
311
|
+
function isAsyncSentinel(value) {
|
|
312
|
+
return value !== null && typeof value === "object" && value[ASYNC_SENTINEL] === true;
|
|
313
|
+
}
|
|
314
|
+
function compileStringFilter(filter) {
|
|
315
|
+
if (filter.length > 2048) {
|
|
316
|
+
throw new Error(`wait filter source exceeds 2KB cap (${filter.length} bytes)`);
|
|
317
|
+
}
|
|
318
|
+
let fn;
|
|
319
|
+
try {
|
|
320
|
+
fn = new Function(...PARAM_SHADOW_KEYS, "payload", `${BODY_SHADOW_PREAMBLE} return (${filter})(payload);`);
|
|
321
|
+
} catch (err) {
|
|
322
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
323
|
+
throw new Error(`wait filter compile error: ${msg}`);
|
|
324
|
+
}
|
|
325
|
+
return (payload) => fn(...PARAM_SHADOW_VALUES, payload);
|
|
326
|
+
}
|
|
327
|
+
function nullifyPrototypes(value) {
|
|
328
|
+
if (value === null || typeof value !== "object")
|
|
329
|
+
return value;
|
|
330
|
+
if (Array.isArray(value)) {
|
|
331
|
+
return value.map(nullifyPrototypes);
|
|
332
|
+
}
|
|
333
|
+
const out = Object.create(null);
|
|
334
|
+
for (const [k, v] of Object.entries(value)) {
|
|
335
|
+
out[k] = nullifyPrototypes(v);
|
|
336
|
+
}
|
|
337
|
+
return out;
|
|
338
|
+
}
|
|
339
|
+
async function matchesFilter(payload, filter) {
|
|
340
|
+
if (filter === undefined || filter === null)
|
|
341
|
+
return true;
|
|
342
|
+
if (typeof filter === "object" && !Array.isArray(filter)) {
|
|
343
|
+
return matchObjectFilter(payload, filter);
|
|
344
|
+
}
|
|
345
|
+
if (typeof filter === "string") {
|
|
346
|
+
let userFn;
|
|
347
|
+
try {
|
|
348
|
+
userFn = compileStringFilter(filter);
|
|
349
|
+
} catch {
|
|
350
|
+
return false;
|
|
351
|
+
}
|
|
352
|
+
let cloned;
|
|
353
|
+
try {
|
|
354
|
+
const cloneOnce = structuredClone(payload);
|
|
355
|
+
cloned = nullifyPrototypes(cloneOnce);
|
|
356
|
+
} catch {
|
|
357
|
+
try {
|
|
358
|
+
cloned = nullifyPrototypes(payload);
|
|
359
|
+
} catch {
|
|
360
|
+
cloned = payload;
|
|
361
|
+
}
|
|
362
|
+
}
|
|
363
|
+
const result = await runWithTimeout(() => userFn(cloned));
|
|
364
|
+
if (result === null)
|
|
365
|
+
return false;
|
|
366
|
+
if (isAsyncSentinel(result))
|
|
367
|
+
return false;
|
|
368
|
+
return !!result;
|
|
369
|
+
}
|
|
370
|
+
return false;
|
|
371
|
+
}
|
|
372
|
+
|
|
373
|
+
// src/workflows/executors/wait.ts
|
|
374
|
+
var WaitConfigSchema = discriminatedUnion("mode", [
|
|
375
|
+
object2({
|
|
376
|
+
mode: literal2("time"),
|
|
377
|
+
durationMs: number2().int().min(1).max(31536000000).describe("Wait duration in milliseconds (effective resolution ~5s)")
|
|
378
|
+
}),
|
|
379
|
+
object2({
|
|
380
|
+
mode: literal2("event"),
|
|
381
|
+
eventName: string2().min(1),
|
|
382
|
+
filter: union2([record2(string2(), unknown()), string2().max(2048)]).optional(),
|
|
383
|
+
scope: _enum2(["run", "global"]).default("run"),
|
|
384
|
+
timeoutMs: number2().int().min(1).max(31536000000).optional().describe("Timeout in milliseconds (effective resolution ~5s) — when reached, routes via 'timeout' port")
|
|
385
|
+
})
|
|
386
|
+
]);
|
|
387
|
+
var WaitOutputSchema = object2({
|
|
388
|
+
waitId: string2().uuid(),
|
|
389
|
+
mode: _enum2(["time", "event"]),
|
|
390
|
+
firedAt: string2().nullable(),
|
|
391
|
+
payload: unknown().optional()
|
|
392
|
+
});
|
|
393
|
+
|
|
394
|
+
class WaitExecutor extends BaseExecutor {
|
|
395
|
+
type = "wait";
|
|
396
|
+
mode = "async";
|
|
397
|
+
configSchema = WaitConfigSchema;
|
|
398
|
+
outputSchema = WaitOutputSchema;
|
|
399
|
+
async execute(config, _context, meta) {
|
|
400
|
+
const { db } = this.deps;
|
|
401
|
+
const existing = await db.getWaitStateByStepId(meta.stepId);
|
|
402
|
+
if (existing) {
|
|
403
|
+
if (existing.status !== "pending") {
|
|
404
|
+
const nextPort = computeNextPort(existing.mode, existing.status);
|
|
405
|
+
return {
|
|
406
|
+
status: "success",
|
|
407
|
+
output: {
|
|
408
|
+
waitId: existing.id,
|
|
409
|
+
mode: existing.mode,
|
|
410
|
+
firedAt: existing.resolvedAt,
|
|
411
|
+
payload: existing.firedPayload ?? undefined
|
|
412
|
+
},
|
|
413
|
+
nextPort
|
|
414
|
+
};
|
|
415
|
+
}
|
|
416
|
+
return {
|
|
417
|
+
status: "success",
|
|
418
|
+
async: true,
|
|
419
|
+
waitFor: "wait.fired",
|
|
420
|
+
correlationId: existing.id
|
|
421
|
+
};
|
|
422
|
+
}
|
|
423
|
+
if (config.mode === "time") {
|
|
424
|
+
const waitId = crypto.randomUUID();
|
|
425
|
+
const wakeUpAt = new Date(Date.now() + config.durationMs).toISOString();
|
|
426
|
+
await db.createWaitState({
|
|
427
|
+
id: waitId,
|
|
428
|
+
workflowRunId: meta.runId,
|
|
429
|
+
workflowRunStepId: meta.stepId,
|
|
430
|
+
mode: "time",
|
|
431
|
+
wakeUpAt
|
|
432
|
+
});
|
|
433
|
+
return {
|
|
434
|
+
status: "success",
|
|
435
|
+
async: true,
|
|
436
|
+
waitFor: "wait.fired",
|
|
437
|
+
correlationId: waitId
|
|
438
|
+
};
|
|
439
|
+
}
|
|
440
|
+
if (typeof config.filter === "string") {
|
|
441
|
+
compileStringFilter(config.filter);
|
|
442
|
+
}
|
|
443
|
+
const waitId = crypto.randomUUID();
|
|
444
|
+
const expiresAt = config.timeoutMs ? new Date(Date.now() + config.timeoutMs).toISOString() : null;
|
|
445
|
+
await db.createWaitState({
|
|
446
|
+
id: waitId,
|
|
447
|
+
workflowRunId: meta.runId,
|
|
448
|
+
workflowRunStepId: meta.stepId,
|
|
449
|
+
mode: "event",
|
|
450
|
+
eventName: config.eventName,
|
|
451
|
+
eventFilter: config.filter ?? null,
|
|
452
|
+
expiresAt,
|
|
453
|
+
scope: config.scope
|
|
454
|
+
});
|
|
455
|
+
subscribeWaitToBus2(waitId, config.eventName);
|
|
456
|
+
return {
|
|
457
|
+
status: "success",
|
|
458
|
+
async: true,
|
|
459
|
+
waitFor: "wait.fired",
|
|
460
|
+
correlationId: waitId
|
|
461
|
+
};
|
|
462
|
+
}
|
|
463
|
+
}
|
|
464
|
+
function computeNextPort(mode, status) {
|
|
465
|
+
if (mode === "time")
|
|
466
|
+
return "default";
|
|
467
|
+
return status === "timeout" ? "timeout" : "event";
|
|
468
|
+
}
|
|
469
|
+
|
|
470
|
+
// src/workflows/task-step-routing.ts
|
|
471
|
+
init_db3();
|
|
472
|
+
init_types();
|
|
473
|
+
var UNCLAIMED = {
|
|
474
|
+
claimed: false,
|
|
475
|
+
foreachChild: false,
|
|
476
|
+
joined: false,
|
|
477
|
+
successors: [],
|
|
478
|
+
runFailed: false
|
|
479
|
+
};
|
|
480
|
+
async function isTaskBoundToStep(taskId, stepId) {
|
|
481
|
+
const task = await getTaskById2(taskId);
|
|
482
|
+
return task?.workflowRunStepId === stepId;
|
|
483
|
+
}
|
|
484
|
+
async function failStepAndRunIfWaiting(def, stepId, runId, reason, ownerTaskId, options = {}) {
|
|
485
|
+
const now = new Date().toISOString();
|
|
486
|
+
return await getDbClient2().transaction(async () => {
|
|
487
|
+
const current = await getWorkflowRunStep2(stepId);
|
|
488
|
+
if (!current || current.status !== "waiting")
|
|
489
|
+
return UNCLAIMED;
|
|
490
|
+
if (ownerTaskId && !await isTaskBoundToStep(ownerTaskId, stepId))
|
|
491
|
+
return UNCLAIMED;
|
|
492
|
+
if (resolveForeachParent(def, current.nodeId)) {
|
|
493
|
+
const ctx = options.ctx ?? ((await getWorkflowRun2(runId))?.context ?? {});
|
|
494
|
+
return toRoutingResult(await settleForeachChild(def, runId, stepId, { status: "failed", error: reason }, ctx, {
|
|
495
|
+
registry: options.registry
|
|
496
|
+
}));
|
|
497
|
+
}
|
|
498
|
+
await updateWorkflowRunStep2(stepId, {
|
|
499
|
+
status: "failed",
|
|
500
|
+
error: reason,
|
|
501
|
+
finishedAt: now
|
|
502
|
+
});
|
|
503
|
+
await updateWorkflowRun2(runId, {
|
|
504
|
+
status: "failed",
|
|
505
|
+
error: reason,
|
|
506
|
+
finishedAt: now
|
|
507
|
+
});
|
|
508
|
+
return { claimed: true, foreachChild: false, joined: false, successors: [], runFailed: true };
|
|
509
|
+
});
|
|
510
|
+
}
|
|
511
|
+
function toRoutingResult(settle) {
|
|
512
|
+
return {
|
|
513
|
+
claimed: settle.claimed,
|
|
514
|
+
foreachChild: true,
|
|
515
|
+
joined: settle.joined,
|
|
516
|
+
successors: settle.successors,
|
|
517
|
+
runFailed: settle.runFailed
|
|
518
|
+
};
|
|
519
|
+
}
|
|
520
|
+
async function checkpointPortStepAndResolveSuccessors(def, runId, stepId, nodeId, output, nextPort, ctx, registry) {
|
|
521
|
+
return await getDbClient2().transaction(async () => {
|
|
522
|
+
const current = await getWorkflowRunStep2(stepId);
|
|
523
|
+
if (!current || current.status !== "waiting")
|
|
524
|
+
return UNCLAIMED;
|
|
525
|
+
const run = await getWorkflowRun2(runId);
|
|
526
|
+
if (!run || run.status !== "waiting" && run.status !== "running") {
|
|
527
|
+
return UNCLAIMED;
|
|
528
|
+
}
|
|
529
|
+
if (resolveForeachParent(def, nodeId)) {
|
|
530
|
+
return toRoutingResult(await settleForeachChild(def, runId, stepId, { status: "completed", output, nextPort }, ctx, {
|
|
531
|
+
registry
|
|
532
|
+
}));
|
|
533
|
+
}
|
|
534
|
+
await checkpointStep(runId, stepId, nodeId, { output, nextPort }, ctx);
|
|
535
|
+
await updateWorkflowRun2(runId, { status: "running" });
|
|
536
|
+
return {
|
|
537
|
+
claimed: true,
|
|
538
|
+
foreachChild: false,
|
|
539
|
+
joined: true,
|
|
540
|
+
successors: getSuccessors(def, nodeId, nextPort),
|
|
541
|
+
runFailed: false
|
|
542
|
+
};
|
|
543
|
+
});
|
|
544
|
+
}
|
|
545
|
+
var SUPERSEDE_RETRY_POLICY = {
|
|
546
|
+
maxRetries: 1,
|
|
547
|
+
strategy: "static",
|
|
548
|
+
baseDelayMs: 30000,
|
|
549
|
+
maxDelayMs: 30000
|
|
550
|
+
};
|
|
551
|
+
async function scheduleTaskStepRetry(def, runId, step, taskId, reason) {
|
|
552
|
+
const node = def.nodes.find((n) => n.id === step.nodeId);
|
|
553
|
+
if (!node || node.type !== "agent-task")
|
|
554
|
+
return "not-eligible";
|
|
555
|
+
let policy;
|
|
556
|
+
if (node.retry) {
|
|
557
|
+
const parsed = RetryPolicySchema.safeParse(node.retry);
|
|
558
|
+
if (!parsed.success)
|
|
559
|
+
return "not-eligible";
|
|
560
|
+
policy = parsed.data;
|
|
561
|
+
} else {
|
|
562
|
+
if (!await isSupersedeCut(taskId, reason))
|
|
563
|
+
return "not-eligible";
|
|
564
|
+
policy = SUPERSEDE_RETRY_POLICY;
|
|
565
|
+
}
|
|
566
|
+
return await getDbClient2().transaction(async () => {
|
|
567
|
+
const current = await getWorkflowRunStep2(step.id);
|
|
568
|
+
if (!current || current.status !== "waiting")
|
|
569
|
+
return "not-claimed";
|
|
570
|
+
if (!await isTaskBoundToStep(taskId, step.id))
|
|
571
|
+
return "not-claimed";
|
|
572
|
+
if (current.retryCount >= policy.maxRetries)
|
|
573
|
+
return "not-eligible";
|
|
574
|
+
await detachTaskFromWorkflowRunStep2(taskId);
|
|
575
|
+
await checkpointStepFailure(runId, step.id, reason, current.retryCount, policy);
|
|
576
|
+
return "scheduled";
|
|
577
|
+
});
|
|
578
|
+
}
|
|
579
|
+
async function isSupersedeCut(taskId, reason) {
|
|
580
|
+
if (reason === SUPERSEDED_WORKFLOW_TASK_REASON)
|
|
581
|
+
return true;
|
|
582
|
+
const task = await getTaskById2(taskId);
|
|
583
|
+
return task?.failureReason === SUPERSEDED_WORKFLOW_TASK_REASON;
|
|
584
|
+
}
|
|
585
|
+
async function completeTaskStepAndResolveSuccessors(def, runId, step, output, ctx, failureReason, ownerTaskId, registry) {
|
|
586
|
+
return await getDbClient2().transaction(async () => {
|
|
587
|
+
const current = await getWorkflowRunStep2(step.id);
|
|
588
|
+
if (!current || current.status !== "waiting")
|
|
589
|
+
return UNCLAIMED;
|
|
590
|
+
if (ownerTaskId && !await isTaskBoundToStep(ownerTaskId, step.id))
|
|
591
|
+
return UNCLAIMED;
|
|
592
|
+
if (resolveForeachParent(def, step.nodeId)) {
|
|
593
|
+
return toRoutingResult(await settleForeachChild(def, runId, step.id, {
|
|
594
|
+
status: "completed",
|
|
595
|
+
output,
|
|
596
|
+
...failureReason !== undefined ? { error: failureReason } : {}
|
|
597
|
+
}, ctx, { registry }));
|
|
598
|
+
}
|
|
599
|
+
await checkpointStep(runId, step.id, step.nodeId, { output }, ctx);
|
|
600
|
+
await updateWorkflowRun2(runId, { status: "running" });
|
|
601
|
+
return {
|
|
602
|
+
claimed: true,
|
|
603
|
+
foreachChild: false,
|
|
604
|
+
joined: true,
|
|
605
|
+
successors: getSuccessors(def, step.nodeId),
|
|
606
|
+
runFailed: false
|
|
607
|
+
};
|
|
608
|
+
});
|
|
609
|
+
}
|
|
610
|
+
|
|
611
|
+
// src/workflows/resume.ts
|
|
612
|
+
function setupWorkflowResumeListener2(eventBus, registry) {
|
|
613
|
+
const onTaskCompleted = async (data) => {
|
|
614
|
+
try {
|
|
615
|
+
const event = data;
|
|
616
|
+
if (!event.workflowRunId || !event.workflowRunStepId)
|
|
617
|
+
return;
|
|
618
|
+
await resumeFromTaskCompletion(event, registry);
|
|
619
|
+
} catch (err) {
|
|
620
|
+
console.error("[workflows] Resume from task completion failed:", err);
|
|
621
|
+
}
|
|
622
|
+
};
|
|
623
|
+
eventBus.on("task.completed", onTaskCompleted);
|
|
624
|
+
const onTaskFailed = async (data) => {
|
|
625
|
+
try {
|
|
626
|
+
const event = data;
|
|
627
|
+
if (!event.workflowRunId || !event.workflowRunStepId)
|
|
628
|
+
return;
|
|
629
|
+
await handleTaskFailure(event, event.failureReason ?? "Task failed", registry, {
|
|
630
|
+
retryable: true
|
|
631
|
+
});
|
|
632
|
+
} catch (err) {
|
|
633
|
+
console.error("[workflows] Handle task failure error:", err);
|
|
634
|
+
}
|
|
635
|
+
};
|
|
636
|
+
eventBus.on("task.failed", onTaskFailed);
|
|
637
|
+
const onTaskCancelled = async (data) => {
|
|
638
|
+
try {
|
|
639
|
+
const event = data;
|
|
640
|
+
if (!event.workflowRunId || !event.workflowRunStepId)
|
|
641
|
+
return;
|
|
642
|
+
await handleTaskFailure(event, "Task was cancelled", registry, { retryable: false });
|
|
643
|
+
} catch (err) {
|
|
644
|
+
console.error("[workflows] Handle task cancellation error:", err);
|
|
645
|
+
}
|
|
646
|
+
};
|
|
647
|
+
eventBus.on("task.cancelled", onTaskCancelled);
|
|
648
|
+
const onApprovalResolved = async (data) => {
|
|
649
|
+
try {
|
|
650
|
+
const event = data;
|
|
651
|
+
if (!event.workflowRunId || !event.workflowRunStepId)
|
|
652
|
+
return;
|
|
653
|
+
await resumeFromApprovalResolution(event, registry);
|
|
654
|
+
} catch (err) {
|
|
655
|
+
console.error("[workflows] Resume from approval resolution failed:", err);
|
|
656
|
+
}
|
|
657
|
+
};
|
|
658
|
+
eventBus.on("approval.resolved", onApprovalResolved);
|
|
659
|
+
const onChildRunFinished = async (data) => {
|
|
660
|
+
try {
|
|
661
|
+
const { parentStepId } = data;
|
|
662
|
+
if (!parentStepId)
|
|
663
|
+
return;
|
|
664
|
+
await resumeFromChildRun2(parentStepId, registry);
|
|
665
|
+
} catch (err) {
|
|
666
|
+
console.error("[workflows] Resume from child workflow run failed:", err);
|
|
667
|
+
}
|
|
668
|
+
};
|
|
669
|
+
eventBus.on("workflow.child.finished", onChildRunFinished);
|
|
670
|
+
return () => {
|
|
671
|
+
eventBus.off("task.completed", onTaskCompleted);
|
|
672
|
+
eventBus.off("task.failed", onTaskFailed);
|
|
673
|
+
eventBus.off("task.cancelled", onTaskCancelled);
|
|
674
|
+
eventBus.off("approval.resolved", onApprovalResolved);
|
|
675
|
+
eventBus.off("workflow.child.finished", onChildRunFinished);
|
|
676
|
+
};
|
|
677
|
+
}
|
|
678
|
+
async function failClosedOnUnreadableReplay(runId, stepId, resume) {
|
|
679
|
+
try {
|
|
680
|
+
await resume();
|
|
681
|
+
} catch (err) {
|
|
682
|
+
if (!await failRunOnUnreadableReplay(runId, err, stepId))
|
|
683
|
+
throw err;
|
|
684
|
+
}
|
|
685
|
+
}
|
|
686
|
+
async function resumeFromTaskCompletion(event, registry) {
|
|
687
|
+
await failClosedOnUnreadableReplay(event.workflowRunId, event.workflowRunStepId, () => resumeFromTaskCompletionUnguarded(event, registry));
|
|
688
|
+
}
|
|
689
|
+
async function resumeFromTaskCompletionUnguarded(event, registry) {
|
|
690
|
+
const run = await getWorkflowRun2(event.workflowRunId);
|
|
691
|
+
if (!run)
|
|
692
|
+
return;
|
|
693
|
+
let taskOutput = event.output;
|
|
694
|
+
if (event.output) {
|
|
695
|
+
try {
|
|
696
|
+
const parsed = JSON.parse(event.output);
|
|
697
|
+
if (typeof parsed === "object" && parsed !== null) {
|
|
698
|
+
taskOutput = parsed;
|
|
699
|
+
}
|
|
700
|
+
} catch {}
|
|
701
|
+
}
|
|
702
|
+
const stepOutput = { taskId: event.taskId, taskOutput };
|
|
703
|
+
if (run.status !== "waiting" && run.status !== "running") {
|
|
704
|
+
await recordLateForeachOutcome(run, event.workflowRunStepId, event, {
|
|
705
|
+
status: "completed",
|
|
706
|
+
output: stepOutput
|
|
707
|
+
});
|
|
708
|
+
return;
|
|
709
|
+
}
|
|
710
|
+
const step = await getWorkflowRunStep2(event.workflowRunStepId);
|
|
711
|
+
if (!step || step.status !== "waiting")
|
|
712
|
+
return;
|
|
713
|
+
if (await isStaleTaskEvent(step.id, event))
|
|
714
|
+
return;
|
|
715
|
+
const workflow = await getWorkflow2(run.workflowId);
|
|
716
|
+
if (!workflow)
|
|
717
|
+
return;
|
|
718
|
+
const ctx = run.context ?? {};
|
|
719
|
+
const routing = await completeTaskStepAndResolveSuccessors(workflow.definition, run.id, step, stepOutput, ctx, undefined, undefined, registry);
|
|
720
|
+
if (!routing.claimed)
|
|
721
|
+
return;
|
|
722
|
+
const successors = routing.successors;
|
|
723
|
+
if (successors.length > 0) {
|
|
724
|
+
const secretKeys = getSecretInputKeys(workflow.input);
|
|
725
|
+
await walkGraph(workflow.definition, run.id, ctx, successors, registry, workflow.id, secretKeys);
|
|
726
|
+
} else {
|
|
727
|
+
await finalizeOrWait2(run.id);
|
|
728
|
+
}
|
|
729
|
+
}
|
|
730
|
+
async function finalizeOrWait2(runId) {
|
|
731
|
+
await getDbClient2().transaction(async () => {
|
|
732
|
+
const steps = await getWorkflowRunStepsByRunId2(runId);
|
|
733
|
+
if (hasRunningStep(steps)) {
|
|
734
|
+
const run = await getWorkflowRun2(runId);
|
|
735
|
+
if (run?.status === "waiting")
|
|
736
|
+
await updateWorkflowRun2(runId, { status: "running" });
|
|
737
|
+
return;
|
|
738
|
+
}
|
|
739
|
+
const hasWaiting = steps.some((s) => s.status === "waiting" || s.status === "failed" && s.nextRetryAt != null);
|
|
740
|
+
if (hasWaiting) {
|
|
741
|
+
await updateWorkflowRun2(runId, { status: "waiting" });
|
|
742
|
+
} else {
|
|
743
|
+
await updateWorkflowRun2(runId, {
|
|
744
|
+
status: "completed",
|
|
745
|
+
finishedAt: new Date().toISOString()
|
|
746
|
+
});
|
|
747
|
+
}
|
|
748
|
+
});
|
|
749
|
+
}
|
|
750
|
+
async function handleTaskFailure(event, reason, registry, options) {
|
|
751
|
+
await failClosedOnUnreadableReplay(event.workflowRunId, event.workflowRunStepId, () => handleTaskFailureUnguarded(event, reason, registry, options));
|
|
752
|
+
}
|
|
753
|
+
async function handleTaskFailureUnguarded(event, reason, registry, options) {
|
|
754
|
+
const run = await getWorkflowRun2(event.workflowRunId);
|
|
755
|
+
if (!run)
|
|
756
|
+
return;
|
|
757
|
+
if (run.status !== "waiting" && run.status !== "running") {
|
|
758
|
+
await recordLateForeachOutcome(run, event.workflowRunStepId, event, {
|
|
759
|
+
status: "failed",
|
|
760
|
+
error: reason
|
|
761
|
+
});
|
|
762
|
+
return;
|
|
763
|
+
}
|
|
764
|
+
const step = await getWorkflowRunStep2(event.workflowRunStepId);
|
|
765
|
+
if (!step || step.status !== "waiting")
|
|
766
|
+
return;
|
|
767
|
+
if (await isStaleTaskEvent(step.id, event))
|
|
768
|
+
return;
|
|
769
|
+
const workflow = await getWorkflow2(run.workflowId);
|
|
770
|
+
if (!workflow)
|
|
771
|
+
return;
|
|
772
|
+
if (options.retryable) {
|
|
773
|
+
const retry = await scheduleTaskStepRetry(workflow.definition, run.id, step, event.taskId, reason);
|
|
774
|
+
if (retry === "scheduled") {
|
|
775
|
+
console.log(`[workflows] Task ${event.taskId} failed; step ${step.nodeId} of run ${run.id} queued for retry ${step.retryCount + 1}`);
|
|
776
|
+
return;
|
|
777
|
+
}
|
|
778
|
+
if (retry === "not-claimed")
|
|
779
|
+
return;
|
|
780
|
+
}
|
|
781
|
+
const onFailure = stepFailureMode(workflow.definition, step.nodeId);
|
|
782
|
+
if (onFailure === "fail") {
|
|
783
|
+
await markRunFailed(workflow.definition, event, reason, registry);
|
|
784
|
+
return;
|
|
785
|
+
}
|
|
786
|
+
const ctx = run.context ?? {};
|
|
787
|
+
const stepOutput = {
|
|
788
|
+
taskId: event.taskId,
|
|
789
|
+
taskOutput: `${FAILED_TASK_OUTPUT_PREFIX} ${reason}] This node failed or was cancelled.`
|
|
790
|
+
};
|
|
791
|
+
const routing = await completeTaskStepAndResolveSuccessors(workflow.definition, run.id, step, stepOutput, ctx, reason, undefined, registry);
|
|
792
|
+
if (!routing.claimed)
|
|
793
|
+
return;
|
|
794
|
+
const successors = routing.successors;
|
|
795
|
+
if (successors.length > 0) {
|
|
796
|
+
const secretKeys = getSecretInputKeys(workflow.input);
|
|
797
|
+
await walkGraph(workflow.definition, run.id, ctx, successors, registry, workflow.id, secretKeys);
|
|
798
|
+
} else {
|
|
799
|
+
await finalizeOrWait2(run.id);
|
|
800
|
+
}
|
|
801
|
+
}
|
|
802
|
+
async function recordLateForeachOutcome(run, stepId, event, outcome) {
|
|
803
|
+
if (run.status !== "failed")
|
|
804
|
+
return;
|
|
805
|
+
const step = await getWorkflowRunStep2(stepId);
|
|
806
|
+
if (!step || step.status !== "waiting")
|
|
807
|
+
return;
|
|
808
|
+
if (await isStaleTaskEvent(step.id, event))
|
|
809
|
+
return;
|
|
810
|
+
const workflow = await getWorkflow2(run.workflowId);
|
|
811
|
+
if (!workflow || !resolveForeachParent(workflow.definition, step.nodeId))
|
|
812
|
+
return;
|
|
813
|
+
await settleForeachChild(workflow.definition, run.id, step.id, outcome, run.context ?? {});
|
|
814
|
+
}
|
|
815
|
+
async function isStaleTaskEvent(stepId, event) {
|
|
816
|
+
if (!event.taskId)
|
|
817
|
+
return false;
|
|
818
|
+
const boundTask = await getTaskByWorkflowRunStepId2(stepId);
|
|
819
|
+
return boundTask != null && boundTask.id !== event.taskId;
|
|
820
|
+
}
|
|
821
|
+
async function markRunFailed(def, event, reason, registry) {
|
|
822
|
+
await failStepAndRunIfWaiting(def, event.workflowRunStepId, event.workflowRunId, reason, undefined, { registry });
|
|
823
|
+
}
|
|
824
|
+
async function retryFailedRun2(runId, registry) {
|
|
825
|
+
const run = await getWorkflowRun2(runId);
|
|
826
|
+
if (!run || run.status !== "failed")
|
|
827
|
+
throw new Error("Run is not in failed state");
|
|
828
|
+
const workflow = await getWorkflow2(run.workflowId);
|
|
829
|
+
if (!workflow)
|
|
830
|
+
throw new Error("Workflow not found");
|
|
831
|
+
const steps = await getWorkflowRunStepsByRunId2(runId);
|
|
832
|
+
const latestSteps = [...latestStepPerNode(steps).values()];
|
|
833
|
+
const failedStep = latestSteps.find((s) => s.status === "failed");
|
|
834
|
+
if (!failedStep)
|
|
835
|
+
throw new Error("No failed step found");
|
|
836
|
+
const completedNodeIds = new Set(await getCompletedStepNodeIds2(runId));
|
|
837
|
+
const notReady = await findWorkflowReadinessProblems({ nodes: workflow.definition.nodes.filter((node) => !completedNodeIds.has(node.id)) }, registry);
|
|
838
|
+
if (notReady.length > 0) {
|
|
839
|
+
throw new Error(`Retry not started, run left failed: ${formatReadinessProblems(notReady)}`);
|
|
840
|
+
}
|
|
841
|
+
const { activeEdges } = await loadCompletedStepRouting(workflow.definition, runId, completedNodeIds);
|
|
842
|
+
const foreachParent = resolveForeachParent(workflow.definition, failedStep.nodeId);
|
|
843
|
+
const failedNode = foreachParent ?? workflow.definition.nodes.find((n) => n.id === failedStep.nodeId);
|
|
844
|
+
if (!failedNode)
|
|
845
|
+
throw new Error(`Node ${failedStep.nodeId} not found in workflow definition`);
|
|
846
|
+
const hasStructuralPredecessor = workflow.definition.nodes.some((node) => node.next && getNextTargets(node.next).includes(failedNode.id));
|
|
847
|
+
const hasActivePredecessor = [...activeEdges].some((edge) => edge.endsWith(`→${failedNode.id}`));
|
|
848
|
+
const shouldRetryFailedNode = foreachParent != null || !hasStructuralPredecessor || hasActivePredecessor;
|
|
849
|
+
const ctx = run.context ?? {};
|
|
850
|
+
const attempts = await getDbClient2().transaction(async () => {
|
|
851
|
+
const current = await getWorkflowRun2(runId);
|
|
852
|
+
if (!current || current.status !== "failed")
|
|
853
|
+
return null;
|
|
854
|
+
const currentSteps = await getWorkflowRunStepsByRunId2(runId);
|
|
855
|
+
const attempts = foreachRetryAttempts(workflow.definition, [
|
|
856
|
+
...latestStepPerNode(currentSteps).values()
|
|
857
|
+
]);
|
|
858
|
+
const capError = foreachRetryCapError(currentSteps.length, attempts);
|
|
859
|
+
if (capError)
|
|
860
|
+
throw new Error(`Retry not started, run left failed: ${capError}`);
|
|
861
|
+
if (!foreachParent) {
|
|
862
|
+
await updateWorkflowRunStep2(failedStep.id, {
|
|
863
|
+
status: shouldRetryFailedNode ? "pending" : "cancelled",
|
|
864
|
+
error: shouldRetryFailedNode ? null : "Skipped on retry because its branch was not active"
|
|
865
|
+
});
|
|
866
|
+
}
|
|
867
|
+
await createForeachAttemptSteps(runId, attempts.flatMap((attempt) => attempt.rows));
|
|
868
|
+
await updateWorkflowRun2(runId, { status: "running", error: null, context: ctx });
|
|
869
|
+
return attempts;
|
|
870
|
+
});
|
|
871
|
+
if (!attempts)
|
|
872
|
+
throw new Error("Run is not in failed state");
|
|
873
|
+
const liveNodeIds = new Set(steps.filter((s) => s.status === "running" || s.status === "waiting").map((s) => s.nodeId));
|
|
874
|
+
const readyNodes = findReadyNodes(workflow.definition, completedNodeIds, activeEdges).filter((n) => n.id === failedNode.id || !liveNodeIds.has(n.id));
|
|
875
|
+
const nodesToRun = !shouldRetryFailedNode || readyNodes.some((n) => n.id === failedNode.id) ? readyNodes : [failedNode, ...readyNodes];
|
|
876
|
+
const secretKeys = getSecretInputKeys(workflow.input);
|
|
877
|
+
await walkGraph(workflow.definition, runId, ctx, nodesToRun, registry, workflow.id, secretKeys);
|
|
878
|
+
for (const attempt of attempts) {
|
|
879
|
+
if (attempt.parent.id !== failedNode.id) {
|
|
880
|
+
startForeachDrive(runId, attempt.parent.id, registry);
|
|
881
|
+
}
|
|
882
|
+
}
|
|
883
|
+
}
|
|
884
|
+
function latestStepPerNode(steps) {
|
|
885
|
+
const latest = new Map;
|
|
886
|
+
for (const step of steps)
|
|
887
|
+
latest.set(step.nodeId, step);
|
|
888
|
+
return latest;
|
|
889
|
+
}
|
|
890
|
+
function foreachRetryAttempts(def, latestSteps) {
|
|
891
|
+
const byParent = new Map;
|
|
892
|
+
const latestByNode = new Map(latestSteps.map((step) => [step.nodeId, step]));
|
|
893
|
+
for (const step of latestSteps) {
|
|
894
|
+
if (step.status !== "failed" && step.status !== "cancelled")
|
|
895
|
+
continue;
|
|
896
|
+
const parent = resolveForeachParent(def, step.nodeId);
|
|
897
|
+
if (!parent)
|
|
898
|
+
continue;
|
|
899
|
+
const parentStep = latestByNode.get(parent.id);
|
|
900
|
+
if (!parentStep || FOREACH_TERMINAL_STEP_STATUSES.has(parentStep.status))
|
|
901
|
+
continue;
|
|
902
|
+
const input = step.input ?? {};
|
|
903
|
+
const entry = byParent.get(parent.id) ?? { parent, rows: [] };
|
|
904
|
+
entry.rows.push({
|
|
905
|
+
nodeId: step.nodeId,
|
|
906
|
+
nodeType: step.nodeType,
|
|
907
|
+
itemKey: typeof input.itemKey === "string" ? input.itemKey : parseSyntheticNodeId(step.nodeId)?.itemKey ?? "",
|
|
908
|
+
index: typeof input.index === "number" ? input.index : 0
|
|
909
|
+
});
|
|
910
|
+
byParent.set(parent.id, entry);
|
|
911
|
+
}
|
|
912
|
+
return [...byParent.values()];
|
|
913
|
+
}
|
|
914
|
+
function foreachRetryCapError(existingSteps, attempts) {
|
|
915
|
+
const added = attempts.reduce((sum, attempt) => sum + attempt.rows.length, 0);
|
|
916
|
+
if (added === 0)
|
|
917
|
+
return null;
|
|
918
|
+
const maxSteps = getMaxWorkflowStepsPerRun();
|
|
919
|
+
if (existingSteps + added < maxSteps)
|
|
920
|
+
return null;
|
|
921
|
+
return `foreach retry would reach ${maxSteps} total steps (WORKFLOW_MAX_STEPS_PER_RUN): ${existingSteps} existing + ${added} new attempts leaves no room for the retry walk`;
|
|
922
|
+
}
|
|
923
|
+
async function cancelWorkflowRunRows2(runId, cancelReason) {
|
|
924
|
+
const terminalStatuses = ["completed", "failed", "cancelled", "skipped"];
|
|
925
|
+
const now = new Date().toISOString();
|
|
926
|
+
return await getDbClient2().transaction(async () => {
|
|
927
|
+
const current = await getWorkflowRun2(runId);
|
|
928
|
+
if (!current || terminalStatuses.includes(current.status))
|
|
929
|
+
return false;
|
|
930
|
+
const steps = await getWorkflowRunStepsByRunId2(runId);
|
|
931
|
+
for (const step of steps) {
|
|
932
|
+
if (terminalStatuses.includes(step.status))
|
|
933
|
+
continue;
|
|
934
|
+
const task = await getTaskByWorkflowRunStepId2(step.id);
|
|
935
|
+
if (task) {
|
|
936
|
+
await cancelTask2(task.id, cancelReason);
|
|
937
|
+
}
|
|
938
|
+
await updateWorkflowRunStep2(step.id, {
|
|
939
|
+
status: "cancelled",
|
|
940
|
+
error: cancelReason,
|
|
941
|
+
finishedAt: now
|
|
942
|
+
});
|
|
943
|
+
}
|
|
944
|
+
await cancelPendingApprovalRequestsForRun2(runId, cancelReason);
|
|
945
|
+
await updateWorkflowRun2(runId, {
|
|
946
|
+
status: "cancelled",
|
|
947
|
+
error: cancelReason,
|
|
948
|
+
finishedAt: now
|
|
949
|
+
});
|
|
950
|
+
return true;
|
|
951
|
+
});
|
|
952
|
+
}
|
|
953
|
+
async function cancelWorkflowRun2(runId, reason) {
|
|
954
|
+
const run = await getWorkflowRun2(runId);
|
|
955
|
+
if (!run)
|
|
956
|
+
throw new Error("Workflow run not found");
|
|
957
|
+
const terminalStatuses = ["completed", "failed", "cancelled", "skipped"];
|
|
958
|
+
if (terminalStatuses.includes(run.status) && run.status !== "cancelled") {
|
|
959
|
+
throw new Error(`Cannot cancel run in '${run.status}' state`);
|
|
960
|
+
}
|
|
961
|
+
const cancelReason = reason ?? "Cancelled by user";
|
|
962
|
+
const applied = await cancelWorkflowRunRows2(runId, cancelReason);
|
|
963
|
+
const cancelledApprovals = await listCancelledApprovalRequestsForRun2(runId);
|
|
964
|
+
const latestRun = await getWorkflowRun2(runId);
|
|
965
|
+
if (!applied && latestRun?.status !== "cancelled") {
|
|
966
|
+
throw new Error(`Cannot cancel run in '${latestRun?.status ?? "missing"}' state`);
|
|
967
|
+
}
|
|
968
|
+
await postApprovalCancellationUpdates(cancelledApprovals, applied ? cancelReason : latestRun?.error ?? cancelReason);
|
|
969
|
+
}
|
|
970
|
+
async function resumeFromApprovalResolution(event, registry) {
|
|
971
|
+
await failClosedOnUnreadableReplay(event.workflowRunId, event.workflowRunStepId, () => resumeFromApprovalResolutionUnguarded(event, registry));
|
|
972
|
+
}
|
|
973
|
+
async function resumeFromApprovalResolutionUnguarded(event, registry) {
|
|
974
|
+
const run = await getWorkflowRun2(event.workflowRunId);
|
|
975
|
+
if (!run || run.status !== "waiting" && run.status !== "running")
|
|
976
|
+
return;
|
|
977
|
+
const step = await getWorkflowRunStep2(event.workflowRunStepId);
|
|
978
|
+
if (!step || step.status !== "waiting")
|
|
979
|
+
return;
|
|
980
|
+
const workflow = await getWorkflow2(run.workflowId);
|
|
981
|
+
if (!workflow)
|
|
982
|
+
return;
|
|
983
|
+
if (event.status === "cancelled") {
|
|
984
|
+
console.warn(`[workflows] approval ${event.requestId} is cancelled; step ${event.workflowRunStepId} stays waiting for the run cancel path`);
|
|
985
|
+
return;
|
|
986
|
+
}
|
|
987
|
+
const ctx = run.context ?? {};
|
|
988
|
+
const { output: stepOutput, nextPort } = shapeApprovalResolution(registry, workflow.definition, step.nodeId, step.output, { requestId: event.requestId, status: event.status, responses: event.responses });
|
|
989
|
+
const routing = await checkpointPortStepAndResolveSuccessors(workflow.definition, run.id, step.id, step.nodeId, stepOutput, nextPort, ctx, registry);
|
|
990
|
+
if (!routing.claimed)
|
|
991
|
+
return;
|
|
992
|
+
const successors = routing.successors;
|
|
993
|
+
if (successors.length > 0) {
|
|
994
|
+
const secretKeys = getSecretInputKeys(workflow.input);
|
|
995
|
+
await walkGraph(workflow.definition, run.id, ctx, successors, registry, workflow.id, secretKeys);
|
|
996
|
+
} else {
|
|
997
|
+
await finalizeOrWait2(run.id);
|
|
998
|
+
}
|
|
999
|
+
}
|
|
1000
|
+
async function resumeWaitState2(waitId, status, payload, registry) {
|
|
1001
|
+
const cappedPayload = capPayload(payload);
|
|
1002
|
+
const result = await resolveWaitState2(waitId, { status, firedPayload: cappedPayload });
|
|
1003
|
+
if (!result.updated || !result.row)
|
|
1004
|
+
return;
|
|
1005
|
+
const waitRow = result.row;
|
|
1006
|
+
await failClosedOnUnreadableReplay(waitRow.workflowRunId, waitRow.workflowRunStepId, () => resumeClaimedWait(waitRow, status, cappedPayload, registry));
|
|
1007
|
+
}
|
|
1008
|
+
async function recoverResolvedWaitState2(waitId, registry) {
|
|
1009
|
+
const waitRow = await getWaitStateById2(waitId);
|
|
1010
|
+
if (!waitRow)
|
|
1011
|
+
return;
|
|
1012
|
+
const status = waitRow.status;
|
|
1013
|
+
if (status !== "fired" && status !== "timeout")
|
|
1014
|
+
return;
|
|
1015
|
+
await failClosedOnUnreadableReplay(waitRow.workflowRunId, waitRow.workflowRunStepId, () => resumeClaimedWait(waitRow, status, waitRow.firedPayload, registry));
|
|
1016
|
+
}
|
|
1017
|
+
async function resumeClaimedWait(waitRow, status, cappedPayload, registry) {
|
|
1018
|
+
const run = await getWorkflowRun2(waitRow.workflowRunId);
|
|
1019
|
+
if (!run || run.status !== "waiting" && run.status !== "running")
|
|
1020
|
+
return;
|
|
1021
|
+
const step = await getWorkflowRunStep2(waitRow.workflowRunStepId);
|
|
1022
|
+
if (!step || step.status !== "waiting")
|
|
1023
|
+
return;
|
|
1024
|
+
const workflow = await getWorkflow2(run.workflowId);
|
|
1025
|
+
if (!workflow)
|
|
1026
|
+
return;
|
|
1027
|
+
const nextPort = computeNextPort(waitRow.mode, status);
|
|
1028
|
+
const ctx = run.context ?? {};
|
|
1029
|
+
const stepOutput = {
|
|
1030
|
+
waitId: waitRow.id,
|
|
1031
|
+
mode: waitRow.mode,
|
|
1032
|
+
firedAt: waitRow.resolvedAt,
|
|
1033
|
+
payload: cappedPayload === undefined ? undefined : cappedPayload
|
|
1034
|
+
};
|
|
1035
|
+
const routing = await checkpointPortStepAndResolveSuccessors(workflow.definition, run.id, step.id, step.nodeId, stepOutput, nextPort, ctx, registry);
|
|
1036
|
+
if (!routing.claimed)
|
|
1037
|
+
return;
|
|
1038
|
+
if (waitRow.mode === "event" && waitRow.eventName) {
|
|
1039
|
+
pruneWaitFromBus(waitRow.id, waitRow.eventName);
|
|
1040
|
+
}
|
|
1041
|
+
const successors = routing.successors;
|
|
1042
|
+
if (successors.length > 0) {
|
|
1043
|
+
const secretKeys = getSecretInputKeys(workflow.input);
|
|
1044
|
+
await walkGraph(workflow.definition, run.id, ctx, successors, registry, workflow.id, secretKeys);
|
|
1045
|
+
} else {
|
|
1046
|
+
await finalizeOrWait2(run.id);
|
|
1047
|
+
}
|
|
1048
|
+
}
|
|
1049
|
+
async function resumeFromChildRun2(parentStepId, registry) {
|
|
1050
|
+
const step = await getWorkflowRunStep2(parentStepId);
|
|
1051
|
+
if (!step || step.status !== "waiting" || step.nodeType !== "sub-workflow")
|
|
1052
|
+
return false;
|
|
1053
|
+
const childId = await getChildWorkflowRunId(parentStepId);
|
|
1054
|
+
const child = childId ? await getWorkflowRun2(childId) : null;
|
|
1055
|
+
const outcome = child ? childRunOutcome(child) : { error: `Child workflow run of step ${parentStepId} was deleted` };
|
|
1056
|
+
if (!outcome)
|
|
1057
|
+
return false;
|
|
1058
|
+
let claimed = false;
|
|
1059
|
+
await failClosedOnUnreadableReplay(step.runId, step.id, async () => {
|
|
1060
|
+
const run = await getWorkflowRun2(step.runId);
|
|
1061
|
+
if (!run || run.status !== "waiting" && run.status !== "running")
|
|
1062
|
+
return;
|
|
1063
|
+
const workflow = await getWorkflow2(run.workflowId);
|
|
1064
|
+
if (!workflow)
|
|
1065
|
+
return;
|
|
1066
|
+
const ctx = run.context ?? {};
|
|
1067
|
+
const routing = "error" in outcome ? await failStepAndRunIfWaiting(workflow.definition, step.id, run.id, outcome.error, undefined, { ctx, registry }) : await checkpointPortStepAndResolveSuccessors(workflow.definition, run.id, step.id, step.nodeId, outcome.output, "success", ctx, registry);
|
|
1068
|
+
if (!routing.claimed || routing.runFailed) {
|
|
1069
|
+
claimed = routing.claimed;
|
|
1070
|
+
return;
|
|
1071
|
+
}
|
|
1072
|
+
claimed = true;
|
|
1073
|
+
if (routing.successors.length > 0) {
|
|
1074
|
+
const secretKeys = getSecretInputKeys(workflow.input);
|
|
1075
|
+
await walkGraph(workflow.definition, run.id, ctx, routing.successors, registry, workflow.id, secretKeys);
|
|
1076
|
+
} else {
|
|
1077
|
+
await finalizeOrWait2(run.id);
|
|
1078
|
+
}
|
|
1079
|
+
});
|
|
1080
|
+
return claimed;
|
|
1081
|
+
}
|
|
1082
|
+
var FIRED_PAYLOAD_BYTE_CAP = 64 * 1024;
|
|
1083
|
+
function capPayload(payload) {
|
|
1084
|
+
if (payload === undefined || payload === null)
|
|
1085
|
+
return payload;
|
|
1086
|
+
let encoded;
|
|
1087
|
+
try {
|
|
1088
|
+
encoded = JSON.stringify(payload);
|
|
1089
|
+
} catch {
|
|
1090
|
+
return { truncated: true, reason: "non-serializable" };
|
|
1091
|
+
}
|
|
1092
|
+
if (encoded.length <= FIRED_PAYLOAD_BYTE_CAP) {
|
|
1093
|
+
return payload;
|
|
1094
|
+
}
|
|
1095
|
+
const summary = encoded.slice(0, 1024);
|
|
1096
|
+
return {
|
|
1097
|
+
truncated: true,
|
|
1098
|
+
originalSize: encoded.length,
|
|
1099
|
+
summary
|
|
1100
|
+
};
|
|
1101
|
+
}
|
|
1102
|
+
var waitsByEvent = new Map;
|
|
1103
|
+
var listenersByEvent = new Map;
|
|
1104
|
+
var busRegistry = null;
|
|
1105
|
+
async function initWaitBusSubscriptions2(registry) {
|
|
1106
|
+
busRegistry = registry;
|
|
1107
|
+
const pendingNames = await collectPendingEventNames();
|
|
1108
|
+
for (const name of pendingNames) {
|
|
1109
|
+
const pending = await getPendingWaitsByEvent2(name);
|
|
1110
|
+
for (const w of pending) {
|
|
1111
|
+
registerWait(w.id, name);
|
|
1112
|
+
}
|
|
1113
|
+
}
|
|
1114
|
+
}
|
|
1115
|
+
async function collectPendingEventNames() {
|
|
1116
|
+
return new Set(await getPendingEventWaitNames2());
|
|
1117
|
+
}
|
|
1118
|
+
function subscribeWaitToBus2(waitId, eventName) {
|
|
1119
|
+
registerWait(waitId, eventName);
|
|
1120
|
+
}
|
|
1121
|
+
function registerWait(waitId, eventName) {
|
|
1122
|
+
let set = waitsByEvent.get(eventName);
|
|
1123
|
+
if (!set) {
|
|
1124
|
+
set = new Set;
|
|
1125
|
+
waitsByEvent.set(eventName, set);
|
|
1126
|
+
}
|
|
1127
|
+
set.add(waitId);
|
|
1128
|
+
if (!listenersByEvent.has(eventName)) {
|
|
1129
|
+
const listener = (data) => {
|
|
1130
|
+
processBusEvent(eventName, data).catch((err) => {
|
|
1131
|
+
console.error(`[workflows] Wait bus listener failed for event=${eventName}:`, scrubSecrets(err instanceof Error ? err.message : String(err)));
|
|
1132
|
+
});
|
|
1133
|
+
};
|
|
1134
|
+
listenersByEvent.set(eventName, listener);
|
|
1135
|
+
workflowEventBus2.on(eventName, listener);
|
|
1136
|
+
}
|
|
1137
|
+
}
|
|
1138
|
+
function pruneWaitFromBus(waitId, eventName) {
|
|
1139
|
+
const set = waitsByEvent.get(eventName);
|
|
1140
|
+
if (!set)
|
|
1141
|
+
return;
|
|
1142
|
+
set.delete(waitId);
|
|
1143
|
+
if (set.size === 0) {
|
|
1144
|
+
waitsByEvent.delete(eventName);
|
|
1145
|
+
const listener = listenersByEvent.get(eventName);
|
|
1146
|
+
if (listener) {
|
|
1147
|
+
workflowEventBus2.off(eventName, listener);
|
|
1148
|
+
listenersByEvent.delete(eventName);
|
|
1149
|
+
}
|
|
1150
|
+
}
|
|
1151
|
+
}
|
|
1152
|
+
async function processBusEvent(eventName, payload) {
|
|
1153
|
+
const set = waitsByEvent.get(eventName);
|
|
1154
|
+
if (!set || set.size === 0)
|
|
1155
|
+
return;
|
|
1156
|
+
if (!busRegistry)
|
|
1157
|
+
return;
|
|
1158
|
+
const waitIds = [...set];
|
|
1159
|
+
for (const waitId of waitIds) {
|
|
1160
|
+
try {
|
|
1161
|
+
const row = await getWaitStateById2(waitId);
|
|
1162
|
+
if (!row || row.status !== "pending") {
|
|
1163
|
+
set.delete(waitId);
|
|
1164
|
+
continue;
|
|
1165
|
+
}
|
|
1166
|
+
if (row.eventScope === "run") {
|
|
1167
|
+
if (!isPayloadInRun(payload, row.workflowRunId))
|
|
1168
|
+
continue;
|
|
1169
|
+
}
|
|
1170
|
+
const ok = await matchesFilter(payload, row.eventFilter ?? undefined);
|
|
1171
|
+
if (!ok)
|
|
1172
|
+
continue;
|
|
1173
|
+
await resumeWaitState2(waitId, "fired", payload, busRegistry);
|
|
1174
|
+
} catch (err) {
|
|
1175
|
+
console.error(`[workflows] Wait bus listener failed for wait=${waitId} event=${eventName}:`, err);
|
|
1176
|
+
}
|
|
1177
|
+
}
|
|
1178
|
+
if (set.size === 0) {
|
|
1179
|
+
waitsByEvent.delete(eventName);
|
|
1180
|
+
const listener = listenersByEvent.get(eventName);
|
|
1181
|
+
if (listener) {
|
|
1182
|
+
workflowEventBus2.off(eventName, listener);
|
|
1183
|
+
listenersByEvent.delete(eventName);
|
|
1184
|
+
}
|
|
1185
|
+
}
|
|
1186
|
+
}
|
|
1187
|
+
function isPayloadInRun(payload, runId) {
|
|
1188
|
+
if (typeof payload !== "object" || payload === null)
|
|
1189
|
+
return false;
|
|
1190
|
+
const rec = payload;
|
|
1191
|
+
return rec._runId === runId || rec.workflowRunId === runId;
|
|
1192
|
+
}
|
|
1193
|
+
export { shapeApprovalResolution, SubWorkflowExecutor, WaitExecutor, failStepAndRunIfWaiting, checkpointPortStepAndResolveSuccessors, scheduleTaskStepRetry, completeTaskStepAndResolveSuccessors, setupWorkflowResumeListener2, finalizeOrWait2, retryFailedRun2, cancelWorkflowRunRows2, cancelWorkflowRun2, resumeWaitState2, recoverResolvedWaitState2, resumeFromChildRun2, initWaitBusSubscriptions2, subscribeWaitToBus2 };
|