@desplega.ai/agent-swarm 2.4.4 → 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-thq04adh.js → acp-adapter-11gp67nr.js} +8 -6
- package/dist/{actions-j6ywh6x5.js → actions-8p2kze8j.js} +24 -22
- package/dist/{amp-adapter-k8b5xmj8.js → amp-adapter-z4qvhacj.js} +2 -2
- package/dist/{anthropic-messages-g6pqmqk4.js → anthropic-messages-txs190dd.js} +17 -20
- package/dist/{app-rb7vc02s.js → app-0qp0a2gk.js} +3 -3
- package/dist/{assistant-ftta1zjf.js → assistant-jedd3ktm.js} +27 -25
- package/dist/{azure-openai-responses-9dbmj346.js → azure-openai-responses-9kfwhmn7.js} +18 -13
- package/dist/{boot-reembed-xns7h5h9.js → boot-reembed-4xwq5tg0.js} +1 -1
- package/dist/{boot-reembed-srp6v6af.js → boot-reembed-n4wq5dd2.js} +1 -1
- package/dist/{boot-scrub-sweep-3qqrh2mz.js → boot-scrub-sweep-69h90xcq.js} +4 -4
- package/dist/{boot-seeds-9ks8hce7.js → boot-seeds-3vn0m9sg.js} +4 -4
- package/dist/{claude-adapter-k5ksyres.js → claude-adapter-221mvad5.js} +8 -6
- package/dist/{claude-managed-adapter-h2y889ge.js → claude-managed-adapter-gt6pa35d.js} +2 -2
- package/dist/{claude-managed-setup-n5a5eqks.js → claude-managed-setup-c3hp36ph.js} +4 -4
- package/dist/{claude-sdk-session-2j3j4kwn.js → claude-sdk-session-8thjsk5e.js} +9729 -9628
- package/dist/{cli-b9fcxxev.js → cli-007cnhm1.js} +50 -4
- package/dist/{cli-j4rqdf2n.js → cli-03vpq45j.js} +2 -2
- package/dist/{cli-amyhm3gj.js → cli-0y6f0ckq.js} +1 -1
- package/dist/{cli-4dg55wsk.js → cli-0z3vjrpm.js} +1 -1
- package/dist/{cli-pccfmtb5.js → cli-179xcyky.js} +11 -11
- package/dist/{cli-3r8samv3.js → cli-1f0gp9vz.js} +3 -2
- package/dist/{cli-hj3debgw.js → cli-1pr672hz.js} +3 -3
- package/dist/{cli-0bqteszy.js → cli-2nf3b08t.js} +328 -25
- package/dist/{cli-t7cyef5g.js → cli-36j4jnk3.js} +4 -4
- package/dist/{cli-er83cjbn.js → cli-3qv8naq8.js} +527 -231
- package/dist/cli-3zwye9sn.js +1193 -0
- package/dist/{cli-60cgh1rv.js → cli-459fk97x.js} +7 -7
- package/dist/{cli-xj0re5tm.js → cli-4w24f7z3.js} +1 -1
- package/dist/{cli-v3yr2gbg.js → cli-5kzk3472.js} +2198 -968
- package/dist/{cli-h7gc0b4r.js → cli-5m84zd2k.js} +55 -18
- package/dist/{cli-w6nadkh4.js → cli-5rvg1ddk.js} +5 -5
- package/dist/{cli-dvkmp9bt.js → cli-6ctwrf1z.js} +20 -10
- package/dist/{cli-9qtjfd60.js → cli-6rg2jn2y.js} +3 -2
- package/dist/{cli-2v045xyf.js → cli-7bwtc6yx.js} +3 -3
- package/dist/{cli-3cgngyd8.js → cli-7g40g8hq.js} +35 -17
- package/dist/{cli-q6r7t299.js → cli-92mqenkc.js} +2379 -154
- package/dist/{cli-cynvc9fj.js → cli-9ce2xw98.js} +8 -8
- package/dist/{cli-gqvh8d8a.js → cli-bgdtv62r.js} +36 -22
- 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-p20r8w42.js → cli-fcc5qe3f.js} +763 -268
- package/dist/{cli-1ephgwnq.js → cli-fes51ya2.js} +3 -3
- package/dist/{cli-1d70crsy.js → cli-ftmq4nvk.js} +2 -2
- package/dist/{cli-qkhc5aqy.js → cli-fy1jyyym.js} +13 -13
- package/dist/{cli-s95dknxh.js → cli-g1cw7xtt.js} +1 -1
- package/dist/{cli-qje9k2nh.js → cli-g9ypd5en.js} +12 -2
- package/dist/{cli-74c1wpm0.js → cli-gpv60278.js} +1 -1
- package/dist/{cli-pemywhfq.js → cli-ha9rmwgq.js} +2 -1
- package/dist/{cli-bnz0r3kc.js → cli-j1eghqfm.js} +5 -5
- package/dist/{cli-2zqpkvtt.js → cli-jpxsrxcb.js} +1 -1
- package/dist/{cli-ejs3m1sx.js → cli-kxpfx2xr.js} +1 -1
- package/dist/{cli-37yb7307.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-a0kcty51.js → cli-n6kmqngw.js} +2 -2
- package/dist/{cli-te6b5wvg.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-zrwpctp4.js → cli-r25qpaxt.js} +161 -63
- package/dist/{cli-nc35pxb7.js → cli-rh5smt8t.js} +19 -4
- package/dist/{cli-qwkcwjdn.js → cli-rycdaj0a.js} +1 -1
- package/dist/{cli-esxerzg7.js → cli-s4sntwt4.js} +2 -2
- package/dist/{cli-b8k7epj1.js → cli-segs7hx9.js} +8 -8
- package/dist/{cli-36ckw0wx.js → cli-stxdrqs2.js} +2 -2
- package/dist/{cli-vdm20wej.js → cli-sy8f9705.js} +4 -4
- package/dist/{cli-1m17dc9d.js → cli-ta8bd7th.js} +2 -2
- package/dist/{cli-2y91wnbh.js → cli-we0ra057.js} +222 -10
- package/dist/{cli-55qmbp6z.js → cli-x21dqn18.js} +5 -5
- package/dist/{cli-dqb4n629.js → cli-x6vf4sad.js} +11 -11
- package/dist/{cli-d2mb5s8a.js → cli-x77gen2p.js} +680 -440
- package/dist/{cli-vq97g1jq.js → cli-xawhpgc9.js} +95 -195
- package/dist/{cli-82d3nkc3.js → cli-xd4eqtr2.js} +2 -2
- package/dist/{cli-9fhkkpj5.js → cli-xt768r5m.js} +2 -2
- package/dist/{cli-zrk6jxv8.js → cli-y4x8ww6s.js} +2 -2
- package/dist/{cli-xc27fwzg.js → cli-y9jvxfhk.js} +3 -3
- package/dist/{cli-44159k28.js → cli-z63a269p.js} +2 -2
- package/dist/{cli-sm8rses9.js → cli-zfdp01hn.js} +8 -5
- package/dist/cli.js +31 -29
- package/dist/{cloudflare-workers-ai-system-one-ch2wxfcp.js → cloudflare-workers-ai-system-one-43v9qkjk.js} +7 -2
- package/dist/{codex-adapter-968xwfy7.js → codex-adapter-tcg1q3jk.js} +6 -4
- package/dist/{codex-login-m819c41r.js → codex-login-rn7c5gxt.js} +1 -1
- package/dist/{codex-session-runner-ht4z99fa.js → codex-session-runner-xfx4wd9x.js} +7 -5
- package/dist/{commands-pcymaeg9.js → commands-ew83nj70.js} +2 -2
- package/dist/{controller-fpjyk46r.js → controller-gnq1k1mf.js} +4 -4
- package/dist/{controllers-connect-mwzx1fh6.js → controllers-connect-tw1dan9r.js} +26 -24
- package/dist/{cursor-adapter-cp4s2e2f.js → cursor-adapter-yq5xgfcg.js} +1 -1
- package/dist/{db-xsjg9jqq.js → db-7gbc53fd.js} +14 -2
- package/dist/{db-sgd12bt9.js → db-t86xrnes.js} +3 -3
- package/dist/{devin-adapter-zgqadvnm.js → devin-adapter-4ycy4vrf.js} +1 -1
- package/dist/{dispatcher-x0jfw2c9.js → dispatcher-64bdyhps.js} +23 -21
- package/dist/{docker-fkgtr75t.js → docker-nfy2gc8t.js} +2 -2
- package/dist/{dsh-adapter-jrq7ma7h.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-8njj4h3b.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-kkr97v2f.js → grok-adapter-v82srhrx.js} +9 -7
- package/dist/handlers-c9ajzhbn.js +41 -0
- package/dist/{heartbeat-wd03zhn0.js → heartbeat-2vqs1319.js} +26 -24
- package/dist/{hook-xzj2ts2p.js → hook-01nk13wx.js} +7 -5
- package/dist/{hook-f0vghj42.js → hook-jzpheegg.js} +9 -7
- package/dist/{http-4hwxv62p.js → http-fc1k6ec8.js} +859 -252
- 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-c4ykd0f1.js → index-dr94zbnh.js} +13348 -11611
- package/dist/index-q3t40ryv.js +46 -0
- package/dist/{index-ww2vznkp.js → index-sepja052.js} +1 -1
- package/dist/{index-97q4xn03.js → index-zdycv3ja.js} +135 -79
- package/dist/{index-g216q6jr.js → index-zsyx51ap.js} +1 -4
- package/dist/{keepalive-hrh7xnr9.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-e8wnwnqx.js → maintenance-hjhzdt3k.js} +1 -1
- package/dist/{mistral-conversations-1medfwv7.js → mistral-conversations-sd0xf1tn.js} +13 -13
- package/dist/{node-3v3f08f7.js → node-jzjj7r9x.js} +10 -9
- package/dist/{oauth-refresh-sweep-zm0yj4sj.js → oauth-refresh-sweep-y3n1knmd.js} +3 -3
- package/dist/{onboard-dtwcq603.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-w3fbhpn0.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-j38dehfs.js → pricing-refresh-nevhat8h.js} +3 -3
- package/dist/{rbac-roles-ae8yttq4.js → rbac-roles-4y6ngvzq.js} +1 -1
- package/dist/{rbac-roles-ejxx9b35.js → rbac-roles-exbg8xb6.js} +2 -2
- package/dist/{registry-rxw10xzg.js → registry-ajhexe44.js} +1 -1
- package/dist/{render-v2-n16n0phe.js → render-v2-cfsh13z3.js} +3 -3
- package/dist/{resume-z0fgjc38.js → resume-5s2t0dbs.js} +10 -8
- package/dist/{runtime-xe3aqy4x.js → runtime-bt5pkpm1.js} +1 -1
- package/dist/{session-access-cv3mq6mc.js → session-access-dnbyp3cn.js} +6 -4
- package/dist/{setup-mafhj3yt.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-8b86mgjp.js → work-objects-7mg6216g.js} +26 -24
- package/dist/worker-nwb789mg.js +79 -0
- package/dist/{x-y45mthwn.js → x-pbpezr0s.js} +2 -2
- package/openapi.json +793 -6
- package/package.json +11 -11
- package/src/apps/catalog.generated.json +206 -4
- package/src/apps/page-validator.ts +13 -1
- package/src/assets/key.ts +17 -0
- package/src/azure-devops/handlers.ts +1 -1
- package/src/be/chunking.ts +42 -9
- 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/draft-text.ts +15 -0
- package/src/be/db/tasks/read.ts +54 -5
- package/src/be/db/workflow-runs.ts +96 -0
- package/src/be/db.ts +85 -237
- package/src/be/memory/index-content.ts +36 -17
- package/src/be/memory/providers/openai-embedding.ts +115 -61
- package/src/be/memory/providers/sqlite-store.ts +69 -23
- package/src/be/memory/reranker.ts +31 -1
- package/src/be/memory/search-candidates.ts +47 -0
- package/src/be/memory/types.ts +14 -0
- package/src/be/onboarding.ts +3 -0
- package/src/be/scripts/typecheck.ts +2 -0
- package/src/be/seed-scripts/catalog/heartbeat-jev-gate.ts +106 -54
- package/src/be/seed-scripts/index.ts +2 -2
- package/src/be/skill-sync.ts +43 -2
- package/src/be/swarm-config-guard.ts +25 -1
- package/src/be/task-attachment-store.ts +30 -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/catalog.generated.json +4 -4
- package/src/extensions/contract-types.generated.ts +1 -1
- package/src/extensions/contract.ts +9 -2
- package/src/extensions/post-bridge.ts +1 -0
- package/src/fs/provider.ts +13 -1
- package/src/github/handlers.ts +6 -6
- package/src/gitlab/handlers.ts +5 -5
- package/src/heartbeat/heartbeat.ts +31 -11
- package/src/http/assets.ts +14 -2
- package/src/http/core.ts +12 -0
- package/src/http/fs.ts +54 -34
- package/src/http/index.ts +4 -0
- package/src/http/mcp-body.ts +37 -0
- package/src/http/mcp-user.ts +3 -5
- package/src/http/mcp.ts +6 -7
- package/src/http/memory.ts +26 -15
- package/src/http/onboarding.ts +1 -0
- package/src/http/page-proxy.ts +61 -5
- package/src/http/pages-public.ts +27 -44
- package/src/http/pages.ts +4 -4
- package/src/http/script-runs.ts +2 -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 +13 -1
- package/src/http/webhooks.ts +141 -0
- package/src/integrations/chat-sdk/adapters.ts +24 -4
- package/src/integrations/chat-sdk/attachments.ts +90 -12
- package/src/integrations/chat-sdk/facts.ts +138 -0
- package/src/integrations/chat-sdk/inbound.ts +156 -8
- package/src/integrations/chat-sdk/limits.ts +24 -0
- package/src/integrations/chat-sdk/outbound-files.ts +186 -0
- package/src/integrations/chat-sdk/outbound.ts +272 -5
- package/src/integrations/chat-sdk/relay.ts +4 -1
- package/src/integrations/chat-sdk/router.ts +28 -2
- package/src/integrations/chat-sdk/steering.ts +9 -0
- package/src/integrations/chat-sdk/templates.ts +14 -0
- package/src/integrations/chat-sdk/thread-buffer.ts +4 -0
- package/src/mcp-limits.ts +20 -0
- package/src/pages/browser-sdk.ts +8 -3
- package/src/pages/feedback-overlay.ts +1 -1
- package/src/providers/acp-targets.ts +1 -1
- package/src/providers/amp-adapter.ts +1 -1
- package/src/providers/codex-adapter.ts +106 -119
- package/src/providers/codex-agents-md.ts +35 -229
- package/src/providers/cursor-adapter.ts +1 -1
- package/src/providers/pi-mono-adapter.ts +5 -53
- package/src/realtime/transport.ts +1 -2
- package/src/script-workflows/workflow-ctx.ts +1 -0
- package/src/scripts-runtime/types/stdlib.d.ts +2 -0
- package/src/scripts-runtime/types/swarm-extension.d.ts +14 -4
- package/src/scripts-runtime/types/swarm-sdk.d.ts +2 -0
- package/src/server-user.ts +2 -0
- package/src/server.ts +2 -0
- package/src/slack/assistant.ts +2 -2
- package/src/slack/files.ts +2 -0
- package/src/slack/handlers.test.ts +158 -0
- package/src/slack/handlers.ts +23 -32
- package/src/slack/inbound-files.ts +39 -6
- package/src/slack/thread-buffer.ts +2 -1
- package/src/tasks/context-key.ts +61 -1
- package/src/tasks/task-terminal-effects.ts +21 -17
- package/src/tests/apps-card-grid.test.ts +197 -0
- package/src/tests/apps-catalog-additions.test.ts +120 -0
- package/src/tests/apps-tree.test.ts +129 -0
- 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-attachments-reply.test.ts +52 -0
- package/src/tests/chat-sdk-config.test.ts +11 -0
- package/src/tests/chat-sdk-facts.test.ts +23 -0
- package/src/tests/chat-sdk-inbound.test.ts +597 -4
- package/src/tests/chat-sdk-outbound-files.test.ts +652 -0
- package/src/tests/chat-sdk-telegram-markdown.test.ts +92 -0
- package/src/tests/codex-adapter.test.ts +167 -242
- package/src/tests/config-api-only-keys.test.ts +9 -0
- package/src/tests/context-key.test.ts +50 -0
- package/src/tests/core-auth.test.ts +5 -0
- package/src/tests/cors-allowlist.test.ts +1 -1
- 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/extensions-example-slack-reply-gate.test.ts +262 -91
- 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/heartbeat-checklist.test.ts +33 -0
- package/src/tests/heartbeat-jev-gate-credentials.test.ts +44 -0
- package/src/tests/heartbeat-jev-gate.test.ts +242 -1
- package/src/tests/integration-disable-flags.test.ts +36 -0
- package/src/tests/kv-page-proxy.test.ts +91 -5
- package/src/tests/launch-password-rejection.test.ts +2 -2
- package/src/tests/mcp-input-caps.test.ts +294 -0
- package/src/tests/mcp-tools.test.ts +1 -1
- package/src/tests/memory-chunked-search-starvation.test.ts +200 -0
- package/src/tests/memory-embedding-input-cap.test.ts +114 -0
- package/src/tests/memory.test.ts +14 -0
- package/src/tests/onboarding.test.ts +12 -0
- package/src/tests/page-guest-session.test.ts +19 -8
- package/src/tests/page-proxy-authed.test.ts +1 -1
- package/src/tests/page-proxy.test.ts +17 -10
- package/src/tests/page-session.test.ts +102 -1
- package/src/tests/pages-authed-mode.test.ts +82 -12
- package/src/tests/pages-password-mode.test.ts +5 -5
- 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/realtime-transport.test.ts +13 -7
- 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/script-runs-http.test.ts +84 -0
- package/src/tests/scripts-http.test.ts +10 -1
- 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 +23 -3
- package/src/tests/task-audio-transcription.test.ts +299 -0
- package/src/tests/task-completion-memory-chunking.test.ts +434 -0
- package/src/tests/task-terminal-secret-scrub.test.ts +6 -1
- package/src/tests/transcription.test.ts +299 -0
- 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-system-one-decision-providers.test.ts +98 -1
- package/src/tests/workflow-system-one-decision.test.ts +2 -1
- package/src/tests/workflow-tla-races.test.ts +2 -2
- package/src/tools/chat-access.ts +10 -0
- package/src/tools/chat-post.ts +31 -11
- package/src/tools/chat-reply.ts +32 -10
- package/src/tools/memory-search.ts +3 -1
- package/src/tools/script-delete.ts +1 -1
- package/src/tools/system-one-decide.ts +17 -4
- package/src/tools/workflows/create-workflow.ts +5 -3
- package/src/types.ts +9 -2
- package/src/utils/config-env-export.ts +4 -1
- package/src/utils/page-session.ts +133 -20
- package/src/utils/secret-scrubber.ts +4 -0
- package/src/utils/skills-refresh.ts +83 -43
- package/src/utils/transcription.ts +246 -0
- 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/executors/system-one-decision.ts +28 -7
- package/src/workflows/executors/system-one-providers.ts +39 -4
- 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/extensions/slack-reply-gate/README.md +24 -22
- package/templates/extensions/slack-reply-gate/hooks.ts +146 -95
- package/templates/extensions/slack-reply-gate/manifest.json +2 -2
- package/templates/skills/apps/SKILL.md +28 -6
- package/templates/skills/apps/content.md +28 -6
- package/templates/skills/chat-sdk/content.md +6 -2
- package/templates/skills/code-quality/content.md +2 -1
- package/templates/skills/heartbeat-runbook/content.md +5 -5
- package/templates/skills/pages/content.md +6 -4
- package/templates/skills/slack-interaction/content.md +5 -0
- package/templates/skills/swarm-scripts/SKILL.md +2 -2
- package/templates/skills/swarm-scripts/content.md +2 -2
- package/templates/skills/work-on-task/SKILL.md +1 -1
- package/templates/skills/work-on-task/content.md +1 -1
- package/dist/cli-51y09c6p.js +0 -2657
- package/dist/cli-yw91rnmw.js +0 -13
- package/dist/handlers-0pvee9nw.js +0 -39
- package/dist/index-gdh8jrdm.js +0 -43
- package/dist/index-hg0x2cdv.js +0 -40
- package/dist/index-thm0y6a8.js +0 -41
- package/dist/lead-ee8vazz2.js +0 -77
- package/dist/worker-16s75hm5.js +0 -77
|
@@ -1,8 +1,24 @@
|
|
|
1
|
-
import {
|
|
2
|
-
|
|
1
|
+
import {
|
|
2
|
+
getDbClient,
|
|
3
|
+
getLatestStepForNode,
|
|
4
|
+
getWorkflowRun,
|
|
5
|
+
getWorkflowRunStep,
|
|
6
|
+
getWorkflowRunStepsByRunId,
|
|
7
|
+
updateWorkflowRun,
|
|
8
|
+
updateWorkflowRunStep,
|
|
9
|
+
} from "../be/db";
|
|
10
|
+
import type {
|
|
11
|
+
WorkflowDefinition,
|
|
12
|
+
WorkflowNode,
|
|
13
|
+
WorkflowRunStep,
|
|
14
|
+
WorkflowRunStepStatus,
|
|
15
|
+
} from "../types";
|
|
3
16
|
import { checkpointStep } from "./checkpoint";
|
|
4
17
|
import { getSuccessors } from "./definition";
|
|
5
18
|
import type { ForeachOutput } from "./executors/foreach";
|
|
19
|
+
import type { ExecutorRegistry } from "./executors/registry";
|
|
20
|
+
import { effectiveFailurePolicy } from "./foreach-body";
|
|
21
|
+
import { startForeachDrive } from "./foreach-driver";
|
|
6
22
|
|
|
7
23
|
export const FOREACH_TERMINAL_STEP_STATUSES = new Set([
|
|
8
24
|
"completed",
|
|
@@ -29,7 +45,10 @@ export function parseSyntheticNodeId(nodeId: string): SyntheticNodeId | null {
|
|
|
29
45
|
};
|
|
30
46
|
}
|
|
31
47
|
|
|
32
|
-
export function resolveForeachParent(
|
|
48
|
+
export function resolveForeachParent(
|
|
49
|
+
def: Pick<WorkflowDefinition, "nodes">,
|
|
50
|
+
nodeId: string,
|
|
51
|
+
): WorkflowNode | null {
|
|
33
52
|
const parsed = parseSyntheticNodeId(nodeId);
|
|
34
53
|
if (!parsed) return null;
|
|
35
54
|
const parent = def.nodes.find((node) => node.id === parsed.parentNodeId);
|
|
@@ -53,10 +72,9 @@ export async function joinForeach(
|
|
|
53
72
|
throw new Error(`Step "${childStep.nodeId}" is not a foreach child`);
|
|
54
73
|
}
|
|
55
74
|
|
|
56
|
-
const children = (await getWorkflowRunStepsByRunId(runId))
|
|
57
|
-
.filter((step) => resolveForeachParent(def, step.nodeId)?.id === parent.id)
|
|
58
|
-
.sort((a, b) => childIndex(a) - childIndex(b));
|
|
75
|
+
const children = latestForeachChildren(def, parent.id, await getWorkflowRunStepsByRunId(runId));
|
|
59
76
|
|
|
77
|
+
// `pending` (queued, not started) is non-terminal like running and waiting.
|
|
60
78
|
if (children.some((step) => !FOREACH_TERMINAL_STEP_STATUSES.has(step.status))) {
|
|
61
79
|
return { joined: false, parentNodeId: parent.id, successors: [] };
|
|
62
80
|
}
|
|
@@ -81,8 +99,28 @@ export async function joinForeach(
|
|
|
81
99
|
};
|
|
82
100
|
}
|
|
83
101
|
|
|
102
|
+
/**
|
|
103
|
+
* The latest step row per child node id of foreach `parentNodeId`, in item
|
|
104
|
+
* order. `steps` must be in insert order (as `getWorkflowRunStepsByRunId`
|
|
105
|
+
* returns them), so a later attempt row supersedes an earlier one.
|
|
106
|
+
*/
|
|
107
|
+
export function latestForeachChildren(
|
|
108
|
+
def: WorkflowDefinition,
|
|
109
|
+
parentNodeId: string,
|
|
110
|
+
steps: WorkflowRunStep[],
|
|
111
|
+
): WorkflowRunStep[] {
|
|
112
|
+
const latest = new Map<string, WorkflowRunStep>();
|
|
113
|
+
for (const step of steps) {
|
|
114
|
+
if (resolveForeachParent(def, step.nodeId)?.id === parentNodeId) latest.set(step.nodeId, step);
|
|
115
|
+
}
|
|
116
|
+
return [...latest.values()].sort((a, b) => childIndex(a) - childIndex(b));
|
|
117
|
+
}
|
|
118
|
+
|
|
84
119
|
export function buildForeachAggregate(children: WorkflowRunStep[]): ForeachOutput {
|
|
85
|
-
|
|
120
|
+
// Only the latest row per child node id counts (insert order).
|
|
121
|
+
const latest = new Map<string, WorkflowRunStep>();
|
|
122
|
+
for (const step of children) latest.set(step.nodeId, step);
|
|
123
|
+
const orderedChildren = [...latest.values()].sort((a, b) => childIndex(a) - childIndex(b));
|
|
86
124
|
const results: ForeachOutput["results"] = orderedChildren.map((step) => {
|
|
87
125
|
const parsed = parseSyntheticNodeId(step.nodeId)!;
|
|
88
126
|
const status = resultStatus(step);
|
|
@@ -92,11 +130,15 @@ export function buildForeachAggregate(children: WorkflowRunStep[]): ForeachOutpu
|
|
|
92
130
|
output: step.output,
|
|
93
131
|
};
|
|
94
132
|
});
|
|
133
|
+
// Only `completed` without an error is ok: failed, cancelled, skipped and the
|
|
134
|
+
// legacy completed+error shape all count as failed.
|
|
95
135
|
const failedCount = results.filter((result) => result.status !== "completed").length;
|
|
136
|
+
const cancelledCount = results.filter((result) => result.status === "cancelled").length;
|
|
96
137
|
const aggregate: ForeachOutput = {
|
|
97
138
|
results,
|
|
98
139
|
okCount: results.length - failedCount,
|
|
99
140
|
failedCount,
|
|
141
|
+
...(cancelledCount > 0 ? { cancelledCount } : {}),
|
|
100
142
|
};
|
|
101
143
|
return aggregate;
|
|
102
144
|
}
|
|
@@ -115,3 +157,160 @@ function resultStatus(step: WorkflowRunStep): ForeachOutput["results"][number]["
|
|
|
115
157
|
// (e.g. an agent quoting a log line) is user-controlled text, not a failure.
|
|
116
158
|
return step.error ? "failed" : "completed";
|
|
117
159
|
}
|
|
160
|
+
|
|
161
|
+
/** The terminal outcome of one foreach item. */
|
|
162
|
+
export interface ForeachChildOutcome {
|
|
163
|
+
status: "completed" | "failed" | "cancelled";
|
|
164
|
+
output?: unknown;
|
|
165
|
+
/**
|
|
166
|
+
* The failure reason. On a `completed` outcome it marks the legacy
|
|
167
|
+
* `onNodeFailure: "continue"` shape (completed with error), which the
|
|
168
|
+
* aggregate counts as failed.
|
|
169
|
+
*/
|
|
170
|
+
error?: string;
|
|
171
|
+
/** Stored on the child row for display only: a body's ports do not route. */
|
|
172
|
+
nextPort?: string;
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
export interface ForeachSettleResult {
|
|
176
|
+
/** False when the child was not in an expected status, or the run is no longer live. */
|
|
177
|
+
claimed: boolean;
|
|
178
|
+
foreachChild: true;
|
|
179
|
+
/** True when this settlement closed the join and checkpointed the parent. */
|
|
180
|
+
joined: boolean;
|
|
181
|
+
/** The parent's successors to walk, when `joined`. */
|
|
182
|
+
successors: WorkflowNode[];
|
|
183
|
+
/** True when this settlement failed the run (a failed item under fail-fast). */
|
|
184
|
+
runFailed: boolean;
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
/**
|
|
188
|
+
* The one place a foreach child reaches a terminal status. Every resume path
|
|
189
|
+
* (task, approval, wait, child run, and their failure paths) and the foreach
|
|
190
|
+
* driver's inline items settle through here.
|
|
191
|
+
*
|
|
192
|
+
* In one transaction:
|
|
193
|
+
* 1. Re-read the child; claim it only while it is in one of `from` (default
|
|
194
|
+
* `waiting`, the parked status every resume path expects).
|
|
195
|
+
* 2. Write the terminal status, output, error and port.
|
|
196
|
+
* 3. A run that is no longer live (failed, cancelled) only records the
|
|
197
|
+
* outcome: a late sibling must not revive a failed run or route its join.
|
|
198
|
+
* 4. A failed or cancelled item under fail-fast (see effectiveFailurePolicy)
|
|
199
|
+
* cancels the queued siblings and fails the run; in-flight siblings still
|
|
200
|
+
* record their outcomes. Under collect-outcomes it counts as failed in the
|
|
201
|
+
* aggregate and the join proceeds.
|
|
202
|
+
* 5. Otherwise run the join and set the run `running`.
|
|
203
|
+
*
|
|
204
|
+
* When the join stays open with a queued (`pending`) item and a `registry` is
|
|
205
|
+
* passed, a drive of the parent starts after commit, so the next item starts.
|
|
206
|
+
* Orphaned `running` items are the recovery sweep's.
|
|
207
|
+
*/
|
|
208
|
+
export async function settleForeachChild(
|
|
209
|
+
def: WorkflowDefinition,
|
|
210
|
+
runId: string,
|
|
211
|
+
childStepId: string,
|
|
212
|
+
outcome: ForeachChildOutcome,
|
|
213
|
+
ctx: Record<string, unknown>,
|
|
214
|
+
options: {
|
|
215
|
+
from?: readonly WorkflowRunStepStatus[];
|
|
216
|
+
registry?: ExecutorRegistry;
|
|
217
|
+
/** Run error when this outcome fails the run. Defaults to the outcome error. */
|
|
218
|
+
runError?: string;
|
|
219
|
+
} = {},
|
|
220
|
+
): Promise<ForeachSettleResult> {
|
|
221
|
+
const from = options.from ?? ["waiting"];
|
|
222
|
+
return await getDbClient().transaction(async (): Promise<ForeachSettleResult> => {
|
|
223
|
+
const unclaimed: ForeachSettleResult = {
|
|
224
|
+
claimed: false,
|
|
225
|
+
foreachChild: true,
|
|
226
|
+
joined: false,
|
|
227
|
+
successors: [],
|
|
228
|
+
runFailed: false,
|
|
229
|
+
};
|
|
230
|
+
const current = await getWorkflowRunStep(childStepId);
|
|
231
|
+
if (!current || !from.includes(current.status)) return unclaimed;
|
|
232
|
+
const parent = resolveForeachParent(def, current.nodeId);
|
|
233
|
+
if (!parent) throw new Error(`Step "${current.nodeId}" is not a foreach child`);
|
|
234
|
+
const run = await getWorkflowRun(runId);
|
|
235
|
+
if (!run) return unclaimed;
|
|
236
|
+
|
|
237
|
+
const finishedAt = new Date().toISOString();
|
|
238
|
+
await updateWorkflowRunStep(childStepId, {
|
|
239
|
+
status: outcome.status,
|
|
240
|
+
...(outcome.output !== undefined ? { output: outcome.output } : {}),
|
|
241
|
+
...(outcome.error !== undefined ? { error: outcome.error } : {}),
|
|
242
|
+
...(outcome.nextPort ? { nextPort: outcome.nextPort } : {}),
|
|
243
|
+
finishedAt,
|
|
244
|
+
});
|
|
245
|
+
|
|
246
|
+
if (run.status !== "running" && run.status !== "waiting") return unclaimed;
|
|
247
|
+
|
|
248
|
+
if (outcome.status !== "completed" && effectiveFailurePolicy(parent, def) === "fail-fast") {
|
|
249
|
+
const reason = options.runError ?? outcome.error ?? `foreach item ${current.nodeId} failed`;
|
|
250
|
+
await cancelQueuedForeachChildren(def, runId, parent.id, current.nodeId);
|
|
251
|
+
await updateWorkflowRun(runId, { status: "failed", error: reason, finishedAt });
|
|
252
|
+
return { ...unclaimed, claimed: true, runFailed: true };
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
const join = await joinForeach(def, runId, current, ctx);
|
|
256
|
+
await updateWorkflowRun(runId, { status: "running" });
|
|
257
|
+
const registry = options.registry;
|
|
258
|
+
if (!join.joined && registry && (await hasQueuedChild(def, runId, parent.id))) {
|
|
259
|
+
getDbClient().afterCommit(() => startForeachDrive(runId, parent.id, registry));
|
|
260
|
+
}
|
|
261
|
+
return {
|
|
262
|
+
claimed: true,
|
|
263
|
+
foreachChild: true,
|
|
264
|
+
joined: join.joined,
|
|
265
|
+
successors: join.successors,
|
|
266
|
+
runFailed: false,
|
|
267
|
+
};
|
|
268
|
+
});
|
|
269
|
+
}
|
|
270
|
+
|
|
271
|
+
/**
|
|
272
|
+
* Fail-fast: cancel every queued (`pending`) child of the fan-out so none of
|
|
273
|
+
* them starts. In-flight children are left to finish and record their outcome.
|
|
274
|
+
* A run retry gives each cancelled item a new attempt.
|
|
275
|
+
*/
|
|
276
|
+
export async function cancelQueuedForeachChildren(
|
|
277
|
+
def: WorkflowDefinition,
|
|
278
|
+
runId: string,
|
|
279
|
+
parentNodeId: string,
|
|
280
|
+
failedChildNodeId?: string,
|
|
281
|
+
): Promise<number> {
|
|
282
|
+
const failedKey = failedChildNodeId
|
|
283
|
+
? (parseSyntheticNodeId(failedChildNodeId)?.itemKey ?? failedChildNodeId)
|
|
284
|
+
: undefined;
|
|
285
|
+
const error = failedKey
|
|
286
|
+
? `not started: fail-fast after item ${failedKey} failed`
|
|
287
|
+
: "not started: fail-fast after the run failed";
|
|
288
|
+
const finishedAt = new Date().toISOString();
|
|
289
|
+
let cancelled = 0;
|
|
290
|
+
await getDbClient().transaction(async () => {
|
|
291
|
+
const children = latestForeachChildren(
|
|
292
|
+
def,
|
|
293
|
+
parentNodeId,
|
|
294
|
+
await getWorkflowRunStepsByRunId(runId),
|
|
295
|
+
);
|
|
296
|
+
for (const child of children) {
|
|
297
|
+
if (child.status !== "pending") continue;
|
|
298
|
+
const row = await getDbClient().get<{ id: string }>(
|
|
299
|
+
"UPDATE workflow_run_steps SET status = 'cancelled', error = ?, finishedAt = ? WHERE id = ? AND status = 'pending' RETURNING id",
|
|
300
|
+
[error, finishedAt, child.id],
|
|
301
|
+
);
|
|
302
|
+
if (row) cancelled++;
|
|
303
|
+
}
|
|
304
|
+
});
|
|
305
|
+
return cancelled;
|
|
306
|
+
}
|
|
307
|
+
|
|
308
|
+
async function hasQueuedChild(
|
|
309
|
+
def: WorkflowDefinition,
|
|
310
|
+
runId: string,
|
|
311
|
+
parentNodeId: string,
|
|
312
|
+
): Promise<boolean> {
|
|
313
|
+
return latestForeachChildren(def, parentNodeId, await getWorkflowRunStepsByRunId(runId)).some(
|
|
314
|
+
(child) => child.status === "pending",
|
|
315
|
+
);
|
|
316
|
+
}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import type { WorkflowNode } from "../types";
|
|
2
|
+
|
|
3
|
+
/** Watchdog budget for a node that configures none. */
|
|
4
|
+
export const DEFAULT_NODE_TIMEOUT_MS = 30_000;
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* The workflow watchdog budget for one executor call of `node`.
|
|
8
|
+
*
|
|
9
|
+
* Inline script nodes historically expose their wall-clock budget as
|
|
10
|
+
* `config.timeout`, while the workflow watchdog reads `config.timeoutMs` for
|
|
11
|
+
* every other executor. Keep the outer watchdog aligned with the script
|
|
12
|
+
* executor so a script configured for (for example) 90s is not killed by the
|
|
13
|
+
* 30s default first. The foreach driver applies this to each child with the
|
|
14
|
+
* synthesized body node, so every item gets its own budget.
|
|
15
|
+
*/
|
|
16
|
+
export function resolveNodeTimeoutMs(node: Pick<WorkflowNode, "type" | "config">): number {
|
|
17
|
+
const config = node.config ?? {};
|
|
18
|
+
if (typeof config.timeoutMs === "number") return config.timeoutMs;
|
|
19
|
+
if (node.type === "script" && typeof config.timeout === "number") return config.timeout;
|
|
20
|
+
return DEFAULT_NODE_TIMEOUT_MS;
|
|
21
|
+
}
|
|
@@ -24,6 +24,8 @@ import {
|
|
|
24
24
|
walkGraph,
|
|
25
25
|
} from "./engine";
|
|
26
26
|
import type { ExecutorRegistry } from "./executors/registry";
|
|
27
|
+
import { stepFailureMode } from "./foreach-body";
|
|
28
|
+
import { isForeachChildOwned, runForeachDrive } from "./foreach-driver";
|
|
27
29
|
import { getSecretInputKeys } from "./input";
|
|
28
30
|
import {
|
|
29
31
|
finalizeOrWait,
|
|
@@ -71,6 +73,9 @@ export async function recoverIncompleteRuns(registry: ExecutorRegistry): Promise
|
|
|
71
73
|
}
|
|
72
74
|
}
|
|
73
75
|
|
|
76
|
+
// --- Case 6: foreach fan-outs with queued or orphaned items ---
|
|
77
|
+
recovered += await recoverForeachFanouts(registry);
|
|
78
|
+
|
|
74
79
|
if (recovered > 0) {
|
|
75
80
|
console.log(`[workflows] Recovered ${recovered} incomplete run(s)`);
|
|
76
81
|
}
|
|
@@ -211,20 +216,22 @@ async function recoverWaitingRuns(registry: ExecutorRegistry): Promise<number> {
|
|
|
211
216
|
if (retry === "scheduled") recovered++;
|
|
212
217
|
if (retry !== "not-eligible") continue;
|
|
213
218
|
}
|
|
214
|
-
if (!taskCompleted && (workflow.definition.
|
|
219
|
+
if (!taskCompleted && stepFailureMode(workflow.definition, stuck.nodeId) === "fail") {
|
|
215
220
|
// Preserve the fail-fast recovery policy for failed/cancelled tasks.
|
|
216
221
|
// Claimed: this sweep runs on every heartbeat, so the live task.failed
|
|
217
222
|
// bus event may already have routed this step — a blind write here
|
|
218
223
|
// would kill a run that is already advancing.
|
|
219
224
|
const reason =
|
|
220
225
|
stuck.taskStatus === "failed" ? "Task failed (recovered)" : "Task cancelled (recovered)";
|
|
221
|
-
const
|
|
226
|
+
const failed = await failStepAndRunIfWaiting(
|
|
227
|
+
workflow.definition,
|
|
222
228
|
stuck.stepId,
|
|
223
229
|
stuck.runId,
|
|
224
230
|
reason,
|
|
225
231
|
stuck.taskId,
|
|
232
|
+
{ registry },
|
|
226
233
|
);
|
|
227
|
-
if (!claimed) continue;
|
|
234
|
+
if (!failed.claimed) continue;
|
|
228
235
|
recovered++;
|
|
229
236
|
continue;
|
|
230
237
|
}
|
|
@@ -248,6 +255,7 @@ async function recoverWaitingRuns(registry: ExecutorRegistry): Promise<number> {
|
|
|
248
255
|
ctx,
|
|
249
256
|
taskCompleted ? undefined : reason,
|
|
250
257
|
stuck.taskId,
|
|
258
|
+
registry,
|
|
251
259
|
);
|
|
252
260
|
if (!routing.claimed) continue;
|
|
253
261
|
if (routing.foreachChild && !routing.joined) {
|
|
@@ -351,6 +359,7 @@ async function recoverApprovalWaitingRuns(registry: ExecutorRegistry): Promise<n
|
|
|
351
359
|
stepOutput,
|
|
352
360
|
nextPort,
|
|
353
361
|
ctx,
|
|
362
|
+
registry,
|
|
354
363
|
);
|
|
355
364
|
});
|
|
356
365
|
if (!routing?.claimed) continue;
|
|
@@ -413,6 +422,62 @@ async function recoverWaitStates(registry: ExecutorRegistry): Promise<number> {
|
|
|
413
422
|
return recovered;
|
|
414
423
|
}
|
|
415
424
|
|
|
425
|
+
/**
|
|
426
|
+
* Re-drive foreach fan-outs of live runs whose parent is still open and that
|
|
427
|
+
* have a queued (`pending`) item, or a `running` item no drive in this process
|
|
428
|
+
* owns. Catches a crash after a settlement committed and before the next
|
|
429
|
+
* drive, and items a crash left between their claim and their settlement. An
|
|
430
|
+
* orphaned instant item runs again (at-least-once, like a top-level instant
|
|
431
|
+
* node); an async item reconnects, because its executor dedupes on the child
|
|
432
|
+
* step id.
|
|
433
|
+
*/
|
|
434
|
+
async function recoverForeachFanouts(registry: ExecutorRegistry): Promise<number> {
|
|
435
|
+
const rows = await getDbClient().query<{
|
|
436
|
+
runId: string;
|
|
437
|
+
workflowId: string;
|
|
438
|
+
stepId: string;
|
|
439
|
+
nodeId: string;
|
|
440
|
+
status: string;
|
|
441
|
+
}>(
|
|
442
|
+
`SELECT s.runId AS runId, r.workflowId AS workflowId, s.id AS stepId, s.nodeId AS nodeId, s.status AS status
|
|
443
|
+
FROM workflow_run_steps s
|
|
444
|
+
JOIN workflow_runs r ON r.id = s.runId
|
|
445
|
+
WHERE r.status IN ('running', 'waiting')
|
|
446
|
+
AND s.status IN ('pending', 'running')
|
|
447
|
+
AND instr(s.nodeId, '#') > 1`,
|
|
448
|
+
);
|
|
449
|
+
const fanouts = new Map<string, { runId: string; workflowId: string; parentNodeId: string }>();
|
|
450
|
+
for (const row of rows) {
|
|
451
|
+
if (row.status === "running" && isForeachChildOwned(row.stepId)) continue;
|
|
452
|
+
const parentNodeId = row.nodeId.slice(0, row.nodeId.indexOf("#"));
|
|
453
|
+
fanouts.set(`${row.runId}\u0000${parentNodeId}`, {
|
|
454
|
+
runId: row.runId,
|
|
455
|
+
workflowId: row.workflowId,
|
|
456
|
+
parentNodeId,
|
|
457
|
+
});
|
|
458
|
+
}
|
|
459
|
+
|
|
460
|
+
let recovered = 0;
|
|
461
|
+
for (const fanout of fanouts.values()) {
|
|
462
|
+
try {
|
|
463
|
+
// The drive re-checks the run, the parent step and each child's latest
|
|
464
|
+
// row, so a stale snapshot row claims nothing.
|
|
465
|
+
const workflow = await getWorkflow(fanout.workflowId);
|
|
466
|
+
const parent = workflow?.definition.nodes.find((node) => node.id === fanout.parentNodeId);
|
|
467
|
+
if (parent?.type !== "foreach") continue;
|
|
468
|
+
const drive = await runForeachDrive(fanout.runId, fanout.parentNodeId, registry);
|
|
469
|
+
if (drive.claimed > 0) recovered++;
|
|
470
|
+
} catch (err) {
|
|
471
|
+
if (await failRunOnUnreadableReplay(fanout.runId, err)) continue;
|
|
472
|
+
console.error(
|
|
473
|
+
`[workflows] Failed to re-drive foreach ${fanout.parentNodeId} of run ${fanout.runId}:`,
|
|
474
|
+
err,
|
|
475
|
+
);
|
|
476
|
+
}
|
|
477
|
+
}
|
|
478
|
+
return recovered;
|
|
479
|
+
}
|
|
480
|
+
|
|
416
481
|
/**
|
|
417
482
|
* Get run IDs by status. Simple query since there's no dedicated function for this.
|
|
418
483
|
*/
|