@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
package/src/http/status.ts
CHANGED
|
@@ -29,6 +29,7 @@ import {
|
|
|
29
29
|
getDbClient,
|
|
30
30
|
getInstanceActivity,
|
|
31
31
|
getLiveAgentCounts,
|
|
32
|
+
getReadyAgentCounts,
|
|
32
33
|
hasFirstCompletedTask,
|
|
33
34
|
listAgentsWithCredStatusByProvider,
|
|
34
35
|
NOT_EXTENSION_AGENT_SQL,
|
|
@@ -121,6 +122,12 @@ export type StatusIdentity = z.infer<typeof StatusIdentitySchema>;
|
|
|
121
122
|
export const StatusActivitySchema = z.object({
|
|
122
123
|
agents_online: z.number().int().nonnegative(),
|
|
123
124
|
leads_online: z.number().int().nonnegative(),
|
|
125
|
+
/**
|
|
126
|
+
* Leads that can take work now (status `idle` or `busy`). Unlike
|
|
127
|
+
* `leads_online`, this does not depend on a recent heartbeat: an idle lead
|
|
128
|
+
* stops updating its activity time.
|
|
129
|
+
*/
|
|
130
|
+
leads_ready: z.number().int().nonnegative(),
|
|
124
131
|
recent_tasks_count: z.number().int().nonnegative(),
|
|
125
132
|
});
|
|
126
133
|
export type StatusActivity = z.infer<typeof StatusActivitySchema>;
|
|
@@ -194,12 +201,13 @@ export type AutomationStatus = z.infer<typeof AutomationStatusSchema>;
|
|
|
194
201
|
/**
|
|
195
202
|
* Phase 2: Aggregate health derived from setup milestones.
|
|
196
203
|
*
|
|
197
|
-
* - `broken
|
|
198
|
-
* - `degraded
|
|
199
|
-
*
|
|
200
|
-
*
|
|
201
|
-
*
|
|
202
|
-
*
|
|
204
|
+
* - `broken`: harness or workers blocking (creds missing, no live workers ever).
|
|
205
|
+
* - `degraded`: no lead can take work (none is `idle` or `busy`), OR at
|
|
206
|
+
* least one optional integration `unverified`/`configured` while another
|
|
207
|
+
* is configured, OR harness creds present but the latest live test failed
|
|
208
|
+
* or never ran (`configured`).
|
|
209
|
+
* - `ok`: harness + workers `verified`, a lead can take work, and no
|
|
210
|
+
* integration left in `configured` state.
|
|
203
211
|
*/
|
|
204
212
|
export const StatusHealthSchema = z.enum(["ok", "degraded", "broken"]);
|
|
205
213
|
export type StatusHealth = z.infer<typeof StatusHealthSchema>;
|
|
@@ -692,19 +700,23 @@ export async function buildSetup(
|
|
|
692
700
|
* Decision matrix:
|
|
693
701
|
* - Harness `unverified` (no creds at all) → `broken` — the swarm cannot run a task.
|
|
694
702
|
* - Workers `unverified` (no agents ever) → `broken` — same reason.
|
|
703
|
+
* - No lead can take work (`readyLeads` is 0) → `degraded`. Tasks wait until a lead is online.
|
|
695
704
|
* - Harness `configured` (creds present, never live-tested) → `degraded`.
|
|
696
705
|
* - Any of {slack, github, linear, jira} `configured` (i.e. half-set-up) → `degraded`.
|
|
697
706
|
* - Otherwise → `ok`.
|
|
698
707
|
*
|
|
699
708
|
* Notes:
|
|
709
|
+
* - `readyLeads` counts leads with status `idle` or `busy`
|
|
710
|
+
* (`getReadyAgentCounts`). It does not use the heartbeat time: an agent
|
|
711
|
+
* updates `lastActivityAt` only while a session runs, so an idle, healthy
|
|
712
|
+
* lead looks stale.
|
|
700
713
|
* - Worker `configured` (fleet exists but missed recent heartbeats) does NOT
|
|
701
|
-
* degrade the rollup
|
|
702
|
-
* /agents page and the dashboard canvas — not a setup health signal.
|
|
714
|
+
* degrade the rollup on its own, for the same reason.
|
|
703
715
|
* - Integrations in `unverified` are NOT degrading on their own — most
|
|
704
716
|
* deployments don't connect every integration. They only nudge the rollup if
|
|
705
717
|
* paired with another integration in `configured` (the brainstorm contract).
|
|
706
718
|
*/
|
|
707
|
-
export function computeHealth(setup: SetupMilestone[]): StatusHealth {
|
|
719
|
+
export function computeHealth(setup: SetupMilestone[], readyLeads: number): StatusHealth {
|
|
708
720
|
const byId = new Map(setup.map((m) => [m.id, m] as const));
|
|
709
721
|
const harness = byId.get("harness");
|
|
710
722
|
const workers = byId.get("workers");
|
|
@@ -714,6 +726,7 @@ export function computeHealth(setup: SetupMilestone[]): StatusHealth {
|
|
|
714
726
|
if (!workers || workers.state === "unverified") return "broken";
|
|
715
727
|
|
|
716
728
|
// `degraded` rules.
|
|
729
|
+
if (readyLeads === 0) return "degraded";
|
|
717
730
|
if (harness.state === "configured") return "degraded";
|
|
718
731
|
|
|
719
732
|
for (const id of ["slack", "github", "linear", "jira"] as const) {
|
|
@@ -740,12 +753,13 @@ export async function buildStatusPayload(): Promise<StatusResponse> {
|
|
|
740
753
|
.map((automation) => toStatus(preflightAutomation(automation, automationSetup)))
|
|
741
754
|
.sort((a, b) => a.name.localeCompare(b.name) || a.kind.localeCompare(b.kind));
|
|
742
755
|
const activity = await getInstanceActivity();
|
|
756
|
+
const readyLeads = (await getReadyAgentCounts()).leads;
|
|
743
757
|
const instance = await getInstanceFeedbackSignals();
|
|
744
758
|
return {
|
|
745
759
|
runtime_pools: { enabled: isRuntimePoolsEnabled(), supervisor: runtimeSupervisorStatus() },
|
|
746
760
|
identity: buildIdentity(),
|
|
747
761
|
setup,
|
|
748
|
-
activity,
|
|
762
|
+
activity: { ...activity, leads_ready: readyLeads },
|
|
749
763
|
agent_fs: {
|
|
750
764
|
configured: !!process.env.AGENT_FS_API_URL,
|
|
751
765
|
base_url: process.env.AGENT_FS_API_URL ?? null,
|
|
@@ -753,7 +767,7 @@ export async function buildStatusPayload(): Promise<StatusResponse> {
|
|
|
753
767
|
comb: getCombStatus(await combServiceUserId),
|
|
754
768
|
},
|
|
755
769
|
automations,
|
|
756
|
-
health: computeHealth(setup),
|
|
770
|
+
health: computeHealth(setup, readyLeads),
|
|
757
771
|
telemetry: { enabled: isTelemetryEnabled() },
|
|
758
772
|
instance,
|
|
759
773
|
};
|
|
@@ -797,7 +811,7 @@ const getStatus = route({
|
|
|
797
811
|
pattern: ["status"],
|
|
798
812
|
summary: "Identity + setup readiness + live activity for the swarm dashboard",
|
|
799
813
|
description:
|
|
800
|
-
"Single source of truth consumed by the UI home page. Identity comes from SWARM_* envs; setup milestones each emit `unverified | configured | verified`; automations report `running | needs_setup` from the same runtime preflight used at dispatch; activity counts agents alive in the last 5 min and tasks created in the last 24h; agent_fs reports whether AGENT_FS_API_URL is set, plus the Comb settings (COMB_ENABLED, the browser-facing agent-fs URL, the shared org and drive ids, and the agent-fs user id of the swarm service account); telemetry reports the effective ANONYMIZED_TELEMETRY opt-out state.",
|
|
814
|
+
"Single source of truth consumed by the UI home page. Identity comes from SWARM_* envs; setup milestones each emit `unverified | configured | verified`; automations report `running | needs_setup` from the same runtime preflight used at dispatch; activity counts agents alive in the last 5 min, leads that can take work (status idle or busy), and tasks created in the last 24h; agent_fs reports whether AGENT_FS_API_URL is set, plus the Comb settings (COMB_ENABLED, the browser-facing agent-fs URL, the shared org and drive ids, and the agent-fs user id of the swarm service account); telemetry reports the effective ANONYMIZED_TELEMETRY opt-out state.",
|
|
801
815
|
tags: ["Status"],
|
|
802
816
|
responses: {
|
|
803
817
|
200: { description: "Status payload", schema: StatusResponseSchema },
|
package/src/http/tasks.ts
CHANGED
|
@@ -227,6 +227,8 @@ const listTasks = route({
|
|
|
227
227
|
status: z.string().optional(),
|
|
228
228
|
agentId: z.string().optional(),
|
|
229
229
|
scheduleId: z.string().optional(),
|
|
230
|
+
/** Only tasks of this workflow run (indexed; independent of fleet size). */
|
|
231
|
+
workflowRunId: z.string().uuid().optional(),
|
|
230
232
|
key: AssetKeySchema.optional(),
|
|
231
233
|
keyPrefix: AssetKeySchema.optional(),
|
|
232
234
|
search: z.string().optional(),
|
|
@@ -976,6 +978,7 @@ export async function handleTasks(
|
|
|
976
978
|
status,
|
|
977
979
|
agentId: parsed.query.agentId || undefined,
|
|
978
980
|
scheduleId: parsed.query.scheduleId || undefined,
|
|
981
|
+
workflowRunId: parsed.query.workflowRunId,
|
|
979
982
|
key: parsed.query.key,
|
|
980
983
|
keyPrefix: parsed.query.keyPrefix,
|
|
981
984
|
search: parsed.query.search || undefined,
|
package/src/http/used-by.ts
CHANGED
|
@@ -2,7 +2,7 @@ import type { IncomingMessage, ServerResponse } from "node:http";
|
|
|
2
2
|
import { z } from "zod";
|
|
3
3
|
import { getDbClient, upsertSwarmConfig } from "../be/db";
|
|
4
4
|
import { isTelemetryEnabled, trackRelayed } from "../telemetry";
|
|
5
|
-
import { USER_FUNCTIONS } from "../utils/company-profile";
|
|
5
|
+
import { reportedUserFunction, USER_FUNCTIONS } from "../utils/company-profile";
|
|
6
6
|
import { orgIdentityStatus } from "../utils/org-identity";
|
|
7
7
|
import { normalizeOrgWebsite, TELEMETRY_PROXY_ORIGIN } from "../utils/org-website";
|
|
8
8
|
import { ensureConfigAdmin } from "./config";
|
|
@@ -129,7 +129,12 @@ export async function handleUsedBy(
|
|
|
129
129
|
...(orgDomain ? { org_domain: orgDomain } : {}),
|
|
130
130
|
granted,
|
|
131
131
|
...(granted
|
|
132
|
-
? {
|
|
132
|
+
? {
|
|
133
|
+
contact_email: contactEmail,
|
|
134
|
+
// The proxy rejects a job function its catalog does not list yet.
|
|
135
|
+
contact_function: reportedUserFunction(contactFunction),
|
|
136
|
+
quote,
|
|
137
|
+
}
|
|
133
138
|
: {}),
|
|
134
139
|
changed_at: changedAt,
|
|
135
140
|
}),
|
package/src/http/workflows.ts
CHANGED
|
@@ -5,6 +5,7 @@ import { resolveHttpAuditUserId } from "../be/audit-user";
|
|
|
5
5
|
import {
|
|
6
6
|
createWorkflow,
|
|
7
7
|
deleteWorkflow,
|
|
8
|
+
getDbClient,
|
|
8
9
|
getWorkflow,
|
|
9
10
|
getWorkflowRun,
|
|
10
11
|
getWorkflowRunStepsByRunId,
|
|
@@ -15,7 +16,21 @@ import {
|
|
|
15
16
|
type updateWorkflow,
|
|
16
17
|
withFavoriteFlags,
|
|
17
18
|
} from "../be/db";
|
|
18
|
-
import {
|
|
19
|
+
import {
|
|
20
|
+
assertWorkflowIdempotencyKeyUnused,
|
|
21
|
+
IdempotencyKeyTakenError,
|
|
22
|
+
recordAcceptedWorkflowOperation,
|
|
23
|
+
type WorkflowIdempotencyClaim,
|
|
24
|
+
workflowRequestFingerprint,
|
|
25
|
+
} from "../be/db/workflow-idempotency";
|
|
26
|
+
import {
|
|
27
|
+
getWorkflowRunParent,
|
|
28
|
+
getWorkflowRunStepsSlimByRunId,
|
|
29
|
+
getWorkflowRunSummary,
|
|
30
|
+
getWorkflowRunTree,
|
|
31
|
+
WORKFLOW_RUN_TREE_MAX_DEPTH,
|
|
32
|
+
WorkflowRunTreeError,
|
|
33
|
+
} from "../be/db/workflow-runs";
|
|
19
34
|
import {
|
|
20
35
|
AssetKeySchema,
|
|
21
36
|
AutomationIntegrationIdSchema,
|
|
@@ -31,6 +46,7 @@ import {
|
|
|
31
46
|
WorkflowRunSchema,
|
|
32
47
|
WorkflowRunStatusSchema,
|
|
33
48
|
WorkflowRunStepSchema,
|
|
49
|
+
WorkflowRunStepSlimSchema,
|
|
34
50
|
WorkflowRunSummarySchema,
|
|
35
51
|
WorkflowSchema,
|
|
36
52
|
WorkflowVersionSchema,
|
|
@@ -46,6 +62,11 @@ import { cancelWorkflowRun, retryFailedRun } from "../workflows/resume";
|
|
|
46
62
|
import { handleWebhookTrigger, WebhookError } from "../workflows/triggers";
|
|
47
63
|
import { snapshotAndUpdateWorkflow } from "../workflows/version";
|
|
48
64
|
import { resolveHttpFavoriteOwner } from "./favorite-owner";
|
|
65
|
+
import {
|
|
66
|
+
idempotencyKeyHeader,
|
|
67
|
+
readIdempotencyKey,
|
|
68
|
+
replayAcceptedOperation,
|
|
69
|
+
} from "./idempotency-key";
|
|
49
70
|
import { route } from "./route-def";
|
|
50
71
|
import { jsonError, parseBody, triggerSchemaErrorResponse } from "./utils";
|
|
51
72
|
|
|
@@ -96,6 +117,13 @@ const WorkflowRunPageSchema = z.object({
|
|
|
96
117
|
}),
|
|
97
118
|
});
|
|
98
119
|
|
|
120
|
+
/** Mirrors `WorkflowRunTree` in src/be/db/workflow-runs.ts. */
|
|
121
|
+
const WorkflowRunTreeSchema = z.object({
|
|
122
|
+
rootRunId: z.string(),
|
|
123
|
+
runs: z.array(WorkflowRunSummarySchema),
|
|
124
|
+
hasOpenWork: z.boolean(),
|
|
125
|
+
});
|
|
126
|
+
|
|
99
127
|
const SuccessResponseSchema = z.object({ success: z.literal(true) });
|
|
100
128
|
|
|
101
129
|
/** Adds `warnings` to a just-saved workflow when a node in it cannot run yet. */
|
|
@@ -142,6 +170,7 @@ const createWorkflowRoute = route({
|
|
|
142
170
|
pattern: ["api", "workflows"],
|
|
143
171
|
summary: "Create a new workflow",
|
|
144
172
|
tags: ["Workflows"],
|
|
173
|
+
headers: idempotencyKeyHeader("creates one workflow"),
|
|
145
174
|
body: z.object({
|
|
146
175
|
name: z.string().min(1),
|
|
147
176
|
key: AssetKeySchema.optional(),
|
|
@@ -159,7 +188,9 @@ const createWorkflowRoute = route({
|
|
|
159
188
|
}),
|
|
160
189
|
responses: {
|
|
161
190
|
201: { description: "Workflow created", schema: WorkflowSaveResponseSchema },
|
|
162
|
-
400: { description: "Invalid definition" },
|
|
191
|
+
400: { description: "Invalid definition, or a malformed Idempotency-Key" },
|
|
192
|
+
409: { description: "Idempotency-Key was already used for a different request" },
|
|
193
|
+
410: { description: "The workflow created under this Idempotency-Key was deleted" },
|
|
163
194
|
},
|
|
164
195
|
});
|
|
165
196
|
|
|
@@ -272,16 +303,21 @@ const triggerWorkflowRoute = route({
|
|
|
272
303
|
path: "/api/workflows/{id}/trigger",
|
|
273
304
|
pattern: ["api", "workflows", null, "trigger"],
|
|
274
305
|
summary: "Trigger a workflow execution",
|
|
306
|
+
description:
|
|
307
|
+
"With an `Idempotency-Key`, a retry returns the accepted run's `runId` and `skipped` without starting, resuming or waiting for anything, even after the workflow was disabled or edited and whatever state the run is in now.",
|
|
275
308
|
tags: ["Workflows"],
|
|
276
309
|
params: z.object({ id: z.string() }),
|
|
310
|
+
headers: idempotencyKeyHeader("starts one run"),
|
|
277
311
|
responses: {
|
|
278
312
|
201: {
|
|
279
313
|
description: "Workflow run started (or skipped if cooldown active)",
|
|
280
314
|
schema: z.object({ runId: z.string(), skipped: z.boolean() }),
|
|
281
315
|
},
|
|
282
|
-
400: { description: "Workflow is disabled" },
|
|
316
|
+
400: { description: "Workflow is disabled, or a malformed Idempotency-Key" },
|
|
283
317
|
401: { description: "Unauthorized" },
|
|
284
318
|
404: { description: "Workflow not found" },
|
|
319
|
+
409: { description: "Idempotency-Key was already used for a different request" },
|
|
320
|
+
410: { description: "The workflow triggered under this Idempotency-Key was deleted" },
|
|
285
321
|
},
|
|
286
322
|
});
|
|
287
323
|
|
|
@@ -328,22 +364,50 @@ const getWorkflowRunRoute = route({
|
|
|
328
364
|
path: "/api/workflow-runs/{id}",
|
|
329
365
|
pattern: ["api", "workflow-runs", null],
|
|
330
366
|
summary: "Get a workflow run with steps (includes retry columns)",
|
|
367
|
+
description:
|
|
368
|
+
"Returns the run with its `context` and every step with its `input` by default. A step's `input` is a snapshot of the context when the step started, so in runs with loops (polling, `wait`, iterations) the response grows with steps × context size. Pass `fields=slim` to drop `run.context` and `steps[].input`; outputs, errors, diagnostics, retry fields and `parent` stay.",
|
|
331
369
|
tags: ["Workflows"],
|
|
332
370
|
params: z.object({ id: z.string() }),
|
|
371
|
+
query: z.object({
|
|
372
|
+
/** `slim` omits `run.context` and `steps[].input`; default is full. */
|
|
373
|
+
fields: z.enum(["full", "slim"]).optional(),
|
|
374
|
+
}),
|
|
333
375
|
responses: {
|
|
334
376
|
200: {
|
|
335
377
|
description: "Workflow run details with steps including retry info",
|
|
336
|
-
schema: z.
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
378
|
+
schema: z.union([
|
|
379
|
+
z.object({
|
|
380
|
+
run: WorkflowRunSchema,
|
|
381
|
+
steps: z.array(WorkflowRunStepSchema),
|
|
382
|
+
/** Set on a child run: the run and `sub-workflow` step that started it. */
|
|
383
|
+
parent: WorkflowRunParentSchema.optional(),
|
|
384
|
+
}),
|
|
385
|
+
z.object({
|
|
386
|
+
run: WorkflowRunSummarySchema,
|
|
387
|
+
steps: z.array(WorkflowRunStepSlimSchema),
|
|
388
|
+
parent: WorkflowRunParentSchema.optional(),
|
|
389
|
+
}),
|
|
390
|
+
]),
|
|
342
391
|
},
|
|
343
392
|
404: { description: "Run not found" },
|
|
344
393
|
},
|
|
345
394
|
});
|
|
346
395
|
|
|
396
|
+
const getWorkflowRunTreeRoute = route({
|
|
397
|
+
method: "get",
|
|
398
|
+
path: "/api/workflow-runs/{id}/tree",
|
|
399
|
+
pattern: ["api", "workflow-runs", null, "tree"],
|
|
400
|
+
summary: "Get a workflow run and every sub-workflow run below it",
|
|
401
|
+
description: `Follows stored parent links (\`parentStepId\` → step → run) through every attempt of every step, terminal runs included. \`runs\` holds list rows without \`context\`, root first. \`hasOpenWork\` is true while any run in the tree is live, has a pending, running or waiting step, a queued retry, an open task or a pending approval; after a cancel it turns false once the tree has settled. Both come from one read snapshot. The tree is not paginated. Links that form a cycle or nest deeper than ${WORKFLOW_RUN_TREE_MAX_DEPTH} levels return 409.`,
|
|
402
|
+
tags: ["Workflows"],
|
|
403
|
+
params: z.object({ id: z.string() }),
|
|
404
|
+
responses: {
|
|
405
|
+
200: { description: "The run tree", schema: WorkflowRunTreeSchema },
|
|
406
|
+
404: { description: "Run not found" },
|
|
407
|
+
409: { description: "Stored parent links form a cycle or nest past the depth bound" },
|
|
408
|
+
},
|
|
409
|
+
});
|
|
410
|
+
|
|
347
411
|
const retryWorkflowRunRoute = route({
|
|
348
412
|
method: "post",
|
|
349
413
|
path: "/api/workflow-runs/{id}/retry",
|
|
@@ -574,8 +638,21 @@ export async function handleWorkflows(
|
|
|
574
638
|
}
|
|
575
639
|
|
|
576
640
|
if (createWorkflowRoute.match(req.method, pathSegments)) {
|
|
641
|
+
const idempotency = readIdempotencyKey(req, res, myAgentId);
|
|
642
|
+
if (idempotency === null) return true;
|
|
577
643
|
const parsed = await createWorkflowRoute.parse(req, res, pathSegments, queryParams);
|
|
578
644
|
if (!parsed) return true;
|
|
645
|
+
const claim: WorkflowIdempotencyClaim | undefined = idempotency && {
|
|
646
|
+
...idempotency,
|
|
647
|
+
operation: "workflow.create",
|
|
648
|
+
targetId: "",
|
|
649
|
+
fingerprint: workflowRequestFingerprint("workflow.create", parsed.body),
|
|
650
|
+
};
|
|
651
|
+
const replayCreate = (body: unknown) =>
|
|
652
|
+
createWorkflowRoute.respond(res, 201, body as z.input<typeof WorkflowSaveResponseSchema>);
|
|
653
|
+
// Before validation: a retry gets the original result even if the same
|
|
654
|
+
// definition would no longer validate.
|
|
655
|
+
if (claim && (await replayAcceptedOperation(res, claim, replayCreate))) return true;
|
|
579
656
|
|
|
580
657
|
// Validate definition structure
|
|
581
658
|
const validation = validateDefinition(parsed.body.definition, getExecutorRegistry());
|
|
@@ -603,27 +680,55 @@ export async function handleWorkflows(
|
|
|
603
680
|
throw error;
|
|
604
681
|
}
|
|
605
682
|
|
|
606
|
-
const
|
|
607
|
-
|
|
608
|
-
|
|
609
|
-
|
|
610
|
-
|
|
611
|
-
|
|
612
|
-
|
|
613
|
-
|
|
614
|
-
|
|
615
|
-
|
|
616
|
-
|
|
617
|
-
|
|
618
|
-
|
|
619
|
-
|
|
620
|
-
|
|
621
|
-
|
|
622
|
-
|
|
623
|
-
|
|
624
|
-
"api"
|
|
625
|
-
|
|
626
|
-
|
|
683
|
+
const data: Parameters<typeof createWorkflow>[0] = {
|
|
684
|
+
key,
|
|
685
|
+
name: parsed.body.name,
|
|
686
|
+
description: parsed.body.description,
|
|
687
|
+
definition: parsed.body.definition,
|
|
688
|
+
triggers: parsed.body.triggers,
|
|
689
|
+
cooldown: parsed.body.cooldown,
|
|
690
|
+
input: parsed.body.input,
|
|
691
|
+
triggerSchema: parsed.body.triggerSchema,
|
|
692
|
+
params: parsed.body.params,
|
|
693
|
+
requiredParams: parsed.body.requiredParams,
|
|
694
|
+
requires: parsed.body.requires,
|
|
695
|
+
dir: parsed.body.dir,
|
|
696
|
+
vcsRepo: parsed.body.vcsRepo,
|
|
697
|
+
createdByAgentId: myAgentId ?? undefined,
|
|
698
|
+
createdBy: (await resolveHttpAuditUserId(req, myAgentId)) ?? undefined,
|
|
699
|
+
};
|
|
700
|
+
if (!claim) {
|
|
701
|
+
const workflow = await createWorkflow(data, "api");
|
|
702
|
+
createWorkflowRoute.respond(res, 201, await withSaveWarnings(workflow));
|
|
703
|
+
return true;
|
|
704
|
+
}
|
|
705
|
+
|
|
706
|
+
// Warnings are computed before the transaction (a readiness check may
|
|
707
|
+
// call out) and stored with the workflow, so a retry gets this snapshot,
|
|
708
|
+
// not the workflow as it is by then.
|
|
709
|
+
const warnings = await workflowSaveWarnings(parsed.body.definition, getExecutorRegistry());
|
|
710
|
+
let saved: z.input<typeof WorkflowSaveResponseSchema>;
|
|
711
|
+
try {
|
|
712
|
+
saved = await getDbClient().transaction(async () => {
|
|
713
|
+
// Before the insert: a concurrent winner's workflow holds the same name.
|
|
714
|
+
await assertWorkflowIdempotencyKeyUnused(claim);
|
|
715
|
+
const workflow = await createWorkflow(data, "api");
|
|
716
|
+
const body = warnings.length > 0 ? { ...workflow, warnings } : workflow;
|
|
717
|
+
await recordAcceptedWorkflowOperation(claim, {
|
|
718
|
+
workflowId: workflow.id,
|
|
719
|
+
status: 201,
|
|
720
|
+
body,
|
|
721
|
+
});
|
|
722
|
+
return body;
|
|
723
|
+
});
|
|
724
|
+
} catch (err) {
|
|
725
|
+
// A concurrent request with this key committed first; nothing of this one did.
|
|
726
|
+
if (err instanceof IdempotencyKeyTakenError) {
|
|
727
|
+
if (await replayAcceptedOperation(res, claim, replayCreate)) return true;
|
|
728
|
+
}
|
|
729
|
+
throw err;
|
|
730
|
+
}
|
|
731
|
+
createWorkflowRoute.respond(res, 201, saved);
|
|
627
732
|
return true;
|
|
628
733
|
}
|
|
629
734
|
|
|
@@ -858,8 +963,26 @@ export async function handleWorkflows(
|
|
|
858
963
|
}
|
|
859
964
|
|
|
860
965
|
if (triggerWorkflowRoute.match(req.method, pathSegments)) {
|
|
966
|
+
const idempotency = readIdempotencyKey(req, res, myAgentId);
|
|
967
|
+
if (idempotency === null) return true;
|
|
861
968
|
const parsed = await triggerWorkflowRoute.parse(req, res, pathSegments, queryParams);
|
|
862
969
|
if (!parsed) return true;
|
|
970
|
+
let claim: WorkflowIdempotencyClaim | undefined;
|
|
971
|
+
let keyedBody: Record<string, unknown> | undefined;
|
|
972
|
+
const replayTrigger = (body: unknown) =>
|
|
973
|
+
triggerWorkflowRoute.respond(res, 201, body as { runId: string; skipped: boolean });
|
|
974
|
+
if (idempotency) {
|
|
975
|
+
keyedBody = await parseBody<Record<string, unknown>>(req);
|
|
976
|
+
claim = {
|
|
977
|
+
...idempotency,
|
|
978
|
+
operation: "workflow.trigger",
|
|
979
|
+
targetId: parsed.params.id,
|
|
980
|
+
fingerprint: workflowRequestFingerprint("workflow.trigger", keyedBody),
|
|
981
|
+
};
|
|
982
|
+
// Before the enabled, cooldown, readiness and trigger-schema checks: an
|
|
983
|
+
// accepted trigger replays even after the workflow changed.
|
|
984
|
+
if (await replayAcceptedOperation(res, claim, replayTrigger)) return true;
|
|
985
|
+
}
|
|
863
986
|
const workflow = await getWorkflow(parsed.params.id);
|
|
864
987
|
if (!workflow) {
|
|
865
988
|
res.writeHead(404);
|
|
@@ -870,20 +993,45 @@ export async function handleWorkflows(
|
|
|
870
993
|
jsonError(res, "Workflow is disabled", 400);
|
|
871
994
|
return true;
|
|
872
995
|
}
|
|
873
|
-
|
|
996
|
+
// Not `??`: a JSON `null` body is a parsed body too, and reading the
|
|
997
|
+
// already-ended stream again would wait forever.
|
|
998
|
+
const body = idempotency ? keyedBody : await parseBody<Record<string, unknown>>(req);
|
|
874
999
|
let runId: string;
|
|
1000
|
+
let accepted: { runId: string; skipped: boolean } | undefined;
|
|
875
1001
|
try {
|
|
876
1002
|
runId = await startWorkflowExecution(workflow, body, getExecutorRegistry(), {
|
|
877
1003
|
triggerType: "api",
|
|
878
1004
|
requestedByUserId: (await resolveHttpAuditUserId(req, myAgentId)) ?? undefined,
|
|
1005
|
+
// Recorded in the run's own transaction, before the walk starts.
|
|
1006
|
+
accept: claim
|
|
1007
|
+
? async (run) => {
|
|
1008
|
+
const response = { runId: run.runId, skipped: run.skipped };
|
|
1009
|
+
await recordAcceptedWorkflowOperation(claim, {
|
|
1010
|
+
workflowId: workflow.id,
|
|
1011
|
+
runId: run.runId,
|
|
1012
|
+
status: 201,
|
|
1013
|
+
body: response,
|
|
1014
|
+
});
|
|
1015
|
+
accepted = response;
|
|
1016
|
+
}
|
|
1017
|
+
: undefined,
|
|
879
1018
|
});
|
|
880
1019
|
} catch (err) {
|
|
881
1020
|
if (err instanceof TriggerSchemaError) {
|
|
882
1021
|
triggerSchemaErrorResponse(res, err.message, err.validationErrors);
|
|
883
1022
|
return true;
|
|
884
1023
|
}
|
|
1024
|
+
// A concurrent request with this key committed its run first; this one
|
|
1025
|
+
// created none and walks nothing.
|
|
1026
|
+
if (claim && err instanceof IdempotencyKeyTakenError) {
|
|
1027
|
+
if (await replayAcceptedOperation(res, claim, replayTrigger)) return true;
|
|
1028
|
+
}
|
|
885
1029
|
throw err;
|
|
886
1030
|
}
|
|
1031
|
+
if (accepted) {
|
|
1032
|
+
triggerWorkflowRoute.respond(res, 201, accepted);
|
|
1033
|
+
return true;
|
|
1034
|
+
}
|
|
887
1035
|
|
|
888
1036
|
// Check if skipped due to cooldown
|
|
889
1037
|
const run = await getWorkflowRun(runId);
|
|
@@ -909,18 +1057,44 @@ export async function handleWorkflows(
|
|
|
909
1057
|
const parsed = await getWorkflowRunRoute.parse(req, res, pathSegments, queryParams);
|
|
910
1058
|
if (!parsed) return true;
|
|
911
1059
|
// Display view: the scrubbed context and step outputs, never replay state.
|
|
912
|
-
|
|
1060
|
+
// `fields=slim` skips the context and step-input columns in SQL too.
|
|
1061
|
+
const slim = parsed.query.fields === "slim";
|
|
1062
|
+
const run = slim
|
|
1063
|
+
? await getWorkflowRunSummary(parsed.params.id)
|
|
1064
|
+
: await getWorkflowRun(parsed.params.id, "display");
|
|
913
1065
|
if (!run) {
|
|
914
1066
|
res.writeHead(404);
|
|
915
1067
|
res.end();
|
|
916
1068
|
return true;
|
|
917
1069
|
}
|
|
918
|
-
const steps =
|
|
1070
|
+
const steps = slim
|
|
1071
|
+
? await getWorkflowRunStepsSlimByRunId(parsed.params.id)
|
|
1072
|
+
: await getWorkflowRunStepsByRunId(parsed.params.id, "display");
|
|
919
1073
|
const parent = run.parentStepId ? await getWorkflowRunParent(run.parentStepId) : null;
|
|
920
1074
|
getWorkflowRunRoute.respond(res, 200, { run, steps, ...(parent ? { parent } : {}) });
|
|
921
1075
|
return true;
|
|
922
1076
|
}
|
|
923
1077
|
|
|
1078
|
+
if (getWorkflowRunTreeRoute.match(req.method, pathSegments)) {
|
|
1079
|
+
const parsed = await getWorkflowRunTreeRoute.parse(req, res, pathSegments, queryParams);
|
|
1080
|
+
if (!parsed) return true;
|
|
1081
|
+
let tree: Awaited<ReturnType<typeof getWorkflowRunTree>>;
|
|
1082
|
+
try {
|
|
1083
|
+
tree = await getWorkflowRunTree(parsed.params.id);
|
|
1084
|
+
} catch (err) {
|
|
1085
|
+
if (!(err instanceof WorkflowRunTreeError)) throw err;
|
|
1086
|
+
jsonError(res, err.message, 409);
|
|
1087
|
+
return true;
|
|
1088
|
+
}
|
|
1089
|
+
if (!tree) {
|
|
1090
|
+
res.writeHead(404);
|
|
1091
|
+
res.end();
|
|
1092
|
+
return true;
|
|
1093
|
+
}
|
|
1094
|
+
getWorkflowRunTreeRoute.respond(res, 200, tree);
|
|
1095
|
+
return true;
|
|
1096
|
+
}
|
|
1097
|
+
|
|
924
1098
|
if (retryWorkflowRunRoute.match(req.method, pathSegments)) {
|
|
925
1099
|
const parsed = await retryWorkflowRunRoute.parse(req, res, pathSegments, queryParams);
|
|
926
1100
|
if (!parsed) return true;
|
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
import type { Adapter, Message, Thread } from "chat";
|
|
2
2
|
import { z } from "zod";
|
|
3
|
+
import { sessionGroupKey } from "@/assets/key";
|
|
4
|
+
import { sessionSourceKey } from "@/assets/source-key";
|
|
3
5
|
import {
|
|
4
6
|
getAgentById,
|
|
5
7
|
getAgentWorkingOnContextKey,
|
|
@@ -9,6 +11,7 @@ import {
|
|
|
9
11
|
getLeadAgent,
|
|
10
12
|
isExtensionAgent,
|
|
11
13
|
} from "@/be/db";
|
|
14
|
+
import { rememberSessionLabel } from "@/be/db/session-labels";
|
|
12
15
|
import { resolveIdentity } from "@/be/identity";
|
|
13
16
|
import { recordUnmappedIdentity } from "@/be/unmapped-identities";
|
|
14
17
|
import { dispatchPre } from "@/extensions/dispatcher";
|
|
@@ -373,6 +376,20 @@ async function targetFromExtension(
|
|
|
373
376
|
* Runs outside any transaction, so `pre.chat.route` is dispatched here, after
|
|
374
377
|
* the flush and before any task exists.
|
|
375
378
|
*/
|
|
379
|
+
/**
|
|
380
|
+
* The source group label a Telegram or Discord message gives: the DM sender,
|
|
381
|
+
* or a Telegram group title. Slack labels come from the Slack lookup instead.
|
|
382
|
+
*/
|
|
383
|
+
function chatSourceLabel(adapter: string, item: ChatBufferedMessage): string | undefined {
|
|
384
|
+
if (adapter !== "telegram" && adapter !== "discord") return undefined;
|
|
385
|
+
const { facts } = item;
|
|
386
|
+
if (facts.chatType === "dm" || item.isDirectMessage)
|
|
387
|
+
return facts.from ? `DM · ${facts.from}` : undefined;
|
|
388
|
+
if (adapter === "telegram" && (facts.chatType === "group" || facts.chatType === "supergroup"))
|
|
389
|
+
return facts.title || undefined;
|
|
390
|
+
return undefined;
|
|
391
|
+
}
|
|
392
|
+
|
|
376
393
|
export async function flushChatThread(
|
|
377
394
|
threadId: string,
|
|
378
395
|
items: ChatBufferedMessage[],
|
|
@@ -538,6 +555,7 @@ export async function flushChatThread(
|
|
|
538
555
|
const latestActive = await getLatestActiveTaskByContextKey(contextKey, "chat");
|
|
539
556
|
const dependsOn = !immediate && latestActive ? [latestActive.id] : undefined;
|
|
540
557
|
let created = 0;
|
|
558
|
+
let sourceKey: string | null = null;
|
|
541
559
|
for (const match of target.matches) {
|
|
542
560
|
const options: CreateTaskOptions = {
|
|
543
561
|
agentId: match.agent?.id ?? null,
|
|
@@ -564,6 +582,7 @@ export async function flushChatThread(
|
|
|
564
582
|
};
|
|
565
583
|
try {
|
|
566
584
|
const { task, unattached } = await createChatTaskWithFiles(description, options, inbound);
|
|
585
|
+
sourceKey ??= sessionSourceKey(task);
|
|
567
586
|
created++;
|
|
568
587
|
console.log(
|
|
569
588
|
`[chat-sdk] ${threadId}: task ${task.id} for ${match.agent ? `agent ${match.agent.id}` : "no agent (queued)"} (${match.matchedText}, dependsOn: ${dependsOn?.join(", ") ?? "none"}${unattached.length ? `, ${unattached.length} file(s) not attached` : ""})`,
|
|
@@ -578,6 +597,14 @@ export async function flushChatThread(
|
|
|
578
597
|
}
|
|
579
598
|
}
|
|
580
599
|
}
|
|
600
|
+
const label = chatSourceLabel(adapter, requester);
|
|
601
|
+
if (sourceKey && label) {
|
|
602
|
+
// Every message refreshes the label, so a renamed group shows its new title.
|
|
603
|
+
// The error may echo the label, which can hold a person's name: do not log it.
|
|
604
|
+
await rememberSessionLabel(sessionGroupKey(sourceKey), label).catch(() => {
|
|
605
|
+
console.warn("[chat-sdk] Could not save a session source label");
|
|
606
|
+
});
|
|
607
|
+
}
|
|
581
608
|
if (created > 0) {
|
|
582
609
|
await rememberChatThreadRequester(threadId, requester.userId);
|
|
583
610
|
await subscribe(thread);
|
|
@@ -12,8 +12,20 @@ const RESERVED_FAMILIES = new Map([
|
|
|
12
12
|
["worker", "namespace is reserved for worker nudges"],
|
|
13
13
|
// Durable dedupe for SLACK_BLOCK_EXTERNAL_CHANNELS alerts (src/slack/external-channel.ts).
|
|
14
14
|
["slack-external-alert", "namespace is reserved for Slack external channel alerts"],
|
|
15
|
+
// Source group labels (src/be/db/session-labels.ts). Private, see PRIVATE_FAMILIES.
|
|
16
|
+
[
|
|
17
|
+
"session-labels",
|
|
18
|
+
"namespace is reserved for session source labels; GET /api/session-tree returns them",
|
|
19
|
+
],
|
|
15
20
|
]);
|
|
16
21
|
|
|
22
|
+
/**
|
|
23
|
+
* Reserved families that generic KV callers may not read either. Their rows
|
|
24
|
+
* hold data that another route returns only to callers with access (a
|
|
25
|
+
* source label names a private channel or a DM partner).
|
|
26
|
+
*/
|
|
27
|
+
const PRIVATE_FAMILIES = new Set(["session-labels"]);
|
|
28
|
+
|
|
17
29
|
export function isReservedNamespace(namespace: string): boolean {
|
|
18
30
|
return reservedNamespaceError(namespace) !== null;
|
|
19
31
|
}
|
|
@@ -22,6 +34,13 @@ export function reservedNamespaceError(namespace: string): string | null {
|
|
|
22
34
|
return RESERVED_FAMILIES.get(namespace.split(":", 1)[0] ?? "") ?? null;
|
|
23
35
|
}
|
|
24
36
|
|
|
37
|
+
/** The error a generic KV read of a private family gets, or null. */
|
|
38
|
+
export function privateNamespaceError(namespace: string): string | null {
|
|
39
|
+
return PRIVATE_FAMILIES.has(namespace.split(":", 1)[0] ?? "")
|
|
40
|
+
? reservedNamespaceError(namespace)
|
|
41
|
+
: null;
|
|
42
|
+
}
|
|
43
|
+
|
|
25
44
|
export function reservedRoomKeyError(key: string): string | null {
|
|
26
45
|
return key.startsWith("_room/")
|
|
27
46
|
? "room snapshot keys are reserved; use the room endpoints"
|