@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,351 @@
|
|
|
1
|
+
import { createHash } from "node:crypto";
|
|
2
|
+
import type { Chat } from "chat";
|
|
3
|
+
import { isEnvFlagEnabled } from "@/utils/env-flag";
|
|
4
|
+
import { registerVolatileSecret, scrubSecrets } from "@/utils/secret-scrubber";
|
|
5
|
+
import { loadAdapter, watchSocketConnection } from "./adapters";
|
|
6
|
+
import { drainBackground } from "./background";
|
|
7
|
+
import { registerTelegramCommands } from "./commands";
|
|
8
|
+
import {
|
|
9
|
+
assertNoSlackClash,
|
|
10
|
+
CHAT_SDK_ADAPTER_NAMES,
|
|
11
|
+
type ChatSdkAdapterName,
|
|
12
|
+
type ChatSdkConfig,
|
|
13
|
+
getChatSdkConfig,
|
|
14
|
+
type ResolvedAdapterConfig,
|
|
15
|
+
} from "./config";
|
|
16
|
+
import {
|
|
17
|
+
type DiscordGatewayHealth,
|
|
18
|
+
getDiscordGatewayHealth,
|
|
19
|
+
isDiscordGatewayListener,
|
|
20
|
+
resetDiscordGatewayHealth,
|
|
21
|
+
startDiscordGateway,
|
|
22
|
+
stopDiscordGateway,
|
|
23
|
+
} from "./discord-gateway";
|
|
24
|
+
import { registerChatHandlers } from "./handlers";
|
|
25
|
+
import { createScrubbingLogger, errorMessage } from "./logger";
|
|
26
|
+
import { startChatRelayWatcher, stopChatRelayWatcher } from "./relay";
|
|
27
|
+
import { SqliteChatState } from "./state";
|
|
28
|
+
import { getChatSdkTestHooks } from "./test-hooks";
|
|
29
|
+
import { flushAllChatBuffers } from "./thread-buffer";
|
|
30
|
+
import { stopAllTypingKeepalives } from "./typing";
|
|
31
|
+
|
|
32
|
+
export interface ChatAdapterHealth {
|
|
33
|
+
status: "ok" | "error";
|
|
34
|
+
error?: string;
|
|
35
|
+
/** Last warn/error line the adapter logged, scrubbed. */
|
|
36
|
+
lastWarning?: string;
|
|
37
|
+
since: string;
|
|
38
|
+
/** Discord only: the supervised Gateway listener loop. */
|
|
39
|
+
gateway?: DiscordGatewayHealth;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
/**
|
|
43
|
+
* One `Chat` per adapter. `Chat.initialize()` runs every adapter under one
|
|
44
|
+
* `Promise.all` and caches a failure forever, so a shared instance would let one
|
|
45
|
+
* broken adapter take every other adapter down. Health lives here, outside
|
|
46
|
+
* `Chat`, for the same reason.
|
|
47
|
+
*/
|
|
48
|
+
const instances = new Map<ChatSdkAdapterName, Chat>();
|
|
49
|
+
const health = new Map<ChatSdkAdapterName, ChatAdapterHealth>();
|
|
50
|
+
let stopping = false;
|
|
51
|
+
/** Fingerprint of the config the last `startChatSdk` applied. */
|
|
52
|
+
let appliedFingerprint: string | undefined;
|
|
53
|
+
|
|
54
|
+
/** True while shutdown drains; the webhook path refuses new work. */
|
|
55
|
+
export function isChatSdkStopping(): boolean {
|
|
56
|
+
return stopping;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
const DEFAULT_CHAT_USER_NAME = "swarm";
|
|
60
|
+
|
|
61
|
+
/** Total wait for every step of `stopChatSdk`, inside the 15 s SIGTERM budget. */
|
|
62
|
+
const SHUTDOWN_BUDGET_MS = 10_000;
|
|
63
|
+
/** A config reload waits this long for the adapters to come back before it answers. */
|
|
64
|
+
const RELOAD_START_BUDGET_MS = 15_000;
|
|
65
|
+
|
|
66
|
+
/** Fields whose string value is a credential, registered with the scrubber. */
|
|
67
|
+
const SECRET_FIELDS = ["botToken", "secretToken", "signingSecret", "appToken", "appPassword"];
|
|
68
|
+
|
|
69
|
+
function setHealth(
|
|
70
|
+
name: ChatSdkAdapterName,
|
|
71
|
+
status: "ok" | "error",
|
|
72
|
+
error: string | undefined,
|
|
73
|
+
lastWarning: string | undefined,
|
|
74
|
+
): void {
|
|
75
|
+
health.set(name, { status, error, lastWarning, since: new Date().toISOString() });
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
/**
|
|
79
|
+
* Raw values in `CHAT_SDK_ADAPTERS` (env-only deployments) are not named by a
|
|
80
|
+
* sensitive env key, so the scrubber would not know them. `$config`-resolved
|
|
81
|
+
* secret rows are already registered by `loadGlobalConfigsIntoEnv`.
|
|
82
|
+
*/
|
|
83
|
+
async function registerAdapterSecrets(
|
|
84
|
+
name: ChatSdkAdapterName,
|
|
85
|
+
config: ResolvedAdapterConfig,
|
|
86
|
+
): Promise<void> {
|
|
87
|
+
for (const field of SECRET_FIELDS) {
|
|
88
|
+
const raw = config[field];
|
|
89
|
+
const value = typeof raw === "function" ? await (raw as () => Promise<string>)() : raw;
|
|
90
|
+
if (typeof value === "string") {
|
|
91
|
+
registerVolatileSecret(value, `chat-sdk:${name}:${field}`);
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
async function startAdapter(
|
|
97
|
+
name: ChatSdkAdapterName,
|
|
98
|
+
config: ResolvedAdapterConfig,
|
|
99
|
+
state: SqliteChatState,
|
|
100
|
+
ChatClass: typeof Chat,
|
|
101
|
+
): Promise<void> {
|
|
102
|
+
let lastWarning: string | undefined;
|
|
103
|
+
// No level prefix on the stored line: the message already says what failed,
|
|
104
|
+
// and the status payload carries the tone separately.
|
|
105
|
+
const logger = createScrubbingLogger(name, (_level, message) => {
|
|
106
|
+
lastWarning = message;
|
|
107
|
+
const current = health.get(name);
|
|
108
|
+
if (current) health.set(name, { ...current, lastWarning });
|
|
109
|
+
});
|
|
110
|
+
try {
|
|
111
|
+
await registerAdapterSecrets(name, config);
|
|
112
|
+
const adapter = await loadAdapter(name, config, logger);
|
|
113
|
+
const chat = new ChatClass({
|
|
114
|
+
adapters: { [name]: adapter },
|
|
115
|
+
state,
|
|
116
|
+
// Chat requires a userName. The adapter only gets one when the operator
|
|
117
|
+
// set it, so Telegram can still take the real bot name from getMe.
|
|
118
|
+
userName: typeof config.userName === "string" ? config.userName : DEFAULT_CHAT_USER_NAME,
|
|
119
|
+
logger,
|
|
120
|
+
// The swarm buffers bursts itself (Phase 2). The SDK "queue" strategy
|
|
121
|
+
// would keep only the latest queued message and skip the rest.
|
|
122
|
+
concurrency: "concurrent",
|
|
123
|
+
});
|
|
124
|
+
registerChatHandlers(chat);
|
|
125
|
+
instances.set(name, chat);
|
|
126
|
+
await chat.initialize();
|
|
127
|
+
// Shutdown began while this adapter initialized: do not mark it ready or
|
|
128
|
+
// start the Gateway after `stopChatSdk` already stopped it.
|
|
129
|
+
if (stopping) {
|
|
130
|
+
console.log(`[chat-sdk] ${name} initialized during shutdown, not started`);
|
|
131
|
+
return;
|
|
132
|
+
}
|
|
133
|
+
watchSocketConnection(adapter, logger);
|
|
134
|
+
if (name === "telegram") {
|
|
135
|
+
// The `/` menu in Telegram clients. The commands work without it.
|
|
136
|
+
try {
|
|
137
|
+
await registerTelegramCommands(config);
|
|
138
|
+
} catch (error) {
|
|
139
|
+
logger.warn(`Could not register the Telegram commands: ${errorMessage(error)}`);
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
setHealth(name, "ok", undefined, lastWarning);
|
|
143
|
+
console.log(`[chat-sdk] ${name} ready`);
|
|
144
|
+
// Discord messages arrive only over the Gateway, never the webhook.
|
|
145
|
+
if (
|
|
146
|
+
name === "discord" &&
|
|
147
|
+
isDiscordGatewayListener(adapter) &&
|
|
148
|
+
!getChatSdkTestHooks().disableDiscordGateway
|
|
149
|
+
) {
|
|
150
|
+
startDiscordGateway(adapter);
|
|
151
|
+
}
|
|
152
|
+
} catch (error) {
|
|
153
|
+
setHealth(name, "error", errorMessage(error), lastWarning);
|
|
154
|
+
console.error(`[chat-sdk] ${name} failed to start: ${errorMessage(error)}`);
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
/**
|
|
159
|
+
* Boot the Chat SDK channel. Never throws for config or adapter problems: they
|
|
160
|
+
* are logged and recorded in health, and the API keeps booting.
|
|
161
|
+
*/
|
|
162
|
+
export async function startChatSdk(): Promise<void> {
|
|
163
|
+
stopping = false;
|
|
164
|
+
const config = await getChatSdkConfig();
|
|
165
|
+
appliedFingerprint = await configFingerprint(config);
|
|
166
|
+
if (!config.enabled) {
|
|
167
|
+
console.log("[chat-sdk] Disabled (CHAT_SDK_ENABLED is not true)");
|
|
168
|
+
return;
|
|
169
|
+
}
|
|
170
|
+
if (config.error) {
|
|
171
|
+
console.error(`[chat-sdk] Not started: ${scrubSecrets(config.error)}`);
|
|
172
|
+
return;
|
|
173
|
+
}
|
|
174
|
+
try {
|
|
175
|
+
assertNoSlackClash(config);
|
|
176
|
+
} catch (error) {
|
|
177
|
+
console.error(`[chat-sdk] Not started: ${errorMessage(error)}`);
|
|
178
|
+
return;
|
|
179
|
+
}
|
|
180
|
+
for (const [name, reason] of Object.entries(config.incomplete ?? {})) {
|
|
181
|
+
console.warn(`[chat-sdk] ${name} not started, incomplete: ${scrubSecrets(reason)}`);
|
|
182
|
+
}
|
|
183
|
+
const { Chat: ChatClass } = await import("chat");
|
|
184
|
+
const state = new SqliteChatState();
|
|
185
|
+
// In parallel: one slow adapter (Slack's WebClient retries auth.test for
|
|
186
|
+
// up to ~30 min on errors) must not delay the others.
|
|
187
|
+
// Before the adapters: rows for an adapter still initializing are skipped
|
|
188
|
+
// without counting an attempt, and pending rows from before a restart drain
|
|
189
|
+
// as soon as their adapter is up.
|
|
190
|
+
startChatRelayWatcher();
|
|
191
|
+
await Promise.all(
|
|
192
|
+
CHAT_SDK_ADAPTER_NAMES.map(async (name) => {
|
|
193
|
+
const adapterConfig = config.adapters[name];
|
|
194
|
+
if (adapterConfig) await startAdapter(name, adapterConfig, state, ChatClass);
|
|
195
|
+
}),
|
|
196
|
+
);
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
/** Only adapters whose `initialize()` succeeded serve webhooks. */
|
|
200
|
+
export function getChatInstance(name: string): Chat | undefined {
|
|
201
|
+
const adapterName = name as ChatSdkAdapterName;
|
|
202
|
+
return health.get(adapterName)?.status === "ok" ? instances.get(adapterName) : undefined;
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
/** True when the adapter is configured (started, even if it failed). */
|
|
206
|
+
export function isChatAdapterConfigured(name: string): boolean {
|
|
207
|
+
return health.has(name as ChatSdkAdapterName);
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
export function getChatSdkHealth(): Partial<Record<ChatSdkAdapterName, ChatAdapterHealth>> {
|
|
211
|
+
const out: Partial<Record<ChatSdkAdapterName, ChatAdapterHealth>> = Object.fromEntries(health);
|
|
212
|
+
const gateway = getDiscordGatewayHealth();
|
|
213
|
+
if (out.discord && gateway) out.discord = { ...out.discord, gateway };
|
|
214
|
+
return out;
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
/**
|
|
218
|
+
* Wait for `work`, at most `ms`. Never rejects: a rejection is logged, and a
|
|
219
|
+
* timeout logs the abandoned step. Returns true when `work` settled in time.
|
|
220
|
+
*/
|
|
221
|
+
async function withTimeout(work: Promise<unknown>, ms: number, step: string): Promise<boolean> {
|
|
222
|
+
let timer: ReturnType<typeof setTimeout> | undefined;
|
|
223
|
+
const timeout = new Promise<false>((resolve) => {
|
|
224
|
+
timer = setTimeout(() => resolve(false), ms);
|
|
225
|
+
});
|
|
226
|
+
const settled = work.then(
|
|
227
|
+
() => true as const,
|
|
228
|
+
(error: unknown) => {
|
|
229
|
+
console.error(`[chat-sdk] ${step} failed: ${errorMessage(error)}`);
|
|
230
|
+
return true as const;
|
|
231
|
+
},
|
|
232
|
+
);
|
|
233
|
+
try {
|
|
234
|
+
const done = await Promise.race([settled, timeout]);
|
|
235
|
+
if (!done) console.warn(`[chat-sdk] Shutdown budget spent, abandoned: ${step}`);
|
|
236
|
+
return done;
|
|
237
|
+
} finally {
|
|
238
|
+
clearTimeout(timer);
|
|
239
|
+
}
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
/**
|
|
243
|
+
* Stop the channel inside one deadline (`budgetMs`, injectable for tests). Every
|
|
244
|
+
* wait below uses the time that is left, so a stuck platform call cannot hold
|
|
245
|
+
* the API shutdown.
|
|
246
|
+
*/
|
|
247
|
+
export async function stopChatSdk(budgetMs: number = SHUTDOWN_BUDGET_MS): Promise<void> {
|
|
248
|
+
// Ingress first: webhooks answer 503 and refuse new work from here.
|
|
249
|
+
stopping = true;
|
|
250
|
+
stopAllTypingKeepalives();
|
|
251
|
+
const deadline = Date.now() + budgetMs;
|
|
252
|
+
const remaining = (): number => Math.max(0, deadline - Date.now());
|
|
253
|
+
// No new relay tick or claim from here. The wait for an in-flight send comes
|
|
254
|
+
// after the inbound flush, so a stuck send cannot use the flush's budget.
|
|
255
|
+
const relayStopped = stopChatRelayWatcher();
|
|
256
|
+
// Discord has no `disconnect()`, so `chat.shutdown()` would leave the
|
|
257
|
+
// Gateway open. Abort it before the drain: the open window is tracked
|
|
258
|
+
// background work, so the drain bounds the wait for it to close.
|
|
259
|
+
stopDiscordGateway();
|
|
260
|
+
// Let in-flight handlers finish buffering, then turn waiting threads into
|
|
261
|
+
// tasks while adapters and the DB are still up. Buffered messages exist only
|
|
262
|
+
// in memory: threads not flushed in time are lost (logged).
|
|
263
|
+
let drained = await drainBackground(remaining());
|
|
264
|
+
drained =
|
|
265
|
+
(await withTimeout(
|
|
266
|
+
flushAllChatBuffers(),
|
|
267
|
+
remaining(),
|
|
268
|
+
"inbound buffer flush (threads not yet turned into tasks are lost)",
|
|
269
|
+
)) && drained;
|
|
270
|
+
drained = (await drainBackground(remaining())) && drained;
|
|
271
|
+
// The relay posts through the adapters, so it must end before they shut
|
|
272
|
+
// down. An abandoned send keeps its claimed row: it retries after restart.
|
|
273
|
+
await withTimeout(
|
|
274
|
+
relayStopped,
|
|
275
|
+
remaining(),
|
|
276
|
+
"in-flight relay send (its obligation stays pending and retries after restart)",
|
|
277
|
+
);
|
|
278
|
+
await withTimeout(
|
|
279
|
+
Promise.all(
|
|
280
|
+
[...instances].map(async ([name, chat]) => {
|
|
281
|
+
try {
|
|
282
|
+
await chat.shutdown();
|
|
283
|
+
} catch (error) {
|
|
284
|
+
console.error(`[chat-sdk] ${name} shutdown failed: ${errorMessage(error)}`);
|
|
285
|
+
}
|
|
286
|
+
}),
|
|
287
|
+
),
|
|
288
|
+
remaining(),
|
|
289
|
+
"adapter shutdown",
|
|
290
|
+
);
|
|
291
|
+
instances.clear();
|
|
292
|
+
health.clear();
|
|
293
|
+
resetDiscordGatewayHealth();
|
|
294
|
+
if (!drained) {
|
|
295
|
+
console.warn(
|
|
296
|
+
`[chat-sdk] Background handlers still running after the ${budgetMs / 1000}s shutdown budget`,
|
|
297
|
+
);
|
|
298
|
+
}
|
|
299
|
+
}
|
|
300
|
+
|
|
301
|
+
/**
|
|
302
|
+
* Hash of the resolved config plus `SLACK_DISABLE` (the Slack clash input).
|
|
303
|
+
* Resolver fields are functions, so their values are read first: a rotated
|
|
304
|
+
* `$config` secret changes the hash. The hashed JSON holds secrets: never log it.
|
|
305
|
+
*/
|
|
306
|
+
async function configFingerprint(config: ChatSdkConfig): Promise<string> {
|
|
307
|
+
const adapters: Record<string, Record<string, unknown>> = {};
|
|
308
|
+
for (const [name, adapterConfig] of Object.entries(config.adapters)) {
|
|
309
|
+
const fields: Record<string, unknown> = {};
|
|
310
|
+
for (const [key, value] of Object.entries(adapterConfig ?? {})) {
|
|
311
|
+
fields[key] = typeof value === "function" ? await (value as () => Promise<unknown>)() : value;
|
|
312
|
+
}
|
|
313
|
+
adapters[name] = fields;
|
|
314
|
+
}
|
|
315
|
+
const slackDisabled = isEnvFlagEnabled("SLACK_DISABLE", false);
|
|
316
|
+
// Incomplete adapters are not part of the running state, so editing one
|
|
317
|
+
// does not restart the adapters that run.
|
|
318
|
+
const { incomplete: _incomplete, disabled: _disabled, ...rest } = config;
|
|
319
|
+
return createHash("sha256")
|
|
320
|
+
.update(JSON.stringify({ ...rest, adapters, slackDisabled }))
|
|
321
|
+
.digest("hex");
|
|
322
|
+
}
|
|
323
|
+
|
|
324
|
+
/**
|
|
325
|
+
* Config reload hook: restart the adapters when the config differs from the
|
|
326
|
+
* one the last `startChatSdk` applied, so a dashboard save applies without an
|
|
327
|
+
* API restart. Compared against the applied config, not the pre-reload one:
|
|
328
|
+
* `$config` secrets resolve from the DB, which already holds the new row. An
|
|
329
|
+
* equal config leaves Telegram polling and the Discord Gateway alone. Never
|
|
330
|
+
* throws. Returns true when the restart left at least one adapter configured.
|
|
331
|
+
*/
|
|
332
|
+
export async function restartChatSdkIfConfigChanged(): Promise<boolean> {
|
|
333
|
+
try {
|
|
334
|
+
if ((await configFingerprint(await getChatSdkConfig())) === appliedFingerprint) return false;
|
|
335
|
+
console.log("[chat-sdk] Configuration changed, restarting adapters");
|
|
336
|
+
await stopChatSdk();
|
|
337
|
+
// Bounded: a Slack adapter can retry auth.test for a long time, and the
|
|
338
|
+
// reload must not hold every later config save behind it. The start
|
|
339
|
+
// continues in the background after the budget.
|
|
340
|
+
const started = await withTimeout(startChatSdk(), RELOAD_START_BUDGET_MS, "start after reload");
|
|
341
|
+
console.log(
|
|
342
|
+
started
|
|
343
|
+
? `[chat-sdk] Restart done, adapters configured: ${health.size}`
|
|
344
|
+
: "[chat-sdk] Restart still in progress after the reload budget; check the card status",
|
|
345
|
+
);
|
|
346
|
+
return health.size > 0;
|
|
347
|
+
} catch (error) {
|
|
348
|
+
console.error(`[chat-sdk] Restart after configuration change failed: ${errorMessage(error)}`);
|
|
349
|
+
return false;
|
|
350
|
+
}
|
|
351
|
+
}
|
|
@@ -0,0 +1,245 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* `CHAT_SDK_ADAPTERS` schema. Kept free of DB imports because
|
|
5
|
+
* `src/be/swarm-config-guard.ts` (imported by `src/be/db.ts`) validates writes
|
|
6
|
+
* with it; `config.ts` adds the DB-backed resolution on top.
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
export const CHAT_SDK_ADAPTER_NAMES = ["telegram", "slack", "discord", "teams"] as const;
|
|
10
|
+
export type ChatSdkAdapterName = (typeof CHAT_SDK_ADAPTER_NAMES)[number];
|
|
11
|
+
|
|
12
|
+
export const ConfigRefSchema = z.strictObject({ $config: z.string().min(1) });
|
|
13
|
+
|
|
14
|
+
/** A literal string or a `{ "$config": "KEY" }` reference to a swarm_config key. */
|
|
15
|
+
const Str = z.union([z.string().trim().min(1), ConfigRefSchema]);
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* Keys an operator must never set. A base URL plus a `$config` ref would send
|
|
19
|
+
* the referenced secret to an arbitrary host. A verifier or an "unverified"
|
|
20
|
+
* switch bypasses the platform signature check on the public webhook.
|
|
21
|
+
* Mock base URLs come only from the env-only `CHAT_SDK_API_URL_OVERRIDES`.
|
|
22
|
+
*/
|
|
23
|
+
const FORBIDDEN_ADAPTER_KEYS = [
|
|
24
|
+
"apiUrl",
|
|
25
|
+
"apiBaseUrl",
|
|
26
|
+
"webhookVerifier",
|
|
27
|
+
"allowUnverifiedWebhooks",
|
|
28
|
+
] as const;
|
|
29
|
+
|
|
30
|
+
type AdapterShape = z.ZodRawShape;
|
|
31
|
+
|
|
32
|
+
function forbiddenKeyIssues(value: Record<string, unknown>, ctx: z.RefinementCtx): void {
|
|
33
|
+
for (const key of FORBIDDEN_ADAPTER_KEYS) {
|
|
34
|
+
if (key in value) {
|
|
35
|
+
ctx.addIssue({
|
|
36
|
+
code: "custom",
|
|
37
|
+
path: [key],
|
|
38
|
+
message: `${key} is not allowed in CHAT_SDK_ADAPTERS`,
|
|
39
|
+
});
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
/** Fields every adapter has next to its own shape. `enabled: false` keeps the entry but does not start it. */
|
|
45
|
+
const COMMON_SHAPE = {
|
|
46
|
+
userName: z.string().min(1).optional(),
|
|
47
|
+
enabled: z.boolean().optional(),
|
|
48
|
+
};
|
|
49
|
+
|
|
50
|
+
function adapterSchema<S extends AdapterShape>(shape: S) {
|
|
51
|
+
return z.looseObject({ ...shape, ...COMMON_SHAPE }).superRefine(forbiddenKeyIssues);
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
/**
|
|
55
|
+
* The same adapter, with every field optional: the shape a dashboard save may
|
|
56
|
+
* store before the operator has every value (`.partial()` refuses schemas with
|
|
57
|
+
* refinements, so the optional shape is built by hand). Forbidden keys and
|
|
58
|
+
* value formats still apply.
|
|
59
|
+
*/
|
|
60
|
+
function draftShape<S extends AdapterShape>(shape: S): AdapterShape {
|
|
61
|
+
return Object.fromEntries(
|
|
62
|
+
Object.entries(shape).map(([key, field]) => [key, (field as z.ZodType).optional()]),
|
|
63
|
+
);
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
const TELEGRAM_SHAPE = {
|
|
67
|
+
botToken: Str,
|
|
68
|
+
secretToken: Str.optional(),
|
|
69
|
+
// Never the SDK's "auto": auto starts long polling against the real Bot API
|
|
70
|
+
// when no webhook is registered.
|
|
71
|
+
mode: z.enum(["webhook", "polling"]).default("webhook"),
|
|
72
|
+
allowedUserIds: z.array(z.union([z.string(), z.number()])).optional(),
|
|
73
|
+
};
|
|
74
|
+
|
|
75
|
+
const SLACK_SHAPE = {
|
|
76
|
+
botToken: Str,
|
|
77
|
+
signingSecret: Str.optional(),
|
|
78
|
+
appToken: Str.optional(),
|
|
79
|
+
mode: z.enum(["webhook", "socket"]).default("webhook"),
|
|
80
|
+
};
|
|
81
|
+
|
|
82
|
+
export const TelegramAdapterSchema = adapterSchema(TELEGRAM_SHAPE);
|
|
83
|
+
|
|
84
|
+
export const SlackAdapterSchema = adapterSchema(SLACK_SHAPE).superRefine((value, ctx) => {
|
|
85
|
+
// Required here, not left to the adapter: the adapter falls back to
|
|
86
|
+
// SLACK_SIGNING_SECRET / SLACK_APP_TOKEN, which belong to the built-in Slack app.
|
|
87
|
+
if (value.mode === "webhook" && value.signingSecret === undefined) {
|
|
88
|
+
ctx.addIssue({
|
|
89
|
+
code: "custom",
|
|
90
|
+
path: ["signingSecret"],
|
|
91
|
+
message: "signingSecret is required in webhook mode",
|
|
92
|
+
});
|
|
93
|
+
}
|
|
94
|
+
if (value.mode === "socket" && value.appToken === undefined) {
|
|
95
|
+
ctx.addIssue({
|
|
96
|
+
code: "custom",
|
|
97
|
+
path: ["appToken"],
|
|
98
|
+
message: "appToken is required in socket mode",
|
|
99
|
+
});
|
|
100
|
+
}
|
|
101
|
+
});
|
|
102
|
+
|
|
103
|
+
const DISCORD_PUBLIC_KEY_PATTERN = /^[0-9a-fA-F]{64}$/;
|
|
104
|
+
|
|
105
|
+
const DISCORD_SHAPE = {
|
|
106
|
+
botToken: Str,
|
|
107
|
+
publicKey: Str,
|
|
108
|
+
applicationId: Str,
|
|
109
|
+
respondToChannelIds: z.array(z.string()).optional(),
|
|
110
|
+
respondToGlobalMentions: z.boolean().optional(),
|
|
111
|
+
mentionRoleIds: z.array(z.string()).optional(),
|
|
112
|
+
};
|
|
113
|
+
|
|
114
|
+
// A literal key must be the hex Ed25519 public key from the Developer Portal.
|
|
115
|
+
// A `$config` value resolves at startup, where the adapter logs a bad format.
|
|
116
|
+
function discordPublicKeyIssue(value: Record<string, unknown>, ctx: z.RefinementCtx): void {
|
|
117
|
+
if (typeof value.publicKey === "string" && !DISCORD_PUBLIC_KEY_PATTERN.test(value.publicKey)) {
|
|
118
|
+
ctx.addIssue({
|
|
119
|
+
code: "custom",
|
|
120
|
+
path: ["publicKey"],
|
|
121
|
+
message: "publicKey must be 64 hex characters (the application's Ed25519 public key)",
|
|
122
|
+
});
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
export const DiscordAdapterSchema = adapterSchema(DISCORD_SHAPE).superRefine(discordPublicKeyIssue);
|
|
127
|
+
|
|
128
|
+
const TEAMS_SHAPE = {
|
|
129
|
+
appId: Str,
|
|
130
|
+
appPassword: Str,
|
|
131
|
+
appTenantId: Str.optional(),
|
|
132
|
+
appType: z.enum(["MultiTenant", "SingleTenant"]).default("MultiTenant"),
|
|
133
|
+
};
|
|
134
|
+
|
|
135
|
+
export const TeamsAdapterSchema = adapterSchema(TEAMS_SHAPE).superRefine((value, ctx) => {
|
|
136
|
+
// The adapter falls back to TEAMS_APP_TENANT_ID when a single-tenant app has no tenant.
|
|
137
|
+
if (value.appType === "SingleTenant" && value.appTenantId === undefined) {
|
|
138
|
+
ctx.addIssue({
|
|
139
|
+
code: "custom",
|
|
140
|
+
path: ["appTenantId"],
|
|
141
|
+
message: "appTenantId is required for a SingleTenant app",
|
|
142
|
+
});
|
|
143
|
+
}
|
|
144
|
+
});
|
|
145
|
+
|
|
146
|
+
// Strict: a typo'd adapter name must fail loudly, not silently disable it.
|
|
147
|
+
export const ChatSdkAdaptersSchema = z
|
|
148
|
+
.strictObject({
|
|
149
|
+
telegram: TelegramAdapterSchema,
|
|
150
|
+
slack: SlackAdapterSchema,
|
|
151
|
+
discord: DiscordAdapterSchema,
|
|
152
|
+
teams: TeamsAdapterSchema,
|
|
153
|
+
})
|
|
154
|
+
.partial();
|
|
155
|
+
|
|
156
|
+
export type ChatSdkAdapters = z.infer<typeof ChatSdkAdaptersSchema>;
|
|
157
|
+
|
|
158
|
+
/** The full schema of one adapter entry. An entry that fails it is "incomplete". */
|
|
159
|
+
export const CHAT_SDK_ADAPTER_SCHEMAS: Record<ChatSdkAdapterName, z.ZodType> = {
|
|
160
|
+
telegram: TelegramAdapterSchema,
|
|
161
|
+
slack: SlackAdapterSchema,
|
|
162
|
+
discord: DiscordAdapterSchema,
|
|
163
|
+
teams: TeamsAdapterSchema,
|
|
164
|
+
};
|
|
165
|
+
|
|
166
|
+
/**
|
|
167
|
+
* What a `CHAT_SDK_ADAPTERS` write must satisfy: known adapter names, object
|
|
168
|
+
* entries, no forbidden keys, well-formed values. Required fields may be
|
|
169
|
+
* missing (a partial dashboard save); such an adapter does not start and the
|
|
170
|
+
* status reports it as incomplete, while the complete adapters run.
|
|
171
|
+
*/
|
|
172
|
+
export const ChatSdkAdaptersDraftSchema = z
|
|
173
|
+
.strictObject({
|
|
174
|
+
telegram: adapterSchema(draftShape(TELEGRAM_SHAPE)),
|
|
175
|
+
slack: adapterSchema(draftShape(SLACK_SHAPE)),
|
|
176
|
+
discord: adapterSchema(draftShape(DISCORD_SHAPE)).superRefine(discordPublicKeyIssue),
|
|
177
|
+
teams: adapterSchema(draftShape(TEAMS_SHAPE)),
|
|
178
|
+
})
|
|
179
|
+
.partial();
|
|
180
|
+
|
|
181
|
+
function valueAt(input: unknown, path: PropertyKey[]): unknown {
|
|
182
|
+
let current: unknown = input;
|
|
183
|
+
for (const key of path) {
|
|
184
|
+
if (current === null || typeof current !== "object") return undefined;
|
|
185
|
+
current = (current as Record<PropertyKey, unknown>)[key];
|
|
186
|
+
}
|
|
187
|
+
return current;
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
/**
|
|
191
|
+
* `<prefix>.<path>: <message>` for the first issue of a failed parse. With
|
|
192
|
+
* `input`, a missing value reads "required" instead of zod's union text
|
|
193
|
+
* ("Invalid input"), which is what a partial dashboard save produces.
|
|
194
|
+
*/
|
|
195
|
+
export function firstIssueText(
|
|
196
|
+
error: z.ZodError,
|
|
197
|
+
prefix = "CHAT_SDK_ADAPTERS",
|
|
198
|
+
input?: unknown,
|
|
199
|
+
): string {
|
|
200
|
+
const issue = error.issues[0];
|
|
201
|
+
if (!issue) return `${prefix} is invalid`;
|
|
202
|
+
const path = issue.path.length > 0 ? issue.path.join(".") : "(root)";
|
|
203
|
+
const missing =
|
|
204
|
+
input !== undefined && issue.path.length > 0 && valueAt(input, issue.path) === undefined;
|
|
205
|
+
return `${prefix}.${path}: ${missing ? "required" : issue.message}`;
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
/**
|
|
209
|
+
* Field names per adapter, derived from the schemas. The dashboard keeps its
|
|
210
|
+
* own field list (`apps/ui/src/lib/chat-sdk-fields.ts`, it cannot import zod
|
|
211
|
+
* schemas from the API); `src/tests/chat-sdk-fields-sync.test.ts` keeps the two equal.
|
|
212
|
+
*/
|
|
213
|
+
export const CHAT_SDK_ADAPTER_FIELDS: Record<ChatSdkAdapterName, readonly string[]> = {
|
|
214
|
+
telegram: formFields(TelegramAdapterSchema.shape),
|
|
215
|
+
slack: formFields(SlackAdapterSchema.shape),
|
|
216
|
+
discord: formFields(DiscordAdapterSchema.shape),
|
|
217
|
+
teams: formFields(TeamsAdapterSchema.shape),
|
|
218
|
+
};
|
|
219
|
+
|
|
220
|
+
/** `enabled` is the panel switch on the card, not a form field. */
|
|
221
|
+
function formFields(shape: AdapterShape): string[] {
|
|
222
|
+
return Object.keys(shape).filter((key) => key !== "enabled");
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
/** True unless the entry carries `enabled: false`. */
|
|
226
|
+
export function isAdapterEntryEnabled(entry: Record<string, unknown> | undefined): boolean {
|
|
227
|
+
return entry !== undefined && entry.enabled !== false;
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
/**
|
|
231
|
+
* `VALIDATED_KEYS` validator for `CHAT_SDK_ADAPTERS`: the draft shape only
|
|
232
|
+
* (missing required fields are allowed, see `ChatSdkAdaptersDraftSchema`), no
|
|
233
|
+
* reference resolution. Returns the first issue as a string, or null.
|
|
234
|
+
*/
|
|
235
|
+
export function validateChatSdkAdaptersValue(value: unknown): string | null {
|
|
236
|
+
if (typeof value !== "string") return "CHAT_SDK_ADAPTERS must be a JSON string";
|
|
237
|
+
let parsed: unknown;
|
|
238
|
+
try {
|
|
239
|
+
parsed = JSON.parse(value);
|
|
240
|
+
} catch (error) {
|
|
241
|
+
return `CHAT_SDK_ADAPTERS is not valid JSON: ${error instanceof Error ? error.message : String(error)}`;
|
|
242
|
+
}
|
|
243
|
+
const result = ChatSdkAdaptersDraftSchema.safeParse(parsed);
|
|
244
|
+
return result.success ? null : firstIssueText(result.error);
|
|
245
|
+
}
|