@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,243 @@
|
|
|
1
|
+
import type { Lock, Message, QueueEntry, SerializedMessage, StateAdapter } from "chat";
|
|
2
|
+
import { getDbClient } from "@/be/db";
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Chat SDK `StateAdapter` backed by the dedicated `chat_*` tables (migration
|
|
6
|
+
* 213). Every method is one statement or one short transaction through the
|
|
7
|
+
* async DB seam, so each operation is atomic by construction.
|
|
8
|
+
*
|
|
9
|
+
* Expiry columns hold epoch milliseconds. A `ttlMs` of 0 or undefined means no
|
|
10
|
+
* expiry. Queue entries are not filtered by `expiresAt` here: the Chat core
|
|
11
|
+
* drops expired entries itself on dequeue.
|
|
12
|
+
*/
|
|
13
|
+
|
|
14
|
+
function encode(value: unknown): string {
|
|
15
|
+
return JSON.stringify(value ?? null);
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
function decode<T>(value: string): T {
|
|
19
|
+
return JSON.parse(value) as T;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
function expiresAtFor(ttlMs: number | undefined, now: number): number | null {
|
|
23
|
+
return ttlMs ? now + ttlMs : null;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
export class SqliteChatState implements StateAdapter {
|
|
27
|
+
async connect(): Promise<void> {}
|
|
28
|
+
|
|
29
|
+
/**
|
|
30
|
+
* No-op on purpose. The memory adapter clears subscriptions on disconnect
|
|
31
|
+
* because it is memory. A durable store keeps "we own this thread" across
|
|
32
|
+
* restarts, and locks expire by TTL.
|
|
33
|
+
*/
|
|
34
|
+
async disconnect(): Promise<void> {}
|
|
35
|
+
|
|
36
|
+
async subscribe(threadId: string): Promise<void> {
|
|
37
|
+
await getDbClient().run("INSERT OR IGNORE INTO chat_subscriptions (thread_id) VALUES (?)", [
|
|
38
|
+
threadId,
|
|
39
|
+
]);
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
async unsubscribe(threadId: string): Promise<void> {
|
|
43
|
+
await getDbClient().run("DELETE FROM chat_subscriptions WHERE thread_id = ?", [threadId]);
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
async isSubscribed(threadId: string): Promise<boolean> {
|
|
47
|
+
const row = await getDbClient().get<{ one: number }>(
|
|
48
|
+
"SELECT 1 AS one FROM chat_subscriptions WHERE thread_id = ?",
|
|
49
|
+
[threadId],
|
|
50
|
+
);
|
|
51
|
+
return row !== null;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
async acquireLock(threadId: string, ttlMs: number): Promise<Lock | null> {
|
|
55
|
+
const now = Date.now();
|
|
56
|
+
const lock: Lock = { threadId, token: crypto.randomUUID(), expiresAt: now + ttlMs };
|
|
57
|
+
const result = await getDbClient().run(
|
|
58
|
+
`INSERT INTO chat_locks (thread_id, token, expires_at) VALUES (?, ?, ?)
|
|
59
|
+
ON CONFLICT(thread_id) DO UPDATE SET
|
|
60
|
+
token = excluded.token,
|
|
61
|
+
expires_at = excluded.expires_at
|
|
62
|
+
WHERE chat_locks.expires_at <= ?`,
|
|
63
|
+
[threadId, lock.token, lock.expiresAt, now],
|
|
64
|
+
);
|
|
65
|
+
return result.changes > 0 ? lock : null;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
async extendLock(lock: Lock, ttlMs: number): Promise<boolean> {
|
|
69
|
+
const now = Date.now();
|
|
70
|
+
const result = await getDbClient().run(
|
|
71
|
+
"UPDATE chat_locks SET expires_at = ? WHERE thread_id = ? AND token = ? AND expires_at > ?",
|
|
72
|
+
[now + ttlMs, lock.threadId, lock.token, now],
|
|
73
|
+
);
|
|
74
|
+
return result.changes > 0;
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
async releaseLock(lock: Lock): Promise<void> {
|
|
78
|
+
await getDbClient().run("DELETE FROM chat_locks WHERE thread_id = ? AND token = ?", [
|
|
79
|
+
lock.threadId,
|
|
80
|
+
lock.token,
|
|
81
|
+
]);
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
async forceReleaseLock(threadId: string): Promise<void> {
|
|
85
|
+
await getDbClient().run("DELETE FROM chat_locks WHERE thread_id = ?", [threadId]);
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
async get<T = unknown>(key: string): Promise<T | null> {
|
|
89
|
+
const now = Date.now();
|
|
90
|
+
const row = await getDbClient().get<{ value: string; expires_at: number | null }>(
|
|
91
|
+
"SELECT value, expires_at FROM chat_state_kv WHERE key = ?",
|
|
92
|
+
[key],
|
|
93
|
+
);
|
|
94
|
+
if (!row) return null;
|
|
95
|
+
if (row.expires_at !== null && row.expires_at <= now) {
|
|
96
|
+
await getDbClient().run("DELETE FROM chat_state_kv WHERE key = ? AND expires_at <= ?", [
|
|
97
|
+
key,
|
|
98
|
+
now,
|
|
99
|
+
]);
|
|
100
|
+
return null;
|
|
101
|
+
}
|
|
102
|
+
return decode<T>(row.value);
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
async set<T = unknown>(key: string, value: T, ttlMs?: number): Promise<void> {
|
|
106
|
+
await getDbClient().run(
|
|
107
|
+
`INSERT INTO chat_state_kv (key, value, expires_at) VALUES (?, ?, ?)
|
|
108
|
+
ON CONFLICT(key) DO UPDATE SET value = excluded.value, expires_at = excluded.expires_at`,
|
|
109
|
+
[key, encode(value), expiresAtFor(ttlMs, Date.now())],
|
|
110
|
+
);
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
async setIfNotExists(key: string, value: unknown, ttlMs?: number): Promise<boolean> {
|
|
114
|
+
const now = Date.now();
|
|
115
|
+
const result = await getDbClient().run(
|
|
116
|
+
`INSERT INTO chat_state_kv (key, value, expires_at) VALUES (?, ?, ?)
|
|
117
|
+
ON CONFLICT(key) DO UPDATE SET value = excluded.value, expires_at = excluded.expires_at
|
|
118
|
+
WHERE chat_state_kv.expires_at IS NOT NULL AND chat_state_kv.expires_at <= ?`,
|
|
119
|
+
[key, encode(value), expiresAtFor(ttlMs, now), now],
|
|
120
|
+
);
|
|
121
|
+
return result.changes > 0;
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
async delete(key: string): Promise<void> {
|
|
125
|
+
await getDbClient().run("DELETE FROM chat_state_kv WHERE key = ?", [key]);
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
async appendToList(
|
|
129
|
+
key: string,
|
|
130
|
+
value: unknown,
|
|
131
|
+
options?: { maxLength?: number; ttlMs?: number },
|
|
132
|
+
): Promise<void> {
|
|
133
|
+
const now = Date.now();
|
|
134
|
+
const expiresAt = expiresAtFor(options?.ttlMs, now);
|
|
135
|
+
await getDbClient().transaction(async (tx) => {
|
|
136
|
+
// An expired list starts over, like the memory adapter.
|
|
137
|
+
await tx.run(
|
|
138
|
+
"DELETE FROM chat_lists WHERE key = ? AND expires_at IS NOT NULL AND expires_at <= ?",
|
|
139
|
+
[key, now],
|
|
140
|
+
);
|
|
141
|
+
await tx.run(
|
|
142
|
+
`INSERT INTO chat_lists (key, seq, value, expires_at)
|
|
143
|
+
VALUES (?, (SELECT COALESCE(MAX(seq), 0) + 1 FROM chat_lists WHERE key = ?), ?, ?)`,
|
|
144
|
+
[key, key, encode(value), expiresAt],
|
|
145
|
+
);
|
|
146
|
+
if (options?.maxLength && options.maxLength > 0) {
|
|
147
|
+
await tx.run(
|
|
148
|
+
`DELETE FROM chat_lists WHERE key = ? AND seq NOT IN (
|
|
149
|
+
SELECT seq FROM chat_lists WHERE key = ? ORDER BY seq DESC LIMIT ?
|
|
150
|
+
)`,
|
|
151
|
+
[key, key, options.maxLength],
|
|
152
|
+
);
|
|
153
|
+
}
|
|
154
|
+
await tx.run("UPDATE chat_lists SET expires_at = ? WHERE key = ?", [expiresAt, key]);
|
|
155
|
+
});
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
async getList<T = unknown>(key: string): Promise<T[]> {
|
|
159
|
+
const rows = await getDbClient().query<{ value: string }>(
|
|
160
|
+
`SELECT value FROM chat_lists
|
|
161
|
+
WHERE key = ? AND (expires_at IS NULL OR expires_at > ?)
|
|
162
|
+
ORDER BY seq ASC`,
|
|
163
|
+
[key, Date.now()],
|
|
164
|
+
);
|
|
165
|
+
return rows.map((row) => decode<T>(row.value));
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
async enqueue(threadId: string, entry: QueueEntry, maxSize: number): Promise<number> {
|
|
169
|
+
return await getDbClient().transaction(async (tx) => {
|
|
170
|
+
await tx.run("INSERT INTO chat_queue (thread_id, entry) VALUES (?, ?)", [
|
|
171
|
+
threadId,
|
|
172
|
+
encode(entry),
|
|
173
|
+
]);
|
|
174
|
+
await tx.run(
|
|
175
|
+
`DELETE FROM chat_queue WHERE thread_id = ? AND id NOT IN (
|
|
176
|
+
SELECT id FROM chat_queue WHERE thread_id = ? ORDER BY id DESC LIMIT ?
|
|
177
|
+
)`,
|
|
178
|
+
[threadId, threadId, Math.max(maxSize, 0)],
|
|
179
|
+
);
|
|
180
|
+
const row = await tx.get<{ depth: number }>(
|
|
181
|
+
"SELECT COUNT(*) AS depth FROM chat_queue WHERE thread_id = ?",
|
|
182
|
+
[threadId],
|
|
183
|
+
);
|
|
184
|
+
return row?.depth ?? 0;
|
|
185
|
+
});
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
async dequeue(threadId: string): Promise<QueueEntry | null> {
|
|
189
|
+
return await getDbClient().transaction(async (tx) => {
|
|
190
|
+
const row = await tx.get<{ id: number; entry: string }>(
|
|
191
|
+
"SELECT id, entry FROM chat_queue WHERE thread_id = ? ORDER BY id ASC LIMIT 1",
|
|
192
|
+
[threadId],
|
|
193
|
+
);
|
|
194
|
+
if (!row) return null;
|
|
195
|
+
await tx.run("DELETE FROM chat_queue WHERE id = ?", [row.id]);
|
|
196
|
+
return decode<QueueEntry>(row.entry);
|
|
197
|
+
});
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
async queueDepth(threadId: string): Promise<number> {
|
|
201
|
+
const row = await getDbClient().get<{ depth: number }>(
|
|
202
|
+
"SELECT COUNT(*) AS depth FROM chat_queue WHERE thread_id = ?",
|
|
203
|
+
[threadId],
|
|
204
|
+
);
|
|
205
|
+
return row?.depth ?? 0;
|
|
206
|
+
}
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
/**
|
|
210
|
+
* Delete expired state rows. Called from the Phase 3 watcher tick; exported
|
|
211
|
+
* now for tests.
|
|
212
|
+
*/
|
|
213
|
+
export async function sweepExpired(now: number = Date.now()): Promise<void> {
|
|
214
|
+
await getDbClient().transaction(async (tx) => {
|
|
215
|
+
await tx.run("DELETE FROM chat_state_kv WHERE expires_at IS NOT NULL AND expires_at <= ?", [
|
|
216
|
+
now,
|
|
217
|
+
]);
|
|
218
|
+
await tx.run("DELETE FROM chat_lists WHERE expires_at IS NOT NULL AND expires_at <= ?", [now]);
|
|
219
|
+
await tx.run("DELETE FROM chat_locks WHERE expires_at <= ?", [now]);
|
|
220
|
+
});
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
/**
|
|
224
|
+
* Key prefix of the SDK `ThreadHistoryCache` (chat 4.41): the per-thread
|
|
225
|
+
* message list for adapters with `persistThreadHistory` (Telegram, WhatsApp).
|
|
226
|
+
* Pinned by `chat-sdk-commands.test.ts` against the real SDK.
|
|
227
|
+
*/
|
|
228
|
+
export const SDK_THREAD_HISTORY_PREFIX = "msg-history:";
|
|
229
|
+
|
|
230
|
+
/**
|
|
231
|
+
* The thread history the SDK persisted here, oldest first. The adapters that
|
|
232
|
+
* persist it answer `fetchMessages` from process memory, and the SDK reads this
|
|
233
|
+
* store only when that memory is empty. After a restart the memory holds just
|
|
234
|
+
* the newest message, so callers merge both sources themselves.
|
|
235
|
+
*/
|
|
236
|
+
export async function readPersistedThreadHistory(threadId: string): Promise<Message[]> {
|
|
237
|
+
const rows = await new SqliteChatState().getList<SerializedMessage>(
|
|
238
|
+
`${SDK_THREAD_HISTORY_PREFIX}${threadId}`,
|
|
239
|
+
);
|
|
240
|
+
if (rows.length === 0) return [];
|
|
241
|
+
const { Message: MessageClass } = await import("chat");
|
|
242
|
+
return rows.map((row) => MessageClass.fromJSON(row));
|
|
243
|
+
}
|
|
@@ -0,0 +1,244 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
import { getPublicMcpBaseUrl } from "@/utils/constants";
|
|
3
|
+
import { isEnvFlagEnabled } from "@/utils/env-flag";
|
|
4
|
+
import { scrubSecrets } from "@/utils/secret-scrubber";
|
|
5
|
+
import { resolveAdapterApiUrl } from "./adapters";
|
|
6
|
+
import { CHAT_SDK_ADAPTER_NAMES, type ChatSdkAdapterName, getChatSdkConfig } from "./config";
|
|
7
|
+
import { fetchDiscordGuilds } from "./discord-api";
|
|
8
|
+
import { getChatSdkHealth } from "./runtime";
|
|
9
|
+
import { isAdapterEntryEnabled } from "./schema";
|
|
10
|
+
import { callTelegramBotApi, TelegramBotApiError } from "./telegram-api";
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* Read-only Chat SDK status for the dashboard card, and the Telegram
|
|
14
|
+
* register-webhook action. Never returns secret values: configured state comes
|
|
15
|
+
* from adapter names and the non-secret `mode` field only.
|
|
16
|
+
*/
|
|
17
|
+
|
|
18
|
+
export const ChatSdkStatusSchema = z.object({
|
|
19
|
+
enabled: z.boolean(),
|
|
20
|
+
slackClash: z.boolean(),
|
|
21
|
+
/** Config problem that stops every adapter from starting (bad JSON, missing `$config` row). */
|
|
22
|
+
error: z.string().optional(),
|
|
23
|
+
adapters: z.array(
|
|
24
|
+
z.object({
|
|
25
|
+
name: z.enum(CHAT_SDK_ADAPTER_NAMES),
|
|
26
|
+
configured: z.boolean(),
|
|
27
|
+
/** False when the entry carries `enabled: false`: configured and kept, not started. */
|
|
28
|
+
enabled: z.boolean(),
|
|
29
|
+
/** `incomplete`: present in `CHAT_SDK_ADAPTERS` with a missing field or an unresolvable `$config`; `error` names it. */
|
|
30
|
+
health: z.enum(["ok", "error", "disabled", "incomplete"]),
|
|
31
|
+
error: z.string().optional(),
|
|
32
|
+
webhookUrl: z.string(),
|
|
33
|
+
mode: z.string().optional(),
|
|
34
|
+
gateway: z
|
|
35
|
+
.object({
|
|
36
|
+
state: z.enum(["running", "backoff", "stopped"]),
|
|
37
|
+
since: z.string(),
|
|
38
|
+
restarts: z.number(),
|
|
39
|
+
})
|
|
40
|
+
.optional(),
|
|
41
|
+
/** Discord only, while the Gateway runs: the servers the bot was invited to. */
|
|
42
|
+
guilds: z.object({ count: z.number(), names: z.array(z.string()) }).optional(),
|
|
43
|
+
}),
|
|
44
|
+
),
|
|
45
|
+
});
|
|
46
|
+
|
|
47
|
+
export type ChatSdkStatus = z.infer<typeof ChatSdkStatusSchema>;
|
|
48
|
+
export type ChatSdkAdapterStatus = ChatSdkStatus["adapters"][number];
|
|
49
|
+
|
|
50
|
+
/** Schema defaults, repeated here because the raw JSON may omit `mode`. */
|
|
51
|
+
const DEFAULT_MODES: Partial<Record<ChatSdkAdapterName, string>> = {
|
|
52
|
+
telegram: "webhook",
|
|
53
|
+
slack: "webhook",
|
|
54
|
+
};
|
|
55
|
+
|
|
56
|
+
const NOT_RUNNING =
|
|
57
|
+
"The adapter is starting, or the last configuration save is still applying. Refresh in a few seconds.";
|
|
58
|
+
|
|
59
|
+
const SLACK_CLASH_CAUSE =
|
|
60
|
+
"the Slack adapter conflicts with the built-in Slack integration (set SLACK_DISABLE=true)";
|
|
61
|
+
|
|
62
|
+
/** Bound on error text in the status payload. Adapter and parser errors can be long. */
|
|
63
|
+
const MAX_STATUS_ERROR_CHARS = 300;
|
|
64
|
+
|
|
65
|
+
function statusError(message: string): string {
|
|
66
|
+
return scrubSecrets(message).slice(0, MAX_STATUS_ERROR_CHARS);
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
/**
|
|
70
|
+
* The raw `CHAT_SDK_ADAPTERS` object, without validation or `$config`
|
|
71
|
+
* resolution. Lets the card show which adapters an operator configured even
|
|
72
|
+
* while the config fails to resolve. Empty on invalid JSON.
|
|
73
|
+
*/
|
|
74
|
+
function readRawAdapters(): Partial<Record<ChatSdkAdapterName, Record<string, unknown>>> {
|
|
75
|
+
const raw = process.env.CHAT_SDK_ADAPTERS?.trim();
|
|
76
|
+
if (!raw) return {};
|
|
77
|
+
try {
|
|
78
|
+
const parsed = JSON.parse(raw) as unknown;
|
|
79
|
+
if (parsed === null || typeof parsed !== "object" || Array.isArray(parsed)) return {};
|
|
80
|
+
const out: Partial<Record<ChatSdkAdapterName, Record<string, unknown>>> = {};
|
|
81
|
+
for (const name of CHAT_SDK_ADAPTER_NAMES) {
|
|
82
|
+
const value = (parsed as Record<string, unknown>)[name];
|
|
83
|
+
if (value !== null && typeof value === "object" && !Array.isArray(value)) {
|
|
84
|
+
out[name] = value as Record<string, unknown>;
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
return out;
|
|
88
|
+
} catch {
|
|
89
|
+
return {};
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
export function chatSdkWebhookUrl(name: ChatSdkAdapterName): string {
|
|
94
|
+
return `${getPublicMcpBaseUrl()}/api/integrations/chat/${name}/webhook`;
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
/**
|
|
98
|
+
* The Discord bot's servers, only while its Gateway runs (a bot that is in no
|
|
99
|
+
* server is the usual "it does not answer" cause). The token comes from the
|
|
100
|
+
* resolved config, never from the status payload.
|
|
101
|
+
*/
|
|
102
|
+
async function discordGuildsFor(
|
|
103
|
+
config: Awaited<ReturnType<typeof getChatSdkConfig>>,
|
|
104
|
+
health: ReturnType<typeof getChatSdkHealth>["discord"],
|
|
105
|
+
): Promise<ChatSdkAdapterStatus["guilds"]> {
|
|
106
|
+
if (health?.status !== "ok" || health.gateway?.state !== "running") return undefined;
|
|
107
|
+
const raw = config.adapters.discord?.botToken;
|
|
108
|
+
const token = typeof raw === "function" ? await (raw as () => Promise<unknown>)() : raw;
|
|
109
|
+
if (typeof token !== "string" || token.length === 0) return undefined;
|
|
110
|
+
return fetchDiscordGuilds(token);
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
export async function getChatSdkStatus(): Promise<ChatSdkStatus> {
|
|
114
|
+
const config = await getChatSdkConfig();
|
|
115
|
+
const raw = readRawAdapters();
|
|
116
|
+
const runtimeHealth = getChatSdkHealth();
|
|
117
|
+
const slackClash = raw.slack !== undefined && !isEnvFlagEnabled("SLACK_DISABLE", false);
|
|
118
|
+
// A config error or a Slack clash stops `startChatSdk` before any adapter.
|
|
119
|
+
const blockedCause =
|
|
120
|
+
config.error ?? (config.enabled && slackClash ? SLACK_CLASH_CAUSE : undefined);
|
|
121
|
+
const discordGuilds = await discordGuildsFor(config, runtimeHealth.discord);
|
|
122
|
+
const adapters = CHAT_SDK_ADAPTER_NAMES.map((name): ChatSdkAdapterStatus => {
|
|
123
|
+
const configured = raw[name] !== undefined;
|
|
124
|
+
const enabled = isAdapterEntryEnabled(raw[name]);
|
|
125
|
+
const current = runtimeHealth[name];
|
|
126
|
+
const incomplete = config.incomplete?.[name];
|
|
127
|
+
const rawMode = raw[name]?.mode;
|
|
128
|
+
const mode =
|
|
129
|
+
typeof rawMode === "string" ? rawMode : configured ? DEFAULT_MODES[name] : undefined;
|
|
130
|
+
let health: ChatSdkAdapterStatus["health"] = "disabled";
|
|
131
|
+
let error: string | undefined;
|
|
132
|
+
if (current) {
|
|
133
|
+
health = current.status;
|
|
134
|
+
// A Discord Gateway in backoff keeps health `ok` (the webhook still
|
|
135
|
+
// works); its last warning says why the listener keeps failing.
|
|
136
|
+
const showWarning = current.status === "error" || current.gateway?.state === "backoff";
|
|
137
|
+
error = current.error ?? (showWarning ? current.lastWarning : undefined);
|
|
138
|
+
} else if (incomplete) {
|
|
139
|
+
// Saved with a missing field, or a `$config` row that does not exist.
|
|
140
|
+
health = "incomplete";
|
|
141
|
+
error = incomplete;
|
|
142
|
+
} else if (configured && !enabled) {
|
|
143
|
+
error = "Turned off on the card. The configuration is kept.";
|
|
144
|
+
} else if (config.enabled && configured) {
|
|
145
|
+
error = blockedCause ? `Not started: ${blockedCause}` : NOT_RUNNING;
|
|
146
|
+
}
|
|
147
|
+
return {
|
|
148
|
+
name,
|
|
149
|
+
configured,
|
|
150
|
+
enabled,
|
|
151
|
+
health,
|
|
152
|
+
...(error ? { error: statusError(error) } : {}),
|
|
153
|
+
webhookUrl: chatSdkWebhookUrl(name),
|
|
154
|
+
...(mode ? { mode } : {}),
|
|
155
|
+
...(current?.gateway ? { gateway: current.gateway } : {}),
|
|
156
|
+
...(name === "discord" && discordGuilds ? { guilds: discordGuilds } : {}),
|
|
157
|
+
};
|
|
158
|
+
});
|
|
159
|
+
return {
|
|
160
|
+
enabled: config.enabled,
|
|
161
|
+
slackClash,
|
|
162
|
+
...(config.error ? { error: statusError(config.error) } : {}),
|
|
163
|
+
adapters,
|
|
164
|
+
};
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
/** `getWebhookInfo` result as Telegram returns it (subset; unknown keys pass through). */
|
|
168
|
+
export type TelegramWebhookInfo = Record<string, unknown> & { url: string };
|
|
169
|
+
|
|
170
|
+
export class ChatSdkActionError extends Error {
|
|
171
|
+
constructor(
|
|
172
|
+
message: string,
|
|
173
|
+
readonly status: 400 | 502,
|
|
174
|
+
) {
|
|
175
|
+
super(message);
|
|
176
|
+
this.name = "ChatSdkActionError";
|
|
177
|
+
}
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
async function callBotApi(
|
|
181
|
+
baseUrl: string,
|
|
182
|
+
token: string,
|
|
183
|
+
method: string,
|
|
184
|
+
body: Record<string, unknown>,
|
|
185
|
+
): Promise<unknown> {
|
|
186
|
+
try {
|
|
187
|
+
return await callTelegramBotApi(baseUrl, token, method, body);
|
|
188
|
+
} catch (error) {
|
|
189
|
+
if (error instanceof TelegramBotApiError) throw new ChatSdkActionError(error.message, 502);
|
|
190
|
+
throw error;
|
|
191
|
+
}
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
/**
|
|
195
|
+
* Point the Telegram bot at this swarm's public webhook URL with the
|
|
196
|
+
* configured secret token, then return `getWebhookInfo`. The Bot API base is
|
|
197
|
+
* the one the adapter uses (`resolveAdapterApiUrl`), so tests and local QA hit
|
|
198
|
+
* the fake Bot API.
|
|
199
|
+
*/
|
|
200
|
+
export async function registerTelegramWebhook(): Promise<{
|
|
201
|
+
webhookUrl: string;
|
|
202
|
+
webhookInfo: TelegramWebhookInfo;
|
|
203
|
+
}> {
|
|
204
|
+
const config = await getChatSdkConfig();
|
|
205
|
+
if (!config.enabled) {
|
|
206
|
+
throw new ChatSdkActionError(
|
|
207
|
+
"The Chat SDK channel is disabled (CHAT_SDK_ENABLED is not true). Enable it and restart the API first",
|
|
208
|
+
400,
|
|
209
|
+
);
|
|
210
|
+
}
|
|
211
|
+
if (config.error) throw new ChatSdkActionError(statusError(config.error), 400);
|
|
212
|
+
const telegram = config.adapters.telegram;
|
|
213
|
+
if (!telegram) {
|
|
214
|
+
throw new ChatSdkActionError("Telegram is not configured in CHAT_SDK_ADAPTERS", 400);
|
|
215
|
+
}
|
|
216
|
+
if (telegram.mode !== "webhook") {
|
|
217
|
+
throw new ChatSdkActionError(
|
|
218
|
+
"Telegram runs in polling mode; a registered webhook would stop polling",
|
|
219
|
+
400,
|
|
220
|
+
);
|
|
221
|
+
}
|
|
222
|
+
const secretToken = telegram.secretToken;
|
|
223
|
+
if (typeof secretToken !== "string" || secretToken.length === 0) {
|
|
224
|
+
throw new ChatSdkActionError("Telegram webhook mode needs a secretToken", 400);
|
|
225
|
+
}
|
|
226
|
+
const rawToken = telegram.botToken;
|
|
227
|
+
const token =
|
|
228
|
+
typeof rawToken === "function" ? await (rawToken as () => Promise<string>)() : rawToken;
|
|
229
|
+
if (typeof token !== "string" || token.length === 0) {
|
|
230
|
+
throw new ChatSdkActionError("Telegram botToken is empty", 400);
|
|
231
|
+
}
|
|
232
|
+
const baseUrl = resolveAdapterApiUrl("telegram");
|
|
233
|
+
const webhookUrl = chatSdkWebhookUrl("telegram");
|
|
234
|
+
await callBotApi(baseUrl, token, "setWebhook", { url: webhookUrl, secret_token: secretToken });
|
|
235
|
+
const info = await callBotApi(baseUrl, token, "getWebhookInfo", {});
|
|
236
|
+
if (
|
|
237
|
+
info === null ||
|
|
238
|
+
typeof info !== "object" ||
|
|
239
|
+
typeof (info as { url?: unknown }).url !== "string"
|
|
240
|
+
) {
|
|
241
|
+
throw new ChatSdkActionError("Telegram getWebhookInfo returned an unexpected result", 502);
|
|
242
|
+
}
|
|
243
|
+
return { webhookUrl, webhookInfo: info as TelegramWebhookInfo };
|
|
244
|
+
}
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
import { getLatestLeadTaskByContextKey } from "@/be/db";
|
|
2
|
+
import { requestSteering, SteeringRequestError } from "@/be/steering";
|
|
3
|
+
import type { AgentTask, SteerResult } from "@/types";
|
|
4
|
+
import { scrubSecrets } from "@/utils/secret-scrubber";
|
|
5
|
+
import { isSteeringEnabled } from "@/utils/steering-enabled";
|
|
6
|
+
|
|
7
|
+
export interface ChatThreadSteeringResult {
|
|
8
|
+
task: AgentTask;
|
|
9
|
+
result: SteerResult;
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* Steer the lead's in-progress task on this thread with a new message. Same
|
|
14
|
+
* rule as the Slack default (`SLACK_THREAD_STEERING=lead`, queue mode) as
|
|
15
|
+
* constants: `isSteeringEnabled()` is the only switch. Returns null when the
|
|
16
|
+
* caller should buffer the message into a follow-up task instead.
|
|
17
|
+
*/
|
|
18
|
+
export async function requestChatThreadSteering(args: {
|
|
19
|
+
contextKey: string;
|
|
20
|
+
message: string;
|
|
21
|
+
}): Promise<ChatThreadSteeringResult | null> {
|
|
22
|
+
if (!isSteeringEnabled()) return null;
|
|
23
|
+
|
|
24
|
+
const task = await getLatestLeadTaskByContextKey(args.contextKey, "chat");
|
|
25
|
+
if (!task || task.status !== "in_progress") return null;
|
|
26
|
+
|
|
27
|
+
try {
|
|
28
|
+
const result = await requestSteering({
|
|
29
|
+
taskId: task.id,
|
|
30
|
+
message: args.message,
|
|
31
|
+
mode: "queue",
|
|
32
|
+
onUnsupported: "degrade",
|
|
33
|
+
source: "chat",
|
|
34
|
+
createdByKind: "user",
|
|
35
|
+
});
|
|
36
|
+
return { task, result };
|
|
37
|
+
} catch (error) {
|
|
38
|
+
const detail = error instanceof Error ? error.message : String(error);
|
|
39
|
+
const what = error instanceof SteeringRequestError ? "was refused" : "failed";
|
|
40
|
+
console.warn(
|
|
41
|
+
scrubSecrets(
|
|
42
|
+
`[chat-sdk] Steering task ${task.id} ${what} (${detail}); creating a follow-up task`,
|
|
43
|
+
),
|
|
44
|
+
);
|
|
45
|
+
return null;
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
/** Thread acknowledgement for the steering outcome. Constants, not prompts. */
|
|
50
|
+
export function formatChatSteeringAck(result: SteerResult): string {
|
|
51
|
+
if (result.outcome === "promoted") return "_Your message was queued as a follow-up task._";
|
|
52
|
+
if (result.degradedFrom) {
|
|
53
|
+
return "_Interrupt steering is unavailable for this task, so your message was queued._";
|
|
54
|
+
}
|
|
55
|
+
return result.outcome === "steered"
|
|
56
|
+
? "_Your message was sent to the active task._"
|
|
57
|
+
: "_Your message was queued for the active task._";
|
|
58
|
+
}
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
import { readApiUrlOverrides } from "./config";
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Bot Framework Connector hosts for Teams: public cloud, GCC, GCC High, DoD
|
|
5
|
+
* and the China cloud. The same set as `TRUSTED_CONNECTOR_HOSTS` in
|
|
6
|
+
* `@chat-adapter/teams` (its standalone connector API), minus the loopback
|
|
7
|
+
* Emulator exception. The adapter's own send path does not check the host.
|
|
8
|
+
*/
|
|
9
|
+
const TRUSTED_TEAMS_SERVICE_HOSTS = new Set([
|
|
10
|
+
"smba.trafficmanager.net",
|
|
11
|
+
"smba.infra.gcc.teams.microsoft.com",
|
|
12
|
+
"smba.infra.gov.teams.microsoft.us",
|
|
13
|
+
"smba.infra.dod.teams.microsoft.us",
|
|
14
|
+
"msteams.botframework.azure.cn",
|
|
15
|
+
]);
|
|
16
|
+
|
|
17
|
+
/** A Teams service URL that is not a Bot Framework host. Nothing was sent. */
|
|
18
|
+
export class UntrustedTeamsServiceUrlError extends Error {
|
|
19
|
+
constructor(detail: string) {
|
|
20
|
+
super(`Refusing to send the Teams bot token: ${detail}`);
|
|
21
|
+
this.name = "UntrustedTeamsServiceUrlError";
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
function originOf(value: string): string | undefined {
|
|
26
|
+
try {
|
|
27
|
+
return new URL(value).origin;
|
|
28
|
+
} catch {
|
|
29
|
+
return undefined;
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
/**
|
|
34
|
+
* The Teams adapter sends the bot token to the service URL of the target
|
|
35
|
+
* conversation. Trusted: https on a Bot Framework host, or the origin of the
|
|
36
|
+
* env-only `CHAT_SDK_API_URL_OVERRIDES.teams` (tests and emulators). Every
|
|
37
|
+
* other origin, loopback included, is refused.
|
|
38
|
+
*/
|
|
39
|
+
export function isTrustedTeamsServiceUrl(serviceUrl: string): boolean {
|
|
40
|
+
let url: URL;
|
|
41
|
+
try {
|
|
42
|
+
url = new URL(serviceUrl);
|
|
43
|
+
} catch {
|
|
44
|
+
return false;
|
|
45
|
+
}
|
|
46
|
+
if (url.protocol === "https:" && TRUSTED_TEAMS_SERVICE_HOSTS.has(url.hostname.toLowerCase())) {
|
|
47
|
+
return true;
|
|
48
|
+
}
|
|
49
|
+
const override = readApiUrlOverrides().teams;
|
|
50
|
+
return override !== undefined && originOf(override) === url.origin;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
/**
|
|
54
|
+
* Throw `UntrustedTeamsServiceUrlError` unless the Teams thread or channel id
|
|
55
|
+
* (`teams:<base64url conversation>:<base64url serviceUrl>[:<type>]`) carries a
|
|
56
|
+
* trusted service URL. Call it before every Teams call that takes an id from
|
|
57
|
+
* outside the adapter. Ids of other adapters pass.
|
|
58
|
+
*/
|
|
59
|
+
export function assertTrustedChatTarget(id: string): void {
|
|
60
|
+
const parts = id.split(":");
|
|
61
|
+
if (parts[0] !== "teams") return;
|
|
62
|
+
const encoded = parts[2];
|
|
63
|
+
if ((parts.length !== 3 && parts.length !== 4) || !encoded) {
|
|
64
|
+
throw new UntrustedTeamsServiceUrlError("the Teams id is malformed");
|
|
65
|
+
}
|
|
66
|
+
assertTrustedTeamsServiceUrl(Buffer.from(encoded, "base64url").toString("utf-8"));
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
export function assertTrustedTeamsServiceUrl(serviceUrl: string): void {
|
|
70
|
+
if (isTrustedTeamsServiceUrl(serviceUrl)) return;
|
|
71
|
+
const origin = originOf(serviceUrl);
|
|
72
|
+
throw new UntrustedTeamsServiceUrlError(
|
|
73
|
+
origin && origin !== "null"
|
|
74
|
+
? `${origin} is not a Microsoft Bot Framework service host`
|
|
75
|
+
: "the service URL is not a valid URL",
|
|
76
|
+
);
|
|
77
|
+
}
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
import { scrubSecrets } from "@/utils/secret-scrubber";
|
|
2
|
+
import { errorMessage } from "./logger";
|
|
3
|
+
|
|
4
|
+
const TELEGRAM_TIMEOUT_MS = 10_000;
|
|
5
|
+
|
|
6
|
+
/** A Bot API call that did not return `ok: true`. The message never carries the token. */
|
|
7
|
+
export class TelegramBotApiError extends Error {
|
|
8
|
+
constructor(message: string) {
|
|
9
|
+
super(message);
|
|
10
|
+
this.name = "TelegramBotApiError";
|
|
11
|
+
}
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* One Telegram Bot API call. Shared by the register-webhook action and the
|
|
16
|
+
* command registration at adapter start, so both hit the same base URL
|
|
17
|
+
* (`resolveAdapterApiUrl`) and scrub the token out of every error.
|
|
18
|
+
*/
|
|
19
|
+
export async function callTelegramBotApi(
|
|
20
|
+
baseUrl: string,
|
|
21
|
+
token: string,
|
|
22
|
+
method: string,
|
|
23
|
+
body: Record<string, unknown>,
|
|
24
|
+
): Promise<unknown> {
|
|
25
|
+
let response: Response;
|
|
26
|
+
try {
|
|
27
|
+
response = await fetch(`${baseUrl.replace(/\/+$/, "")}/bot${token}/${method}`, {
|
|
28
|
+
method: "POST",
|
|
29
|
+
headers: { "content-type": "application/json" },
|
|
30
|
+
body: JSON.stringify(body),
|
|
31
|
+
signal: AbortSignal.timeout(TELEGRAM_TIMEOUT_MS),
|
|
32
|
+
});
|
|
33
|
+
} catch (error) {
|
|
34
|
+
if (error instanceof Error && error.name === "TimeoutError") {
|
|
35
|
+
throw new TelegramBotApiError(
|
|
36
|
+
`Telegram ${method} failed: no response from the Bot API within ${TELEGRAM_TIMEOUT_MS / 1000} s`,
|
|
37
|
+
);
|
|
38
|
+
}
|
|
39
|
+
// Fetch errors can echo the URL, which carries the bot token.
|
|
40
|
+
throw new TelegramBotApiError(
|
|
41
|
+
`Telegram ${method} failed: ${scrubSecrets(errorMessage(error).replaceAll(token, "[REDACTED]"))}`,
|
|
42
|
+
);
|
|
43
|
+
}
|
|
44
|
+
const payload = (await response.json().catch(() => null)) as {
|
|
45
|
+
ok?: boolean;
|
|
46
|
+
result?: unknown;
|
|
47
|
+
description?: string;
|
|
48
|
+
} | null;
|
|
49
|
+
if (!response.ok || !payload?.ok) {
|
|
50
|
+
const description = payload?.description ?? `HTTP ${response.status}`;
|
|
51
|
+
throw new TelegramBotApiError(
|
|
52
|
+
`Telegram ${method} failed: ${scrubSecrets(description.replaceAll(token, "[REDACTED]"))}`,
|
|
53
|
+
);
|
|
54
|
+
}
|
|
55
|
+
return payload.result;
|
|
56
|
+
}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import { registerTemplate } from "@/prompts/registry";
|
|
2
|
+
|
|
3
|
+
registerTemplate({
|
|
4
|
+
eventType: "chat.message.thread_context",
|
|
5
|
+
header: "",
|
|
6
|
+
defaultBody: `<thread_context>
|
|
7
|
+
{{thread_messages}}
|
|
8
|
+
</thread_context>`,
|
|
9
|
+
variables: [
|
|
10
|
+
{
|
|
11
|
+
name: "thread_messages",
|
|
12
|
+
description: "Formatted Chat SDK thread messages (user: text pairs)",
|
|
13
|
+
},
|
|
14
|
+
],
|
|
15
|
+
category: "system",
|
|
16
|
+
});
|