@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
|
@@ -0,0 +1,426 @@
|
|
|
1
|
+
import type { Adapter, Message, Thread } from "chat";
|
|
2
|
+
import { z } from "zod";
|
|
3
|
+
import {
|
|
4
|
+
getAgentById,
|
|
5
|
+
getAllAgents,
|
|
6
|
+
getLatestActiveTaskByContextKey,
|
|
7
|
+
getLatestTaskByContextKey,
|
|
8
|
+
getLeadAgent,
|
|
9
|
+
isExtensionAgent,
|
|
10
|
+
} from "@/be/db";
|
|
11
|
+
import { dispatchPre } from "@/extensions/dispatcher";
|
|
12
|
+
import { resolveTemplate } from "@/prompts/resolver";
|
|
13
|
+
import { chatSdkContextKey } from "@/tasks/context-key";
|
|
14
|
+
import { TaskCreationBlockedError } from "@/tasks/errors";
|
|
15
|
+
import type { CreateTaskOptions } from "@/types";
|
|
16
|
+
import { scrubSecrets } from "@/utils/secret-scrubber";
|
|
17
|
+
import { getExecutorRegistry } from "@/workflows";
|
|
18
|
+
import { workflowEventBus } from "@/workflows/event-bus";
|
|
19
|
+
import { handleWebhookTrigger } from "@/workflows/triggers";
|
|
20
|
+
import {
|
|
21
|
+
buildChatAttachmentText,
|
|
22
|
+
createChatTaskWithFiles,
|
|
23
|
+
fetchChatFiles,
|
|
24
|
+
toChatFiles,
|
|
25
|
+
} from "./attachments";
|
|
26
|
+
import { getChatThreadResetAt } from "./commands";
|
|
27
|
+
import { errorMessage } from "./logger";
|
|
28
|
+
import { emitChatMessageSent } from "./outbound";
|
|
29
|
+
import {
|
|
30
|
+
type ChatAgentMatch,
|
|
31
|
+
type ChatRouteTarget,
|
|
32
|
+
routeChatMessage,
|
|
33
|
+
stripRoutingTokens,
|
|
34
|
+
} from "./router";
|
|
35
|
+
import { getChatInstance } from "./runtime";
|
|
36
|
+
import { readPersistedThreadHistory } from "./state";
|
|
37
|
+
import { formatChatSteeringAck, requestChatThreadSteering } from "./steering";
|
|
38
|
+
import {
|
|
39
|
+
bufferChatMessage,
|
|
40
|
+
type ChatBufferedMessage,
|
|
41
|
+
instantFlushChat,
|
|
42
|
+
isChatThreadBuffered,
|
|
43
|
+
} from "./thread-buffer";
|
|
44
|
+
import { startTypingKeepalive, stopTypingKeepalive } from "./typing";
|
|
45
|
+
// Side-effect import: registers the Chat SDK prompt templates.
|
|
46
|
+
import "./templates";
|
|
47
|
+
|
|
48
|
+
export type ChatInboundKind = "mention" | "dm" | "subscribed";
|
|
49
|
+
|
|
50
|
+
/** Runtime check for `pre.chat.route` modifications (mirrors `ChatRouteModify`). */
|
|
51
|
+
export const ChatRouteModifySchema = z
|
|
52
|
+
.object({
|
|
53
|
+
target: z.discriminatedUnion("kind", [
|
|
54
|
+
z.object({ kind: z.literal("agent"), agentId: z.string().min(1) }).strict(),
|
|
55
|
+
z.object({ kind: z.literal("lead") }).strict(),
|
|
56
|
+
z.object({ kind: z.literal("broadcast") }).strict(),
|
|
57
|
+
z.object({ kind: z.literal("workflow"), workflowId: z.string().min(1) }).strict(),
|
|
58
|
+
]),
|
|
59
|
+
})
|
|
60
|
+
.strict();
|
|
61
|
+
|
|
62
|
+
const THREAD_CONTEXT_LIMIT = 20;
|
|
63
|
+
const AGENT_MESSAGE_TRUNCATE = 500;
|
|
64
|
+
|
|
65
|
+
function escapeRegex(value: string): string {
|
|
66
|
+
return value.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
/** Remove the bot's own `@name` / `<@id>` mention; other mentions stay. */
|
|
70
|
+
function stripBotMention(text: string, adapter: Adapter): string {
|
|
71
|
+
let out = text;
|
|
72
|
+
if (adapter.userName) {
|
|
73
|
+
out = out.replace(new RegExp(`(?<!\\w)@${escapeRegex(adapter.userName)}(?![\\w-])`, "gi"), "");
|
|
74
|
+
}
|
|
75
|
+
if (adapter.botUserId) {
|
|
76
|
+
out = out.replace(new RegExp(`<@${escapeRegex(adapter.botUserId)}>`, "g"), "");
|
|
77
|
+
}
|
|
78
|
+
return out.trim();
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
/** The adapter name is the thread id's first segment; the rest of the id is opaque. */
|
|
82
|
+
function adapterOf(threadId: string): string {
|
|
83
|
+
const i = threadId.indexOf(":");
|
|
84
|
+
return i === -1 ? threadId : threadId.slice(0, i);
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
/**
|
|
88
|
+
* Entry point for every human message the SDK hands to the swarm. Steering is
|
|
89
|
+
* tried whatever the handler kind: the SDK routes a DM to `onDirectMessage`
|
|
90
|
+
* even when its thread is subscribed. Everything else goes through the buffer.
|
|
91
|
+
*/
|
|
92
|
+
export async function handleChatInbound(
|
|
93
|
+
thread: Thread,
|
|
94
|
+
message: Message,
|
|
95
|
+
kind: ChatInboundKind,
|
|
96
|
+
): Promise<void> {
|
|
97
|
+
const adapter = thread.adapter.name;
|
|
98
|
+
workflowEventBus.emit("chat.message.received", {
|
|
99
|
+
adapter,
|
|
100
|
+
channelId: thread.channelId,
|
|
101
|
+
threadId: thread.id,
|
|
102
|
+
userId: message.author.userId,
|
|
103
|
+
text: message.text,
|
|
104
|
+
messageId: message.id,
|
|
105
|
+
});
|
|
106
|
+
|
|
107
|
+
let text = stripBotMention(message.text ?? "", thread.adapter);
|
|
108
|
+
const immediate = /^!now\b/i.test(text);
|
|
109
|
+
if (immediate) text = text.replace(/^!now\s*/i, "").trim();
|
|
110
|
+
const attachments = message.attachments ?? [];
|
|
111
|
+
|
|
112
|
+
// A thread with messages already waiting keeps its order: append, do not steer past them.
|
|
113
|
+
if (!immediate && attachments.length === 0 && text && !isChatThreadBuffered(thread.id)) {
|
|
114
|
+
const steering = await requestChatThreadSteering({
|
|
115
|
+
contextKey: chatSdkContextKey(thread.id),
|
|
116
|
+
message: text,
|
|
117
|
+
});
|
|
118
|
+
if (steering) {
|
|
119
|
+
console.log(
|
|
120
|
+
`[chat-sdk] ${adapter} message steered task ${steering.task.id} (${steering.result.outcome})`,
|
|
121
|
+
);
|
|
122
|
+
try {
|
|
123
|
+
const ack = formatChatSteeringAck(steering.result);
|
|
124
|
+
const sent = await thread.post({ markdown: ack });
|
|
125
|
+
emitChatMessageSent({
|
|
126
|
+
adapter,
|
|
127
|
+
channelId: thread.channelId,
|
|
128
|
+
threadId: thread.id,
|
|
129
|
+
text: ack,
|
|
130
|
+
taskId: steering.task.id,
|
|
131
|
+
messageId: sent.id,
|
|
132
|
+
});
|
|
133
|
+
// The acknowledgement is the reply to this message.
|
|
134
|
+
stopTypingKeepalive(thread.id);
|
|
135
|
+
} catch (error) {
|
|
136
|
+
console.warn(
|
|
137
|
+
`[chat-sdk] Could not post the steering acknowledgement: ${errorMessage(error)}`,
|
|
138
|
+
);
|
|
139
|
+
}
|
|
140
|
+
return;
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
if (text || attachments.length > 0) {
|
|
145
|
+
bufferChatMessage(thread.id, {
|
|
146
|
+
messageId: message.id,
|
|
147
|
+
channelId: thread.channelId,
|
|
148
|
+
text,
|
|
149
|
+
userId: message.author.userId,
|
|
150
|
+
userName: message.author.userName || undefined,
|
|
151
|
+
botMentioned: kind === "mention" || message.isMention === true,
|
|
152
|
+
isDirectMessage: kind === "dm" || thread.isDM,
|
|
153
|
+
attachments,
|
|
154
|
+
});
|
|
155
|
+
// Subscribed-thread messages may be dropped by the router at flush time;
|
|
156
|
+
// they get typing once a task exists (see flushChatThread).
|
|
157
|
+
if (kind !== "subscribed") startTypingKeepalive(adapter, thread.id);
|
|
158
|
+
}
|
|
159
|
+
if (immediate) await instantFlushChat(thread.id);
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
/**
|
|
163
|
+
* Earlier messages of the thread for the task description, minus the flushed
|
|
164
|
+
* ones and anything before the last `/reset`. The adapter's view is merged with
|
|
165
|
+
* the history the SDK persisted: Telegram answers from process memory, which an
|
|
166
|
+
* adapter restart (API restart, config reload) empties.
|
|
167
|
+
*/
|
|
168
|
+
async function getChatThreadContext(
|
|
169
|
+
thread: Thread,
|
|
170
|
+
exclude: ReadonlySet<string>,
|
|
171
|
+
since: Date | null,
|
|
172
|
+
): Promise<string> {
|
|
173
|
+
try {
|
|
174
|
+
await thread.refresh();
|
|
175
|
+
const byId = new Map<string, Message>();
|
|
176
|
+
for (const m of await readPersistedThreadHistory(thread.id)) byId.set(m.id, m);
|
|
177
|
+
for (const m of thread.recentMessages) byId.set(m.id, m);
|
|
178
|
+
return [...byId.values()]
|
|
179
|
+
.sort((a, b) => a.metadata.dateSent.getTime() - b.metadata.dateSent.getTime())
|
|
180
|
+
.filter((m) => !exclude.has(m.id) && m.text.trim() !== "")
|
|
181
|
+
.filter((m) => since === null || m.metadata.dateSent.getTime() > since.getTime())
|
|
182
|
+
.slice(-THREAD_CONTEXT_LIMIT)
|
|
183
|
+
.map((m) => {
|
|
184
|
+
if (m.author.isMe || m.author.isBot === true) {
|
|
185
|
+
const truncated =
|
|
186
|
+
m.text.length > AGENT_MESSAGE_TRUNCATE
|
|
187
|
+
? `${m.text.slice(0, AGENT_MESSAGE_TRUNCATE)}...`
|
|
188
|
+
: m.text;
|
|
189
|
+
return `[Agent]: ${truncated}`;
|
|
190
|
+
}
|
|
191
|
+
return `${m.author.userName || m.author.fullName || m.author.userId}: ${m.text}`;
|
|
192
|
+
})
|
|
193
|
+
.join("\n");
|
|
194
|
+
} catch (error) {
|
|
195
|
+
console.warn(`[chat-sdk] Could not read thread context: ${errorMessage(error)}`);
|
|
196
|
+
return "";
|
|
197
|
+
}
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
/** Apply a `pre.chat.route` modification, or null to fall back to the built-in router. */
|
|
201
|
+
async function targetFromExtension(
|
|
202
|
+
target: z.infer<typeof ChatRouteModifySchema>["target"],
|
|
203
|
+
): Promise<ChatRouteTarget | null> {
|
|
204
|
+
switch (target.kind) {
|
|
205
|
+
case "workflow":
|
|
206
|
+
return { kind: "workflow", workflowId: target.workflowId };
|
|
207
|
+
case "broadcast": {
|
|
208
|
+
const matches = (await getAllAgents())
|
|
209
|
+
.filter((agent) => !agent.isLead)
|
|
210
|
+
.map(
|
|
211
|
+
(agent): ChatAgentMatch => ({ agent, reason: "human_pinned", matchedText: "extension" }),
|
|
212
|
+
);
|
|
213
|
+
if (matches.length === 0) {
|
|
214
|
+
console.warn("[chat-sdk] Extension broadcast target matched no agents; message dropped");
|
|
215
|
+
}
|
|
216
|
+
return { kind: "agents", matches };
|
|
217
|
+
}
|
|
218
|
+
case "lead": {
|
|
219
|
+
const lead = await getLeadAgent();
|
|
220
|
+
if (lead) {
|
|
221
|
+
return {
|
|
222
|
+
kind: "agents",
|
|
223
|
+
matches: [{ agent: lead, reason: "human_pinned", matchedText: "extension" }],
|
|
224
|
+
};
|
|
225
|
+
}
|
|
226
|
+
console.warn("[chat-sdk] Extension selected the lead, but no lead exists. Using the router.");
|
|
227
|
+
return null;
|
|
228
|
+
}
|
|
229
|
+
case "agent": {
|
|
230
|
+
const agent = await getAgentById(target.agentId);
|
|
231
|
+
if (agent && !isExtensionAgent(agent)) {
|
|
232
|
+
return {
|
|
233
|
+
kind: "agents",
|
|
234
|
+
matches: [{ agent, reason: "human_pinned", matchedText: "extension" }],
|
|
235
|
+
};
|
|
236
|
+
}
|
|
237
|
+
console.warn(
|
|
238
|
+
scrubSecrets(
|
|
239
|
+
`[chat-sdk] Extension selected ${agent ? "an extension-identity" : "an unknown"} agent ${target.agentId}. Using the router.`,
|
|
240
|
+
),
|
|
241
|
+
);
|
|
242
|
+
return null;
|
|
243
|
+
}
|
|
244
|
+
}
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
/**
|
|
248
|
+
* Buffer flush: one routed decision for every message the thread collected.
|
|
249
|
+
* Runs outside any transaction, so `pre.chat.route` is dispatched here, after
|
|
250
|
+
* the flush and before any task exists.
|
|
251
|
+
*/
|
|
252
|
+
export async function flushChatThread(
|
|
253
|
+
threadId: string,
|
|
254
|
+
items: ChatBufferedMessage[],
|
|
255
|
+
immediate: boolean,
|
|
256
|
+
): Promise<void> {
|
|
257
|
+
const first = items[0];
|
|
258
|
+
if (!first) return;
|
|
259
|
+
const adapter = adapterOf(threadId);
|
|
260
|
+
const contextKey = chatSdkContextKey(threadId);
|
|
261
|
+
const thread = getChatInstance(adapter)?.thread(threadId);
|
|
262
|
+
const rawText = items
|
|
263
|
+
.map((item) => item.text)
|
|
264
|
+
.filter(Boolean)
|
|
265
|
+
.join("\n---\n");
|
|
266
|
+
const botMentioned = items.some((item) => item.botMentioned);
|
|
267
|
+
const isDirectMessage = items.some((item) => item.isDirectMessage);
|
|
268
|
+
|
|
269
|
+
// After `/reset` the thread's earlier tasks are not context: the new task gets
|
|
270
|
+
// no parent and no thread history older than the reset.
|
|
271
|
+
const resetAt = await getChatThreadResetAt(threadId);
|
|
272
|
+
const previousTask = await getLatestTaskByContextKey(contextKey);
|
|
273
|
+
const mostRecentTask =
|
|
274
|
+
previousTask && resetAt && new Date(previousTask.createdAt).getTime() <= resetAt.getTime()
|
|
275
|
+
? null
|
|
276
|
+
: previousTask;
|
|
277
|
+
const routeResult = await dispatchPre(
|
|
278
|
+
"pre.chat.route",
|
|
279
|
+
{
|
|
280
|
+
adapter,
|
|
281
|
+
channelId: first.channelId,
|
|
282
|
+
threadId,
|
|
283
|
+
userId: first.userId,
|
|
284
|
+
...(first.userName ? { userName: first.userName } : {}),
|
|
285
|
+
text: rawText,
|
|
286
|
+
botMentioned,
|
|
287
|
+
isDirectMessage,
|
|
288
|
+
...(mostRecentTask ? { threadContext: { adapter, threadId } } : {}),
|
|
289
|
+
},
|
|
290
|
+
{
|
|
291
|
+
// A runtime-invalid target is an extension failure, not a routing input.
|
|
292
|
+
validateModify: (data) => {
|
|
293
|
+
const parsed = ChatRouteModifySchema.safeParse(data);
|
|
294
|
+
return parsed.success
|
|
295
|
+
? { success: true, data: parsed.data }
|
|
296
|
+
: { success: false, error: new Error(parsed.error.message) };
|
|
297
|
+
},
|
|
298
|
+
},
|
|
299
|
+
);
|
|
300
|
+
if (routeResult.action === "block") {
|
|
301
|
+
console.info(
|
|
302
|
+
`[chat-sdk] Extension blocked routing for ${threadId}:`,
|
|
303
|
+
scrubSecrets(routeResult.reason),
|
|
304
|
+
);
|
|
305
|
+
stopTypingKeepalive(threadId);
|
|
306
|
+
return;
|
|
307
|
+
}
|
|
308
|
+
|
|
309
|
+
let target: ChatRouteTarget | null = null;
|
|
310
|
+
if (routeResult.action === "modify") target = await targetFromExtension(routeResult.data.target);
|
|
311
|
+
target ??= await routeChatMessage(rawText, {
|
|
312
|
+
adapter,
|
|
313
|
+
channelId: first.channelId,
|
|
314
|
+
threadId,
|
|
315
|
+
botMentioned,
|
|
316
|
+
isDirectMessage,
|
|
317
|
+
});
|
|
318
|
+
if (!target) {
|
|
319
|
+
console.log(`[chat-sdk] ${threadId}: message not addressed to the swarm, ignored`);
|
|
320
|
+
stopTypingKeepalive(threadId);
|
|
321
|
+
return;
|
|
322
|
+
}
|
|
323
|
+
|
|
324
|
+
if (target.kind === "workflow") {
|
|
325
|
+
// A workflow run has no relay that would stop the indicator.
|
|
326
|
+
stopTypingKeepalive(threadId);
|
|
327
|
+
try {
|
|
328
|
+
// The adapter verified the platform signature, and the workflow id comes
|
|
329
|
+
// from an operator mapping or an extension, never from the message.
|
|
330
|
+
const { runId } = await handleWebhookTrigger(
|
|
331
|
+
target.workflowId,
|
|
332
|
+
{
|
|
333
|
+
adapter,
|
|
334
|
+
channelId: first.channelId,
|
|
335
|
+
threadId,
|
|
336
|
+
contextKey,
|
|
337
|
+
userId: first.userId,
|
|
338
|
+
userName: first.userName,
|
|
339
|
+
text: rawText,
|
|
340
|
+
messageIds: items.map((item) => item.messageId),
|
|
341
|
+
},
|
|
342
|
+
{},
|
|
343
|
+
getExecutorRegistry(),
|
|
344
|
+
{ alreadyAuthenticated: true },
|
|
345
|
+
);
|
|
346
|
+
console.log(`[chat-sdk] ${threadId}: started workflow run ${runId}`);
|
|
347
|
+
await subscribe(thread);
|
|
348
|
+
} catch (error) {
|
|
349
|
+
console.error(`[chat-sdk] ${threadId}: workflow dispatch failed: ${errorMessage(error)}`);
|
|
350
|
+
}
|
|
351
|
+
return;
|
|
352
|
+
}
|
|
353
|
+
|
|
354
|
+
const body = stripRoutingTokens(rawText);
|
|
355
|
+
const files = items.flatMap((item) => toChatFiles(adapter, item.messageId, item.attachments));
|
|
356
|
+
await using inbound = await fetchChatFiles(files);
|
|
357
|
+
const fileText = buildChatAttachmentText(inbound.files, inbound.failed);
|
|
358
|
+
const combined = [body, fileText].filter(Boolean).join("\n\n");
|
|
359
|
+
const marked =
|
|
360
|
+
items.length > 1
|
|
361
|
+
? `[Thread follow-up: ${items.length} message(s) buffered]\n\n${combined}`
|
|
362
|
+
: combined;
|
|
363
|
+
const threadContext =
|
|
364
|
+
mostRecentTask && thread
|
|
365
|
+
? await getChatThreadContext(thread, new Set(items.map((item) => item.messageId)), resetAt)
|
|
366
|
+
: "";
|
|
367
|
+
const description = threadContext
|
|
368
|
+
? `${resolveTemplate("chat.message.thread_context", { thread_messages: threadContext }).text}\n\n${marked}`
|
|
369
|
+
: marked;
|
|
370
|
+
|
|
371
|
+
const latestActive = await getLatestActiveTaskByContextKey(contextKey, "chat");
|
|
372
|
+
const dependsOn = !immediate && latestActive ? [latestActive.id] : undefined;
|
|
373
|
+
let created = 0;
|
|
374
|
+
for (const match of target.matches) {
|
|
375
|
+
const options: CreateTaskOptions = {
|
|
376
|
+
agentId: match.agent?.id ?? null,
|
|
377
|
+
routingReason: match.agent ? match.reason : undefined,
|
|
378
|
+
routingSource: match.agent ? "engine_default" : undefined,
|
|
379
|
+
source: "chat",
|
|
380
|
+
taskType: "chat-inbound",
|
|
381
|
+
tags: ["chat-sdk", adapter],
|
|
382
|
+
priority: 70,
|
|
383
|
+
contextKey,
|
|
384
|
+
dependsOn,
|
|
385
|
+
parentTaskId: mostRecentTask?.id,
|
|
386
|
+
// A human message in the thread: never answer it in the previous task's
|
|
387
|
+
// structured-output contract.
|
|
388
|
+
inheritParentOutputSchema: false,
|
|
389
|
+
};
|
|
390
|
+
try {
|
|
391
|
+
const { task, unattached } = await createChatTaskWithFiles(description, options, inbound);
|
|
392
|
+
created++;
|
|
393
|
+
console.log(
|
|
394
|
+
`[chat-sdk] ${threadId}: task ${task.id} for ${match.agent ? `agent ${match.agent.id}` : "no agent (queued)"} (${match.matchedText}, dependsOn: ${dependsOn?.join(", ") ?? "none"}${unattached.length ? `, ${unattached.length} file(s) not attached` : ""})`,
|
|
395
|
+
);
|
|
396
|
+
} catch (error) {
|
|
397
|
+
if (error instanceof TaskCreationBlockedError) {
|
|
398
|
+
console.info(
|
|
399
|
+
`[chat-sdk] ${threadId}: task creation blocked: ${scrubSecrets(error.reason)}`,
|
|
400
|
+
);
|
|
401
|
+
} else {
|
|
402
|
+
console.error(`[chat-sdk] ${threadId}: task creation failed: ${errorMessage(error)}`);
|
|
403
|
+
}
|
|
404
|
+
}
|
|
405
|
+
}
|
|
406
|
+
if (created > 0) {
|
|
407
|
+
await subscribe(thread);
|
|
408
|
+
// Subscribed-thread messages start typing only here, once a task exists.
|
|
409
|
+
startTypingKeepalive(adapter, threadId);
|
|
410
|
+
} else {
|
|
411
|
+
stopTypingKeepalive(threadId);
|
|
412
|
+
}
|
|
413
|
+
}
|
|
414
|
+
|
|
415
|
+
/**
|
|
416
|
+
* Subscribe so plain replies reach `onSubscribedMessage`. Threads stay
|
|
417
|
+
* subscribed after a terminal task: a later reply is a follow-up.
|
|
418
|
+
*/
|
|
419
|
+
async function subscribe(thread: Thread | undefined): Promise<void> {
|
|
420
|
+
if (!thread) return;
|
|
421
|
+
try {
|
|
422
|
+
if (!(await thread.isSubscribed())) await thread.subscribe();
|
|
423
|
+
} catch (error) {
|
|
424
|
+
console.warn(`[chat-sdk] Could not subscribe to ${thread.id}: ${errorMessage(error)}`);
|
|
425
|
+
}
|
|
426
|
+
}
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Per-adapter message length limits, in UTF-16 code units (what the platforms
|
|
3
|
+
* count). Outbound text longer than the limit is split into several messages.
|
|
4
|
+
*/
|
|
5
|
+
const CHAT_MESSAGE_LIMITS: Record<string, number> = {
|
|
6
|
+
telegram: 4096,
|
|
7
|
+
discord: 2000,
|
|
8
|
+
// Slack messages use mrkdwn, not markdown. The SDK posts our markdown as
|
|
9
|
+
// `markdown_text` (Slack converts it, 12000 character cap). 4000 is Slack's
|
|
10
|
+
// guidance for one readable message and stays well under that cap.
|
|
11
|
+
slack: 4000,
|
|
12
|
+
teams: 20000,
|
|
13
|
+
};
|
|
14
|
+
|
|
15
|
+
/** Used for an adapter without an entry: the smallest known limit. */
|
|
16
|
+
const FALLBACK_LIMIT = 2000;
|
|
17
|
+
|
|
18
|
+
export function chatMessageLimit(adapter: string): number {
|
|
19
|
+
return CHAT_MESSAGE_LIMITS[adapter] ?? FALLBACK_LIMIT;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* Split `text` into chunks of at most `limit` characters. A chunk ends at the
|
|
24
|
+
* last line break inside the window, else at the last space, else at the
|
|
25
|
+
* limit. Breaks in the first half of the window are ignored, so chunks never
|
|
26
|
+
* get very short.
|
|
27
|
+
*/
|
|
28
|
+
export function splitChatMessage(text: string, limit: number): string[] {
|
|
29
|
+
if (limit <= 0) throw new Error("splitChatMessage: limit must be positive");
|
|
30
|
+
const chunks: string[] = [];
|
|
31
|
+
let rest = text;
|
|
32
|
+
while (rest.length > limit) {
|
|
33
|
+
const window = rest.slice(0, limit);
|
|
34
|
+
const minBreak = Math.floor(limit / 2);
|
|
35
|
+
let cut = window.lastIndexOf("\n");
|
|
36
|
+
if (cut < minBreak) cut = window.lastIndexOf(" ");
|
|
37
|
+
if (cut < minBreak) {
|
|
38
|
+
cut = limit;
|
|
39
|
+
// Never split a surrogate pair (emoji and other astral characters).
|
|
40
|
+
const code = rest.charCodeAt(cut - 1);
|
|
41
|
+
if (code >= 0xd800 && code <= 0xdbff) cut -= 1;
|
|
42
|
+
}
|
|
43
|
+
chunks.push(rest.slice(0, cut).trimEnd());
|
|
44
|
+
rest = rest.slice(cut).replace(/^[\n ]+/, "");
|
|
45
|
+
}
|
|
46
|
+
if (rest.length > 0 || chunks.length === 0) chunks.push(rest);
|
|
47
|
+
return chunks.filter((chunk, index) => chunk.length > 0 || index === 0);
|
|
48
|
+
}
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
import type { Logger } from "chat";
|
|
2
|
+
import { scrubObject, scrubSecrets } from "@/utils/secret-scrubber";
|
|
3
|
+
|
|
4
|
+
/** Called for every warn/error line, already scrubbed. Feeds adapter health. */
|
|
5
|
+
export type ChatLogIssueSink = (level: "warn" | "error", message: string) => void;
|
|
6
|
+
|
|
7
|
+
/** Bound on the issue text handed to the sink (adapter health `lastWarning`). */
|
|
8
|
+
const MAX_ISSUE_CHARS = 300;
|
|
9
|
+
|
|
10
|
+
function scrubArg(arg: unknown): unknown {
|
|
11
|
+
if (arg instanceof Error) return scrubSecrets(`${arg.name}: ${arg.message}`);
|
|
12
|
+
return scrubObject(arg);
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
function describeCause(value: unknown): string {
|
|
16
|
+
return value instanceof Error ? `${value.name}: ${value.message}` : String(value);
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* The cause of a warn/error line: an `Error` argument, or the `error` / `err`
|
|
21
|
+
* field of an object argument (adapters log `{ error: String(error) }`).
|
|
22
|
+
*/
|
|
23
|
+
function causeOf(args: unknown[]): string | undefined {
|
|
24
|
+
for (const arg of args) {
|
|
25
|
+
if (arg instanceof Error) return describeCause(arg);
|
|
26
|
+
if (arg && typeof arg === "object") {
|
|
27
|
+
const record = arg as Record<string, unknown>;
|
|
28
|
+
const value = record.error ?? record.err;
|
|
29
|
+
if (value !== undefined && value !== null) return describeCause(value);
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
return undefined;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
/**
|
|
36
|
+
* SDK `Logger` that scrubs every argument before it reaches the console. The
|
|
37
|
+
* Chat SDK has no egress hook like `installSlackEgressScrub`, so the logger is
|
|
38
|
+
* the scrub point for everything the SDK and its adapters print.
|
|
39
|
+
*/
|
|
40
|
+
export function createScrubbingLogger(prefix: string, onIssue?: ChatLogIssueSink): Logger {
|
|
41
|
+
const tag = `[chat-sdk:${prefix}]`;
|
|
42
|
+
const emit = (level: "info" | "warn" | "error", message: string, args: unknown[]): void => {
|
|
43
|
+
const scrubbedMessage = scrubSecrets(message);
|
|
44
|
+
const scrubbedArgs = args.map(scrubArg);
|
|
45
|
+
if (onIssue && (level === "warn" || level === "error")) {
|
|
46
|
+
const cause = causeOf(args);
|
|
47
|
+
const issue = cause ? `${scrubbedMessage}: ${scrubSecrets(cause)}` : scrubbedMessage;
|
|
48
|
+
onIssue(level, issue.slice(0, MAX_ISSUE_CHARS));
|
|
49
|
+
}
|
|
50
|
+
console[level](tag, scrubbedMessage, ...scrubbedArgs);
|
|
51
|
+
};
|
|
52
|
+
return {
|
|
53
|
+
// Debug is dropped, matching the SDK's default ConsoleLogger("info") level.
|
|
54
|
+
debug: () => {},
|
|
55
|
+
info: (message, ...args) => emit("info", message, args),
|
|
56
|
+
warn: (message, ...args) => emit("warn", message, args),
|
|
57
|
+
error: (message, ...args) => emit("error", message, args),
|
|
58
|
+
child: (childPrefix) => createScrubbingLogger(`${prefix}:${childPrefix}`, onIssue),
|
|
59
|
+
};
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
/**
|
|
63
|
+
* Drop `info` lines for which `keep(message)` is false. Warn and error lines
|
|
64
|
+
* always pass. Child loggers keep the filter.
|
|
65
|
+
*/
|
|
66
|
+
export function withInfoFilter(logger: Logger, keep: (message: string) => boolean): Logger {
|
|
67
|
+
return {
|
|
68
|
+
debug: (message, ...args) => logger.debug(message, ...args),
|
|
69
|
+
info: (message, ...args) => {
|
|
70
|
+
if (keep(message)) logger.info(message, ...args);
|
|
71
|
+
},
|
|
72
|
+
warn: (message, ...args) => logger.warn(message, ...args),
|
|
73
|
+
error: (message, ...args) => logger.error(message, ...args),
|
|
74
|
+
child: (childPrefix) => withInfoFilter(logger.child(childPrefix), keep),
|
|
75
|
+
};
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
/** Error text for logs and tool results, scrubbed. */
|
|
79
|
+
export function errorMessage(err: unknown): string {
|
|
80
|
+
return scrubSecrets(err instanceof Error ? err.message : String(err));
|
|
81
|
+
}
|
|
@@ -0,0 +1,123 @@
|
|
|
1
|
+
import { scrubSecrets } from "@/utils/secret-scrubber";
|
|
2
|
+
import { workflowEventBus } from "@/workflows/event-bus";
|
|
3
|
+
import { chatMessageLimit, splitChatMessage } from "./limits";
|
|
4
|
+
import { getChatInstance } from "./runtime";
|
|
5
|
+
import { assertTrustedChatTarget } from "./teams-origin";
|
|
6
|
+
|
|
7
|
+
/** The adapter is not configured, failed to start, or is shutting down. */
|
|
8
|
+
export class ChatAdapterUnavailableError extends Error {
|
|
9
|
+
constructor(adapter: string) {
|
|
10
|
+
super(`Chat SDK adapter '${adapter}' is not running`);
|
|
11
|
+
this.name = "ChatAdapterUnavailableError";
|
|
12
|
+
}
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
export interface ChatSentEvent {
|
|
16
|
+
adapter: string;
|
|
17
|
+
channelId: string;
|
|
18
|
+
threadId: string;
|
|
19
|
+
text: string;
|
|
20
|
+
taskId?: string;
|
|
21
|
+
messageId?: string;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
/**
|
|
25
|
+
* Publish an outbound message on the bus. The post-bridge turns it into the
|
|
26
|
+
* `post.chat.message` extension event with `direction: "outbound"`.
|
|
27
|
+
*/
|
|
28
|
+
export function emitChatMessageSent(event: ChatSentEvent): void {
|
|
29
|
+
workflowEventBus.emit("chat.message.sent", { ...event, text: scrubSecrets(event.text) });
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
/** The adapter name is the first segment of an SDK thread or channel id. */
|
|
33
|
+
export function adapterOfChatId(id: string): string {
|
|
34
|
+
const index = id.indexOf(":");
|
|
35
|
+
return index === -1 ? id : id.slice(0, index);
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
export interface ChatPostResult {
|
|
39
|
+
adapter: string;
|
|
40
|
+
threadId: string;
|
|
41
|
+
channelId: string;
|
|
42
|
+
/** One id per chunk, in send order. */
|
|
43
|
+
messageIds: string[];
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
interface PostTarget {
|
|
47
|
+
post(message: { markdown: string }): Promise<{ id: string; threadId?: string }>;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
async function postChunks(
|
|
51
|
+
adapter: string,
|
|
52
|
+
target: PostTarget,
|
|
53
|
+
text: string,
|
|
54
|
+
): Promise<{ messageIds: string[]; threadId?: string }> {
|
|
55
|
+
const messageIds: string[] = [];
|
|
56
|
+
let threadId: string | undefined;
|
|
57
|
+
for (const chunk of splitChatMessage(text, chatMessageLimit(adapter))) {
|
|
58
|
+
const sent = await target.post({ markdown: chunk });
|
|
59
|
+
messageIds.push(sent.id);
|
|
60
|
+
threadId ??= sent.threadId;
|
|
61
|
+
}
|
|
62
|
+
return { messageIds, threadId };
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
/**
|
|
66
|
+
* Post markdown to a Chat SDK thread or channel. Scrubs the text, splits it by
|
|
67
|
+
* the adapter limit, and emits one `chat.message.sent` for the whole message
|
|
68
|
+
* once every chunk was accepted. Throws `ChatAdapterUnavailableError` when the
|
|
69
|
+
* adapter is not running, `UntrustedTeamsServiceUrlError` before any send
|
|
70
|
+
* when a Teams id points at a host that is not Bot Framework, and the adapter
|
|
71
|
+
* error when a send fails.
|
|
72
|
+
*
|
|
73
|
+
* Delivery is at-least-once: a chunk failure re-posts earlier chunks on retry,
|
|
74
|
+
* and a crash between the send and `markChatRelayDelivered` re-sends the
|
|
75
|
+
* message.
|
|
76
|
+
*/
|
|
77
|
+
export async function postChatMessage(args: {
|
|
78
|
+
adapter: string;
|
|
79
|
+
threadId?: string;
|
|
80
|
+
channelId?: string;
|
|
81
|
+
text: string;
|
|
82
|
+
taskId?: string;
|
|
83
|
+
}): Promise<ChatPostResult> {
|
|
84
|
+
// Ids come from tools and stored context keys. Check the Teams origin
|
|
85
|
+
// before the adapter sends its token.
|
|
86
|
+
assertTrustedChatTarget(args.threadId ?? args.channelId ?? "");
|
|
87
|
+
const chat = getChatInstance(args.adapter);
|
|
88
|
+
if (!chat) throw new ChatAdapterUnavailableError(args.adapter);
|
|
89
|
+
const text = scrubSecrets(args.text);
|
|
90
|
+
|
|
91
|
+
let result: ChatPostResult;
|
|
92
|
+
if (args.threadId) {
|
|
93
|
+
const thread = chat.thread(args.threadId);
|
|
94
|
+
const sent = await postChunks(args.adapter, thread, text);
|
|
95
|
+
result = {
|
|
96
|
+
adapter: args.adapter,
|
|
97
|
+
threadId: thread.id,
|
|
98
|
+
channelId: thread.channelId,
|
|
99
|
+
messageIds: sent.messageIds,
|
|
100
|
+
};
|
|
101
|
+
} else if (args.channelId) {
|
|
102
|
+
const channel = chat.channel(args.channelId);
|
|
103
|
+
const sent = await postChunks(args.adapter, channel, text);
|
|
104
|
+
result = {
|
|
105
|
+
adapter: args.adapter,
|
|
106
|
+
threadId: sent.threadId ?? channel.id,
|
|
107
|
+
channelId: channel.id,
|
|
108
|
+
messageIds: sent.messageIds,
|
|
109
|
+
};
|
|
110
|
+
} else {
|
|
111
|
+
throw new Error("postChatMessage needs a threadId or a channelId");
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
emitChatMessageSent({
|
|
115
|
+
adapter: result.adapter,
|
|
116
|
+
channelId: result.channelId,
|
|
117
|
+
threadId: result.threadId,
|
|
118
|
+
text,
|
|
119
|
+
taskId: args.taskId,
|
|
120
|
+
messageId: result.messageIds.at(-1),
|
|
121
|
+
});
|
|
122
|
+
return result;
|
|
123
|
+
}
|