@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
|
@@ -1,19 +1,22 @@
|
|
|
1
1
|
import { z } from "zod";
|
|
2
|
+
import { createForeachChildSteps } from "../../be/db/workflow-runs";
|
|
2
3
|
import type { ExecutorMeta } from "../../types";
|
|
3
|
-
import {
|
|
4
|
+
import { FOREACH_ITEMS_WAIT_FOR } from "../constants";
|
|
4
5
|
import { getSuccessors } from "../definition";
|
|
6
|
+
import { foreachBodyTypeError } from "../foreach-body";
|
|
5
7
|
import {
|
|
6
8
|
buildForeachAggregate,
|
|
7
9
|
FOREACH_TERMINAL_STEP_STATUSES,
|
|
8
10
|
parseSyntheticNodeId,
|
|
9
11
|
} from "../foreach-join";
|
|
10
12
|
import { getMaxWorkflowStepsPerRun } from "../limits";
|
|
11
|
-
import { AgentTaskExecutor } from "./agent-task";
|
|
12
13
|
import type { AsyncExecutorResult, ExecutorResult } from "./base";
|
|
13
14
|
import { BaseExecutor } from "./base";
|
|
14
15
|
|
|
16
|
+
// Any registered executor except `foreach` (see foreach-body.ts); the guard in
|
|
17
|
+
// execute() and definition validation enforce the policy.
|
|
15
18
|
const ForeachBodySchema = z.object({
|
|
16
|
-
type: z.
|
|
19
|
+
type: z.string().min(1),
|
|
17
20
|
config: z.record(z.string(), z.unknown()),
|
|
18
21
|
});
|
|
19
22
|
|
|
@@ -21,7 +24,12 @@ export const ForeachConfigSchema = z.object({
|
|
|
21
24
|
over: z.array(z.record(z.string(), z.unknown())),
|
|
22
25
|
itemKey: z.string().min(1),
|
|
23
26
|
body: ForeachBodySchema,
|
|
24
|
-
|
|
27
|
+
/** Items in flight at once (`running` or `waiting` children). Unbounded when absent. */
|
|
28
|
+
concurrency: z.number().int().min(1).optional(),
|
|
29
|
+
/** Defaults from the workflow `onNodeFailure` (see effectiveFailurePolicy). */
|
|
30
|
+
// Literal, not FOREACH_FAILURE_POLICIES: foreach-body.ts sits in an import
|
|
31
|
+
// cycle with this module and would be read before it initializes.
|
|
32
|
+
failurePolicy: z.enum(["fail-fast", "collect-outcomes"]).optional(),
|
|
25
33
|
});
|
|
26
34
|
|
|
27
35
|
const ForeachResultSchema = z.object({
|
|
@@ -34,6 +42,8 @@ export const ForeachOutputSchema = z.object({
|
|
|
34
42
|
results: z.array(ForeachResultSchema),
|
|
35
43
|
okCount: z.number().int().nonnegative(),
|
|
36
44
|
failedCount: z.number().int().nonnegative(),
|
|
45
|
+
/** Items cancelled before they finished (fail-fast queued items, cancelled bodies). Counted in failedCount too. */
|
|
46
|
+
cancelledCount: z.number().int().nonnegative().optional(),
|
|
37
47
|
});
|
|
38
48
|
|
|
39
49
|
export type ForeachOutput = z.infer<typeof ForeachOutputSchema>;
|
|
@@ -49,7 +59,7 @@ export class ForeachExecutor extends BaseExecutor<
|
|
|
49
59
|
|
|
50
60
|
protected async execute(
|
|
51
61
|
config: z.infer<typeof ForeachConfigSchema>,
|
|
52
|
-
|
|
62
|
+
_context: Readonly<Record<string, unknown>>,
|
|
53
63
|
meta: ExecutorMeta,
|
|
54
64
|
): Promise<ExecutorResult<ForeachOutput>> {
|
|
55
65
|
if (config.over.length === 0) {
|
|
@@ -77,16 +87,19 @@ export class ForeachExecutor extends BaseExecutor<
|
|
|
77
87
|
seenItemKeys.add(itemKey);
|
|
78
88
|
}
|
|
79
89
|
|
|
80
|
-
const
|
|
90
|
+
const bodyTypeError = foreachBodyTypeError(meta.nodeId, config.body.type);
|
|
91
|
+
if (bodyTypeError) return { status: "failed", error: bodyTypeError };
|
|
92
|
+
|
|
81
93
|
const runSteps = await this.deps.db.getWorkflowRunStepsByRunId(meta.runId);
|
|
82
|
-
const
|
|
94
|
+
const existingChildNodeIds = new Set(
|
|
83
95
|
runSteps
|
|
84
96
|
.filter((step) => parseSyntheticNodeId(step.nodeId)?.parentNodeId === meta.nodeId)
|
|
85
|
-
.map((step) =>
|
|
97
|
+
.map((step) => step.nodeId),
|
|
98
|
+
);
|
|
99
|
+
const missingItems = items.filter(
|
|
100
|
+
({ itemKey }) => !existingChildNodeIds.has(`${meta.nodeId}#${itemKey}`),
|
|
86
101
|
);
|
|
87
|
-
const childCountToCreate =
|
|
88
|
-
({ itemKey }) => !childStepsByNodeId.has(`${meta.nodeId}#${itemKey}`),
|
|
89
|
-
).length;
|
|
102
|
+
const childCountToCreate = missingItems.length;
|
|
90
103
|
const maxSteps = getMaxWorkflowStepsPerRun();
|
|
91
104
|
// walkGraph's circuit breaker rejects any post-join walk once allSteps.length
|
|
92
105
|
// >= maxSteps, so a fan-out with a successor that lands exactly on the cap would
|
|
@@ -107,79 +120,26 @@ export class ForeachExecutor extends BaseExecutor<
|
|
|
107
120
|
};
|
|
108
121
|
}
|
|
109
122
|
|
|
110
|
-
// Materialize EVERY child
|
|
111
|
-
//
|
|
112
|
-
// later children are still being set up
|
|
113
|
-
// child set
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
input: { itemKey, index },
|
|
124
|
-
});
|
|
125
|
-
await this.deps.db.updateWorkflowRunStep(childStepId, {
|
|
126
|
-
idempotencyKey: `${meta.runId}:${childNodeId}:0`,
|
|
127
|
-
});
|
|
128
|
-
childStepsByNodeId.set(childNodeId, childStep);
|
|
129
|
-
}
|
|
130
|
-
|
|
131
|
-
for (const { item, index, itemKey } of items) {
|
|
132
|
-
const childNodeId = `${meta.nodeId}#${itemKey}`;
|
|
133
|
-
const childStep = childStepsByNodeId.get(childNodeId)!;
|
|
134
|
-
|
|
135
|
-
// Re-walks must not regress terminal children or disturb their aggregate
|
|
136
|
-
// inputs. Retry resets only the failed child to pending before arriving here.
|
|
137
|
-
if (FOREACH_TERMINAL_STEP_STATUSES.has(childStep.status)) continue;
|
|
138
|
-
|
|
139
|
-
// The deferred per-item pass interpolates against the node's declared-inputs
|
|
140
|
-
// context (meta.inputCtx) plus item/index — NOT the full run context — so
|
|
141
|
-
// body.config obeys the same explicit-dataflow boundary as normal node
|
|
142
|
-
// config instead of silently reading undeclared upstream outputs.
|
|
143
|
-
const bodyCtx = { ...(meta.inputCtx ?? context), item, index };
|
|
144
|
-
const childInterpolation = deepInterpolate(config.body.config, bodyCtx);
|
|
145
|
-
if (childInterpolation.unresolved.length > 0) {
|
|
146
|
-
console.warn(
|
|
147
|
-
`[workflow] Step ${childNodeId}: unresolved interpolation tokens: ${childInterpolation.unresolved.join(", ")}`,
|
|
148
|
-
);
|
|
149
|
-
await this.deps.db.updateWorkflowRunStep(childStep.id, {
|
|
150
|
-
diagnostics: JSON.stringify({ unresolvedTokens: childInterpolation.unresolved }),
|
|
151
|
-
});
|
|
152
|
-
}
|
|
153
|
-
|
|
154
|
-
const linkedTask = await this.deps.db.getTaskByWorkflowRunStepId(childStep.id);
|
|
155
|
-
if (linkedTask?.status === "failed" || linkedTask?.status === "cancelled") {
|
|
156
|
-
// A retry needs a fresh task. Detaching the stale terminal task keeps the
|
|
157
|
-
// executor's one-task-per-step de-duplication deterministic and scoped.
|
|
158
|
-
await this.deps.db.detachTaskFromWorkflowRunStep(linkedTask.id);
|
|
159
|
-
}
|
|
160
|
-
|
|
161
|
-
const childResult = await agentTaskExecutor.run({
|
|
162
|
-
config: childInterpolation.value as Record<string, unknown>,
|
|
163
|
-
context,
|
|
164
|
-
meta: {
|
|
165
|
-
...meta,
|
|
166
|
-
stepId: childStep.id,
|
|
123
|
+
// Materialize EVERY child row, queued (`pending`), in one transaction and
|
|
124
|
+
// before any child starts. An early child can complete (and trigger the
|
|
125
|
+
// join) while later children are still being set up, so the join must
|
|
126
|
+
// always see the full child set. The item value is not persisted: the
|
|
127
|
+
// driver looks it up by key in the re-interpolated `over`, which keeps
|
|
128
|
+
// resolved secrets out of step rows.
|
|
129
|
+
await createForeachChildSteps(
|
|
130
|
+
meta.runId,
|
|
131
|
+
meta.nodeId,
|
|
132
|
+
missingItems.map(({ index, itemKey }) => {
|
|
133
|
+
const childNodeId = `${meta.nodeId}#${itemKey}`;
|
|
134
|
+
return {
|
|
135
|
+
id: crypto.randomUUID(),
|
|
167
136
|
nodeId: childNodeId,
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
}
|
|
173
|
-
|
|
174
|
-
await this.deps.db.updateWorkflowRunStep(childStep.id, { status: "waiting" });
|
|
175
|
-
} else {
|
|
176
|
-
await this.deps.db.updateWorkflowRunStep(childStep.id, {
|
|
177
|
-
status: "completed",
|
|
178
|
-
output: normalizeExistingTaskOutput(childResult.output),
|
|
179
|
-
finishedAt: new Date().toISOString(),
|
|
180
|
-
});
|
|
181
|
-
}
|
|
182
|
-
}
|
|
137
|
+
nodeType: config.body.type,
|
|
138
|
+
input: { itemKey, index, attempt: 0 },
|
|
139
|
+
idempotencyKey: `${meta.runId}:${childNodeId}:0`,
|
|
140
|
+
};
|
|
141
|
+
}),
|
|
142
|
+
);
|
|
183
143
|
|
|
184
144
|
const childSteps = (await this.deps.db.getWorkflowRunStepsByRunId(meta.runId)).filter(
|
|
185
145
|
(step) => parseSyntheticNodeId(step.nodeId)?.parentNodeId === meta.nodeId,
|
|
@@ -188,26 +148,12 @@ export class ForeachExecutor extends BaseExecutor<
|
|
|
188
148
|
return { status: "success", output: buildForeachAggregate(childSteps) };
|
|
189
149
|
}
|
|
190
150
|
|
|
151
|
+
// The engine parks the parent and the foreach driver starts the children.
|
|
191
152
|
return {
|
|
192
153
|
status: "success",
|
|
193
154
|
async: true,
|
|
194
|
-
waitFor:
|
|
155
|
+
waitFor: FOREACH_ITEMS_WAIT_FOR,
|
|
195
156
|
correlationId: meta.stepId,
|
|
196
157
|
} as AsyncExecutorResult<ForeachOutput>;
|
|
197
158
|
}
|
|
198
159
|
}
|
|
199
|
-
|
|
200
|
-
function normalizeExistingTaskOutput(output: unknown): unknown {
|
|
201
|
-
if (typeof output !== "object" || output === null) return output;
|
|
202
|
-
const record = output as Record<string, unknown>;
|
|
203
|
-
if (typeof record.taskOutput !== "string") return output;
|
|
204
|
-
try {
|
|
205
|
-
const parsed = JSON.parse(record.taskOutput);
|
|
206
|
-
if (typeof parsed === "object" && parsed !== null) {
|
|
207
|
-
return { ...record, taskOutput: parsed };
|
|
208
|
-
}
|
|
209
|
-
} catch {
|
|
210
|
-
// Non-structured task output remains a string.
|
|
211
|
-
}
|
|
212
|
-
return output;
|
|
213
|
-
}
|
|
@@ -0,0 +1,146 @@
|
|
|
1
|
+
import type { WorkflowDefinition, WorkflowNode } from "../types";
|
|
2
|
+
import type { ExecutorRegistry } from "./executors/registry";
|
|
3
|
+
import { resolveForeachParent } from "./foreach-join";
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Executor types a `foreach` body must not use, with the reason. Every other
|
|
7
|
+
* registered executor is a valid body.
|
|
8
|
+
*/
|
|
9
|
+
export const FOREACH_BODY_REJECTED: Readonly<Record<string, string>> = {
|
|
10
|
+
foreach: "nested fan-out is not supported: synthetic ids and the join resolve one level only",
|
|
11
|
+
};
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* Built-in executors accepted as a `foreach` body, by how one item completes:
|
|
15
|
+
* `inline` items are run and settled by the foreach driver; `parks` items wait
|
|
16
|
+
* on their own resume path (task, child run, approval, wait), which settles
|
|
17
|
+
* the item through `settleForeachChild`. A `system-one-decision` without
|
|
18
|
+
* `humanReview` returns inline.
|
|
19
|
+
*
|
|
20
|
+
* Every built-in executor must appear here or in FOREACH_BODY_REJECTED; the
|
|
21
|
+
* registry exhaustiveness test enforces that a new executor gets a decision.
|
|
22
|
+
*/
|
|
23
|
+
export const FOREACH_BODY_ACCEPTED: Readonly<Record<string, "inline" | "parks">> = {
|
|
24
|
+
script: "inline",
|
|
25
|
+
"swarm-script": "inline",
|
|
26
|
+
"raw-llm": "inline",
|
|
27
|
+
notify: "inline",
|
|
28
|
+
"property-match": "inline",
|
|
29
|
+
"code-match": "inline",
|
|
30
|
+
validate: "inline",
|
|
31
|
+
vcs: "inline",
|
|
32
|
+
"system-one-extraction": "inline",
|
|
33
|
+
"agent-task": "parks",
|
|
34
|
+
"sub-workflow": "parks",
|
|
35
|
+
"human-in-the-loop": "parks",
|
|
36
|
+
wait: "parks",
|
|
37
|
+
"system-one-decision": "parks",
|
|
38
|
+
};
|
|
39
|
+
|
|
40
|
+
/**
|
|
41
|
+
* True when `type` may be a foreach body: not rejected, and registered (or a
|
|
42
|
+
* known built-in when no registry is passed).
|
|
43
|
+
*/
|
|
44
|
+
export function isForeachBodyType(type: string, registry?: ExecutorRegistry): boolean {
|
|
45
|
+
if (Object.hasOwn(FOREACH_BODY_REJECTED, type)) return false;
|
|
46
|
+
return registry ? registry.has(type) : Object.hasOwn(FOREACH_BODY_ACCEPTED, type);
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
/**
|
|
50
|
+
* The validation error for a foreach body type, or null when it is allowed.
|
|
51
|
+
* Without a registry only the rejection list applies, so a custom executor
|
|
52
|
+
* registered at run time is not refused by a registry-less validation pass.
|
|
53
|
+
*/
|
|
54
|
+
export function foreachBodyTypeError(
|
|
55
|
+
nodeId: string,
|
|
56
|
+
type: string,
|
|
57
|
+
registry?: ExecutorRegistry,
|
|
58
|
+
): string | null {
|
|
59
|
+
const reason = FOREACH_BODY_REJECTED[type];
|
|
60
|
+
if (reason !== undefined) {
|
|
61
|
+
return `Node "${nodeId}" foreach config.body.type "${type}" is not supported: ${reason} (a ${type} body inside foreach "${nodeId}" is rejected)`;
|
|
62
|
+
}
|
|
63
|
+
if (registry && !registry.has(type)) {
|
|
64
|
+
return `Node "${nodeId}" foreach config.body.type "${type}" is not a registered executor`;
|
|
65
|
+
}
|
|
66
|
+
return null;
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
/**
|
|
70
|
+
* The definition node a step row executes. A real node wins. A synthetic
|
|
71
|
+
* foreach child id (`<foreachId>#<itemKey>`) resolves to a node synthesized
|
|
72
|
+
* from the parent's body: the child's id, the body's type and config, and the
|
|
73
|
+
* parent's declared `inputs` (the body sees the parent's input context, and the
|
|
74
|
+
* script-body allowlist classifies those aliases by their source).
|
|
75
|
+
*/
|
|
76
|
+
export function resolveStepNode(
|
|
77
|
+
def: Pick<WorkflowDefinition, "nodes">,
|
|
78
|
+
nodeId: string,
|
|
79
|
+
): WorkflowNode | null {
|
|
80
|
+
const node = def.nodes.find((candidate) => candidate.id === nodeId);
|
|
81
|
+
if (node) return node;
|
|
82
|
+
const parent = resolveForeachParent(def, nodeId);
|
|
83
|
+
if (!parent) return null;
|
|
84
|
+
const body = parent.config.body;
|
|
85
|
+
if (!isRecord(body) || typeof body.type !== "string") return null;
|
|
86
|
+
// `item` and `index` are bound per item, so a parent alias with either name
|
|
87
|
+
// is dropped: it would otherwise classify item data by the alias's source
|
|
88
|
+
// and let it past the script-body allowlist.
|
|
89
|
+
const inputs = parent.inputs
|
|
90
|
+
? Object.fromEntries(
|
|
91
|
+
Object.entries(parent.inputs).filter(([alias]) => !FOREACH_ITEM_BINDINGS.has(alias)),
|
|
92
|
+
)
|
|
93
|
+
: undefined;
|
|
94
|
+
return {
|
|
95
|
+
id: nodeId,
|
|
96
|
+
type: body.type,
|
|
97
|
+
config: isRecord(body.config) ? body.config : {},
|
|
98
|
+
...(inputs ? { inputs } : {}),
|
|
99
|
+
};
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
const FOREACH_ITEM_BINDINGS = new Set(["item", "index"]);
|
|
103
|
+
|
|
104
|
+
export const FOREACH_FAILURE_POLICIES = ["fail-fast", "collect-outcomes"] as const;
|
|
105
|
+
export type ForeachFailurePolicy = (typeof FOREACH_FAILURE_POLICIES)[number];
|
|
106
|
+
|
|
107
|
+
/**
|
|
108
|
+
* How a failed or cancelled item affects its fan-out. `config.failurePolicy`
|
|
109
|
+
* wins; otherwise it follows the workflow's `onNodeFailure`, so a definition
|
|
110
|
+
* without the field behaves as before: `"continue"` collects outcomes, the
|
|
111
|
+
* default `"fail"` is fail-fast.
|
|
112
|
+
*/
|
|
113
|
+
export function effectiveFailurePolicy(
|
|
114
|
+
node: WorkflowNode,
|
|
115
|
+
def: Pick<WorkflowDefinition, "onNodeFailure">,
|
|
116
|
+
): ForeachFailurePolicy {
|
|
117
|
+
const configured = node.config.failurePolicy;
|
|
118
|
+
if (configured === "fail-fast" || configured === "collect-outcomes") return configured;
|
|
119
|
+
return def.onNodeFailure === "continue" ? "collect-outcomes" : "fail-fast";
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
/** How many items of a fan-out may be in flight at once. Unbounded by default. */
|
|
123
|
+
export function foreachConcurrencyCap(node: WorkflowNode): number {
|
|
124
|
+
const cap = node.config.concurrency;
|
|
125
|
+
return typeof cap === "number" && Number.isInteger(cap) && cap >= 1
|
|
126
|
+
? cap
|
|
127
|
+
: Number.POSITIVE_INFINITY;
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
/**
|
|
131
|
+
* The failure mode a foreach child's task failure takes: the fan-out's
|
|
132
|
+
* effective policy for a synthetic child, the workflow's `onNodeFailure`
|
|
133
|
+
* otherwise.
|
|
134
|
+
*/
|
|
135
|
+
export function stepFailureMode(
|
|
136
|
+
def: Pick<WorkflowDefinition, "nodes" | "onNodeFailure">,
|
|
137
|
+
nodeId: string,
|
|
138
|
+
): "fail" | "continue" {
|
|
139
|
+
const parent = resolveForeachParent(def, nodeId);
|
|
140
|
+
if (parent) return effectiveFailurePolicy(parent, def) === "fail-fast" ? "fail" : "continue";
|
|
141
|
+
return def.onNodeFailure ?? "fail";
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
function isRecord(value: unknown): value is Record<string, unknown> {
|
|
145
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
146
|
+
}
|