@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/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@desplega.ai/agent-swarm",
|
|
3
|
-
"version": "2.
|
|
3
|
+
"version": "2.6.0",
|
|
4
4
|
"description": "agent-swarm.dev — an open-source operating system for AI work: a lead agent breaks goals into tasks, routes them to specialized workers such as Claude Code or Codex, runs each worker in an isolated container, and preserves shared memory, tools, schedules, and review gates so delegated work compounds across sessions.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"agent-swarm.dev",
|
|
@@ -54,7 +54,7 @@
|
|
|
54
54
|
"config": {
|
|
55
55
|
"name": "agent-swarm"
|
|
56
56
|
},
|
|
57
|
-
"packageManager": "bun@1.4.
|
|
57
|
+
"packageManager": "bun@1.4.4",
|
|
58
58
|
"workspaces": [
|
|
59
59
|
"apps/ui",
|
|
60
60
|
"apps/templates-ui",
|
|
@@ -199,10 +199,10 @@
|
|
|
199
199
|
"bun": ">=1.3.12"
|
|
200
200
|
},
|
|
201
201
|
"dependencies": {
|
|
202
|
-
"@agentclientprotocol/sdk": "2.0.
|
|
202
|
+
"@agentclientprotocol/sdk": "2.0.2",
|
|
203
203
|
"@ai-sdk/openai": "^3.0.41",
|
|
204
|
-
"@anthropic-ai/claude-agent-sdk": "0.3.
|
|
205
|
-
"@anthropic-ai/sdk": "^1.
|
|
204
|
+
"@anthropic-ai/claude-agent-sdk": "0.3.310",
|
|
205
|
+
"@anthropic-ai/sdk": "^1.3.0",
|
|
206
206
|
"@asteasolutions/zod-to-openapi": "^8.0.0",
|
|
207
207
|
"@aws-sdk/client-bedrock": "3.1048.0",
|
|
208
208
|
"@chat-adapter/discord": "4.41.1",
|
|
@@ -31,8 +31,8 @@ You should be working in a repository cloned to `/workspace/personal/<repo-name>
|
|
|
31
31
|
- **Show the change, do not narrate it.** Aim for about 250 prose words; code blocks, mermaid, tables and `<details>` do not count. No file-by-file changelog: that belongs in your task output.
|
|
32
32
|
- **Review map**: build it from `git diff --stat <base>...HEAD`. One row per area, biggest risk first, with `(+N/-M)`, a depth (🔍 deep, 👀 skim, ⏭ skip for tests and generated files) and what could break.
|
|
33
33
|
- **Change outline**: 1 or 2 views of the shape of the change. Pseudocode with `+`/`-` lines for logic, mermaid for flow, a tree with paths on the nodes for structure.
|
|
34
|
-
- **Swarm provenance** (when you run in the swarm): fill it from your task context. Link the task (`$APP_URL/tasks/<taskId>`) and its tree (`$APP_URL/sessions/<rootTaskId>`), the Slack permalink of the ask if the task came from Slack, any workflow run, and the plan or research as
|
|
35
|
-
- **Media**: images and mp4s as GitHub user attachments (`github-attach` skill), docs as `agent-fs share-create`
|
|
34
|
+
- **Swarm provenance** (when you run in the swarm): fill it from your task context. Link the task (`$APP_URL/tasks/<taskId>`) and its tree (`$APP_URL/sessions/<rootTaskId>`), the Slack permalink of the ask if the task came from Slack, any workflow run, and the plan or research as its agent-fs live app link (`${AGENT_FS_LIVE_URL}/file/~/<org>/<drive>/<path>`). These auth-gated links go in this section only.
|
|
35
|
+
- **Media**: images and mp4s as GitHub user attachments (`github-attach` skill), docs as the agent-fs live app link under Swarm provenance (an `agent-fs share-create` link only when the requester asks for one or a reader has no agent-fs login). Never a presigned agent-fs URL for docs or videos.
|
|
36
36
|
6. **Create the PR/MR** using `gh pr create` or `glab mr create`. `gh pr create --body` does not apply the repo template, so write the filled description to a file and pass it with `--body-file <file>`.
|
|
37
37
|
7. **Check CI status** — After creating the PR, wait ~30 seconds, then check CI with `gh pr checks <pr-number>` (GitHub) or `glab mr view --json pipelines` (GitLab). If any check is failing, investigate the failure, fix it, push the fix, and re-check. Repeat until CI is green.
|
|
38
38
|
8. **Report** the PR/MR URL and CI status.
|
|
@@ -31,8 +31,8 @@ You should be working in a repository cloned to `/workspace/personal/<repo-name>
|
|
|
31
31
|
- **Show the change, do not narrate it.** Aim for about 250 prose words; code blocks, mermaid, tables and `<details>` do not count. No file-by-file changelog: that belongs in your task output.
|
|
32
32
|
- **Review map**: build it from `git diff --stat <base>...HEAD`. One row per area, biggest risk first, with `(+N/-M)`, a depth (🔍 deep, 👀 skim, ⏭ skip for tests and generated files) and what could break.
|
|
33
33
|
- **Change outline**: 1 or 2 views of the shape of the change. Pseudocode with `+`/`-` lines for logic, mermaid for flow, a tree with paths on the nodes for structure.
|
|
34
|
-
- **Swarm provenance** (when you run in the swarm): fill it from your task context. Link the task (`$APP_URL/tasks/<taskId>`) and its tree (`$APP_URL/sessions/<rootTaskId>`), the Slack permalink of the ask if the task came from Slack, any workflow run, and the plan or research as
|
|
35
|
-
- **Media**: images and mp4s as GitHub user attachments (`github-attach` skill), docs as `agent-fs share-create`
|
|
34
|
+
- **Swarm provenance** (when you run in the swarm): fill it from your task context. Link the task (`$APP_URL/tasks/<taskId>`) and its tree (`$APP_URL/sessions/<rootTaskId>`), the Slack permalink of the ask if the task came from Slack, any workflow run, and the plan or research as its agent-fs live app link (`${AGENT_FS_LIVE_URL}/file/~/<org>/<drive>/<path>`). These auth-gated links go in this section only.
|
|
35
|
+
- **Media**: images and mp4s as GitHub user attachments (`github-attach` skill), docs as the agent-fs live app link under Swarm provenance (an `agent-fs share-create` link only when the requester asks for one or a reader has no agent-fs login). Never a presigned agent-fs URL for docs or videos.
|
|
36
36
|
6. **Create the PR/MR** using `gh pr create` or `glab mr create`. `gh pr create --body` does not apply the repo template, so write the filled description to a file and pass it with `--body-file <file>`.
|
|
37
37
|
7. **Check CI status** — After creating the PR, wait ~30 seconds, then check CI with `gh pr checks <pr-number>` (GitHub) or `glab mr view --json pipelines` (GitLab). If any check is failing, investigate the failure, fix it, push the fix, and re-check. Repeat until CI is green.
|
|
38
38
|
8. **Report** the PR/MR URL and CI status.
|
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
import { chatThreadOfContextKey } from "../tasks/context-key";
|
|
2
|
+
import { normalizeAssetKey, sessionGroupKey } from "./key";
|
|
3
|
+
|
|
4
|
+
export const SESSION_SOURCES = ["slack", "telegram", "discord"] as const;
|
|
5
|
+
export type SessionSource = (typeof SESSION_SOURCES)[number];
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* The source asset key of a root session, or null when the task has no Slack
|
|
9
|
+
* thread and no supported Chat SDK thread. Shapes:
|
|
10
|
+
* - Slack fields or chat-sdk `slack:<channel>:<ts>`: `shared/slack/<channel>/thread:<ts>/`
|
|
11
|
+
* - chat-sdk `slack:<channel>:` (a top-level DM, empty ts): `shared/slack/<channel>/task:<id>/`
|
|
12
|
+
* - `telegram:<chatId>[:<topicId>]`: `shared/telegram/<chatId>/thread:<topicId>/`, else `.../task:<id>/`
|
|
13
|
+
* - `discord:<guild|@me>:<channelId>[:<threadId>]`: `shared/discord/<channelId>/thread:<threadId>/`, else `.../task:<id>/`
|
|
14
|
+
*/
|
|
15
|
+
export function sessionSourceKey({
|
|
16
|
+
id,
|
|
17
|
+
slackChannelId,
|
|
18
|
+
slackThreadTs,
|
|
19
|
+
contextKey,
|
|
20
|
+
}: {
|
|
21
|
+
id: string;
|
|
22
|
+
slackChannelId?: string | null;
|
|
23
|
+
slackThreadTs?: string | null;
|
|
24
|
+
contextKey?: string | null;
|
|
25
|
+
}): string | null {
|
|
26
|
+
let parts: [SessionSource, string | undefined, string | undefined];
|
|
27
|
+
if (slackChannelId && slackThreadTs) {
|
|
28
|
+
parts = ["slack", slackChannelId, slackThreadTs];
|
|
29
|
+
} else {
|
|
30
|
+
const chat = chatThreadOfContextKey(contextKey);
|
|
31
|
+
if (!chat) return null;
|
|
32
|
+
// threadId keeps its adapter prefix, so index 0 is the adapter.
|
|
33
|
+
const ids = chat.threadId.split(":");
|
|
34
|
+
if (chat.adapter === "slack" && ids.length === 3 && !ids[2]) ids.pop();
|
|
35
|
+
if (ids.some((part) => !part)) return null;
|
|
36
|
+
if (chat.adapter === "slack" && (ids.length === 2 || ids.length === 3)) {
|
|
37
|
+
parts = ["slack", ids[1], ids[2]];
|
|
38
|
+
} else if (chat.adapter === "telegram" && (ids.length === 2 || ids.length === 3)) {
|
|
39
|
+
parts = ["telegram", ids[1], ids[2]];
|
|
40
|
+
} else if (chat.adapter === "discord" && (ids.length === 3 || ids.length === 4)) {
|
|
41
|
+
parts = ["discord", ids[2], ids[3]];
|
|
42
|
+
} else {
|
|
43
|
+
return null;
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
const [source, channel, thread] = parts;
|
|
47
|
+
// A "/" inside an id would add key segments and break the group shape.
|
|
48
|
+
if (!channel || channel.includes("/") || thread?.includes("/")) return null;
|
|
49
|
+
try {
|
|
50
|
+
return normalizeAssetKey(
|
|
51
|
+
`shared/${source}/${channel}/${thread ? `thread:${thread}` : `task:${id}`}/`,
|
|
52
|
+
);
|
|
53
|
+
} catch {
|
|
54
|
+
return null;
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
export function sourceOfKey(key: string): SessionSource | null {
|
|
59
|
+
return SESSION_SOURCES.find((source) => key.startsWith(`shared/${source}/`)) ?? null;
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
export const NESTED_THREAD_MIN_ROOTS = 2;
|
|
63
|
+
|
|
64
|
+
export function isSourceThreadKey(key: string): boolean {
|
|
65
|
+
return sourceOfKey(key) !== null && /\/thread:[^/]+\/$/.test(key);
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
/** A source thread with at least two owned roots gets its own nested group. */
|
|
69
|
+
export function sessionThreadGroupKey(key: string, threadRoots: number): string {
|
|
70
|
+
const normalized = normalizeAssetKey(key);
|
|
71
|
+
return isSourceThreadKey(normalized) && threadRoots >= NESTED_THREAD_MIN_ROOTS
|
|
72
|
+
? normalized
|
|
73
|
+
: sessionGroupKey(normalized);
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
export const SOURCE_GROUPS_READ_ONLY = "Source groups are read-only.";
|
|
77
|
+
|
|
78
|
+
/** The source a filter matches: the source of the key prefix, else the raw task source. */
|
|
79
|
+
export function effectiveSource(key: string, source: string): string {
|
|
80
|
+
return sourceOfKey(key) ?? source;
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
/**
|
|
84
|
+
* Why a session root may not move to `destination` (a normalized key), or null.
|
|
85
|
+
* Source groups are read-only: only a move back to the root's own source key
|
|
86
|
+
* (Undo after a move out) may land under a source prefix. Null is No group.
|
|
87
|
+
*/
|
|
88
|
+
export function sourceMoveError(
|
|
89
|
+
destination: string | null,
|
|
90
|
+
root: Parameters<typeof sessionSourceKey>[0],
|
|
91
|
+
): string | null {
|
|
92
|
+
return destination !== null && sourceOfKey(destination) && destination !== sessionSourceKey(root)
|
|
93
|
+
? SOURCE_GROUPS_READ_ONLY
|
|
94
|
+
: null;
|
|
95
|
+
}
|
package/src/be/db/api-keys.ts
CHANGED
|
@@ -68,3 +68,83 @@ export async function recordKeySeatMismatch(
|
|
|
68
68
|
return { planChanged };
|
|
69
69
|
});
|
|
70
70
|
}
|
|
71
|
+
|
|
72
|
+
/** Store definitive live-test results and retire rejections for removed pool members. */
|
|
73
|
+
export async function recordKeyValidation(
|
|
74
|
+
keyType: string,
|
|
75
|
+
keys: Array<{
|
|
76
|
+
keyIndex: number;
|
|
77
|
+
keySuffix: string;
|
|
78
|
+
ok: boolean;
|
|
79
|
+
httpStatus?: number;
|
|
80
|
+
error?: string;
|
|
81
|
+
}>,
|
|
82
|
+
scope = "global",
|
|
83
|
+
scopeId: string | null = null,
|
|
84
|
+
options: { retireAbsent?: boolean } = {},
|
|
85
|
+
): Promise<void> {
|
|
86
|
+
const effectiveScopeId = scopeId ?? "";
|
|
87
|
+
const provider = deriveProviderFromKeyType(keyType);
|
|
88
|
+
await getDbClient().transaction(async (tx) => {
|
|
89
|
+
const now = new Date().toISOString();
|
|
90
|
+
for (const key of keys) {
|
|
91
|
+
// Only 401 means the key is bad. A 403 on a list endpoint can be a valid
|
|
92
|
+
// key without that scope (an OpenAI restricted key, a gateway virtual key).
|
|
93
|
+
if (!key.ok && key.httpStatus !== 401) continue;
|
|
94
|
+
await tx.run(
|
|
95
|
+
`INSERT INTO api_key_status (keyType, keySuffix, keyIndex, scope, scopeId, lastValidationError, lastValidationFailedAt, provider, updatedAt)
|
|
96
|
+
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)
|
|
97
|
+
ON CONFLICT(keyType, keySuffix, scope, scopeId)
|
|
98
|
+
DO UPDATE SET lastValidationError = excluded.lastValidationError,
|
|
99
|
+
lastValidationFailedAt = excluded.lastValidationFailedAt,
|
|
100
|
+
keyIndex = excluded.keyIndex, updatedAt = excluded.updatedAt`,
|
|
101
|
+
[
|
|
102
|
+
keyType,
|
|
103
|
+
key.keySuffix,
|
|
104
|
+
key.keyIndex,
|
|
105
|
+
scope,
|
|
106
|
+
effectiveScopeId,
|
|
107
|
+
key.ok ? null : `HTTP ${key.httpStatus}`,
|
|
108
|
+
key.ok ? null : now,
|
|
109
|
+
provider,
|
|
110
|
+
now,
|
|
111
|
+
],
|
|
112
|
+
);
|
|
113
|
+
}
|
|
114
|
+
if (options.retireAbsent === false) return;
|
|
115
|
+
const placeholders = keys.map(() => "?").join(", ");
|
|
116
|
+
await tx.run(
|
|
117
|
+
`UPDATE api_key_status SET lastValidationError = NULL, lastValidationFailedAt = NULL, updatedAt = ?
|
|
118
|
+
WHERE keyType = ? AND scope = ? AND scopeId = ?
|
|
119
|
+
${keys.length ? `AND keySuffix NOT IN (${placeholders})` : ""}
|
|
120
|
+
AND lastValidationFailedAt IS NOT NULL`,
|
|
121
|
+
[now, keyType, scope, effectiveScopeId, ...keys.map((key) => key.keySuffix)],
|
|
122
|
+
);
|
|
123
|
+
});
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
/**
|
|
127
|
+
* Forget the status of a key that left its pool, and store the new index of
|
|
128
|
+
* each remaining key. A new key that later takes the same index or Codex slot
|
|
129
|
+
* then starts with no rejection, rate limit or auth bench.
|
|
130
|
+
*/
|
|
131
|
+
export async function retirePoolKeyStatus(
|
|
132
|
+
keyType: string,
|
|
133
|
+
removedSuffix: string,
|
|
134
|
+
remaining: Array<{ keySuffix: string; keyIndex: number }>,
|
|
135
|
+
): Promise<void> {
|
|
136
|
+
await getDbClient().transaction(async (tx) => {
|
|
137
|
+
if (!remaining.some((key) => key.keySuffix === removedSuffix)) {
|
|
138
|
+
await tx.run(
|
|
139
|
+
"DELETE FROM api_key_status WHERE keyType = ? AND keySuffix = ? AND scope = 'global' AND scopeId = ''",
|
|
140
|
+
[keyType, removedSuffix],
|
|
141
|
+
);
|
|
142
|
+
}
|
|
143
|
+
for (const key of remaining) {
|
|
144
|
+
await tx.run(
|
|
145
|
+
"UPDATE api_key_status SET keyIndex = ? WHERE keyType = ? AND keySuffix = ? AND scope = 'global' AND scopeId = ''",
|
|
146
|
+
[key.keyIndex, keyType, key.keySuffix],
|
|
147
|
+
);
|
|
148
|
+
}
|
|
149
|
+
});
|
|
150
|
+
}
|
package/src/be/db/approvals.ts
CHANGED
|
@@ -162,3 +162,51 @@ export async function getPendingApprovalsForTasks(taskIds: string[]): Promise<
|
|
|
162
162
|
taskIds,
|
|
163
163
|
);
|
|
164
164
|
}
|
|
165
|
+
|
|
166
|
+
/** The claim key of one Slack thread's cancellation post: `<channel>:<messageTs>`. */
|
|
167
|
+
export function approvalCancellationNotificationKey(target: string, messageTs: string): string {
|
|
168
|
+
return `${target}:${messageTs}`;
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
/**
|
|
172
|
+
* Ids of the cancelled requests of the given runs with a Slack cancellation
|
|
173
|
+
* post not yet delivered (never claimed, or a claim a failed post released).
|
|
174
|
+
* A delivered post never comes back, so a repeat cancel of a settled tree
|
|
175
|
+
* retries only what failed. One query per 500 runs.
|
|
176
|
+
*/
|
|
177
|
+
export async function listUndeliveredCancelledApprovalRequestIds(
|
|
178
|
+
runIds: string[],
|
|
179
|
+
): Promise<string[]> {
|
|
180
|
+
const ids: string[] = [];
|
|
181
|
+
for (let i = 0; i < runIds.length; i += 500) {
|
|
182
|
+
const chunk = runIds.slice(i, i + 500);
|
|
183
|
+
const rows = await getDbClient().query<{
|
|
184
|
+
id: string;
|
|
185
|
+
notificationChannels: string;
|
|
186
|
+
claims: string | null;
|
|
187
|
+
}>(
|
|
188
|
+
`SELECT id, notificationChannels, cancellationNotificationClaims AS claims
|
|
189
|
+
FROM approval_requests
|
|
190
|
+
WHERE workflowRunId IN (${chunk.map(() => "?").join(", ")})
|
|
191
|
+
AND status = 'cancelled' AND notificationChannels IS NOT NULL`,
|
|
192
|
+
chunk,
|
|
193
|
+
);
|
|
194
|
+
for (const row of rows) {
|
|
195
|
+
const claims = row.claims ? (JSON.parse(row.claims) as Record<string, unknown>) : {};
|
|
196
|
+
const channels = JSON.parse(row.notificationChannels) as Array<{
|
|
197
|
+
channel?: string;
|
|
198
|
+
target?: string;
|
|
199
|
+
messageTs?: string;
|
|
200
|
+
}>;
|
|
201
|
+
const undelivered = channels.some(
|
|
202
|
+
(n) =>
|
|
203
|
+
n.channel === "slack" &&
|
|
204
|
+
n.target &&
|
|
205
|
+
n.messageTs &&
|
|
206
|
+
claims[approvalCancellationNotificationKey(n.target, n.messageTs)] !== "delivered",
|
|
207
|
+
);
|
|
208
|
+
if (undelivered) ids.push(row.id);
|
|
209
|
+
}
|
|
210
|
+
}
|
|
211
|
+
return ids;
|
|
212
|
+
}
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import { getDbClient, upsertKv } from "../db";
|
|
2
|
+
|
|
3
|
+
const NAMESPACE = "session-labels";
|
|
4
|
+
|
|
5
|
+
export async function getSessionLabels(keys: string[]): Promise<Record<string, string>> {
|
|
6
|
+
if (keys.length === 0) return {};
|
|
7
|
+
const rows = await getDbClient().query<{ key: string; value: string }>(
|
|
8
|
+
`SELECT key, value FROM kv_entries
|
|
9
|
+
WHERE namespace = ? AND key IN (${keys.map(() => "?").join(", ")})
|
|
10
|
+
AND (expires_at IS NULL OR expires_at > ?)`,
|
|
11
|
+
[NAMESPACE, ...keys, Date.now()],
|
|
12
|
+
);
|
|
13
|
+
return Object.fromEntries(rows.map((row) => [row.key, row.value]));
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
export async function rememberSessionLabel(
|
|
17
|
+
key: string,
|
|
18
|
+
label: string,
|
|
19
|
+
ttlMs?: number,
|
|
20
|
+
): Promise<void> {
|
|
21
|
+
await upsertKv({
|
|
22
|
+
namespace: NAMESPACE,
|
|
23
|
+
key,
|
|
24
|
+
value: label,
|
|
25
|
+
valueType: "string",
|
|
26
|
+
expiresAt: ttlMs === undefined ? null : Date.now() + ttlMs,
|
|
27
|
+
});
|
|
28
|
+
}
|
|
@@ -85,15 +85,14 @@ export async function moveTaskChainKeyInTx(
|
|
|
85
85
|
[task.id, task.id],
|
|
86
86
|
);
|
|
87
87
|
// The namespace is metadata, and lastUpdatedAt tracks task activity, as in updateTaskTitle.
|
|
88
|
-
await client.run(
|
|
89
|
-
key,
|
|
90
|
-
changedBy ?? null,
|
|
91
|
-
|
|
92
|
-
]);
|
|
88
|
+
await client.run(
|
|
89
|
+
'UPDATE agent_tasks SET "key" = ?, updated_by = COALESCE(?, updated_by) WHERE id = ?',
|
|
90
|
+
[key, changedBy ?? null, task.id],
|
|
91
|
+
);
|
|
93
92
|
for (const mapping of mappedFiles) {
|
|
94
93
|
if (mapping.key === key) continue;
|
|
95
94
|
await client.run(
|
|
96
|
-
'UPDATE asset_key_mappings SET "key" = ?, updated_at = ?, updated_by =
|
|
95
|
+
'UPDATE asset_key_mappings SET "key" = ?, updated_at = ?, updated_by = COALESCE(?, updated_by) WHERE id = ?',
|
|
97
96
|
[key, now, changedBy ?? null, mapping.id],
|
|
98
97
|
);
|
|
99
98
|
await insertAssetKeyHistory({
|
|
@@ -4,6 +4,14 @@ import {
|
|
|
4
4
|
SESSION_LEAF_KINDS,
|
|
5
5
|
sessionGroupKey,
|
|
6
6
|
} from "../../assets/key";
|
|
7
|
+
import {
|
|
8
|
+
effectiveSource,
|
|
9
|
+
isSourceThreadKey,
|
|
10
|
+
NESTED_THREAD_MIN_ROOTS,
|
|
11
|
+
SESSION_SOURCES,
|
|
12
|
+
sessionThreadGroupKey,
|
|
13
|
+
sourceOfKey,
|
|
14
|
+
} from "../../assets/source-key";
|
|
7
15
|
import { isTerminalTaskStatus, TERMINAL_TASK_STATUSES } from "../../types";
|
|
8
16
|
import { getDbClient, literalPrefixPattern } from "../db";
|
|
9
17
|
import {
|
|
@@ -14,13 +22,17 @@ import {
|
|
|
14
22
|
|
|
15
23
|
export type SessionReadState = "unread" | "read" | "later" | "done";
|
|
16
24
|
export interface SessionFilters {
|
|
17
|
-
/** Restrict roots to these task sources. */
|
|
25
|
+
/** Restrict roots to these raw task sources (`agent_tasks.source`). */
|
|
18
26
|
source?: string[];
|
|
19
|
-
/**
|
|
27
|
+
/**
|
|
28
|
+
* Leave out roots of these effective sources (the older sessions v2 source
|
|
29
|
+
* filter). The effective source of a root under a source key prefix such as
|
|
30
|
+
* `shared/slack/` is that source, else its raw task source.
|
|
31
|
+
*/
|
|
20
32
|
hideSource?: string[];
|
|
21
33
|
/**
|
|
22
|
-
* Keep only roots of these
|
|
23
|
-
* Unlike `source`, an empty list matches no root.
|
|
34
|
+
* Keep only roots of these effective sources (the sessions v2 source filter,
|
|
35
|
+
* see `hideSource`). Unlike `source`, an empty list matches no root.
|
|
24
36
|
*/
|
|
25
37
|
showSource?: string[];
|
|
26
38
|
/** Match root task text or title with a case-insensitive substring. */
|
|
@@ -74,15 +86,47 @@ function sessionReadStateSql(now = new Date().toISOString()): SqlFragment {
|
|
|
74
86
|
};
|
|
75
87
|
}
|
|
76
88
|
|
|
89
|
+
function sourceKeyRanges() {
|
|
90
|
+
return SESSION_SOURCES.map((source) => ({ source, ...keyPrefixRange(`shared/${source}/`) }));
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
/** Count every owned root at the exact source thread key, independent of viewer filters. */
|
|
94
|
+
export function threadRootsSql(): SqlFragment {
|
|
95
|
+
const key = 'r."key"';
|
|
96
|
+
// Trim the final slash, then all non-slash characters to locate the last segment.
|
|
97
|
+
// For example, shared/slack/c1/thread:1.2/ gives thread:1.2/.
|
|
98
|
+
const leaf = `substr(${key}, length(rtrim(rtrim(${key}, '/'), replace(${key}, '/', ''))) + 1)`;
|
|
99
|
+
const ranges = sourceKeyRanges();
|
|
100
|
+
return {
|
|
101
|
+
sql: `(CASE WHEN (${ranges.map(() => `(${key} >= ? AND ${key} < ?)`).join(" OR ")}) AND ${leaf} GLOB 'thread:?*/'
|
|
102
|
+
THEN (SELECT COUNT(*) FROM agent_tasks session_thread_root WHERE session_thread_root."key" = ${key}
|
|
103
|
+
AND session_thread_root.parentTaskId IS NULL AND session_thread_root.requestedByUserId IS NOT NULL)
|
|
104
|
+
ELSE 0 END)`,
|
|
105
|
+
params: ranges.flatMap(({ lo, hi }) => [lo, hi]),
|
|
106
|
+
};
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
/** A task's group with the list row's rule (`groupKey` on `GET /api/sessions`). */
|
|
110
|
+
export async function getSessionGroupKey(task: { id: string; key: string }): Promise<string> {
|
|
111
|
+
const threadRoots = threadRootsSql();
|
|
112
|
+
const row = await getDbClient().get<{ threadRoots: number }>(
|
|
113
|
+
`SELECT ${threadRoots.sql} AS threadRoots FROM agent_tasks r WHERE r.id = ?`,
|
|
114
|
+
[...threadRoots.params, task.id],
|
|
115
|
+
);
|
|
116
|
+
return sessionThreadGroupKey(task.key, row?.threadRoots ?? 0);
|
|
117
|
+
}
|
|
118
|
+
|
|
77
119
|
function groupCondition(group: string): SqlFragment {
|
|
78
120
|
const { lo, hi } = keyPrefixRange(group);
|
|
79
121
|
// The remaining key must be one resource leaf, with no nested path.
|
|
80
122
|
// SQLite substr/length count code points, so count them here too (not UTF-16 units).
|
|
81
123
|
const start = [...lo].length + 1;
|
|
124
|
+
const threadRoots = threadRootsSql();
|
|
82
125
|
return {
|
|
83
126
|
sql: `(r."key" >= ? AND r."key" < ? AND (r."key" = ? OR (
|
|
84
127
|
instr(substr(r."key", ?, length(r."key") - ?), '/') = 0 AND
|
|
85
|
-
(${SESSION_LEAF_KINDS.map(() => 'substr(r."key", ?) GLOB ?').join(" OR ")})
|
|
128
|
+
(${SESSION_LEAF_KINDS.map(() => 'substr(r."key", ?) GLOB ?').join(" OR ")}) AND
|
|
129
|
+
${threadRoots.sql} < ?
|
|
86
130
|
)))`,
|
|
87
131
|
params: [
|
|
88
132
|
lo,
|
|
@@ -91,10 +135,21 @@ function groupCondition(group: string): SqlFragment {
|
|
|
91
135
|
start,
|
|
92
136
|
start,
|
|
93
137
|
...SESSION_LEAF_KINDS.flatMap((kind) => [start, `${kind}:?*/`]),
|
|
138
|
+
...threadRoots.params,
|
|
139
|
+
NESTED_THREAD_MIN_ROOTS,
|
|
94
140
|
],
|
|
95
141
|
};
|
|
96
142
|
}
|
|
97
143
|
|
|
144
|
+
/** `effectiveSource` of the root `r` in SQL, with key ranges so the key index still serves it. */
|
|
145
|
+
function effectiveSourceSql(): SqlFragment {
|
|
146
|
+
const ranges = sourceKeyRanges();
|
|
147
|
+
return {
|
|
148
|
+
sql: `(CASE ${ranges.map(() => `WHEN r."key" >= ? AND r."key" < ? THEN ?`).join(" ")} ELSE r.source END)`,
|
|
149
|
+
params: ranges.flatMap(({ source, lo, hi }) => [lo, hi, source]),
|
|
150
|
+
};
|
|
151
|
+
}
|
|
152
|
+
|
|
98
153
|
export function sessionRootFilter(opts: SessionFilters = {}): SqlFragment {
|
|
99
154
|
const conditions = ["r.parentTaskId IS NULL"];
|
|
100
155
|
const params: SqlFragment["params"] = [];
|
|
@@ -107,13 +162,22 @@ export function sessionRootFilter(opts: SessionFilters = {}): SqlFragment {
|
|
|
107
162
|
if (sources.length)
|
|
108
163
|
add({ sql: `r.source IN (${sources.map(() => "?").join(",")})`, params: sources });
|
|
109
164
|
const hidden = opts.hideSource?.filter(Boolean) ?? [];
|
|
110
|
-
if (hidden.length)
|
|
111
|
-
|
|
165
|
+
if (hidden.length) {
|
|
166
|
+
const source = effectiveSourceSql();
|
|
167
|
+
add({
|
|
168
|
+
sql: `${source.sql} NOT IN (${hidden.map(() => "?").join(",")})`,
|
|
169
|
+
params: [...source.params, ...hidden],
|
|
170
|
+
});
|
|
171
|
+
}
|
|
112
172
|
if (opts.showSource) {
|
|
113
173
|
const shown = opts.showSource.filter(Boolean);
|
|
174
|
+
const source = effectiveSourceSql();
|
|
114
175
|
add(
|
|
115
176
|
shown.length
|
|
116
|
-
? {
|
|
177
|
+
? {
|
|
178
|
+
sql: `${source.sql} IN (${shown.map(() => "?").join(",")})`,
|
|
179
|
+
params: [...source.params, ...shown],
|
|
180
|
+
}
|
|
117
181
|
: { sql: "0", params: [] },
|
|
118
182
|
);
|
|
119
183
|
}
|
|
@@ -388,8 +452,45 @@ export interface SessionTree {
|
|
|
388
452
|
later: SessionSectionCounts;
|
|
389
453
|
done: SessionSectionCounts;
|
|
390
454
|
noGroup: SessionSectionCounts;
|
|
391
|
-
/** Each root source in this view with its session count, hidden sources included, most first. */
|
|
455
|
+
/** Each effective root source in this view with its session count, hidden sources included, most first. */
|
|
392
456
|
sources: { source: string; total: number }[];
|
|
457
|
+
/** Every source group key in this view, hidden sources included, so a pane of a hidden source still gets its label. */
|
|
458
|
+
sourceGroups: string[];
|
|
459
|
+
/** Slack channel ids backed by visible roots with matching Slack fields, including hidden sources. */
|
|
460
|
+
slackChannels: string[];
|
|
461
|
+
/**
|
|
462
|
+
* The oldest visible root of each nested source thread, including hidden
|
|
463
|
+
* sources: its title, else a preview of its prompt without the thread context.
|
|
464
|
+
*/
|
|
465
|
+
threadLabels: Record<string, string>;
|
|
466
|
+
}
|
|
467
|
+
|
|
468
|
+
/**
|
|
469
|
+
* The first `<thread_context>` block of a prompt, as the Slack and Chat SDK
|
|
470
|
+
* templates write it (each tag alone on its line). It holds the earlier thread
|
|
471
|
+
* messages, so a label cut from the start of the prompt never reaches the ask.
|
|
472
|
+
*/
|
|
473
|
+
const THREAD_OPEN = "<thread_context>";
|
|
474
|
+
const THREAD_CLOSE = "</thread_context>";
|
|
475
|
+
const THREAD_OPEN_AT = `instr(r.task, '${THREAD_OPEN}' || char(10))`;
|
|
476
|
+
const THREAD_CLOSE_AT = `instr(r.task, char(10) || '${THREAD_CLOSE}')`;
|
|
477
|
+
/**
|
|
478
|
+
* The task list preview length. The UI reads this length plus "…" as a list
|
|
479
|
+
* preview and repairs a Slack token or a block that the cut splits
|
|
480
|
+
* (`isListPreview` in `apps/ui/src/lib/prompt-display.ts`).
|
|
481
|
+
*/
|
|
482
|
+
const THREAD_LABEL_LENGTH = 300;
|
|
483
|
+
/** Extra characters for the blank lines that `threadPromptPreview` trims, so the preview stays full. */
|
|
484
|
+
const THREAD_LABEL_TRIM_MARGIN = 100;
|
|
485
|
+
/** The prompt of `r` without its first thread context block, cut for `threadPromptPreview`. */
|
|
486
|
+
const THREAD_PROMPT_SQL = `substr(CASE
|
|
487
|
+
WHEN ${THREAD_OPEN_AT} > 0 AND ${THREAD_CLOSE_AT} > ${THREAD_OPEN_AT}
|
|
488
|
+
THEN substr(r.task, 1, ${THREAD_OPEN_AT} - 1) || substr(r.task, ${THREAD_CLOSE_AT} + ${THREAD_CLOSE.length + 1})
|
|
489
|
+
ELSE r.task END, 1, ${THREAD_LABEL_LENGTH + THREAD_LABEL_TRIM_MARGIN})`;
|
|
490
|
+
|
|
491
|
+
function threadPromptPreview(prompt: string): string {
|
|
492
|
+
const text = prompt.trim();
|
|
493
|
+
return text.length > THREAD_LABEL_LENGTH ? `${text.slice(0, THREAD_LABEL_LENGTH)}…` : text;
|
|
393
494
|
}
|
|
394
495
|
|
|
395
496
|
export async function getSessionTree(
|
|
@@ -405,17 +506,22 @@ export async function getSessionTree(
|
|
|
405
506
|
tree: true,
|
|
406
507
|
mine: opts.mine,
|
|
407
508
|
});
|
|
509
|
+
const threadRoots = threadRootsSql();
|
|
408
510
|
const rows = await getDbClient().query<{
|
|
511
|
+
id: string;
|
|
409
512
|
key: string;
|
|
410
513
|
source: string;
|
|
514
|
+
slackChannelId: string | null;
|
|
411
515
|
lastActivityAt: string;
|
|
412
516
|
latestStatus: string;
|
|
413
517
|
readState: SessionReadState | null;
|
|
414
518
|
needsYou: number;
|
|
519
|
+
threadRoots: number;
|
|
415
520
|
}>(
|
|
416
|
-
`SELECT r."key", r.source, a.lastActivityAt, a.latestStatus, a.readState, a.needsYou
|
|
521
|
+
`SELECT r.id, r."key", r.source, r.slackChannelId, a.lastActivityAt, a.latestStatus, a.readState, a.needsYou,
|
|
522
|
+
${threadRoots.sql} AS threadRoots
|
|
417
523
|
FROM (${query.sql}) a CROSS JOIN agent_tasks r ON r.id = a.root_id`,
|
|
418
|
-
query.params,
|
|
524
|
+
[...threadRoots.params, ...query.params],
|
|
419
525
|
);
|
|
420
526
|
const counts = (): SessionSectionCounts => ({ total: 0, unread: 0, running: 0, needsYou: 0 });
|
|
421
527
|
const result: SessionTree = {
|
|
@@ -425,10 +531,16 @@ export async function getSessionTree(
|
|
|
425
531
|
done: counts(),
|
|
426
532
|
noGroup: counts(),
|
|
427
533
|
sources: [],
|
|
534
|
+
sourceGroups: [],
|
|
535
|
+
slackChannels: [],
|
|
536
|
+
threadLabels: {},
|
|
428
537
|
};
|
|
429
538
|
const hidden = new Set(opts.hideSource);
|
|
430
539
|
const shown = opts.showSource ? new Set(opts.showSource) : null;
|
|
431
540
|
const sources = new Map<string, number>();
|
|
541
|
+
const sourceGroups = new Set<string>();
|
|
542
|
+
const slackChannels = new Set<string>();
|
|
543
|
+
const threadRootIds: string[] = [];
|
|
432
544
|
const groups = new Map<string, SessionTreeGroup>();
|
|
433
545
|
const add = (section: SessionSectionCounts, row: (typeof rows)[number]) => {
|
|
434
546
|
section.total++;
|
|
@@ -437,9 +549,20 @@ export async function getSessionTree(
|
|
|
437
549
|
if (row.needsYou) section.needsYou++;
|
|
438
550
|
};
|
|
439
551
|
for (const row of rows) {
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
const key =
|
|
552
|
+
const source = effectiveSource(row.key, row.source);
|
|
553
|
+
sources.set(source, (sources.get(source) ?? 0) + 1);
|
|
554
|
+
const key = sessionThreadGroupKey(row.key, row.threadRoots);
|
|
555
|
+
const sourceGroupKey = sessionGroupKey(row.key);
|
|
556
|
+
const channelId = /^shared\/slack\/([^/]+)\/$/.exec(sourceGroupKey)?.[1];
|
|
557
|
+
if (channelId && row.slackChannelId?.toLowerCase() === channelId) {
|
|
558
|
+
slackChannels.add(channelId);
|
|
559
|
+
}
|
|
560
|
+
if (row.threadRoots >= NESTED_THREAD_MIN_ROOTS && isSourceThreadKey(row.key)) {
|
|
561
|
+
threadRootIds.push(row.id);
|
|
562
|
+
sourceGroups.add(sourceGroupKey);
|
|
563
|
+
}
|
|
564
|
+
if (sourceOfKey(key)) sourceGroups.add(key);
|
|
565
|
+
if (hidden.has(source) || (shown && !shown.has(source))) continue;
|
|
443
566
|
const noGroup =
|
|
444
567
|
key === "shared/" || (actor.kind === "user" && key === `personal/${actor.userId}/`);
|
|
445
568
|
let group: SessionTreeGroup | undefined;
|
|
@@ -462,6 +585,22 @@ export async function getSessionTree(
|
|
|
462
585
|
result.sources = [...sources]
|
|
463
586
|
.map(([source, total]) => ({ source, total }))
|
|
464
587
|
.sort((a, b) => b.total - a.total || a.source.localeCompare(b.source));
|
|
588
|
+
result.sourceGroups = [...sourceGroups].sort();
|
|
589
|
+
result.slackChannels = [...slackChannels].sort();
|
|
590
|
+
if (threadRootIds.length) {
|
|
591
|
+
// A missing or empty title falls back to a preview of the prompt without its thread context.
|
|
592
|
+
const labels = await getDbClient().query<{ key: string; title: string | null; prompt: string }>(
|
|
593
|
+
`SELECT "key", title, prompt FROM (
|
|
594
|
+
SELECT r."key", NULLIF(trim(r.title), '') AS title, ${THREAD_PROMPT_SQL} AS prompt,
|
|
595
|
+
ROW_NUMBER() OVER (PARTITION BY r."key" ORDER BY r.createdAt, r.rowid) AS ordinal
|
|
596
|
+
FROM agent_tasks r WHERE r.id IN (${threadRootIds.map(() => "?").join(",")})
|
|
597
|
+
) WHERE ordinal = 1`,
|
|
598
|
+
threadRootIds,
|
|
599
|
+
);
|
|
600
|
+
result.threadLabels = Object.fromEntries(
|
|
601
|
+
labels.map((row) => [row.key, row.title ?? threadPromptPreview(row.prompt)]),
|
|
602
|
+
);
|
|
603
|
+
}
|
|
465
604
|
return result;
|
|
466
605
|
}
|
|
467
606
|
|
package/src/be/db/tasks/read.ts
CHANGED
|
@@ -803,6 +803,7 @@ export interface TaskFilters {
|
|
|
803
803
|
taskType?: string;
|
|
804
804
|
tags?: string[];
|
|
805
805
|
scheduleId?: string;
|
|
806
|
+
workflowRunId?: string;
|
|
806
807
|
/** Exact canonical asset namespace. */
|
|
807
808
|
key?: string;
|
|
808
809
|
/** Canonical namespace subtree prefix. */
|
|
@@ -937,6 +938,11 @@ export async function getAllTasks(
|
|
|
937
938
|
params.push(filters.scheduleId);
|
|
938
939
|
}
|
|
939
940
|
|
|
941
|
+
if (filters?.workflowRunId) {
|
|
942
|
+
conditions.push("workflowRunId = ?");
|
|
943
|
+
params.push(filters.workflowRunId);
|
|
944
|
+
}
|
|
945
|
+
|
|
940
946
|
if (filters?.key) {
|
|
941
947
|
conditions.push('"key" = ?');
|
|
942
948
|
params.push(normalizeAssetKey(filters.key));
|
|
@@ -1103,6 +1109,11 @@ export async function getTasksCount(
|
|
|
1103
1109
|
params.push(filters.scheduleId);
|
|
1104
1110
|
}
|
|
1105
1111
|
|
|
1112
|
+
if (filters?.workflowRunId) {
|
|
1113
|
+
conditions.push("workflowRunId = ?");
|
|
1114
|
+
params.push(filters.workflowRunId);
|
|
1115
|
+
}
|
|
1116
|
+
|
|
1106
1117
|
if (filters?.key) {
|
|
1107
1118
|
conditions.push('"key" = ?');
|
|
1108
1119
|
params.push(normalizeAssetKey(filters.key));
|