@desplega.ai/agent-swarm 2.4.4 → 2.5.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 +4 -2
- package/dist/{acp-adapter-thq04adh.js → acp-adapter-11gp67nr.js} +8 -6
- package/dist/{actions-j6ywh6x5.js → actions-8p2kze8j.js} +24 -22
- package/dist/{amp-adapter-k8b5xmj8.js → amp-adapter-z4qvhacj.js} +2 -2
- package/dist/{anthropic-messages-g6pqmqk4.js → anthropic-messages-txs190dd.js} +17 -20
- package/dist/{app-rb7vc02s.js → app-0qp0a2gk.js} +3 -3
- package/dist/{assistant-ftta1zjf.js → assistant-jedd3ktm.js} +27 -25
- package/dist/{azure-openai-responses-9dbmj346.js → azure-openai-responses-9kfwhmn7.js} +18 -13
- package/dist/{boot-reembed-xns7h5h9.js → boot-reembed-4xwq5tg0.js} +1 -1
- package/dist/{boot-reembed-srp6v6af.js → boot-reembed-n4wq5dd2.js} +1 -1
- package/dist/{boot-scrub-sweep-3qqrh2mz.js → boot-scrub-sweep-69h90xcq.js} +4 -4
- package/dist/{boot-seeds-9ks8hce7.js → boot-seeds-3vn0m9sg.js} +4 -4
- package/dist/{claude-adapter-k5ksyres.js → claude-adapter-221mvad5.js} +8 -6
- package/dist/{claude-managed-adapter-h2y889ge.js → claude-managed-adapter-gt6pa35d.js} +2 -2
- package/dist/{claude-managed-setup-n5a5eqks.js → claude-managed-setup-c3hp36ph.js} +4 -4
- package/dist/{claude-sdk-session-2j3j4kwn.js → claude-sdk-session-8thjsk5e.js} +9729 -9628
- package/dist/{cli-b9fcxxev.js → cli-007cnhm1.js} +50 -4
- package/dist/{cli-j4rqdf2n.js → cli-03vpq45j.js} +2 -2
- package/dist/{cli-amyhm3gj.js → cli-0y6f0ckq.js} +1 -1
- package/dist/{cli-4dg55wsk.js → cli-0z3vjrpm.js} +1 -1
- package/dist/{cli-pccfmtb5.js → cli-179xcyky.js} +11 -11
- package/dist/{cli-3r8samv3.js → cli-1f0gp9vz.js} +3 -2
- package/dist/{cli-hj3debgw.js → cli-1pr672hz.js} +3 -3
- package/dist/{cli-0bqteszy.js → cli-2nf3b08t.js} +328 -25
- package/dist/{cli-t7cyef5g.js → cli-36j4jnk3.js} +4 -4
- package/dist/{cli-er83cjbn.js → cli-3qv8naq8.js} +527 -231
- package/dist/cli-3zwye9sn.js +1193 -0
- package/dist/{cli-60cgh1rv.js → cli-459fk97x.js} +7 -7
- package/dist/{cli-xj0re5tm.js → cli-4w24f7z3.js} +1 -1
- package/dist/{cli-v3yr2gbg.js → cli-5kzk3472.js} +2198 -968
- package/dist/{cli-h7gc0b4r.js → cli-5m84zd2k.js} +55 -18
- package/dist/{cli-w6nadkh4.js → cli-5rvg1ddk.js} +5 -5
- package/dist/{cli-dvkmp9bt.js → cli-6ctwrf1z.js} +20 -10
- package/dist/{cli-9qtjfd60.js → cli-6rg2jn2y.js} +3 -2
- package/dist/{cli-2v045xyf.js → cli-7bwtc6yx.js} +3 -3
- package/dist/{cli-3cgngyd8.js → cli-7g40g8hq.js} +35 -17
- package/dist/{cli-q6r7t299.js → cli-92mqenkc.js} +2379 -154
- package/dist/{cli-cynvc9fj.js → cli-9ce2xw98.js} +8 -8
- package/dist/{cli-gqvh8d8a.js → cli-bgdtv62r.js} +36 -22
- package/dist/{cli-wxbn3yfx.js → cli-cz5nmdxt.js} +2 -2
- package/dist/{cli-wt645v7q.js → cli-dabehxhp.js} +1 -1
- package/dist/{cli-vfha21c9.js → cli-e5bxnjps.js} +2 -2
- package/dist/{cli-p20r8w42.js → cli-fcc5qe3f.js} +763 -268
- package/dist/{cli-1ephgwnq.js → cli-fes51ya2.js} +3 -3
- package/dist/{cli-1d70crsy.js → cli-ftmq4nvk.js} +2 -2
- package/dist/{cli-qkhc5aqy.js → cli-fy1jyyym.js} +13 -13
- package/dist/{cli-s95dknxh.js → cli-g1cw7xtt.js} +1 -1
- package/dist/{cli-qje9k2nh.js → cli-g9ypd5en.js} +12 -2
- package/dist/{cli-74c1wpm0.js → cli-gpv60278.js} +1 -1
- package/dist/{cli-pemywhfq.js → cli-ha9rmwgq.js} +2 -1
- package/dist/{cli-bnz0r3kc.js → cli-j1eghqfm.js} +5 -5
- package/dist/{cli-2zqpkvtt.js → cli-jpxsrxcb.js} +1 -1
- package/dist/{cli-ejs3m1sx.js → cli-kxpfx2xr.js} +1 -1
- package/dist/{cli-37yb7307.js → cli-mbfj11fq.js} +1 -1
- package/dist/{cli-pnp2j5zg.js → cli-mmhj8vd7.js} +2 -2
- package/dist/{cli-xf234ws8.js → cli-n4c8e3s5.js} +2 -0
- package/dist/{cli-a0kcty51.js → cli-n6kmqngw.js} +2 -2
- package/dist/{cli-te6b5wvg.js → cli-nnvjb17s.js} +3 -3
- package/dist/{cli-1dmcq2eh.js → cli-nzfnt2gs.js} +1 -1
- package/dist/{cli-625tb7aq.js → cli-p859hcc8.js} +2 -2
- package/dist/{cli-smvcw9z5.js → cli-pasyvjmm.js} +1 -1
- package/dist/cli-pkaqk57w.js +128 -0
- package/dist/{cli-8nyhf7za.js → cli-qqzmjxkf.js} +129 -105
- package/dist/{cli-zrwpctp4.js → cli-r25qpaxt.js} +161 -63
- package/dist/{cli-nc35pxb7.js → cli-rh5smt8t.js} +19 -4
- package/dist/{cli-qwkcwjdn.js → cli-rycdaj0a.js} +1 -1
- package/dist/{cli-esxerzg7.js → cli-s4sntwt4.js} +2 -2
- package/dist/{cli-b8k7epj1.js → cli-segs7hx9.js} +8 -8
- package/dist/{cli-36ckw0wx.js → cli-stxdrqs2.js} +2 -2
- package/dist/{cli-vdm20wej.js → cli-sy8f9705.js} +4 -4
- package/dist/{cli-1m17dc9d.js → cli-ta8bd7th.js} +2 -2
- package/dist/{cli-2y91wnbh.js → cli-we0ra057.js} +222 -10
- package/dist/{cli-55qmbp6z.js → cli-x21dqn18.js} +5 -5
- package/dist/{cli-dqb4n629.js → cli-x6vf4sad.js} +11 -11
- package/dist/{cli-d2mb5s8a.js → cli-x77gen2p.js} +680 -440
- package/dist/{cli-vq97g1jq.js → cli-xawhpgc9.js} +95 -195
- package/dist/{cli-82d3nkc3.js → cli-xd4eqtr2.js} +2 -2
- package/dist/{cli-9fhkkpj5.js → cli-xt768r5m.js} +2 -2
- package/dist/{cli-zrk6jxv8.js → cli-y4x8ww6s.js} +2 -2
- package/dist/{cli-xc27fwzg.js → cli-y9jvxfhk.js} +3 -3
- package/dist/{cli-44159k28.js → cli-z63a269p.js} +2 -2
- package/dist/{cli-sm8rses9.js → cli-zfdp01hn.js} +8 -5
- package/dist/cli.js +31 -29
- package/dist/{cloudflare-workers-ai-system-one-ch2wxfcp.js → cloudflare-workers-ai-system-one-43v9qkjk.js} +7 -2
- package/dist/{codex-adapter-968xwfy7.js → codex-adapter-tcg1q3jk.js} +6 -4
- package/dist/{codex-login-m819c41r.js → codex-login-rn7c5gxt.js} +1 -1
- package/dist/{codex-session-runner-ht4z99fa.js → codex-session-runner-xfx4wd9x.js} +7 -5
- package/dist/{commands-pcymaeg9.js → commands-ew83nj70.js} +2 -2
- package/dist/{controller-fpjyk46r.js → controller-gnq1k1mf.js} +4 -4
- package/dist/{controllers-connect-mwzx1fh6.js → controllers-connect-tw1dan9r.js} +26 -24
- package/dist/{cursor-adapter-cp4s2e2f.js → cursor-adapter-yq5xgfcg.js} +1 -1
- package/dist/{db-xsjg9jqq.js → db-7gbc53fd.js} +14 -2
- package/dist/{db-sgd12bt9.js → db-t86xrnes.js} +3 -3
- package/dist/{devin-adapter-zgqadvnm.js → devin-adapter-4ycy4vrf.js} +1 -1
- package/dist/{dispatcher-x0jfw2c9.js → dispatcher-64bdyhps.js} +23 -21
- package/dist/{docker-fkgtr75t.js → docker-nfy2gc8t.js} +2 -2
- package/dist/{dsh-adapter-jrq7ma7h.js → dsh-adapter-tvhc4k2d.js} +1 -1
- package/dist/{e2b-16d6ch39.js → e2b-9gkdhm3c.js} +2 -2
- package/dist/{e2b-zn0b4w5w.js → e2b-cjtf8k1z.js} +2 -2
- package/dist/{easter-egg-3d-5cgynaja.js → easter-egg-3d-e4qv8dxj.js} +1 -1
- package/dist/{event-bus-9vpk8gmq.js → event-bus-3z78ep5n.js} +1 -1
- package/dist/{execute-t4e6m3rw.js → execute-0xpz8950.js} +85 -23
- package/dist/{external-channel-8njj4h3b.js → external-channel-e9nw24zv.js} +2 -2
- package/dist/{google-generative-ai-bmq8gkf3.js → google-generative-ai-mk9vcpe1.js} +15 -15
- package/dist/{google-vertex-b1rvp52s.js → google-vertex-q580kz8k.js} +20 -20
- package/dist/{grok-adapter-kkr97v2f.js → grok-adapter-v82srhrx.js} +9 -7
- package/dist/handlers-c9ajzhbn.js +41 -0
- package/dist/{heartbeat-wd03zhn0.js → heartbeat-2vqs1319.js} +26 -24
- package/dist/{hook-xzj2ts2p.js → hook-01nk13wx.js} +7 -5
- package/dist/{hook-f0vghj42.js → hook-jzpheegg.js} +9 -7
- package/dist/{http-4hwxv62p.js → http-fc1k6ec8.js} +859 -252
- package/dist/index-07ef7nkm.js +42 -0
- package/dist/{index-g6tbb6ve.js → index-31ceyf42.js} +1 -1
- package/dist/index-7yts8n9s.js +43 -0
- package/dist/{index-c4ykd0f1.js → index-dr94zbnh.js} +13348 -11611
- package/dist/index-q3t40ryv.js +46 -0
- package/dist/{index-ww2vznkp.js → index-sepja052.js} +1 -1
- package/dist/{index-97q4xn03.js → index-zdycv3ja.js} +135 -79
- package/dist/{index-g216q6jr.js → index-zsyx51ap.js} +1 -4
- package/dist/{keepalive-hrh7xnr9.js → keepalive-5nh2rhmr.js} +4 -4
- package/dist/lead-aayq64jn.js +79 -0
- package/dist/{llama-cpp-classify-vv43gswy.js → llama-cpp-classify-ee7vmd7q.js} +4 -1
- package/dist/{maintenance-e8wnwnqx.js → maintenance-hjhzdt3k.js} +1 -1
- package/dist/{mistral-conversations-1medfwv7.js → mistral-conversations-sd0xf1tn.js} +13 -13
- package/dist/{node-3v3f08f7.js → node-jzjj7r9x.js} +10 -9
- package/dist/{oauth-refresh-sweep-zm0yj4sj.js → oauth-refresh-sweep-y3n1knmd.js} +3 -3
- package/dist/{onboard-dtwcq603.js → onboard-1zq3m7db.js} +2 -2
- package/dist/{openai-codex-responses-de9pvs4d.js → openai-codex-responses-79s8xnbh.js} +29 -23
- package/dist/{openai-completions-mtqge3nf.js → openai-completions-tdbyts0x.js} +21 -20
- package/dist/{openai-decisions-ygk4kkxd.js → openai-decisions-bqhnjyfz.js} +2 -1
- package/dist/{openai-responses-dkyng71g.js → openai-responses-dm3t5vqp.js} +26 -17
- package/dist/{opencode-adapter-x4tqzhkh.js → opencode-adapter-e56086m1.js} +7 -5
- package/dist/{openrouter-images-wvv7camj.js → openrouter-images-ssttr1y4.js} +6 -3
- package/dist/{otel-impl-w3fbhpn0.js → otel-impl-h9yyza47.js} +2 -2
- package/dist/{pi-messages-nhh1pkz2.js → pi-messages-3nf8bq1a.js} +6 -4
- package/dist/{pi-mono-adapter-mcfkn9vf.js → pi-mono-adapter-tzps8f38.js} +9 -37
- package/dist/{pricing-refresh-j38dehfs.js → pricing-refresh-nevhat8h.js} +3 -3
- package/dist/{rbac-roles-ae8yttq4.js → rbac-roles-4y6ngvzq.js} +1 -1
- package/dist/{rbac-roles-ejxx9b35.js → rbac-roles-exbg8xb6.js} +2 -2
- package/dist/{registry-rxw10xzg.js → registry-ajhexe44.js} +1 -1
- package/dist/{render-v2-n16n0phe.js → render-v2-cfsh13z3.js} +3 -3
- package/dist/{resume-z0fgjc38.js → resume-5s2t0dbs.js} +10 -8
- package/dist/{runtime-xe3aqy4x.js → runtime-bt5pkpm1.js} +1 -1
- package/dist/{session-access-cv3mq6mc.js → session-access-dnbyp3cn.js} +6 -4
- package/dist/{setup-mafhj3yt.js → setup-1wj1y77j.js} +1 -1
- package/dist/{typesafe-system-one-2hgpayzy.js → typesafe-system-one-bre38hvs.js} +4 -2
- package/dist/{virtual-modules-4r3zkc20.js → virtual-modules-mnfbh5mr.js} +8 -2
- package/dist/{work-objects-8b86mgjp.js → work-objects-7mg6216g.js} +26 -24
- package/dist/worker-nwb789mg.js +79 -0
- package/dist/{x-y45mthwn.js → x-pbpezr0s.js} +2 -2
- package/openapi.json +793 -6
- package/package.json +11 -11
- package/src/apps/catalog.generated.json +206 -4
- package/src/apps/page-validator.ts +13 -1
- package/src/assets/key.ts +17 -0
- package/src/azure-devops/handlers.ts +1 -1
- package/src/be/chunking.ts +42 -9
- package/src/be/db/approvals.ts +18 -0
- package/src/be/db/session-moves.ts +164 -0
- package/src/be/db/session-tree.ts +384 -0
- package/src/be/db/tasks/draft-text.ts +15 -0
- package/src/be/db/tasks/read.ts +54 -5
- package/src/be/db/workflow-runs.ts +96 -0
- package/src/be/db.ts +85 -237
- package/src/be/memory/index-content.ts +36 -17
- package/src/be/memory/providers/openai-embedding.ts +115 -61
- package/src/be/memory/providers/sqlite-store.ts +69 -23
- package/src/be/memory/reranker.ts +31 -1
- package/src/be/memory/search-candidates.ts +47 -0
- package/src/be/memory/types.ts +14 -0
- package/src/be/onboarding.ts +3 -0
- package/src/be/scripts/typecheck.ts +2 -0
- package/src/be/seed-scripts/catalog/heartbeat-jev-gate.ts +106 -54
- package/src/be/seed-scripts/index.ts +2 -2
- package/src/be/skill-sync.ts +43 -2
- package/src/be/swarm-config-guard.ts +25 -1
- package/src/be/task-attachment-store.ts +30 -0
- package/src/bitbucket/api.ts +81 -0
- package/src/bitbucket/auth.ts +64 -0
- package/src/bitbucket/handlers.ts +434 -0
- package/src/bitbucket/index.ts +28 -0
- package/src/bitbucket/templates.ts +134 -0
- package/src/bitbucket/types.ts +65 -0
- package/src/commands/runner.ts +55 -3
- package/src/e2b/env.ts +1 -0
- package/src/extensions/catalog.generated.json +4 -4
- package/src/extensions/contract-types.generated.ts +1 -1
- package/src/extensions/contract.ts +9 -2
- package/src/extensions/post-bridge.ts +1 -0
- package/src/fs/provider.ts +13 -1
- package/src/github/handlers.ts +6 -6
- package/src/gitlab/handlers.ts +5 -5
- package/src/heartbeat/heartbeat.ts +31 -11
- package/src/http/assets.ts +14 -2
- package/src/http/core.ts +12 -0
- package/src/http/fs.ts +54 -34
- package/src/http/index.ts +4 -0
- package/src/http/mcp-body.ts +37 -0
- package/src/http/mcp-user.ts +3 -5
- package/src/http/mcp.ts +6 -7
- package/src/http/memory.ts +26 -15
- package/src/http/onboarding.ts +1 -0
- package/src/http/page-proxy.ts +61 -5
- package/src/http/pages-public.ts +27 -44
- package/src/http/pages.ts +4 -4
- package/src/http/script-runs.ts +2 -0
- package/src/http/sessions.ts +366 -12
- package/src/http/skills.ts +48 -1
- package/src/http/tasks.ts +1 -1
- package/src/http/users.ts +13 -1
- package/src/http/webhooks.ts +141 -0
- package/src/integrations/chat-sdk/adapters.ts +24 -4
- package/src/integrations/chat-sdk/attachments.ts +90 -12
- package/src/integrations/chat-sdk/facts.ts +138 -0
- package/src/integrations/chat-sdk/inbound.ts +156 -8
- package/src/integrations/chat-sdk/limits.ts +24 -0
- package/src/integrations/chat-sdk/outbound-files.ts +186 -0
- package/src/integrations/chat-sdk/outbound.ts +272 -5
- package/src/integrations/chat-sdk/relay.ts +4 -1
- package/src/integrations/chat-sdk/router.ts +28 -2
- package/src/integrations/chat-sdk/steering.ts +9 -0
- package/src/integrations/chat-sdk/templates.ts +14 -0
- package/src/integrations/chat-sdk/thread-buffer.ts +4 -0
- package/src/mcp-limits.ts +20 -0
- package/src/pages/browser-sdk.ts +8 -3
- package/src/pages/feedback-overlay.ts +1 -1
- package/src/providers/acp-targets.ts +1 -1
- package/src/providers/amp-adapter.ts +1 -1
- package/src/providers/codex-adapter.ts +106 -119
- package/src/providers/codex-agents-md.ts +35 -229
- package/src/providers/cursor-adapter.ts +1 -1
- package/src/providers/pi-mono-adapter.ts +5 -53
- package/src/realtime/transport.ts +1 -2
- package/src/script-workflows/workflow-ctx.ts +1 -0
- package/src/scripts-runtime/types/stdlib.d.ts +2 -0
- package/src/scripts-runtime/types/swarm-extension.d.ts +14 -4
- package/src/scripts-runtime/types/swarm-sdk.d.ts +2 -0
- package/src/server-user.ts +2 -0
- package/src/server.ts +2 -0
- package/src/slack/assistant.ts +2 -2
- package/src/slack/files.ts +2 -0
- package/src/slack/handlers.test.ts +158 -0
- package/src/slack/handlers.ts +23 -32
- package/src/slack/inbound-files.ts +39 -6
- package/src/slack/thread-buffer.ts +2 -1
- package/src/tasks/context-key.ts +61 -1
- package/src/tasks/task-terminal-effects.ts +21 -17
- package/src/tests/apps-card-grid.test.ts +197 -0
- package/src/tests/apps-catalog-additions.test.ts +120 -0
- package/src/tests/apps-tree.test.ts +129 -0
- package/src/tests/bitbucket-auth.test.ts +156 -0
- package/src/tests/bitbucket-handlers.test.ts +425 -0
- package/src/tests/bitbucket-webhook.test.ts +220 -0
- package/src/tests/chat-sdk-attachments-reply.test.ts +52 -0
- package/src/tests/chat-sdk-config.test.ts +11 -0
- package/src/tests/chat-sdk-facts.test.ts +23 -0
- package/src/tests/chat-sdk-inbound.test.ts +597 -4
- package/src/tests/chat-sdk-outbound-files.test.ts +652 -0
- package/src/tests/chat-sdk-telegram-markdown.test.ts +92 -0
- package/src/tests/codex-adapter.test.ts +167 -242
- package/src/tests/config-api-only-keys.test.ts +9 -0
- package/src/tests/context-key.test.ts +50 -0
- package/src/tests/core-auth.test.ts +5 -0
- package/src/tests/cors-allowlist.test.ts +1 -1
- package/src/tests/db-task-read-contract.test.ts +7 -0
- package/src/tests/db-task-write-contract.test.ts +6 -0
- package/src/tests/docs-index-footprint.test.js +3 -3
- package/src/tests/extensions-example-slack-reply-gate.test.ts +262 -91
- package/src/tests/fixtures/bitbucket/pullrequest-comment-created-inline.json +174 -0
- package/src/tests/fixtures/bitbucket/pullrequest-comment-created.json +170 -0
- package/src/tests/fixtures/bitbucket/pullrequest-created-mention.json +132 -0
- package/src/tests/fixtures/bitbucket/pullrequest-created-reviewer.json +140 -0
- package/src/tests/fixtures/bitbucket/pullrequest-fulfilled.json +134 -0
- package/src/tests/fixtures/bitbucket/pullrequest-rejected.json +133 -0
- package/src/tests/generate-mcp-docs.test.ts +101 -0
- package/src/tests/gitlab-handlers.test.ts +3 -3
- package/src/tests/gitlab-vcs-db.test.ts +4 -4
- package/src/tests/heartbeat-checklist.test.ts +33 -0
- package/src/tests/heartbeat-jev-gate-credentials.test.ts +44 -0
- package/src/tests/heartbeat-jev-gate.test.ts +242 -1
- package/src/tests/integration-disable-flags.test.ts +36 -0
- package/src/tests/kv-page-proxy.test.ts +91 -5
- package/src/tests/launch-password-rejection.test.ts +2 -2
- package/src/tests/mcp-input-caps.test.ts +294 -0
- package/src/tests/mcp-tools.test.ts +1 -1
- package/src/tests/memory-chunked-search-starvation.test.ts +200 -0
- package/src/tests/memory-embedding-input-cap.test.ts +114 -0
- package/src/tests/memory.test.ts +14 -0
- package/src/tests/onboarding.test.ts +12 -0
- package/src/tests/page-guest-session.test.ts +19 -8
- package/src/tests/page-proxy-authed.test.ts +1 -1
- package/src/tests/page-proxy.test.ts +17 -10
- package/src/tests/page-session.test.ts +102 -1
- package/src/tests/pages-authed-mode.test.ts +82 -12
- package/src/tests/pages-password-mode.test.ts +5 -5
- package/src/tests/pi-codemode.test.ts +8 -2
- package/src/tests/preload-env.test.ts +57 -0
- package/src/tests/preload.ts +21 -0
- package/src/tests/prompt-template-remaining.test.ts +38 -0
- package/src/tests/realtime-transport.test.ts +13 -7
- package/src/tests/run-root-coverage.test.ts +39 -0
- package/src/tests/runner-bitbucket-vcs.test.ts +191 -0
- package/src/tests/runner-skills-refresh.test.ts +58 -0
- package/src/tests/script-runs-http.test.ts +84 -0
- package/src/tests/scripts-http.test.ts +10 -1
- package/src/tests/secret-scrubber.test.ts +15 -0
- package/src/tests/sessions-move.test.ts +576 -0
- package/src/tests/sessions-tree.test.ts +493 -0
- package/src/tests/skill-files-http.test.ts +50 -0
- package/src/tests/skills-bundle.test.ts +99 -0
- package/src/tests/swarm-config-export-to-env.test.ts +23 -3
- package/src/tests/task-audio-transcription.test.ts +299 -0
- package/src/tests/task-completion-memory-chunking.test.ts +434 -0
- package/src/tests/task-terminal-secret-scrub.test.ts +6 -1
- package/src/tests/transcription.test.ts +299 -0
- package/src/tests/vcs-bitbucket.test.ts +91 -0
- package/src/tests/vcs-provider-isolation.test.ts +123 -0
- package/src/tests/vcs-provider.test.ts +16 -1
- package/src/tests/vcs-tracking.test.ts +3 -3
- package/src/tests/workflow-foreach-bodies.test.ts +637 -0
- package/src/tests/workflow-foreach-concurrency.test.ts +342 -0
- package/src/tests/workflow-foreach-retry.test.ts +537 -0
- package/src/tests/workflow-foreach.test.ts +44 -23
- package/src/tests/workflow-step-claims.test.ts +6 -6
- package/src/tests/workflow-system-one-decision-providers.test.ts +98 -1
- package/src/tests/workflow-system-one-decision.test.ts +2 -1
- package/src/tests/workflow-tla-races.test.ts +2 -2
- package/src/tools/chat-access.ts +10 -0
- package/src/tools/chat-post.ts +31 -11
- package/src/tools/chat-reply.ts +32 -10
- package/src/tools/memory-search.ts +3 -1
- package/src/tools/script-delete.ts +1 -1
- package/src/tools/system-one-decide.ts +17 -4
- package/src/tools/workflows/create-workflow.ts +5 -3
- package/src/types.ts +9 -2
- package/src/utils/config-env-export.ts +4 -1
- package/src/utils/page-session.ts +133 -20
- package/src/utils/secret-scrubber.ts +4 -0
- package/src/utils/skills-refresh.ts +83 -43
- package/src/utils/transcription.ts +246 -0
- package/src/vcs/bitbucket.ts +58 -0
- package/src/vcs/index.ts +2 -0
- package/src/vcs/types.ts +2 -2
- package/src/workflows/approval-resolution.ts +4 -1
- package/src/workflows/checkpoint.ts +9 -1
- package/src/workflows/constants.ts +3 -0
- package/src/workflows/definition.ts +48 -17
- package/src/workflows/engine.ts +25 -15
- package/src/workflows/executors/foreach.ts +45 -99
- package/src/workflows/executors/system-one-decision.ts +28 -7
- package/src/workflows/executors/system-one-providers.ts +39 -4
- package/src/workflows/foreach-body.ts +146 -0
- package/src/workflows/foreach-driver.ts +552 -0
- package/src/workflows/foreach-join.ts +206 -7
- package/src/workflows/node-timeout.ts +21 -0
- package/src/workflows/recovery.ts +68 -3
- package/src/workflows/resume.ts +221 -43
- package/src/workflows/retry-poller.ts +39 -24
- package/src/workflows/step-input.ts +59 -0
- package/src/workflows/task-step-routing.ts +114 -56
- package/templates/extensions/slack-reply-gate/README.md +24 -22
- package/templates/extensions/slack-reply-gate/hooks.ts +146 -95
- package/templates/extensions/slack-reply-gate/manifest.json +2 -2
- package/templates/skills/apps/SKILL.md +28 -6
- package/templates/skills/apps/content.md +28 -6
- package/templates/skills/chat-sdk/content.md +6 -2
- package/templates/skills/code-quality/content.md +2 -1
- package/templates/skills/heartbeat-runbook/content.md +5 -5
- package/templates/skills/pages/content.md +6 -4
- package/templates/skills/slack-interaction/content.md +5 -0
- package/templates/skills/swarm-scripts/SKILL.md +2 -2
- package/templates/skills/swarm-scripts/content.md +2 -2
- package/templates/skills/work-on-task/SKILL.md +1 -1
- package/templates/skills/work-on-task/content.md +1 -1
- package/dist/cli-51y09c6p.js +0 -2657
- package/dist/cli-yw91rnmw.js +0 -13
- package/dist/handlers-0pvee9nw.js +0 -39
- package/dist/index-gdh8jrdm.js +0 -43
- package/dist/index-hg0x2cdv.js +0 -40
- package/dist/index-thm0y6a8.js +0 -41
- package/dist/lead-ee8vazz2.js +0 -77
- package/dist/worker-16s75hm5.js +0 -77
|
@@ -75,9 +75,11 @@ async function createPage(
|
|
|
75
75
|
async function launchPage(pageId: string, agentId: string): Promise<string> {
|
|
76
76
|
const response = await api("POST", `/api/pages/${pageId}/launch`, { agentId });
|
|
77
77
|
expect(response.status).toBe(204);
|
|
78
|
-
const cookie = response.headers
|
|
78
|
+
const cookie = response.headers
|
|
79
|
+
.get("set-cookie")
|
|
80
|
+
?.match(/page_session_[A-Za-z0-9_-]+=([^;]+)/)?.[1];
|
|
79
81
|
expect(cookie).toBeTruthy();
|
|
80
|
-
return `
|
|
82
|
+
return `page_session_${pageId}=${cookie}`;
|
|
81
83
|
}
|
|
82
84
|
|
|
83
85
|
async function createUserToken(name: string): Promise<{ id: string; token: string }> {
|
|
@@ -107,17 +109,21 @@ async function launchPageAsUser(pageId: string, token: string): Promise<string>
|
|
|
107
109
|
headers: { Authorization: `Bearer ${token}` },
|
|
108
110
|
});
|
|
109
111
|
expect(response.status).toBe(204);
|
|
110
|
-
const cookie = response.headers
|
|
112
|
+
const cookie = response.headers
|
|
113
|
+
.get("set-cookie")
|
|
114
|
+
?.match(/page_session_[A-Za-z0-9_-]+=([^;]+)/)?.[1];
|
|
111
115
|
expect(cookie).toBeTruthy();
|
|
112
|
-
return `
|
|
116
|
+
return `page_session_${pageId}=${cookie}`;
|
|
113
117
|
}
|
|
114
118
|
|
|
115
119
|
async function unlockPasswordPage(pageId: string, password: string): Promise<string> {
|
|
116
120
|
const response = await fetch(`${baseUrl}/p/${pageId}?key=${encodeURIComponent(password)}`);
|
|
117
121
|
expect(response.status).toBe(200);
|
|
118
|
-
const cookie = response.headers
|
|
122
|
+
const cookie = response.headers
|
|
123
|
+
.get("set-cookie")
|
|
124
|
+
?.match(/page_session_[A-Za-z0-9_-]+=([^;]+)/)?.[1];
|
|
119
125
|
expect(cookie).toBeTruthy();
|
|
120
|
-
return `
|
|
126
|
+
return `page_session_${pageId}=${cookie}`;
|
|
121
127
|
}
|
|
122
128
|
|
|
123
129
|
function openSocket(
|
|
@@ -443,7 +449,7 @@ describe("realtime transport", () => {
|
|
|
443
449
|
const passwordPage = await createPage(agentId, "password", "secret");
|
|
444
450
|
expect(await socketAccepted({ Origin: baseUrl }, pageSocketUrl(passwordPage))).toBe(false);
|
|
445
451
|
const passwordCookie = await unlockPasswordPage(passwordPage, "secret");
|
|
446
|
-
expect(passwordCookie).toContain("
|
|
452
|
+
expect(passwordCookie).toContain("page_session_");
|
|
447
453
|
});
|
|
448
454
|
|
|
449
455
|
test("persists room state across an API restart and cascades page deletion", async () => {
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
import { describe, expect, test } from "bun:test";
|
|
2
|
+
import { mergeLcov } from "../../scripts/run-root-coverage";
|
|
3
|
+
|
|
4
|
+
describe("mergeLcov", () => {
|
|
5
|
+
test("disjoint function hits remain a conservative lower bound without FN/FNDA identities", () => {
|
|
6
|
+
// a.test.ts calls a(); b.test.ts calls b() in the same two-function module.
|
|
7
|
+
const report = "SF:src/example.ts\nFNF:2\nFNH:1\nend_of_record\n";
|
|
8
|
+
const merged = mergeLcov([report, report]);
|
|
9
|
+
|
|
10
|
+
expect(merged).toContain("FNF:2\nFNH:1\n");
|
|
11
|
+
});
|
|
12
|
+
|
|
13
|
+
test("DA hits sum across reports and LH counts lines hit in any report", () => {
|
|
14
|
+
const merged = mergeLcov([
|
|
15
|
+
"SF:src/example.ts\nDA:1,2\nDA:2,0\nDA:3,0\nend_of_record\n",
|
|
16
|
+
"SF:src/example.ts\nDA:1,3\nDA:2,1\nDA:3,0\nend_of_record\n",
|
|
17
|
+
]);
|
|
18
|
+
|
|
19
|
+
expect(merged).toBe(
|
|
20
|
+
"TN:\nSF:src/example.ts\nFNF:0\nFNH:0\nDA:1,5\nDA:2,1\nDA:3,0\nLF:3\nLH:2\nend_of_record\n",
|
|
21
|
+
);
|
|
22
|
+
});
|
|
23
|
+
|
|
24
|
+
test("FNH never exceeds FNF when merging function totals", () => {
|
|
25
|
+
const reports = [
|
|
26
|
+
"SF:src/example.ts\nFNF:2\nFNH:2\nend_of_record\n",
|
|
27
|
+
"SF:src/example.ts\nFNF:3\nFNH:1\nend_of_record\n",
|
|
28
|
+
];
|
|
29
|
+
for (const inputs of [reports, reports.toReversed(), [...reports, ...reports]]) {
|
|
30
|
+
const merged = mergeLcov(inputs);
|
|
31
|
+
const found = Number(merged.match(/^FNF:(\d+)$/m)![1]);
|
|
32
|
+
const hit = Number(merged.match(/^FNH:(\d+)$/m)![1]);
|
|
33
|
+
|
|
34
|
+
expect(found).toBe(3);
|
|
35
|
+
expect(hit).toBe(2);
|
|
36
|
+
expect(hit).toBeLessThanOrEqual(found);
|
|
37
|
+
}
|
|
38
|
+
});
|
|
39
|
+
});
|
|
@@ -0,0 +1,191 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Worker-side Bitbucket PR linking. There is no Bitbucket CLI in the worker
|
|
3
|
+
* image, so `detectVcsForTask` asks the Bitbucket REST API (via `fetch`) for an
|
|
4
|
+
* open PR on the current branch and PATCHes `/api/tasks/:id/vcs` when it finds
|
|
5
|
+
* one. Both calls go through `globalThis.fetch`, which these tests stub, so no
|
|
6
|
+
* server or port is involved. The git side runs against a real temp repo.
|
|
7
|
+
*/
|
|
8
|
+
import { afterAll, afterEach, beforeAll, beforeEach, describe, expect, test } from "bun:test";
|
|
9
|
+
import { mkdtemp, rm } from "node:fs/promises";
|
|
10
|
+
import { tmpdir } from "node:os";
|
|
11
|
+
import { join } from "node:path";
|
|
12
|
+
import { detectVcsForTask, findBitbucketOpenPullRequest } from "../commands/runner";
|
|
13
|
+
import { expectChildOk, runChild } from "./test-proc";
|
|
14
|
+
|
|
15
|
+
const API_URL = "http://swarm.invalid";
|
|
16
|
+
const BRANCH = "feature/bb-link";
|
|
17
|
+
const PR_HTML = "https://bitbucket.org/acme-ws/web.app/pull-requests/17";
|
|
18
|
+
|
|
19
|
+
type Call = { url: string; method: string; headers: Headers; body: string };
|
|
20
|
+
type BitbucketReply = { status: number; body: unknown } | "throw";
|
|
21
|
+
|
|
22
|
+
const originalFetch = globalThis.fetch;
|
|
23
|
+
const originalToken = process.env.BITBUCKET_TOKEN;
|
|
24
|
+
let calls: Call[] = [];
|
|
25
|
+
let bitbucketReply: BitbucketReply = { status: 200, body: { values: [] } };
|
|
26
|
+
let repoDir = "";
|
|
27
|
+
let taskSeq = 0;
|
|
28
|
+
|
|
29
|
+
function nextTaskId(): string {
|
|
30
|
+
taskSeq += 1;
|
|
31
|
+
return `00000000-0000-4000-8000-${String(taskSeq).padStart(12, "0")}`;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
async function git(args: string[]): Promise<void> {
|
|
35
|
+
expectChildOk(await runChild(["git", "-C", repoDir, ...args]), `git ${args.join(" ")}`);
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
function stubFetch(): void {
|
|
39
|
+
globalThis.fetch = (async (input: string | URL | Request, init?: RequestInit) => {
|
|
40
|
+
const url = typeof input === "string" ? input : input instanceof URL ? input.href : input.url;
|
|
41
|
+
calls.push({
|
|
42
|
+
url,
|
|
43
|
+
method: init?.method ?? "GET",
|
|
44
|
+
headers: new Headers(init?.headers),
|
|
45
|
+
body: typeof init?.body === "string" ? init.body : "",
|
|
46
|
+
});
|
|
47
|
+
if (url.startsWith("https://api.bitbucket.org/")) {
|
|
48
|
+
if (bitbucketReply === "throw") throw new TypeError("network down");
|
|
49
|
+
return new Response(JSON.stringify(bitbucketReply.body), {
|
|
50
|
+
status: bitbucketReply.status,
|
|
51
|
+
headers: { "Content-Type": "application/json" },
|
|
52
|
+
});
|
|
53
|
+
}
|
|
54
|
+
return new Response("{}", { status: 200, headers: { "Content-Type": "application/json" } });
|
|
55
|
+
}) as typeof fetch;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
const bitbucketCalls = () => calls.filter((c) => c.url.startsWith("https://api.bitbucket.org/"));
|
|
59
|
+
const patchCalls = () => calls.filter((c) => c.method === "PATCH");
|
|
60
|
+
|
|
61
|
+
beforeAll(async () => {
|
|
62
|
+
repoDir = await mkdtemp(join(tmpdir(), "runner-bb-vcs-"));
|
|
63
|
+
await git(["init", "--quiet"]);
|
|
64
|
+
await git(["checkout", "--quiet", "-b", BRANCH]);
|
|
65
|
+
await git(["remote", "add", "origin", "https://bitbucket.org/acme-ws/web.app.git"]);
|
|
66
|
+
});
|
|
67
|
+
|
|
68
|
+
afterAll(async () => {
|
|
69
|
+
await rm(repoDir, { recursive: true, force: true });
|
|
70
|
+
});
|
|
71
|
+
|
|
72
|
+
beforeEach(() => {
|
|
73
|
+
calls = [];
|
|
74
|
+
bitbucketReply = { status: 200, body: { values: [] } };
|
|
75
|
+
process.env.BITBUCKET_TOKEN = "example-bb-token";
|
|
76
|
+
stubFetch();
|
|
77
|
+
});
|
|
78
|
+
|
|
79
|
+
afterEach(() => {
|
|
80
|
+
globalThis.fetch = originalFetch;
|
|
81
|
+
if (originalToken === undefined) delete process.env.BITBUCKET_TOKEN;
|
|
82
|
+
else process.env.BITBUCKET_TOKEN = originalToken;
|
|
83
|
+
});
|
|
84
|
+
|
|
85
|
+
describe("detectVcsForTask on a Bitbucket remote", () => {
|
|
86
|
+
test("an open PR for the branch links the task", async () => {
|
|
87
|
+
bitbucketReply = {
|
|
88
|
+
status: 200,
|
|
89
|
+
body: { values: [{ id: 17, links: { html: { href: PR_HTML } } }] },
|
|
90
|
+
};
|
|
91
|
+
const taskId = nextTaskId();
|
|
92
|
+
await detectVcsForTask(API_URL, "swarm-key", taskId, repoDir);
|
|
93
|
+
|
|
94
|
+
const [lookup] = bitbucketCalls();
|
|
95
|
+
expect(lookup).toBeDefined();
|
|
96
|
+
const lookupUrl = new URL(lookup!.url);
|
|
97
|
+
expect(lookupUrl.pathname).toBe("/2.0/repositories/acme-ws/web.app/pullrequests");
|
|
98
|
+
expect(lookupUrl.searchParams.get("q")).toBe(`source.branch.name="${BRANCH}" AND state="OPEN"`);
|
|
99
|
+
expect(lookupUrl.searchParams.get("pagelen")).toBe("1");
|
|
100
|
+
expect(lookup!.headers.get("authorization")).toBe("Bearer example-bb-token");
|
|
101
|
+
|
|
102
|
+
const patches = patchCalls();
|
|
103
|
+
expect(patches).toHaveLength(1);
|
|
104
|
+
expect(patches[0]!.url).toBe(`${API_URL}/api/tasks/${taskId}/vcs`);
|
|
105
|
+
expect(patches[0]!.headers.get("authorization")).toBe("Bearer swarm-key");
|
|
106
|
+
expect(JSON.parse(patches[0]!.body)).toEqual({
|
|
107
|
+
vcsProvider: "bitbucket",
|
|
108
|
+
vcsRepo: "acme-ws/web.app",
|
|
109
|
+
vcsNumber: 17,
|
|
110
|
+
vcsUrl: PR_HTML,
|
|
111
|
+
});
|
|
112
|
+
});
|
|
113
|
+
|
|
114
|
+
test("no open PR sends no PATCH", async () => {
|
|
115
|
+
await detectVcsForTask(API_URL, "swarm-key", nextTaskId(), repoDir);
|
|
116
|
+
expect(bitbucketCalls()).toHaveLength(1);
|
|
117
|
+
expect(patchCalls()).toHaveLength(0);
|
|
118
|
+
});
|
|
119
|
+
|
|
120
|
+
test("a non-200 answer is swallowed and sends no PATCH", async () => {
|
|
121
|
+
bitbucketReply = { status: 401, body: { type: "error", error: { message: "Unauthorized" } } };
|
|
122
|
+
await expect(
|
|
123
|
+
detectVcsForTask(API_URL, "swarm-key", nextTaskId(), repoDir),
|
|
124
|
+
).resolves.toBeUndefined();
|
|
125
|
+
expect(bitbucketCalls()).toHaveLength(1);
|
|
126
|
+
expect(patchCalls()).toHaveLength(0);
|
|
127
|
+
});
|
|
128
|
+
|
|
129
|
+
test("a network failure is swallowed and sends no PATCH", async () => {
|
|
130
|
+
bitbucketReply = "throw";
|
|
131
|
+
await expect(
|
|
132
|
+
detectVcsForTask(API_URL, "swarm-key", nextTaskId(), repoDir),
|
|
133
|
+
).resolves.toBeUndefined();
|
|
134
|
+
expect(patchCalls()).toHaveLength(0);
|
|
135
|
+
});
|
|
136
|
+
|
|
137
|
+
test("without BITBUCKET_TOKEN it calls nothing", async () => {
|
|
138
|
+
delete process.env.BITBUCKET_TOKEN;
|
|
139
|
+
await detectVcsForTask(API_URL, "swarm-key", nextTaskId(), repoDir);
|
|
140
|
+
expect(calls).toHaveLength(0);
|
|
141
|
+
});
|
|
142
|
+
});
|
|
143
|
+
|
|
144
|
+
describe("findBitbucketOpenPullRequest", () => {
|
|
145
|
+
test("escapes quotes in the branch name inside the BBQL query", async () => {
|
|
146
|
+
let seen = "";
|
|
147
|
+
const fetchImpl = (async (input: string | URL | Request) => {
|
|
148
|
+
seen = String(input);
|
|
149
|
+
return new Response(JSON.stringify({ values: [] }), { status: 200 });
|
|
150
|
+
}) as typeof fetch;
|
|
151
|
+
await findBitbucketOpenPullRequest("git@bitbucket.org:ws/repo.git", 'we"ird', {
|
|
152
|
+
token: "t",
|
|
153
|
+
fetchImpl,
|
|
154
|
+
});
|
|
155
|
+
expect(new URL(seen).searchParams.get("q")).toBe(
|
|
156
|
+
'source.branch.name="we\\"ird" AND state="OPEN"',
|
|
157
|
+
);
|
|
158
|
+
});
|
|
159
|
+
|
|
160
|
+
test("rejects a reply without a numeric id or an html link", async () => {
|
|
161
|
+
for (const values of [
|
|
162
|
+
[{ id: "17", links: { html: { href: PR_HTML } } }],
|
|
163
|
+
[{ id: 17, links: {} }],
|
|
164
|
+
[{}],
|
|
165
|
+
]) {
|
|
166
|
+
const fetchImpl = (async () =>
|
|
167
|
+
new Response(JSON.stringify({ values }), { status: 200 })) as unknown as typeof fetch;
|
|
168
|
+
expect(
|
|
169
|
+
await findBitbucketOpenPullRequest("https://bitbucket.org/ws/repo", "b", {
|
|
170
|
+
token: "t",
|
|
171
|
+
fetchImpl,
|
|
172
|
+
}),
|
|
173
|
+
).toBeNull();
|
|
174
|
+
}
|
|
175
|
+
});
|
|
176
|
+
|
|
177
|
+
test("returns null for a non-Bitbucket remote without calling fetch", async () => {
|
|
178
|
+
let called = false;
|
|
179
|
+
const fetchImpl = (async () => {
|
|
180
|
+
called = true;
|
|
181
|
+
return new Response("{}");
|
|
182
|
+
}) as unknown as typeof fetch;
|
|
183
|
+
expect(
|
|
184
|
+
await findBitbucketOpenPullRequest("https://github.com/ws/repo", "b", {
|
|
185
|
+
token: "t",
|
|
186
|
+
fetchImpl,
|
|
187
|
+
}),
|
|
188
|
+
).toBeNull();
|
|
189
|
+
expect(called).toBe(false);
|
|
190
|
+
});
|
|
191
|
+
});
|
|
@@ -34,6 +34,9 @@ type StubState = {
|
|
|
34
34
|
};
|
|
35
35
|
calls: { signature: number; list: number; sync: number };
|
|
36
36
|
skillFilesStatus: number;
|
|
37
|
+
/** 404 by default so the legacy list path stays covered; bundle tests opt in. */
|
|
38
|
+
bundleStatus: number;
|
|
39
|
+
bundleCalls: number;
|
|
37
40
|
};
|
|
38
41
|
|
|
39
42
|
const FAKE_HOME = join(tmpdir(), `runner-refresh-test-${process.pid}`);
|
|
@@ -67,6 +70,8 @@ const state: StubState = {
|
|
|
67
70
|
signature: "hash-v1",
|
|
68
71
|
},
|
|
69
72
|
calls: { signature: 0, list: 0, sync: 0 },
|
|
73
|
+
bundleStatus: 404,
|
|
74
|
+
bundleCalls: 0,
|
|
70
75
|
};
|
|
71
76
|
|
|
72
77
|
let server: ReturnType<typeof Bun.serve> | null = null;
|
|
@@ -91,6 +96,22 @@ describe("refreshSkillsIfChanged", () => {
|
|
|
91
96
|
generatedAt: new Date().toISOString(),
|
|
92
97
|
});
|
|
93
98
|
}
|
|
99
|
+
if (url.pathname.endsWith("/skills/bundle")) {
|
|
100
|
+
state.bundleCalls++;
|
|
101
|
+
if (state.bundleStatus !== 200) {
|
|
102
|
+
return new Response("not found", { status: state.bundleStatus });
|
|
103
|
+
}
|
|
104
|
+
return Response.json({
|
|
105
|
+
skills: state.skillsBody.skills.map((skill) => ({
|
|
106
|
+
...skill,
|
|
107
|
+
files: skill.isComplex
|
|
108
|
+
? [{ path: "refs/notes.md", content: "bundled note", isBinary: false }]
|
|
109
|
+
: [],
|
|
110
|
+
})),
|
|
111
|
+
total: state.skillsBody.skills.length,
|
|
112
|
+
signature: state.skillsBody.signature,
|
|
113
|
+
});
|
|
114
|
+
}
|
|
94
115
|
if (url.pathname.match(/\/api\/agents\/[^/]+\/skills$/)) {
|
|
95
116
|
state.calls.list++;
|
|
96
117
|
if (state.listStatus !== 200) {
|
|
@@ -337,6 +358,43 @@ describe("refreshSkillsIfChanged", () => {
|
|
|
337
358
|
rmSync(cleanHome, { recursive: true, force: true });
|
|
338
359
|
});
|
|
339
360
|
|
|
361
|
+
test("bundle endpoint replaces the list call and the per-file fan-out", async () => {
|
|
362
|
+
state.calls = { signature: 0, list: 0, sync: 0 };
|
|
363
|
+
state.bundleCalls = 0;
|
|
364
|
+
state.bundleStatus = 200;
|
|
365
|
+
state.signatureHash = "hash-bundle";
|
|
366
|
+
state.skillsBody.signature = "hash-bundle";
|
|
367
|
+
state.skillsBody.skills = [
|
|
368
|
+
{
|
|
369
|
+
id: "skill-complex",
|
|
370
|
+
name: "complex-skill",
|
|
371
|
+
description: "has bundled files",
|
|
372
|
+
content: "---\nname: complex-skill\n---\n\nComplex body.",
|
|
373
|
+
isComplex: true,
|
|
374
|
+
isActive: true,
|
|
375
|
+
isEnabled: true,
|
|
376
|
+
},
|
|
377
|
+
];
|
|
378
|
+
|
|
379
|
+
const home = join(FAKE_HOME, "bundle-home");
|
|
380
|
+
mkdirSync(home, { recursive: true });
|
|
381
|
+
const lastHash = { current: null as string | null };
|
|
382
|
+
const result = await refreshWithHome(makeCtx(), lastHash, home);
|
|
383
|
+
|
|
384
|
+
expect(result.changed).toBe(true);
|
|
385
|
+
expect(lastHash.current).toBe("hash-bundle");
|
|
386
|
+
// One signature probe + one bundle call. No list call, no manifest and no
|
|
387
|
+
// per-file fetches.
|
|
388
|
+
expect(state.bundleCalls).toBe(1);
|
|
389
|
+
expect(state.calls).toEqual({ signature: 1, list: 0, sync: 0 });
|
|
390
|
+
expect(
|
|
391
|
+
readFileSync(join(home, ".claude", "skills", "complex-skill", "refs", "notes.md"), "utf-8"),
|
|
392
|
+
).toBe("bundled note");
|
|
393
|
+
|
|
394
|
+
state.bundleStatus = 404;
|
|
395
|
+
rmSync(home, { recursive: true, force: true });
|
|
396
|
+
});
|
|
397
|
+
|
|
340
398
|
test("list fetch failure after signature drift leaves pre-existing skills on disk and does not advance hash", async () => {
|
|
341
399
|
// Arrange: fresh isolated home with a pre-existing swarm-managed skill
|
|
342
400
|
const isolatedHome = join(FAKE_HOME, "list-fail-home");
|
|
@@ -11,6 +11,7 @@ import {
|
|
|
11
11
|
getDbClient,
|
|
12
12
|
getLatestScriptRunStepTaskByContextKey,
|
|
13
13
|
getLatestTaskByContextKey,
|
|
14
|
+
getPendingTaskForAgent,
|
|
14
15
|
initDb,
|
|
15
16
|
updateScriptRun,
|
|
16
17
|
} from "../be/db";
|
|
@@ -20,6 +21,7 @@ import { handleScriptRuns } from "../http/script-runs";
|
|
|
20
21
|
import { getPathSegments, parseQueryParams } from "../http/utils";
|
|
21
22
|
import { localProcessScriptExecutor } from "../script-workflows/executor";
|
|
22
23
|
import { setScriptRunExecutor } from "../script-workflows/supervisor";
|
|
24
|
+
import type { AgentTask } from "../types";
|
|
23
25
|
import {
|
|
24
26
|
clearVolatileSecretsForTesting,
|
|
25
27
|
refreshSecretScrubberCache,
|
|
@@ -537,6 +539,88 @@ describe("/api/script-runs HTTP", () => {
|
|
|
537
539
|
});
|
|
538
540
|
});
|
|
539
541
|
|
|
542
|
+
async function dispatchStepFor(
|
|
543
|
+
stepKey: string,
|
|
544
|
+
stepAgentId: string,
|
|
545
|
+
extra: Record<string, unknown> = {},
|
|
546
|
+
): Promise<{ response: Promise<TestResponse>; task: AgentTask }> {
|
|
547
|
+
const created = await dispatch("/api/script-runs", {
|
|
548
|
+
method: "POST",
|
|
549
|
+
agentId,
|
|
550
|
+
body: createBody(),
|
|
551
|
+
});
|
|
552
|
+
const { id: runId } = (await created.json()) as { id: string };
|
|
553
|
+
const response = dispatch(`/api/internal/script-runs/${runId}/agent-task`, {
|
|
554
|
+
method: "POST",
|
|
555
|
+
agentId,
|
|
556
|
+
body: JSON.stringify({ stepKey, task: `do ${stepKey}`, agentId: stepAgentId, ...extra }),
|
|
557
|
+
});
|
|
558
|
+
const contextKey = `script-run:${runId}:${stepKey}`;
|
|
559
|
+
let task = await getLatestScriptRunStepTaskByContextKey(contextKey);
|
|
560
|
+
for (let i = 0; i < 50 && !task; i++) {
|
|
561
|
+
await Bun.sleep(20);
|
|
562
|
+
task = await getLatestScriptRunStepTaskByContextKey(contextKey);
|
|
563
|
+
}
|
|
564
|
+
expect(task).not.toBeNull();
|
|
565
|
+
return { response, task: task! };
|
|
566
|
+
}
|
|
567
|
+
|
|
568
|
+
test("agent-task dispatch persists dependsOn and holds the task until the dependency completes", async () => {
|
|
569
|
+
const stepAgent = await createAgent({
|
|
570
|
+
name: "deps-step-worker",
|
|
571
|
+
isLead: false,
|
|
572
|
+
status: "idle",
|
|
573
|
+
});
|
|
574
|
+
const dependency = await createTaskExtended("upstream fix", { source: "mcp" });
|
|
575
|
+
|
|
576
|
+
const { response, task } = await dispatchStepFor("after-fix", stepAgent.id, {
|
|
577
|
+
dependsOn: [dependency.id],
|
|
578
|
+
});
|
|
579
|
+
|
|
580
|
+
expect(task.dependsOn).toEqual([dependency.id]);
|
|
581
|
+
expect(await getPendingTaskForAgent(stepAgent.id)).toBeNull();
|
|
582
|
+
|
|
583
|
+
await completeTask(dependency.id, "fixed");
|
|
584
|
+
expect((await getPendingTaskForAgent(stepAgent.id))?.id).toBe(task.id);
|
|
585
|
+
|
|
586
|
+
await completeTask(task.id, "done");
|
|
587
|
+
expect((await response).status).toBe(200);
|
|
588
|
+
});
|
|
589
|
+
|
|
590
|
+
test("agent-task dispatch without dependsOn is startable at once", async () => {
|
|
591
|
+
const stepAgent = await createAgent({
|
|
592
|
+
name: "no-deps-step-worker",
|
|
593
|
+
isLead: false,
|
|
594
|
+
status: "idle",
|
|
595
|
+
});
|
|
596
|
+
|
|
597
|
+
const { response, task } = await dispatchStepFor("no-deps", stepAgent.id);
|
|
598
|
+
|
|
599
|
+
expect(task.dependsOn).toEqual([]);
|
|
600
|
+
expect((await getPendingTaskForAgent(stepAgent.id))?.id).toBe(task.id);
|
|
601
|
+
|
|
602
|
+
await completeTask(task.id, "done");
|
|
603
|
+
expect((await response).status).toBe(200);
|
|
604
|
+
});
|
|
605
|
+
|
|
606
|
+
test("agent-task dispatch rejects a dependsOn entry that is not a task UUID", async () => {
|
|
607
|
+
const created = await dispatch("/api/script-runs", {
|
|
608
|
+
method: "POST",
|
|
609
|
+
agentId,
|
|
610
|
+
body: createBody(),
|
|
611
|
+
});
|
|
612
|
+
const { id: runId } = (await created.json()) as { id: string };
|
|
613
|
+
|
|
614
|
+
const response = await dispatch(`/api/internal/script-runs/${runId}/agent-task`, {
|
|
615
|
+
method: "POST",
|
|
616
|
+
agentId,
|
|
617
|
+
body: JSON.stringify({ stepKey: "bad-deps", task: "x", dependsOn: ["not-a-uuid"] }),
|
|
618
|
+
});
|
|
619
|
+
|
|
620
|
+
expect(response.status).toBe(400);
|
|
621
|
+
expect(await getLatestScriptRunStepTaskByContextKey(`script-run:${runId}:bad-deps`)).toBeNull();
|
|
622
|
+
});
|
|
623
|
+
|
|
540
624
|
test("agent-task polling returns the step output when a newer completed follow-up shares its context key", async () => {
|
|
541
625
|
const created = await dispatch("/api/script-runs", {
|
|
542
626
|
method: "POST",
|
|
@@ -785,6 +785,7 @@ describe("ctx.swarm.config.get through POST /api/scripts/run", () => {
|
|
|
785
785
|
missingIsUndefined: ctx.swarm.config.get("SCRIPT_CFG_NOT_SET") === undefined,
|
|
786
786
|
fallback: read("SCRIPT_CFG_NOT_SET") ?? "fallback",
|
|
787
787
|
apiOnly: read("SLACK_SIGNING_SECRET"),
|
|
788
|
+
transcriptionKey: read("TRANSCRIPTION_API_KEY"),
|
|
788
789
|
proto: read("__proto__"),
|
|
789
790
|
};
|
|
790
791
|
};`;
|
|
@@ -798,6 +799,7 @@ describe("ctx.swarm.config.get through POST /api/scripts/run", () => {
|
|
|
798
799
|
missingIsUndefined: boolean;
|
|
799
800
|
fallback: string;
|
|
800
801
|
apiOnly: string | null;
|
|
802
|
+
transcriptionKey: string | null;
|
|
801
803
|
proto: string | null;
|
|
802
804
|
};
|
|
803
805
|
|
|
@@ -829,11 +831,17 @@ describe("ctx.swarm.config.get through POST /api/scripts/run", () => {
|
|
|
829
831
|
value: `signing-${randomToken(24)}`,
|
|
830
832
|
isSecret: true,
|
|
831
833
|
});
|
|
834
|
+
await upsertSwarmConfig({
|
|
835
|
+
scope: "global",
|
|
836
|
+
key: "TRANSCRIPTION_API_KEY",
|
|
837
|
+
value: `stt-${randomToken(24)}`,
|
|
838
|
+
isSecret: true,
|
|
839
|
+
});
|
|
832
840
|
});
|
|
833
841
|
|
|
834
842
|
afterAll(async () => {
|
|
835
843
|
await getDbClient().run(
|
|
836
|
-
"DELETE FROM swarm_config WHERE key LIKE 'SCRIPT_CFG_%' OR key IN ('SLACK_SIGNING_SECRET', '__proto__')",
|
|
844
|
+
"DELETE FROM swarm_config WHERE key LIKE 'SCRIPT_CFG_%' OR key IN ('SLACK_SIGNING_SECRET', 'TRANSCRIPTION_API_KEY', '__proto__')",
|
|
837
845
|
);
|
|
838
846
|
clearVolatileSecretsForTesting();
|
|
839
847
|
});
|
|
@@ -862,6 +870,7 @@ describe("ctx.swarm.config.get through POST /api/scripts/run", () => {
|
|
|
862
870
|
missingIsUndefined: true,
|
|
863
871
|
fallback: "fallback",
|
|
864
872
|
apiOnly: null,
|
|
873
|
+
transcriptionKey: null,
|
|
865
874
|
proto: "proto-global",
|
|
866
875
|
});
|
|
867
876
|
// The unwrapped secret is scrubbed from everything the run returns.
|
|
@@ -74,6 +74,21 @@ describe("scrubSecrets — env-based replacement", () => {
|
|
|
74
74
|
expect(out).toBe("Authorization: Bearer [REDACTED:GITHUB_TOKEN] end");
|
|
75
75
|
});
|
|
76
76
|
|
|
77
|
+
test("redacts the Bitbucket token and webhook secret as exact keys", () => {
|
|
78
|
+
const values = {
|
|
79
|
+
BITBUCKET_TOKEN: `example-bitbucket-${randomToken(24)}`,
|
|
80
|
+
BITBUCKET_WEBHOOK_SECRET: `example-bbhook-${randomToken(24)}`,
|
|
81
|
+
};
|
|
82
|
+
for (const [name, value] of Object.entries(values)) process.env[name] = value;
|
|
83
|
+
refreshSecretScrubberCache();
|
|
84
|
+
for (const [name, value] of Object.entries(values)) {
|
|
85
|
+
expect(isSensitiveKey(name)).toBe(true);
|
|
86
|
+
expect(scrubSecrets(`Authorization: Bearer ${value} end`)).toBe(
|
|
87
|
+
`Authorization: Bearer [REDACTED:${name}] end`,
|
|
88
|
+
);
|
|
89
|
+
}
|
|
90
|
+
});
|
|
91
|
+
|
|
77
92
|
test("redacts any key with _API_KEY suffix", () => {
|
|
78
93
|
process.env.FOO_SERVICE_API_KEY = "example-supersecretFooApiKey_longerthan12chars";
|
|
79
94
|
refreshSecretScrubberCache();
|