@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,63 @@
|
|
|
1
|
+
import { scrubSecrets } from "@/utils/secret-scrubber";
|
|
2
|
+
import { resolveAdapterApiUrl } from "./adapters";
|
|
3
|
+
import { errorMessage } from "./logger";
|
|
4
|
+
|
|
5
|
+
const DISCORD_TIMEOUT_MS = 5_000;
|
|
6
|
+
/** The status card polls every 5 s; one Discord call per minute is enough. */
|
|
7
|
+
const GUILDS_CACHE_MS = 60_000;
|
|
8
|
+
/** Bound on the server names the status carries. */
|
|
9
|
+
const MAX_GUILD_NAMES = 10;
|
|
10
|
+
|
|
11
|
+
export interface DiscordGuilds {
|
|
12
|
+
count: number;
|
|
13
|
+
/** Up to `MAX_GUILD_NAMES` server names, for the card. */
|
|
14
|
+
names: string[];
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
let cache: { token: string; at: number; guilds: DiscordGuilds | undefined } | undefined;
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* The servers the bot was invited to (`GET /users/@me/guilds`). A connected
|
|
21
|
+
* Gateway with zero servers means the invite step was skipped, which the card
|
|
22
|
+
* can say outright. Undefined when Discord does not answer; the error is
|
|
23
|
+
* logged without the token.
|
|
24
|
+
*/
|
|
25
|
+
export async function fetchDiscordGuilds(
|
|
26
|
+
token: string,
|
|
27
|
+
now = Date.now(),
|
|
28
|
+
): Promise<DiscordGuilds | undefined> {
|
|
29
|
+
if (cache && cache.token === token && now - cache.at < GUILDS_CACHE_MS) return cache.guilds;
|
|
30
|
+
let guilds: DiscordGuilds | undefined;
|
|
31
|
+
try {
|
|
32
|
+
const response = await fetch(`${resolveAdapterApiUrl("discord")}/users/@me/guilds`, {
|
|
33
|
+
headers: { authorization: `Bot ${token}` },
|
|
34
|
+
signal: AbortSignal.timeout(DISCORD_TIMEOUT_MS),
|
|
35
|
+
});
|
|
36
|
+
if (!response.ok) throw new Error(`HTTP ${response.status}`);
|
|
37
|
+
const payload = (await response.json()) as unknown;
|
|
38
|
+
if (!Array.isArray(payload)) throw new Error("unexpected response shape");
|
|
39
|
+
const names = payload
|
|
40
|
+
.map((guild: unknown) =>
|
|
41
|
+
guild !== null &&
|
|
42
|
+
typeof guild === "object" &&
|
|
43
|
+
typeof (guild as { name?: unknown }).name === "string"
|
|
44
|
+
? (guild as { name: string }).name
|
|
45
|
+
: undefined,
|
|
46
|
+
)
|
|
47
|
+
.filter((name): name is string => name !== undefined);
|
|
48
|
+
guilds = { count: payload.length, names: names.slice(0, MAX_GUILD_NAMES) };
|
|
49
|
+
} catch (error) {
|
|
50
|
+
console.warn(
|
|
51
|
+
scrubSecrets(
|
|
52
|
+
`[chat-sdk] discord: could not list the bot's servers: ${errorMessage(error).replaceAll(token, "[REDACTED]")}`,
|
|
53
|
+
),
|
|
54
|
+
);
|
|
55
|
+
}
|
|
56
|
+
cache = { token, at: now, guilds };
|
|
57
|
+
return guilds;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
/** Test seam. */
|
|
61
|
+
export function resetDiscordGuildsCache(): void {
|
|
62
|
+
cache = undefined;
|
|
63
|
+
}
|
|
@@ -0,0 +1,185 @@
|
|
|
1
|
+
import type { DiscordAdapter } from "@chat-adapter/discord";
|
|
2
|
+
import type { Adapter } from "chat";
|
|
3
|
+
import { scrubSecrets } from "@/utils/secret-scrubber";
|
|
4
|
+
import { trackBackground } from "./background";
|
|
5
|
+
import { errorMessage } from "./logger";
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* Discord sends channel messages and mentions only over the Gateway WebSocket.
|
|
9
|
+
* The adapter's `startGatewayListener` listens for one window and then
|
|
10
|
+
* destroys its client (it is built for serverless re-invocation), so a
|
|
11
|
+
* long-lived API process re-arms it in a loop. The adapter has no
|
|
12
|
+
* `disconnect()`, so `chat.shutdown()` does not stop the Gateway; aborting
|
|
13
|
+
* this loop is the only stop path.
|
|
14
|
+
*
|
|
15
|
+
* The Gateway does not replay `MESSAGE_CREATE`: messages sent during a re-arm
|
|
16
|
+
* gap or a backoff are lost. A long window keeps gaps rare. 6 h uses 4
|
|
17
|
+
* identifies per day of Discord's 1000.
|
|
18
|
+
*/
|
|
19
|
+
const DISCORD_GATEWAY_WINDOW_MS = 6 * 60 * 60 * 1000;
|
|
20
|
+
|
|
21
|
+
/** A window that ends sooner than this failed (bad token, network down). */
|
|
22
|
+
const MIN_HEALTHY_WINDOW_MS = 10_000;
|
|
23
|
+
const BACKOFF_MS = 30_000;
|
|
24
|
+
/** Bound on the response body text logged when the listener refuses to start. */
|
|
25
|
+
const MAX_LOGGED_BODY_CHARS = 200;
|
|
26
|
+
|
|
27
|
+
export interface DiscordGatewayHealth {
|
|
28
|
+
state: "running" | "backoff" | "stopped";
|
|
29
|
+
since: string;
|
|
30
|
+
/** Re-arms after the first listener window. */
|
|
31
|
+
restarts: number;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
/** The part of the Discord adapter the loop uses. */
|
|
35
|
+
export type DiscordGatewayListener = Pick<DiscordAdapter, "startGatewayListener">;
|
|
36
|
+
|
|
37
|
+
export function isDiscordGatewayListener(
|
|
38
|
+
adapter: Adapter,
|
|
39
|
+
): adapter is Adapter & DiscordGatewayListener {
|
|
40
|
+
return "startGatewayListener" in adapter && typeof adapter.startGatewayListener === "function";
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
/** Test seams. Production uses the defaults. */
|
|
44
|
+
export interface DiscordGatewayOptions {
|
|
45
|
+
windowMs?: number;
|
|
46
|
+
sleep?: (ms: number, signal: AbortSignal) => Promise<void>;
|
|
47
|
+
now?: () => number;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
/** Resolves after `ms`, or at once when `signal` aborts. */
|
|
51
|
+
function abortableSleep(ms: number, signal: AbortSignal): Promise<void> {
|
|
52
|
+
return new Promise((resolve) => {
|
|
53
|
+
if (signal.aborted) {
|
|
54
|
+
resolve();
|
|
55
|
+
return;
|
|
56
|
+
}
|
|
57
|
+
const done = (): void => {
|
|
58
|
+
clearTimeout(timer);
|
|
59
|
+
signal.removeEventListener("abort", done);
|
|
60
|
+
resolve();
|
|
61
|
+
};
|
|
62
|
+
const timer = setTimeout(done, ms);
|
|
63
|
+
signal.addEventListener("abort", done, { once: true });
|
|
64
|
+
});
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
let current: { controller: AbortController; loop: Promise<void>; now: () => number } | undefined;
|
|
68
|
+
let health: DiscordGatewayHealth | undefined;
|
|
69
|
+
|
|
70
|
+
function setState(state: DiscordGatewayHealth["state"], restarts: number, nowMs: number): void {
|
|
71
|
+
if (health?.state === state) {
|
|
72
|
+
health = { ...health, restarts };
|
|
73
|
+
return;
|
|
74
|
+
}
|
|
75
|
+
health = { state, since: new Date(nowMs).toISOString(), restarts };
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
async function runGatewayLoop(
|
|
79
|
+
adapter: DiscordGatewayListener,
|
|
80
|
+
signal: AbortSignal,
|
|
81
|
+
options: DiscordGatewayOptions,
|
|
82
|
+
): Promise<void> {
|
|
83
|
+
const windowMs = options.windowMs ?? DISCORD_GATEWAY_WINDOW_MS;
|
|
84
|
+
const sleep = options.sleep ?? abortableSleep;
|
|
85
|
+
const now = options.now ?? Date.now;
|
|
86
|
+
let restarts = 0;
|
|
87
|
+
let first = true;
|
|
88
|
+
while (!signal.aborted) {
|
|
89
|
+
if (!first) restarts += 1;
|
|
90
|
+
first = false;
|
|
91
|
+
setState("running", restarts, now());
|
|
92
|
+
const started = now();
|
|
93
|
+
let inner: Promise<unknown> | undefined;
|
|
94
|
+
// One controller per window. The adapter adds an `abort` listener to the
|
|
95
|
+
// signal it gets and never removes it, so passing the outer signal would
|
|
96
|
+
// leak one listener (and the destroyed client) per re-arm.
|
|
97
|
+
const windowController = new AbortController();
|
|
98
|
+
const abortWindow = (): void => windowController.abort();
|
|
99
|
+
signal.addEventListener("abort", abortWindow, { once: true });
|
|
100
|
+
try {
|
|
101
|
+
const response = await adapter.startGatewayListener(
|
|
102
|
+
{
|
|
103
|
+
waitUntil: (task) => {
|
|
104
|
+
inner = task;
|
|
105
|
+
trackBackground(task);
|
|
106
|
+
},
|
|
107
|
+
},
|
|
108
|
+
windowMs,
|
|
109
|
+
windowController.signal,
|
|
110
|
+
);
|
|
111
|
+
if (!response.ok) {
|
|
112
|
+
const body = await response.text().catch(() => "");
|
|
113
|
+
console.error(
|
|
114
|
+
scrubSecrets(
|
|
115
|
+
`[chat-sdk] discord gateway listener refused to start: HTTP ${response.status}: ${body.slice(0, MAX_LOGGED_BODY_CHARS)}`,
|
|
116
|
+
),
|
|
117
|
+
);
|
|
118
|
+
}
|
|
119
|
+
// The inner promise resolves when the window ends or on abort. It never
|
|
120
|
+
// rejects: login errors are logged by the adapter and end the window.
|
|
121
|
+
await inner;
|
|
122
|
+
} catch (error) {
|
|
123
|
+
console.error(
|
|
124
|
+
scrubSecrets(`[chat-sdk] discord gateway listener failed: ${errorMessage(error)}`),
|
|
125
|
+
);
|
|
126
|
+
} finally {
|
|
127
|
+
signal.removeEventListener("abort", abortWindow);
|
|
128
|
+
}
|
|
129
|
+
const elapsed = now() - started;
|
|
130
|
+
if (signal.aborted) break;
|
|
131
|
+
if (elapsed < MIN_HEALTHY_WINDOW_MS) {
|
|
132
|
+
setState("backoff", restarts, now());
|
|
133
|
+
console.warn(
|
|
134
|
+
scrubSecrets(
|
|
135
|
+
`[chat-sdk] discord gateway backoff: window ended after ${elapsed}ms, retrying in ${BACKOFF_MS / 1000}s`,
|
|
136
|
+
),
|
|
137
|
+
);
|
|
138
|
+
await sleep(BACKOFF_MS, signal);
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
/**
|
|
144
|
+
* Start the supervised Gateway loop. Call only after `chat.initialize()`
|
|
145
|
+
* succeeded: the listener refuses to start before that.
|
|
146
|
+
*/
|
|
147
|
+
export function startDiscordGateway(
|
|
148
|
+
adapter: DiscordGatewayListener,
|
|
149
|
+
options: DiscordGatewayOptions = {},
|
|
150
|
+
): void {
|
|
151
|
+
if (current) return;
|
|
152
|
+
const now = options.now ?? Date.now;
|
|
153
|
+
health = { state: "running", since: new Date(now()).toISOString(), restarts: 0 };
|
|
154
|
+
const controller = new AbortController();
|
|
155
|
+
const loop = runGatewayLoop(adapter, controller.signal, options).catch((error: unknown) => {
|
|
156
|
+
console.error(`[chat-sdk] discord gateway loop crashed: ${errorMessage(error)}`);
|
|
157
|
+
});
|
|
158
|
+
current = { controller, loop, now };
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
/**
|
|
162
|
+
* Abort the loop and return at once. The open window is tracked by
|
|
163
|
+
* `trackBackground`, so the shutdown drain bounds the wait for it to close.
|
|
164
|
+
* The `stopped` state and log line follow when the loop exits.
|
|
165
|
+
*/
|
|
166
|
+
export function stopDiscordGateway(): void {
|
|
167
|
+
if (!current) return;
|
|
168
|
+
const { controller, loop, now } = current;
|
|
169
|
+
current = undefined;
|
|
170
|
+
controller.abort();
|
|
171
|
+
void loop.then(() => {
|
|
172
|
+
// Skip when a new loop started, or when shutdown already reset health.
|
|
173
|
+
if (!current && health) setState("stopped", health.restarts, now());
|
|
174
|
+
console.log("[chat-sdk] discord gateway stopped");
|
|
175
|
+
});
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
export function getDiscordGatewayHealth(): DiscordGatewayHealth | undefined {
|
|
179
|
+
return health;
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
/** Forget the gateway record. Runtime shutdown clears adapter health the same way. */
|
|
183
|
+
export function resetDiscordGatewayHealth(): void {
|
|
184
|
+
health = undefined;
|
|
185
|
+
}
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import type { Chat, Message, Thread } from "chat";
|
|
2
|
+
import { trackBackground } from "./background";
|
|
3
|
+
import { registerChatCommands } from "./commands";
|
|
4
|
+
import { type ChatInboundKind, handleChatInbound } from "./inbound";
|
|
5
|
+
|
|
6
|
+
async function onHumanMessage(
|
|
7
|
+
thread: Thread,
|
|
8
|
+
message: Message,
|
|
9
|
+
kind: ChatInboundKind,
|
|
10
|
+
): Promise<void> {
|
|
11
|
+
// The SDK drops the bot's own messages; other bots never become tasks.
|
|
12
|
+
if (message.author.isBot === true) return;
|
|
13
|
+
// Socket-mode events (Slack) reach the SDK without a request, so it has no
|
|
14
|
+
// `waitUntil` to hand this work to. Track it here so the shutdown drain
|
|
15
|
+
// waits for it before the DB closes. The tracker logs a rejection.
|
|
16
|
+
const work = handleChatInbound(thread, message, kind);
|
|
17
|
+
trackBackground(work);
|
|
18
|
+
await work.catch(() => undefined);
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
export function registerChatHandlers(chat: Chat): void {
|
|
22
|
+
chat.onNewMention((thread, message) => onHumanMessage(thread, message, "mention"));
|
|
23
|
+
chat.onDirectMessage((thread, message) => onHumanMessage(thread, message, "dm"));
|
|
24
|
+
chat.onSubscribedMessage((thread, message) => onHumanMessage(thread, message, "subscribed"));
|
|
25
|
+
// A `/command` message never reaches the handlers above: the adapters route
|
|
26
|
+
// it here. Without a handler Telegram would drop it silently.
|
|
27
|
+
registerChatCommands(chat);
|
|
28
|
+
}
|
|
@@ -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
|
+
}
|