@desplega.ai/agent-swarm 1.173.0 → 2.1.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-kr4abrsk.js} +17 -16
- package/dist/{actions-sgeyv08k.js → actions-eh4g6w2r.js} +39 -34
- package/dist/{amp-adapter-gpxp3kc9.js → amp-adapter-7h7wdt8q.js} +8 -7
- package/dist/{app-km9qnv2f.js → app-7k8s9w5p.js} +18 -14
- package/dist/{assistant-5r8tcvvd.js → assistant-wswt2y7p.js} +40 -35
- package/dist/{boot-reembed-16rv9843.js → boot-reembed-9apm9qw6.js} +10 -9
- package/dist/{boot-reembed-swwzdgd7.js → boot-reembed-whzbdthj.js} +10 -9
- package/dist/{boot-scrub-sweep-zkr6vkqb.js → boot-scrub-sweep-ndzmvmgt.js} +11 -10
- package/dist/{boot-seeds-d8sp49t2.js → boot-seeds-e9sta0yt.js} +16 -13
- package/dist/{claude-adapter-yzzzh8jp.js → claude-adapter-4mqd3r6r.js} +19 -18
- package/dist/{claude-managed-adapter-xgxhqrt2.js → claude-managed-adapter-9wswb52m.js} +7 -5
- package/dist/{claude-managed-setup-bnmjarjc.js → claude-managed-setup-tpe1effx.js} +6 -4
- package/dist/{claude-sdk-session-4whxere4.js → claude-sdk-session-q49ghmh9.js} +12402 -11850
- package/dist/{cli-ne7smj1b.js → cli-01xvj1rq.js} +10 -10
- package/dist/{cli-nwrdx840.js → cli-035vekcw.js} +3 -3
- package/dist/{cli-9xf7fdtp.js → cli-0pd8hzhc.js} +29 -6
- package/dist/{cli-tmhsxhb8.js → cli-0veb92a3.js} +28 -6
- package/dist/{cli-c3rk1v0x.js → cli-10jesr25.js} +3 -3
- package/dist/{cli-6thk4jz3.js → cli-1y7yywmj.js} +6 -6
- package/dist/{cli-6r2f7edk.js → cli-20krdhz5.js} +47 -19
- package/dist/{cli-rfs4vddv.js → cli-29rja109.js} +39 -20
- package/dist/cli-2m1ktfzz.js +17795 -0
- package/dist/{cli-na2pxe4f.js → cli-2pwa0q4d.js} +15 -4
- package/dist/{cli-7a93cv8c.js → cli-4mengw46.js} +441 -43
- package/dist/{cli-94b48jhk.js → cli-5twdke5x.js} +16 -14
- package/dist/{cli-7a0sde2x.js → cli-6cagzc96.js} +9 -0
- package/dist/cli-6cvfxs8r.js +47 -0
- package/dist/{cli-w1apjecb.js → cli-711vwbxn.js} +2 -2
- package/dist/{cli-r4fqfpwf.js → cli-78vv7446.js} +50 -27
- package/dist/{cli-srqq3gc5.js → cli-7n6s5man.js} +5 -5
- package/dist/{cli-m89t9jdf.js → cli-7x0g4sn1.js} +15416 -16024
- package/dist/{cli-2bw3w47j.js → cli-8wwxw8a3.js} +1 -1
- package/dist/{cli-yatxqynm.js → cli-9ha6hs2d.js} +1 -290
- package/dist/{cli-ywm1azg9.js → cli-9jzdj90z.js} +8 -4
- package/dist/cli-9traj3dn.js +638 -0
- package/dist/{cli-smcrrmhy.js → cli-agzebbd5.js} +9 -9
- package/dist/{cli-c5km5znr.js → cli-c8r3kzzb.js} +1 -1
- package/dist/cli-cvv9k0hh.js +10143 -0
- package/dist/{cli-anzdk0z4.js → cli-d9286m9h.js} +2 -2
- package/dist/cli-e3g5mbkz.js +776 -0
- package/dist/cli-f527ce74.js +781 -0
- package/dist/{cli-gmdajfw5.js → cli-gxzkg1de.js} +5020 -1729
- package/dist/{cli-451ks6aw.js → cli-h1arqn31.js} +1 -1
- package/dist/cli-hjwmhcg1.js +10622 -0
- package/dist/{cli-jpc4r224.js → cli-hqr88ew6.js} +19 -3
- package/dist/{cli-h7k0xtry.js → cli-hvqbtna4.js} +4 -1256
- package/dist/{cli-cd0hv9nh.js → cli-j67mh2vg.js} +2 -2
- package/dist/cli-jta2eyzc.js +619 -0
- package/dist/{cli-wd0hz6ah.js → cli-kbrt8gqa.js} +9 -91
- package/dist/cli-kceah1eh.js +92 -0
- package/dist/{cli-d603qf86.js → cli-ksphgh48.js} +10 -8
- package/dist/{cli-3nxdmjrc.js → cli-mgz4nbgg.js} +4 -4
- package/dist/{cli-saqyage0.js → cli-mz5as5ha.js} +179 -27
- package/dist/cli-n2wq4j7m.js +6985 -0
- package/dist/{cli-ppy4nnnh.js → cli-n8ndw8j7.js} +2 -2
- package/dist/{cli-1w0vj3gw.js → cli-naqzr17j.js} +7 -7
- package/dist/{cli-0a7mm3zp.js → cli-nm1p8jfr.js} +25 -21
- package/dist/{cli-wrh08zjd.js → cli-p3zbc1rw.js} +5 -18
- package/dist/{cli-hsem16hb.js → cli-pe05f8yk.js} +29 -6
- package/dist/{cli-5wpw4rab.js → cli-qmzsbkvy.js} +5 -5
- package/dist/{cli-bv2etw2y.js → cli-qnm4e9e4.js} +99 -590
- package/dist/{cli-1x7da0de.js → cli-qqy4q48b.js} +8 -8
- package/dist/{cli-r44t9wqc.js → cli-r106725k.js} +2 -2
- package/dist/{cli-5er5gjbm.js → cli-r35rw5de.js} +15 -15
- package/dist/{cli-v3zhnxsb.js → cli-r610p9bs.js} +2 -2
- package/dist/{cli-7jykv7ef.js → cli-rc6pf5kv.js} +1 -1
- package/dist/{cli-x119kdba.js → cli-rdxm77k3.js} +1 -1
- package/dist/cli-rhs9pz33.js +294 -0
- package/dist/{cli-0p3enk35.js → cli-s69nfyeh.js} +9 -1
- package/dist/{cli-0kxf81jj.js → cli-sawv63e8.js} +2 -2
- package/dist/{cli-e3pmyt39.js → cli-sg19gbf0.js} +1 -1
- package/dist/{cli-d161g69q.js → cli-shk7r5cs.js} +57 -6
- package/dist/{cli-7pz3b7x5.js → cli-sksetb2q.js} +36 -1
- package/dist/{cli-014vns2y.js → cli-stfs0k5s.js} +208 -525
- package/dist/{cli-db3m6zdt.js → cli-t02z59f2.js} +1 -1
- package/dist/{cli-b07hr3bt.js → cli-txqb9s38.js} +2 -2
- package/dist/{cli-s8z6x4b4.js → cli-v7g551te.js} +225 -113
- package/dist/{cli-rfz2ve55.js → cli-vgqzp4em.js} +3341 -955
- package/dist/{cli-7avy4v8w.js → cli-w1q7h2r2.js} +5 -3
- package/dist/{cli-rcp5aqdw.js → cli-w7m81tb7.js} +1 -1
- package/dist/cli-wfnemr1t.js +86 -0
- package/dist/cli-wmgrkscj.js +23254 -0
- package/dist/{cli-qm1448f9.js → cli-wms6x9db.js} +8 -8
- package/dist/{cli-tadfgd0t.js → cli-x1r545x8.js} +33 -4
- package/dist/{cli-xc7p7s3g.js → cli-xczsgshn.js} +6 -6
- package/dist/{cli-fmhj1ehj.js → cli-y3167wt1.js} +882 -271
- package/dist/{cli-r0byd61h.js → cli-yjmqpxt3.js} +17 -16
- package/dist/{cli-81r1wjyv.js → cli-z780ec7c.js} +1 -1
- package/dist/{cli-phcqp1b9.js → cli-zm9t2vde.js} +2 -2
- package/dist/cli.js +34 -49
- package/dist/{codex-adapter-r3dn4fp5.js → codex-adapter-4z16v9n0.js} +15 -14
- package/dist/{codex-login-841kv5fk.js → codex-login-pkepy2j1.js} +3 -2
- package/dist/{codex-session-runner-fwt793h4.js → codex-session-runner-rvms9y2x.js} +15 -14
- package/dist/{commands-z8fq9qna.js → commands-2a9rtyat.js} +11 -10
- package/dist/{controller-hq5ggpcp.js → controller-3k3madbn.js} +5 -4
- package/dist/{controllers-connect-3mf2btyj.js → controllers-connect-v9yn8zb9.js} +42 -37
- package/dist/{cursor-adapter-q8pgv5qa.js → cursor-adapter-98gbhyzn.js} +7 -6
- package/dist/{db-qfpq4bt7.js → db-eywwsnjw.js} +30 -9
- package/dist/{db-yyhqqrbf.js → db-ncypk9np.js} +12 -11
- package/dist/{devin-adapter-xgtj680j.js → devin-adapter-zeq8b9gz.js} +3 -2
- package/dist/{dispatcher-x0x7qfcq.js → dispatcher-w94kc575.js} +38 -33
- package/dist/{docker-f503k9p0.js → docker-b1e4cqab.js} +3 -2
- package/dist/{dsh-adapter-34etvb5m.js → dsh-adapter-47ztej36.js} +7 -6
- package/dist/{e2b-66xra4az.js → e2b-bbt8ey45.js} +4 -3
- package/dist/{e2b-xwhyj8cw.js → e2b-dp804sen.js} +4 -3
- package/dist/{event-bus-vjtv82gm.js → event-bus-j2jdg3qb.js} +3 -2
- package/dist/{external-channel-h1fjecwe.js → external-channel-zrkg8ar9.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-s9ncp9vy.js} +18 -17
- package/dist/{handlers-znb9n7d4.js → handlers-kqftvjgw.js} +41 -36
- package/dist/{heartbeat-bm4qkn8d.js → heartbeat-9q96p8v9.js} +43 -38
- package/dist/{hook-254r4mza.js → hook-56qe10dp.js} +11 -23
- package/dist/{hook-xaa3q80s.js → hook-yy6rw42k.js} +12 -11
- package/dist/{http-3dgrgp8y.js → http-r2dnb8f8.js} +2411 -275
- package/dist/{index-fx3jqq1e.js → index-3gtmkhc6.js} +38 -33
- package/dist/{index-bdsqa799.js → index-3k5hkz7r.js} +43 -38
- package/dist/index-52j1aja4.js +3465 -0
- package/dist/{index-g2byveez.js → index-5e101240.js} +38 -33
- package/dist/{index-2ybk7khy.js → index-5wyfjc2c.js} +845 -683
- 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-jd7sejm2.js → index-vxvhxxbn.js} +10 -9
- package/dist/index-we27kd31.js +12810 -0
- package/dist/{keepalive-6ywhpja1.js → keepalive-psj6j54c.js} +12 -11
- package/dist/{lead-cw45n21s.js → lead-hajz4688.js} +61 -56
- package/dist/{maintenance-ss1a1ge9.js → maintenance-ps02sb5m.js} +10 -9
- package/dist/{node-fkajaq96.js → node-mwb5r697.js} +2 -1
- package/dist/{oauth-refresh-sweep-3c6f8vyy.js → oauth-refresh-sweep-0284h93b.js} +11 -10
- package/dist/{onboard-8hzdhc1k.js → onboard-rbf6bg01.js} +11 -3
- package/dist/{opencode-adapter-qbchbcfz.js → opencode-adapter-13dcatgq.js} +18 -13
- package/dist/{otel-impl-e6mnvc4v.js → otel-impl-zvqcbhp7.js} +242 -868
- package/dist/{pi-mono-adapter-3c9g9snh.js → pi-mono-adapter-xxy47q4v.js} +244 -106
- package/dist/{pricing-refresh-gedkjdw0.js → pricing-refresh-kefx9v3t.js} +12 -11
- package/dist/{rbac-roles-dt4vh1yy.js → rbac-roles-3m36rxm2.js} +11 -10
- package/dist/{rbac-roles-eszdg6pm.js → rbac-roles-z76xv0hq.js} +12 -11
- package/dist/{render-v2-907cy6sa.js → render-v2-wz73bs25.js} +18 -14
- package/dist/{resume-42stcaz0.js → resume-k4wzge8w.js} +25 -21
- package/dist/{runtime-qfry88v2.js → runtime-sa2vr16g.js} +2 -2
- package/dist/{setup-adbc1c10.js → setup-2fp2pypp.js} +2 -2
- package/dist/{virtual-modules-rtmt494q.js → virtual-modules-9gkxqrap.js} +6 -5
- package/dist/{work-objects-wpj8ck95.js → work-objects-6k0ywz3m.js} +41 -36
- package/dist/{worker-bzdra9rf.js → worker-5v3rxsdh.js} +61 -56
- package/dist/{x-qfmygqqv.js → x-4mtdeym1.js} +3 -2
- package/openapi.json +1279 -329
- package/package.json +15 -15
- package/src/agentmail/handlers.ts +21 -11
- package/src/apps/definition.ts +16 -0
- package/src/azure-devops/api.ts +52 -2
- package/src/azure-devops/boards-sync.ts +50 -20
- package/src/azure-devops/index.ts +1 -0
- package/src/azure-devops/outbound.ts +95 -0
- package/src/be/boot-seeds.ts +11 -0
- package/src/be/config-refs.ts +60 -0
- package/src/be/db/model-profiles.ts +315 -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 +237 -0
- package/src/be/db/tasks/write.ts +7 -0
- package/src/be/db.ts +87 -36
- 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/migrations/219_relabel_pi_context_compactions.sql +22 -0
- package/src/be/migrations/220_model_profiles.sql +137 -0
- package/src/be/model-catalog-store.ts +5 -1
- package/src/be/model-tier-resolution.ts +155 -10
- package/src/be/model-validation.ts +12 -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 +22 -8
- package/src/be/secret-registry.ts +6 -2
- 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-model-profiles.ts +41 -0
- package/src/be/seed-pricing.ts +19 -2
- package/src/be/seed-scripts/catalog/heartbeat-jev-gate.ts +368 -0
- 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-scripts/index.ts +9 -0
- 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 +53 -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 +193 -71
- package/src/e2b/env.ts +5 -0
- package/src/extensions/catalog.generated.json +17 -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/loader.ts +10 -5
- package/src/extensions/post-bridge.ts +40 -0
- package/src/github/handlers.ts +11 -4
- package/src/heartbeat/heartbeat.ts +2 -1
- package/src/heartbeat/templates.ts +8 -5
- package/src/hooks/hook.ts +0 -26
- package/src/http/agents.ts +49 -2
- package/src/http/all-routes.ts +1 -1
- package/src/http/apps.ts +30 -3
- package/src/http/config.ts +62 -19
- package/src/http/core.ts +8 -0
- package/src/http/index.ts +25 -3
- package/src/http/integrations.ts +88 -1
- package/src/http/mcp.ts +15 -5
- package/src/http/model-profiles.ts +303 -0
- package/src/http/page-proxy.ts +2 -4
- package/src/http/pages-public.ts +13 -7
- package/src/http/pages.ts +3 -13
- package/src/http/poll.ts +42 -71
- package/src/http/requested-by.ts +54 -0
- package/src/http/script-runs.ts +1 -1
- package/src/http/scripts.ts +26 -2
- package/src/http/stats.ts +1 -30
- package/src/http/tasks.ts +79 -5
- package/src/http/utils.ts +14 -0
- package/src/http/webhooks.ts +160 -3
- 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/{artifact-sdk → pages}/feedback-overlay.ts +65 -6
- 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 +56 -3
- 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/pi-mono-extension.ts +17 -3
- package/src/providers/pricing-sources.md +33 -2
- package/src/providers/reasoning-effort.ts +37 -0
- package/src/providers/types.ts +6 -2
- package/src/rbac/legacy-policy.ts +16 -2
- package/src/rbac/permissions.ts +26 -0
- package/src/scheduler/deferred-task-waits.ts +18 -5
- package/src/scheduler/scheduler.ts +11 -3
- package/src/scripts-runtime/executors/quickjs-prelude.ts +75 -6
- package/src/scripts-runtime/executors/quickjs-runner.ts +85 -8
- package/src/scripts-runtime/executors/quickjs-web-shims.ts +1041 -0
- package/src/scripts-runtime/extract-args-schema.ts +9 -8
- package/src/scripts-runtime/loader.ts +19 -7
- package/src/scripts-runtime/quickjs-compat.ts +464 -0
- package/src/scripts-runtime/sdk-allowlist.ts +12 -6
- package/src/scripts-runtime/swarm-sdk.ts +0 -11
- package/src/scripts-runtime/types/stdlib.d.ts +39 -15
- package/src/scripts-runtime/types/swarm-extension.d.ts +66 -1
- package/src/scripts-runtime/types/swarm-sdk.d.ts +39 -15
- package/src/server.ts +62 -19
- package/src/slack/blocks.ts +4 -2
- package/src/slack/event-dedup.ts +1 -1
- package/src/slack/reaction-shortcode.ts +14 -0
- package/src/slack/render-v2.ts +12 -7
- package/src/slack/responses.ts +5 -4
- package/src/slack/thread-buffer.ts +41 -22
- 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 +15 -33
- package/src/tests/acp-adapter.test.ts +42 -0
- package/src/tests/agentmail-filters.test.ts +47 -0
- package/src/tests/api-drain-worker-e2e.test.ts +1 -6
- package/src/tests/apps-spike2.test.ts +35 -0
- package/src/tests/azure-devops-outbound.test.ts +420 -0
- 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 +22 -0
- package/src/tests/db-task-write-contract.test.ts +11 -0
- package/src/tests/deferred-task-wake.test.ts +78 -1
- 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/env-flag.test.ts +1 -0
- package/src/tests/explicit-model-validation.test.ts +105 -1
- package/src/tests/extensions-catalog.test.ts +1 -0
- package/src/tests/extensions-example-comms-adherence-gate.test.ts +413 -0
- package/src/tests/extensions-example-route-chat-channel.test.ts +154 -0
- package/src/tests/extensions-loader.test.ts +15 -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/heartbeat-jev-gate-credentials.test.ts +168 -0
- package/src/tests/heartbeat-jev-gate.test.ts +385 -0
- 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-profile-backcompat.test.ts +327 -0
- package/src/tests/model-profile-providers.test.ts +302 -0
- package/src/tests/model-profile-runtime-secrets.test.ts +223 -0
- package/src/tests/model-profiles-api.test.ts +614 -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-keys.test.ts +121 -0
- package/src/tests/pages-feedback.test.ts +4 -1
- package/src/tests/paused-tasks-requested-by.test.ts +102 -0
- package/src/tests/pi-context-compaction.test.ts +193 -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/profile-claim.test.ts +269 -0
- package/src/tests/prompt-template-remaining.test.ts +35 -0
- package/src/tests/quickjs-compat.test.ts +213 -0
- package/src/tests/rbac-engine.test.ts +32 -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 +6 -0
- package/src/tests/script-executor-conformance.test.ts +252 -1
- package/src/tests/script-executor-quickjs.test.ts +96 -0
- package/src/tests/script-runs-http.test.ts +89 -0
- package/src/tests/scripts-http.test.ts +217 -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 +96 -0
- package/src/tests/seed-model-profiles.test.ts +174 -0
- package/src/tests/seed-scripts.test.ts +2 -2
- 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/skill-invocations.test.ts +4 -2
- package/src/tests/slack-blocks.test.ts +36 -0
- package/src/tests/slack-inline-output.test.ts +14 -0
- package/src/tests/slack-reaction-config.test.ts +20 -0
- package/src/tests/slack-render-v2.test.ts +37 -0
- package/src/tests/slack-thread-buffer.test.ts +98 -1
- 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-llm.test.ts +61 -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/model-profiles/list-model-profiles.ts +54 -0
- package/src/tools/register-chat-channel.ts +139 -0
- package/src/tools/request-human-input.ts +11 -3
- package/src/tools/script-apis.ts +5 -1
- package/src/tools/script-upsert.ts +4 -1
- package/src/tools/send-task.ts +48 -1
- 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/system-one-decide.ts +104 -0
- package/src/tools/task-action.ts +22 -1
- package/src/tools/tool-config.ts +14 -6
- package/src/types.ts +48 -3
- 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 +86 -10
- package/src/utils/task-tool-manifest.ts +19 -2
- package/src/utils/vcs-repo-match.ts +29 -0
- package/src/workflows/executors/raw-llm.ts +8 -3
- package/src/workflows/executors/swarm-script.ts +1 -0
- package/src/workflows/executors/system-one-decision.ts +21 -11
- package/src/workflows/executors/workflow-llm.ts +22 -0
- package/src/workflows/index.ts +21 -12
- package/templates/extensions/README.md +1 -0
- package/templates/extensions/comms-adherence-gate/README.md +88 -0
- package/templates/extensions/comms-adherence-gate/hooks.ts +489 -0
- package/templates/extensions/comms-adherence-gate/manifest.json +8 -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-42qhyvwx.js +0 -33
- 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
|
@@ -0,0 +1,154 @@
|
|
|
1
|
+
import { afterAll, beforeAll, beforeEach, describe, expect, spyOn, test } from "bun:test";
|
|
2
|
+
import { unlink } from "node:fs/promises";
|
|
3
|
+
import { closeDb, createAgent, getDbClient, getKv, initDb } from "../be/db";
|
|
4
|
+
import { installExtension, listExtensionRuns } from "../be/extensions/db";
|
|
5
|
+
import { enableExtension, stopExtensionRuntime } from "../extensions/lifecycle";
|
|
6
|
+
import { chatSdkContextKey } from "../tasks/context-key";
|
|
7
|
+
import type { Agent } from "../types";
|
|
8
|
+
import {
|
|
9
|
+
flushChatBuffers,
|
|
10
|
+
postTelegramUpdate,
|
|
11
|
+
startTelegramHarness,
|
|
12
|
+
type TelegramHarness,
|
|
13
|
+
telegramUpdate,
|
|
14
|
+
} from "./chat-sdk-harness";
|
|
15
|
+
import { loadBundleFixture } from "./fixtures/extensions/load";
|
|
16
|
+
|
|
17
|
+
const TEST_DB_PATH = "./test-extensions-example-route-chat-channel.sqlite";
|
|
18
|
+
|
|
19
|
+
let harness: TelegramHarness;
|
|
20
|
+
let lead: Agent;
|
|
21
|
+
let worker: Agent;
|
|
22
|
+
|
|
23
|
+
async function removeDbFiles(): Promise<void> {
|
|
24
|
+
for (const suffix of ["", "-wal", "-shm"]) {
|
|
25
|
+
await unlink(`${TEST_DB_PATH}${suffix}`).catch(() => {});
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
async function chatTask(chatId: number) {
|
|
30
|
+
return getDbClient().get<{ agentId: string | null }>(
|
|
31
|
+
"SELECT agentId FROM agent_tasks WHERE source = 'chat' AND contextKey = ? ORDER BY createdAt DESC LIMIT 1",
|
|
32
|
+
[chatSdkContextKey(`telegram:${chatId}`)],
|
|
33
|
+
);
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
/** A private-chat message, flushed out of the thread buffer. */
|
|
37
|
+
async function sendDm(chatId: number, text: string): Promise<void> {
|
|
38
|
+
await postTelegramUpdate(telegramUpdate({ id: chatId, type: "private" }, text));
|
|
39
|
+
await flushChatBuffers();
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
async function installRouteFixture(channelId: string, agentId: string) {
|
|
43
|
+
const bundle = await loadBundleFixture("route-chat-channel-to-agent");
|
|
44
|
+
const { extension } = await installExtension({
|
|
45
|
+
...bundle,
|
|
46
|
+
config: { adapter: "telegram", channelId, agentId },
|
|
47
|
+
});
|
|
48
|
+
return { extension, enabled: await enableExtension(extension.id) };
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
describe("extension Chat SDK channel routing", () => {
|
|
52
|
+
beforeAll(async () => {
|
|
53
|
+
await removeDbFiles();
|
|
54
|
+
initDb(TEST_DB_PATH);
|
|
55
|
+
lead = await createAgent({
|
|
56
|
+
id: crypto.randomUUID(),
|
|
57
|
+
name: "chat-route-lead",
|
|
58
|
+
isLead: true,
|
|
59
|
+
status: "idle",
|
|
60
|
+
});
|
|
61
|
+
worker = await createAgent({
|
|
62
|
+
id: crypto.randomUUID(),
|
|
63
|
+
name: "chat-route-worker",
|
|
64
|
+
isLead: false,
|
|
65
|
+
status: "idle",
|
|
66
|
+
});
|
|
67
|
+
harness = await startTelegramHarness();
|
|
68
|
+
});
|
|
69
|
+
|
|
70
|
+
afterAll(async () => {
|
|
71
|
+
await harness.stop();
|
|
72
|
+
await stopExtensionRuntime();
|
|
73
|
+
closeDb();
|
|
74
|
+
await removeDbFiles();
|
|
75
|
+
});
|
|
76
|
+
|
|
77
|
+
beforeEach(async () => {
|
|
78
|
+
await stopExtensionRuntime();
|
|
79
|
+
await getDbClient().run("DELETE FROM extensions");
|
|
80
|
+
});
|
|
81
|
+
|
|
82
|
+
test("routes the configured channel to its agent and leaves other channels with the lead", async () => {
|
|
83
|
+
const { extension } = await installRouteFixture("telegram:3001", worker.id);
|
|
84
|
+
|
|
85
|
+
await sendDm(3001, "route this to the worker");
|
|
86
|
+
expect(await chatTask(3001)).toEqual({ agentId: worker.id });
|
|
87
|
+
expect(
|
|
88
|
+
(await listExtensionRuns(extension.id)).some(
|
|
89
|
+
(run) => run.event === "pre.chat.route" && run.action === "modify",
|
|
90
|
+
),
|
|
91
|
+
).toBe(true);
|
|
92
|
+
|
|
93
|
+
await sendDm(3002, "use the normal route");
|
|
94
|
+
expect(await chatTask(3002)).toEqual({ agentId: lead.id });
|
|
95
|
+
});
|
|
96
|
+
|
|
97
|
+
test("a block creates no task", async () => {
|
|
98
|
+
const bundle = await loadBundleFixture("minimal");
|
|
99
|
+
const { extension } = await installExtension({
|
|
100
|
+
manifest: { ...bundle.manifest, name: "block-chat-route", description: "blocks chat" },
|
|
101
|
+
files: {
|
|
102
|
+
"hooks.ts": `import { block, type SwarmExtension } from "swarm-extension";
|
|
103
|
+
const extension: SwarmExtension = (api) => {
|
|
104
|
+
api.on("pre.chat.route", () => block("chat is muted"));
|
|
105
|
+
};
|
|
106
|
+
export default extension;
|
|
107
|
+
`,
|
|
108
|
+
},
|
|
109
|
+
});
|
|
110
|
+
await enableExtension(extension.id);
|
|
111
|
+
|
|
112
|
+
await sendDm(3003, "this must not become a task");
|
|
113
|
+
expect(await chatTask(3003)).toBeNull();
|
|
114
|
+
const runs = await listExtensionRuns(extension.id);
|
|
115
|
+
expect(runs.some((run) => run.event === "pre.chat.route" && run.action === "block")).toBe(true);
|
|
116
|
+
// Ids only in the run subject: never the message text.
|
|
117
|
+
expect(runs.every((run) => !run.subject?.includes("must not"))).toBe(true);
|
|
118
|
+
});
|
|
119
|
+
|
|
120
|
+
test("falls back to the built-in router when the extension names an unknown agent", async () => {
|
|
121
|
+
await installRouteFixture("telegram:3004", "missing-agent");
|
|
122
|
+
const warn = spyOn(console, "warn").mockImplementation(() => {});
|
|
123
|
+
try {
|
|
124
|
+
await sendDm(3004, "use the fallback");
|
|
125
|
+
expect(await chatTask(3004)).toEqual({ agentId: lead.id });
|
|
126
|
+
expect(warn).toHaveBeenCalledWith(
|
|
127
|
+
"[chat-sdk] Extension selected an unknown agent missing-agent. Using the router.",
|
|
128
|
+
);
|
|
129
|
+
} finally {
|
|
130
|
+
warn.mockRestore();
|
|
131
|
+
}
|
|
132
|
+
});
|
|
133
|
+
|
|
134
|
+
test("post.chat.message carries the inbound sender", async () => {
|
|
135
|
+
const { extension, enabled } = await installRouteFixture("telegram:3005", worker.id);
|
|
136
|
+
await sendDm(3005, "record this message");
|
|
137
|
+
|
|
138
|
+
const deadline = Date.now() + 5_000;
|
|
139
|
+
while (
|
|
140
|
+
!(await listExtensionRuns(extension.id)).some(
|
|
141
|
+
(run) => run.event === "post.chat.message" && run.action === "continue",
|
|
142
|
+
) &&
|
|
143
|
+
Date.now() < deadline
|
|
144
|
+
) {
|
|
145
|
+
await Bun.sleep(20);
|
|
146
|
+
}
|
|
147
|
+
expect(
|
|
148
|
+
await getKv(
|
|
149
|
+
`task:agent:${enabled.agentId}`,
|
|
150
|
+
"ext:route-chat-channel-to-agent:chat:telegram:3005",
|
|
151
|
+
),
|
|
152
|
+
).toMatchObject({ value: "777001" });
|
|
153
|
+
});
|
|
154
|
+
});
|
|
@@ -52,6 +52,21 @@ describe("extension loader", () => {
|
|
|
52
52
|
expect(await Bun.file(loaded.sourcePath).exists()).toBe(false);
|
|
53
53
|
});
|
|
54
54
|
|
|
55
|
+
test("loads hooks from a safe nested bundle path", async () => {
|
|
56
|
+
const bundle = await loadBundleFixture("minimal");
|
|
57
|
+
const hooks = bundle.files[bundle.manifest.assets.hooks]!;
|
|
58
|
+
bundle.manifest = {
|
|
59
|
+
...bundle.manifest,
|
|
60
|
+
assets: { ...bundle.manifest.assets, hooks: "nested/hooks.ts" },
|
|
61
|
+
};
|
|
62
|
+
bundle.files = { "nested/hooks.ts": hooks };
|
|
63
|
+
const installed = await installExtension(bundle);
|
|
64
|
+
const loaded = await loadExtension({ record: installed.extension, ...bundle });
|
|
65
|
+
|
|
66
|
+
expect(await Bun.file(loaded.sourcePath).exists()).toBe(true);
|
|
67
|
+
await loaded.dispose();
|
|
68
|
+
});
|
|
69
|
+
|
|
55
70
|
test("defensively rejects file traversal before writing bundle files", async () => {
|
|
56
71
|
const input = await installLoadable("minimal");
|
|
57
72
|
input.files["../escape.ts"] = "export default () => {};";
|
|
@@ -33,6 +33,7 @@ const NEW_EVENTS = [
|
|
|
33
33
|
"post.task.budgetRefused",
|
|
34
34
|
"post.email.received",
|
|
35
35
|
"post.kapso.message",
|
|
36
|
+
"post.chat.message",
|
|
36
37
|
"post.vcs.event",
|
|
37
38
|
] as const satisfies readonly (keyof SwarmEventMap)[];
|
|
38
39
|
|
|
@@ -358,6 +359,56 @@ describe("extension post bridge: new events", () => {
|
|
|
358
359
|
});
|
|
359
360
|
});
|
|
360
361
|
|
|
362
|
+
describe("post.chat.message", () => {
|
|
363
|
+
test("chat.message.received dispatches an inbound event", async () => {
|
|
364
|
+
await registerCapture("capture");
|
|
365
|
+
workflowEventBus.emit("chat.message.received", {
|
|
366
|
+
adapter: "telegram",
|
|
367
|
+
channelId: "telegram:555001",
|
|
368
|
+
threadId: "telegram:555001",
|
|
369
|
+
userId: "555001",
|
|
370
|
+
text: "hello swarm",
|
|
371
|
+
messageId: "555001:11",
|
|
372
|
+
});
|
|
373
|
+
await waitForEvents(1);
|
|
374
|
+
expect(received).toEqual([
|
|
375
|
+
{
|
|
376
|
+
extension: "capture",
|
|
377
|
+
event: "post.chat.message",
|
|
378
|
+
payload: {
|
|
379
|
+
adapter: "telegram",
|
|
380
|
+
channelId: "telegram:555001",
|
|
381
|
+
threadId: "telegram:555001",
|
|
382
|
+
userId: "555001",
|
|
383
|
+
text: "hello swarm",
|
|
384
|
+
direction: "inbound",
|
|
385
|
+
messageId: "555001:11",
|
|
386
|
+
},
|
|
387
|
+
},
|
|
388
|
+
]);
|
|
389
|
+
});
|
|
390
|
+
|
|
391
|
+
test("chat.message.sent dispatches an outbound event", async () => {
|
|
392
|
+
await registerCapture("capture");
|
|
393
|
+
workflowEventBus.emit("chat.message.sent", {
|
|
394
|
+
adapter: "telegram",
|
|
395
|
+
channelId: "telegram:555001",
|
|
396
|
+
threadId: "telegram:555001",
|
|
397
|
+
text: "done",
|
|
398
|
+
taskId: "task-1",
|
|
399
|
+
});
|
|
400
|
+
await waitForEvents(1);
|
|
401
|
+
expect(received[0]?.payload).toEqual({
|
|
402
|
+
adapter: "telegram",
|
|
403
|
+
channelId: "telegram:555001",
|
|
404
|
+
threadId: "telegram:555001",
|
|
405
|
+
text: "done",
|
|
406
|
+
direction: "outbound",
|
|
407
|
+
taskId: "task-1",
|
|
408
|
+
});
|
|
409
|
+
});
|
|
410
|
+
});
|
|
411
|
+
|
|
361
412
|
describe("post.kapso.message", () => {
|
|
362
413
|
test("a bus emit dispatches the typed payload", async () => {
|
|
363
414
|
await registerCapture("capture");
|
|
@@ -120,6 +120,95 @@ describe("fetchResolvedEnv", () => {
|
|
|
120
120
|
warning.mockRestore();
|
|
121
121
|
}
|
|
122
122
|
});
|
|
123
|
+
|
|
124
|
+
test("exportToEnv=false rows stay out of the harness env, even over a boot copy", async () => {
|
|
125
|
+
const agentId = crypto.randomUUID();
|
|
126
|
+
mockResponsesByAgentId.set(agentId, {
|
|
127
|
+
status: 200,
|
|
128
|
+
body: {
|
|
129
|
+
configs: [
|
|
130
|
+
{ key: "VERCEL_TOKEN", value: "row-vercel", isSecret: true, exportToEnv: false },
|
|
131
|
+
{ key: "GITHUB_TOKEN", value: "row-github", isSecret: true, exportToEnv: true },
|
|
132
|
+
{ key: "PUBLIC_SETTING", value: "visible", isSecret: false, exportToEnv: true },
|
|
133
|
+
{ key: "HIDDEN_SETTING", value: "hidden", isSecret: false, exportToEnv: false },
|
|
134
|
+
// A pre-215 API sends no flag: the row keeps exporting.
|
|
135
|
+
{ key: "LEGACY_SECRET", value: "row-legacy", isSecret: true },
|
|
136
|
+
// Restricted keys never export, whatever the row says.
|
|
137
|
+
{ key: "SLACK_APP_TOKEN", value: "row-xapp", isSecret: true, exportToEnv: true },
|
|
138
|
+
],
|
|
139
|
+
},
|
|
140
|
+
});
|
|
141
|
+
const log = spyOn(console, "log").mockImplementation(() => {});
|
|
142
|
+
try {
|
|
143
|
+
// The base env stands for the container env or an older entrypoint's
|
|
144
|
+
// boot export; a withheld row must remove those copies too.
|
|
145
|
+
const result = await fetchResolvedEnv(testUrl, "key", agentId, {
|
|
146
|
+
VERCEL_TOKEN: "boot-vercel",
|
|
147
|
+
SLACK_APP_TOKEN: "boot-xapp",
|
|
148
|
+
});
|
|
149
|
+
expect(result.env.VERCEL_TOKEN).toBeUndefined();
|
|
150
|
+
expect(result.env.HIDDEN_SETTING).toBeUndefined();
|
|
151
|
+
expect(result.env.SLACK_APP_TOKEN).toBeUndefined();
|
|
152
|
+
expect(result.env.GITHUB_TOKEN).toBe("row-github");
|
|
153
|
+
expect(result.env.PUBLIC_SETTING).toBe("visible");
|
|
154
|
+
expect(result.env.LEGACY_SECRET).toBe("row-legacy");
|
|
155
|
+
const logged = log.mock.calls.map((call) => String(call[0])).join("\n");
|
|
156
|
+
expect(logged).toContain("HIDDEN_SETTING, SLACK_APP_TOKEN, VERCEL_TOKEN");
|
|
157
|
+
expect(logged).not.toContain("row-vercel");
|
|
158
|
+
expect(logged).not.toContain("boot-vercel");
|
|
159
|
+
} finally {
|
|
160
|
+
log.mockRestore();
|
|
161
|
+
}
|
|
162
|
+
});
|
|
163
|
+
|
|
164
|
+
test("a row revoked after boot leaves process.env, so merged adapter envs lose it", async () => {
|
|
165
|
+
const agentId = crypto.randomUUID();
|
|
166
|
+
const key = "REVOKED_AFTER_BOOT_SECRET";
|
|
167
|
+
mockResponsesByAgentId.set(agentId, {
|
|
168
|
+
status: 200,
|
|
169
|
+
body: { configs: [{ key, value: "row-value", isSecret: true, exportToEnv: false }] },
|
|
170
|
+
});
|
|
171
|
+
// The entrypoint exported the row at boot, before it was switched off.
|
|
172
|
+
process.env[key] = "boot-export";
|
|
173
|
+
const log = spyOn(console, "log").mockImplementation(() => {});
|
|
174
|
+
try {
|
|
175
|
+
const result = await fetchResolvedEnv(testUrl, "key", agentId);
|
|
176
|
+
expect(result.env[key]).toBeUndefined();
|
|
177
|
+
expect(process.env[key]).toBeUndefined();
|
|
178
|
+
// The amp, dsh, grok, cursor and claude adapters spawn with this shape.
|
|
179
|
+
expect({ ...process.env, ...result.env }[key]).toBeUndefined();
|
|
180
|
+
} finally {
|
|
181
|
+
delete process.env[key];
|
|
182
|
+
log.mockRestore();
|
|
183
|
+
}
|
|
184
|
+
});
|
|
185
|
+
|
|
186
|
+
test("a masked row keeps its boot value only when the row is exported", async () => {
|
|
187
|
+
const agentId = crypto.randomUUID();
|
|
188
|
+
mockResponsesByAgentId.set(agentId, {
|
|
189
|
+
status: 200,
|
|
190
|
+
body: {
|
|
191
|
+
configs: [
|
|
192
|
+
{ key: "EXPORTED_BOOT", value: MASKED_SECRET_VALUE, isSecret: true, exportToEnv: true },
|
|
193
|
+
{ key: "WITHHELD_BOOT", value: MASKED_SECRET_VALUE, isSecret: true, exportToEnv: false },
|
|
194
|
+
],
|
|
195
|
+
},
|
|
196
|
+
});
|
|
197
|
+
const warning = spyOn(console, "warn").mockImplementation(() => {});
|
|
198
|
+
const log = spyOn(console, "log").mockImplementation(() => {});
|
|
199
|
+
try {
|
|
200
|
+
const result = await fetchResolvedEnv(testUrl, "key", agentId, {
|
|
201
|
+
EXPORTED_BOOT: "boot-exported",
|
|
202
|
+
WITHHELD_BOOT: "boot-withheld",
|
|
203
|
+
});
|
|
204
|
+
expect(result.env.EXPORTED_BOOT).toBe("boot-exported");
|
|
205
|
+
expect(result.env.WITHHELD_BOOT).toBeUndefined();
|
|
206
|
+
} finally {
|
|
207
|
+
warning.mockRestore();
|
|
208
|
+
log.mockRestore();
|
|
209
|
+
}
|
|
210
|
+
});
|
|
211
|
+
|
|
123
212
|
test("repository scope requires the requested repository identity", async () => {
|
|
124
213
|
const repoId = crypto.randomUUID();
|
|
125
214
|
let url = "https://github.com/another-owner/fixture";
|
|
@@ -305,6 +394,45 @@ describe("fetchResolvedEnv", () => {
|
|
|
305
394
|
]);
|
|
306
395
|
});
|
|
307
396
|
|
|
397
|
+
test("HELMCODE_API_NAN alias becomes NAN_API_KEY and pools for a nan/* model", async () => {
|
|
398
|
+
const agentId = crypto.randomUUID();
|
|
399
|
+
mockResponsesByAgentId.set(agentId, {
|
|
400
|
+
status: 200,
|
|
401
|
+
body: { configs: [{ key: "HELMCODE_API_NAN", value: "fixture-nan-a,fixture-nan-b" }] },
|
|
402
|
+
});
|
|
403
|
+
const result = await fetchResolvedEnv(testUrl, "key", agentId, {}, "nan/glm5.3-flash", {
|
|
404
|
+
provider: "opencode",
|
|
405
|
+
});
|
|
406
|
+
expect(result.env.NAN_API_KEY).toMatch(/^fixture-nan-[ab]$/);
|
|
407
|
+
expect(result.credentialSelections.map((selection) => selection.keyType)).toEqual([
|
|
408
|
+
"NAN_API_KEY",
|
|
409
|
+
]);
|
|
410
|
+
});
|
|
411
|
+
|
|
412
|
+
test("an explicit NAN_API_KEY wins over the HELMCODE_API_NAN alias", async () => {
|
|
413
|
+
const agentId = crypto.randomUUID();
|
|
414
|
+
mockResponsesByAgentId.set(agentId, {
|
|
415
|
+
status: 200,
|
|
416
|
+
body: {
|
|
417
|
+
configs: [
|
|
418
|
+
{ key: "NAN_API_KEY", value: "fixture-nan-canonical" },
|
|
419
|
+
{ key: "HELMCODE_API_NAN", value: "fixture-nan-alias" },
|
|
420
|
+
],
|
|
421
|
+
},
|
|
422
|
+
});
|
|
423
|
+
const result = await fetchResolvedEnv(testUrl, "key", agentId, {}, "nan/qwen3.6", {
|
|
424
|
+
provider: "pi",
|
|
425
|
+
});
|
|
426
|
+
expect(result.env.NAN_API_KEY).toBe("fixture-nan-canonical");
|
|
427
|
+
});
|
|
428
|
+
|
|
429
|
+
test("the alias also normalizes from the boot env when the API is unreachable", async () => {
|
|
430
|
+
const result = await fetchResolvedEnv("", "key", "agent-1", {
|
|
431
|
+
HELMCODE_API_NAN: "fixture-nan-boot",
|
|
432
|
+
});
|
|
433
|
+
expect(result.env.NAN_API_KEY).toBe("fixture-nan-boot");
|
|
434
|
+
});
|
|
435
|
+
|
|
308
436
|
describe("model-scoped window filter — GET /api/keys/available?model=<family>", () => {
|
|
309
437
|
beforeEach(() => {
|
|
310
438
|
keysAvailableRequests.length = 0;
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
{
|
|
2
|
+
"update_id": 900001,
|
|
3
|
+
"message": {
|
|
4
|
+
"message_id": 11,
|
|
5
|
+
"date": 1760400000,
|
|
6
|
+
"chat": { "id": 555001, "type": "private", "first_name": "Taras", "username": "taras_test" },
|
|
7
|
+
"from": { "id": 555001, "is_bot": false, "first_name": "Taras", "username": "taras_test" },
|
|
8
|
+
"text": "hello swarm, please summarize the open tasks"
|
|
9
|
+
}
|
|
10
|
+
}
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import { modify, type SwarmExtension } from "swarm-extension";
|
|
2
|
+
import { z } from "zod";
|
|
3
|
+
|
|
4
|
+
export const config = z.object({ adapter: z.string(), channelId: z.string(), agentId: z.string() });
|
|
5
|
+
|
|
6
|
+
const manifest = {
|
|
7
|
+
name: "route-chat-channel-to-agent",
|
|
8
|
+
description: "Routes one Chat SDK channel to a configured agent",
|
|
9
|
+
version: "1.0.0",
|
|
10
|
+
runtime: "api",
|
|
11
|
+
assets: { hooks: "hooks.ts" },
|
|
12
|
+
config,
|
|
13
|
+
} as const;
|
|
14
|
+
|
|
15
|
+
const extension: SwarmExtension<typeof manifest> = (api) => {
|
|
16
|
+
api.on("pre.chat.route", (event, ctx) => {
|
|
17
|
+
if (event.adapter !== ctx.config.adapter || event.channelId !== ctx.config.channelId) return;
|
|
18
|
+
return modify({ target: { kind: "agent", agentId: ctx.config.agentId } });
|
|
19
|
+
});
|
|
20
|
+
|
|
21
|
+
api.on("post.chat.message", async (event, ctx) => {
|
|
22
|
+
if (event.direction !== "inbound" || !event.userId) return;
|
|
23
|
+
await ctx.state.set(`chat:${event.channelId}`, event.userId);
|
|
24
|
+
});
|
|
25
|
+
};
|
|
26
|
+
|
|
27
|
+
export default extension;
|
|
@@ -0,0 +1,146 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Proof that adding an OpenAI-compatible provider takes one registry entry.
|
|
3
|
+
*
|
|
4
|
+
* A fake `testgw` entry is added to the registry table
|
|
5
|
+
* (`packages/model-catalog/src/openai-compatible-providers.ts`) through
|
|
6
|
+
* `mock.module`, without editing any production file, and must reach
|
|
7
|
+
* credential routing, the opencode config, the pi models.json writer, the
|
|
8
|
+
* dashboard integration cards and the model picker groups.
|
|
9
|
+
*
|
|
10
|
+
* The mock is installed before any consumer below is imported (dynamic
|
|
11
|
+
* imports). Consumers cache derivations of the table at import (the
|
|
12
|
+
* integration cards, the picker constants), and `mock.module` cannot undo
|
|
13
|
+
* those, so this file only runs in its own `bun test` child process,
|
|
14
|
+
* spawned by `src/tests/openai-compatible-registry-extension.test.ts`. Its
|
|
15
|
+
* name keeps it out of the root test glob.
|
|
16
|
+
*/
|
|
17
|
+
import { afterAll, describe, expect, mock, test } from "bun:test";
|
|
18
|
+
import { mkdtemp, readFile, rm } from "node:fs/promises";
|
|
19
|
+
import { tmpdir } from "node:os";
|
|
20
|
+
import { join } from "node:path";
|
|
21
|
+
|
|
22
|
+
const REGISTRY_MODULE = "../../../packages/model-catalog/src/openai-compatible-providers.ts";
|
|
23
|
+
|
|
24
|
+
const TESTGW = {
|
|
25
|
+
id: "testgw",
|
|
26
|
+
displayName: "TestGW",
|
|
27
|
+
description: "A fake gateway for the registry extension test.",
|
|
28
|
+
baseUrl: "https://testgw.invalid/v1",
|
|
29
|
+
docsUrl: "https://testgw.invalid/docs",
|
|
30
|
+
envVar: "TESTGW_API_KEY",
|
|
31
|
+
aliasEnvVars: [],
|
|
32
|
+
catalog: "registry",
|
|
33
|
+
npm: "@ai-sdk/openai-compatible",
|
|
34
|
+
models: [
|
|
35
|
+
{
|
|
36
|
+
id: "m1",
|
|
37
|
+
name: "Test Model 1",
|
|
38
|
+
contextWindow: 131072,
|
|
39
|
+
maxOutputTokens: 8192,
|
|
40
|
+
input: ["text"],
|
|
41
|
+
reasoning: true,
|
|
42
|
+
compat: { supportsDeveloperRole: true },
|
|
43
|
+
reasoningEfforts: ["low", "high"],
|
|
44
|
+
},
|
|
45
|
+
],
|
|
46
|
+
} as const;
|
|
47
|
+
|
|
48
|
+
const real = (await import(REGISTRY_MODULE)) as {
|
|
49
|
+
OPENAI_COMPATIBLE_PROVIDERS: Record<string, unknown>;
|
|
50
|
+
};
|
|
51
|
+
const original = real.OPENAI_COMPATIBLE_PROVIDERS;
|
|
52
|
+
mock.module(REGISTRY_MODULE, () => ({
|
|
53
|
+
OPENAI_COMPATIBLE_PROVIDERS: { ...original, testgw: TESTGW },
|
|
54
|
+
}));
|
|
55
|
+
|
|
56
|
+
// The test preload already loaded `src/utils/credentials.ts`, so only its
|
|
57
|
+
// call-time paths (routing, pooling) see the mock; `CREDENTIAL_POOL_VARS` is
|
|
58
|
+
// fixed at import and covered by the registry spread in its declaration.
|
|
59
|
+
const { getModelAwareCredentialVars, resolveCredentialPools } = await import(
|
|
60
|
+
"../../utils/credentials"
|
|
61
|
+
);
|
|
62
|
+
const { applyProviderKeySplice } = await import("../../providers/opencode-adapter");
|
|
63
|
+
type OpencodeConfig = import("../../providers/opencode-adapter").OpencodeConfig;
|
|
64
|
+
const { writeSessionModelsJson } = await import("../../providers/pi-mono-adapter");
|
|
65
|
+
const { INTEGRATIONS } = await import("../../../apps/ui/src/lib/integrations-catalog");
|
|
66
|
+
const { modelGroupsForHarness } = await import("../../../apps/ui/src/lib/agent-runtime-models");
|
|
67
|
+
|
|
68
|
+
const tmpDirs: string[] = [];
|
|
69
|
+
afterAll(async () => {
|
|
70
|
+
for (const dir of tmpDirs) await rm(dir, { recursive: true, force: true });
|
|
71
|
+
});
|
|
72
|
+
|
|
73
|
+
describe("a new registry entry (testgw) reaches every derived surface", () => {
|
|
74
|
+
test("credential routing picks its key on pi and opencode", () => {
|
|
75
|
+
expect(getModelAwareCredentialVars("pi", "testgw/x")).toEqual(["TESTGW_API_KEY"]);
|
|
76
|
+
expect(getModelAwareCredentialVars("opencode", "testgw/x")).toEqual(["TESTGW_API_KEY"]);
|
|
77
|
+
});
|
|
78
|
+
|
|
79
|
+
test("its key pools alone for a testgw/* model", async () => {
|
|
80
|
+
const env: Record<string, string | undefined> = {
|
|
81
|
+
TESTGW_API_KEY: "example-gw-a,example-gw-b",
|
|
82
|
+
OPENROUTER_API_KEY: "example-or-a,example-or-b",
|
|
83
|
+
};
|
|
84
|
+
const selections = await resolveCredentialPools(env, { provider: "pi", model: "testgw/m1" });
|
|
85
|
+
expect(["example-gw-a", "example-gw-b"]).toContain(env.TESTGW_API_KEY ?? "");
|
|
86
|
+
expect(selections.map((s) => [s.keyType, s.total])).toEqual([["TESTGW_API_KEY", 2]]);
|
|
87
|
+
expect(env.OPENROUTER_API_KEY).toBe("example-or-a,example-or-b");
|
|
88
|
+
});
|
|
89
|
+
|
|
90
|
+
test("opencode gets a custom block with the picked key at the base URL", () => {
|
|
91
|
+
const config: OpencodeConfig = {};
|
|
92
|
+
const picked = applyProviderKeySplice(config, "testgw/m1", { TESTGW_API_KEY: "k1,k2" });
|
|
93
|
+
expect(picked).toEqual({ key: "k1", name: "TESTGW_API_KEY" });
|
|
94
|
+
const block = config.provider?.testgw;
|
|
95
|
+
expect(block?.npm).toBe("@ai-sdk/openai-compatible");
|
|
96
|
+
expect(block?.env).toEqual([]);
|
|
97
|
+
expect(block?.options).toEqual({ baseURL: "https://testgw.invalid/v1", apiKey: "k1" });
|
|
98
|
+
expect(block?.models?.m1).toMatchObject({ name: "Test Model 1", tool_call: true });
|
|
99
|
+
|
|
100
|
+
const overridden: OpencodeConfig = {};
|
|
101
|
+
applyProviderKeySplice(overridden, "testgw/m1", {
|
|
102
|
+
TESTGW_API_KEY: "k1",
|
|
103
|
+
TESTGW_BASE_URL: "http://127.0.0.1:9/v1",
|
|
104
|
+
});
|
|
105
|
+
expect(overridden.provider?.testgw?.options?.baseURL).toBe("http://127.0.0.1:9/v1");
|
|
106
|
+
});
|
|
107
|
+
|
|
108
|
+
test("the pi models.json writer adds its provider block", async () => {
|
|
109
|
+
const dir = await mkdtemp(join(tmpdir(), "testgw-pi-"));
|
|
110
|
+
tmpDirs.push(dir);
|
|
111
|
+
const models = await writeSessionModelsJson(dir, { TESTGW_API_KEY: "k1" }, "testgw/m1");
|
|
112
|
+
const written = JSON.parse(await readFile(models.modelsPath as string, "utf-8"));
|
|
113
|
+
await models.dispose();
|
|
114
|
+
expect(written.providers.testgw).toMatchObject({
|
|
115
|
+
baseUrl: "https://testgw.invalid/v1",
|
|
116
|
+
api: "openai-completions",
|
|
117
|
+
apiKey: "$TESTGW_API_KEY",
|
|
118
|
+
});
|
|
119
|
+
expect(written.providers.testgw.models.map((m: { id: string }) => m.id)).toEqual(["m1"]);
|
|
120
|
+
});
|
|
121
|
+
|
|
122
|
+
test("the dashboard lists an integration card with its key and base URL", () => {
|
|
123
|
+
const card = INTEGRATIONS.find((def) => def.id === "testgw");
|
|
124
|
+
expect(card?.name).toBe("TestGW");
|
|
125
|
+
expect(card?.providerIconKey as string | undefined).toBe("testgw");
|
|
126
|
+
expect(card?.fields.map((f) => [f.key, f.isSecret])).toEqual([
|
|
127
|
+
["TESTGW_API_KEY", true],
|
|
128
|
+
["TESTGW_BASE_URL", false],
|
|
129
|
+
]);
|
|
130
|
+
});
|
|
131
|
+
|
|
132
|
+
test("the picker shows its models, locked without a key and enabled with one", () => {
|
|
133
|
+
for (const harness of ["pi", "opencode"] as const) {
|
|
134
|
+
const locked = modelGroupsForHarness(harness, [], {}).find((g) => g.provider === "TestGW");
|
|
135
|
+
expect(locked?.enabled).toBe(false);
|
|
136
|
+
expect(locked?.requiredKey).toBe("TESTGW_API_KEY");
|
|
137
|
+
expect(locked?.models.map((m) => m.id)).toEqual(["testgw/m1"]);
|
|
138
|
+
expect(locked?.models[0]?.reasoningLevels).toEqual(["low", "high"]);
|
|
139
|
+
|
|
140
|
+
const enabled = modelGroupsForHarness(harness, [], { TESTGW_API_KEY: true }).find(
|
|
141
|
+
(g) => g.provider === "TestGW",
|
|
142
|
+
);
|
|
143
|
+
expect(enabled?.enabled).toBe(true);
|
|
144
|
+
}
|
|
145
|
+
});
|
|
146
|
+
});
|