@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/integrations.ts
CHANGED
|
@@ -8,6 +8,7 @@ import {
|
|
|
8
8
|
registerTelegramWebhook,
|
|
9
9
|
} from "../integrations/chat-sdk/status";
|
|
10
10
|
import { buildSlackManifest } from "../slack/manifest";
|
|
11
|
+
import { selectCredential } from "../utils/credentials";
|
|
11
12
|
import { ensureHttpPermission } from "./config";
|
|
12
13
|
import { resolveConfigValue, resolveMcpBaseUrl } from "./config-values";
|
|
13
14
|
import { route } from "./route-def";
|
|
@@ -231,7 +232,10 @@ export function createIntegrationsHandler(deps: TestConnectionDeps = {}) {
|
|
|
231
232
|
}
|
|
232
233
|
|
|
233
234
|
try {
|
|
234
|
-
|
|
235
|
+
// A comma pool is one key per call, as the runner does.
|
|
236
|
+
const client = buildClient(
|
|
237
|
+
selectCredential(apiKey, undefined, "ANTHROPIC_API_KEY").selected,
|
|
238
|
+
);
|
|
235
239
|
const agent = await client.beta.agents.retrieve(agentId);
|
|
236
240
|
// `agent.model` is `BetaManagedAgentsModelConfig` ({id, speed}). Flatten
|
|
237
241
|
// to a string so the UI can render it directly without type guards.
|
package/src/http/kv.ts
CHANGED
|
@@ -12,7 +12,11 @@ import {
|
|
|
12
12
|
upsertKv,
|
|
13
13
|
} from "../be/db";
|
|
14
14
|
import { mcpOverflowAuthError } from "../kv-overflow";
|
|
15
|
-
import {
|
|
15
|
+
import {
|
|
16
|
+
privateNamespaceError,
|
|
17
|
+
reservedNamespaceError,
|
|
18
|
+
reservedRoomKeyError,
|
|
19
|
+
} from "../kv-reserved-namespaces";
|
|
16
20
|
import { hasKvViewArgs, resolveKvView } from "../kv-view";
|
|
17
21
|
import { can } from "../rbac";
|
|
18
22
|
import { agentContextKey, pageContextKey } from "../tasks/context-key";
|
|
@@ -405,8 +409,9 @@ function authorizeRead(
|
|
|
405
409
|
namespace: string,
|
|
406
410
|
ctx: AuthCtx,
|
|
407
411
|
): { status: number; message: string } | null {
|
|
408
|
-
const
|
|
409
|
-
|
|
412
|
+
const readErr =
|
|
413
|
+
privateNamespaceError(namespace) ?? mcpOverflowAuthError(namespace, ctx.callerAgentId);
|
|
414
|
+
return readErr ? { status: 403, message: readErr } : null;
|
|
410
415
|
}
|
|
411
416
|
|
|
412
417
|
function encodeValueOrError(
|
package/src/http/onboarding.ts
CHANGED
|
@@ -21,6 +21,7 @@ import {
|
|
|
21
21
|
} from "../be/onboarding";
|
|
22
22
|
import { assertUrlSafe, publicEndpointSsrfOptions } from "../oauth/mcp-wrapper";
|
|
23
23
|
import { ProviderNameSchema } from "../types";
|
|
24
|
+
import { splitCredentialPool } from "../utils/credentials";
|
|
24
25
|
import { getOpenRouterAttributionHeaders } from "../utils/openrouter-base-url";
|
|
25
26
|
import { ensureConfigAdmin } from "./config";
|
|
26
27
|
import { scheduleIntegrationsReload } from "./core";
|
|
@@ -36,11 +37,14 @@ async function buildSignals(state: OnboardingState): Promise<OnboardingSignals>
|
|
|
36
37
|
const provider = ProviderNameSchema.safeParse(entry.provider);
|
|
37
38
|
if (!provider.success) continue;
|
|
38
39
|
const rollup = await rollupCredStatusForProvider(provider.data);
|
|
40
|
+
const live = rollup.latestLiveTest;
|
|
39
41
|
providers.push({
|
|
40
42
|
provider: provider.data,
|
|
41
43
|
state: rollup.state,
|
|
42
44
|
workers: rollup.workers,
|
|
43
45
|
verifiedWorkers: rollup.verifiedWorkers,
|
|
46
|
+
error: live && !live.ok ? (live.error ?? null) : null,
|
|
47
|
+
warning: live?.ok ? (live.warning ?? null) : null,
|
|
44
48
|
});
|
|
45
49
|
}
|
|
46
50
|
|
|
@@ -241,7 +245,8 @@ async function handleMemoryProbe(body: OnboardingMemoryRequest): Promise<Onboard
|
|
|
241
245
|
body.preset === "existing"
|
|
242
246
|
? (process.env.EMBEDDING_API_KEY ?? process.env.OPENAI_API_KEY)
|
|
243
247
|
: undefined;
|
|
244
|
-
|
|
248
|
+
// Test and save the same pool member.
|
|
249
|
+
const apiKey = splitCredentialPool(explicitKey ?? reusedKey ?? implicitKey ?? "")[0];
|
|
245
250
|
|
|
246
251
|
if (!apiKey) {
|
|
247
252
|
return {
|
|
@@ -0,0 +1,354 @@
|
|
|
1
|
+
import type { IncomingMessage, ServerResponse } from "node:http";
|
|
2
|
+
import { baseUrlEnvVar, providerForKeyVar } from "@desplega/model-catalog";
|
|
3
|
+
import { z } from "zod";
|
|
4
|
+
import {
|
|
5
|
+
deleteSwarmConfigByKey,
|
|
6
|
+
getDbClient,
|
|
7
|
+
getKeyStatuses,
|
|
8
|
+
getSwarmConfigs,
|
|
9
|
+
recordKeyValidation,
|
|
10
|
+
} from "../be/db";
|
|
11
|
+
import { retirePoolKeyStatus } from "../be/db/api-keys";
|
|
12
|
+
import { upsertSwarmConfigWithPolicyMirror } from "../be/multi-runtime";
|
|
13
|
+
import { validateConfigEntry } from "../be/swarm-config-guard";
|
|
14
|
+
import type { SwarmConfig } from "../types";
|
|
15
|
+
import { CREDENTIAL_POOL_VARS, splitCredentialPool } from "../utils/credentials";
|
|
16
|
+
import { checkProviderPoolKey } from "../utils/provider-key-probes";
|
|
17
|
+
import { registerEnvSecrets, scrubSecrets } from "../utils/secret-scrubber";
|
|
18
|
+
import { ensureConfigAdmin, requestSecretReadScope, runConfigWriteSideEffects } from "./config";
|
|
19
|
+
import { getDeploymentEnvValue } from "./core";
|
|
20
|
+
import { route } from "./route-def";
|
|
21
|
+
import { jsonError } from "./utils";
|
|
22
|
+
|
|
23
|
+
export const PoolKeyStatusSchema = z.enum(["ok", "rejected", "rate_limited", "unchecked"]);
|
|
24
|
+
export type PoolKeyStatus = z.infer<typeof PoolKeyStatusSchema>;
|
|
25
|
+
export const PoolKeySchema = z.object({
|
|
26
|
+
index: z.number().int().min(0),
|
|
27
|
+
suffix: z.string(),
|
|
28
|
+
status: PoolKeyStatusSchema,
|
|
29
|
+
error: z.string().nullable(),
|
|
30
|
+
checkedAt: z.string().nullable(),
|
|
31
|
+
rateLimitedUntil: z.string().nullable(),
|
|
32
|
+
});
|
|
33
|
+
export type PoolKey = z.infer<typeof PoolKeySchema>;
|
|
34
|
+
export const PoolKeysResponseSchema = z.object({
|
|
35
|
+
key: z.string(),
|
|
36
|
+
source: z.enum(["stored", "env", "none"]),
|
|
37
|
+
readOnly: z.boolean(),
|
|
38
|
+
keys: z.array(PoolKeySchema),
|
|
39
|
+
});
|
|
40
|
+
export type PoolKeysResponse = z.infer<typeof PoolKeysResponseSchema>;
|
|
41
|
+
export const PoolKeyCheckResultSchema = z.enum(["verified", "rejected", "not_tested"]);
|
|
42
|
+
export type PoolKeyCheckResult = z.infer<typeof PoolKeyCheckResultSchema>;
|
|
43
|
+
export const PoolKeyCheckSchema = z.object({
|
|
44
|
+
suffix: z.string(),
|
|
45
|
+
result: PoolKeyCheckResultSchema,
|
|
46
|
+
httpStatus: z.number().nullable(),
|
|
47
|
+
error: z.string().nullable(),
|
|
48
|
+
});
|
|
49
|
+
export type PoolKeyCheck = z.infer<typeof PoolKeyCheckSchema>;
|
|
50
|
+
|
|
51
|
+
const poolVar = z
|
|
52
|
+
.string()
|
|
53
|
+
.refine(
|
|
54
|
+
(key) => key !== "CODEX_OAUTH" && CREDENTIAL_POOL_VARS.includes(key),
|
|
55
|
+
"Choose a credential pool variable.",
|
|
56
|
+
);
|
|
57
|
+
const getPoolKeys = route({
|
|
58
|
+
method: "get",
|
|
59
|
+
path: "/api/config/pool-keys",
|
|
60
|
+
pattern: ["api", "config", "pool-keys"],
|
|
61
|
+
summary: "List credential pool suffixes and status",
|
|
62
|
+
tags: ["Config"],
|
|
63
|
+
query: z.object({ key: poolVar }),
|
|
64
|
+
responses: {
|
|
65
|
+
200: { description: "Credential pool", schema: PoolKeysResponseSchema },
|
|
66
|
+
400: { description: "Invalid pool variable" },
|
|
67
|
+
403: { description: "Secret read permission required" },
|
|
68
|
+
},
|
|
69
|
+
});
|
|
70
|
+
const addPoolKeys = route({
|
|
71
|
+
method: "post",
|
|
72
|
+
path: "/api/config/pool-keys",
|
|
73
|
+
pattern: ["api", "config", "pool-keys"],
|
|
74
|
+
summary: "Test and append keys to a credential pool",
|
|
75
|
+
tags: ["Config"],
|
|
76
|
+
rbac: { permission: "config.write.any" },
|
|
77
|
+
body: z.object({
|
|
78
|
+
key: poolVar,
|
|
79
|
+
value: z.string(),
|
|
80
|
+
// The OpenRouter card can switch away from a stored gateway before the new key is saved.
|
|
81
|
+
baseUrl: z
|
|
82
|
+
.string()
|
|
83
|
+
.nullable()
|
|
84
|
+
.optional()
|
|
85
|
+
.describe(
|
|
86
|
+
"Base URL for the check only. Null tests the provider default. Omit to use the stored URL.",
|
|
87
|
+
),
|
|
88
|
+
}),
|
|
89
|
+
responses: {
|
|
90
|
+
200: {
|
|
91
|
+
description: "Updated credential pool and checks",
|
|
92
|
+
schema: PoolKeysResponseSchema.extend({ checks: z.array(PoolKeyCheckSchema) }),
|
|
93
|
+
},
|
|
94
|
+
400: { description: "Invalid key" },
|
|
95
|
+
403: { description: "Config administration and permission to read all secrets required" },
|
|
96
|
+
409: { description: "Duplicate key or deployment env pool" },
|
|
97
|
+
422: {
|
|
98
|
+
description: "Provider rejected a key. Nothing was saved.",
|
|
99
|
+
schema: z.object({ error: z.string(), checks: z.array(PoolKeyCheckSchema) }),
|
|
100
|
+
},
|
|
101
|
+
},
|
|
102
|
+
});
|
|
103
|
+
const deletePoolKey = route({
|
|
104
|
+
method: "delete",
|
|
105
|
+
path: "/api/config/pool-keys",
|
|
106
|
+
pattern: ["api", "config", "pool-keys"],
|
|
107
|
+
summary: "Remove one credential pool member",
|
|
108
|
+
tags: ["Config"],
|
|
109
|
+
rbac: { permission: "config.delete.any" },
|
|
110
|
+
query: z.object({
|
|
111
|
+
key: poolVar,
|
|
112
|
+
index: z.coerce.number().int().min(0),
|
|
113
|
+
suffix: z.string().min(1).max(5),
|
|
114
|
+
}),
|
|
115
|
+
responses: {
|
|
116
|
+
200: { description: "Updated credential pool", schema: PoolKeysResponseSchema },
|
|
117
|
+
400: { description: "Invalid pool member" },
|
|
118
|
+
403: { description: "Config administration and permission to read all secrets required" },
|
|
119
|
+
409: { description: "The pool changed or comes from the deployment env" },
|
|
120
|
+
},
|
|
121
|
+
});
|
|
122
|
+
|
|
123
|
+
const ENV_POOL_ERROR = "These keys come from the deployment env. Change them there.";
|
|
124
|
+
|
|
125
|
+
async function readPool(key: string) {
|
|
126
|
+
const [row] = await getSwarmConfigs({ scope: "global", key });
|
|
127
|
+
const envValue = getDeploymentEnvValue(key);
|
|
128
|
+
const source = row ? "stored" : envValue ? "env" : "none";
|
|
129
|
+
const value = row?.value ?? envValue ?? "";
|
|
130
|
+
registerEnvSecrets({ [key]: value });
|
|
131
|
+
return { row, source, members: splitCredentialPool(value) } as const;
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
async function poolResponse(key: string): Promise<PoolKeysResponse> {
|
|
135
|
+
const pool = await readPool(key);
|
|
136
|
+
const statuses = await getKeyStatuses(key, "global", null);
|
|
137
|
+
return {
|
|
138
|
+
key,
|
|
139
|
+
source: pool.source,
|
|
140
|
+
readOnly: pool.source === "env",
|
|
141
|
+
keys: pool.members.map((member, index) => {
|
|
142
|
+
const suffix = member.slice(-5);
|
|
143
|
+
const row = statuses.find((status) => status.keySuffix === suffix);
|
|
144
|
+
const rejected = !!row?.lastValidationFailedAt;
|
|
145
|
+
const limited =
|
|
146
|
+
row?.status === "rate_limited" &&
|
|
147
|
+
!!row.rateLimitedUntil &&
|
|
148
|
+
Date.parse(row.rateLimitedUntil) > Date.now();
|
|
149
|
+
return {
|
|
150
|
+
index,
|
|
151
|
+
suffix,
|
|
152
|
+
status: rejected ? "rejected" : limited ? "rate_limited" : row ? "ok" : "unchecked",
|
|
153
|
+
error: rejected ? scrubSecrets(row?.lastValidationError) || null : null,
|
|
154
|
+
checkedAt: rejected ? row!.lastValidationFailedAt : (row?.updatedAt ?? null),
|
|
155
|
+
rateLimitedUntil: row?.rateLimitedUntil ?? null,
|
|
156
|
+
};
|
|
157
|
+
}),
|
|
158
|
+
};
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
function duplicateError(members: readonly string[], additions: string[]): string | null {
|
|
162
|
+
const seen = new Set(members);
|
|
163
|
+
for (const member of additions) {
|
|
164
|
+
if (seen.has(member)) return `The key ending ${member.slice(-5)} is already in the pool.`;
|
|
165
|
+
seen.add(member);
|
|
166
|
+
}
|
|
167
|
+
return null;
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
async function savePool(key: string, members: string[], row: SwarmConfig | undefined) {
|
|
171
|
+
const value = members.join(",");
|
|
172
|
+
const validationError = validateConfigEntry({
|
|
173
|
+
scope: "global",
|
|
174
|
+
key,
|
|
175
|
+
value,
|
|
176
|
+
exportToEnv: row?.exportToEnv,
|
|
177
|
+
});
|
|
178
|
+
if (validationError) return validationError;
|
|
179
|
+
await upsertSwarmConfigWithPolicyMirror({
|
|
180
|
+
scope: "global",
|
|
181
|
+
key,
|
|
182
|
+
value,
|
|
183
|
+
isSecret: true,
|
|
184
|
+
description: row?.description ?? null,
|
|
185
|
+
envPath: row?.envPath ?? null,
|
|
186
|
+
exportToEnv: row?.exportToEnv,
|
|
187
|
+
});
|
|
188
|
+
return null;
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
/** Every response lists the pool's key suffixes and statuses, so each route needs this gate. */
|
|
192
|
+
async function ensureSecretRead(req: IncomingMessage, res: ServerResponse): Promise<boolean> {
|
|
193
|
+
if ((await requestSecretReadScope(req)) === "all") return true;
|
|
194
|
+
jsonError(res, "Reading credential pools requires permission to read all secrets.", 403);
|
|
195
|
+
return false;
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
export async function handlePoolKeys(
|
|
199
|
+
req: IncomingMessage,
|
|
200
|
+
res: ServerResponse,
|
|
201
|
+
pathSegments: string[],
|
|
202
|
+
queryParams: URLSearchParams,
|
|
203
|
+
): Promise<boolean> {
|
|
204
|
+
if (getPoolKeys.match(req.method, pathSegments)) {
|
|
205
|
+
const parsed = await getPoolKeys.parse(req, res, pathSegments, queryParams);
|
|
206
|
+
if (!parsed) return true;
|
|
207
|
+
if (!(await ensureSecretRead(req, res))) return true;
|
|
208
|
+
getPoolKeys.respond(res, 200, await poolResponse(parsed.query.key));
|
|
209
|
+
return true;
|
|
210
|
+
}
|
|
211
|
+
if (addPoolKeys.match(req.method, pathSegments)) {
|
|
212
|
+
const parsed = await addPoolKeys.parse(req, res, pathSegments, queryParams);
|
|
213
|
+
if (!parsed) return true;
|
|
214
|
+
if (!(await ensureConfigAdmin(req, res, "config.write.any"))) return true;
|
|
215
|
+
if (!(await ensureSecretRead(req, res))) return true;
|
|
216
|
+
const { key, value, baseUrl } = parsed.body;
|
|
217
|
+
const additions = value
|
|
218
|
+
.split(",")
|
|
219
|
+
.map((part) => part.trim())
|
|
220
|
+
.filter(Boolean);
|
|
221
|
+
if (!additions.length) {
|
|
222
|
+
jsonError(res, "Enter a key.", 400);
|
|
223
|
+
return true;
|
|
224
|
+
}
|
|
225
|
+
if (additions.some((part) => /\s/.test(part))) {
|
|
226
|
+
jsonError(res, "A key cannot contain spaces or line breaks.", 400);
|
|
227
|
+
return true;
|
|
228
|
+
}
|
|
229
|
+
registerEnvSecrets({ [key]: additions.join(",") });
|
|
230
|
+
const initial = await readPool(key);
|
|
231
|
+
if (initial.source === "env") {
|
|
232
|
+
jsonError(res, ENV_POOL_ERROR, 409);
|
|
233
|
+
return true;
|
|
234
|
+
}
|
|
235
|
+
const duplicate = duplicateError(initial.members, additions);
|
|
236
|
+
if (duplicate) {
|
|
237
|
+
jsonError(res, scrubSecrets(duplicate), 409);
|
|
238
|
+
return true;
|
|
239
|
+
}
|
|
240
|
+
const env = { ...process.env };
|
|
241
|
+
const provider = providerForKeyVar(key);
|
|
242
|
+
const baseUrlKey =
|
|
243
|
+
key === "OPENROUTER_API_KEY"
|
|
244
|
+
? "OPENROUTER_BASE_URL"
|
|
245
|
+
: provider
|
|
246
|
+
? baseUrlEnvVar(provider)
|
|
247
|
+
: undefined;
|
|
248
|
+
if (baseUrlKey && baseUrl !== undefined) {
|
|
249
|
+
env[baseUrlKey] = baseUrl ?? "";
|
|
250
|
+
} else if (baseUrlKey) {
|
|
251
|
+
const [row] = await getSwarmConfigs({ scope: "global", key: baseUrlKey });
|
|
252
|
+
if (row) env[baseUrlKey] = row.value;
|
|
253
|
+
}
|
|
254
|
+
const checks = await Promise.all(
|
|
255
|
+
additions.map(async (member): Promise<PoolKeyCheck> => {
|
|
256
|
+
try {
|
|
257
|
+
const probe = await checkProviderPoolKey(key, member, env);
|
|
258
|
+
return {
|
|
259
|
+
suffix: member.slice(-5),
|
|
260
|
+
result: probe.skipped
|
|
261
|
+
? "not_tested"
|
|
262
|
+
: probe.ok
|
|
263
|
+
? "verified"
|
|
264
|
+
: probe.httpStatus === 401
|
|
265
|
+
? "rejected"
|
|
266
|
+
: "not_tested",
|
|
267
|
+
httpStatus: probe.httpStatus ?? null,
|
|
268
|
+
error: scrubSecrets(probe.error?.replaceAll(member, "[REDACTED]")) || null,
|
|
269
|
+
};
|
|
270
|
+
} catch {
|
|
271
|
+
return {
|
|
272
|
+
suffix: member.slice(-5),
|
|
273
|
+
result: "not_tested",
|
|
274
|
+
httpStatus: null,
|
|
275
|
+
error: "The provider check did not complete.",
|
|
276
|
+
};
|
|
277
|
+
}
|
|
278
|
+
}),
|
|
279
|
+
);
|
|
280
|
+
const rejected = checks.find((check) => check.result === "rejected");
|
|
281
|
+
if (rejected) {
|
|
282
|
+
addPoolKeys.respond(res, 422, {
|
|
283
|
+
error: scrubSecrets(
|
|
284
|
+
`The key ending ${rejected.suffix} was rejected (HTTP 401). Nothing was saved.`,
|
|
285
|
+
),
|
|
286
|
+
checks,
|
|
287
|
+
});
|
|
288
|
+
return true;
|
|
289
|
+
}
|
|
290
|
+
const result = await getDbClient().transaction(async () => {
|
|
291
|
+
const current = await readPool(key);
|
|
292
|
+
if (current.source === "env") return { error: ENV_POOL_ERROR, status: 409 };
|
|
293
|
+
const duplicate = duplicateError(current.members, additions);
|
|
294
|
+
if (duplicate) return { error: duplicate, status: 409 };
|
|
295
|
+
const members = [...current.members, ...additions];
|
|
296
|
+
const error = await savePool(key, members, current.row);
|
|
297
|
+
if (error) return { error, status: 400 };
|
|
298
|
+
await recordKeyValidation(
|
|
299
|
+
key,
|
|
300
|
+
checks.flatMap((check, index) =>
|
|
301
|
+
check.result === "verified"
|
|
302
|
+
? [{ keyIndex: current.members.length + index, keySuffix: check.suffix, ok: true }]
|
|
303
|
+
: [],
|
|
304
|
+
),
|
|
305
|
+
"global",
|
|
306
|
+
null,
|
|
307
|
+
{ retireAbsent: false },
|
|
308
|
+
);
|
|
309
|
+
return { value: members.join(",") };
|
|
310
|
+
});
|
|
311
|
+
if ("error" in result) {
|
|
312
|
+
jsonError(res, scrubSecrets(result.error), result.status);
|
|
313
|
+
return true;
|
|
314
|
+
}
|
|
315
|
+
await runConfigWriteSideEffects("global", key, result.value);
|
|
316
|
+
addPoolKeys.respond(res, 200, { ...(await poolResponse(key)), checks });
|
|
317
|
+
return true;
|
|
318
|
+
}
|
|
319
|
+
if (deletePoolKey.match(req.method, pathSegments)) {
|
|
320
|
+
const parsed = await deletePoolKey.parse(req, res, pathSegments, queryParams);
|
|
321
|
+
if (!parsed) return true;
|
|
322
|
+
if (!(await ensureConfigAdmin(req, res, "config.delete.any"))) return true;
|
|
323
|
+
if (!(await ensureSecretRead(req, res))) return true;
|
|
324
|
+
const { key, index, suffix } = parsed.query;
|
|
325
|
+
const result = await getDbClient().transaction(async () => {
|
|
326
|
+
const current = await readPool(key);
|
|
327
|
+
if (current.source === "env") return { error: ENV_POOL_ERROR, status: 409 };
|
|
328
|
+
if (!current.members[index] || current.members[index].slice(-5) !== suffix) {
|
|
329
|
+
return { error: "The key list changed. Reload it and try again.", status: 409 };
|
|
330
|
+
}
|
|
331
|
+
const members = current.members.filter((_, position) => position !== index);
|
|
332
|
+
if (members.length) {
|
|
333
|
+
const error = await savePool(key, members, current.row);
|
|
334
|
+
if (error) return { error, status: 400 };
|
|
335
|
+
} else {
|
|
336
|
+
await deleteSwarmConfigByKey("global", null, key);
|
|
337
|
+
}
|
|
338
|
+
await retirePoolKeyStatus(
|
|
339
|
+
key,
|
|
340
|
+
suffix,
|
|
341
|
+
members.map((member, keyIndex) => ({ keySuffix: member.slice(-5), keyIndex })),
|
|
342
|
+
);
|
|
343
|
+
return { value: members.join(",") };
|
|
344
|
+
});
|
|
345
|
+
if ("error" in result) {
|
|
346
|
+
jsonError(res, scrubSecrets(result.error), result.status);
|
|
347
|
+
return true;
|
|
348
|
+
}
|
|
349
|
+
await runConfigWriteSideEffects("global", key, result.value);
|
|
350
|
+
deletePoolKey.respond(res, 200, await poolResponse(key));
|
|
351
|
+
return true;
|
|
352
|
+
}
|
|
353
|
+
return false;
|
|
354
|
+
}
|
package/src/http/sessions.ts
CHANGED
|
@@ -6,6 +6,7 @@ import {
|
|
|
6
6
|
normalizeAssetKey,
|
|
7
7
|
parseAssetKey,
|
|
8
8
|
} from "../assets/key";
|
|
9
|
+
import { SOURCE_GROUPS_READ_ONLY, sourceMoveError, sourceOfKey } from "../assets/source-key";
|
|
9
10
|
import { AssetKeyAuthorizationError, authorizeAssetKeyWrite } from "../be/asset-key-auth";
|
|
10
11
|
import { resolveHttpAuditUserId } from "../be/audit-user";
|
|
11
12
|
import {
|
|
@@ -20,13 +21,19 @@ import {
|
|
|
20
21
|
getTaskById,
|
|
21
22
|
listRecentSessions,
|
|
22
23
|
} from "../be/db";
|
|
24
|
+
import { getSessionLabels } from "../be/db/session-labels";
|
|
23
25
|
import {
|
|
24
26
|
listPrefixRoots,
|
|
25
27
|
listSharedPrefixRoots,
|
|
26
28
|
moveSessionChains,
|
|
27
29
|
type SessionKeyMove,
|
|
28
30
|
} from "../be/db/session-moves";
|
|
29
|
-
import {
|
|
31
|
+
import {
|
|
32
|
+
getSessionGroupKey,
|
|
33
|
+
getSessionReadState,
|
|
34
|
+
getSessionTree,
|
|
35
|
+
triageSessionList,
|
|
36
|
+
} from "../be/db/session-tree";
|
|
30
37
|
import { getUserGrant } from "../be/rbac-roles";
|
|
31
38
|
import {
|
|
32
39
|
getSessionRoot,
|
|
@@ -53,6 +60,7 @@ import {
|
|
|
53
60
|
import { can, isRbacEnabled } from "../rbac";
|
|
54
61
|
import { sessionAccessTopic } from "../realtime/auth";
|
|
55
62
|
import { realtimeBus } from "../realtime/bus";
|
|
63
|
+
import { warmSlackChannelLabels } from "../slack/channel-labels";
|
|
56
64
|
import {
|
|
57
65
|
type AgentTask,
|
|
58
66
|
AgentTaskSchema,
|
|
@@ -152,6 +160,7 @@ const SessionTreeGroupSchema = SessionSectionCountsSchema.extend({
|
|
|
152
160
|
lastActivityAt: z.string(),
|
|
153
161
|
});
|
|
154
162
|
const SessionTreeResponseSchema = z.object({
|
|
163
|
+
labels: z.record(z.string(), z.string()).optional(),
|
|
155
164
|
groups: z.array(SessionTreeGroupSchema),
|
|
156
165
|
inbox: SessionSectionCountsSchema,
|
|
157
166
|
later: SessionSectionCountsSchema,
|
|
@@ -738,7 +747,7 @@ async function checkSessionMove(
|
|
|
738
747
|
actor: SessionActor,
|
|
739
748
|
req: IncomingMessage,
|
|
740
749
|
myAgentId: string | undefined,
|
|
741
|
-
options: { ownerOnly?: boolean } = {},
|
|
750
|
+
options: { ownerOnly?: boolean; destination?: string | null } = {},
|
|
742
751
|
): Promise<{ reason: string } | { taskIds: string[] }> {
|
|
743
752
|
const root = await getTaskById(rootTaskId);
|
|
744
753
|
if (!root) return { reason: SESSION_MOVE_UNAVAILABLE };
|
|
@@ -752,6 +761,8 @@ async function checkSessionMove(
|
|
|
752
761
|
if (!hasSessionAccess(access, "write")) return { reason: "Session write access required" };
|
|
753
762
|
}
|
|
754
763
|
if (root.parentTaskId) return { reason: "Task is not a session root" };
|
|
764
|
+
const sourceError = sourceMoveError(options.destination ?? null, root);
|
|
765
|
+
if (sourceError) return { reason: sourceError };
|
|
755
766
|
const tasks = await getRootTaskChain(root.id);
|
|
756
767
|
for (const task of tasks) {
|
|
757
768
|
if (!(await canMutateTaskNamespace(task, myAgentId, req))) {
|
|
@@ -778,6 +789,8 @@ const getSession = route({
|
|
|
778
789
|
chain: z.array(TaskWithSteeringSchema),
|
|
779
790
|
access: SessionAccessSchema,
|
|
780
791
|
owner: SessionOwnerSchema.nullable(),
|
|
792
|
+
/** The group of `root` with the list's rule: a source thread with two or more roots is its own group. */
|
|
793
|
+
groupKey: z.string(),
|
|
781
794
|
approvals: z.array(SessionApprovalSchema).optional(),
|
|
782
795
|
/** The viewer's read state, with the list's rule. Omitted for callers that act for no user. */
|
|
783
796
|
readState: z.enum(["unread", "read", "later", "done"]).optional(),
|
|
@@ -1012,15 +1025,18 @@ export async function handleSessions(
|
|
|
1012
1025
|
const parsed = await sessionTree.parse(req, res, pathSegments, queryParams);
|
|
1013
1026
|
if (!parsed) return true;
|
|
1014
1027
|
const actor = await resolveSessionActor(req, myAgentId);
|
|
1015
|
-
|
|
1016
|
-
|
|
1017
|
-
|
|
1018
|
-
|
|
1019
|
-
|
|
1020
|
-
|
|
1021
|
-
|
|
1022
|
-
|
|
1028
|
+
const tree = await getSessionTree(actor, undefined, {
|
|
1029
|
+
mine: parsed.query.show !== "all",
|
|
1030
|
+
hideSource: parsed.query.hideSource,
|
|
1031
|
+
showSource: parsed.query.showSource,
|
|
1032
|
+
});
|
|
1033
|
+
// Labels cover hidden sources too: a pane or tab of a hidden source still names its group.
|
|
1034
|
+
const { sourceGroups, slackChannels, threadLabels, ...counts } = tree;
|
|
1035
|
+
const labels = { ...threadLabels, ...(await getSessionLabels(sourceGroups)) };
|
|
1036
|
+
warmSlackChannelLabels(
|
|
1037
|
+
slackChannels.map((id) => `shared/slack/${id}/`).filter((key) => labels[key] === undefined),
|
|
1023
1038
|
);
|
|
1039
|
+
sessionTree.respond(res, 200, { ...counts, labels });
|
|
1024
1040
|
return true;
|
|
1025
1041
|
}
|
|
1026
1042
|
|
|
@@ -1054,7 +1070,9 @@ export async function handleSessions(
|
|
|
1054
1070
|
const skipped: { rootTaskId: string; reason: string }[] = [];
|
|
1055
1071
|
const moves: SessionKeyMove[] = [];
|
|
1056
1072
|
for (const rootId of new Set(parsed.body.rootTaskIds)) {
|
|
1057
|
-
const checked = await checkSessionMove(rootId, actor, req, myAgentId
|
|
1073
|
+
const checked = await checkSessionMove(rootId, actor, req, myAgentId, {
|
|
1074
|
+
destination: key,
|
|
1075
|
+
});
|
|
1058
1076
|
if ("reason" in checked) {
|
|
1059
1077
|
skipped.push({ rootTaskId: rootId, reason: checked.reason });
|
|
1060
1078
|
} else {
|
|
@@ -1076,6 +1094,10 @@ export async function handleSessions(
|
|
|
1076
1094
|
const parsed = await moveSessionPrefix.parse(req, res, pathSegments, queryParams);
|
|
1077
1095
|
if (!parsed) return true;
|
|
1078
1096
|
const from = normalizeAssetKey(parsed.body.from);
|
|
1097
|
+
if (sourceOfKey(from) || (parsed.body.to && sourceOfKey(normalizeAssetKey(parsed.body.to)))) {
|
|
1098
|
+
jsonError(res, SOURCE_GROUPS_READ_ONLY, 400);
|
|
1099
|
+
return true;
|
|
1100
|
+
}
|
|
1079
1101
|
if (parseAssetKey(from).relativeSegments.length === 0) {
|
|
1080
1102
|
jsonError(res, "Cannot move a namespace root", 400);
|
|
1081
1103
|
return true;
|
|
@@ -1126,6 +1148,7 @@ export async function handleSessions(
|
|
|
1126
1148
|
}
|
|
1127
1149
|
const checked = await checkSessionMove(root.id, actor, req, myAgentId, {
|
|
1128
1150
|
ownerOnly: true,
|
|
1151
|
+
destination: nextKey,
|
|
1129
1152
|
});
|
|
1130
1153
|
if ("reason" in checked) {
|
|
1131
1154
|
skipped.push({ rootTaskId: root.id, reason: checked.reason });
|
|
@@ -1175,7 +1198,7 @@ export async function handleSessions(
|
|
|
1175
1198
|
const chain = await getRootTaskChain(parsed.params.rootTaskId);
|
|
1176
1199
|
// Load steering, citations and pending approvals for the already loaded chain.
|
|
1177
1200
|
const tasks = [root, ...chain];
|
|
1178
|
-
const [steering, citations, approvals, read] = await Promise.all([
|
|
1201
|
+
const [steering, citations, approvals, read, groupKey] = await Promise.all([
|
|
1179
1202
|
getTaskSteeringFieldsForTasks(tasks),
|
|
1180
1203
|
getTaskCitationsForTasks(tasks.map((task) => task.id)),
|
|
1181
1204
|
getPendingApprovalsForTasks(tasks.map((task) => task.id)),
|
|
@@ -1184,6 +1207,7 @@ export async function handleSessions(
|
|
|
1184
1207
|
actor.kind === "user" && actor.userId
|
|
1185
1208
|
? getSessionReadState(sessionRoot.id, actor.userId)
|
|
1186
1209
|
: Promise.resolve(null),
|
|
1210
|
+
getSessionGroupKey(root),
|
|
1187
1211
|
]);
|
|
1188
1212
|
const decorate = (task: AgentTask) => ({
|
|
1189
1213
|
...task,
|
|
@@ -1195,6 +1219,7 @@ export async function handleSessions(
|
|
|
1195
1219
|
chain: chain.map(decorate),
|
|
1196
1220
|
access,
|
|
1197
1221
|
owner,
|
|
1222
|
+
groupKey,
|
|
1198
1223
|
...(approvals.length ? { approvals } : {}),
|
|
1199
1224
|
...(read ?? {}),
|
|
1200
1225
|
});
|
package/src/http/stats.ts
CHANGED
|
@@ -86,6 +86,7 @@ const SwarmMetricsSchema = z.object({
|
|
|
86
86
|
agentLog: RetentionTableStatsSchema.optional(),
|
|
87
87
|
events: RetentionTableStatsSchema.optional(),
|
|
88
88
|
contextVersions: RetentionTableStatsSchema.optional(),
|
|
89
|
+
scriptRuns: RetentionTableStatsSchema.optional(),
|
|
89
90
|
}),
|
|
90
91
|
});
|
|
91
92
|
|