@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,232 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Chat SDK attachments turned into task attachments. A copy of the Slack
|
|
3
|
+
* inbound-files flow (`src/slack/inbound-files.ts`), not an extraction: the
|
|
4
|
+
* Slack integration is left as is.
|
|
5
|
+
*
|
|
6
|
+
* Each attachment is fetched once per buffer flush through the adapter's own
|
|
7
|
+
* `fetchData()` (it carries the platform auth), spooled to a temp file, then
|
|
8
|
+
* stored through the active file provider and recorded as a `task_attachments`
|
|
9
|
+
* row. The task is created in `draft` while its uploads run, with its lease
|
|
10
|
+
* renewed, so no worker can claim it before its attachments exist. Every file
|
|
11
|
+
* also stays in the task text as a `[File: ...]` line. Only a fetch failure is
|
|
12
|
+
* flagged there; upload and record failures are logged.
|
|
13
|
+
*/
|
|
14
|
+
import { mkdtemp, rm } from "node:fs/promises";
|
|
15
|
+
import { tmpdir } from "node:os";
|
|
16
|
+
import { join } from "node:path";
|
|
17
|
+
import type { Attachment } from "chat";
|
|
18
|
+
import { getTaskAttachments, promoteDraftTask, refreshDraftTaskLease } from "@/be/db";
|
|
19
|
+
import { MAX_TASK_ATTACHMENT_BYTES, recordTaskAttachmentUpload } from "@/be/task-attachment-store";
|
|
20
|
+
import { providerPath } from "@/fs/provider";
|
|
21
|
+
import { getFileStorageProvider } from "@/fs/registry";
|
|
22
|
+
import { createTaskWithSiblingAwareness } from "@/tasks/sibling-awareness";
|
|
23
|
+
import type { AgentTask, CreateTaskOptions } from "@/types";
|
|
24
|
+
import { errorMessage } from "./logger";
|
|
25
|
+
|
|
26
|
+
/** How often a draft's lease is renewed while its uploads run; the sweep waits 5 minutes. */
|
|
27
|
+
const DRAFT_LEASE_REFRESH_MS = 60_000;
|
|
28
|
+
/** A file a user sent the bot, like one uploaded from the dashboard composer. */
|
|
29
|
+
const SHARED_FILE_INTENT = "user-upload";
|
|
30
|
+
|
|
31
|
+
/**
|
|
32
|
+
* One attachment of a buffered chat message. `id` is `<messageId>-<n>` with
|
|
33
|
+
* every character outside `[A-Za-z0-9_.-]` replaced by `_` (Telegram message
|
|
34
|
+
* ids carry the chat id and a colon); it names the stored blob.
|
|
35
|
+
*/
|
|
36
|
+
export interface ChatFile {
|
|
37
|
+
id: string;
|
|
38
|
+
adapter: string;
|
|
39
|
+
messageId: string;
|
|
40
|
+
name: string;
|
|
41
|
+
mimeType?: string;
|
|
42
|
+
size?: number;
|
|
43
|
+
attachment: Attachment;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
export type ChatFileFailure = { file: ChatFile; reason: string };
|
|
47
|
+
|
|
48
|
+
export type FetchedChatFile = { file: ChatFile; path: string; sizeBytes: number; sha256: string };
|
|
49
|
+
|
|
50
|
+
export type InboundChatFiles = {
|
|
51
|
+
files: ChatFile[];
|
|
52
|
+
fetched: FetchedChatFile[];
|
|
53
|
+
failed: ChatFileFailure[];
|
|
54
|
+
/** Deletes the downloaded copies; hold the batch with `await using`. */
|
|
55
|
+
[Symbol.asyncDispose](): Promise<void>;
|
|
56
|
+
};
|
|
57
|
+
|
|
58
|
+
function formatFileSize(bytes: number): string {
|
|
59
|
+
if (bytes < 1024) return `${bytes} B`;
|
|
60
|
+
if (bytes < 1024 * 1024) return `${(bytes / 1024).toFixed(1)} KB`;
|
|
61
|
+
if (bytes < 1024 * 1024 * 1024) return `${(bytes / (1024 * 1024)).toFixed(1)} MB`;
|
|
62
|
+
return `${(bytes / (1024 * 1024 * 1024)).toFixed(1)} GB`;
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
/** Describe the attachments of one message as `ChatFile`s. */
|
|
66
|
+
export function toChatFiles(
|
|
67
|
+
adapter: string,
|
|
68
|
+
messageId: string,
|
|
69
|
+
attachments: Attachment[],
|
|
70
|
+
): ChatFile[] {
|
|
71
|
+
return attachments.map((attachment, index) => ({
|
|
72
|
+
id: `${messageId}-${index + 1}`.replace(/[^\w.-]/g, "_"),
|
|
73
|
+
adapter,
|
|
74
|
+
messageId,
|
|
75
|
+
name: attachment.name ?? `${attachment.type}-${index + 1}`,
|
|
76
|
+
mimeType: attachment.mimeType,
|
|
77
|
+
size: attachment.size,
|
|
78
|
+
attachment,
|
|
79
|
+
}));
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
/**
|
|
83
|
+
* `[File: <name> (<mime>, <size>) id=<id>]` per file, followed by
|
|
84
|
+
* `(not attached: <reason>)` when the file never reached the task.
|
|
85
|
+
*/
|
|
86
|
+
export function buildChatAttachmentText(files: ChatFile[], failed: ChatFileFailure[] = []): string {
|
|
87
|
+
const reasons = new Map(failed.map((f) => [f.file.id, f.reason]));
|
|
88
|
+
return files
|
|
89
|
+
.map((f) => {
|
|
90
|
+
const size = typeof f.size === "number" ? formatFileSize(f.size) : "unknown size";
|
|
91
|
+
const line = `[File: ${f.name} (${f.mimeType ?? "unknown type"}, ${size}) id=${f.id}]`;
|
|
92
|
+
const reason = reasons.get(f.id);
|
|
93
|
+
return reason ? `${line} (not attached: ${reason})` : line;
|
|
94
|
+
})
|
|
95
|
+
.join("\n");
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
async function readAttachment(attachment: Attachment): Promise<Uint8Array> {
|
|
99
|
+
if (attachment.data instanceof Blob) return new Uint8Array(await attachment.data.arrayBuffer());
|
|
100
|
+
if (attachment.data) return new Uint8Array(attachment.data);
|
|
101
|
+
if (!attachment.fetchData) throw new Error("the adapter gave no way to download it");
|
|
102
|
+
const data = await attachment.fetchData();
|
|
103
|
+
return new Uint8Array(data);
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
/**
|
|
107
|
+
* Download every attachment, each to its own temp file. Never throws: a file
|
|
108
|
+
* that cannot be fetched lands in `failed` with a readable reason.
|
|
109
|
+
*/
|
|
110
|
+
export async function fetchChatFiles(files: ChatFile[]): Promise<InboundChatFiles> {
|
|
111
|
+
let dir: string | undefined;
|
|
112
|
+
const result: InboundChatFiles = {
|
|
113
|
+
files,
|
|
114
|
+
fetched: [],
|
|
115
|
+
failed: [],
|
|
116
|
+
async [Symbol.asyncDispose]() {
|
|
117
|
+
if (dir) await rm(dir, { recursive: true, force: true });
|
|
118
|
+
},
|
|
119
|
+
};
|
|
120
|
+
if (files.length === 0) return result;
|
|
121
|
+
|
|
122
|
+
const limit = `larger than the ${MAX_TASK_ATTACHMENT_BYTES / (1024 * 1024)} MB limit`;
|
|
123
|
+
dir = await mkdtemp(join(tmpdir(), "chat-files-"));
|
|
124
|
+
for (const [index, file] of files.entries()) {
|
|
125
|
+
if (typeof file.size === "number" && file.size > MAX_TASK_ATTACHMENT_BYTES) {
|
|
126
|
+
result.failed.push({ file, reason: limit });
|
|
127
|
+
continue;
|
|
128
|
+
}
|
|
129
|
+
try {
|
|
130
|
+
const bytes = await readAttachment(file.attachment);
|
|
131
|
+
if (bytes.byteLength > MAX_TASK_ATTACHMENT_BYTES) {
|
|
132
|
+
result.failed.push({ file, reason: limit });
|
|
133
|
+
continue;
|
|
134
|
+
}
|
|
135
|
+
const path = join(dir, String(index));
|
|
136
|
+
await Bun.write(path, bytes);
|
|
137
|
+
const sha256 = new Bun.CryptoHasher("sha256").update(bytes).digest("hex");
|
|
138
|
+
file.size ??= bytes.byteLength;
|
|
139
|
+
result.fetched.push({ file, path, sizeBytes: bytes.byteLength, sha256 });
|
|
140
|
+
} catch (error) {
|
|
141
|
+
const reason = `download failed (${errorMessage(error)})`;
|
|
142
|
+
console.warn(`[chat-sdk] could not fetch file ${file.id}: ${reason}`);
|
|
143
|
+
result.failed.push({ file, reason });
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
return result;
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
/**
|
|
150
|
+
* Create a chat-sourced task and attach the fetched files. Without files this
|
|
151
|
+
* is `createTaskWithSiblingAwareness`. With files the task is created in
|
|
152
|
+
* `draft` and promoted once every upload has settled, success or not.
|
|
153
|
+
*/
|
|
154
|
+
export async function createChatTaskWithFiles(
|
|
155
|
+
description: string,
|
|
156
|
+
options: CreateTaskOptions,
|
|
157
|
+
inbound: InboundChatFiles,
|
|
158
|
+
): Promise<{ task: AgentTask; unattached: ChatFileFailure[] }> {
|
|
159
|
+
if (inbound.fetched.length === 0) {
|
|
160
|
+
const task = await createTaskWithSiblingAwareness(description, options, { origin: "webhook" });
|
|
161
|
+
return { task, unattached: inbound.failed };
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
const task = await createTaskWithSiblingAwareness(
|
|
165
|
+
description,
|
|
166
|
+
{ ...options, status: "draft" },
|
|
167
|
+
{ origin: "webhook" },
|
|
168
|
+
);
|
|
169
|
+
const unattached = [...inbound.failed];
|
|
170
|
+
const lease = setInterval(() => {
|
|
171
|
+
void refreshDraftTaskLease(task.id).catch((error) =>
|
|
172
|
+
console.warn(
|
|
173
|
+
`[chat-sdk] could not renew the draft lease of task ${task.id}: ${errorMessage(error)}`,
|
|
174
|
+
),
|
|
175
|
+
);
|
|
176
|
+
}, DRAFT_LEASE_REFRESH_MS);
|
|
177
|
+
try {
|
|
178
|
+
unattached.push(...(await attachChatFilesToTask(task.id, inbound.fetched)));
|
|
179
|
+
} finally {
|
|
180
|
+
clearInterval(lease);
|
|
181
|
+
await promoteDraftTask(task.id);
|
|
182
|
+
}
|
|
183
|
+
return { task, unattached };
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
/**
|
|
187
|
+
* Store fetched chat files as attachments of a task. A file whose bytes the
|
|
188
|
+
* task already holds is reused. Returns the files that could not be stored.
|
|
189
|
+
*/
|
|
190
|
+
export async function attachChatFilesToTask(
|
|
191
|
+
taskId: string,
|
|
192
|
+
fetched: FetchedChatFile[],
|
|
193
|
+
): Promise<ChatFileFailure[]> {
|
|
194
|
+
const existing = await getTaskAttachments(taskId);
|
|
195
|
+
const bySha = new Set(existing.flatMap((a) => (a.sha256 ? [a.sha256] : [])));
|
|
196
|
+
const usedNames = new Set(existing.map((a) => a.name));
|
|
197
|
+
const provider = getFileStorageProvider();
|
|
198
|
+
const unattached: ChatFileFailure[] = [];
|
|
199
|
+
|
|
200
|
+
for (const { file, path, sizeBytes, sha256 } of fetched) {
|
|
201
|
+
if (bySha.has(sha256)) continue;
|
|
202
|
+
const name = usedNames.has(file.name) ? `${file.id}-${file.name}` : file.name;
|
|
203
|
+
usedNames.add(name);
|
|
204
|
+
const key = providerPath({ taskId, name: `chat-${file.id}-${file.name}` });
|
|
205
|
+
const scope = { taskId, name, key };
|
|
206
|
+
const contentType = file.mimeType ?? "application/octet-stream";
|
|
207
|
+
try {
|
|
208
|
+
const uploaded = await provider.upload(scope, Bun.file(path), {
|
|
209
|
+
contentType,
|
|
210
|
+
sizeBytes,
|
|
211
|
+
message: `Upload ${name} from ${file.adapter} for task ${taskId}`,
|
|
212
|
+
});
|
|
213
|
+
await recordTaskAttachmentUpload({
|
|
214
|
+
provider,
|
|
215
|
+
scope,
|
|
216
|
+
uploaded,
|
|
217
|
+
sizeBytes,
|
|
218
|
+
sha256,
|
|
219
|
+
contentType,
|
|
220
|
+
agentId: null,
|
|
221
|
+
intent: SHARED_FILE_INTENT,
|
|
222
|
+
description: `Shared on ${file.adapter} (message ${file.messageId})`,
|
|
223
|
+
});
|
|
224
|
+
bySha.add(sha256);
|
|
225
|
+
} catch (error) {
|
|
226
|
+
const reason = `could not be stored (${errorMessage(error)})`;
|
|
227
|
+
console.warn(`[chat-sdk] file ${file.id} for task ${taskId} ${reason}`);
|
|
228
|
+
unattached.push({ file, reason });
|
|
229
|
+
}
|
|
230
|
+
}
|
|
231
|
+
return unattached;
|
|
232
|
+
}
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
import { scrubSecrets } from "@/utils/secret-scrubber";
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* In-process `waitUntil` for Chat SDK webhooks. The SDK hands handler work to
|
|
5
|
+
* `waitUntil` and answers the platform immediately; this tracker keeps that
|
|
6
|
+
* work visible so shutdown can drain it before the DB closes.
|
|
7
|
+
*/
|
|
8
|
+
const inFlight = new Set<Promise<unknown>>();
|
|
9
|
+
|
|
10
|
+
/** The `waitUntil` implementation: track, and scrub-log any rejection. */
|
|
11
|
+
export function trackBackground(task: Promise<unknown>): void {
|
|
12
|
+
const tracked = task.then(
|
|
13
|
+
() => undefined,
|
|
14
|
+
(error: unknown) => {
|
|
15
|
+
const message = error instanceof Error ? `${error.name}: ${error.message}` : String(error);
|
|
16
|
+
console.error("[chat-sdk] Background handler failed:", scrubSecrets(message));
|
|
17
|
+
},
|
|
18
|
+
);
|
|
19
|
+
inFlight.add(tracked);
|
|
20
|
+
void tracked.finally(() => inFlight.delete(tracked));
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
/** Await in-flight work, bounded by `timeoutMs`. Returns true when fully drained. */
|
|
24
|
+
export async function drainBackground(timeoutMs: number): Promise<boolean> {
|
|
25
|
+
if (inFlight.size === 0) return true;
|
|
26
|
+
let timer: ReturnType<typeof setTimeout> | undefined;
|
|
27
|
+
const timeout = new Promise<false>((resolve) => {
|
|
28
|
+
timer = setTimeout(() => resolve(false), timeoutMs);
|
|
29
|
+
});
|
|
30
|
+
const drained = Promise.all([...inFlight]).then(() => true as const);
|
|
31
|
+
try {
|
|
32
|
+
return await Promise.race([drained, timeout]);
|
|
33
|
+
} finally {
|
|
34
|
+
clearTimeout(timer);
|
|
35
|
+
}
|
|
36
|
+
}
|
|
@@ -0,0 +1,194 @@
|
|
|
1
|
+
import type { Chat, SlashCommandEvent } from "chat";
|
|
2
|
+
import {
|
|
3
|
+
cancelTask,
|
|
4
|
+
getAgentById,
|
|
5
|
+
getLatestActiveTaskByContextKey,
|
|
6
|
+
getLatestTaskByContextKey,
|
|
7
|
+
updateAgentStatusFromCapacity,
|
|
8
|
+
} from "@/be/db";
|
|
9
|
+
import { chatSdkContextKey } from "@/tasks/context-key";
|
|
10
|
+
import type { AgentTask } from "@/types";
|
|
11
|
+
import { scrubSecrets } from "@/utils/secret-scrubber";
|
|
12
|
+
import { resolveAdapterApiUrl } from "./adapters";
|
|
13
|
+
import { trackBackground } from "./background";
|
|
14
|
+
import type { ResolvedAdapterConfig } from "./config";
|
|
15
|
+
import { errorMessage } from "./logger";
|
|
16
|
+
import { SqliteChatState } from "./state";
|
|
17
|
+
import { callTelegramBotApi } from "./telegram-api";
|
|
18
|
+
import { stopTypingKeepalive } from "./typing";
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
* Slash commands the swarm answers on every adapter that delivers them
|
|
22
|
+
* (Telegram, Discord, Slack). The platform learns the list in its own way:
|
|
23
|
+
* Telegram through `setMyCommands` at adapter start, Discord through a
|
|
24
|
+
* one-time application-command registration, Slack through the app manifest.
|
|
25
|
+
* Names follow the Telegram rules (lowercase, 1-32 chars) so one list fits all.
|
|
26
|
+
*/
|
|
27
|
+
export interface ChatCommandSpec {
|
|
28
|
+
command: string;
|
|
29
|
+
description: string;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
export const CHAT_COMMANDS: readonly ChatCommandSpec[] = [
|
|
33
|
+
{ command: "help", description: "List the commands" },
|
|
34
|
+
{ command: "status", description: "Show the latest task of this chat" },
|
|
35
|
+
{ command: "cancel", description: "Cancel the running task of this chat" },
|
|
36
|
+
{ command: "reset", description: "Start over: the next message gets no earlier context" },
|
|
37
|
+
];
|
|
38
|
+
|
|
39
|
+
const RESET_KEY_PREFIX = "swarm:reset:";
|
|
40
|
+
const RESET_TTL_MS = 30 * 24 * 60 * 60 * 1000;
|
|
41
|
+
/** Upper bound on the active tasks `/cancel` walks in one chat. */
|
|
42
|
+
const CANCEL_LIMIT = 20;
|
|
43
|
+
|
|
44
|
+
/**
|
|
45
|
+
* Record that the user reset the conversation. The next flush in this thread
|
|
46
|
+
* creates a task with no parent and no thread history older than this mark.
|
|
47
|
+
*/
|
|
48
|
+
export async function markChatThreadReset(threadId: string): Promise<Date> {
|
|
49
|
+
const at = new Date();
|
|
50
|
+
await new SqliteChatState().set(`${RESET_KEY_PREFIX}${threadId}`, at.toISOString(), RESET_TTL_MS);
|
|
51
|
+
return at;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
/** The last `/reset` in this thread, or null. */
|
|
55
|
+
export async function getChatThreadResetAt(threadId: string): Promise<Date | null> {
|
|
56
|
+
const raw = await new SqliteChatState().get<string>(`${RESET_KEY_PREFIX}${threadId}`);
|
|
57
|
+
if (typeof raw !== "string") return null;
|
|
58
|
+
const at = new Date(raw);
|
|
59
|
+
return Number.isNaN(at.getTime()) ? null : at;
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
function helpText(): string {
|
|
63
|
+
return ["Commands:", ...CHAT_COMMANDS.map((c) => `/${c.command}: ${c.description}`)].join("\n");
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
function shortId(task: AgentTask): string {
|
|
67
|
+
return task.id.slice(0, 8);
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
async function assigneeLabel(task: AgentTask): Promise<string> {
|
|
71
|
+
if (!task.agentId) return "waiting for an agent";
|
|
72
|
+
const agent = await getAgentById(task.agentId);
|
|
73
|
+
return agent ? `assigned to ${agent.name}` : "assigned to a removed agent";
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
async function statusText(contextKey: string): Promise<string> {
|
|
77
|
+
const latest = await getLatestTaskByContextKey(contextKey);
|
|
78
|
+
if (!latest) return "No task yet for this chat. Send a message to create one.";
|
|
79
|
+
const lines = [
|
|
80
|
+
`Latest task ${shortId(latest)}: ${latest.status}, ${await assigneeLabel(latest)}.`,
|
|
81
|
+
];
|
|
82
|
+
if (latest.status === "pending" || latest.status === "in_progress") {
|
|
83
|
+
lines.push("Send /cancel to stop it.");
|
|
84
|
+
}
|
|
85
|
+
return lines.join("\n");
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
async function cancelActiveTasks(
|
|
89
|
+
contextKey: string,
|
|
90
|
+
threadId: string,
|
|
91
|
+
requestedBy: string,
|
|
92
|
+
): Promise<string> {
|
|
93
|
+
const cancelled: AgentTask[] = [];
|
|
94
|
+
for (let i = 0; i < CANCEL_LIMIT; i++) {
|
|
95
|
+
const active = await getLatestActiveTaskByContextKey(contextKey, "chat");
|
|
96
|
+
if (!active) break;
|
|
97
|
+
const task = await cancelTask(active.id, `Cancelled from chat by ${requestedBy}`);
|
|
98
|
+
if (!task) break;
|
|
99
|
+
if (task.agentId) await updateAgentStatusFromCapacity(task.agentId);
|
|
100
|
+
cancelled.push(task);
|
|
101
|
+
}
|
|
102
|
+
stopTypingKeepalive(threadId);
|
|
103
|
+
if (cancelled.length === 0) return "No running task in this chat.";
|
|
104
|
+
const ids = cancelled.map(shortId).join(", ");
|
|
105
|
+
return cancelled.length === 1
|
|
106
|
+
? `Cancelled task ${ids}.`
|
|
107
|
+
: `Cancelled ${cancelled.length} tasks: ${ids}.`;
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
/** The reply for one command. Exported for tests; the handler posts it. */
|
|
111
|
+
export async function runChatCommand(
|
|
112
|
+
command: string,
|
|
113
|
+
threadId: string,
|
|
114
|
+
requestedBy: string,
|
|
115
|
+
): Promise<string> {
|
|
116
|
+
const name = command.replace(/^\//, "").toLowerCase();
|
|
117
|
+
if (name === "help") return helpText();
|
|
118
|
+
if (!CHAT_COMMANDS.some((c) => c.command === name)) {
|
|
119
|
+
return `Unknown command /${name}.\n${helpText()}`;
|
|
120
|
+
}
|
|
121
|
+
let contextKey: string;
|
|
122
|
+
try {
|
|
123
|
+
contextKey = chatSdkContextKey(threadId);
|
|
124
|
+
} catch {
|
|
125
|
+
return "This command works only in a chat the swarm can address.";
|
|
126
|
+
}
|
|
127
|
+
switch (name) {
|
|
128
|
+
case "status":
|
|
129
|
+
return statusText(contextKey);
|
|
130
|
+
case "cancel":
|
|
131
|
+
return cancelActiveTasks(contextKey, threadId, requestedBy);
|
|
132
|
+
case "reset": {
|
|
133
|
+
await markChatThreadReset(threadId);
|
|
134
|
+
const lines = ["Context reset. Your next message starts a new conversation."];
|
|
135
|
+
// A running task is state, not context: it keeps going, and the next task
|
|
136
|
+
// is still created next to it. Only /cancel stops it.
|
|
137
|
+
if (await getLatestActiveTaskByContextKey(contextKey, "chat")) {
|
|
138
|
+
lines.push("A task is still running in this chat. Send /cancel to stop it.");
|
|
139
|
+
}
|
|
140
|
+
return lines.join("\n");
|
|
141
|
+
}
|
|
142
|
+
default:
|
|
143
|
+
return helpText();
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
async function onSlashCommand(event: SlashCommandEvent): Promise<void> {
|
|
148
|
+
const adapter = event.adapter.name;
|
|
149
|
+
// Telegram passes the thread id as the channel id. Slack and Discord pass
|
|
150
|
+
// the channel, so a command there addresses the channel-level thread.
|
|
151
|
+
const threadId = event.channel.id;
|
|
152
|
+
const requestedBy = event.user.userName || event.user.fullName || event.user.userId;
|
|
153
|
+
console.log(`[chat-sdk] ${adapter} ${threadId}: command ${event.command} from ${requestedBy}`);
|
|
154
|
+
let reply: string;
|
|
155
|
+
try {
|
|
156
|
+
reply = await runChatCommand(event.command, threadId, requestedBy);
|
|
157
|
+
} catch (error) {
|
|
158
|
+
console.warn(
|
|
159
|
+
scrubSecrets(
|
|
160
|
+
`[chat-sdk] ${threadId}: command ${event.command} failed: ${errorMessage(error)}`,
|
|
161
|
+
),
|
|
162
|
+
);
|
|
163
|
+
reply = "The command failed. Check the API log.";
|
|
164
|
+
}
|
|
165
|
+
try {
|
|
166
|
+
await event.channel.post(scrubSecrets(reply));
|
|
167
|
+
} catch (error) {
|
|
168
|
+
console.warn(
|
|
169
|
+
`[chat-sdk] ${threadId}: could not answer ${event.command}: ${errorMessage(error)}`,
|
|
170
|
+
);
|
|
171
|
+
}
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
/** Catch-all handler: known commands act, unknown ones get the list. */
|
|
175
|
+
export function registerChatCommands(chat: Chat): void {
|
|
176
|
+
chat.onSlashCommand((event) => {
|
|
177
|
+
const work = onSlashCommand(event);
|
|
178
|
+
trackBackground(work);
|
|
179
|
+
return work.catch(() => undefined);
|
|
180
|
+
});
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
/**
|
|
184
|
+
* Publish the command list to Telegram so the client shows the `/` menu.
|
|
185
|
+
* Called after the adapter initialized; a failure is logged, never fatal.
|
|
186
|
+
*/
|
|
187
|
+
export async function registerTelegramCommands(config: ResolvedAdapterConfig): Promise<void> {
|
|
188
|
+
const raw = config.botToken;
|
|
189
|
+
const token = typeof raw === "function" ? await (raw as () => Promise<string>)() : raw;
|
|
190
|
+
if (typeof token !== "string" || token.length === 0) return;
|
|
191
|
+
await callTelegramBotApi(resolveAdapterApiUrl("telegram"), token, "setMyCommands", {
|
|
192
|
+
commands: CHAT_COMMANDS.map((c) => ({ command: c.command, description: c.description })),
|
|
193
|
+
});
|
|
194
|
+
}
|
|
@@ -0,0 +1,198 @@
|
|
|
1
|
+
import { ConfigRefError, resolveConfigRefs } from "@/be/config-refs";
|
|
2
|
+
import { getSwarmConfigs } from "@/be/db";
|
|
3
|
+
import { isReservedConfigKey } from "@/be/swarm-config-guard";
|
|
4
|
+
import { isEnvFlagEnabled } from "@/utils/env-flag";
|
|
5
|
+
import {
|
|
6
|
+
CHAT_SDK_ADAPTER_NAMES,
|
|
7
|
+
CHAT_SDK_ADAPTER_SCHEMAS,
|
|
8
|
+
type ChatSdkAdapterName,
|
|
9
|
+
type ChatSdkAdapters,
|
|
10
|
+
ChatSdkAdaptersDraftSchema,
|
|
11
|
+
firstIssueText,
|
|
12
|
+
isAdapterEntryEnabled,
|
|
13
|
+
} from "./schema";
|
|
14
|
+
|
|
15
|
+
export {
|
|
16
|
+
CHAT_SDK_ADAPTER_NAMES,
|
|
17
|
+
type ChatSdkAdapterName,
|
|
18
|
+
type ChatSdkAdapters,
|
|
19
|
+
ChatSdkAdaptersSchema,
|
|
20
|
+
} from "./schema";
|
|
21
|
+
|
|
22
|
+
class ChatSdkConfigError extends Error {
|
|
23
|
+
constructor(message: string) {
|
|
24
|
+
super(message);
|
|
25
|
+
this.name = "ChatSdkConfigError";
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
/**
|
|
30
|
+
* Adapter config after `$config` resolution. Resolver-capable fields
|
|
31
|
+
* (`botToken`, `appId`, `applicationId`) are functions, every other field is
|
|
32
|
+
* the plain value. Passed to the adapter factory as-is.
|
|
33
|
+
*/
|
|
34
|
+
export type ResolvedAdapterConfig = Record<string, unknown>;
|
|
35
|
+
|
|
36
|
+
export interface ChatSdkConfig {
|
|
37
|
+
enabled: boolean;
|
|
38
|
+
/** Adapters that are complete and resolved. Only these start. */
|
|
39
|
+
adapters: Partial<Record<ChatSdkAdapterName, ResolvedAdapterConfig>>;
|
|
40
|
+
/**
|
|
41
|
+
* Adapters present in `CHAT_SDK_ADAPTERS` that cannot start: a required
|
|
42
|
+
* field is missing (a partial dashboard save) or a `$config` reference does
|
|
43
|
+
* not resolve. The text names the field. The other adapters still start.
|
|
44
|
+
*/
|
|
45
|
+
incomplete?: Partial<Record<ChatSdkAdapterName, string>>;
|
|
46
|
+
/** Adapters present with `enabled: false`: configured, kept, not started. */
|
|
47
|
+
disabled?: ChatSdkAdapterName[];
|
|
48
|
+
/** A problem with the whole value (bad JSON, unknown adapter, forbidden key): nothing starts. */
|
|
49
|
+
error?: string;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
const RESOLVER_FIELDS = new Set(["botToken", "appId", "applicationId"]);
|
|
53
|
+
|
|
54
|
+
/**
|
|
55
|
+
* Raw `CHAT_SDK_ENABLED` / `CHAT_SDK_ADAPTERS` from `process.env`. Boot runs
|
|
56
|
+
* `loadGlobalConfigsIntoEnv(true)` first, so stored swarm_config rows win over
|
|
57
|
+
* the deployment env (same mechanism as `SLACK_DISABLE`).
|
|
58
|
+
*/
|
|
59
|
+
function readChatSdkEnv(): { enabled: boolean; adaptersJson: string | undefined } {
|
|
60
|
+
const adaptersJson = process.env.CHAT_SDK_ADAPTERS?.trim();
|
|
61
|
+
return {
|
|
62
|
+
enabled: isEnvFlagEnabled("CHAT_SDK_ENABLED", false),
|
|
63
|
+
adaptersJson: adaptersJson ? adaptersJson : undefined,
|
|
64
|
+
};
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
/** True when `CHAT_SDK_ENABLED` resolves true. Drives the `chat-sdk` capability. */
|
|
68
|
+
export function isChatSdkEnabled(): boolean {
|
|
69
|
+
return readChatSdkEnv().enabled;
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
/**
|
|
73
|
+
* Build the `$config` lookup over decrypted global swarm_config rows. Never
|
|
74
|
+
* reads `process.env`: leads can write ordinary global config, and an
|
|
75
|
+
* env-backed lookup would let `{"$config":"API_KEY"}` leave the host.
|
|
76
|
+
*/
|
|
77
|
+
async function buildSwarmConfigLookup(): Promise<(key: string) => string | undefined> {
|
|
78
|
+
const values = new Map<string, string>();
|
|
79
|
+
for (const row of await getSwarmConfigs({ scope: "global" })) {
|
|
80
|
+
if (typeof row.value === "string" && row.value.length > 0) values.set(row.key, row.value);
|
|
81
|
+
}
|
|
82
|
+
return (key) => {
|
|
83
|
+
if (isReservedConfigKey(key)) {
|
|
84
|
+
throw new ChatSdkConfigError(
|
|
85
|
+
`CHAT_SDK_ADAPTERS references reserved key '${key}', which is never resolvable`,
|
|
86
|
+
);
|
|
87
|
+
}
|
|
88
|
+
return values.get(key);
|
|
89
|
+
};
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
function toResolvedAdapterConfig(raw: Record<string, unknown>): ResolvedAdapterConfig {
|
|
93
|
+
const out: Record<string, unknown> = {};
|
|
94
|
+
for (const [key, value] of Object.entries(raw)) {
|
|
95
|
+
// The card's switch, not an adapter option.
|
|
96
|
+
if (key === "enabled") continue;
|
|
97
|
+
out[key] = RESOLVER_FIELDS.has(key) && typeof value === "string" ? async () => value : value;
|
|
98
|
+
}
|
|
99
|
+
return out;
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
/**
|
|
103
|
+
* Parse, validate and resolve the Chat SDK config. A problem with the whole
|
|
104
|
+
* value is returned in `error` with no adapters, so boot can log it and
|
|
105
|
+
* continue. A problem with one adapter (missing field, unresolvable `$config`)
|
|
106
|
+
* lands in `incomplete[name]` and the other adapters still resolve. Other
|
|
107
|
+
* errors (for example DB errors) propagate.
|
|
108
|
+
*/
|
|
109
|
+
export async function getChatSdkConfig(): Promise<ChatSdkConfig> {
|
|
110
|
+
const { enabled, adaptersJson } = readChatSdkEnv();
|
|
111
|
+
if (!enabled) return { enabled, adapters: {} };
|
|
112
|
+
if (!adaptersJson) {
|
|
113
|
+
return {
|
|
114
|
+
enabled,
|
|
115
|
+
adapters: {},
|
|
116
|
+
error: "CHAT_SDK_ENABLED is true but CHAT_SDK_ADAPTERS is empty",
|
|
117
|
+
};
|
|
118
|
+
}
|
|
119
|
+
let parsed: unknown;
|
|
120
|
+
try {
|
|
121
|
+
parsed = JSON.parse(adaptersJson);
|
|
122
|
+
} catch {
|
|
123
|
+
// Fixed text: the parser message can quote the input, which may hold an inline token.
|
|
124
|
+
return { enabled, adapters: {}, error: "CHAT_SDK_ADAPTERS is not valid JSON" };
|
|
125
|
+
}
|
|
126
|
+
// The draft shape guards the whole value: unknown adapters and forbidden
|
|
127
|
+
// keys stop everything. Required fields are checked per adapter below.
|
|
128
|
+
const draft = ChatSdkAdaptersDraftSchema.safeParse(parsed);
|
|
129
|
+
if (!draft.success) return { enabled, adapters: {}, error: firstIssueText(draft.error) };
|
|
130
|
+
|
|
131
|
+
const lookup = await buildSwarmConfigLookup();
|
|
132
|
+
const adapters: ChatSdkConfig["adapters"] = {};
|
|
133
|
+
const incomplete: NonNullable<ChatSdkConfig["incomplete"]> = {};
|
|
134
|
+
const disabled: ChatSdkAdapterName[] = [];
|
|
135
|
+
for (const name of CHAT_SDK_ADAPTER_NAMES) {
|
|
136
|
+
const entry = draft.data[name];
|
|
137
|
+
if (entry === undefined) continue;
|
|
138
|
+
if (!isAdapterEntryEnabled(entry)) {
|
|
139
|
+
disabled.push(name);
|
|
140
|
+
continue;
|
|
141
|
+
}
|
|
142
|
+
const validated = CHAT_SDK_ADAPTER_SCHEMAS[name].safeParse(entry);
|
|
143
|
+
if (!validated.success) {
|
|
144
|
+
incomplete[name] = firstIssueText(validated.error, `CHAT_SDK_ADAPTERS.${name}`, entry);
|
|
145
|
+
continue;
|
|
146
|
+
}
|
|
147
|
+
try {
|
|
148
|
+
const resolved = resolveConfigRefs<ChatSdkAdapters[typeof name]>(
|
|
149
|
+
validated.data as ChatSdkAdapters[typeof name],
|
|
150
|
+
lookup,
|
|
151
|
+
);
|
|
152
|
+
adapters[name] = toResolvedAdapterConfig(resolved as Record<string, unknown>);
|
|
153
|
+
} catch (error) {
|
|
154
|
+
if (!(error instanceof ChatSdkConfigError || error instanceof ConfigRefError)) throw error;
|
|
155
|
+
incomplete[name] = error.message;
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
return {
|
|
159
|
+
enabled,
|
|
160
|
+
adapters,
|
|
161
|
+
...(Object.keys(incomplete).length > 0 ? { incomplete } : {}),
|
|
162
|
+
...(disabled.length > 0 ? { disabled } : {}),
|
|
163
|
+
};
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
/**
|
|
167
|
+
* One Slack path per deployment: the Chat SDK Slack adapter requires the
|
|
168
|
+
* built-in Slack integration to be off. Evaluated on resolved config.
|
|
169
|
+
*/
|
|
170
|
+
export function assertNoSlackClash(config: ChatSdkConfig): void {
|
|
171
|
+
if (config.adapters.slack && !isEnvFlagEnabled("SLACK_DISABLE", false)) {
|
|
172
|
+
throw new ChatSdkConfigError(
|
|
173
|
+
"CHAT_SDK_ADAPTERS.slack conflicts with the built-in Slack integration; set SLACK_DISABLE=true to use the Chat SDK Slack adapter",
|
|
174
|
+
);
|
|
175
|
+
}
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
/**
|
|
179
|
+
* Env-only test/QA base URL overrides, keyed by adapter name
|
|
180
|
+
* (`CHAT_SDK_API_URL_OVERRIDES`, a RESERVED_KEYS entry, so never injected from
|
|
181
|
+
* a config row). Invalid JSON is ignored with an empty result.
|
|
182
|
+
*/
|
|
183
|
+
export function readApiUrlOverrides(): Partial<Record<ChatSdkAdapterName, string>> {
|
|
184
|
+
const raw = process.env.CHAT_SDK_API_URL_OVERRIDES?.trim();
|
|
185
|
+
if (!raw) return {};
|
|
186
|
+
try {
|
|
187
|
+
const parsed = JSON.parse(raw) as unknown;
|
|
188
|
+
if (parsed === null || typeof parsed !== "object" || Array.isArray(parsed)) return {};
|
|
189
|
+
const out: Partial<Record<ChatSdkAdapterName, string>> = {};
|
|
190
|
+
for (const name of CHAT_SDK_ADAPTER_NAMES) {
|
|
191
|
+
const value = (parsed as Record<string, unknown>)[name];
|
|
192
|
+
if (typeof value === "string" && value.length > 0) out[name] = value;
|
|
193
|
+
}
|
|
194
|
+
return out;
|
|
195
|
+
} catch {
|
|
196
|
+
return {};
|
|
197
|
+
}
|
|
198
|
+
}
|