@desplega.ai/agent-swarm 1.173.0 → 2.0.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 +1 -1
- package/dist/{acp-adapter-ccm9xrep.js → acp-adapter-sj2g0m84.js} +17 -16
- package/dist/{actions-sgeyv08k.js → actions-fegk44gr.js} +39 -34
- package/dist/{amp-adapter-gpxp3kc9.js → amp-adapter-ad8ft89x.js} +8 -7
- package/dist/{app-km9qnv2f.js → app-kfvgbvzy.js} +18 -14
- package/dist/{assistant-5r8tcvvd.js → assistant-z7zdy9ja.js} +40 -35
- package/dist/{boot-reembed-swwzdgd7.js → boot-reembed-5c2z0mbe.js} +10 -9
- package/dist/{boot-reembed-16rv9843.js → boot-reembed-j8xa181e.js} +10 -9
- package/dist/{boot-scrub-sweep-zkr6vkqb.js → boot-scrub-sweep-hnkn3rgt.js} +11 -10
- package/dist/{boot-seeds-d8sp49t2.js → boot-seeds-kwjq4d2z.js} +14 -13
- package/dist/{claude-adapter-yzzzh8jp.js → claude-adapter-9vmvpvgd.js} +19 -18
- package/dist/{claude-managed-adapter-xgxhqrt2.js → claude-managed-adapter-k9qae05j.js} +7 -5
- package/dist/{claude-managed-setup-bnmjarjc.js → claude-managed-setup-q54zx5q2.js} +6 -4
- package/dist/{claude-sdk-session-4whxere4.js → claude-sdk-session-9jye6cxr.js} +11881 -11472
- package/dist/{cli-94b48jhk.js → cli-03gedhng.js} +16 -14
- package/dist/{cli-1w0vj3gw.js → cli-1e4b6bzh.js} +7 -7
- package/dist/{cli-d603qf86.js → cli-29jc5q7w.js} +10 -8
- package/dist/cli-2m1ktfzz.js +17795 -0
- package/dist/{cli-x119kdba.js → cli-3dj8k4vy.js} +1 -1
- package/dist/{cli-srqq3gc5.js → cli-3fbmmawm.js} +5 -5
- package/dist/{cli-gmdajfw5.js → cli-4jxm2qtz.js} +4987 -1708
- package/dist/{cli-tadfgd0t.js → cli-5hkm7whv.js} +33 -4
- package/dist/{cli-42qhyvwx.js → cli-5t4f29ez.js} +3 -3
- package/dist/{cli-7a0sde2x.js → cli-6cagzc96.js} +9 -0
- package/dist/cli-6cvfxs8r.js +47 -0
- package/dist/{cli-0kxf81jj.js → cli-6jyj59nb.js} +2 -2
- package/dist/{cli-w1apjecb.js → cli-6razj6t4.js} +2 -2
- package/dist/{cli-anzdk0z4.js → cli-74ahz61x.js} +2 -2
- package/dist/{cli-m89t9jdf.js → cli-7x0g4sn1.js} +15416 -16024
- package/dist/{cli-yatxqynm.js → cli-9ha6hs2d.js} +1 -290
- package/dist/{cli-81r1wjyv.js → cli-9nfx44eh.js} +1 -1
- package/dist/cli-9traj3dn.js +638 -0
- package/dist/{cli-smcrrmhy.js → cli-agzebbd5.js} +9 -9
- package/dist/{cli-na2pxe4f.js → cli-apx87jne.js} +2 -2
- package/dist/{cli-rfz2ve55.js → cli-bzszjf2p.js} +2855 -862
- package/dist/{cli-c5km5znr.js → cli-c8r3kzzb.js} +1 -1
- package/dist/{cli-hsem16hb.js → cli-cgtksawx.js} +7 -4
- package/dist/{cli-db3m6zdt.js → cli-d2qr7aj4.js} +1 -1
- package/dist/{cli-rfs4vddv.js → cli-d5f9ed5c.js} +5 -5
- package/dist/cli-e3g5mbkz.js +776 -0
- package/dist/{cli-7jykv7ef.js → cli-e6mpjhed.js} +1 -1
- package/dist/{cli-7a93cv8c.js → cli-ee9gtz70.js} +98 -23
- package/dist/{cli-3nxdmjrc.js → cli-eejsgvm4.js} +4 -4
- package/dist/{cli-fmhj1ehj.js → cli-ef1hg1wv.js} +470 -51
- package/dist/cli-f527ce74.js +781 -0
- package/dist/{cli-ppy4nnnh.js → cli-f7kr7svz.js} +2 -2
- package/dist/cli-hjwmhcg1.js +10622 -0
- package/dist/{cli-2bw3w47j.js → cli-hnhtk6cj.js} +1 -1
- package/dist/{cli-ywm1azg9.js → cli-hnmhtzvh.js} +6 -4
- package/dist/{cli-h7k0xtry.js → cli-hvqbtna4.js} +4 -1256
- package/dist/{cli-s8z6x4b4.js → cli-j0pfptdp.js} +218 -102
- package/dist/cli-jta2eyzc.js +619 -0
- package/dist/{cli-5er5gjbm.js → cli-k382tshb.js} +10 -11
- package/dist/{cli-v3zhnxsb.js → cli-k3dp5tpf.js} +2 -2
- package/dist/{cli-wd0hz6ah.js → cli-kbrt8gqa.js} +9 -91
- package/dist/cli-kceah1eh.js +92 -0
- package/dist/{cli-ne7smj1b.js → cli-ke896fcf.js} +10 -10
- package/dist/{cli-xc7p7s3g.js → cli-kt6zqg0e.js} +6 -6
- package/dist/{cli-wrh08zjd.js → cli-m50t4309.js} +5 -18
- package/dist/cli-n2wq4j7m.js +6985 -0
- package/dist/{cli-bv2etw2y.js → cli-n3hzg89b.js} +46 -15
- package/dist/{cli-tmhsxhb8.js → cli-n5vwg842.js} +23 -6
- package/dist/{cli-qm1448f9.js → cli-ne236afy.js} +8 -8
- package/dist/{cli-d161g69q.js → cli-p1x85d2t.js} +50 -2
- package/dist/{cli-7pz3b7x5.js → cli-przpmdgr.js} +25 -0
- package/dist/{cli-0a7mm3zp.js → cli-q0ke1yww.js} +25 -21
- package/dist/{cli-e3pmyt39.js → cli-q50942rs.js} +1 -1
- package/dist/{cli-r44t9wqc.js → cli-r106725k.js} +2 -2
- package/dist/{cli-phcqp1b9.js → cli-r2pnvj8g.js} +2 -2
- package/dist/{cli-jpc4r224.js → cli-r62a35ea.js} +19 -3
- package/dist/{cli-cd0hv9nh.js → cli-ra0xzggj.js} +2 -2
- package/dist/cli-rhs9pz33.js +294 -0
- package/dist/{cli-r4fqfpwf.js → cli-s2sp957d.js} +50 -27
- package/dist/{cli-1x7da0de.js → cli-s4setq91.js} +8 -8
- package/dist/{cli-6thk4jz3.js → cli-se8w6wny.js} +6 -6
- package/dist/cli-skxvvpap.js +10140 -0
- package/dist/{cli-014vns2y.js → cli-stfs0k5s.js} +208 -525
- package/dist/{cli-0p3enk35.js → cli-tqaazemc.js} +9 -1
- package/dist/{cli-nwrdx840.js → cli-tr4sxp68.js} +3 -3
- package/dist/{cli-r0byd61h.js → cli-tsde1hdr.js} +15 -15
- package/dist/{cli-b07hr3bt.js → cli-txqb9s38.js} +2 -2
- package/dist/{cli-6r2f7edk.js → cli-v1yb0rqb.js} +6 -6
- package/dist/{cli-saqyage0.js → cli-vatyhshy.js} +179 -27
- package/dist/{cli-5wpw4rab.js → cli-vdach5a9.js} +5 -5
- package/dist/{cli-7avy4v8w.js → cli-w1q7h2r2.js} +5 -3
- package/dist/{cli-rcp5aqdw.js → cli-w7m81tb7.js} +1 -1
- package/dist/{cli-c3rk1v0x.js → cli-wayw93nt.js} +3 -3
- package/dist/cli-wmgrkscj.js +23254 -0
- package/dist/{cli-9xf7fdtp.js → cli-z5jjpaxw.js} +29 -6
- package/dist/{cli-451ks6aw.js → cli-zkpks3r3.js} +1 -1
- package/dist/cli.js +29 -45
- package/dist/{codex-adapter-r3dn4fp5.js → codex-adapter-y81sssk0.js} +15 -14
- package/dist/{codex-login-841kv5fk.js → codex-login-f2rcfpp9.js} +3 -2
- package/dist/{codex-session-runner-fwt793h4.js → codex-session-runner-8vx4j6a9.js} +15 -14
- package/dist/{commands-z8fq9qna.js → commands-kcpvpcrr.js} +11 -10
- package/dist/{controller-hq5ggpcp.js → controller-ywrmt6yd.js} +5 -4
- package/dist/{controllers-connect-3mf2btyj.js → controllers-connect-kwpvj3rf.js} +42 -37
- package/dist/{cursor-adapter-q8pgv5qa.js → cursor-adapter-cehv1qns.js} +7 -6
- package/dist/{db-yyhqqrbf.js → db-k8ankj20.js} +12 -11
- package/dist/{db-qfpq4bt7.js → db-ww5d7jq3.js} +28 -9
- package/dist/{devin-adapter-xgtj680j.js → devin-adapter-x1022qb1.js} +3 -2
- package/dist/{dispatcher-x0x7qfcq.js → dispatcher-e8gq5m68.js} +38 -33
- package/dist/{docker-f503k9p0.js → docker-ff6y1190.js} +3 -2
- package/dist/{dsh-adapter-34etvb5m.js → dsh-adapter-3epjm3af.js} +7 -6
- package/dist/{e2b-66xra4az.js → e2b-8ygwhg0f.js} +4 -3
- package/dist/{e2b-xwhyj8cw.js → e2b-swhnpw3n.js} +4 -3
- package/dist/{event-bus-vjtv82gm.js → event-bus-6axnngdj.js} +3 -2
- package/dist/{external-channel-h1fjecwe.js → external-channel-h3359cwn.js} +11 -10
- package/dist/{google-generative-ai-szdxbpqg.js → google-generative-ai-g8db7x0f.js} +4 -2
- package/dist/{google-vertex-qzw3p36d.js → google-vertex-r2692q0r.js} +4 -2
- package/dist/{grok-adapter-e7mr287e.js → grok-adapter-0d4x7gkx.js} +18 -17
- package/dist/{handlers-znb9n7d4.js → handlers-an1gzv21.js} +41 -36
- package/dist/{heartbeat-bm4qkn8d.js → heartbeat-6dakeb61.js} +43 -38
- package/dist/{hook-254r4mza.js → hook-6v6cv6d5.js} +11 -23
- package/dist/{hook-xaa3q80s.js → hook-sa5xye4y.js} +12 -11
- package/dist/{http-3dgrgp8y.js → http-r7f3eky0.js} +1499 -251
- package/dist/index-52j1aja4.js +3465 -0
- package/dist/{index-fx3jqq1e.js → index-7ep4gzdk.js} +38 -33
- package/dist/index-9gw58n4k.js +193 -0
- package/dist/index-9j87mcm8.js +56305 -0
- package/dist/index-bsmcresk.js +79424 -0
- package/dist/index-mh85qhec.js +475 -0
- package/dist/{index-g2byveez.js → index-rmpw9a8q.js} +38 -33
- package/dist/{index-2ybk7khy.js → index-s2wxgzr0.js} +468 -683
- package/dist/{index-bdsqa799.js → index-v1v6n7hg.js} +43 -38
- package/dist/index-we27kd31.js +12810 -0
- package/dist/{index-jd7sejm2.js → index-zk96aq1k.js} +10 -9
- package/dist/{keepalive-6ywhpja1.js → keepalive-be9vwy5h.js} +12 -11
- package/dist/{lead-cw45n21s.js → lead-0861cwrf.js} +61 -56
- package/dist/{maintenance-ss1a1ge9.js → maintenance-mzzgc8bn.js} +10 -9
- package/dist/{node-fkajaq96.js → node-mn1zs7an.js} +2 -1
- package/dist/{oauth-refresh-sweep-3c6f8vyy.js → oauth-refresh-sweep-cpdnfwdj.js} +11 -10
- package/dist/{onboard-8hzdhc1k.js → onboard-fh8qs4r3.js} +11 -3
- package/dist/{opencode-adapter-qbchbcfz.js → opencode-adapter-nm42wwj8.js} +18 -13
- package/dist/{otel-impl-e6mnvc4v.js → otel-impl-x6c1c9g4.js} +242 -868
- package/dist/{pi-mono-adapter-3c9g9snh.js → pi-mono-adapter-9n2ya240.js} +244 -106
- package/dist/{pricing-refresh-gedkjdw0.js → pricing-refresh-qb0rv0ap.js} +12 -11
- package/dist/{rbac-roles-dt4vh1yy.js → rbac-roles-1gzdwbh1.js} +11 -10
- package/dist/{rbac-roles-eszdg6pm.js → rbac-roles-3mraey52.js} +12 -11
- package/dist/{render-v2-907cy6sa.js → render-v2-agpa45zz.js} +18 -14
- package/dist/{resume-42stcaz0.js → resume-x0ydy9s9.js} +25 -21
- package/dist/{runtime-qfry88v2.js → runtime-sa2vr16g.js} +2 -2
- package/dist/{setup-adbc1c10.js → setup-as9ga0d6.js} +2 -2
- package/dist/{virtual-modules-rtmt494q.js → virtual-modules-9gkxqrap.js} +6 -5
- package/dist/{work-objects-wpj8ck95.js → work-objects-y5g1y7ev.js} +41 -36
- package/dist/{worker-bzdra9rf.js → worker-860c3e38.js} +61 -56
- package/dist/{x-qfmygqqv.js → x-m477fr0h.js} +3 -2
- package/openapi.json +523 -344
- package/package.json +10 -11
- package/src/be/config-refs.ts +60 -0
- package/src/be/db/page-versions.ts +15 -0
- package/src/be/db/runtime.ts +35 -8
- package/src/be/db/skill-name-resolution.ts +36 -0
- package/src/be/db/tasks/read.ts +230 -0
- package/src/be/db/tasks/write.ts +7 -0
- package/src/be/db.ts +52 -7
- package/src/be/migrations/215_swarm_config_export_to_env.sql +34 -0
- package/src/be/migrations/216_task_resolved_effort.sql +5 -0
- package/src/be/migrations/217_chat_sdk_state.sql +104 -0
- package/src/be/model-catalog-store.ts +5 -1
- package/src/be/model-tier-resolution.ts +12 -1
- package/src/be/model-validation.ts +5 -2
- package/src/be/models-catalog.ts +26 -1
- package/src/be/modelsdev-cache.json +198 -63
- package/src/be/modelsdev-cache.ts +9 -1
- package/src/be/script-credential-broker.ts +39 -2
- package/src/be/scripts/run-saved.ts +1 -0
- package/src/be/scripts/typecheck.ts +10 -8
- package/src/be/seed/runner.ts +13 -1
- package/src/be/seed/state-db.ts +5 -0
- package/src/be/seed/types.ts +6 -0
- package/src/be/seed-pricing.ts +19 -2
- package/src/be/seed-scripts/catalog/ops-catalog-audit.inline.ts +1 -1
- package/src/be/seed-scripts/catalog/ops-catalog-audit.ts +1 -1
- package/src/be/seed-skills/bundled-files.generated.json +0 -18
- package/src/be/seed-skills/index.ts +104 -3
- package/src/be/swarm-config-guard.ts +52 -0
- package/src/cli.tsx +0 -16
- package/src/commands/onboard/non-interactive.ts +4 -2
- package/src/commands/provider-credentials.ts +52 -6
- package/src/commands/runner.ts +190 -54
- package/src/e2b/env.ts +5 -0
- package/src/extensions/contract-types.generated.ts +1 -1
- package/src/extensions/contract.ts +45 -0
- package/src/extensions/dispatcher.ts +7 -0
- package/src/extensions/post-bridge.ts +40 -0
- package/src/github/handlers.ts +11 -4
- package/src/heartbeat/templates.ts +8 -5
- package/src/hooks/hook.ts +0 -26
- package/src/http/all-routes.ts +0 -1
- package/src/http/config.ts +36 -17
- package/src/http/core.ts +4 -0
- package/src/http/index.ts +14 -2
- package/src/http/integrations.ts +88 -1
- package/src/http/mcp.ts +15 -5
- package/src/http/page-proxy.ts +2 -4
- package/src/http/pages-public.ts +3 -3
- package/src/http/pages.ts +3 -13
- package/src/http/poll.ts +28 -68
- package/src/http/requested-by.ts +54 -0
- package/src/http/scripts.ts +1 -0
- package/src/http/stats.ts +1 -30
- package/src/http/tasks.ts +61 -5
- package/src/http/utils.ts +14 -0
- package/src/http/webhooks.ts +159 -1
- package/src/http/x.ts +1 -0
- package/src/integrations/chat-sdk/adapters.ts +129 -0
- package/src/integrations/chat-sdk/attachments.ts +232 -0
- package/src/integrations/chat-sdk/background.ts +36 -0
- package/src/integrations/chat-sdk/commands.ts +194 -0
- package/src/integrations/chat-sdk/config.ts +198 -0
- package/src/integrations/chat-sdk/discord-api.ts +63 -0
- package/src/integrations/chat-sdk/discord-gateway.ts +185 -0
- package/src/integrations/chat-sdk/handlers.ts +28 -0
- package/src/integrations/chat-sdk/inbound.ts +426 -0
- package/src/integrations/chat-sdk/limits.ts +48 -0
- package/src/integrations/chat-sdk/logger.ts +81 -0
- package/src/integrations/chat-sdk/outbound.ts +123 -0
- package/src/integrations/chat-sdk/relay.ts +172 -0
- package/src/integrations/chat-sdk/router.ts +169 -0
- package/src/integrations/chat-sdk/runtime.ts +351 -0
- package/src/integrations/chat-sdk/schema.ts +245 -0
- package/src/integrations/chat-sdk/state.ts +243 -0
- package/src/integrations/chat-sdk/status.ts +244 -0
- package/src/integrations/chat-sdk/steering.ts +58 -0
- package/src/integrations/chat-sdk/teams-origin.ts +77 -0
- package/src/integrations/chat-sdk/telegram-api.ts +56 -0
- package/src/integrations/chat-sdk/templates.ts +16 -0
- package/src/integrations/chat-sdk/test-hooks.ts +37 -0
- package/src/integrations/chat-sdk/thread-buffer.ts +100 -0
- package/src/integrations/chat-sdk/typing.ts +65 -0
- package/src/pages/version.ts +4 -5
- package/src/prompts/base-prompt.ts +111 -45
- package/src/providers/acp-target-catalog.ts +6 -0
- package/src/providers/acp-targets.ts +55 -2
- package/src/providers/amp-adapter.ts +1 -1
- package/src/providers/claude-adapter.ts +1 -1
- package/src/providers/claude-flag-settings.ts +17 -5
- package/src/providers/claude-managed-adapter.ts +1 -1
- package/src/providers/claude-session-events.ts +6 -1
- package/src/providers/dsh-adapter.ts +1 -1
- package/src/providers/opencode-adapter.ts +243 -26
- package/src/providers/opencode-swarm-plugin.ts +31 -0
- package/src/providers/pi-mono-adapter.ts +398 -153
- package/src/providers/pricing-sources.md +33 -2
- package/src/providers/reasoning-effort.ts +27 -0
- package/src/providers/types.ts +6 -2
- package/src/rbac/legacy-policy.ts +8 -2
- package/src/rbac/permissions.ts +21 -0
- package/src/scheduler/scheduler.ts +4 -2
- package/src/scripts-runtime/loader.ts +19 -7
- package/src/scripts-runtime/sdk-allowlist.ts +8 -6
- package/src/scripts-runtime/swarm-sdk.ts +0 -11
- package/src/scripts-runtime/types/stdlib.d.ts +27 -15
- package/src/scripts-runtime/types/swarm-extension.d.ts +63 -1
- package/src/scripts-runtime/types/swarm-sdk.d.ts +27 -15
- package/src/server.ts +52 -18
- package/src/slack/event-dedup.ts +1 -1
- package/src/tasks/additive-buffer.ts +4 -2
- package/src/tasks/context-key.ts +60 -1
- package/src/tasks/model-pin.ts +67 -0
- package/src/tasks/worker-follow-up.ts +4 -31
- package/src/tests/acp-adapter.test.ts +42 -0
- package/src/tests/api-drain-worker-e2e.test.ts +1 -6
- package/src/tests/base-prompt.test.ts +58 -54
- package/src/tests/chat-sdk-commands.test.ts +191 -0
- package/src/tests/chat-sdk-config.test.ts +334 -0
- package/src/tests/chat-sdk-discord-api.test.ts +68 -0
- package/src/tests/chat-sdk-discord-gateway.test.ts +252 -0
- package/src/tests/chat-sdk-discord-webhook.test.ts +126 -0
- package/src/tests/chat-sdk-fields-sync.test.ts +50 -0
- package/src/tests/chat-sdk-harness.ts +146 -0
- package/src/tests/chat-sdk-inbound.test.ts +363 -0
- package/src/tests/chat-sdk-relay.test.ts +470 -0
- package/src/tests/chat-sdk-reload.test.ts +128 -0
- package/src/tests/chat-sdk-shutdown.test.ts +220 -0
- package/src/tests/chat-sdk-slack.test.ts +268 -0
- package/src/tests/chat-sdk-state.test.ts +163 -0
- package/src/tests/chat-sdk-status-route.test.ts +382 -0
- package/src/tests/chat-sdk-teams.test.ts +277 -0
- package/src/tests/chat-sdk-tools.test.ts +520 -0
- package/src/tests/chat-sdk-typing.test.ts +120 -0
- package/src/tests/chat-sdk-webhook-telegram.test.ts +200 -0
- package/src/tests/claude-adapter-binary.test.ts +9 -4
- package/src/tests/claude-flag-settings.test.ts +11 -3
- package/src/tests/claude-managed-adapter.test.ts +1 -1
- package/src/tests/claude-sdk-lifecycle.test.ts +9 -3
- package/src/tests/claude-thinking-tokens.test.ts +57 -0
- package/src/tests/claude-worker-parity.test.ts +2 -7
- package/src/tests/config-env-export-chat-sdk.test.ts +11 -0
- package/src/tests/config-refs.test.ts +66 -0
- package/src/tests/context-key.test.ts +19 -0
- package/src/tests/cors-allowlist.test.ts +25 -0
- package/src/tests/create-page-tool.test.ts +18 -0
- package/src/tests/credential-check.test.ts +139 -0
- package/src/tests/credentials.test.ts +21 -0
- package/src/tests/db-runtime.test.ts +61 -0
- package/src/tests/db-task-read-contract.test.ts +20 -0
- package/src/tests/db-task-write-contract.test.ts +10 -0
- package/src/tests/docs-index-footprint.test.js +3 -3
- package/src/tests/dsh-adapter.test.ts +1 -1
- package/src/tests/enabled-capabilities.test.ts +20 -0
- package/src/tests/entrypoint-api-readiness.test.ts +14 -8
- package/src/tests/entrypoint-config-env-export.test.ts +5 -2
- package/src/tests/entrypoint-export-to-env.test.ts +78 -0
- package/src/tests/explicit-model-validation.test.ts +105 -1
- package/src/tests/extensions-example-route-chat-channel.test.ts +154 -0
- package/src/tests/extensions-post-bridge.test.ts +51 -0
- package/src/tests/fetch-resolved-env.test.ts +128 -0
- package/src/tests/fixtures/chat-sdk/telegram-private-text.json +10 -0
- package/src/tests/fixtures/extensions/route-chat-channel-to-agent/hooks.ts +27 -0
- package/src/tests/fixtures/extensions/route-chat-channel-to-agent/manifest.json +9 -0
- package/src/tests/fixtures/openai-compatible-registry-extension.child.ts +146 -0
- package/src/tests/fixtures/runner-legacy-resume-prompt.ts +122 -0
- package/src/tests/github-handlers.test.ts +35 -0
- package/src/tests/harness-provider-resolution.test.ts +12 -0
- package/src/tests/heartbeat-checklist.test.ts +3 -2
- package/src/tests/hook-runtime-close.test.ts +0 -2
- package/src/tests/http-api-integration.test.ts +0 -25
- package/src/tests/migration-046-budgets.test.ts +17 -16
- package/src/tests/migration-217-chat-sdk-state.test.ts +113 -0
- package/src/tests/model-tier-resolution.test.ts +93 -0
- package/src/tests/models-catalog.test.ts +42 -1
- package/src/tests/openai-compatible-providers.test.ts +134 -0
- package/src/tests/openai-compatible-registry-extension.test.ts +42 -0
- package/src/tests/opencode-adapter.test.ts +216 -2
- package/src/tests/opencode-helmcode-sdk.test.ts +201 -0
- package/src/tests/opencode-shell-env.test.ts +145 -0
- package/src/tests/openrouter-base-url.test.ts +101 -151
- package/src/tests/page-proxy.test.ts +1 -1
- package/src/tests/pages-browser-sdk.test.ts +76 -0
- package/src/tests/pages-feedback.test.ts +1 -1
- package/src/tests/paused-tasks-requested-by.test.ts +102 -0
- package/src/tests/pi-custom-provider-fake-server.test.ts +442 -0
- package/src/tests/pi-mono-adapter.test.ts +385 -5
- package/src/tests/pi-shell-env.test.ts +170 -0
- package/src/tests/prompt-template-remaining.test.ts +35 -0
- package/src/tests/rbac-engine.test.ts +11 -3
- package/src/tests/runner-exit-after-tasks.test.ts +0 -4
- package/src/tests/runner-legacy-resume-prompt.test.ts +64 -0
- package/src/tests/script-connections.test.ts +2 -0
- package/src/tests/scripts-http.test.ts +201 -1
- package/src/tests/scripts-only-gating.test.ts +2 -3
- package/src/tests/scripts-runtime.test.ts +58 -40
- package/src/tests/secret-scrubber.test.ts +86 -0
- package/src/tests/seed-skills-bundled-files.test.ts +45 -53
- package/src/tests/seed-skills-retire.test.ts +182 -0
- package/src/tests/send-task-requester-gate.test.ts +58 -0
- package/src/tests/session-cost-recompute-unit.test.ts +46 -0
- package/src/tests/skill-get-name-resolution.test.ts +137 -0
- package/src/tests/sonnet-5-5-registry.test.ts +9 -1
- package/src/tests/swarm-config-export-to-env.test.ts +267 -0
- package/src/tests/swarm-config-reserved-keys.test.ts +16 -0
- package/src/tests/swarm-diff.test.ts +1 -1
- package/src/tests/task-model-effort-rerun.test.ts +164 -0
- package/src/tests/task-session-prompts.test.ts +132 -0
- package/src/tests/task-tool-manifest.test.ts +14 -0
- package/src/tests/task-tools-ctx.test.ts +2 -0
- package/src/tests/tool-annotations.test.ts +6 -6
- package/src/tests/ui-logs-parser.test.ts +43 -0
- package/src/tests/used-by-route.test.ts +6 -0
- package/src/tests/vercel-ai-gateway.test.ts +149 -0
- package/src/tests/workflow-swarm-script.test.ts +38 -0
- package/src/tests/workflow-wait-filter.test.ts +5 -3
- package/src/tools/chat-access.ts +41 -0
- package/src/tools/chat-open-dm.ts +59 -0
- package/src/tools/chat-post.ts +74 -0
- package/src/tools/chat-read-thread.ts +100 -0
- package/src/tools/chat-reply.ts +75 -0
- package/src/tools/create-page.ts +3 -13
- package/src/tools/model-catalog/index.ts +2 -1
- package/src/tools/register-chat-channel.ts +139 -0
- package/src/tools/send-task.ts +24 -0
- package/src/tools/skills/skill-get.ts +17 -9
- package/src/tools/swarm-config/get-config.ts +2 -1
- package/src/tools/swarm-config/list-config.ts +2 -1
- package/src/tools/swarm-config/set-config.ts +16 -3
- package/src/tools/tool-config.ts +8 -6
- package/src/types.ts +12 -2
- package/src/utils/config-env-export.ts +74 -0
- package/src/utils/credentials.test.ts +92 -0
- package/src/utils/credentials.ts +52 -4
- package/src/utils/harness-provider.ts +15 -6
- package/src/utils/runtime-model-catalog.ts +11 -1
- package/src/utils/secret-scrubber.ts +71 -2
- package/src/utils/task-tool-manifest.ts +19 -2
- package/src/workflows/executors/swarm-script.ts +1 -0
- package/templates/skills/agent-browser/SKILL.md +1 -1
- package/templates/skills/agent-browser/content.md +1 -1
- package/templates/skills/apps/SKILL.md +8 -1
- package/templates/skills/apps/content.md +8 -1
- package/templates/skills/chat-sdk/config.json +14 -0
- package/templates/skills/chat-sdk/content.md +84 -0
- package/templates/skills/composio-gmail/content.md +7 -0
- package/templates/skills/composio-google-docs/content.md +1 -1
- package/templates/skills/email-domain-health/config.json +14 -0
- package/templates/skills/email-domain-health/content.md +131 -0
- package/templates/skills/kv-storage/content.md +2 -2
- package/templates/skills/pages/content.md +8 -10
- package/templates/skills/swarm-extensions/content.md +2 -0
- package/templates/skills/swarm-scripts/SKILL.md +4 -2
- package/templates/skills/swarm-scripts/config.json +1 -1
- package/templates/skills/swarm-scripts/content.md +4 -2
- package/templates/skills/workflow-iterate/content.md +38 -0
- package/dist/artifact-eczsknc3.js +0 -3843
- package/dist/cli-bd957dgb.js +0 -153
- package/dist/cli-q51yprfq.js +0 -1104
- package/dist/cli-t1ze64h3.js +0 -811
- package/dist/cli-tcc4wd5d.js +0 -56471
- package/dist/index-9ch1n761.js +0 -1102
- package/src/artifact-sdk/index.ts +0 -2
- package/src/artifact-sdk/localtunnel.d.ts +0 -20
- package/src/artifact-sdk/port.ts +0 -12
- package/src/artifact-sdk/server.ts +0 -183
- package/src/artifact-sdk/tunnel.ts +0 -19
- package/src/commands/artifact.ts +0 -249
- package/src/http/ecosystem.ts +0 -77
- package/src/tests/artifact-commands.test.ts +0 -92
- package/src/tests/artifact-sdk.test.ts +0 -793
- package/src/tests/register-service-security.test.ts +0 -194
- package/src/tools/list-services.ts +0 -107
- package/src/tools/register-service.ts +0 -177
- package/src/tools/unregister-service.ts +0 -73
- package/src/tools/update-service-status.ts +0 -96
- package/templates/skills/artifacts/config.json +0 -14
- package/templates/skills/artifacts/content.md +0 -344
- package/templates/skills/artifacts/files/examples/approval-flow.ts +0 -34
- package/templates/skills/artifacts/files/examples/hono-dashboard.ts +0 -31
- package/templates/skills/artifacts/files/examples/multi-artifact.ts +0 -20
- package/templates/skills/artifacts/files/examples/static-report.sh +0 -17
- /package/src/{artifact-sdk → pages}/browser-sdk.ts +0 -0
- /package/src/{artifact-sdk → pages}/feedback-overlay.ts +0 -0
|
@@ -1154,6 +1154,49 @@ describe("ui logs parser", () => {
|
|
|
1154
1154
|
]);
|
|
1155
1155
|
});
|
|
1156
1156
|
|
|
1157
|
+
test("keeps a Claude sub-agent's parent_tool_use_id on its messages", () => {
|
|
1158
|
+
const messages = parseSessionLogs([
|
|
1159
|
+
log("spawn", "claude", 1, {
|
|
1160
|
+
type: "assistant",
|
|
1161
|
+
message: {
|
|
1162
|
+
role: "assistant",
|
|
1163
|
+
content: [{ type: "tool_use", id: "toolu_agent_1", name: "Agent", input: {} }],
|
|
1164
|
+
},
|
|
1165
|
+
}),
|
|
1166
|
+
log("child-call", "claude", 2, {
|
|
1167
|
+
type: "assistant",
|
|
1168
|
+
parent_tool_use_id: "toolu_agent_1",
|
|
1169
|
+
message: {
|
|
1170
|
+
role: "assistant",
|
|
1171
|
+
content: [
|
|
1172
|
+
{ type: "thinking", thinking: "Look at the fixtures." },
|
|
1173
|
+
{ type: "tool_use", id: "toolu_read_1", name: "Read", input: { file_path: "a.ts" } },
|
|
1174
|
+
],
|
|
1175
|
+
},
|
|
1176
|
+
}),
|
|
1177
|
+
log("child-result", "claude", 3, {
|
|
1178
|
+
type: "user",
|
|
1179
|
+
parent_tool_use_id: "toolu_agent_1",
|
|
1180
|
+
message: {
|
|
1181
|
+
role: "user",
|
|
1182
|
+
content: [{ type: "tool_result", tool_use_id: "toolu_read_1", content: "ok" }],
|
|
1183
|
+
},
|
|
1184
|
+
}),
|
|
1185
|
+
log("main-text", "claude", 4, {
|
|
1186
|
+
type: "assistant",
|
|
1187
|
+
parent_tool_use_id: null,
|
|
1188
|
+
message: { role: "assistant", content: [{ type: "text", text: "Done." }] },
|
|
1189
|
+
}),
|
|
1190
|
+
]);
|
|
1191
|
+
|
|
1192
|
+
expect(messages.map((m) => [m.id, m.parentToolUseId])).toEqual([
|
|
1193
|
+
["spawn", undefined],
|
|
1194
|
+
["child-call", "toolu_agent_1"],
|
|
1195
|
+
["child-result", "toolu_agent_1"],
|
|
1196
|
+
["main-text", undefined],
|
|
1197
|
+
]);
|
|
1198
|
+
});
|
|
1199
|
+
|
|
1157
1200
|
test("extracts a completed Claude foreground Agent run from its paired result", () => {
|
|
1158
1201
|
const runs = extractSubagentRuns([
|
|
1159
1202
|
log("spawn", "claude", 1, {
|
|
@@ -61,10 +61,14 @@ const configValue = async (key: string) =>
|
|
|
61
61
|
|
|
62
62
|
describe("POST /api/used-by", () => {
|
|
63
63
|
const originalFetch = globalThis.fetch;
|
|
64
|
+
const savedEnvWebsite = process.env.SWARM_ORG_WEBSITE;
|
|
65
|
+
const savedEnvName = process.env.SWARM_ORG_NAME;
|
|
64
66
|
let proxied: Array<{ url: string; body: Record<string, unknown> }>;
|
|
65
67
|
let proxyStatus: number;
|
|
66
68
|
|
|
67
69
|
beforeAll(async () => {
|
|
70
|
+
delete process.env.SWARM_ORG_WEBSITE;
|
|
71
|
+
delete process.env.SWARM_ORG_NAME;
|
|
68
72
|
await removeDbFiles();
|
|
69
73
|
initDb(TEST_DB_PATH);
|
|
70
74
|
await createAgent({ id: WORKER_ID, name: "Worker", isLead: false, status: "idle" });
|
|
@@ -75,6 +79,8 @@ describe("POST /api/used-by", () => {
|
|
|
75
79
|
});
|
|
76
80
|
|
|
77
81
|
afterAll(async () => {
|
|
82
|
+
if (savedEnvWebsite !== undefined) process.env.SWARM_ORG_WEBSITE = savedEnvWebsite;
|
|
83
|
+
if (savedEnvName !== undefined) process.env.SWARM_ORG_NAME = savedEnvName;
|
|
78
84
|
globalThis.fetch = originalFetch;
|
|
79
85
|
closeDb();
|
|
80
86
|
await removeDbFiles();
|
|
@@ -0,0 +1,149 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Vercel AI Gateway (`vercel/<vendor>/<model>`) on pi and opencode: the full
|
|
3
|
+
* chat catalog, and the one endpoint no env var or hand-written block can move.
|
|
4
|
+
*/
|
|
5
|
+
import { afterEach, describe, expect, test } from "bun:test";
|
|
6
|
+
import { mkdtemp, rm } from "node:fs/promises";
|
|
7
|
+
import { tmpdir } from "node:os";
|
|
8
|
+
import { join } from "node:path";
|
|
9
|
+
import {
|
|
10
|
+
isAgentChatModel,
|
|
11
|
+
OPENAI_COMPATIBLE_PROVIDERS,
|
|
12
|
+
openAICompatibleBaseUrlVars,
|
|
13
|
+
providerBaseUrl,
|
|
14
|
+
providerForModel,
|
|
15
|
+
withRegistryCatalogSections,
|
|
16
|
+
} from "@desplega/model-catalog";
|
|
17
|
+
import { buildCatalogEntries } from "../be/model-catalog-store";
|
|
18
|
+
import { isKnownCatalogModel } from "../be/model-validation";
|
|
19
|
+
import { buildModelsCatalog } from "../be/models-catalog";
|
|
20
|
+
import { loadModelsDevCache, type ModelsDevCache } from "../be/modelsdev-cache";
|
|
21
|
+
import snapshot from "../be/modelsdev-cache.json";
|
|
22
|
+
import { applyProviderKeySplice, type OpencodeConfig } from "../providers/opencode-adapter";
|
|
23
|
+
import { customProviderModelsBlock, writeSessionModelsJson } from "../providers/pi-mono-adapter";
|
|
24
|
+
|
|
25
|
+
const VERCEL = OPENAI_COMPATIBLE_PROVIDERS.vercel;
|
|
26
|
+
const ENDPOINT = "https://ai-gateway.vercel.sh/v1";
|
|
27
|
+
const WRONG = "https://ai-gateway.vercel.sh";
|
|
28
|
+
const raw = snapshot as unknown as ModelsDevCache;
|
|
29
|
+
const rawVercel = (raw.vercel?.models ?? {}) as Record<
|
|
30
|
+
string,
|
|
31
|
+
Parameters<typeof isAgentChatModel>[0]
|
|
32
|
+
>;
|
|
33
|
+
const chatIds = Object.keys(rawVercel).filter((id) => isAgentChatModel(rawVercel[id] ?? {}));
|
|
34
|
+
|
|
35
|
+
describe("Vercel AI Gateway catalog", () => {
|
|
36
|
+
test("the snapshot carries non-chat rows, so the filter is exercised", () => {
|
|
37
|
+
expect(rawVercel["voyage/voyage-code-3"]).toBeDefined();
|
|
38
|
+
expect(chatIds.length).toBeGreaterThan(200);
|
|
39
|
+
expect(chatIds.length).toBeLessThan(Object.keys(rawVercel).length);
|
|
40
|
+
});
|
|
41
|
+
|
|
42
|
+
test("pi lists every AI Gateway chat model, and no embedding or video model", () => {
|
|
43
|
+
const block = customProviderModelsBlock(VERCEL);
|
|
44
|
+
const ids = block.models.map((m) => m.id as string);
|
|
45
|
+
expect(ids.sort()).toEqual([...chatIds].sort());
|
|
46
|
+
expect(ids).toContain("zai/glm-5.3");
|
|
47
|
+
expect(ids).toContain("moonshotai/kimi-k3");
|
|
48
|
+
expect(ids).toContain("anthropic/claude-sonnet-4.6");
|
|
49
|
+
expect(ids).not.toContain("voyage/voyage-code-3");
|
|
50
|
+
expect(ids).not.toContain("klingai/kling-v3.0-t2v");
|
|
51
|
+
const glm = block.models.find((m) => m.id === "zai/glm-5.3");
|
|
52
|
+
expect(glm).toMatchObject({ contextWindow: 1000000, maxTokens: 1000000, reasoning: true });
|
|
53
|
+
expect(block).toMatchObject({
|
|
54
|
+
baseUrl: ENDPOINT,
|
|
55
|
+
api: "openai-completions",
|
|
56
|
+
apiKey: "$AI_GATEWAY_API_KEY",
|
|
57
|
+
});
|
|
58
|
+
});
|
|
59
|
+
|
|
60
|
+
test("picker, live and validation catalogs drop non-chat Vercel rows", () => {
|
|
61
|
+
const merged = withRegistryCatalogSections(raw);
|
|
62
|
+
expect(Object.keys(merged.vercel?.models ?? {}).sort()).toEqual([...chatIds].sort());
|
|
63
|
+
// OpenRouter is not a registry provider and keeps its full section.
|
|
64
|
+
expect(Object.keys(merged.openrouter?.models ?? {}).length).toBe(
|
|
65
|
+
Object.keys(raw.openrouter?.models ?? {}).length,
|
|
66
|
+
);
|
|
67
|
+
|
|
68
|
+
const live = buildCatalogEntries(raw, 0).filter((e) => e.provider === "vercel");
|
|
69
|
+
expect(live.map((e) => e.modelId).sort()).toEqual([...chatIds].sort());
|
|
70
|
+
|
|
71
|
+
const cache = loadModelsDevCache();
|
|
72
|
+
if (!cache) throw new Error("snapshot missing");
|
|
73
|
+
const catalog = buildModelsCatalog(cache) as Parameters<typeof isKnownCatalogModel>[1];
|
|
74
|
+
expect(isKnownCatalogModel("vercel/zai/glm-5.3", catalog)).toBe(true);
|
|
75
|
+
expect(isKnownCatalogModel("vercel/anthropic/claude-sonnet-4.6", catalog)).toBe(true);
|
|
76
|
+
expect(isKnownCatalogModel("vercel/voyage/voyage-code-3", catalog)).toBe(false);
|
|
77
|
+
expect(providerForModel("vercel/moonshotai/kimi-k3")?.id).toBe("vercel");
|
|
78
|
+
});
|
|
79
|
+
});
|
|
80
|
+
|
|
81
|
+
describe("Vercel AI Gateway endpoint is fixed", () => {
|
|
82
|
+
const dirs: string[] = [];
|
|
83
|
+
afterEach(async () => {
|
|
84
|
+
await Promise.all(dirs.splice(0).map((d) => rm(d, { recursive: true, force: true })));
|
|
85
|
+
});
|
|
86
|
+
|
|
87
|
+
test("VERCEL_BASE_URL is never read or forwarded", () => {
|
|
88
|
+
expect(VERCEL.baseUrl).toBe(ENDPOINT);
|
|
89
|
+
expect(providerBaseUrl(VERCEL, { VERCEL_BASE_URL: WRONG })).toBe(ENDPOINT);
|
|
90
|
+
expect(openAICompatibleBaseUrlVars()).not.toContain("VERCEL_BASE_URL");
|
|
91
|
+
expect(openAICompatibleBaseUrlVars()).toContain("NAN_BASE_URL");
|
|
92
|
+
});
|
|
93
|
+
|
|
94
|
+
test("opencode gets the key and no baseURL, even over a configured one", () => {
|
|
95
|
+
const key = crypto.randomUUID();
|
|
96
|
+
const config: OpencodeConfig = {
|
|
97
|
+
model: "vercel/zai/glm-5.3",
|
|
98
|
+
provider: { vercel: { options: { baseURL: WRONG } } },
|
|
99
|
+
};
|
|
100
|
+
const picked = applyProviderKeySplice(config, config.model, {
|
|
101
|
+
AI_GATEWAY_API_KEY: key,
|
|
102
|
+
VERCEL_BASE_URL: WRONG,
|
|
103
|
+
});
|
|
104
|
+
expect(picked?.key).toBe(key);
|
|
105
|
+
expect(config.provider?.vercel).toEqual({ env: [], options: { apiKey: key } });
|
|
106
|
+
});
|
|
107
|
+
|
|
108
|
+
test("pi gets the Chat Completions endpoint over VERCEL_BASE_URL and a hand-written block", async () => {
|
|
109
|
+
const agentDir = await mkdtemp(join(tmpdir(), "pi-vercel-"));
|
|
110
|
+
dirs.push(agentDir);
|
|
111
|
+
await Bun.write(
|
|
112
|
+
join(agentDir, "models.json"),
|
|
113
|
+
JSON.stringify({
|
|
114
|
+
providers: { vercel: { baseUrl: WRONG, api: "anthropic-messages", headers: { a: "b" } } },
|
|
115
|
+
}),
|
|
116
|
+
);
|
|
117
|
+
const env = { AI_GATEWAY_API_KEY: "k", VERCEL_BASE_URL: WRONG };
|
|
118
|
+
const session = await writeSessionModelsJson(agentDir, env, "vercel/vendor/brand-new-model");
|
|
119
|
+
try {
|
|
120
|
+
expect(session.modelsPath).toBeDefined();
|
|
121
|
+
const written = await Bun.file(session.modelsPath as string).json();
|
|
122
|
+
const block = written.providers.vercel;
|
|
123
|
+
expect(block.baseUrl).toBe(ENDPOINT);
|
|
124
|
+
expect(block.api).toBe("openai-completions");
|
|
125
|
+
expect(block.headers).toEqual({ a: "b" });
|
|
126
|
+
const ids = (block.models as { id: string }[]).map((m) => m.id);
|
|
127
|
+
expect(ids).toContain("zai/glm-5.3");
|
|
128
|
+
// An id the catalog does not know yet still resolves (bare entry).
|
|
129
|
+
expect(ids).toContain("vendor/brand-new-model");
|
|
130
|
+
} finally {
|
|
131
|
+
await session.dispose();
|
|
132
|
+
}
|
|
133
|
+
});
|
|
134
|
+
|
|
135
|
+
test("pi without a shared block also lands on the endpoint", async () => {
|
|
136
|
+
const agentDir = await mkdtemp(join(tmpdir(), "pi-vercel-"));
|
|
137
|
+
dirs.push(agentDir);
|
|
138
|
+
const session = await writeSessionModelsJson(agentDir, {
|
|
139
|
+
AI_GATEWAY_API_KEY: "k",
|
|
140
|
+
VERCEL_BASE_URL: WRONG,
|
|
141
|
+
});
|
|
142
|
+
try {
|
|
143
|
+
const written = await Bun.file(session.modelsPath as string).json();
|
|
144
|
+
expect(written.providers.vercel.baseUrl).toBe(ENDPOINT);
|
|
145
|
+
} finally {
|
|
146
|
+
await session.dispose();
|
|
147
|
+
}
|
|
148
|
+
});
|
|
149
|
+
});
|
|
@@ -9,6 +9,7 @@ import {
|
|
|
9
9
|
getWorkflowRun,
|
|
10
10
|
getWorkflowRunStepsByRunId,
|
|
11
11
|
initDb,
|
|
12
|
+
upsertSwarmConfig,
|
|
12
13
|
} from "../be/db";
|
|
13
14
|
import { upsertScriptConnection } from "../be/script-connections";
|
|
14
15
|
import { getScript, upsertScriptByName } from "../be/scripts/db";
|
|
@@ -31,6 +32,7 @@ import {
|
|
|
31
32
|
} from "../workflows/executors/swarm-script";
|
|
32
33
|
import { interpolate } from "../workflows/template";
|
|
33
34
|
import { SKIP_SANDBOX_SPAWN_TESTS } from "./sandbox-spawn-test-helpers";
|
|
35
|
+
import { CHILD_PROCESS_TEST_BUDGET_MS } from "./test-proc";
|
|
34
36
|
|
|
35
37
|
const skip = test.skipIf(SKIP_SANDBOX_SPAWN_TESTS);
|
|
36
38
|
|
|
@@ -370,6 +372,42 @@ describe("SwarmScriptExecutor", () => {
|
|
|
370
372
|
expect(result.output?.result).toEqual({ apiKeys: ["workflowVendor"] });
|
|
371
373
|
});
|
|
372
374
|
|
|
375
|
+
skip(
|
|
376
|
+
"swarm-script node reads swarm config through ctx.swarm.config.get",
|
|
377
|
+
async () => {
|
|
378
|
+
await upsertSwarmConfig({ scope: "global", key: "WORKFLOW_CFG_PLAIN", value: "node-value" });
|
|
379
|
+
await saveScript(
|
|
380
|
+
"ctx-config-get",
|
|
381
|
+
`export default async (_args, ctx) => {
|
|
382
|
+
const value = ctx.swarm.config.get("WORKFLOW_CFG_PLAIN");
|
|
383
|
+
return {
|
|
384
|
+
value: value === undefined ? null : ctx.stdlib.Redacted.value(value),
|
|
385
|
+
missing: ctx.swarm.config.get("WORKFLOW_CFG_NOT_SET") ?? "fallback",
|
|
386
|
+
};
|
|
387
|
+
};`,
|
|
388
|
+
);
|
|
389
|
+
|
|
390
|
+
const executor = new SwarmScriptExecutor(deps);
|
|
391
|
+
const wf = await makeWorkflow({ nodes: [] });
|
|
392
|
+
const result = await executor.run({
|
|
393
|
+
config: { scriptName: "ctx-config-get" },
|
|
394
|
+
context: {},
|
|
395
|
+
meta: {
|
|
396
|
+
runId: crypto.randomUUID(),
|
|
397
|
+
stepId: crypto.randomUUID(),
|
|
398
|
+
nodeId: "script",
|
|
399
|
+
workflowId: wf.id,
|
|
400
|
+
dryRun: false,
|
|
401
|
+
},
|
|
402
|
+
});
|
|
403
|
+
await getDbClient().run("DELETE FROM swarm_config WHERE key = 'WORKFLOW_CFG_PLAIN'");
|
|
404
|
+
|
|
405
|
+
expect(result.status).toBe("success");
|
|
406
|
+
expect(result.output?.result).toEqual({ value: "node-value", missing: "fallback" });
|
|
407
|
+
},
|
|
408
|
+
CHILD_PROCESS_TEST_BUDGET_MS,
|
|
409
|
+
);
|
|
410
|
+
|
|
373
411
|
skip("pinHash correctly resolves to a historic script_versions row", async () => {
|
|
374
412
|
const first = await saveScript("versioned", `export default async () => ({ version: "old" });`);
|
|
375
413
|
await saveScript("versioned", `export default async () => ({ version: "new" });`);
|
|
@@ -154,12 +154,14 @@ describe("matchesFilter — sandbox penetration (must all return false, never th
|
|
|
154
154
|
});
|
|
155
155
|
|
|
156
156
|
test("DoS: pathological regex terminates within timeout", async () => {
|
|
157
|
-
|
|
157
|
+
// Still exponential backtracking, but 24 characters finish in ~0.1 s. The
|
|
158
|
+
// 50 ms timer cannot pre-empt a synchronous regex, so the elapsed time is
|
|
159
|
+
// the engine's own: 30 characters took 2058 ms on a loaded CI runner and
|
|
160
|
+
// failed the bound below without the matcher doing anything wrong.
|
|
161
|
+
const filter = "(p) => /^(a+)+$/.test('a'.repeat(24) + 'X')";
|
|
158
162
|
const t0 = Date.now();
|
|
159
163
|
const result = await matchesFilter({}, filter);
|
|
160
164
|
const elapsed = Date.now() - t0;
|
|
161
|
-
// The regex would eventually return false on its own, but if it stalls
|
|
162
|
-
// the timeout catches it.
|
|
163
165
|
expect(typeof result).toBe("boolean");
|
|
164
166
|
expect(elapsed).toBeLessThan(2000);
|
|
165
167
|
});
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
import { getAgentById, getAgentCurrentTask, getTaskById } from "@/be/db";
|
|
2
|
+
import { can } from "@/rbac";
|
|
3
|
+
import { chatThreadOfContextKey } from "@/tasks/context-key";
|
|
4
|
+
import type { RequestInfo } from "@/tools/utils";
|
|
5
|
+
|
|
6
|
+
/** Chat SDK verbs; the legacy policy grants them to the lead only. */
|
|
7
|
+
export type ChatVerb = "integration.chat.post" | "integration.chat.read" | "integration.chat.dm";
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* The Chat SDK thread of the task the caller works on: the task named by the
|
|
11
|
+
* X-Source-Task-ID header, else the agent's current task. Null when the caller
|
|
12
|
+
* neither owns nor created that task, or when the task did not come from a
|
|
13
|
+
* chat thread.
|
|
14
|
+
*/
|
|
15
|
+
export async function callerChatThread(
|
|
16
|
+
requestInfo: RequestInfo,
|
|
17
|
+
): Promise<{ adapter: string; threadId: string } | null> {
|
|
18
|
+
if (!requestInfo.agentId) return null;
|
|
19
|
+
const task = requestInfo.sourceTaskId
|
|
20
|
+
? await getTaskById(requestInfo.sourceTaskId)
|
|
21
|
+
: await getAgentCurrentTask(requestInfo.agentId);
|
|
22
|
+
if (!task) return null;
|
|
23
|
+
// Creator is safe: a non-lead agent can only create a chat-bound task under a parent it owns or created.
|
|
24
|
+
if (task.agentId !== requestInfo.agentId && task.creatorAgentId !== requestInfo.agentId) {
|
|
25
|
+
return null;
|
|
26
|
+
}
|
|
27
|
+
return chatThreadOfContextKey(task.contextKey);
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
/** RBAC decision for a chat verb, with the caller's lead flag read from the database. */
|
|
31
|
+
export async function canUseChatVerb(requestInfo: RequestInfo, verb: ChatVerb): Promise<boolean> {
|
|
32
|
+
if (!requestInfo.agentId) return false;
|
|
33
|
+
const agent = await getAgentById(requestInfo.agentId);
|
|
34
|
+
if (!agent) return false;
|
|
35
|
+
return can({
|
|
36
|
+
principal: { kind: "agent", agentId: agent.id, isLead: agent.isLead },
|
|
37
|
+
verb,
|
|
38
|
+
resource: { kind: "none" },
|
|
39
|
+
source: "mcp",
|
|
40
|
+
}).allow;
|
|
41
|
+
}
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
import type { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
|
|
2
|
+
import * as z from "zod";
|
|
3
|
+
import { CHAT_SDK_ADAPTER_NAMES } from "@/integrations/chat-sdk/config";
|
|
4
|
+
import { errorMessage } from "@/integrations/chat-sdk/logger";
|
|
5
|
+
import { getChatInstance } from "@/integrations/chat-sdk/runtime";
|
|
6
|
+
import { assertTrustedTeamsServiceUrl } from "@/integrations/chat-sdk/teams-origin";
|
|
7
|
+
import { canUseChatVerb } from "@/tools/chat-access";
|
|
8
|
+
import { createToolRegistrar, swarmToolOutputSchema, toolErr, toolOk } from "@/tools/utils";
|
|
9
|
+
|
|
10
|
+
export const registerChatOpenDmTool = (server: McpServer) => {
|
|
11
|
+
createToolRegistrar(server)(
|
|
12
|
+
"chat-open-dm",
|
|
13
|
+
{
|
|
14
|
+
title: "Open a direct message on a chat platform",
|
|
15
|
+
description:
|
|
16
|
+
"Lead only. Open (or find) the direct-message thread with a user on a Chat SDK adapter and return its threadId. Post to it with chat-post. The userId is the platform user id from a chat message (Telegram numeric id, Slack U..., Discord snowflake, Teams 29:...). Telegram bots can message only users who started a chat with the bot.",
|
|
17
|
+
annotations: { openWorldHint: true },
|
|
18
|
+
inputSchema: z.object({
|
|
19
|
+
adapter: z.enum(CHAT_SDK_ADAPTER_NAMES).describe("Chat SDK adapter name."),
|
|
20
|
+
userId: z.string().min(1).describe("Platform user id."),
|
|
21
|
+
}),
|
|
22
|
+
outputSchema: swarmToolOutputSchema({
|
|
23
|
+
yourAgentId: z.string().optional(),
|
|
24
|
+
adapter: z.string().optional(),
|
|
25
|
+
threadId: z.string().optional(),
|
|
26
|
+
}),
|
|
27
|
+
},
|
|
28
|
+
async ({ adapter, userId }, requestInfo) => {
|
|
29
|
+
const data = { yourAgentId: requestInfo.agentId };
|
|
30
|
+
if (!(await canUseChatVerb(requestInfo, "integration.chat.dm"))) {
|
|
31
|
+
return toolErr("Permission denied. Only the lead can open a direct message.", { data });
|
|
32
|
+
}
|
|
33
|
+
const chat = getChatInstance(adapter);
|
|
34
|
+
if (!chat) return toolErr(`Chat SDK adapter '${adapter}' is not running.`, { data });
|
|
35
|
+
// Call the adapter directly: Chat.openDM guesses the adapter from the
|
|
36
|
+
// user id format, and numeric ids are ambiguous.
|
|
37
|
+
const platform = chat.getAdapter(adapter);
|
|
38
|
+
if (!platform?.openDM) {
|
|
39
|
+
return toolErr(`The ${adapter} adapter does not support direct messages.`, { data });
|
|
40
|
+
}
|
|
41
|
+
try {
|
|
42
|
+
if (adapter === "teams") {
|
|
43
|
+
// Teams opens the DM at the service URL cached from the user's last
|
|
44
|
+
// activity (adapter state key). The webhook route refuses untrusted
|
|
45
|
+
// ones; check again before the bot token goes out.
|
|
46
|
+
const cached = await chat.getState().get<string>(`teams:serviceUrl:${userId}`);
|
|
47
|
+
if (cached) assertTrustedTeamsServiceUrl(cached);
|
|
48
|
+
}
|
|
49
|
+
const threadId = await platform.openDM(userId);
|
|
50
|
+
return toolOk(`Direct-message thread with ${userId} on ${adapter}: ${threadId}.`, {
|
|
51
|
+
data: { ...data, adapter, threadId },
|
|
52
|
+
});
|
|
53
|
+
} catch (error) {
|
|
54
|
+
const message = errorMessage(error);
|
|
55
|
+
return toolErr(`Could not open a direct message: ${message}`, { data });
|
|
56
|
+
}
|
|
57
|
+
},
|
|
58
|
+
);
|
|
59
|
+
};
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
import type { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
|
|
2
|
+
import * as z from "zod";
|
|
3
|
+
import { CHAT_SDK_ADAPTER_NAMES } from "@/integrations/chat-sdk/config";
|
|
4
|
+
import { errorMessage } from "@/integrations/chat-sdk/logger";
|
|
5
|
+
import { adapterOfChatId, postChatMessage } from "@/integrations/chat-sdk/outbound";
|
|
6
|
+
import { canUseChatVerb } from "@/tools/chat-access";
|
|
7
|
+
import { createToolRegistrar, swarmToolOutputSchema, toolErr, toolOk } from "@/tools/utils";
|
|
8
|
+
|
|
9
|
+
export const registerChatPostTool = (server: McpServer) => {
|
|
10
|
+
createToolRegistrar(server)(
|
|
11
|
+
"chat-post",
|
|
12
|
+
{
|
|
13
|
+
title: "Post to a chat thread or channel",
|
|
14
|
+
description:
|
|
15
|
+
"Lead only. Post a message to any Chat SDK thread or channel (Telegram, Slack, Discord, Teams), outside the thread of your current task. Give threadId to reply in a thread, or channelId to start a new message in a channel. Ids carry the adapter prefix, such as telegram:12345. To answer the user of your current chat task, use chat-reply instead.",
|
|
16
|
+
annotations: { openWorldHint: true },
|
|
17
|
+
inputSchema: z.object({
|
|
18
|
+
adapter: z.enum(CHAT_SDK_ADAPTER_NAMES).describe("Chat SDK adapter name."),
|
|
19
|
+
threadId: z
|
|
20
|
+
.string()
|
|
21
|
+
.min(1)
|
|
22
|
+
.optional()
|
|
23
|
+
.describe("Full SDK thread id, such as telegram:12345 or slack:C123:1700000000.000100."),
|
|
24
|
+
channelId: z
|
|
25
|
+
.string()
|
|
26
|
+
.min(1)
|
|
27
|
+
.optional()
|
|
28
|
+
.describe("Full SDK channel id, such as slack:C123. Ignored when threadId is set."),
|
|
29
|
+
text: z.string().min(1).max(20_000).describe("Markdown text to post."),
|
|
30
|
+
}),
|
|
31
|
+
outputSchema: swarmToolOutputSchema({
|
|
32
|
+
yourAgentId: z.string().optional(),
|
|
33
|
+
messageId: z.string().optional(),
|
|
34
|
+
adapter: z.string().optional(),
|
|
35
|
+
threadId: z.string().optional(),
|
|
36
|
+
channelId: z.string().optional(),
|
|
37
|
+
}),
|
|
38
|
+
},
|
|
39
|
+
async ({ adapter, threadId, channelId, text }, requestInfo) => {
|
|
40
|
+
const data = { yourAgentId: requestInfo.agentId };
|
|
41
|
+
const target = threadId ?? channelId;
|
|
42
|
+
if (!target) return toolErr("Give a threadId or a channelId.", { data });
|
|
43
|
+
if (adapterOfChatId(target) !== adapter) {
|
|
44
|
+
return toolErr(`The id ${target} does not belong to the ${adapter} adapter.`, { data });
|
|
45
|
+
}
|
|
46
|
+
if (!(await canUseChatVerb(requestInfo, "integration.chat.post"))) {
|
|
47
|
+
return toolErr(
|
|
48
|
+
"Permission denied. Only the lead can post outside the thread of its task. Use chat-reply for the thread of your current task.",
|
|
49
|
+
{ data },
|
|
50
|
+
);
|
|
51
|
+
}
|
|
52
|
+
try {
|
|
53
|
+
const sent = await postChatMessage({
|
|
54
|
+
adapter,
|
|
55
|
+
...(threadId ? { threadId } : { channelId }),
|
|
56
|
+
text,
|
|
57
|
+
taskId: requestInfo.sourceTaskId,
|
|
58
|
+
});
|
|
59
|
+
return toolOk(`Posted to ${adapter} ${threadId ? "thread" : "channel"} ${target}.`, {
|
|
60
|
+
data: {
|
|
61
|
+
...data,
|
|
62
|
+
messageId: sent.messageIds.at(-1),
|
|
63
|
+
adapter: sent.adapter,
|
|
64
|
+
threadId: sent.threadId,
|
|
65
|
+
channelId: sent.channelId,
|
|
66
|
+
},
|
|
67
|
+
});
|
|
68
|
+
} catch (error) {
|
|
69
|
+
const message = errorMessage(error);
|
|
70
|
+
return toolErr(`Could not post: ${message}`, { data });
|
|
71
|
+
}
|
|
72
|
+
},
|
|
73
|
+
);
|
|
74
|
+
};
|
|
@@ -0,0 +1,100 @@
|
|
|
1
|
+
import type { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
|
|
2
|
+
import * as z from "zod";
|
|
3
|
+
import { errorMessage } from "@/integrations/chat-sdk/logger";
|
|
4
|
+
import { adapterOfChatId } from "@/integrations/chat-sdk/outbound";
|
|
5
|
+
import { getChatInstance } from "@/integrations/chat-sdk/runtime";
|
|
6
|
+
import { assertTrustedChatTarget } from "@/integrations/chat-sdk/teams-origin";
|
|
7
|
+
import { callerChatThread, canUseChatVerb } from "@/tools/chat-access";
|
|
8
|
+
import { createToolRegistrar, swarmToolOutputSchema, toolErr, toolOk } from "@/tools/utils";
|
|
9
|
+
import { scrubSecrets } from "@/utils/secret-scrubber";
|
|
10
|
+
|
|
11
|
+
const DEFAULT_LIMIT = 30;
|
|
12
|
+
|
|
13
|
+
export const registerChatReadThreadTool = (server: McpServer) => {
|
|
14
|
+
createToolRegistrar(server)(
|
|
15
|
+
"chat-read-thread",
|
|
16
|
+
{
|
|
17
|
+
title: "Read recent messages of a chat thread",
|
|
18
|
+
description:
|
|
19
|
+
"Read the most recent messages of a Chat SDK thread, oldest first. Every agent can read the thread of its current task. Other threads are lead only. Limits per platform: Telegram has no history API, so you get the history the SDK stored in the swarm database, capped at 100 messages and 7 days. Teams needs the Chat.Read.* Graph permission. Slack and Discord return platform history.",
|
|
20
|
+
annotations: { readOnlyHint: true, openWorldHint: true },
|
|
21
|
+
inputSchema: z.object({
|
|
22
|
+
threadId: z
|
|
23
|
+
.string()
|
|
24
|
+
.min(1)
|
|
25
|
+
.describe("Full SDK thread id with the adapter prefix, such as telegram:12345."),
|
|
26
|
+
limit: z
|
|
27
|
+
.number()
|
|
28
|
+
.int()
|
|
29
|
+
.min(1)
|
|
30
|
+
.max(100)
|
|
31
|
+
.optional()
|
|
32
|
+
.describe(`Number of messages to return (default ${DEFAULT_LIMIT}).`),
|
|
33
|
+
}),
|
|
34
|
+
outputSchema: swarmToolOutputSchema({
|
|
35
|
+
yourAgentId: z.string().optional(),
|
|
36
|
+
threadId: z.string().optional(),
|
|
37
|
+
messages: z
|
|
38
|
+
.array(
|
|
39
|
+
z.looseObject({
|
|
40
|
+
id: z.string().optional(),
|
|
41
|
+
userId: z.string().optional(),
|
|
42
|
+
userName: z.string().optional(),
|
|
43
|
+
isBot: z.boolean().optional(),
|
|
44
|
+
sentAt: z.string().optional(),
|
|
45
|
+
text: z.string().optional(),
|
|
46
|
+
}),
|
|
47
|
+
)
|
|
48
|
+
.optional(),
|
|
49
|
+
}),
|
|
50
|
+
},
|
|
51
|
+
async ({ threadId, limit }, requestInfo) => {
|
|
52
|
+
const data = { yourAgentId: requestInfo.agentId };
|
|
53
|
+
// A worker reads only the thread its task came from; any other thread
|
|
54
|
+
// needs the lead verb, as slack-read does for direct channel reads.
|
|
55
|
+
const own = await callerChatThread(requestInfo);
|
|
56
|
+
if (own?.threadId !== threadId) {
|
|
57
|
+
if (!(await canUseChatVerb(requestInfo, "integration.chat.read"))) {
|
|
58
|
+
return toolErr(
|
|
59
|
+
own
|
|
60
|
+
? `Permission denied. You can read only the thread of your current task, ${own.threadId}. Other threads need lead privileges.`
|
|
61
|
+
: "Permission denied. Reading a chat thread that is not the thread of your current task needs lead privileges.",
|
|
62
|
+
{ data },
|
|
63
|
+
);
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
const adapter = adapterOfChatId(threadId);
|
|
67
|
+
const chat = getChatInstance(adapter);
|
|
68
|
+
if (!chat) return toolErr(`Chat SDK adapter '${adapter}' is not running.`, { data });
|
|
69
|
+
try {
|
|
70
|
+
// Teams reads history with the bot token from the service URL in the id.
|
|
71
|
+
assertTrustedChatTarget(threadId);
|
|
72
|
+
const page = await chat.history.thread.list(threadId, {
|
|
73
|
+
limit: limit ?? DEFAULT_LIMIT,
|
|
74
|
+
direction: "backward",
|
|
75
|
+
});
|
|
76
|
+
const messages = page.messages.map((message) => ({
|
|
77
|
+
id: message.id,
|
|
78
|
+
userId: message.author.userId,
|
|
79
|
+
userName: message.author.userName || message.author.fullName || undefined,
|
|
80
|
+
isBot: message.author.isBot === true,
|
|
81
|
+
sentAt: message.metadata?.dateSent?.toISOString(),
|
|
82
|
+
text: scrubSecrets(message.text ?? ""),
|
|
83
|
+
}));
|
|
84
|
+
const rendered = messages
|
|
85
|
+
.map(
|
|
86
|
+
(m) =>
|
|
87
|
+
`[${m.sentAt ?? "?"}] ${m.userName ?? m.userId}${m.isBot ? " (bot)" : ""}: ${m.text}`,
|
|
88
|
+
)
|
|
89
|
+
.join("\n");
|
|
90
|
+
return toolOk(`${messages.length} message(s) from ${threadId}.`, {
|
|
91
|
+
details: rendered || undefined,
|
|
92
|
+
data: { ...data, threadId, messages },
|
|
93
|
+
});
|
|
94
|
+
} catch (error) {
|
|
95
|
+
const message = errorMessage(error);
|
|
96
|
+
return toolErr(`Could not read the thread: ${message}`, { data });
|
|
97
|
+
}
|
|
98
|
+
},
|
|
99
|
+
);
|
|
100
|
+
};
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
import type { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
|
|
2
|
+
import * as z from "zod";
|
|
3
|
+
import { getAgentCurrentTask, getTaskById } from "@/be/db";
|
|
4
|
+
import { errorMessage } from "@/integrations/chat-sdk/logger";
|
|
5
|
+
import { ChatAdapterUnavailableError, postChatMessage } from "@/integrations/chat-sdk/outbound";
|
|
6
|
+
import { chatThreadOfContextKey } from "@/tasks/context-key";
|
|
7
|
+
import { createToolRegistrar, swarmToolOutputSchema, toolErr, toolOk } from "@/tools/utils";
|
|
8
|
+
|
|
9
|
+
export const registerChatReplyTool = (server: McpServer) => {
|
|
10
|
+
createToolRegistrar(server)(
|
|
11
|
+
"chat-reply",
|
|
12
|
+
{
|
|
13
|
+
title: "Reply in the chat thread of this task",
|
|
14
|
+
description:
|
|
15
|
+
"Post a message to the Telegram, Slack, Discord or Teams thread your current task came from. Works only on a task whose contextKey starts with task:chat-sdk:. The task's final output is posted to the thread automatically when the task ends, so use this only for a question to the user or a short progress note on long work. Markdown is supported; long text is split into several messages.",
|
|
16
|
+
annotations: { openWorldHint: true },
|
|
17
|
+
inputSchema: z.object({
|
|
18
|
+
text: z.string().min(1).max(20_000).describe("Markdown text to post to the thread."),
|
|
19
|
+
}),
|
|
20
|
+
outputSchema: swarmToolOutputSchema({
|
|
21
|
+
yourAgentId: z.string().optional(),
|
|
22
|
+
messageId: z.string().optional(),
|
|
23
|
+
adapter: z.string().optional(),
|
|
24
|
+
threadId: z.string().optional(),
|
|
25
|
+
}),
|
|
26
|
+
},
|
|
27
|
+
async ({ text }, requestInfo) => {
|
|
28
|
+
if (!requestInfo.agentId) return toolErr("Agent ID not found.");
|
|
29
|
+
const data = { yourAgentId: requestInfo.agentId };
|
|
30
|
+
|
|
31
|
+
// The header names the task this session works on; without it, the
|
|
32
|
+
// agent's current in-progress task.
|
|
33
|
+
const task = requestInfo.sourceTaskId
|
|
34
|
+
? await getTaskById(requestInfo.sourceTaskId)
|
|
35
|
+
: await getAgentCurrentTask(requestInfo.agentId);
|
|
36
|
+
if (!task) return toolErr("No current task found for chat-reply.", { data });
|
|
37
|
+
// Creator is safe: a non-lead agent can only create a chat-bound task under a parent it owns or created.
|
|
38
|
+
if (task.agentId !== requestInfo.agentId && task.creatorAgentId !== requestInfo.agentId) {
|
|
39
|
+
return toolErr("You don't have context for this task.", { data });
|
|
40
|
+
}
|
|
41
|
+
const thread = chatThreadOfContextKey(task.contextKey);
|
|
42
|
+
if (!thread) {
|
|
43
|
+
return toolErr(
|
|
44
|
+
`Task ${task.id} did not come from a chat thread (no task:chat-sdk contextKey). Use the tool of the channel the task came from.`,
|
|
45
|
+
{ data },
|
|
46
|
+
);
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
try {
|
|
50
|
+
const sent = await postChatMessage({
|
|
51
|
+
adapter: thread.adapter,
|
|
52
|
+
threadId: thread.threadId,
|
|
53
|
+
text,
|
|
54
|
+
taskId: task.id,
|
|
55
|
+
});
|
|
56
|
+
return toolOk(`Posted to ${thread.adapter} thread ${sent.threadId}.`, {
|
|
57
|
+
data: {
|
|
58
|
+
...data,
|
|
59
|
+
messageId: sent.messageIds.at(-1),
|
|
60
|
+
adapter: sent.adapter,
|
|
61
|
+
threadId: sent.threadId,
|
|
62
|
+
},
|
|
63
|
+
});
|
|
64
|
+
} catch (error) {
|
|
65
|
+
const message = errorMessage(error);
|
|
66
|
+
return toolErr(
|
|
67
|
+
error instanceof ChatAdapterUnavailableError
|
|
68
|
+
? `${message}. The final task output is still posted when the adapter is back.`
|
|
69
|
+
: `Could not post to the chat thread: ${message}`,
|
|
70
|
+
{ data },
|
|
71
|
+
);
|
|
72
|
+
}
|
|
73
|
+
},
|
|
74
|
+
);
|
|
75
|
+
};
|