@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
|
@@ -104,12 +104,19 @@ async function main() {
|
|
|
104
104
|
);
|
|
105
105
|
|
|
106
106
|
const sessionId = "T-fake-session";
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
107
|
+
// `AMP_TEST_REQUEST_USAGE`: "missing" drops per-request usage, "corrupt" sends non-numbers.
|
|
108
|
+
const requestUsage = process.env.AMP_TEST_REQUEST_USAGE;
|
|
109
|
+
const usage =
|
|
110
|
+
requestUsage === "missing"
|
|
111
|
+
? undefined
|
|
112
|
+
: requestUsage === "corrupt"
|
|
113
|
+
? { input_tokens: "lots", cache_creation_input_tokens: null, cache_read_input_tokens: {} }
|
|
114
|
+
: {
|
|
115
|
+
input_tokens: 4,
|
|
116
|
+
output_tokens: 5,
|
|
117
|
+
cache_creation_input_tokens: 1000,
|
|
118
|
+
cache_read_input_tokens: 200,
|
|
119
|
+
};
|
|
113
120
|
// Amp runs shell commands in a session of its own, outside the process group.
|
|
114
121
|
// Return only once the child has left: a group kill that lands before its
|
|
115
122
|
// setsid() would take it down and hide a leak.
|
|
@@ -217,8 +224,13 @@ async function main() {
|
|
|
217
224
|
session_id: sessionId,
|
|
218
225
|
});
|
|
219
226
|
turns += 1;
|
|
220
|
-
const steerTool = mode === "steer" || mode.startsWith("steer-crash");
|
|
221
|
-
|
|
227
|
+
const steerTool = mode === "steer" || mode === "steer-twice" || mode.startsWith("steer-crash");
|
|
228
|
+
const toolCalls = mode === "steer-twice" ? 2 : 1;
|
|
229
|
+
for (
|
|
230
|
+
let call = 0;
|
|
231
|
+
call < toolCalls && (mode === "tool" || (steerTool && index === 0));
|
|
232
|
+
call++
|
|
233
|
+
) {
|
|
222
234
|
assistant(
|
|
223
235
|
[{ type: "tool_use", id: "TU-1", name: "code_exec", input: { code: "1+1" } }],
|
|
224
236
|
"tool_use",
|
|
@@ -261,6 +273,8 @@ async function main() {
|
|
|
261
273
|
if (mode === "success-child") await startDetachedChild();
|
|
262
274
|
// A turn that thinks for a while with no tool call.
|
|
263
275
|
if (mode === "steer-no-tool") await Bun.sleep(600);
|
|
276
|
+
// `AMP_TEST_POST_RESULT_DELAY_MS`: the final reply takes this long after the last tool result.
|
|
277
|
+
await Bun.sleep(Number(process.env.AMP_TEST_POST_RESULT_DELAY_MS) || 0);
|
|
264
278
|
lastText = mode.startsWith("steer") ? `DONE${index + 1}` : "Done ✓";
|
|
265
279
|
assistant([{ type: "text", text: lastText }], "end_turn");
|
|
266
280
|
};
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
import { configureHttpResolver, resetHttpResolver } from "../../prompts/resolver";
|
|
2
|
+
|
|
3
|
+
export const HELD_NOTE_TEXT = "Finish current work and complete or defer.";
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Point the prompt resolver at a local server whose template responses wait
|
|
7
|
+
* for `release()`, so a test can choose when an async note render finishes.
|
|
8
|
+
* `requested` resolves on the first render request. `stop()` releases,
|
|
9
|
+
* restores the resolver and closes the server.
|
|
10
|
+
*/
|
|
11
|
+
export function holdTemplateRender(): {
|
|
12
|
+
release: () => void;
|
|
13
|
+
requested: Promise<void>;
|
|
14
|
+
stop: () => void;
|
|
15
|
+
} {
|
|
16
|
+
let release!: () => void;
|
|
17
|
+
const gate = new Promise<void>((resolve) => {
|
|
18
|
+
release = resolve;
|
|
19
|
+
});
|
|
20
|
+
let onRequest!: () => void;
|
|
21
|
+
const requested = new Promise<void>((resolve) => {
|
|
22
|
+
onRequest = resolve;
|
|
23
|
+
});
|
|
24
|
+
const server = Bun.serve({
|
|
25
|
+
port: 0,
|
|
26
|
+
async fetch() {
|
|
27
|
+
onRequest();
|
|
28
|
+
await gate;
|
|
29
|
+
return Response.json({ text: HELD_NOTE_TEXT, skipped: false, unresolved: [] });
|
|
30
|
+
},
|
|
31
|
+
});
|
|
32
|
+
configureHttpResolver(`http://localhost:${server.port}`, "test");
|
|
33
|
+
return {
|
|
34
|
+
release,
|
|
35
|
+
requested,
|
|
36
|
+
stop: () => {
|
|
37
|
+
release();
|
|
38
|
+
resetHttpResolver();
|
|
39
|
+
server.stop(true);
|
|
40
|
+
},
|
|
41
|
+
};
|
|
42
|
+
}
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* One keyed workflow trigger on its own DB connection, for
|
|
3
|
+
* `workflow-idempotency.test.ts`. Runs the HTTP handler's sequence: look the
|
|
4
|
+
* key up, then start the run with the key recorded in the run's transaction,
|
|
5
|
+
* and answer from the record when a concurrent process took the key first.
|
|
6
|
+
*
|
|
7
|
+
* Barrier: after the lookup finds nothing it creates `<dir>/looked-<n>` and
|
|
8
|
+
* waits for `<dir>/go`, so the parent can release every child only once all
|
|
9
|
+
* of them are past the lookup and about to race for the write lock.
|
|
10
|
+
*
|
|
11
|
+
* Prints one JSON line: `{ runId, replayed }`.
|
|
12
|
+
*/
|
|
13
|
+
import { join } from "node:path";
|
|
14
|
+
import { getWorkflow, initDb } from "../../be/db";
|
|
15
|
+
import {
|
|
16
|
+
findAcceptedWorkflowOperation,
|
|
17
|
+
IdempotencyKeyTakenError,
|
|
18
|
+
recordAcceptedWorkflowOperation,
|
|
19
|
+
type WorkflowIdempotencyClaim,
|
|
20
|
+
workflowRequestFingerprint,
|
|
21
|
+
} from "../../be/db/workflow-idempotency";
|
|
22
|
+
import { getExecutorRegistry, startWorkflowExecution } from "../../workflows";
|
|
23
|
+
|
|
24
|
+
const [dbPath, workflowId, key, barrierDir, n] = process.argv.slice(2);
|
|
25
|
+
if (!dbPath || !workflowId || !key || !barrierDir || !n) {
|
|
26
|
+
console.error("usage: workflow-idempotency-trigger.child <db> <workflowId> <key> <dir> <n>");
|
|
27
|
+
process.exit(2);
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
const realLog = console.log;
|
|
31
|
+
console.log = () => {};
|
|
32
|
+
console.debug = () => {};
|
|
33
|
+
console.warn = () => {};
|
|
34
|
+
|
|
35
|
+
initDb(dbPath);
|
|
36
|
+
const workflow = await getWorkflow(workflowId);
|
|
37
|
+
if (!workflow) throw new Error(`workflow ${workflowId} not found`);
|
|
38
|
+
|
|
39
|
+
const body = { n: 1 };
|
|
40
|
+
const claim: WorkflowIdempotencyClaim = {
|
|
41
|
+
principal: "agent:concurrency-test",
|
|
42
|
+
operation: "workflow.trigger",
|
|
43
|
+
targetId: workflowId,
|
|
44
|
+
key,
|
|
45
|
+
fingerprint: workflowRequestFingerprint("workflow.trigger", body),
|
|
46
|
+
};
|
|
47
|
+
|
|
48
|
+
if (await findAcceptedWorkflowOperation(claim)) throw new Error("key already accepted");
|
|
49
|
+
await Bun.write(join(barrierDir, `looked-${n}`), "");
|
|
50
|
+
const go = Bun.file(join(barrierDir, "go"));
|
|
51
|
+
while (!(await go.exists())) await Bun.sleep(5);
|
|
52
|
+
|
|
53
|
+
let result: { runId: string; replayed: boolean };
|
|
54
|
+
try {
|
|
55
|
+
const runId = await startWorkflowExecution(workflow, body, getExecutorRegistry(), {
|
|
56
|
+
triggerType: "api",
|
|
57
|
+
accept: (run) =>
|
|
58
|
+
recordAcceptedWorkflowOperation(claim, {
|
|
59
|
+
workflowId,
|
|
60
|
+
runId: run.runId,
|
|
61
|
+
status: 201,
|
|
62
|
+
body: run,
|
|
63
|
+
}),
|
|
64
|
+
});
|
|
65
|
+
result = { runId, replayed: false };
|
|
66
|
+
} catch (err) {
|
|
67
|
+
if (!(err instanceof IdempotencyKeyTakenError)) throw err;
|
|
68
|
+
const accepted = await findAcceptedWorkflowOperation(claim);
|
|
69
|
+
result = { runId: (accepted?.body as { runId: string }).runId, replayed: true };
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
realLog(JSON.stringify(result));
|
|
73
|
+
process.exit(0);
|
|
@@ -326,6 +326,14 @@ describe("GET /api/fleet-benchmark", () => {
|
|
|
326
326
|
expect(fetched).toHaveLength(1);
|
|
327
327
|
});
|
|
328
328
|
|
|
329
|
+
test("a use case the catalog does not list yet compares against the whole fleet", async () => {
|
|
330
|
+
await addTasks(10, "completed", 1);
|
|
331
|
+
await setProfile("2-10", "content");
|
|
332
|
+
const response = await buildFleetBenchmarkResponse(NOW);
|
|
333
|
+
expect(response).toMatchObject({ state: "ready", level: "full", peerGroup: null });
|
|
334
|
+
expect(fetched).toEqual(["http://proxy.test/v1/fleet/benchmarks?window=30d"]);
|
|
335
|
+
});
|
|
336
|
+
|
|
329
337
|
test("full profile tolerates absent or null peer_group from the proxy", async () => {
|
|
330
338
|
await addTasks(10, "completed", 1);
|
|
331
339
|
await setProfile();
|
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
import { afterAll, beforeAll, expect, test } from "bun:test";
|
|
2
|
+
import { unlink } from "node:fs/promises";
|
|
3
|
+
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
|
|
4
|
+
import {
|
|
5
|
+
closeDb,
|
|
6
|
+
createWorkflow,
|
|
7
|
+
createWorkflowRun,
|
|
8
|
+
createWorkflowRunStep,
|
|
9
|
+
initDb,
|
|
10
|
+
updateWorkflowRun,
|
|
11
|
+
updateWorkflowRunStep,
|
|
12
|
+
} from "../be/db";
|
|
13
|
+
import { registerGetWorkflowRunTool } from "../tools/workflows/get-workflow-run";
|
|
14
|
+
|
|
15
|
+
const dbPath = `./test-get-workflow-run-tool-${process.pid}.sqlite`;
|
|
16
|
+
|
|
17
|
+
type ToolResult = {
|
|
18
|
+
isError?: boolean;
|
|
19
|
+
structuredContent: {
|
|
20
|
+
message: string;
|
|
21
|
+
run?: Record<string, unknown>;
|
|
22
|
+
steps?: Array<Record<string, unknown>>;
|
|
23
|
+
};
|
|
24
|
+
};
|
|
25
|
+
let handler: (args: unknown, extra: unknown) => Promise<ToolResult>;
|
|
26
|
+
let runId: string;
|
|
27
|
+
|
|
28
|
+
async function removeDb() {
|
|
29
|
+
for (const suffix of ["", "-wal", "-shm"]) await unlink(dbPath + suffix).catch(() => undefined);
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
beforeAll(async () => {
|
|
33
|
+
await removeDb();
|
|
34
|
+
initDb(dbPath);
|
|
35
|
+
const workflow = await createWorkflow({
|
|
36
|
+
name: "get-workflow-run-tool",
|
|
37
|
+
definition: { nodes: [{ id: "poll", type: "notify", config: { template: "x" } }] },
|
|
38
|
+
});
|
|
39
|
+
runId = crypto.randomUUID();
|
|
40
|
+
await createWorkflowRun({ id: runId, workflowId: workflow.id });
|
|
41
|
+
const context = { poll: { attempts: 2 }, blob: "x".repeat(2_000) };
|
|
42
|
+
for (let i = 0; i < 2; i++) {
|
|
43
|
+
const step = await createWorkflowRunStep({
|
|
44
|
+
id: crypto.randomUUID(),
|
|
45
|
+
runId,
|
|
46
|
+
nodeId: "poll",
|
|
47
|
+
nodeType: "notify",
|
|
48
|
+
input: { ...context, iteration: i },
|
|
49
|
+
});
|
|
50
|
+
await updateWorkflowRunStep(step.id, {
|
|
51
|
+
status: "completed",
|
|
52
|
+
output: { iteration: i },
|
|
53
|
+
retryCount: i,
|
|
54
|
+
finishedAt: new Date().toISOString(),
|
|
55
|
+
});
|
|
56
|
+
}
|
|
57
|
+
await updateWorkflowRun(runId, { status: "completed", context });
|
|
58
|
+
|
|
59
|
+
const server = new McpServer({ name: "get-workflow-run-tool", version: "1.0.0" });
|
|
60
|
+
registerGetWorkflowRunTool(server);
|
|
61
|
+
handler = (server as unknown as { _registeredTools: Record<string, { handler: typeof handler }> })
|
|
62
|
+
._registeredTools["get-workflow-run"]!.handler;
|
|
63
|
+
});
|
|
64
|
+
|
|
65
|
+
afterAll(async () => {
|
|
66
|
+
closeDb();
|
|
67
|
+
await removeDb();
|
|
68
|
+
});
|
|
69
|
+
|
|
70
|
+
const extra = { sessionId: "get-workflow-run-tool", requestInfo: { headers: {} } };
|
|
71
|
+
|
|
72
|
+
test("defaults to the full run with context and step inputs", async () => {
|
|
73
|
+
const result = await handler({ id: runId }, extra);
|
|
74
|
+
expect(result.isError).not.toBe(true);
|
|
75
|
+
const { run, steps } = result.structuredContent;
|
|
76
|
+
expect(run?.context).toBeDefined();
|
|
77
|
+
expect(steps).toHaveLength(2);
|
|
78
|
+
for (const step of steps!) expect(step.input).toBeDefined();
|
|
79
|
+
});
|
|
80
|
+
|
|
81
|
+
test("fields=slim drops context and step inputs, keeps outputs and retry fields", async () => {
|
|
82
|
+
const result = await handler({ id: runId, fields: "slim" }, extra);
|
|
83
|
+
expect(result.isError).not.toBe(true);
|
|
84
|
+
const { run, steps } = result.structuredContent;
|
|
85
|
+
expect(run?.status).toBe("completed");
|
|
86
|
+
expect(run?.context).toBeUndefined();
|
|
87
|
+
expect(steps).toHaveLength(2);
|
|
88
|
+
for (const step of steps!) {
|
|
89
|
+
expect(step.input).toBeUndefined();
|
|
90
|
+
expect(step.output).toBeDefined();
|
|
91
|
+
expect(typeof step.retryCount).toBe("number");
|
|
92
|
+
}
|
|
93
|
+
});
|
|
94
|
+
|
|
95
|
+
test("fields=slim reports a missing run as an error", async () => {
|
|
96
|
+
const result = await handler({ id: crypto.randomUUID(), fields: "slim" }, extra);
|
|
97
|
+
expect(result.isError).toBe(true);
|
|
98
|
+
});
|
|
@@ -21,6 +21,7 @@ const TEST_DB_PATH = "./test-integrations-http.sqlite";
|
|
|
21
21
|
|
|
22
22
|
interface FakeClientLog {
|
|
23
23
|
retrieveCalls: string[];
|
|
24
|
+
apiKeys: string[];
|
|
24
25
|
retrieveResult?: { name?: string | null; model?: string | null };
|
|
25
26
|
retrieveError?: Error;
|
|
26
27
|
}
|
|
@@ -60,7 +61,7 @@ async function clearManagedConfigRows() {
|
|
|
60
61
|
describe("POST /api/integrations/claude-managed/test", () => {
|
|
61
62
|
let server: Server;
|
|
62
63
|
let baseUrl = "";
|
|
63
|
-
const log: FakeClientLog = { retrieveCalls: [] };
|
|
64
|
+
const log: FakeClientLog = { retrieveCalls: [], apiKeys: [] };
|
|
64
65
|
const savedEnv = {
|
|
65
66
|
ANTHROPIC_API_KEY: process.env.ANTHROPIC_API_KEY,
|
|
66
67
|
MANAGED_AGENT_ID: process.env.MANAGED_AGENT_ID,
|
|
@@ -71,7 +72,10 @@ describe("POST /api/integrations/claude-managed/test", () => {
|
|
|
71
72
|
initDb(TEST_DB_PATH);
|
|
72
73
|
|
|
73
74
|
const handler = createIntegrationsHandler({
|
|
74
|
-
buildClient: () =>
|
|
75
|
+
buildClient: (apiKey) => {
|
|
76
|
+
log.apiKeys.push(apiKey);
|
|
77
|
+
return buildFakeClient(log);
|
|
78
|
+
},
|
|
75
79
|
});
|
|
76
80
|
|
|
77
81
|
server = createHttpServer(async (req, res) => {
|
|
@@ -101,6 +105,7 @@ describe("POST /api/integrations/claude-managed/test", () => {
|
|
|
101
105
|
|
|
102
106
|
beforeEach(async () => {
|
|
103
107
|
log.retrieveCalls = [];
|
|
108
|
+
log.apiKeys = [];
|
|
104
109
|
log.retrieveResult = undefined;
|
|
105
110
|
log.retrieveError = undefined;
|
|
106
111
|
await clearManagedConfigRows();
|
|
@@ -138,6 +143,31 @@ describe("POST /api/integrations/claude-managed/test", () => {
|
|
|
138
143
|
expect(log.retrieveCalls).toEqual(["agent_abc123"]);
|
|
139
144
|
});
|
|
140
145
|
|
|
146
|
+
test("a comma pool sends one key to Anthropic", async () => {
|
|
147
|
+
await upsertSwarmConfig({
|
|
148
|
+
scope: "global",
|
|
149
|
+
key: "ANTHROPIC_API_KEY",
|
|
150
|
+
value: "sk-ant-first,sk-ant-second",
|
|
151
|
+
isSecret: true,
|
|
152
|
+
});
|
|
153
|
+
await upsertSwarmConfig({
|
|
154
|
+
scope: "global",
|
|
155
|
+
key: "MANAGED_AGENT_ID",
|
|
156
|
+
value: "agent_abc123",
|
|
157
|
+
isSecret: false,
|
|
158
|
+
});
|
|
159
|
+
|
|
160
|
+
const res = await fetch(`${baseUrl}/api/integrations/claude-managed/test`, {
|
|
161
|
+
method: "POST",
|
|
162
|
+
headers: { "Content-Type": "application/json" },
|
|
163
|
+
body: "{}",
|
|
164
|
+
});
|
|
165
|
+
|
|
166
|
+
expect((await res.json()) as { ok: boolean }).toMatchObject({ ok: true });
|
|
167
|
+
expect(log.apiKeys).toHaveLength(1);
|
|
168
|
+
expect(["sk-ant-first", "sk-ant-second"]).toContain(log.apiKeys[0]);
|
|
169
|
+
});
|
|
170
|
+
|
|
141
171
|
test("missing-config path — returns ok:false with helpful error", async () => {
|
|
142
172
|
// No swarm_config rows, no env. resolveConfigValue should return null
|
|
143
173
|
// for both keys and short-circuit before calling Anthropic.
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { describe, expect, test } from "bun:test";
|
|
1
|
+
import { describe, expect, spyOn, test } from "bun:test";
|
|
2
2
|
import { Type } from "typebox";
|
|
3
3
|
import { z } from "zod";
|
|
4
4
|
import { SummaryWithRatingsSchema } from "../../be/memory/raters/llm.js";
|
|
@@ -501,6 +501,42 @@ describe("completeStructured", () => {
|
|
|
501
501
|
});
|
|
502
502
|
|
|
503
503
|
describe("defaultSpawnClaudeCli", () => {
|
|
504
|
+
for (const mirrored of [false, true]) {
|
|
505
|
+
test(`selects single credentials before CLI spawn (mirror=${mirrored})`, async () => {
|
|
506
|
+
const keys = [
|
|
507
|
+
"CLAUDE_CODE_OAUTH_TOKEN",
|
|
508
|
+
"AGENT_SWARM_CLAUDE_OAUTH_TOKEN",
|
|
509
|
+
"ANTHROPIC_API_KEY",
|
|
510
|
+
];
|
|
511
|
+
const saved = Object.fromEntries(keys.map((key) => [key, process.env[key]]));
|
|
512
|
+
process.env.CLAUDE_CODE_OAUTH_TOKEN = mirrored ? "" : "example-first,example-second";
|
|
513
|
+
process.env.AGENT_SWARM_CLAUDE_OAUTH_TOKEN = "example-first,example-second";
|
|
514
|
+
process.env.ANTHROPIC_API_KEY = "example-first,example-second";
|
|
515
|
+
let capturedEnv: Record<string, string | undefined> | undefined;
|
|
516
|
+
const spawn = spyOn(Bun, "spawn").mockImplementation(((
|
|
517
|
+
...args: Parameters<typeof Bun.spawn>
|
|
518
|
+
) => {
|
|
519
|
+
const options = args[0] as Bun.SpawnOptions.OptionsObject;
|
|
520
|
+
capturedEnv = options.env;
|
|
521
|
+
throw new Error("test stops before CLI spawn");
|
|
522
|
+
}) as typeof Bun.spawn);
|
|
523
|
+
try {
|
|
524
|
+
await expect(defaultSpawnClaudeCli("prompt", "haiku")).rejects.toThrow(
|
|
525
|
+
"test stops before CLI spawn",
|
|
526
|
+
);
|
|
527
|
+
for (const key of ["CLAUDE_CODE_OAUTH_TOKEN", "ANTHROPIC_API_KEY"]) {
|
|
528
|
+
expect(["example-first", "example-second"]).toContain(capturedEnv?.[key]);
|
|
529
|
+
}
|
|
530
|
+
} finally {
|
|
531
|
+
spawn.mockRestore();
|
|
532
|
+
for (const [key, value] of Object.entries(saved)) {
|
|
533
|
+
if (value === undefined) delete process.env[key];
|
|
534
|
+
else process.env[key] = value;
|
|
535
|
+
}
|
|
536
|
+
}
|
|
537
|
+
});
|
|
538
|
+
}
|
|
539
|
+
|
|
504
540
|
test("sets SKIP_SESSION_SUMMARY=1 in the child env (Stop-hook recursion guard)", async () => {
|
|
505
541
|
// Without this guard, the spawned `claude -p` summarizer session fires the
|
|
506
542
|
// same global Stop hook on exit, which spawns another summarizer claude,
|
|
@@ -25,6 +25,29 @@ function makeOpts(
|
|
|
25
25
|
}
|
|
26
26
|
|
|
27
27
|
describe("resolveCredential", () => {
|
|
28
|
+
for (const [keyVar, kind] of [
|
|
29
|
+
["OPENROUTER_API_KEY", "openrouter"],
|
|
30
|
+
["ANTHROPIC_API_KEY", "anthropic"],
|
|
31
|
+
["OPENAI_API_KEY", "openai"],
|
|
32
|
+
] as const) {
|
|
33
|
+
for (const source of ["env", "fallback"] as const) {
|
|
34
|
+
test(`${keyVar} selects one pool member from ${source}`, async () => {
|
|
35
|
+
const pool = "example-first, example-second";
|
|
36
|
+
const cred = await resolveCredential(
|
|
37
|
+
makeOpts({
|
|
38
|
+
env: source === "env" ? { [keyVar]: pool } : {},
|
|
39
|
+
_getEnvApiKey: (provider) =>
|
|
40
|
+
source === "fallback" && provider === kind ? pool : undefined,
|
|
41
|
+
}),
|
|
42
|
+
);
|
|
43
|
+
expect(cred?.kind).toBe(kind);
|
|
44
|
+
expect(
|
|
45
|
+
cred && "apiKey" in cred && ["example-first", "example-second"].includes(cred.apiKey),
|
|
46
|
+
).toBe(true);
|
|
47
|
+
});
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
|
|
28
51
|
test("OPENROUTER_API_KEY wins", async () => {
|
|
29
52
|
const cred = await resolveCredential(makeOpts({ env: { OPENROUTER_API_KEY: "or-1" } }));
|
|
30
53
|
expect(cred).not.toBeNull();
|
|
@@ -362,6 +362,38 @@ describe("/api/kv REST — auth on writes", () => {
|
|
|
362
362
|
expect(intruderWrite.status).toBe(403);
|
|
363
363
|
});
|
|
364
364
|
|
|
365
|
+
test("session source labels are private: generic callers cannot read, list, or write them", async () => {
|
|
366
|
+
const namespace = "session-labels";
|
|
367
|
+
const key = encodeURIComponent("shared/slack/d0private1/");
|
|
368
|
+
await upsertKv({
|
|
369
|
+
namespace,
|
|
370
|
+
key: "shared/slack/d0private1/",
|
|
371
|
+
value: "DM · Lena",
|
|
372
|
+
valueType: "string",
|
|
373
|
+
});
|
|
374
|
+
for (const as of [agentId, leadAgentId]) {
|
|
375
|
+
expect((await authedFetch(`/api/kv/_/${namespace}/${key}`, { agentId: as })).status).toBe(
|
|
376
|
+
403,
|
|
377
|
+
);
|
|
378
|
+
expect((await authedFetch(`/api/kv/_/${namespace}`, { agentId: as })).status).toBe(403);
|
|
379
|
+
for (const method of ["PUT", "DELETE"]) {
|
|
380
|
+
const res = await authedFetch(`/api/kv/_/${namespace}/${key}`, {
|
|
381
|
+
method,
|
|
382
|
+
body:
|
|
383
|
+
method === "PUT" ? JSON.stringify({ value: "forged", valueType: "string" }) : undefined,
|
|
384
|
+
agentId: as,
|
|
385
|
+
});
|
|
386
|
+
expect(res.status).toBe(403);
|
|
387
|
+
}
|
|
388
|
+
}
|
|
389
|
+
expect(
|
|
390
|
+
await getDbClient().get<{ value: string }>(
|
|
391
|
+
"SELECT value FROM kv_entries WHERE namespace = ? AND key = ?",
|
|
392
|
+
[namespace, "shared/slack/d0private1/"],
|
|
393
|
+
),
|
|
394
|
+
).toEqual({ value: "DM · Lena" });
|
|
395
|
+
});
|
|
396
|
+
|
|
365
397
|
test("403 when writing to task:page:* without an X-Page-Id header", async () => {
|
|
366
398
|
const ns = "task:page:abc";
|
|
367
399
|
const res = await authedFetch(`/api/kv/_/${encodeURIComponent(ns)}/k`, {
|
|
@@ -133,6 +133,31 @@ describe("kv MCP tools", () => {
|
|
|
133
133
|
).toBeNull();
|
|
134
134
|
});
|
|
135
135
|
|
|
136
|
+
test("kv-get, kv-list, and kv-set refuse the private session-labels namespace, even for lead", async () => {
|
|
137
|
+
const namespace = "session-labels";
|
|
138
|
+
const key = "shared/slack/d0private1/";
|
|
139
|
+
await getDbClient().run(
|
|
140
|
+
`INSERT INTO kv_entries (namespace, key, value, value_type, created_at, updated_at)
|
|
141
|
+
VALUES (?, ?, 'DM · Lena', 'string', 0, 0)`,
|
|
142
|
+
[namespace, key],
|
|
143
|
+
);
|
|
144
|
+
const tools = buildServer();
|
|
145
|
+
for (const result of [
|
|
146
|
+
await tools.get.handler({ namespace, key }, meta(lead)),
|
|
147
|
+
await tools.list.handler({ namespace }, meta(lead)),
|
|
148
|
+
await tools.set.handler({ namespace, key, value: "forged" }, meta(lead)),
|
|
149
|
+
] as StructuredResult<{ success: boolean; message: string }>[]) {
|
|
150
|
+
expect(result.structuredContent.success).toBe(false);
|
|
151
|
+
expect(result.structuredContent.message).toContain("reserved for session source labels");
|
|
152
|
+
}
|
|
153
|
+
expect(
|
|
154
|
+
await getDbClient().get("SELECT value FROM kv_entries WHERE namespace = ? AND key = ?", [
|
|
155
|
+
namespace,
|
|
156
|
+
key,
|
|
157
|
+
]),
|
|
158
|
+
).toEqual({ value: "DM · Lena" });
|
|
159
|
+
});
|
|
160
|
+
|
|
136
161
|
test("kv-get returns entry=null for missing keys", async () => {
|
|
137
162
|
const tools = buildServer();
|
|
138
163
|
const getRes = (await tools.get.handler({ key: "nope" }, meta(agentA))) as StructuredResult<{
|
|
@@ -2995,11 +2995,19 @@ describe("post-task credential recovery without a restart", () => {
|
|
|
2995
2995
|
ready: true,
|
|
2996
2996
|
lastRefreshAt: 0,
|
|
2997
2997
|
inFlight: false,
|
|
2998
|
+
fingerprint: null,
|
|
2999
|
+
rejectedKeys: 0,
|
|
2998
3000
|
};
|
|
2999
3001
|
const check = spyOn(CREDENTIAL_PROVIDER_CHECKERS, "claude");
|
|
3000
3002
|
const originalOAuth = process.env.CLAUDE_CODE_OAUTH_TOKEN;
|
|
3001
|
-
// The real Claude checker and live-test use OAuth presence, no upstream I/O.
|
|
3002
3003
|
process.env.CLAUDE_CODE_OAUTH_TOKEN = "test-credential-recovery";
|
|
3004
|
+
// The subscription live test calls Anthropic's OAuth profile: answer it locally.
|
|
3005
|
+
const realFetch = globalThis.fetch;
|
|
3006
|
+
globalThis.fetch = (async (input: string | URL | Request, init?: RequestInit) => {
|
|
3007
|
+
const url = input instanceof Request ? input.url : String(input);
|
|
3008
|
+
if (url.startsWith("https://api.anthropic.com/")) return new Response("{}");
|
|
3009
|
+
return realFetch(input, init);
|
|
3010
|
+
}) as typeof fetch;
|
|
3003
3011
|
try {
|
|
3004
3012
|
// A live provider swap produces a real not-ready post-task snapshot.
|
|
3005
3013
|
await refreshCredentialStatus(api, refresh, "claude", {}, 1);
|
|
@@ -3021,6 +3029,8 @@ describe("post-task credential recovery without a restart", () => {
|
|
|
3021
3029
|
await refreshCredentialStatus(api, refresh, "claude", {}, CREDENTIAL_RETRY_INTERVAL_MS + 1);
|
|
3022
3030
|
expect(check).toHaveBeenCalledTimes(2);
|
|
3023
3031
|
expect((await getAgentById(id))?.status).toBe("waiting_for_credentials");
|
|
3032
|
+
// Credentials resolve. A changed credential is checked at once, inside
|
|
3033
|
+
// the retry throttle. Same runner state, provider, and runtime identity.
|
|
3024
3034
|
const env = { CLAUDE_CODE_OAUTH_TOKEN: "test-credential-recovery" };
|
|
3025
3035
|
await refreshCredentialStatus(
|
|
3026
3036
|
api,
|
|
@@ -3029,17 +3039,6 @@ describe("post-task credential recovery without a restart", () => {
|
|
|
3029
3039
|
env,
|
|
3030
3040
|
2 * CREDENTIAL_RETRY_INTERVAL_MS,
|
|
3031
3041
|
);
|
|
3032
|
-
expect(check).toHaveBeenCalledTimes(2);
|
|
3033
|
-
expect((await getAgentById(id))?.status).toBe("waiting_for_credentials");
|
|
3034
|
-
|
|
3035
|
-
// Credentials resolve. Same runner state, provider, and runtime identity.
|
|
3036
|
-
await refreshCredentialStatus(
|
|
3037
|
-
api,
|
|
3038
|
-
refresh,
|
|
3039
|
-
"claude",
|
|
3040
|
-
env,
|
|
3041
|
-
2 * CREDENTIAL_RETRY_INTERVAL_MS + 1,
|
|
3042
|
-
);
|
|
3043
3042
|
expect(check).toHaveBeenCalledTimes(3);
|
|
3044
3043
|
expect((await getAgentById(id))?.status).toBe("idle");
|
|
3045
3044
|
expect((await getAgentById(id))?.credStatus?.ready).toBe(true);
|
|
@@ -3058,6 +3057,7 @@ describe("post-task credential recovery without a restart", () => {
|
|
|
3058
3057
|
);
|
|
3059
3058
|
expect(check).toHaveBeenCalledTimes(3);
|
|
3060
3059
|
} finally {
|
|
3060
|
+
globalThis.fetch = realFetch;
|
|
3061
3061
|
check.mockRestore();
|
|
3062
3062
|
if (originalOAuth === undefined) delete process.env.CLAUDE_CODE_OAUTH_TOKEN;
|
|
3063
3063
|
else process.env.CLAUDE_CODE_OAUTH_TOKEN = originalOAuth;
|