@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
|
@@ -4,6 +4,7 @@ import {
|
|
|
4
4
|
agentmailContextKey,
|
|
5
5
|
azureDevOpsContextKey,
|
|
6
6
|
azureDevOpsWorkItemContextKey,
|
|
7
|
+
bitbucketContextKey,
|
|
7
8
|
buildJiraContextKey,
|
|
8
9
|
chatSdkContextKey,
|
|
9
10
|
githubContextKey,
|
|
@@ -157,6 +158,55 @@ describe("azure-devops work item keys", () => {
|
|
|
157
158
|
});
|
|
158
159
|
});
|
|
159
160
|
|
|
161
|
+
describe("bitbucket pull request keys", () => {
|
|
162
|
+
const UUID = "1a2b3c4d-5e6f-7a8b-9c0d-1e2f3a4b5c6d";
|
|
163
|
+
|
|
164
|
+
test("strips the braces Bitbucket wraps repository UUIDs in", () => {
|
|
165
|
+
expect(bitbucketContextKey({ repoUuid: `{${UUID}}`, pullRequestId: 12 })).toBe(
|
|
166
|
+
`task:trackers:bitbucket:${UUID}:pr:12`,
|
|
167
|
+
);
|
|
168
|
+
expect(bitbucketContextKey({ repoUuid: UUID, pullRequestId: "12" })).toBe(
|
|
169
|
+
`task:trackers:bitbucket:${UUID}:pr:12`,
|
|
170
|
+
);
|
|
171
|
+
});
|
|
172
|
+
|
|
173
|
+
test("lowercases the UUID so payload casing cannot fork a thread", () => {
|
|
174
|
+
expect(bitbucketContextKey({ repoUuid: `{${UUID.toUpperCase()}}`, pullRequestId: 3 })).toBe(
|
|
175
|
+
`task:trackers:bitbucket:${UUID}:pr:3`,
|
|
176
|
+
);
|
|
177
|
+
});
|
|
178
|
+
|
|
179
|
+
test("round-trips through parseContextKey", () => {
|
|
180
|
+
const key = bitbucketContextKey({ repoUuid: `{${UUID}}`, pullRequestId: 42 });
|
|
181
|
+
expect(parseContextKey(key)).toEqual({
|
|
182
|
+
family: "trackers",
|
|
183
|
+
subFamily: "bitbucket",
|
|
184
|
+
parts: { repoUuid: UUID, kind: "pr", pullRequestId: 42 },
|
|
185
|
+
});
|
|
186
|
+
});
|
|
187
|
+
|
|
188
|
+
test("rejects empty, braces-only, unbalanced or separator-bearing UUIDs", () => {
|
|
189
|
+
expect(() => bitbucketContextKey({ repoUuid: "", pullRequestId: 1 })).toThrow();
|
|
190
|
+
expect(() => bitbucketContextKey({ repoUuid: "{}", pullRequestId: 1 })).toThrow();
|
|
191
|
+
expect(() => bitbucketContextKey({ repoUuid: "{{abc}", pullRequestId: 1 })).toThrow();
|
|
192
|
+
expect(() => bitbucketContextKey({ repoUuid: "a:b", pullRequestId: 1 })).toThrow();
|
|
193
|
+
});
|
|
194
|
+
|
|
195
|
+
test("rejects a non-integer pull request id", () => {
|
|
196
|
+
expect(() => bitbucketContextKey({ repoUuid: UUID, pullRequestId: 1.5 })).toThrow(/integer/);
|
|
197
|
+
expect(() => bitbucketContextKey({ repoUuid: UUID, pullRequestId: "abc" })).toThrow(/integer/);
|
|
198
|
+
expect(() => bitbucketContextKey({ repoUuid: UUID, pullRequestId: -1 })).toThrow(/integer/);
|
|
199
|
+
});
|
|
200
|
+
|
|
201
|
+
test("parseContextKey rejects malformed bitbucket keys", () => {
|
|
202
|
+
expect(() => parseContextKey(`task:trackers:bitbucket:${UUID}:pr:abc`)).toThrow();
|
|
203
|
+
expect(() => parseContextKey(`task:trackers:bitbucket:${UUID}:issue:1`)).toThrow();
|
|
204
|
+
expect(() => parseContextKey(`task:trackers:bitbucket:${UUID}:pr`)).toThrow();
|
|
205
|
+
expect(() => parseContextKey("task:trackers:bitbucket::pr:1")).toThrow();
|
|
206
|
+
expect(() => parseContextKey(`task:trackers:bitbucket:${UUID}:pr:1:extra`)).toThrow();
|
|
207
|
+
});
|
|
208
|
+
});
|
|
209
|
+
|
|
160
210
|
describe("parseContextKey", () => {
|
|
161
211
|
test("round-trips slack keys", () => {
|
|
162
212
|
const key = slackContextKey({ channelId: "C0A4J7GB0UD", threadTs: "1776800534.257079" });
|
|
@@ -88,6 +88,11 @@ describe("handleCore auth middleware (route() auth.apiKey=false is honored)", ()
|
|
|
88
88
|
expect(res.status).not.toBe(401);
|
|
89
89
|
});
|
|
90
90
|
|
|
91
|
+
test("Bitbucket webhook is public", async () => {
|
|
92
|
+
const res = await fetch(`http://localhost:${port}/api/bitbucket/webhook`, { method: "POST" });
|
|
93
|
+
expect(res.status).not.toBe(401);
|
|
94
|
+
});
|
|
95
|
+
|
|
91
96
|
test("AgentMail webhook is still public", async () => {
|
|
92
97
|
const res = await fetch(`http://localhost:${port}/api/agentmail/webhook`, { method: "POST" });
|
|
93
98
|
expect(res.status).not.toBe(401);
|
|
@@ -336,7 +336,7 @@ describe("page-session CORS over HTTP", () => {
|
|
|
336
336
|
const cookie = response.headers.get("set-cookie")!.split(";")[0]!;
|
|
337
337
|
for (const path of [`/p/${pageId}.json`, `/@swarm/api/pages/${pageId}`]) {
|
|
338
338
|
const page = await fetch(`${base}${path}`, {
|
|
339
|
-
headers: { Origin: origin, Cookie: cookie },
|
|
339
|
+
headers: { Origin: origin, Cookie: cookie, "X-Swarm-Page-Id": pageId },
|
|
340
340
|
});
|
|
341
341
|
expect(page.status).toBe(200);
|
|
342
342
|
expect(page.headers.get("access-control-allow-origin")).toBe(
|
|
@@ -102,6 +102,7 @@ type:UpsertAssetKeyMappingInput
|
|
|
102
102
|
type:WorkflowFilters
|
|
103
103
|
type:WorkflowRunListOptions
|
|
104
104
|
type:WorkflowRunPage
|
|
105
|
+
value:AssetKeyMovesBlockedError
|
|
105
106
|
value:EXTENSION_AGENT_ROLE
|
|
106
107
|
value:ExtensionAgentAssignmentError
|
|
107
108
|
value:KvTypeCollisionError
|
|
@@ -116,6 +117,7 @@ value:UNATTRIBUTED_USER_ID
|
|
|
116
117
|
value:__resetSqliteVecExtensionPathCacheForTests
|
|
117
118
|
value:abandonSlackOutcomeDelivery
|
|
118
119
|
value:acceptTask
|
|
120
|
+
value:assertAssetKeyMovesAllowed
|
|
119
121
|
value:assignUnassignedTaskPending
|
|
120
122
|
value:autoEncryptLegacyPlaintextSecrets
|
|
121
123
|
value:backfillSupersedeTaskResumeTaskId
|
|
@@ -215,6 +217,7 @@ value:ensureSlackRenderV2Activation
|
|
|
215
217
|
value:extensionAgentAssignmentError
|
|
216
218
|
value:failPendingResumeIfUnclaimed
|
|
217
219
|
value:failTask
|
|
220
|
+
value:findActiveTasksByVcs
|
|
218
221
|
value:findCompletedTaskInThread
|
|
219
222
|
value:findExistingLinearTrackerContextWork
|
|
220
223
|
value:findRecentCancelledTaskInThread
|
|
@@ -335,6 +338,7 @@ value:getPageVersion
|
|
|
335
338
|
value:getPageVersions
|
|
336
339
|
value:getPausedTasksForAgent
|
|
337
340
|
value:getPendingApprovalVoteState
|
|
341
|
+
value:getPendingApprovalsForTasks
|
|
338
342
|
value:getPendingChatRelayTasks
|
|
339
343
|
value:getPendingEventWaitNames
|
|
340
344
|
value:getPendingSlackRelayTasks
|
|
@@ -486,6 +490,7 @@ value:listUserFavorites
|
|
|
486
490
|
value:listWorkflowRuns
|
|
487
491
|
value:listWorkflowRunsPage
|
|
488
492
|
value:listWorkflows
|
|
493
|
+
value:literalPrefixPattern
|
|
489
494
|
value:markChatRelayAttempted
|
|
490
495
|
value:markChatRelayDelivered
|
|
491
496
|
value:markChatRelayGivenUp
|
|
@@ -505,6 +510,7 @@ value:markTaskSlackReplySent
|
|
|
505
510
|
value:markTasksNotified
|
|
506
511
|
value:maskSecrets
|
|
507
512
|
value:moveAssetKey
|
|
513
|
+
value:moveTaskChainKeyInTx
|
|
508
514
|
value:moveTaskFromBacklog
|
|
509
515
|
value:moveTaskToBacklog
|
|
510
516
|
value:normalizeSkillFilePath
|
|
@@ -615,6 +621,7 @@ type:PendingChatRelay
|
|
|
615
621
|
type:TaskFilters
|
|
616
622
|
value:SUPERSEDED_RESUME_REPAIR_WINDOW_MS
|
|
617
623
|
value:TASK_LINEAGE_LIMITS
|
|
624
|
+
value:findActiveTasksByVcs
|
|
618
625
|
value:findCompletedTaskInThread
|
|
619
626
|
value:findExistingLinearTrackerContextWork
|
|
620
627
|
value:findRecentCancelledTaskInThread
|
|
@@ -125,6 +125,7 @@ type:UpsertAssetKeyMappingInput
|
|
|
125
125
|
type:WorkflowFilters
|
|
126
126
|
type:WorkflowRunListOptions
|
|
127
127
|
type:WorkflowRunPage
|
|
128
|
+
value:AssetKeyMovesBlockedError
|
|
128
129
|
value:EXTENSION_AGENT_ROLE
|
|
129
130
|
value:ExtensionAgentAssignmentError
|
|
130
131
|
value:KvTypeCollisionError
|
|
@@ -139,6 +140,7 @@ value:UNATTRIBUTED_USER_ID
|
|
|
139
140
|
value:__resetSqliteVecExtensionPathCacheForTests
|
|
140
141
|
value:abandonSlackOutcomeDelivery
|
|
141
142
|
value:acceptTask
|
|
143
|
+
value:assertAssetKeyMovesAllowed
|
|
142
144
|
value:assignUnassignedTaskPending
|
|
143
145
|
value:autoEncryptLegacyPlaintextSecrets
|
|
144
146
|
value:backfillSupersedeTaskResumeTaskId
|
|
@@ -238,6 +240,7 @@ value:ensureSlackRenderV2Activation
|
|
|
238
240
|
value:extensionAgentAssignmentError
|
|
239
241
|
value:failPendingResumeIfUnclaimed
|
|
240
242
|
value:failTask
|
|
243
|
+
value:findActiveTasksByVcs
|
|
241
244
|
value:findCompletedTaskInThread
|
|
242
245
|
value:findExistingLinearTrackerContextWork
|
|
243
246
|
value:findRecentCancelledTaskInThread
|
|
@@ -358,6 +361,7 @@ value:getPageVersion
|
|
|
358
361
|
value:getPageVersions
|
|
359
362
|
value:getPausedTasksForAgent
|
|
360
363
|
value:getPendingApprovalVoteState
|
|
364
|
+
value:getPendingApprovalsForTasks
|
|
361
365
|
value:getPendingChatRelayTasks
|
|
362
366
|
value:getPendingEventWaitNames
|
|
363
367
|
value:getPendingSlackRelayTasks
|
|
@@ -509,6 +513,7 @@ value:listUserFavorites
|
|
|
509
513
|
value:listWorkflowRuns
|
|
510
514
|
value:listWorkflowRunsPage
|
|
511
515
|
value:listWorkflows
|
|
516
|
+
value:literalPrefixPattern
|
|
512
517
|
value:markChatRelayAttempted
|
|
513
518
|
value:markChatRelayDelivered
|
|
514
519
|
value:markChatRelayGivenUp
|
|
@@ -528,6 +533,7 @@ value:markTaskSlackReplySent
|
|
|
528
533
|
value:markTasksNotified
|
|
529
534
|
value:maskSecrets
|
|
530
535
|
value:moveAssetKey
|
|
536
|
+
value:moveTaskChainKeyInTx
|
|
531
537
|
value:moveTaskFromBacklog
|
|
532
538
|
value:moveTaskToBacklog
|
|
533
539
|
value:normalizeSkillFilePath
|
|
@@ -78,7 +78,7 @@ describe("docs API index footprint", () => {
|
|
|
78
78
|
expect(tasks).toContain('"path":"/api/task-templates","method":"get"');
|
|
79
79
|
});
|
|
80
80
|
|
|
81
|
-
test("OpenAPI and generated pages keep all
|
|
81
|
+
test("OpenAPI and generated pages keep all 444 operations", async () => {
|
|
82
82
|
const spec = await Bun.file(resolve(repoRoot, "openapi.json")).json();
|
|
83
83
|
const operationKeys = [];
|
|
84
84
|
const tags = new Set();
|
|
@@ -103,8 +103,8 @@ describe("docs API index footprint", () => {
|
|
|
103
103
|
expect(spec.paths["/api/agents/{id}/runtime"]?.get).toBeDefined();
|
|
104
104
|
|
|
105
105
|
const generatedKeys = await generatedOperationKeys();
|
|
106
|
-
expect(operationKeys).toHaveLength(
|
|
107
|
-
expect(generatedKeys).toHaveLength(
|
|
106
|
+
expect(operationKeys).toHaveLength(445);
|
|
107
|
+
expect(generatedKeys).toHaveLength(445);
|
|
108
108
|
expect(new Set(generatedKeys)).toEqual(new Set(operationKeys));
|
|
109
109
|
});
|
|
110
110
|
|
|
@@ -1,8 +1,12 @@
|
|
|
1
1
|
import { afterAll, afterEach, beforeAll, beforeEach, describe, expect, mock, test } from "bun:test";
|
|
2
|
+
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
|
|
3
|
+
import { closeDb, createAgent, getDbClient, initDb, upsertSwarmConfig } from "../be/db";
|
|
2
4
|
import * as contractRuntime from "../extensions/contract-runtime";
|
|
5
|
+
import { registerSystemOneDecideTool } from "../tools/system-one-decide";
|
|
3
6
|
|
|
4
|
-
// Unit tests for templates/extensions/slack-reply-gate/hooks.ts.
|
|
5
|
-
//
|
|
7
|
+
// Unit tests for templates/extensions/slack-reply-gate/hooks.ts. Decisions go to a
|
|
8
|
+
// stubbed `ctx.swarm.systemOne_decide` and Slack calls to a mocked fetch; nothing
|
|
9
|
+
// reaches the network. The last block runs the real system-one-decide tool behind it.
|
|
6
10
|
|
|
7
11
|
// `swarm-extension` is a bare import the extension loader shims to contract-runtime.
|
|
8
12
|
mock.module("swarm-extension", () => contractRuntime);
|
|
@@ -11,11 +15,14 @@ type Handler = (event: any, ctx: any) => Promise<any>;
|
|
|
11
15
|
let handler: Handler;
|
|
12
16
|
let mod: any;
|
|
13
17
|
|
|
14
|
-
// The hook reads
|
|
15
|
-
// runner's values are saved here and restored after the file.
|
|
18
|
+
// The hook reads credential presence and the Slack token from the API process env.
|
|
19
|
+
// Each test sets its own; the runner's values are saved here and restored after the file.
|
|
16
20
|
const ENV_KEYS = [
|
|
17
21
|
"TYPESAFE_API_KEY",
|
|
18
22
|
"OPENROUTER_API_KEY",
|
|
23
|
+
"CLOUDFLARE_API_TOKEN",
|
|
24
|
+
"CLOUDFLARE_API_KEY",
|
|
25
|
+
"CLOUDFLARE_ACCOUNT_ID",
|
|
19
26
|
"SLACK_BOT_TOKEN",
|
|
20
27
|
...["ACCEPTED", "BUFFERED", "NOW", "STEERED"].map((k) => `SLACK_REACTION_${k}`),
|
|
21
28
|
];
|
|
@@ -54,8 +61,25 @@ const slackOk = (method: string, body: any): Response => {
|
|
|
54
61
|
};
|
|
55
62
|
let slackReply: SlackReply = slackOk;
|
|
56
63
|
|
|
57
|
-
/**
|
|
58
|
-
|
|
64
|
+
/** What the MCP bridge returns for a tool call: the tool's structured content in `data`. */
|
|
65
|
+
type BridgeResult = { success: boolean; status: number; data: Record<string, unknown> };
|
|
66
|
+
const served = (data: Record<string, unknown>): BridgeResult => ({
|
|
67
|
+
success: true,
|
|
68
|
+
status: 200,
|
|
69
|
+
data: { success: true, message: "Decision", ...data },
|
|
70
|
+
});
|
|
71
|
+
const refused = (message: string): BridgeResult => ({
|
|
72
|
+
success: true,
|
|
73
|
+
status: 200,
|
|
74
|
+
data: { success: false, message },
|
|
75
|
+
});
|
|
76
|
+
|
|
77
|
+
type DecideReply = (args: any) => BridgeResult | Promise<BridgeResult>;
|
|
78
|
+
let decideReply: DecideReply = () => refused("no decision stub");
|
|
79
|
+
|
|
80
|
+
/** Stub `systemOne_decide` with `reply(args)`. Slack calls go to `slackReply` through fetch. */
|
|
81
|
+
const mockJev = (reply: DecideReply) => {
|
|
82
|
+
decideReply = reply;
|
|
59
83
|
globalThis.fetch = (async (url: any, init: any) => {
|
|
60
84
|
const body = JSON.parse(String(init?.body ?? "{}"));
|
|
61
85
|
const href = String(url);
|
|
@@ -64,8 +88,7 @@ const mockJev = (reply: (body: any) => Response | Promise<Response>) => {
|
|
|
64
88
|
slackCalls.push({ method, body, auth: String(init?.headers?.Authorization ?? "") });
|
|
65
89
|
return slackReply(method, body);
|
|
66
90
|
}
|
|
67
|
-
|
|
68
|
-
return reply(body);
|
|
91
|
+
throw new Error(`unexpected fetch: ${href}`);
|
|
69
92
|
}) as typeof fetch;
|
|
70
93
|
};
|
|
71
94
|
const jevChoice = (
|
|
@@ -73,27 +96,29 @@ const jevChoice = (
|
|
|
73
96
|
confidence = 0.9,
|
|
74
97
|
probabilities: Record<string, number> | null = { [choice]: confidence, other: 1 - confidence },
|
|
75
98
|
) =>
|
|
76
|
-
mockJev(
|
|
77
|
-
(
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
},
|
|
89
|
-
}),
|
|
90
|
-
{ status: 200 },
|
|
91
|
-
),
|
|
99
|
+
mockJev(() =>
|
|
100
|
+
served({
|
|
101
|
+
model: "jev-1.13.0",
|
|
102
|
+
answers: {
|
|
103
|
+
reason: {
|
|
104
|
+
type: "choice",
|
|
105
|
+
choice,
|
|
106
|
+
confidence,
|
|
107
|
+
...(probabilities ? { probabilities } : {}),
|
|
108
|
+
},
|
|
109
|
+
},
|
|
110
|
+
}),
|
|
92
111
|
);
|
|
93
112
|
|
|
94
113
|
const makeCtx = (
|
|
95
114
|
config: Record<string, unknown> = {},
|
|
96
|
-
opts: {
|
|
115
|
+
opts: {
|
|
116
|
+
key?: string;
|
|
117
|
+
openrouterKey?: string;
|
|
118
|
+
cloudflareToken?: string;
|
|
119
|
+
cloudflareAccount?: string;
|
|
120
|
+
slackToken?: string;
|
|
121
|
+
} = {},
|
|
97
122
|
) => {
|
|
98
123
|
const kv: Record<string, unknown> = {};
|
|
99
124
|
const counters: Record<string, number> = {};
|
|
@@ -103,8 +128,14 @@ const makeCtx = (
|
|
|
103
128
|
};
|
|
104
129
|
setEnv("TYPESAFE_API_KEY", opts.key ?? "tsk-test");
|
|
105
130
|
setEnv("OPENROUTER_API_KEY", opts.openrouterKey ?? "");
|
|
131
|
+
setEnv("CLOUDFLARE_API_TOKEN", opts.cloudflareToken ?? "");
|
|
132
|
+
setEnv("CLOUDFLARE_ACCOUNT_ID", opts.cloudflareAccount ?? "");
|
|
106
133
|
setEnv("SLACK_BOT_TOKEN", opts.slackToken ?? "xoxb-test");
|
|
107
|
-
const swarm = {
|
|
134
|
+
const swarm: Record<string, (args: any) => Promise<unknown>> = {
|
|
135
|
+
systemOne_decide: async (args: any) => {
|
|
136
|
+
jevCalls.push({ provider: args.provider, body: args });
|
|
137
|
+
return decideReply(args);
|
|
138
|
+
},
|
|
108
139
|
kv_set: async ({ key, value }: any) => {
|
|
109
140
|
kv[key] = value;
|
|
110
141
|
return { success: true };
|
|
@@ -154,6 +185,7 @@ beforeEach(() => {
|
|
|
154
185
|
jevCalls = [];
|
|
155
186
|
slackCalls = [];
|
|
156
187
|
slackReply = slackOk;
|
|
188
|
+
decideReply = () => refused("no decision stub");
|
|
157
189
|
mod.resetKeyCache();
|
|
158
190
|
});
|
|
159
191
|
afterEach(async () => {
|
|
@@ -325,107 +357,112 @@ describe("decide", () => {
|
|
|
325
357
|
});
|
|
326
358
|
});
|
|
327
359
|
|
|
328
|
-
describe("
|
|
360
|
+
describe("provider", () => {
|
|
329
361
|
const cfg = (extra: Record<string, unknown> = {}) => mod.config.parse(extra);
|
|
362
|
+
const CF_ACCOUNT = "0123456789abcdef0123456789abcdef";
|
|
330
363
|
|
|
331
|
-
test("auto prefers TypeSafe when
|
|
332
|
-
makeCtx(
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
364
|
+
test("auto prefers TypeSafe when every credential is set", () => {
|
|
365
|
+
makeCtx(
|
|
366
|
+
{},
|
|
367
|
+
{
|
|
368
|
+
key: "tsk-test",
|
|
369
|
+
openrouterKey: "or-test",
|
|
370
|
+
cloudflareToken: "cf",
|
|
371
|
+
cloudflareAccount: CF_ACCOUNT,
|
|
372
|
+
},
|
|
373
|
+
);
|
|
374
|
+
expect(mod.resolveProvider(cfg())).toEqual({ provider: "typesafe", model: "jev-latest" });
|
|
339
375
|
});
|
|
340
376
|
|
|
341
|
-
test("auto falls back to OpenRouter
|
|
342
|
-
makeCtx(
|
|
343
|
-
|
|
377
|
+
test("auto falls back to OpenRouter, then to Clef", () => {
|
|
378
|
+
makeCtx(
|
|
379
|
+
{},
|
|
380
|
+
{ key: "", openrouterKey: "or-test", cloudflareToken: "cf", cloudflareAccount: CF_ACCOUNT },
|
|
381
|
+
);
|
|
382
|
+
expect(mod.resolveProvider(cfg())).toEqual({
|
|
344
383
|
provider: "openrouter",
|
|
345
|
-
endpoint: "https://openrouter.ai/api/alpha/decisions",
|
|
346
|
-
key: "or-test",
|
|
347
384
|
model: "typesafe/jev-1.13",
|
|
348
385
|
});
|
|
386
|
+
mod.resetKeyCache();
|
|
387
|
+
makeCtx({}, { key: "", cloudflareToken: "cf", cloudflareAccount: CF_ACCOUNT });
|
|
388
|
+
expect(mod.resolveProvider(cfg())).toEqual({ provider: "cloudflare", model: "clef" });
|
|
349
389
|
});
|
|
350
390
|
|
|
351
|
-
test("
|
|
352
|
-
makeCtx({}, { key: "
|
|
353
|
-
|
|
391
|
+
test("auto accepts CLOUDFLARE_API_KEY and needs the account id", () => {
|
|
392
|
+
makeCtx({}, { key: "", cloudflareAccount: CF_ACCOUNT });
|
|
393
|
+
process.env.CLOUDFLARE_API_KEY = "cf-key";
|
|
394
|
+
expect(mod.resolveProvider(cfg()).provider).toBe("cloudflare");
|
|
354
395
|
mod.resetKeyCache();
|
|
355
|
-
makeCtx({}, { key: "",
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
396
|
+
makeCtx({}, { key: "", cloudflareToken: "cf" });
|
|
397
|
+
delete process.env.CLOUDFLARE_API_KEY;
|
|
398
|
+
expect(() => mod.resolveProvider(cfg())).toThrow("CLOUDFLARE_ACCOUNT_ID");
|
|
399
|
+
});
|
|
400
|
+
|
|
401
|
+
test("an explicit provider is used as is; the server checks its credential", () => {
|
|
402
|
+
makeCtx({}, { key: "tsk-test", openrouterKey: "or-test" });
|
|
403
|
+
expect(mod.resolveProvider(cfg({ provider: "openrouter" })).provider).toBe("openrouter");
|
|
404
|
+
expect(
|
|
405
|
+
mod.resolveProvider(cfg({ provider: "cloudflare", cloudflareModel: "clef-flash" })),
|
|
406
|
+
).toEqual({
|
|
407
|
+
provider: "cloudflare",
|
|
408
|
+
model: "clef-flash",
|
|
409
|
+
});
|
|
410
|
+
// A host with no model setting sends none: the server uses the host's default.
|
|
411
|
+
expect(mod.resolveProvider(cfg({ provider: "laya" }))).toEqual({
|
|
412
|
+
provider: "laya",
|
|
413
|
+
model: undefined,
|
|
414
|
+
});
|
|
359
415
|
});
|
|
360
416
|
|
|
361
|
-
test("auto with no
|
|
417
|
+
test("auto with no credential names every secret", () => {
|
|
362
418
|
makeCtx({}, { key: "", openrouterKey: "" });
|
|
363
|
-
expect(() => mod.
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
test("an OpenRouter-only swarm classifies through the Decisions endpoint", async () => {
|
|
367
|
-
// Shape captured from a live OpenRouter Decisions call to typesafe/jev-1.13.
|
|
368
|
-
mockJev(() =>
|
|
369
|
-
Response.json({
|
|
370
|
-
model: "typesafe/jev-1.13-20260917",
|
|
371
|
-
answers: {
|
|
372
|
-
reason: {
|
|
373
|
-
type: "choice",
|
|
374
|
-
choice: "reaction_only",
|
|
375
|
-
probabilities: { direct_ask: 0, reaction_only: 0.95, banter_between_others: 0.05 },
|
|
376
|
-
confidence: 0.93,
|
|
377
|
-
},
|
|
378
|
-
},
|
|
379
|
-
usage: { input_tokens: 430, output_tokens: 53, cost: 0.00001806 },
|
|
380
|
-
id: "gen-dec-1",
|
|
381
|
-
provider: "TypeSafe",
|
|
382
|
-
}),
|
|
419
|
+
expect(() => mod.resolveProvider(cfg())).toThrow(
|
|
420
|
+
"no TYPESAFE_API_KEY, OPENROUTER_API_KEY, or CLOUDFLARE_API_TOKEN with CLOUDFLARE_ACCOUNT_ID",
|
|
383
421
|
);
|
|
422
|
+
});
|
|
423
|
+
|
|
424
|
+
test("the decision goes through systemOne_decide, never a direct fetch", async () => {
|
|
425
|
+
jevChoice("reaction_only", 0.95);
|
|
384
426
|
const { ctx, kv } = makeCtx({ mode: "enforce" }, { key: "", openrouterKey: "or-test" });
|
|
385
427
|
const result = await handler(slackEvent(["lol nice"]), ctx);
|
|
386
|
-
expect(result).toEqual({ action: "block", reason: expect.stringContaining("
|
|
428
|
+
expect(result).toEqual({ action: "block", reason: expect.stringContaining("reaction_only") });
|
|
387
429
|
expect(jevCalls).toHaveLength(1);
|
|
388
|
-
expect(jevCalls[0].
|
|
389
|
-
|
|
430
|
+
expect(jevCalls[0].body).toMatchObject({
|
|
431
|
+
provider: "openrouter",
|
|
432
|
+
model: "typesafe/jev-1.13",
|
|
433
|
+
timeoutMs: 3000,
|
|
434
|
+
maxRetries: 0,
|
|
435
|
+
});
|
|
390
436
|
expect(Object.keys(jevCalls[0].body.questions.reason.criteria)).toEqual(
|
|
391
437
|
Object.keys(mod.REASONS),
|
|
392
438
|
);
|
|
439
|
+
expect(JSON.stringify(jevCalls[0].body)).not.toContain("or-test");
|
|
393
440
|
expect(kv.last).toMatchObject({ provider: "openrouter", reason: "reaction_only" });
|
|
394
441
|
});
|
|
395
442
|
|
|
396
|
-
test("
|
|
397
|
-
mockJev(() =>
|
|
443
|
+
test("a provider error relayed by the server fails open and names the provider", async () => {
|
|
444
|
+
mockJev(() => refused("OpenRouter returned HTTP 402"));
|
|
398
445
|
const { ctx, kv } = makeCtx({ mode: "enforce" }, { key: "", openrouterKey: "or-test" });
|
|
399
446
|
expect(await handler(slackEvent(["lol"]), ctx)).toBeUndefined();
|
|
400
447
|
expect(kv.last).toMatchObject({
|
|
401
448
|
action: "fail-open",
|
|
402
|
-
error: expect.stringContaining("openrouter 402"),
|
|
449
|
+
error: expect.stringContaining("openrouter: OpenRouter returned HTTP 402"),
|
|
403
450
|
});
|
|
404
451
|
});
|
|
405
452
|
});
|
|
406
453
|
|
|
407
454
|
describe("fail open", () => {
|
|
408
455
|
const cases: Array<[string, () => void, Record<string, unknown>]> = [
|
|
409
|
-
["
|
|
410
|
-
["network error", () => mockJev(() => Promise.reject(new Error("ECONNRESET"))), {}],
|
|
456
|
+
["provider error", () => mockJev(() => refused("TypeSafe returned HTTP 500")), {}],
|
|
411
457
|
[
|
|
412
|
-
"
|
|
413
|
-
() => mockJev(() =>
|
|
458
|
+
"bridge error",
|
|
459
|
+
() => mockJev(() => ({ success: false, status: 500, data: { error: "boom" } })),
|
|
414
460
|
{},
|
|
415
461
|
],
|
|
462
|
+
["network error", () => mockJev(() => Promise.reject(new Error("ECONNRESET"))), {}],
|
|
463
|
+
["malformed answer", () => mockJev(() => served({ answers: {} })), {}],
|
|
416
464
|
["unknown label", () => jevChoice("definitely_skip"), {}],
|
|
417
|
-
[
|
|
418
|
-
"timeout",
|
|
419
|
-
() =>
|
|
420
|
-
mockJev(
|
|
421
|
-
(_body) =>
|
|
422
|
-
new Promise<Response>((_resolve, reject) => {
|
|
423
|
-
const signal = jevCalls.at(-1)?.signal as AbortSignal;
|
|
424
|
-
signal.addEventListener("abort", () => reject(new Error("aborted")));
|
|
425
|
-
}),
|
|
426
|
-
),
|
|
427
|
-
{ timeoutMs: 50 },
|
|
428
|
-
],
|
|
465
|
+
["timeout", () => mockJev(() => new Promise<BridgeResult>(() => {})), { timeoutMs: 50 }],
|
|
429
466
|
];
|
|
430
467
|
for (const [label, arrange, cfg] of cases) {
|
|
431
468
|
test(`${label} -> continue, error recorded`, async () => {
|
|
@@ -440,7 +477,7 @@ describe("fail open", () => {
|
|
|
440
477
|
});
|
|
441
478
|
}
|
|
442
479
|
|
|
443
|
-
test("missing API key -> continue without
|
|
480
|
+
test("missing API key under auto -> continue without a decision call", async () => {
|
|
444
481
|
jevChoice("reaction_only");
|
|
445
482
|
const { ctx, counters } = makeCtx({ mode: "enforce" }, { key: "" });
|
|
446
483
|
const result = await handler(slackEvent(["lol"]), ctx);
|
|
@@ -754,3 +791,137 @@ describe("mute on drop (enforce)", () => {
|
|
|
754
791
|
expect(slackCalls).toHaveLength(0);
|
|
755
792
|
});
|
|
756
793
|
});
|
|
794
|
+
|
|
795
|
+
describe("on the real system-one-decide tool", () => {
|
|
796
|
+
const CF_ACCOUNT = "0123456789abcdef0123456789abcdef";
|
|
797
|
+
const CF_TOKEN = "cf-reply-gate-example-token";
|
|
798
|
+
const CLEF_URL = `https://api.cloudflare.com/client/v4/accounts/${CF_ACCOUNT}/ai/run/@cf/cloudflare/clef`;
|
|
799
|
+
let agentId: string;
|
|
800
|
+
let workersAi: Array<{ url: string; authorization: string | null; body: any }> = [];
|
|
801
|
+
|
|
802
|
+
beforeAll(async () => {
|
|
803
|
+
initDb(":memory:");
|
|
804
|
+
agentId = (await createAgent({ name: "reply-gate-ext", isLead: false, status: "idle" })).id;
|
|
805
|
+
await upsertSwarmConfig({ scope: "global", key: "CLOUDFLARE_ACCOUNT_ID", value: CF_ACCOUNT });
|
|
806
|
+
});
|
|
807
|
+
afterAll(() => closeDb());
|
|
808
|
+
|
|
809
|
+
/** `ctx.swarm.systemOne_decide` as the MCP bridge serves it, backed by the registered tool. */
|
|
810
|
+
const viaTool = (ctx: any) => {
|
|
811
|
+
const server = new McpServer({ name: "reply-gate-decide", version: "1" });
|
|
812
|
+
registerSystemOneDecideTool(server);
|
|
813
|
+
const tool = (server as any)._registeredTools["system-one-decide"];
|
|
814
|
+
ctx.swarm.systemOne_decide = async (args: any) => {
|
|
815
|
+
jevCalls.push({ provider: args.provider, body: args });
|
|
816
|
+
const result = await tool.handler(args, {
|
|
817
|
+
sessionId: "extension",
|
|
818
|
+
requestInfo: { headers: { "x-agent-id": agentId } },
|
|
819
|
+
});
|
|
820
|
+
return { success: true, status: 200, data: result.structuredContent };
|
|
821
|
+
};
|
|
822
|
+
};
|
|
823
|
+
|
|
824
|
+
/** Workers AI answering in its REST envelope; Slack calls go to `slackReply`. */
|
|
825
|
+
const mockWorkersAi = (choice: string, p: number) => {
|
|
826
|
+
workersAi = [];
|
|
827
|
+
globalThis.fetch = (async (input: any, init: any) => {
|
|
828
|
+
const url = String(input);
|
|
829
|
+
const body = JSON.parse(String(init?.body ?? "{}"));
|
|
830
|
+
if (url.startsWith("https://slack.com/api/")) {
|
|
831
|
+
const method = url.slice("https://slack.com/api/".length);
|
|
832
|
+
slackCalls.push({ method, body, auth: String(init?.headers?.authorization ?? "") });
|
|
833
|
+
return slackReply(method, body);
|
|
834
|
+
}
|
|
835
|
+
workersAi.push({ url, authorization: new Headers(init?.headers).get("authorization"), body });
|
|
836
|
+
const labels = Object.keys(mod.REASONS);
|
|
837
|
+
const rest = (1 - p) / (labels.length - 1);
|
|
838
|
+
const probabilities = Object.fromEntries(labels.map((l) => [l, l === choice ? p : rest]));
|
|
839
|
+
return Response.json({
|
|
840
|
+
result: {
|
|
841
|
+
model: "clef",
|
|
842
|
+
answers: { reason: { type: "choice", choice, probabilities, confidence: p } },
|
|
843
|
+
usage: { input_tokens: 412, output_tokens: 0 },
|
|
844
|
+
},
|
|
845
|
+
success: true,
|
|
846
|
+
errors: [],
|
|
847
|
+
messages: [],
|
|
848
|
+
});
|
|
849
|
+
}) as typeof fetch;
|
|
850
|
+
};
|
|
851
|
+
|
|
852
|
+
test("Clef classifies on the server; the hook never holds the token", async () => {
|
|
853
|
+
await upsertSwarmConfig({
|
|
854
|
+
scope: "global",
|
|
855
|
+
key: "CLOUDFLARE_API_TOKEN",
|
|
856
|
+
value: CF_TOKEN,
|
|
857
|
+
isSecret: true,
|
|
858
|
+
});
|
|
859
|
+
mockWorkersAi("reaction_only", 0.9);
|
|
860
|
+
const { ctx, kv } = makeCtx(
|
|
861
|
+
{ mode: "enforce" },
|
|
862
|
+
{ key: "", cloudflareToken: "present", cloudflareAccount: CF_ACCOUNT },
|
|
863
|
+
);
|
|
864
|
+
viaTool(ctx);
|
|
865
|
+
|
|
866
|
+
const result = await handler(slackEvent(["lol nice"]), ctx);
|
|
867
|
+
expect(result).toEqual({ action: "block", reason: expect.stringContaining("reaction_only") });
|
|
868
|
+
expect(workersAi).toHaveLength(1);
|
|
869
|
+
expect(workersAi[0]).toMatchObject({ url: CLEF_URL, authorization: `Bearer ${CF_TOKEN}` });
|
|
870
|
+
expect(workersAi[0]!.body.model).toBe("clef");
|
|
871
|
+
expect(kv.last).toMatchObject({
|
|
872
|
+
provider: "cloudflare",
|
|
873
|
+
reason: "reaction_only",
|
|
874
|
+
action: "skipped",
|
|
875
|
+
});
|
|
876
|
+
expect(JSON.stringify(kv)).not.toContain(CF_TOKEN);
|
|
877
|
+
});
|
|
878
|
+
|
|
879
|
+
test("auto skips OpenRouter behind a gateway, which the server refuses, and reaches Clef", async () => {
|
|
880
|
+
await upsertSwarmConfig({
|
|
881
|
+
scope: "global",
|
|
882
|
+
key: "CLOUDFLARE_API_TOKEN",
|
|
883
|
+
value: CF_TOKEN,
|
|
884
|
+
isSecret: true,
|
|
885
|
+
});
|
|
886
|
+
const prevBase = process.env.OPENROUTER_BASE_URL;
|
|
887
|
+
process.env.OPENROUTER_BASE_URL = "https://llm-gateway.example.com/v1";
|
|
888
|
+
try {
|
|
889
|
+
mockWorkersAi("reaction_only", 0.9);
|
|
890
|
+
const { ctx, kv } = makeCtx(
|
|
891
|
+
{ mode: "enforce" },
|
|
892
|
+
{
|
|
893
|
+
key: "",
|
|
894
|
+
openrouterKey: "or-gateway-token",
|
|
895
|
+
cloudflareToken: "present",
|
|
896
|
+
cloudflareAccount: CF_ACCOUNT,
|
|
897
|
+
},
|
|
898
|
+
);
|
|
899
|
+
viaTool(ctx);
|
|
900
|
+
|
|
901
|
+
const result = await handler(slackEvent(["lol nice"]), ctx);
|
|
902
|
+
expect(result).toEqual({ action: "block", reason: expect.stringContaining("reaction_only") });
|
|
903
|
+
expect(jevCalls.map((c) => c.provider)).toEqual(["openrouter", "cloudflare"]);
|
|
904
|
+
expect(workersAi).toHaveLength(1);
|
|
905
|
+
expect(workersAi[0]!.url).toBe(CLEF_URL);
|
|
906
|
+
expect(kv.last).toMatchObject({ provider: "cloudflare", action: "skipped" });
|
|
907
|
+
} finally {
|
|
908
|
+
if (prevBase === undefined) delete process.env.OPENROUTER_BASE_URL;
|
|
909
|
+
else process.env.OPENROUTER_BASE_URL = prevBase;
|
|
910
|
+
}
|
|
911
|
+
});
|
|
912
|
+
|
|
913
|
+
test("a token missing on the server fails open, as before", async () => {
|
|
914
|
+
await getDbClient().run("DELETE FROM swarm_config WHERE key = 'CLOUDFLARE_API_TOKEN'");
|
|
915
|
+
mockWorkersAi("reaction_only", 0.9);
|
|
916
|
+
const { ctx, kv, counters } = makeCtx({ mode: "enforce", provider: "cloudflare" }, { key: "" });
|
|
917
|
+
viaTool(ctx);
|
|
918
|
+
|
|
919
|
+
expect(await handler(slackEvent(["lol"]), ctx)).toBeUndefined();
|
|
920
|
+
expect(workersAi).toHaveLength(0);
|
|
921
|
+
expect(counters.error).toBe(1);
|
|
922
|
+
expect(kv.last).toMatchObject({
|
|
923
|
+
action: "fail-open",
|
|
924
|
+
error: expect.stringContaining("CLOUDFLARE_API_TOKEN is not configured"),
|
|
925
|
+
});
|
|
926
|
+
});
|
|
927
|
+
});
|