@desplega.ai/agent-swarm 1.172.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-fpecx1g1.js → acp-adapter-sj2g0m84.js} +18 -17
- package/dist/{actions-pw87a1a8.js → actions-fegk44gr.js} +65 -35
- package/dist/{amp-adapter-8jem2bc0.js → amp-adapter-ad8ft89x.js} +8 -7
- package/dist/{anthropic-messages-8ee2f22q.js → anthropic-messages-r6czkjv1.js} +7 -7
- package/dist/{app-wmf0m843.js → app-kfvgbvzy.js} +19 -13
- package/dist/{assistant-emp1e108.js → assistant-z7zdy9ja.js} +65 -38
- package/dist/{boot-reembed-1de4f4aj.js → boot-reembed-5c2z0mbe.js} +11 -9
- package/dist/{boot-reembed-4ymn9atn.js → boot-reembed-j8xa181e.js} +11 -9
- package/dist/{boot-scrub-sweep-adhk1s8c.js → boot-scrub-sweep-hnkn3rgt.js} +12 -10
- package/dist/{boot-seeds-8q637aew.js → boot-seeds-kwjq4d2z.js} +15 -13
- package/dist/{claude-adapter-t26cv8a4.js → claude-adapter-9vmvpvgd.js} +20 -19
- 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-gsfsh662.js → claude-sdk-session-9jye6cxr.js} +12698 -11995
- package/dist/{cli-k46r9hmk.js → cli-03gedhng.js} +16 -14
- package/dist/{cli-kb4ehzx5.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-8vs9trd4.js → cli-3dj8k4vy.js} +1 -1
- package/dist/{cli-zxm9p3x4.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-6jb2rf77.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-pj6x9qce.js → cli-6jyj59nb.js} +2 -2
- package/dist/{cli-388egyf6.js → cli-6razj6t4.js} +2 -2
- package/dist/{cli-qsztpfry.js → cli-74ahz61x.js} +2 -2
- package/dist/{cli-4nz8p1p5.js → cli-7x0g4sn1.js} +15474 -16082
- 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-bf7485pm.js → cli-apx87jne.js} +2 -2
- package/dist/{cli-ztrv9y21.js → cli-bzszjf2p.js} +3689 -1081
- 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-1hgat6pa.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-j6kpj9az.js → cli-ee9gtz70.js} +1553 -68
- package/dist/{cli-e0a9sk00.js → cli-eejsgvm4.js} +4 -4
- package/dist/{cli-tgpfnm6r.js → cli-ef1hg1wv.js} +518 -59
- 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-4fnb6884.js → cli-hvqbtna4.js} +7 -1259
- package/dist/{cli-pstq9888.js → cli-j0pfptdp.js} +220 -103
- package/dist/cli-jta2eyzc.js +619 -0
- package/dist/{cli-esnpehf5.js → cli-k382tshb.js} +12 -13
- package/dist/cli-k3dp5tpf.js +263 -0
- package/dist/{cli-wd0hz6ah.js → cli-kbrt8gqa.js} +9 -91
- package/dist/cli-kceah1eh.js +92 -0
- package/dist/{cli-prrt9fa0.js → cli-ke896fcf.js} +10 -10
- package/dist/{cli-nv4zd0b9.js → cli-kt6zqg0e.js} +6 -6
- package/dist/{cli-g6t9texm.js → cli-m50t4309.js} +5 -18
- package/dist/cli-n2wq4j7m.js +6985 -0
- package/dist/{cli-c2pq2rjb.js → cli-n3hzg89b.js} +46 -15
- package/dist/{cli-tmhsxhb8.js → cli-n5vwg842.js} +23 -6
- package/dist/{cli-5rv1j9ga.js → cli-ne236afy.js} +53 -7
- package/dist/cli-p0sg24zp.js +16 -0
- package/dist/{cli-d161g69q.js → cli-p1x85d2t.js} +50 -2
- package/dist/{cli-7pz3b7x5.js → cli-przpmdgr.js} +25 -0
- package/dist/{cli-g4f5pj26.js → cli-q0ke1yww.js} +111 -25
- package/dist/{cli-e3pmyt39.js → cli-q50942rs.js} +1 -1
- package/dist/{cli-r44t9wqc.js → cli-r106725k.js} +2 -2
- package/dist/{cli-gj81e47t.js → cli-r2pnvj8g.js} +2 -2
- package/dist/{cli-r8smj8b8.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-nyhb1r6x.js → cli-s2sp957d.js} +52 -29
- package/dist/{cli-1x7da0de.js → cli-s4setq91.js} +8 -8
- package/dist/{cli-xczpdp9e.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-cf69s3ew.js → cli-tr4sxp68.js} +3 -3
- package/dist/{cli-8hzfb6xz.js → cli-tsde1hdr.js} +15 -15
- package/dist/{cli-cceskcvt.js → cli-txqb9s38.js} +8 -8
- package/dist/{cli-aspcaef3.js → cli-v1yb0rqb.js} +6 -6
- package/dist/{cli-j1pmc07z.js → cli-vatyhshy.js} +268 -40
- package/dist/{cli-04emfe2q.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-hwwvy36w.js → cli-wayw93nt.js} +3 -3
- package/dist/cli-wmgrkscj.js +23254 -0
- package/dist/{cli-gp1rtyvk.js → cli-z5jjpaxw.js} +29 -6
- package/dist/{cli-451ks6aw.js → cli-zkpks3r3.js} +1 -1
- package/dist/cli.js +32 -48
- package/dist/{codex-adapter-m4tgdysy.js → codex-adapter-y81sssk0.js} +16 -15
- package/dist/{codex-login-841kv5fk.js → codex-login-f2rcfpp9.js} +3 -2
- package/dist/{codex-session-runner-ymwzm6ea.js → codex-session-runner-8vx4j6a9.js} +16 -15
- package/dist/{commands-n0hshcvs.js → commands-kcpvpcrr.js} +21 -10
- package/dist/{controller-hq5ggpcp.js → controller-ywrmt6yd.js} +5 -4
- package/dist/{controllers-connect-y07cqcvy.js → controllers-connect-kwpvj3rf.js} +45 -38
- package/dist/{cursor-adapter-q8pgv5qa.js → cursor-adapter-cehv1qns.js} +7 -6
- package/dist/{db-hkqvrdcb.js → db-k8ankj20.js} +13 -11
- package/dist/{db-4w7tmt1g.js → db-ww5d7jq3.js} +29 -9
- package/dist/{devin-adapter-xgtj680j.js → devin-adapter-x1022qb1.js} +3 -2
- package/dist/{dispatcher-5sd69y14.js → dispatcher-e8gq5m68.js} +41 -34
- package/dist/{docker-f503k9p0.js → docker-ff6y1190.js} +3 -2
- package/dist/{dsh-adapter-2h8m4cab.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-h3359cwn.js +52 -0
- package/dist/{google-generative-ai-7pavrbv7.js → google-generative-ai-g8db7x0f.js} +5 -3
- package/dist/{google-vertex-3f1jk41f.js → google-vertex-r2692q0r.js} +5 -3
- package/dist/{grok-adapter-8m9yqzbq.js → grok-adapter-0d4x7gkx.js} +19 -18
- package/dist/{handlers-s20xr10r.js → handlers-an1gzv21.js} +44 -37
- package/dist/{heartbeat-3k31q4g2.js → heartbeat-6dakeb61.js} +46 -39
- package/dist/{hook-gdn6a2fn.js → hook-6v6cv6d5.js} +12 -24
- package/dist/{hook-ygwerrdh.js → hook-sa5xye4y.js} +13 -12
- package/dist/{http-jdteeg81.js → http-r7f3eky0.js} +1598 -279
- package/dist/index-52j1aja4.js +3465 -0
- package/dist/{index-e0snym4t.js → index-7ep4gzdk.js} +41 -34
- 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-vm3bm9t7.js → index-rmpw9a8q.js} +41 -34
- package/dist/{index-6ksewd5y.js → index-s2wxgzr0.js} +1015 -732
- package/dist/{index-tsszt7g9.js → index-v1v6n7hg.js} +46 -39
- package/dist/index-we27kd31.js +12810 -0
- package/dist/{index-ffqzqgwb.js → index-zk96aq1k.js} +11 -9
- package/dist/{keepalive-jekc896t.js → keepalive-be9vwy5h.js} +13 -11
- package/dist/{lead-4k9s9szx.js → lead-0861cwrf.js} +64 -57
- package/dist/{maintenance-shazch4n.js → maintenance-mzzgc8bn.js} +11 -9
- package/dist/node-c9we95xf.js +1 -1
- package/dist/{node-fkajaq96.js → node-mn1zs7an.js} +2 -1
- package/dist/{oauth-refresh-sweep-5gb36kn8.js → oauth-refresh-sweep-cpdnfwdj.js} +12 -10
- package/dist/{onboard-ydrbnvww.js → onboard-fh8qs4r3.js} +11 -3
- package/dist/{opencode-adapter-5zh0he20.js → opencode-adapter-nm42wwj8.js} +19 -14
- package/dist/{otel-impl-6pf5cv86.js → otel-impl-x6c1c9g4.js} +248 -869
- package/dist/{pi-mono-adapter-ef9wwx9m.js → pi-mono-adapter-9n2ya240.js} +245 -107
- package/dist/{pricing-refresh-s52nsa5q.js → pricing-refresh-qb0rv0ap.js} +13 -11
- package/dist/{rbac-roles-aq3msh4e.js → rbac-roles-1gzdwbh1.js} +12 -10
- package/dist/{rbac-roles-q5gb5e8v.js → rbac-roles-3mraey52.js} +13 -11
- package/dist/{render-v2-pd1rg54q.js → render-v2-agpa45zz.js} +19 -13
- package/dist/{resume-gw4a2c6v.js → resume-x0ydy9s9.js} +27 -21
- package/dist/{runtime-qfry88v2.js → runtime-sa2vr16g.js} +2 -2
- package/dist/{setup-jda1ksdh.js → setup-as9ga0d6.js} +2 -2
- package/dist/{virtual-modules-b3ga2m65.js → virtual-modules-9gkxqrap.js} +7 -6
- package/dist/{work-objects-tb7ycye8.js → work-objects-y5g1y7ev.js} +49 -37
- package/dist/{worker-njjccz5d.js → worker-860c3e38.js} +64 -57
- package/dist/{x-qfmygqqv.js → x-m477fr0h.js} +3 -2
- package/openapi.json +951 -356
- package/package.json +12 -13
- package/src/apps/catalog.generated.json +1309 -53
- package/src/apps/page-validator.ts +93 -31
- package/src/azure-devops/api.ts +94 -18
- package/src/azure-devops/auth.ts +32 -0
- package/src/azure-devops/boards-sync.ts +708 -0
- package/src/azure-devops/handlers.ts +52 -8
- package/src/azure-devops/html.ts +26 -0
- package/src/azure-devops/index.ts +15 -0
- package/src/azure-devops/templates.ts +86 -0
- package/src/azure-devops/types.ts +65 -1
- 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 +74 -13
- package/src/be/migrations/213_script_connections_sql.sql +79 -0
- 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-connections.ts +47 -8
- 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 +10 -18
- package/src/be/seed-skills/index.ts +107 -3
- package/src/be/sql-connections.ts +118 -0
- 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/profile-sync.ts +5 -3
- package/src/commands/provider-credentials.ts +52 -6
- package/src/commands/runner.ts +196 -56
- 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-oauth.ts +6 -13
- package/src/http/mcp.ts +15 -5
- package/src/http/page-proxy.ts +2 -4
- package/src/http/pages-public.ts +6 -5
- package/src/http/pages.ts +5 -14
- package/src/http/poll.ts +28 -68
- package/src/http/requested-by.ts +54 -0
- package/src/http/route-def.ts +7 -6
- package/src/http/script-connection-proxy.ts +51 -4
- package/src/http/script-connections.ts +5 -2
- package/src/http/scripts.ts +1 -0
- package/src/http/session-cost-recompute.ts +12 -2
- package/src/http/session-data.ts +4 -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 +183 -2
- 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/jira/adf.ts +3 -1
- package/src/kv-reserved-namespaces.ts +2 -0
- package/src/oauth/mcp-wrapper.ts +10 -6
- package/src/otel-impl.ts +2 -1
- 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 +27 -2
- package/src/providers/claude-flag-settings.ts +133 -0
- package/src/providers/claude-managed-adapter.ts +1 -1
- package/src/providers/claude-sdk-session.ts +7 -0
- package/src/providers/claude-session-events.ts +6 -1
- package/src/providers/dsh-adapter.ts +1 -1
- package/src/providers/opencode-adapter.ts +268 -37
- package/src/providers/opencode-billing.ts +86 -0
- package/src/providers/opencode-swarm-plugin.ts +51 -0
- package/src/providers/pi-mono-adapter.ts +398 -153
- package/src/providers/pricing-sources.md +37 -3
- package/src/providers/reasoning-effort.ts +27 -0
- package/src/providers/types.ts +11 -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/api-client.ts +15 -1
- package/src/scripts-runtime/api-types.ts +9 -1
- package/src/scripts-runtime/ctx.ts +1 -1
- package/src/scripts-runtime/loader.ts +19 -7
- package/src/scripts-runtime/mcp-client.ts +30 -19
- 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/actions.ts +29 -1
- package/src/slack/app.ts +14 -2
- package/src/slack/assistant.ts +36 -2
- package/src/slack/channel-join.ts +3 -16
- package/src/slack/commands.ts +9 -1
- package/src/slack/event-dedup.ts +1 -1
- package/src/slack/external-channel-egress.ts +113 -0
- package/src/slack/external-channel.ts +309 -0
- package/src/slack/handlers.ts +61 -0
- package/src/slack/render-v2.ts +40 -47
- package/src/slack/work-objects.ts +5 -0
- package/src/tasks/additive-buffer.ts +4 -2
- package/src/tasks/context-key.ts +94 -2
- package/src/tasks/model-pin.ts +67 -0
- package/src/tasks/worker-follow-up.ts +4 -31
- package/src/telemetry.ts +2 -1
- package/src/tests/acp-adapter.test.ts +42 -0
- package/src/tests/api-drain-worker-e2e.test.ts +1 -6
- package/src/tests/apps-catalog-feedback.test.ts +310 -0
- package/src/tests/apps-catalog-inputs.test.ts +70 -0
- package/src/tests/apps-catalog-table.test.ts +113 -0
- package/src/tests/azure-devops-boards-sync.test.ts +584 -0
- package/src/tests/azure-devops-html.test.ts +13 -0
- package/src/tests/azure-devops-webhook-malformed.test.ts +4 -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 +144 -0
- package/src/tests/claude-flag-settings.test.ts +114 -0
- package/src/tests/claude-managed-adapter.test.ts +1 -1
- package/src/tests/claude-sdk-lifecycle.test.ts +148 -1
- package/src/tests/claude-sdk-process.fixture.ts +7 -0
- 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 +51 -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/deferred-task-wake.test.ts +52 -4
- 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/azure-boards/workitem.commented.json +89 -0
- package/src/tests/fixtures/azure-boards/workitem.created.json +81 -0
- package/src/tests/fixtures/azure-boards/workitem.deleted.json +70 -0
- package/src/tests/fixtures/azure-boards/workitem.restored.json +82 -0
- package/src/tests/fixtures/azure-boards/workitem.updated.json +130 -0
- package/src/tests/fixtures/catalog-feedback-definition.json.txt +860 -0
- package/src/tests/fixtures/catalog-inputs-definition.json.txt +910 -0
- package/src/tests/fixtures/catalog-table-definition.json.txt +468 -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-identity-refresh.ts +3 -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/node-env-runtime.test.ts +98 -0
- 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 +348 -3
- package/src/tests/opencode-billing.test.ts +28 -0
- 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-identity-refresh.test.ts +3 -0
- package/src/tests/runner-legacy-resume-prompt.test.ts +64 -0
- package/src/tests/script-connections-sql.test.ts +675 -0
- package/src/tests/script-connections.test.ts +7 -1
- 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 +94 -0
- package/src/tests/skill-get-name-resolution.test.ts +137 -0
- package/src/tests/slack-channel-join.test.ts +4 -1
- package/src/tests/slack-external-channel-config.test.ts +65 -0
- package/src/tests/slack-external-channel-egress.test.ts +237 -0
- package/src/tests/slack-external-channel-guard.test.ts +280 -0
- package/src/tests/slack-external-channel-tools.test.ts +173 -0
- package/src/tests/slack-external-channel.test.ts +370 -0
- package/src/tests/slack-render-v2.test.ts +86 -10
- package/src/tests/sonnet-5-5-registry.test.ts +9 -1
- package/src/tests/spec-base-url.test.ts +16 -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/vendored-openapi.test.ts +16 -1
- 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/defer-task.ts +17 -0
- 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/slack-post.ts +6 -0
- package/src/tools/slack-read.ts +8 -0
- package/src/tools/slack-reply.ts +6 -0
- package/src/tools/slack-start-thread.ts +6 -0
- package/src/tools/slack-update.ts +6 -0
- package/src/tools/slack-upload-file.ts +6 -0
- 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/node-env.ts +24 -0
- 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 +749 -0
- package/templates/skills/apps/content.md +60 -22
- 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/qa-loom/config.json +14 -0
- package/templates/skills/qa-loom/content.md +187 -0
- package/templates/skills/qa-loom/files/build-loom.mjs +221 -0
- package/templates/skills/qa-loom/files/run-qa.sh +57 -0
- package/templates/skills/slack-interaction/content.md +4 -0
- 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-9xsmdpdt.js +0 -56377
- package/dist/cli-bd957dgb.js +0 -153
- package/dist/cli-q51yprfq.js +0 -1104
- package/dist/cli-t1ze64h3.js +0 -811
- 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/dist/{azure-openai-responses-ap1gwxgr.js → azure-openai-responses-ygfreq4t.js} +5 -5
- package/dist/{cli-5rqrj7q0.js → cli-pw7ebjdt.js} +0 -0
- package/dist/{openai-codex-responses-33qhkkdy.js → openai-codex-responses-3bk0grnr.js} +4 -4
- package/dist/{openai-completions-s88xm3ns.js → openai-completions-5d4jpakc.js} +9 -9
- package/dist/{openai-responses-7xd2f2e2.js → openai-responses-krb51f59.js} +5 -5
- package/dist/{pi-messages-xszk613n.js → pi-messages-x9gzynm1.js} +3 -3
- /package/src/{artifact-sdk → pages}/browser-sdk.ts +0 -0
- /package/src/{artifact-sdk → pages}/feedback-overlay.ts +0 -0
|
@@ -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
|
+
}
|
|
@@ -0,0 +1,172 @@
|
|
|
1
|
+
import {
|
|
2
|
+
getPendingChatRelayTasks,
|
|
3
|
+
giveUpStaleChatRelays,
|
|
4
|
+
markChatRelayAttempted,
|
|
5
|
+
markChatRelayDelivered,
|
|
6
|
+
markChatRelayGivenUp,
|
|
7
|
+
recordChatRelayError,
|
|
8
|
+
} from "@/be/db";
|
|
9
|
+
import { chatThreadOfContextKey } from "@/tasks/context-key";
|
|
10
|
+
import type { AgentTask } from "@/types";
|
|
11
|
+
import { scrubSecrets } from "@/utils/secret-scrubber";
|
|
12
|
+
import { workflowEventBus } from "@/workflows/event-bus";
|
|
13
|
+
import { errorMessage } from "./logger";
|
|
14
|
+
import { postChatMessage } from "./outbound";
|
|
15
|
+
import { getChatInstance, getChatSdkHealth } from "./runtime";
|
|
16
|
+
import { sweepExpired } from "./state";
|
|
17
|
+
import { stopTypingKeepalive } from "./typing";
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* Stop retrying a relay row this long after its obligation was created. Time,
|
|
21
|
+
* not attempts: with exponential backoff a short platform outage would
|
|
22
|
+
* otherwise burn a fixed attempt budget in minutes.
|
|
23
|
+
*/
|
|
24
|
+
export const RELAY_GIVE_UP_AFTER_MS = 24 * 60 * 60 * 1000;
|
|
25
|
+
|
|
26
|
+
/**
|
|
27
|
+
* Run `sweepExpired` on every Nth tick, including event-triggered ticks (so
|
|
28
|
+
* about once a minute when only the 3 s interval ticks).
|
|
29
|
+
*/
|
|
30
|
+
const SWEEP_EVERY_N_TICKS = 20;
|
|
31
|
+
|
|
32
|
+
/** Coalesce bursts of terminal-task events into one early tick. */
|
|
33
|
+
const EARLY_TICK_DEBOUNCE_MS = 250;
|
|
34
|
+
|
|
35
|
+
/** The text posted for a terminal task. Failed and cancelled tasks get a status line first. */
|
|
36
|
+
export function formatChatRelayText(task: AgentTask): string {
|
|
37
|
+
const body = (task.output || task.failureReason || "").trim();
|
|
38
|
+
if (task.status === "failed") return body ? `**Task failed.**\n\n${body}` : "**Task failed.**";
|
|
39
|
+
if (task.status === "cancelled") {
|
|
40
|
+
return body ? `**Task cancelled.**\n\n${body}` : "**Task cancelled.**";
|
|
41
|
+
}
|
|
42
|
+
return body || "Task completed.";
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
/**
|
|
46
|
+
* Deliver every pending, out-of-backoff chat relay row for a running adapter.
|
|
47
|
+
* Exported with an injectable clock so retry and give-up behavior is testable
|
|
48
|
+
* without waiting.
|
|
49
|
+
*
|
|
50
|
+
* Delivery is at-least-once: a chunk failure re-posts the earlier chunks on
|
|
51
|
+
* retry, and a crash between the send and `markChatRelayDelivered` re-sends the
|
|
52
|
+
* message.
|
|
53
|
+
*
|
|
54
|
+
* Once `signal` aborts, no further row is claimed. A send already in flight is
|
|
55
|
+
* not cancelled (`postChatMessage` takes no signal); its row keeps the claim
|
|
56
|
+
* and retries after the normal backoff.
|
|
57
|
+
*/
|
|
58
|
+
export async function processPendingChatRelays(
|
|
59
|
+
now: number = Date.now(),
|
|
60
|
+
signal?: AbortSignal,
|
|
61
|
+
): Promise<void> {
|
|
62
|
+
// Rows older than the give-up age are retired first, so rows of a stopped
|
|
63
|
+
// adapter do not stay pending forever.
|
|
64
|
+
const retired = await giveUpStaleChatRelays(now, RELAY_GIVE_UP_AFTER_MS);
|
|
65
|
+
if (retired > 0) console.warn(`[chat-sdk] Gave up relaying ${retired} stale task(s)`);
|
|
66
|
+
|
|
67
|
+
const running = Object.entries(getChatSdkHealth())
|
|
68
|
+
.filter(([, adapterHealth]) => adapterHealth?.status === "ok")
|
|
69
|
+
.map(([name]) => name);
|
|
70
|
+
|
|
71
|
+
for (const pending of await getPendingChatRelayTasks(now, 200, running)) {
|
|
72
|
+
if (signal?.aborted) return;
|
|
73
|
+
const { task } = pending;
|
|
74
|
+
const target = chatThreadOfContextKey(task.contextKey);
|
|
75
|
+
// `created_at` is ISO text with a `Z` suffix (migration 217 default).
|
|
76
|
+
const createdAtMs = Date.parse(pending.createdAt);
|
|
77
|
+
|
|
78
|
+
if (Number.isFinite(createdAtMs) && now - createdAtMs >= RELAY_GIVE_UP_AFTER_MS) {
|
|
79
|
+
const reason = pending.lastError ?? "no successful delivery within 24 hours";
|
|
80
|
+
if (await markChatRelayGivenUp(task.id, reason, now)) {
|
|
81
|
+
console.warn(scrubSecrets(`[chat-sdk] Gave up relaying task ${task.id}: ${reason}`));
|
|
82
|
+
}
|
|
83
|
+
if (target) stopTypingKeepalive(target.threadId);
|
|
84
|
+
continue;
|
|
85
|
+
}
|
|
86
|
+
if (!target) {
|
|
87
|
+
// The trigger only enqueues chat-sdk keys; a key that no longer parses
|
|
88
|
+
// can never be delivered.
|
|
89
|
+
await markChatRelayGivenUp(task.id, "task has no chat-sdk contextKey", now);
|
|
90
|
+
continue;
|
|
91
|
+
}
|
|
92
|
+
// Stopping adapter: skip without counting an attempt.
|
|
93
|
+
if (!getChatInstance(target.adapter)) continue;
|
|
94
|
+
|
|
95
|
+
if (!(await markChatRelayAttempted(task.id, now))) continue;
|
|
96
|
+
try {
|
|
97
|
+
await postChatMessage({
|
|
98
|
+
adapter: target.adapter,
|
|
99
|
+
threadId: target.threadId,
|
|
100
|
+
text: formatChatRelayText(task),
|
|
101
|
+
taskId: task.id,
|
|
102
|
+
});
|
|
103
|
+
stopTypingKeepalive(target.threadId);
|
|
104
|
+
await markChatRelayDelivered(task.id, now);
|
|
105
|
+
console.log(`[chat-sdk] Relayed ${task.status} task ${task.id} to ${target.adapter}`);
|
|
106
|
+
} catch (error) {
|
|
107
|
+
const message = errorMessage(error);
|
|
108
|
+
await recordChatRelayError(task.id, message, now);
|
|
109
|
+
console.error(`[chat-sdk] Relay of task ${task.id} failed: ${message}`);
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
let interval: ReturnType<typeof setInterval> | undefined;
|
|
115
|
+
let earlyTick: ReturnType<typeof setTimeout> | undefined;
|
|
116
|
+
let processing: Promise<void> | undefined;
|
|
117
|
+
let stopController: AbortController | undefined;
|
|
118
|
+
let tickCount = 0;
|
|
119
|
+
|
|
120
|
+
async function tick(): Promise<void> {
|
|
121
|
+
if (processing) return;
|
|
122
|
+
tickCount += 1;
|
|
123
|
+
const now = Date.now();
|
|
124
|
+
processing = (async () => {
|
|
125
|
+
try {
|
|
126
|
+
await processPendingChatRelays(now, stopController?.signal);
|
|
127
|
+
if (tickCount % SWEEP_EVERY_N_TICKS === 0) await sweepExpired(now);
|
|
128
|
+
} catch (error) {
|
|
129
|
+
console.error(`[chat-sdk] Relay tick failed: ${errorMessage(error)}`);
|
|
130
|
+
}
|
|
131
|
+
})();
|
|
132
|
+
try {
|
|
133
|
+
await processing;
|
|
134
|
+
} finally {
|
|
135
|
+
processing = undefined;
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
function scheduleEarlyTick(): void {
|
|
140
|
+
if (!interval || earlyTick) return;
|
|
141
|
+
earlyTick = setTimeout(() => {
|
|
142
|
+
earlyTick = undefined;
|
|
143
|
+
void tick();
|
|
144
|
+
}, EARLY_TICK_DEBOUNCE_MS);
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
const TERMINAL_EVENTS = ["task.completed", "task.failed", "task.cancelled"] as const;
|
|
148
|
+
|
|
149
|
+
/** Start the relay watcher. Idempotent. A terminal task event triggers an early tick. */
|
|
150
|
+
export function startChatRelayWatcher(intervalMs = 3000): void {
|
|
151
|
+
if (interval) return;
|
|
152
|
+
tickCount = 0;
|
|
153
|
+
stopController = new AbortController();
|
|
154
|
+
interval = setInterval(() => void tick(), intervalMs);
|
|
155
|
+
for (const event of TERMINAL_EVENTS) workflowEventBus.on(event, scheduleEarlyTick);
|
|
156
|
+
void tick();
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
/**
|
|
160
|
+
* Stop the watcher: no new tick, no new claim. The returned promise settles
|
|
161
|
+
* when an in-flight tick finishes, which an unanswered platform call can delay
|
|
162
|
+
* without bound, so shutdown waits for it under its own deadline.
|
|
163
|
+
*/
|
|
164
|
+
export async function stopChatRelayWatcher(): Promise<void> {
|
|
165
|
+
stopController?.abort();
|
|
166
|
+
if (interval) clearInterval(interval);
|
|
167
|
+
interval = undefined;
|
|
168
|
+
if (earlyTick) clearTimeout(earlyTick);
|
|
169
|
+
earlyTick = undefined;
|
|
170
|
+
for (const event of TERMINAL_EVENTS) workflowEventBus.off(event, scheduleEarlyTick);
|
|
171
|
+
if (processing) await processing;
|
|
172
|
+
}
|
|
@@ -0,0 +1,169 @@
|
|
|
1
|
+
import {
|
|
2
|
+
deleteKv,
|
|
3
|
+
getAgentById,
|
|
4
|
+
getAgentWorkingOnContextKey,
|
|
5
|
+
getAllAgents,
|
|
6
|
+
getKv,
|
|
7
|
+
getLeadAgent,
|
|
8
|
+
upsertKv,
|
|
9
|
+
} from "@/be/db";
|
|
10
|
+
import { isAgentReachable } from "@/be/runtime-pools/reachability";
|
|
11
|
+
import { chatSdkContextKey } from "@/tasks/context-key";
|
|
12
|
+
import type { Agent, RoutingReason } from "@/types";
|
|
13
|
+
import { scrubSecrets } from "@/utils/secret-scrubber";
|
|
14
|
+
|
|
15
|
+
/** KV namespace of the channel-to-agent/workflow mapping, written by `register-chat-channel`. */
|
|
16
|
+
export const CHAT_CHANNELS_NAMESPACE = "integrations:chat:channels";
|
|
17
|
+
|
|
18
|
+
export interface ChatChannelMapping {
|
|
19
|
+
agentId?: string;
|
|
20
|
+
workflowId?: string;
|
|
21
|
+
name?: string;
|
|
22
|
+
createdAt?: string;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
export interface ChatAgentMatch {
|
|
26
|
+
/** Null when no lead exists: the task is created unassigned and queued. */
|
|
27
|
+
agent: Agent | null;
|
|
28
|
+
reason: RoutingReason;
|
|
29
|
+
matchedText: string;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
export type ChatRouteTarget =
|
|
33
|
+
| { kind: "agents"; matches: ChatAgentMatch[] }
|
|
34
|
+
| { kind: "workflow"; workflowId: string };
|
|
35
|
+
|
|
36
|
+
export interface ChatRouteContext {
|
|
37
|
+
adapter: string;
|
|
38
|
+
channelId: string;
|
|
39
|
+
threadId: string;
|
|
40
|
+
botMentioned: boolean;
|
|
41
|
+
isDirectMessage: boolean;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
/**
|
|
45
|
+
* KV key of a channel mapping. SDK channel ids already carry the adapter
|
|
46
|
+
* prefix (`telegram:555001`, `slack:C123`); the prefix is added only when an
|
|
47
|
+
* adapter returns a bare id, so the key is always `<adapter>:<platform id>`.
|
|
48
|
+
*/
|
|
49
|
+
export function chatChannelKey(adapter: string, channelId: string): string {
|
|
50
|
+
return channelId.startsWith(`${adapter}:`) ? channelId : `${adapter}:${channelId}`;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
export async function getChatChannelMapping(
|
|
54
|
+
adapter: string,
|
|
55
|
+
channelId: string,
|
|
56
|
+
): Promise<ChatChannelMapping | null> {
|
|
57
|
+
const row = await getKv(CHAT_CHANNELS_NAMESPACE, chatChannelKey(adapter, channelId));
|
|
58
|
+
if (!row || typeof row.value !== "object" || row.value === null) return null;
|
|
59
|
+
return row.value as ChatChannelMapping;
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
/** Store a channel mapping under `chatChannelKey(adapter, channelId)`. Returns the key. */
|
|
63
|
+
export async function putChatChannelMapping(
|
|
64
|
+
adapter: string,
|
|
65
|
+
channelId: string,
|
|
66
|
+
mapping: ChatChannelMapping,
|
|
67
|
+
): Promise<string> {
|
|
68
|
+
const key = chatChannelKey(adapter, channelId);
|
|
69
|
+
await upsertKv({
|
|
70
|
+
namespace: CHAT_CHANNELS_NAMESPACE,
|
|
71
|
+
key,
|
|
72
|
+
value: mapping,
|
|
73
|
+
valueType: "json",
|
|
74
|
+
expiresAt: null,
|
|
75
|
+
});
|
|
76
|
+
return key;
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
export async function deleteChatChannelMapping(
|
|
80
|
+
adapter: string,
|
|
81
|
+
channelId: string,
|
|
82
|
+
): Promise<boolean> {
|
|
83
|
+
return deleteKv(CHAT_CHANNELS_NAMESPACE, chatChannelKey(adapter, channelId));
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
/** Remove `swarm#<uuid>` and `swarm#all` routing tokens from task text. */
|
|
87
|
+
export function stripRoutingTokens(text: string): string {
|
|
88
|
+
return text
|
|
89
|
+
.replace(/swarm#[a-f0-9-]{36}/gi, "")
|
|
90
|
+
.replace(/swarm#all/gi, "")
|
|
91
|
+
.trim();
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
async function leadMatch(reachable: Agent[], matchedText: string): Promise<ChatAgentMatch> {
|
|
95
|
+
const lead = reachable.find((agent) => agent.isLead) ?? (await getLeadAgent());
|
|
96
|
+
return { agent: lead, reason: "skill", matchedText };
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
/**
|
|
100
|
+
* Port of `src/slack/router.ts` for Chat SDK threads. Order: `swarm#<uuid>`,
|
|
101
|
+
* `swarm#all`, KV channel mapping (agent, or workflow for a mention or DM), the
|
|
102
|
+
* thread owner (lead when the owner is offline), the lead for a mention or DM.
|
|
103
|
+
* Returns null when the message is not addressed to the swarm.
|
|
104
|
+
*/
|
|
105
|
+
export async function routeChatMessage(
|
|
106
|
+
text: string,
|
|
107
|
+
ctx: ChatRouteContext,
|
|
108
|
+
): Promise<ChatRouteTarget | null> {
|
|
109
|
+
const allAgents = await getAllAgents();
|
|
110
|
+
const reachableFlags = await Promise.all(allAgents.map((agent) => isAgentReachable(agent)));
|
|
111
|
+
const reachable = allAgents.filter((_, index) => reachableFlags[index]);
|
|
112
|
+
|
|
113
|
+
const matches: ChatAgentMatch[] = [];
|
|
114
|
+
for (const match of text.matchAll(/swarm#([a-f0-9-]{36})/gi)) {
|
|
115
|
+
const agentId = match[1];
|
|
116
|
+
if (!agentId || matches.some((m) => m.agent?.id === agentId)) continue;
|
|
117
|
+
const agent = await getAgentById(agentId);
|
|
118
|
+
if (agent && (await isAgentReachable(agent))) {
|
|
119
|
+
matches.push({ agent, reason: "human_pinned", matchedText: match[0] });
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
if (/swarm#all/i.test(text)) {
|
|
123
|
+
for (const agent of reachable) {
|
|
124
|
+
if (agent.isLead || matches.some((m) => m.agent?.id === agent.id)) continue;
|
|
125
|
+
matches.push({ agent, reason: "human_pinned", matchedText: "swarm#all" });
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
if (matches.length > 0) return { kind: "agents", matches };
|
|
129
|
+
|
|
130
|
+
const mapping = await getChatChannelMapping(ctx.adapter, ctx.channelId);
|
|
131
|
+
// A workflow run has no continuity: an unaddressed reply in the subscribed
|
|
132
|
+
// thread must not start another run. It still reaches a thread owner below.
|
|
133
|
+
if (mapping?.workflowId && (ctx.botMentioned || ctx.isDirectMessage)) {
|
|
134
|
+
return { kind: "workflow", workflowId: mapping.workflowId };
|
|
135
|
+
}
|
|
136
|
+
if (mapping?.agentId) {
|
|
137
|
+
const agent = await getAgentById(mapping.agentId);
|
|
138
|
+
if (agent) {
|
|
139
|
+
return {
|
|
140
|
+
kind: "agents",
|
|
141
|
+
matches: [{ agent, reason: "human_pinned", matchedText: "channel mapping" }],
|
|
142
|
+
};
|
|
143
|
+
}
|
|
144
|
+
console.warn(
|
|
145
|
+
scrubSecrets(
|
|
146
|
+
`[chat-sdk] Channel mapping for ${ctx.channelId} names unknown agent ${mapping.agentId}; using the default route`,
|
|
147
|
+
),
|
|
148
|
+
);
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
const owner = await getAgentWorkingOnContextKey(chatSdkContextKey(ctx.threadId), "chat");
|
|
152
|
+
if (owner && (await isAgentReachable(owner))) {
|
|
153
|
+
return {
|
|
154
|
+
kind: "agents",
|
|
155
|
+
matches: [{ agent: owner, reason: "continuity", matchedText: "thread follow-up" }],
|
|
156
|
+
};
|
|
157
|
+
}
|
|
158
|
+
if (owner) {
|
|
159
|
+
return {
|
|
160
|
+
kind: "agents",
|
|
161
|
+
matches: [await leadMatch(reachable, "thread follow-up (lead fallback)")],
|
|
162
|
+
};
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
if (ctx.botMentioned || ctx.isDirectMessage) {
|
|
166
|
+
return { kind: "agents", matches: [await leadMatch(reachable, "@bot")] };
|
|
167
|
+
}
|
|
168
|
+
return null;
|
|
169
|
+
}
|