@desplega.ai/agent-swarm 2.5.3 → 2.6.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 +2 -2
- package/dist/{acp-adapter-gr2fcyyj.js → acp-adapter-dgfakfpw.js} +9 -9
- package/dist/{actions-wv766ghf.js → actions-fbqma8aa.js} +21 -21
- package/dist/{amp-adapter-speb906t.js → amp-adapter-en0jy6hs.js} +4 -4
- package/dist/{app-ag29n14f.js → app-k8a5cgpp.js} +3 -3
- package/dist/{assistant-3x8bz3eg.js → assistant-a16433gr.js} +30 -26
- package/dist/{boot-reembed-jsmfrpzf.js → boot-reembed-6c1sjfnr.js} +1 -1
- package/dist/{boot-reembed-5b3d2eea.js → boot-reembed-f4nn561n.js} +1 -1
- package/dist/{boot-scrub-sweep-s73gerxy.js → boot-scrub-sweep-d4w9gajf.js} +4 -4
- package/dist/{boot-seeds-5zmax2wr.js → boot-seeds-9bmkspyw.js} +4 -4
- package/dist/{claude-adapter-hswdnm3t.js → claude-adapter-nys1kjxa.js} +9 -9
- package/dist/{claude-managed-adapter-cvbzj1h6.js → claude-managed-adapter-55207973.js} +3 -3
- package/dist/{claude-managed-setup-3k8m3hhc.js → claude-managed-setup-3cm35gey.js} +11 -4
- package/dist/{claude-sdk-session-fkbyk66x.js → claude-sdk-session-nw5yd8cp.js} +9313 -9260
- package/dist/{cli-210xd1f4.js → cli-03gg9pd1.js} +4 -4
- package/dist/{cli-kxpfx2xr.js → cli-0z1xjzwa.js} +2 -2
- package/dist/cli-1tem6ns9.js +894 -0
- package/dist/{cli-25j01dpj.js → cli-24j1jeeb.js} +3 -3
- package/dist/{cli-ta8bd7th.js → cli-2sjhpsns.js} +2 -2
- package/dist/{cli-p859hcc8.js → cli-39aet655.js} +2 -2
- package/dist/{cli-g1cw7xtt.js → cli-39rrw8yp.js} +1 -1
- package/dist/{cli-e2ar4bp1.js → cli-3aqd4jaa.js} +121 -47
- package/dist/{cli-jjznk3f7.js → cli-3fwqy71j.js} +69 -35
- package/dist/{cli-p9rafdrb.js → cli-3gfvmzht.js} +7 -7
- package/dist/{cli-03vpq45j.js → cli-3ndmpxxv.js} +2 -2
- package/dist/{cli-aecdcr77.js → cli-4hxrf0p4.js} +228 -74
- package/dist/{cli-mz9afxxf.js → cli-4rf0f5vg.js} +15 -3
- package/dist/{cli-qzme5949.js → cli-5rj83bam.js} +284 -202
- package/dist/{cli-fsdprwrg.js → cli-5sckpqcx.js} +2 -2
- package/dist/{cli-cz5nmdxt.js → cli-683y0b3a.js} +2 -2
- package/dist/{cli-3ngdk5te.js → cli-6y51ryva.js} +2 -2
- package/dist/{cli-n4c8e3s5.js → cli-708wdy32.js} +1 -1
- package/dist/{cli-pasyvjmm.js → cli-737pyqxr.js} +1 -1
- package/dist/{cli-gx4rm0qx.js → cli-7enjwya9.js} +1 -1
- package/dist/{cli-6x9ma5w5.js → cli-803188pb.js} +1 -1
- package/dist/{cli-avdvgcay.js → cli-9abdgn5t.js} +28 -9
- package/dist/{cli-99xgw26r.js → cli-a2rr0fmg.js} +1 -1
- package/dist/{cli-e5bxnjps.js → cli-a34pr3w1.js} +7 -7
- package/dist/{cli-502eqv93.js → cli-abkm71vr.js} +1 -1
- package/dist/{cli-mmhj8vd7.js → cli-ag2yrnt5.js} +11 -2
- package/dist/{cli-n45mr7zh.js → cli-aj8djyqt.js} +5 -5
- package/dist/{cli-1fc85mvt.js → cli-art7c1zh.js} +5 -5
- package/dist/{cli-40jsnrjm.js → cli-az25fn5f.js} +10 -8
- package/dist/{cli-9s05422w.js → cli-bqztfyve.js} +1 -1
- package/dist/{cli-bn0rewsy.js → cli-c081bj3b.js} +2 -2
- package/dist/{cli-svrj71pg.js → cli-c2habvsf.js} +12 -12
- package/dist/{cli-s49hpn37.js → cli-ca6banwz.js} +85 -19
- package/dist/{cli-hhxjnjzs.js → cli-cz37jqss.js} +12 -10
- package/dist/{cli-6n8axjv1.js → cli-d2dpf95n.js} +2 -2
- package/dist/{cli-tx00c0h6.js → cli-dqygeeh5.js} +17 -1
- package/dist/{cli-z21whrrg.js → cli-emhjcj2y.js} +17 -17
- package/dist/{cli-e8jyq7d8.js → cli-esakngh8.js} +7 -3
- package/dist/{cli-ha9rmwgq.js → cli-fb8m699c.js} +2 -2
- package/dist/{cli-35gtk1p6.js → cli-ffvsj2qw.js} +44 -13
- package/dist/{cli-ce0ak9ms.js → cli-g20jjwt4.js} +26 -26
- package/dist/{cli-kxawjrxg.js → cli-g8ka5j4j.js} +772 -194
- package/dist/{cli-vwn81jbs.js → cli-gf95q3bm.js} +5 -5
- package/dist/{cli-s53vp709.js → cli-gnsb1zxa.js} +2 -2
- package/dist/{cli-nwyw3zhg.js → cli-gxee0bqa.js} +1 -1
- package/dist/{cli-gpv60278.js → cli-hfeb68z4.js} +7 -7
- package/dist/{cli-0ebey9hg.js → cli-j6rktc91.js} +1 -1
- package/dist/{cli-ewm3pwbq.js → cli-k21gx5ht.js} +46 -23
- package/dist/{cli-egjggehm.js → cli-kf4jree9.js} +2 -2
- package/dist/{cli-dabehxhp.js → cli-kt907da6.js} +1 -1
- package/dist/{cli-p0rzjkya.js → cli-m5a9m0ak.js} +32 -13
- package/dist/{cli-fzcn3d87.js → cli-m70bajzr.js} +3 -3
- package/dist/{cli-w1zwctj2.js → cli-mtnj4fwa.js} +11 -5
- package/dist/{cli-gpsz7g88.js → cli-mvjz5an3.js} +16 -12
- package/dist/{cli-hcpj2xqz.js → cli-n7asmce6.js} +225 -3
- package/dist/{cli-y561h5a9.js → cli-nn6vx2mq.js} +5 -5
- package/dist/cli-nq677csy.js +140 -0
- package/dist/{cli-a5ebc95n.js → cli-pfk92eqz.js} +18 -15
- package/dist/{cli-p6zs9h4y.js → cli-rr7zx19j.js} +767 -249
- package/dist/{cli-qgf8emrn.js → cli-sqma6vw1.js} +10 -7
- package/dist/{cli-0xk505e8.js → cli-sxgtf5fw.js} +1 -1
- package/dist/{cli-nzfnt2gs.js → cli-tf1n0r0b.js} +1 -1
- package/dist/{cli-qqzmjxkf.js → cli-v49fkghp.js} +11 -6
- package/dist/{cli-x77gen2p.js → cli-w2656ka1.js} +7 -7
- package/dist/{cli-5kqnemen.js → cli-yvy48785.js} +1 -1
- package/dist/{cli-8s5wx3nz.js → cli-zqne1qcj.js} +44 -17
- package/dist/cli.js +22 -22
- package/dist/{codex-adapter-n8ka66vf.js → codex-adapter-d95q4qsn.js} +7 -7
- package/dist/{codex-hook-e2he7aca.js → codex-hook-brpnycn5.js} +1 -1
- package/dist/{codex-login-rn7c5gxt.js → codex-login-8cg0vhf2.js} +1 -1
- package/dist/{codex-session-runner-fsebts37.js → codex-session-runner-gdwd8h0c.js} +8 -8
- package/dist/{commands-t04rt2pc.js → commands-jqmgk54b.js} +2 -2
- package/dist/{controller-rgna04r2.js → controller-z3jq589f.js} +30 -4
- package/dist/{controllers-connect-bqm1rtft.js → controllers-connect-r2za39ef.js} +23 -23
- package/dist/{cursor-adapter-swr2k95y.js → cursor-adapter-4mr4jgkw.js} +2 -3
- package/dist/{db-a4m7haw0.js → db-ppv2tze7.js} +3 -3
- package/dist/{db-24ck09xy.js → db-w36wwb5g.js} +4 -2
- package/dist/{devin-adapter-4ycy4vrf.js → devin-adapter-byen2cxt.js} +1 -1
- package/dist/{dispatcher-nwf9ss0q.js → dispatcher-pv5h77vf.js} +20 -20
- package/dist/{docker-nfy2gc8t.js → docker-fdzyewpz.js} +4 -4
- package/dist/{dsh-adapter-kpdnhjst.js → dsh-adapter-d4pdpb3r.js} +2 -3
- package/dist/{e2b-9gkdhm3c.js → e2b-222hsajc.js} +2 -2
- package/dist/{e2b-cjtf8k1z.js → e2b-fn3zacvt.js} +2 -2
- package/dist/{easter-egg-3d-e4qv8dxj.js → easter-egg-3d-4sp4dvmc.js} +1 -1
- package/dist/{event-bus-3z78ep5n.js → event-bus-pghg8hqy.js} +1 -1
- package/dist/{execute-0xpz8950.js → execute-jev839y1.js} +1 -1
- package/dist/{external-channel-j1e2p34p.js → external-channel-y6nkzhnn.js} +2 -2
- package/dist/{grok-adapter-qtjz0whk.js → grok-adapter-t08b9ntm.js} +10 -10
- package/dist/handlers-eqsnmv95.js +41 -0
- package/dist/{heartbeat-vtesda5d.js → heartbeat-vn31xwdk.js} +23 -23
- package/dist/{hook-b2hhj6rd.js → hook-6d0zbvvt.js} +6 -5
- package/dist/{hook-8wqaszda.js → hook-cpjs8q51.js} +13 -7
- package/dist/{http-mxcqhhcs.js → http-9c6medda.js} +1085 -136
- package/dist/{index-spwxjgay.js → index-38t35h0n.js} +20 -20
- package/dist/{index-1r8wfpzs.js → index-g6k9tctj.js} +1 -1
- package/dist/{index-srg522m6.js → index-k0yt1tvq.js} +20 -20
- package/dist/{index-31ceyf42.js → index-pkma1yf8.js} +1 -1
- package/dist/{index-n1vbvta9.js → index-qhjf2htm.js} +29 -24
- package/dist/index-yqgbfgaj.js +43 -0
- package/dist/{keepalive-dsd8rtgx.js → keepalive-em0117bp.js} +4 -4
- package/dist/lead-23e7z59t.js +79 -0
- package/dist/{maintenance-jqmqwnet.js → maintenance-kbme3qy9.js} +1 -1
- package/dist/{oauth-refresh-sweep-q2c3bnvm.js → oauth-refresh-sweep-xysj4yse.js} +3 -3
- package/dist/{onboard-w4yd7s8t.js → onboard-p6q0vz97.js} +3 -3
- package/dist/{opencode-adapter-9tppvxar.js → opencode-adapter-esv3k9yn.js} +7 -7
- package/dist/{otel-impl-f36byzbd.js → otel-impl-54qhqh1z.js} +2 -2
- package/dist/{pi-mono-adapter-tqsge1ay.js → pi-mono-adapter-mymbwhj0.js} +41 -14
- package/dist/{pricing-refresh-xvak5p4v.js → pricing-refresh-yb75wv33.js} +3 -3
- package/dist/{rbac-roles-gfn30fb8.js → rbac-roles-1vp2mw4p.js} +2 -2
- package/dist/{rbac-roles-682k2mt5.js → rbac-roles-dexhc9x8.js} +1 -1
- package/dist/render-v2-tjay5hhf.js +16 -0
- package/dist/{resume-2dvq348g.js → resume-8ayf37g5.js} +11 -9
- package/dist/{runtime-bt5pkpm1.js → runtime-hqe5cqcd.js} +1 -1
- package/dist/{session-access-7f21ekq9.js → session-access-fdhp987s.js} +4 -4
- package/dist/session-source-backfill-qgtc86z1.js +110 -0
- package/dist/{setup-epffxtbk.js → setup-4rrmqcq3.js} +1 -1
- package/dist/{virtual-modules-mnfbh5mr.js → virtual-modules-52km00d6.js} +2 -2
- package/dist/{work-objects-xz1a5rqk.js → work-objects-p28fgd70.js} +23 -23
- package/dist/worker-e3w7vye1.js +79 -0
- package/dist/{x-pbpezr0s.js → x-wxrzvzx8.js} +2 -2
- package/openapi.json +1323 -16
- package/package.json +5 -5
- package/plugin/commands/create-pr.md +2 -2
- package/plugin/pi-skills/create-pr/SKILL.md +2 -2
- package/src/assets/source-key.ts +95 -0
- package/src/be/db/api-keys.ts +80 -0
- package/src/be/db/approvals.ts +48 -0
- package/src/be/db/session-labels.ts +28 -0
- package/src/be/db/session-moves.ts +5 -6
- package/src/be/db/session-tree.ts +153 -14
- package/src/be/db/tasks/read.ts +11 -0
- package/src/be/db/tasks/write.ts +21 -6
- package/src/be/db/workflow-idempotency.ts +183 -0
- package/src/be/db/workflow-runs.ts +500 -7
- package/src/be/db-retention.ts +61 -6
- package/src/be/db.ts +126 -131
- package/src/be/memory/providers/openai-embedding.ts +5 -1
- package/src/be/migrations/242_workflow_idempotency_keys.sql +40 -0
- package/src/be/migrations/244_api_key_validation.sql +4 -0
- package/src/be/onboarding.ts +4 -0
- package/src/be/scripts/typecheck.ts +1 -1
- package/src/be/sealed-json.ts +1 -0
- package/src/be/seed-scripts/catalog/compound-insights.inline.ts +2 -2
- package/src/be/seed-scripts/catalog/compound-insights.ts +2 -2
- package/src/be/seed-skills/bundled-files.generated.json +1 -1
- package/src/be/session-source-backfill.ts +163 -0
- package/src/be/sql-connections.ts +7 -1
- package/src/be/swarm-config-guard.ts +3 -0
- package/src/be/users.ts +9 -0
- package/src/comb/review-batch.ts +28 -1
- package/src/comb/templates.ts +7 -2
- package/src/commands/claude-managed-setup.ts +7 -1
- package/src/commands/controller.ts +33 -0
- package/src/commands/credential-refresh.ts +76 -1
- package/src/commands/credential-wait.ts +2 -2
- package/src/commands/provider-credentials.ts +210 -170
- package/src/commands/runner.ts +31 -6
- package/src/extensions/apply-task-create.ts +64 -11
- package/src/extensions/contract-types.generated.ts +1 -1
- package/src/extensions/contract.ts +30 -0
- package/src/extensions/dispatcher.ts +4 -0
- package/src/extensions/post-bridge.ts +8 -0
- package/src/hooks/context-bound-note.ts +196 -0
- package/src/hooks/hook.ts +12 -0
- package/src/http/all-routes.ts +2 -0
- package/src/http/api-keys.ts +53 -0
- package/src/http/assets.ts +15 -0
- package/src/http/codex-oauth-accounts.ts +191 -0
- package/src/http/comb.ts +15 -2
- package/src/http/config.ts +49 -17
- package/src/http/core.ts +6 -0
- package/src/http/fleet-benchmark.ts +5 -2
- package/src/http/idempotency-key.ts +117 -0
- package/src/http/index.ts +11 -0
- package/src/http/integrations.ts +5 -1
- package/src/http/kv.ts +8 -3
- package/src/http/onboarding.ts +6 -1
- package/src/http/pool-keys.ts +354 -0
- package/src/http/sessions.ts +37 -12
- package/src/http/stats.ts +1 -0
- package/src/http/status.ts +26 -12
- package/src/http/tasks.ts +3 -0
- package/src/http/used-by.ts +7 -2
- package/src/http/workflows.ts +207 -33
- package/src/integrations/chat-sdk/inbound.ts +27 -0
- package/src/kv-reserved-namespaces.ts +19 -0
- package/src/prompts/context-bound-note.ts +48 -0
- package/src/prompts/session-templates.ts +25 -1
- package/src/providers/acp-targets.ts +1 -1
- package/src/providers/amp-adapter.ts +30 -8
- package/src/providers/claude-adapter.ts +34 -5
- package/src/providers/claude-managed-adapter.ts +42 -9
- package/src/providers/claude-sdk-session.ts +10 -0
- package/src/providers/claude-session-events.ts +13 -0
- package/src/providers/codex-adapter.ts +83 -11
- package/src/providers/opencode-adapter.ts +18 -0
- package/src/providers/pi-mono-adapter.ts +35 -0
- package/src/runtime-providers/docker.ts +6 -2
- package/src/scheduler/scheduler.ts +3 -2
- package/src/scripts-runtime/swarm-sdk.ts +7 -1
- package/src/scripts-runtime/types/stdlib.d.ts +1 -1
- package/src/scripts-runtime/types/swarm-extension.d.ts +52 -8
- package/src/scripts-runtime/types/swarm-sdk.d.ts +1 -1
- package/src/slack/assistant.ts +7 -0
- package/src/slack/channel-labels.ts +74 -0
- package/src/slack/handlers.ts +8 -0
- package/src/slack/inbound-files.ts +5 -2
- package/src/slack/thread-buffer.ts +4 -0
- package/src/tasks/sibling-awareness.ts +12 -3
- package/src/telemetry-context.ts +5 -6
- package/src/telemetry-identity.ts +3 -3
- package/src/tests/amp-adapter.test.ts +97 -0
- package/src/tests/api-keys-validation.test.ts +170 -0
- package/src/tests/approval-requests.test.ts +10 -7
- package/src/tests/approval-sweeps.test.ts +12 -6
- package/src/tests/base-prompt.test.ts +1 -1
- package/src/tests/claude-hook-output.test.ts +79 -1
- package/src/tests/claude-managed-adapter.test.ts +321 -0
- package/src/tests/claude-routes-e2e.test.ts +7 -2
- package/src/tests/codex-adapter.test.ts +163 -0
- package/src/tests/codex-credential-pools.test.ts +120 -0
- package/src/tests/codex-gateway-route.test.ts +165 -0
- package/src/tests/codex-oauth-accounts.test.ts +357 -0
- package/src/tests/comb-review-batch.test.ts +63 -2
- package/src/tests/config-pool-keys.test.ts +533 -0
- package/src/tests/context-bound-note.test.ts +269 -0
- package/src/tests/credential-check.test.ts +290 -2
- package/src/tests/credential-refresh.test.ts +172 -1
- package/src/tests/db-retention.test.ts +9 -3
- package/src/tests/db-task-read-contract.test.ts +1 -0
- package/src/tests/db-task-write-contract.test.ts +1 -3
- package/src/tests/docs-index-footprint.test.js +3 -3
- package/src/tests/extensions-pre-task.test.ts +325 -0
- package/src/tests/fetch-resolved-env.test.ts +17 -0
- package/src/tests/fixtures/extensions/record-blocks/hooks.ts +10 -0
- package/src/tests/fixtures/extensions/record-blocks/manifest.json +7 -0
- package/src/tests/fixtures/extensions/record-task-create/hooks.ts +9 -0
- package/src/tests/fixtures/extensions/record-task-create/manifest.json +7 -0
- package/src/tests/fixtures/fake-amp.ts +22 -8
- package/src/tests/fixtures/held-template-render.ts +42 -0
- package/src/tests/fixtures/workflow-idempotency-trigger.child.ts +73 -0
- package/src/tests/fleet-benchmark.test.ts +8 -0
- package/src/tests/get-workflow-run-tool.test.ts +98 -0
- package/src/tests/integrations-http.test.ts +32 -2
- package/src/tests/internal-ai/complete-structured.test.ts +37 -1
- package/src/tests/internal-ai/credentials.test.ts +23 -0
- package/src/tests/kv-http.test.ts +32 -0
- package/src/tests/kv-tool.test.ts +25 -0
- package/src/tests/multi-runtime-registration.test.ts +12 -12
- package/src/tests/onboarding.test.ts +108 -1
- package/src/tests/openai-embedding-credentials.test.ts +35 -0
- package/src/tests/openapi-route-headers.test.ts +2 -0
- package/src/tests/opencode-adapter.test.ts +134 -0
- package/src/tests/openrouter-attribution-call-sites.test.ts +2 -2
- package/src/tests/org-website.test.ts +36 -0
- package/src/tests/pi-mono-adapter.test.ts +105 -0
- package/src/tests/prompt-template-session.test.ts +4 -3
- package/src/tests/provider-key-probes.test.ts +181 -0
- package/src/tests/rbac-e2e-helpers.test.ts +88 -1
- package/src/tests/rbac-e2e-helpers.ts +36 -11
- package/src/tests/runner-push-delivery.test.ts +25 -0
- package/src/tests/runtime-controller-liveness.test.ts +133 -0
- package/src/tests/runtime-providers-docker.test.ts +2 -0
- package/src/tests/script-connections.test.ts +5 -0
- package/src/tests/script-run-config-scope.test.ts +91 -3
- package/src/tests/script-runs-retention.test.ts +212 -0
- package/src/tests/sdk-allowlist.test.ts +39 -0
- package/src/tests/session-logs.test.ts +70 -0
- package/src/tests/session-source-backfill.test.ts +429 -0
- package/src/tests/session-source-keys.test.ts +236 -0
- package/src/tests/session-source-labels.test.ts +419 -0
- package/src/tests/sessions-detail-batch.test.ts +2 -0
- package/src/tests/sessions-move.test.ts +131 -0
- package/src/tests/sessions-tree.test.ts +336 -2
- package/src/tests/slack-inbound-files.test.ts +103 -23
- package/src/tests/status.test.ts +101 -18
- package/src/tests/tasks-workflow-run-filter.test.ts +146 -0
- package/src/tests/test-net.ts +31 -0
- package/src/tests/transcription.test.ts +8 -0
- package/src/tests/used-by-route.test.ts +11 -0
- package/src/tests/workflow-cascade-cancel.test.ts +1341 -0
- package/src/tests/workflow-foreach-supersede-retry.test.ts +488 -0
- package/src/tests/workflow-http-v2.test.ts +131 -0
- package/src/tests/workflow-idempotency.test.ts +915 -0
- package/src/tests/workflow-step-order.test.ts +92 -0
- package/src/tests/workflow-sub-workflow.test.ts +30 -0
- package/src/tools/get-tasks.ts +8 -0
- package/src/tools/kv/kv-read-auth.ts +4 -2
- package/src/tools/task-action.ts +1 -1
- package/src/tools/workflows/cancel-workflow-run.ts +1 -1
- package/src/tools/workflows/get-workflow-run.ts +14 -4
- package/src/types.ts +24 -0
- package/src/utils/company-profile.ts +51 -1
- package/src/utils/context-window.ts +8 -6
- package/src/utils/credentials.ts +5 -2
- package/src/utils/internal-ai/complete-structured.ts +5 -0
- package/src/utils/internal-ai/credentials.ts +4 -3
- package/src/utils/provider-key-probes.ts +328 -0
- package/src/utils/transcription.ts +4 -1
- package/src/workflows/approval-cancel.ts +15 -13
- package/src/workflows/approval-notifications.ts +5 -1
- package/src/workflows/checkpoint.ts +6 -3
- package/src/workflows/engine.ts +30 -8
- package/src/workflows/executors/foreach.ts +2 -2
- package/src/workflows/executors/sub-workflow.ts +8 -1
- package/src/workflows/foreach-driver.ts +10 -3
- package/src/workflows/foreach-join.ts +29 -6
- package/src/workflows/recovery.ts +6 -1
- package/src/workflows/resume.ts +168 -56
- package/src/workflows/retry-poller.ts +49 -3
- package/src/workflows/task-step-routing.ts +29 -7
- package/templates/ai-toolbox.manifest.json +8 -8
- package/templates/community/open-prompt-showreel/PLAYBOOK.md +5 -6
- package/templates/community/open-prompt-showreel/lead-prompt.md +1 -1
- package/templates/community/open-prompt-showreel/skills/open-prompt-showreel/SKILL.md +4 -4
- package/templates/skills/agent-browser/SKILL.md +4 -4
- package/templates/skills/agent-browser/content.md +4 -4
- package/templates/skills/code-quality/config.json +1 -1
- package/templates/skills/code-quality/content.md +2 -2
- package/templates/skills/delegate-work/content.md +29 -25
- package/templates/skills/delegate-work/files/scripts/codex-exec.sh +5 -5
- package/templates/skills/qa/content.md +2 -2
- package/templates/skills/researching/content.md +4 -0
- package/templates/skills/swarm-scripts/SKILL.md +1 -0
- package/templates/skills/swarm-scripts/content.md +1 -0
- package/templates/skills/work-on-task/SKILL.md +1 -1
- package/templates/skills/work-on-task/content.md +1 -1
- package/dist/cli-7421jzsw.js +0 -84
- package/dist/cli-e45e4m6b.js +0 -413
- package/dist/cli-zw88zjba.js +0 -307
- package/dist/handlers-19xr6dnz.js +0 -41
- package/dist/index-kw2mmat7.js +0 -43
- package/dist/lead-zyerk1x2.js +0 -79
- package/dist/render-v2-hgytz8nn.js +0 -12
- package/dist/worker-42t8emjt.js +0 -79
|
@@ -0,0 +1,328 @@
|
|
|
1
|
+
/** Shared credential probes. This module does not import worker harnesses. */
|
|
2
|
+
import {
|
|
3
|
+
type OpenAICompatibleProvider,
|
|
4
|
+
providerBaseUrl,
|
|
5
|
+
providerForKeyVar,
|
|
6
|
+
} from "@desplega/model-catalog";
|
|
7
|
+
import {
|
|
8
|
+
createScopedFetch,
|
|
9
|
+
deriveDefaultRoute,
|
|
10
|
+
getRouteProvider,
|
|
11
|
+
type ModelRoute,
|
|
12
|
+
} from "@desplega/model-routing";
|
|
13
|
+
import { assertUrlSafe, publicEndpointSsrfOptions } from "../oauth/mcp-wrapper";
|
|
14
|
+
import { getOpenRouterAttributionHeaders, getOpenRouterBaseUrl } from "./openrouter-base-url";
|
|
15
|
+
import { scrubSecrets } from "./secret-scrubber";
|
|
16
|
+
|
|
17
|
+
interface ProbeOptions {
|
|
18
|
+
env?: Record<string, string | undefined>;
|
|
19
|
+
publicEndpoint?: boolean;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
const LIVE_TEST_TIMEOUT_MS = 5_000;
|
|
23
|
+
|
|
24
|
+
function scrubProbeBody(body: string, init: RequestInit): string {
|
|
25
|
+
const headers = new Headers(init.headers);
|
|
26
|
+
const apiKey =
|
|
27
|
+
headers.get("x-api-key") ?? headers.get("authorization")?.replace(/^Bearer\s+/i, "");
|
|
28
|
+
return scrubSecrets(apiKey ? body.replaceAll(apiKey, "[REDACTED]") : body);
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
export interface LiveKeyResult {
|
|
32
|
+
keyType: string;
|
|
33
|
+
index: number;
|
|
34
|
+
suffix: string;
|
|
35
|
+
ok: boolean;
|
|
36
|
+
error?: string;
|
|
37
|
+
httpStatus?: number;
|
|
38
|
+
latency_ms: number;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
export interface LiveValidationResult {
|
|
42
|
+
ok: boolean;
|
|
43
|
+
error?: string;
|
|
44
|
+
httpStatus?: number;
|
|
45
|
+
warning?: string;
|
|
46
|
+
keys?: LiveKeyResult[];
|
|
47
|
+
latency_ms: number;
|
|
48
|
+
/** No free live check exists for this credential (cloud routes): report presence only. */
|
|
49
|
+
skipped?: boolean;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
export async function timedFetch(
|
|
53
|
+
url: string,
|
|
54
|
+
init: RequestInit,
|
|
55
|
+
options: ProbeOptions = {},
|
|
56
|
+
): Promise<{ ok: boolean; status: number; bodyText: string; latency_ms: number }> {
|
|
57
|
+
const controller = new AbortController();
|
|
58
|
+
const timer = setTimeout(() => controller.abort(), LIVE_TEST_TIMEOUT_MS);
|
|
59
|
+
const start = Date.now();
|
|
60
|
+
try {
|
|
61
|
+
if (options.publicEndpoint) assertUrlSafe(url, publicEndpointSsrfOptions());
|
|
62
|
+
const res = await fetch(url, {
|
|
63
|
+
...init,
|
|
64
|
+
...(options.publicEndpoint ? { redirect: "manual" } : {}),
|
|
65
|
+
signal: controller.signal,
|
|
66
|
+
});
|
|
67
|
+
let bodyText = "";
|
|
68
|
+
try {
|
|
69
|
+
bodyText = scrubProbeBody(await res.text(), init);
|
|
70
|
+
} catch {
|
|
71
|
+
// The HTTP status is sufficient when the response body cannot be read.
|
|
72
|
+
}
|
|
73
|
+
return {
|
|
74
|
+
ok: res.ok,
|
|
75
|
+
status: res.status,
|
|
76
|
+
bodyText,
|
|
77
|
+
latency_ms: Date.now() - start,
|
|
78
|
+
};
|
|
79
|
+
} finally {
|
|
80
|
+
clearTimeout(timer);
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
export async function checkAnthropicApiKey(
|
|
85
|
+
apiKey: string,
|
|
86
|
+
options: ProbeOptions = {},
|
|
87
|
+
): Promise<LiveValidationResult> {
|
|
88
|
+
const r = await timedFetch(
|
|
89
|
+
"https://api.anthropic.com/v1/models",
|
|
90
|
+
{
|
|
91
|
+
method: "GET",
|
|
92
|
+
headers: { "x-api-key": apiKey, "anthropic-version": "2023-06-01" },
|
|
93
|
+
},
|
|
94
|
+
options,
|
|
95
|
+
);
|
|
96
|
+
if (r.ok) return { ok: true, latency_ms: r.latency_ms };
|
|
97
|
+
return {
|
|
98
|
+
ok: false,
|
|
99
|
+
error: scrubSecrets(`HTTP ${r.status}: ${r.bodyText.slice(0, 200)}`),
|
|
100
|
+
httpStatus: r.status,
|
|
101
|
+
latency_ms: r.latency_ms,
|
|
102
|
+
};
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
export async function checkOpenAiApiKey(
|
|
106
|
+
apiKey: string,
|
|
107
|
+
options: ProbeOptions = {},
|
|
108
|
+
): Promise<LiveValidationResult> {
|
|
109
|
+
const r = await timedFetch(
|
|
110
|
+
"https://api.openai.com/v1/models",
|
|
111
|
+
{
|
|
112
|
+
method: "GET",
|
|
113
|
+
headers: { Authorization: `Bearer ${apiKey}` },
|
|
114
|
+
},
|
|
115
|
+
options,
|
|
116
|
+
);
|
|
117
|
+
if (r.ok) return { ok: true, latency_ms: r.latency_ms };
|
|
118
|
+
return {
|
|
119
|
+
ok: false,
|
|
120
|
+
error: scrubSecrets(`HTTP ${r.status}: ${r.bodyText.slice(0, 200)}`),
|
|
121
|
+
httpStatus: r.status,
|
|
122
|
+
latency_ms: r.latency_ms,
|
|
123
|
+
};
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
/** `GET /v1/models` is the call the Grok CLI makes first; it runs no inference. */
|
|
127
|
+
export async function checkXaiApiKey(
|
|
128
|
+
apiKey: string,
|
|
129
|
+
options: ProbeOptions = {},
|
|
130
|
+
): Promise<LiveValidationResult> {
|
|
131
|
+
const r = await timedFetch(
|
|
132
|
+
"https://api.x.ai/v1/models",
|
|
133
|
+
{
|
|
134
|
+
method: "GET",
|
|
135
|
+
headers: { Authorization: `Bearer ${apiKey}` },
|
|
136
|
+
},
|
|
137
|
+
options,
|
|
138
|
+
);
|
|
139
|
+
if (r.ok) return { ok: true, latency_ms: r.latency_ms };
|
|
140
|
+
return {
|
|
141
|
+
ok: false,
|
|
142
|
+
error: scrubSecrets(`HTTP ${r.status}: ${r.bodyText.slice(0, 200)}`),
|
|
143
|
+
httpStatus: r.status,
|
|
144
|
+
latency_ms: r.latency_ms,
|
|
145
|
+
};
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
/** Fetch a key-authenticated GET and normalize it into a `LiveValidationResult`. */
|
|
149
|
+
async function checkBearerGet(
|
|
150
|
+
url: string,
|
|
151
|
+
init: RequestInit,
|
|
152
|
+
options: ProbeOptions = {},
|
|
153
|
+
): Promise<LiveValidationResult> {
|
|
154
|
+
const r = await timedFetch(url, { method: "GET", ...init }, options);
|
|
155
|
+
if (r.ok) return { ok: true, latency_ms: r.latency_ms };
|
|
156
|
+
return {
|
|
157
|
+
ok: false,
|
|
158
|
+
error: scrubSecrets(`HTTP ${r.status}: ${r.bodyText.slice(0, 200)}`),
|
|
159
|
+
httpStatus: r.status,
|
|
160
|
+
latency_ms: r.latency_ms,
|
|
161
|
+
};
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
/**
|
|
165
|
+
* OpenRouter's `/models` is public and answers 200 for any key, so the check
|
|
166
|
+
* reads `GET /key` (the key's own info, 401 for a bad key). A custom
|
|
167
|
+
* `OPENROUTER_BASE_URL` without `/key` (404/405) falls back to `/models`.
|
|
168
|
+
*/
|
|
169
|
+
export async function checkOpenRouter(
|
|
170
|
+
apiKey: string,
|
|
171
|
+
options: ProbeOptions = {},
|
|
172
|
+
): Promise<LiveValidationResult> {
|
|
173
|
+
const base = getOpenRouterBaseUrl(options.env);
|
|
174
|
+
const get = (url: string) =>
|
|
175
|
+
checkBearerGet(
|
|
176
|
+
url,
|
|
177
|
+
{
|
|
178
|
+
headers: {
|
|
179
|
+
...getOpenRouterAttributionHeaders(url, options.env),
|
|
180
|
+
Authorization: `Bearer ${apiKey}`,
|
|
181
|
+
},
|
|
182
|
+
},
|
|
183
|
+
options,
|
|
184
|
+
);
|
|
185
|
+
const keyResult = await get(`${base}/key`);
|
|
186
|
+
if (keyResult.httpStatus === 404 || keyResult.httpStatus === 405) return get(`${base}/models`);
|
|
187
|
+
return keyResult;
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
/** `GET /models` on the official DeepSeek API: lists models, runs no inference. */
|
|
191
|
+
export function checkDeepSeekApiKey(
|
|
192
|
+
apiKey: string,
|
|
193
|
+
options: ProbeOptions = {},
|
|
194
|
+
): Promise<LiveValidationResult> {
|
|
195
|
+
return checkBearerGet(
|
|
196
|
+
"https://api.deepseek.com/models",
|
|
197
|
+
{
|
|
198
|
+
headers: { Authorization: `Bearer ${apiKey}` },
|
|
199
|
+
},
|
|
200
|
+
options,
|
|
201
|
+
);
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
/**
|
|
205
|
+
* `GET <baseUrl>/models` with a Bearer key against an OpenAI-compatible
|
|
206
|
+
* registry provider (NaN, Helmcode). Lists models, runs no inference.
|
|
207
|
+
* `baseUrl` defaults to the registry entry; the probe passes the
|
|
208
|
+
* `<ID>_BASE_URL` override, and tests inject a fake server.
|
|
209
|
+
*/
|
|
210
|
+
export async function checkOpenAICompatibleProvider(
|
|
211
|
+
provider: OpenAICompatibleProvider,
|
|
212
|
+
apiKey: string,
|
|
213
|
+
baseUrl: string = provider.baseUrl,
|
|
214
|
+
options: ProbeOptions = {},
|
|
215
|
+
): Promise<LiveValidationResult> {
|
|
216
|
+
const r = await timedFetch(
|
|
217
|
+
`${baseUrl.replace(/\/+$/, "")}/models`,
|
|
218
|
+
{
|
|
219
|
+
method: "GET",
|
|
220
|
+
headers: { Authorization: `Bearer ${apiKey}` },
|
|
221
|
+
},
|
|
222
|
+
options,
|
|
223
|
+
);
|
|
224
|
+
if (r.ok) return { ok: true, latency_ms: r.latency_ms };
|
|
225
|
+
return {
|
|
226
|
+
ok: false,
|
|
227
|
+
error: scrubSecrets(`HTTP ${r.status}: ${r.bodyText.slice(0, 200)}`),
|
|
228
|
+
httpStatus: r.status,
|
|
229
|
+
latency_ms: r.latency_ms,
|
|
230
|
+
};
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
/**
|
|
234
|
+
* Run the route provider's own check. Requests stay on the route's origin and
|
|
235
|
+
* path prefix, go to public hosts only, and never follow a redirect.
|
|
236
|
+
*/
|
|
237
|
+
async function checkRoute(
|
|
238
|
+
route: ModelRoute,
|
|
239
|
+
env: Record<string, string | undefined>,
|
|
240
|
+
): Promise<LiveValidationResult> {
|
|
241
|
+
const startedAt = Date.now();
|
|
242
|
+
const provider = getRouteProvider(route.provider);
|
|
243
|
+
const baseUrl = route.baseUrl ?? provider?.defaultBaseUrl;
|
|
244
|
+
if (!provider?.validate || !baseUrl) return { ok: true, skipped: true, latency_ms: 0 };
|
|
245
|
+
const publicFetch = async (url: string | URL | Request, init?: RequestInit) => {
|
|
246
|
+
assertUrlSafe(String(url), publicEndpointSsrfOptions());
|
|
247
|
+
const res = await fetch(url, init);
|
|
248
|
+
if (res.ok || !res.body) return res;
|
|
249
|
+
return new Response(scrubProbeBody(await res.text(), init ?? {}), {
|
|
250
|
+
status: res.status,
|
|
251
|
+
statusText: res.statusText,
|
|
252
|
+
headers: res.headers,
|
|
253
|
+
});
|
|
254
|
+
};
|
|
255
|
+
const result = await provider.validate({
|
|
256
|
+
route,
|
|
257
|
+
env,
|
|
258
|
+
signal: AbortSignal.timeout(LIVE_TEST_TIMEOUT_MS),
|
|
259
|
+
fetch: createScopedFetch(baseUrl, publicFetch as typeof fetch),
|
|
260
|
+
});
|
|
261
|
+
const latency_ms = Date.now() - startedAt;
|
|
262
|
+
if (result.status === "verified") return { ok: true, latency_ms };
|
|
263
|
+
return {
|
|
264
|
+
ok: result.status === "configured",
|
|
265
|
+
...(result.status === "configured" ? { skipped: true } : { httpStatus: result.httpStatus }),
|
|
266
|
+
error: scrubSecrets(result.reason),
|
|
267
|
+
latency_ms,
|
|
268
|
+
};
|
|
269
|
+
}
|
|
270
|
+
|
|
271
|
+
/** Check one newly submitted pool member without loading a worker harness. */
|
|
272
|
+
export async function checkProviderPoolKey(
|
|
273
|
+
keyType: string,
|
|
274
|
+
apiKey: string,
|
|
275
|
+
env: Record<string, string | undefined> = process.env,
|
|
276
|
+
): Promise<LiveValidationResult> {
|
|
277
|
+
const startedAt = Date.now();
|
|
278
|
+
const options: ProbeOptions = { env, publicEndpoint: true };
|
|
279
|
+
try {
|
|
280
|
+
switch (keyType) {
|
|
281
|
+
case "ANTHROPIC_API_KEY": {
|
|
282
|
+
// Behind a gateway (`ANTHROPIC_BASE_URL`), Claude Code sends this key
|
|
283
|
+
// to the gateway, so check it there and not at api.anthropic.com.
|
|
284
|
+
const keyEnv = { ...env, ANTHROPIC_API_KEY: apiKey };
|
|
285
|
+
const route = deriveDefaultRoute("claude", keyEnv);
|
|
286
|
+
if (
|
|
287
|
+
route?.provider === "anthropic-gateway" &&
|
|
288
|
+
"secretKey" in route.auth &&
|
|
289
|
+
route.auth.secretKey === "ANTHROPIC_API_KEY"
|
|
290
|
+
) {
|
|
291
|
+
return await checkRoute(route, keyEnv);
|
|
292
|
+
}
|
|
293
|
+
return await checkAnthropicApiKey(apiKey, options);
|
|
294
|
+
}
|
|
295
|
+
case "OPENAI_API_KEY":
|
|
296
|
+
return await checkOpenAiApiKey(apiKey, options);
|
|
297
|
+
case "OPENROUTER_API_KEY":
|
|
298
|
+
return await checkOpenRouter(apiKey, options);
|
|
299
|
+
case "DEEPSEEK_API_KEY":
|
|
300
|
+
return await checkDeepSeekApiKey(apiKey, options);
|
|
301
|
+
case "XAI_API_KEY":
|
|
302
|
+
return await checkXaiApiKey(apiKey, options);
|
|
303
|
+
case "CLAUDE_CODE_OAUTH_TOKEN": {
|
|
304
|
+
const tokenEnv = { CLAUDE_CODE_OAUTH_TOKEN: apiKey };
|
|
305
|
+
return await checkRoute(deriveDefaultRoute("claude", tokenEnv)!, tokenEnv);
|
|
306
|
+
}
|
|
307
|
+
default: {
|
|
308
|
+
const provider = providerForKeyVar(keyType);
|
|
309
|
+
if (provider) {
|
|
310
|
+
return await checkOpenAICompatibleProvider(
|
|
311
|
+
provider,
|
|
312
|
+
apiKey,
|
|
313
|
+
providerBaseUrl(provider, env),
|
|
314
|
+
options,
|
|
315
|
+
);
|
|
316
|
+
}
|
|
317
|
+
return { ok: true, skipped: true, latency_ms: 0 };
|
|
318
|
+
}
|
|
319
|
+
}
|
|
320
|
+
} catch (err) {
|
|
321
|
+
const raw = err instanceof Error ? err.message : String(err);
|
|
322
|
+
return {
|
|
323
|
+
ok: false,
|
|
324
|
+
error: scrubSecrets(raw.split(apiKey).join("[REDACTED]")) || "Unknown error",
|
|
325
|
+
latency_ms: Date.now() - startedAt,
|
|
326
|
+
};
|
|
327
|
+
}
|
|
328
|
+
}
|
|
@@ -20,6 +20,7 @@
|
|
|
20
20
|
* auto-detected key always goes to its provider's own endpoint. Deepgram and AssemblyAI use other request shapes and are not
|
|
21
21
|
* supported. Read at call time, so a config reload applies without a restart.
|
|
22
22
|
*/
|
|
23
|
+
import { selectCredential } from "./credentials";
|
|
23
24
|
import { createValueScrubber, scrubSecrets } from "./secret-scrubber";
|
|
24
25
|
|
|
25
26
|
const TRANSCRIPTION_TIMEOUT_MS = 60_000;
|
|
@@ -119,7 +120,9 @@ export function resolveTranscriptionConfig(): TranscriptionConfig | null {
|
|
|
119
120
|
: AUTO_DETECT_ORDER;
|
|
120
121
|
for (const name of candidates) {
|
|
121
122
|
const { keyEnv } = PROVIDERS[name];
|
|
122
|
-
const
|
|
123
|
+
const value = envValue(keyEnv);
|
|
124
|
+
// OPENAI_API_KEY may be a comma pool: send one member.
|
|
125
|
+
const apiKey = value ? selectCredential(value, undefined, keyEnv).selected : undefined;
|
|
123
126
|
if (apiKey) return { provider: name, apiKey, keyEnv, source: "auto" };
|
|
124
127
|
}
|
|
125
128
|
return null;
|
|
@@ -4,16 +4,15 @@ import {
|
|
|
4
4
|
getApprovalRequestById,
|
|
5
5
|
getDbClient,
|
|
6
6
|
getTaskById,
|
|
7
|
-
getWorkflowRun,
|
|
8
|
-
listCancelledApprovalRequestsForRun,
|
|
9
7
|
resolveApprovalRequest,
|
|
10
8
|
} from "../be/db";
|
|
9
|
+
import { getWorkflowRunStatus } from "../be/db/workflow-runs";
|
|
11
10
|
import { can, type RbacPrincipal } from "../rbac";
|
|
12
11
|
import {
|
|
13
12
|
createApprovalFollowUpTask,
|
|
14
13
|
postApprovalCancellationUpdates,
|
|
15
14
|
} from "./approval-notifications";
|
|
16
|
-
import { cancelWorkflowRunRows } from "./resume";
|
|
15
|
+
import { cancelWorkflowRunRows, postTreeApprovalCancellations } from "./resume";
|
|
17
16
|
|
|
18
17
|
export type CancelApprovalRequestResult =
|
|
19
18
|
| { ok: true; request: ApprovalRequest; alreadyCancelled: boolean; runCancelled: boolean }
|
|
@@ -126,25 +125,28 @@ export async function cancelApprovalRequestAndRun(
|
|
|
126
125
|
if (!request) return null;
|
|
127
126
|
|
|
128
127
|
// The request is cancelled first so it keeps this reason; the run cancel
|
|
129
|
-
// then skips it because it is no longer pending.
|
|
128
|
+
// then skips it because it is no longer pending. The run's child tree is
|
|
129
|
+
// cancelled with it.
|
|
130
130
|
let runCancelled = false;
|
|
131
|
+
let treeRunIds: string[] = [];
|
|
131
132
|
const runId = request.workflowRunId;
|
|
132
133
|
if (runId) {
|
|
133
|
-
const
|
|
134
|
-
if (
|
|
135
|
-
|
|
134
|
+
const status = await getWorkflowRunStatus(runId);
|
|
135
|
+
if (status && LIVE_RUN_STATUSES.has(status)) {
|
|
136
|
+
const result = await cancelWorkflowRunRows(runId, data.reason);
|
|
137
|
+
runCancelled = result.cancelled;
|
|
138
|
+
treeRunIds = result.runIds;
|
|
136
139
|
}
|
|
137
140
|
}
|
|
138
141
|
|
|
139
142
|
const slackReason = data.slackReason ?? data.reason;
|
|
140
143
|
client.afterCommit(async () => {
|
|
141
144
|
await postApprovalCancellationUpdates([request], slackReason);
|
|
142
|
-
if (runCancelled
|
|
143
|
-
// Other requests the
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
);
|
|
147
|
-
await postApprovalCancellationUpdates(others, data.reason);
|
|
145
|
+
if (runCancelled) {
|
|
146
|
+
// Other requests the tree cancel closed get the run's reason.
|
|
147
|
+
await postTreeApprovalCancellations(treeRunIds, data.reason, {
|
|
148
|
+
exceptRequestId: request.id,
|
|
149
|
+
});
|
|
148
150
|
}
|
|
149
151
|
});
|
|
150
152
|
return { request, runCancelled };
|
|
@@ -9,6 +9,7 @@ import {
|
|
|
9
9
|
releaseApprovalCancellationNotificationClaim,
|
|
10
10
|
updateApprovalRequestNotifications,
|
|
11
11
|
} from "../be/db";
|
|
12
|
+
import { approvalCancellationNotificationKey } from "../be/db/approvals";
|
|
12
13
|
import { resolveTemplate } from "../prompts/resolver";
|
|
13
14
|
import { getSlackApp } from "../slack/app";
|
|
14
15
|
import { resolveSlackConversationId } from "../slack/dm";
|
|
@@ -263,7 +264,10 @@ export async function postApprovalCancellationUpdates(
|
|
|
263
264
|
|
|
264
265
|
for (const notification of slackThreads) {
|
|
265
266
|
if (notification.channel !== "slack" || !notification.messageTs) continue;
|
|
266
|
-
const notificationKey =
|
|
267
|
+
const notificationKey = approvalCancellationNotificationKey(
|
|
268
|
+
notification.target,
|
|
269
|
+
notification.messageTs,
|
|
270
|
+
);
|
|
267
271
|
const claim = await claimApprovalCancellationNotification(approval.id, notificationKey);
|
|
268
272
|
if (!claim) continue;
|
|
269
273
|
try {
|
|
@@ -1,12 +1,14 @@
|
|
|
1
1
|
import { getDbClient, getWorkflowRun, updateWorkflowRun, updateWorkflowRunStep } from "../be/db";
|
|
2
|
+
import { getRunLineageBlock } from "../be/db/workflow-runs";
|
|
2
3
|
import type { RetryPolicy } from "../types";
|
|
3
4
|
|
|
4
5
|
/**
|
|
5
6
|
* The execution a checkpoint belongs to: the step attempt (`retryCount`) that
|
|
6
7
|
* claimed the row. A fenced checkpoint writes only while that attempt still
|
|
7
8
|
* owns the row: the step is `running` with that `retryCount` in a `running` or
|
|
8
|
-
* `waiting` run. A cancel, a newer attempt or
|
|
9
|
-
* meanwhile wins, and the stale execution
|
|
9
|
+
* `waiting` run with no cancelled ancestor. A cancel, a newer attempt or
|
|
10
|
+
* another settle that committed meanwhile wins, and the stale execution
|
|
11
|
+
* writes nothing.
|
|
10
12
|
*/
|
|
11
13
|
export type StepFence = { retryCount: number };
|
|
12
14
|
|
|
@@ -19,7 +21,7 @@ async function holdsFence(runId: string, stepId: string, fence: StepFence): Prom
|
|
|
19
21
|
AND r.status IN ('running', 'waiting')`,
|
|
20
22
|
[stepId, runId, fence.retryCount],
|
|
21
23
|
);
|
|
22
|
-
return row != null;
|
|
24
|
+
return row != null && (await getRunLineageBlock(runId)) == null;
|
|
23
25
|
}
|
|
24
26
|
|
|
25
27
|
/**
|
|
@@ -129,6 +131,7 @@ export async function checkpointStepWaiting(
|
|
|
129
131
|
options: { keepRunStatus?: boolean } = {},
|
|
130
132
|
): Promise<boolean> {
|
|
131
133
|
return getDbClient().transaction(async () => {
|
|
134
|
+
if (await getRunLineageBlock(runId)) return false;
|
|
132
135
|
const step = await getDbClient().get<{ id: string }>(
|
|
133
136
|
`UPDATE workflow_run_steps
|
|
134
137
|
SET status = 'waiting'
|
package/src/workflows/engine.ts
CHANGED
|
@@ -21,7 +21,11 @@ import {
|
|
|
21
21
|
updateWorkflowRun,
|
|
22
22
|
updateWorkflowRunStep,
|
|
23
23
|
} from "../be/db";
|
|
24
|
-
import {
|
|
24
|
+
import {
|
|
25
|
+
getChildWorkflowRunId,
|
|
26
|
+
getRunLineageBlock,
|
|
27
|
+
isParentStepAttemptCurrent,
|
|
28
|
+
} from "../be/db/workflow-runs";
|
|
25
29
|
import { WorkflowReplayStateError } from "../be/workflow-replay";
|
|
26
30
|
import { telemetry } from "../telemetry";
|
|
27
31
|
import type { Workflow, WorkflowDefinition, WorkflowNode, WorkflowRunStep } from "../types";
|
|
@@ -67,6 +71,13 @@ export interface WorkflowExecutionOptions {
|
|
|
67
71
|
parentStepId?: string;
|
|
68
72
|
/** The parent step's `retryCount`; each attempt gets its own child run. */
|
|
69
73
|
parentStepAttempt?: number;
|
|
74
|
+
/**
|
|
75
|
+
* Runs inside the transaction that commits a top-level run (or reuses the
|
|
76
|
+
* day's setup-failure run), after the run row is written. A throw rolls the
|
|
77
|
+
* run back and nothing else happens: no walk, no alert, no telemetry. The
|
|
78
|
+
* HTTP trigger records its `Idempotency-Key` here.
|
|
79
|
+
*/
|
|
80
|
+
accept?: (run: { runId: string; skipped: boolean }) => Promise<void>;
|
|
70
81
|
}
|
|
71
82
|
|
|
72
83
|
/**
|
|
@@ -111,12 +122,16 @@ export async function startWorkflowExecution(
|
|
|
111
122
|
await getAutomationSetupStates(),
|
|
112
123
|
);
|
|
113
124
|
if (preflight.state === "needs_setup" && !options.parentStepId) {
|
|
114
|
-
const { runId, recorded } = await
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
125
|
+
const { runId, recorded } = await getDbClient().transaction(async () => {
|
|
126
|
+
const failure = await recordWorkflowPreflightFailure({
|
|
127
|
+
workflowId: workflow.id,
|
|
128
|
+
triggerType: options.triggerType ?? "manual",
|
|
129
|
+
triggerData,
|
|
130
|
+
failureReason: preflight.failureReason!,
|
|
131
|
+
createdBy: options.requestedByUserId,
|
|
132
|
+
});
|
|
133
|
+
await options.accept?.({ runId: failure.runId, skipped: false });
|
|
134
|
+
return failure;
|
|
120
135
|
});
|
|
121
136
|
if (recorded) await notifyAutomationPreflightFailure(preflight);
|
|
122
137
|
return runId;
|
|
@@ -276,7 +291,8 @@ async function buildInitialRunState(
|
|
|
276
291
|
* executing and returns the child that attempt already started instead of a
|
|
277
292
|
* second one; the unique (parentStepId, parentStepAttempt) index backs this
|
|
278
293
|
* across processes. A setup failure claims the day's alert in the same
|
|
279
|
-
* transaction, so concurrent failures alert exactly once.
|
|
294
|
+
* transaction, so concurrent failures alert exactly once. `options.accept`
|
|
295
|
+
* runs last in it.
|
|
280
296
|
*/
|
|
281
297
|
async function createRunWithInitialState(
|
|
282
298
|
workflow: Workflow,
|
|
@@ -314,6 +330,7 @@ async function createRunWithInitialState(
|
|
|
314
330
|
initial.status === "failed" && initial.preflight
|
|
315
331
|
? await isFirstWorkflowPreflightFailureToday(workflow.id, runId)
|
|
316
332
|
: undefined;
|
|
333
|
+
await options.accept?.({ runId, skipped: initial.status === "skipped" });
|
|
317
334
|
return { runId, created: true, preflightAlert };
|
|
318
335
|
});
|
|
319
336
|
}
|
|
@@ -606,6 +623,8 @@ async function walkGraphOwned(
|
|
|
606
623
|
await getDbClient().transaction(async () => {
|
|
607
624
|
const run = await getWorkflowRun(runId);
|
|
608
625
|
if (!run || run.status !== "running") return;
|
|
626
|
+
// Inside a cancelled tree the run's outcome is the cancel, not its steps.
|
|
627
|
+
if (await getRunLineageBlock(runId)) return;
|
|
609
628
|
|
|
610
629
|
const finalSteps = await getWorkflowRunStepsByRunId(runId);
|
|
611
630
|
// Another walker is still executing a branch; it finalizes the run.
|
|
@@ -793,6 +812,9 @@ async function executeStep(
|
|
|
793
812
|
if (!run || (run.status !== "running" && run.status !== "waiting")) {
|
|
794
813
|
return { halted: true as const };
|
|
795
814
|
}
|
|
815
|
+
// A live run under a cancelled ancestor (or with a broken parent link)
|
|
816
|
+
// starts no step: the cancel covers its whole tree.
|
|
817
|
+
if (await getRunLineageBlock(runId)) return { halted: true as const };
|
|
796
818
|
|
|
797
819
|
// Another walker already started or finished this node for the same
|
|
798
820
|
// predecessor completions (two branch completions both walking the join,
|
|
@@ -6,7 +6,7 @@ import { getSuccessors } from "../definition";
|
|
|
6
6
|
import { foreachBodyTypeError } from "../foreach-body";
|
|
7
7
|
import {
|
|
8
8
|
buildForeachAggregate,
|
|
9
|
-
|
|
9
|
+
isForeachChildSettled,
|
|
10
10
|
parseSyntheticNodeId,
|
|
11
11
|
} from "../foreach-join";
|
|
12
12
|
import { getMaxWorkflowStepsPerRun } from "../limits";
|
|
@@ -144,7 +144,7 @@ export class ForeachExecutor extends BaseExecutor<
|
|
|
144
144
|
const childSteps = (await this.deps.db.getWorkflowRunStepsByRunId(meta.runId)).filter(
|
|
145
145
|
(step) => parseSyntheticNodeId(step.nodeId)?.parentNodeId === meta.nodeId,
|
|
146
146
|
);
|
|
147
|
-
if (childSteps.every(
|
|
147
|
+
if (childSteps.every(isForeachChildSettled)) {
|
|
148
148
|
return { status: "success", output: buildForeachAggregate(childSteps) };
|
|
149
149
|
}
|
|
150
150
|
|
|
@@ -133,7 +133,14 @@ export class SubWorkflowExecutor extends BaseExecutor<
|
|
|
133
133
|
throw err;
|
|
134
134
|
}
|
|
135
135
|
const child = await db.getWorkflowRun(childRunId);
|
|
136
|
-
|
|
136
|
+
// The start committed the row, so a missing one was deleted with its workflow.
|
|
137
|
+
if (!child) {
|
|
138
|
+
return {
|
|
139
|
+
status: "failed",
|
|
140
|
+
error: `Child workflow run ${childRunId} not found`,
|
|
141
|
+
retryable: false,
|
|
142
|
+
};
|
|
143
|
+
}
|
|
137
144
|
return this.settle(child, config.workflowId);
|
|
138
145
|
}
|
|
139
146
|
|
|
@@ -11,6 +11,7 @@ import {
|
|
|
11
11
|
updateWorkflowRun,
|
|
12
12
|
updateWorkflowRunStep,
|
|
13
13
|
} from "../be/db";
|
|
14
|
+
import { getRunLineageBlock, getWorkflowRunStatus } from "../be/db/workflow-runs";
|
|
14
15
|
import type {
|
|
15
16
|
Workflow,
|
|
16
17
|
WorkflowDefinition,
|
|
@@ -41,6 +42,7 @@ import {
|
|
|
41
42
|
cancelQueuedForeachChildren,
|
|
42
43
|
FOREACH_TERMINAL_STEP_STATUSES,
|
|
43
44
|
type ForeachChildOutcome,
|
|
45
|
+
isRetryPending,
|
|
44
46
|
latestForeachChildren,
|
|
45
47
|
settleForeachChild,
|
|
46
48
|
} from "./foreach-join";
|
|
@@ -352,8 +354,8 @@ async function parkRunIfRunning(runId: string): Promise<void> {
|
|
|
352
354
|
|
|
353
355
|
/**
|
|
354
356
|
* Flip `pending` children to `running`, in item order, while fewer than the
|
|
355
|
-
* fan-out's `concurrency` cap are in flight (`running` or
|
|
356
|
-
* attempt rows only), and collect unowned `running` ones. Ownership is registered before commit, so no other
|
|
357
|
+
* fan-out's `concurrency` cap are in flight (`running`, `waiting` or queued
|
|
358
|
+
* for a retry, latest attempt rows only), and collect unowned `running` ones. Ownership is registered before commit, so no other
|
|
357
359
|
* drive can see a claimed row without also seeing it owned.
|
|
358
360
|
*/
|
|
359
361
|
async function claimChildren(
|
|
@@ -367,6 +369,10 @@ async function claimChildren(
|
|
|
367
369
|
// so concurrent drives (a settlement's drive and the recovery sweep)
|
|
368
370
|
// serialize here and never exceed the cap together.
|
|
369
371
|
await getDbClient().transaction(async () => {
|
|
372
|
+
// A cancel of the run (or of an ancestor) that committed first wins.
|
|
373
|
+
const status = await getWorkflowRunStatus(runId);
|
|
374
|
+
if (status !== "running" && status !== "waiting") return;
|
|
375
|
+
if (await getRunLineageBlock(runId)) return;
|
|
370
376
|
const children = latestForeachChildren(
|
|
371
377
|
def,
|
|
372
378
|
parentNode.id,
|
|
@@ -379,7 +385,8 @@ async function claimChildren(
|
|
|
379
385
|
}
|
|
380
386
|
}
|
|
381
387
|
const inFlight = children.filter(
|
|
382
|
-
(child) =>
|
|
388
|
+
(child) =>
|
|
389
|
+
child.status === "running" || child.status === "waiting" || isRetryPending(child),
|
|
383
390
|
).length;
|
|
384
391
|
let free = foreachConcurrencyCap(parentNode) - inFlight;
|
|
385
392
|
for (const child of children) {
|