@desplega.ai/agent-swarm 1.173.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-ccm9xrep.js → acp-adapter-sj2g0m84.js} +17 -16
- package/dist/{actions-sgeyv08k.js → actions-fegk44gr.js} +39 -34
- package/dist/{amp-adapter-gpxp3kc9.js → amp-adapter-ad8ft89x.js} +8 -7
- package/dist/{app-km9qnv2f.js → app-kfvgbvzy.js} +18 -14
- package/dist/{assistant-5r8tcvvd.js → assistant-z7zdy9ja.js} +40 -35
- package/dist/{boot-reembed-swwzdgd7.js → boot-reembed-5c2z0mbe.js} +10 -9
- package/dist/{boot-reembed-16rv9843.js → boot-reembed-j8xa181e.js} +10 -9
- package/dist/{boot-scrub-sweep-zkr6vkqb.js → boot-scrub-sweep-hnkn3rgt.js} +11 -10
- package/dist/{boot-seeds-d8sp49t2.js → boot-seeds-kwjq4d2z.js} +14 -13
- package/dist/{claude-adapter-yzzzh8jp.js → claude-adapter-9vmvpvgd.js} +19 -18
- 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-4whxere4.js → claude-sdk-session-9jye6cxr.js} +11881 -11472
- package/dist/{cli-94b48jhk.js → cli-03gedhng.js} +16 -14
- package/dist/{cli-1w0vj3gw.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-x119kdba.js → cli-3dj8k4vy.js} +1 -1
- package/dist/{cli-srqq3gc5.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-42qhyvwx.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-0kxf81jj.js → cli-6jyj59nb.js} +2 -2
- package/dist/{cli-w1apjecb.js → cli-6razj6t4.js} +2 -2
- package/dist/{cli-anzdk0z4.js → cli-74ahz61x.js} +2 -2
- package/dist/{cli-m89t9jdf.js → cli-7x0g4sn1.js} +15416 -16024
- 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-na2pxe4f.js → cli-apx87jne.js} +2 -2
- package/dist/{cli-rfz2ve55.js → cli-bzszjf2p.js} +2855 -862
- 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-rfs4vddv.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-7a93cv8c.js → cli-ee9gtz70.js} +98 -23
- package/dist/{cli-3nxdmjrc.js → cli-eejsgvm4.js} +4 -4
- package/dist/{cli-fmhj1ehj.js → cli-ef1hg1wv.js} +470 -51
- 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-h7k0xtry.js → cli-hvqbtna4.js} +4 -1256
- package/dist/{cli-s8z6x4b4.js → cli-j0pfptdp.js} +218 -102
- package/dist/cli-jta2eyzc.js +619 -0
- package/dist/{cli-5er5gjbm.js → cli-k382tshb.js} +10 -11
- package/dist/{cli-v3zhnxsb.js → cli-k3dp5tpf.js} +2 -2
- package/dist/{cli-wd0hz6ah.js → cli-kbrt8gqa.js} +9 -91
- package/dist/cli-kceah1eh.js +92 -0
- package/dist/{cli-ne7smj1b.js → cli-ke896fcf.js} +10 -10
- package/dist/{cli-xc7p7s3g.js → cli-kt6zqg0e.js} +6 -6
- package/dist/{cli-wrh08zjd.js → cli-m50t4309.js} +5 -18
- package/dist/cli-n2wq4j7m.js +6985 -0
- package/dist/{cli-bv2etw2y.js → cli-n3hzg89b.js} +46 -15
- package/dist/{cli-tmhsxhb8.js → cli-n5vwg842.js} +23 -6
- package/dist/{cli-qm1448f9.js → cli-ne236afy.js} +8 -8
- package/dist/{cli-d161g69q.js → cli-p1x85d2t.js} +50 -2
- package/dist/{cli-7pz3b7x5.js → cli-przpmdgr.js} +25 -0
- package/dist/{cli-0a7mm3zp.js → cli-q0ke1yww.js} +25 -21
- package/dist/{cli-e3pmyt39.js → cli-q50942rs.js} +1 -1
- package/dist/{cli-r44t9wqc.js → cli-r106725k.js} +2 -2
- package/dist/{cli-phcqp1b9.js → cli-r2pnvj8g.js} +2 -2
- package/dist/{cli-jpc4r224.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-r4fqfpwf.js → cli-s2sp957d.js} +50 -27
- package/dist/{cli-1x7da0de.js → cli-s4setq91.js} +8 -8
- package/dist/{cli-6thk4jz3.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-nwrdx840.js → cli-tr4sxp68.js} +3 -3
- package/dist/{cli-r0byd61h.js → cli-tsde1hdr.js} +15 -15
- package/dist/{cli-b07hr3bt.js → cli-txqb9s38.js} +2 -2
- package/dist/{cli-6r2f7edk.js → cli-v1yb0rqb.js} +6 -6
- package/dist/{cli-saqyage0.js → cli-vatyhshy.js} +179 -27
- package/dist/{cli-5wpw4rab.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-c3rk1v0x.js → cli-wayw93nt.js} +3 -3
- package/dist/cli-wmgrkscj.js +23254 -0
- package/dist/{cli-9xf7fdtp.js → cli-z5jjpaxw.js} +29 -6
- package/dist/{cli-451ks6aw.js → cli-zkpks3r3.js} +1 -1
- package/dist/cli.js +29 -45
- package/dist/{codex-adapter-r3dn4fp5.js → codex-adapter-y81sssk0.js} +15 -14
- package/dist/{codex-login-841kv5fk.js → codex-login-f2rcfpp9.js} +3 -2
- package/dist/{codex-session-runner-fwt793h4.js → codex-session-runner-8vx4j6a9.js} +15 -14
- package/dist/{commands-z8fq9qna.js → commands-kcpvpcrr.js} +11 -10
- package/dist/{controller-hq5ggpcp.js → controller-ywrmt6yd.js} +5 -4
- package/dist/{controllers-connect-3mf2btyj.js → controllers-connect-kwpvj3rf.js} +42 -37
- package/dist/{cursor-adapter-q8pgv5qa.js → cursor-adapter-cehv1qns.js} +7 -6
- package/dist/{db-yyhqqrbf.js → db-k8ankj20.js} +12 -11
- package/dist/{db-qfpq4bt7.js → db-ww5d7jq3.js} +28 -9
- package/dist/{devin-adapter-xgtj680j.js → devin-adapter-x1022qb1.js} +3 -2
- package/dist/{dispatcher-x0x7qfcq.js → dispatcher-e8gq5m68.js} +38 -33
- package/dist/{docker-f503k9p0.js → docker-ff6y1190.js} +3 -2
- package/dist/{dsh-adapter-34etvb5m.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-h1fjecwe.js → external-channel-h3359cwn.js} +11 -10
- package/dist/{google-generative-ai-szdxbpqg.js → google-generative-ai-g8db7x0f.js} +4 -2
- package/dist/{google-vertex-qzw3p36d.js → google-vertex-r2692q0r.js} +4 -2
- package/dist/{grok-adapter-e7mr287e.js → grok-adapter-0d4x7gkx.js} +18 -17
- package/dist/{handlers-znb9n7d4.js → handlers-an1gzv21.js} +41 -36
- package/dist/{heartbeat-bm4qkn8d.js → heartbeat-6dakeb61.js} +43 -38
- package/dist/{hook-254r4mza.js → hook-6v6cv6d5.js} +11 -23
- package/dist/{hook-xaa3q80s.js → hook-sa5xye4y.js} +12 -11
- package/dist/{http-3dgrgp8y.js → http-r7f3eky0.js} +1499 -251
- package/dist/index-52j1aja4.js +3465 -0
- package/dist/{index-fx3jqq1e.js → index-7ep4gzdk.js} +38 -33
- 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-g2byveez.js → index-rmpw9a8q.js} +38 -33
- package/dist/{index-2ybk7khy.js → index-s2wxgzr0.js} +468 -683
- package/dist/{index-bdsqa799.js → index-v1v6n7hg.js} +43 -38
- package/dist/index-we27kd31.js +12810 -0
- package/dist/{index-jd7sejm2.js → index-zk96aq1k.js} +10 -9
- package/dist/{keepalive-6ywhpja1.js → keepalive-be9vwy5h.js} +12 -11
- package/dist/{lead-cw45n21s.js → lead-0861cwrf.js} +61 -56
- package/dist/{maintenance-ss1a1ge9.js → maintenance-mzzgc8bn.js} +10 -9
- package/dist/{node-fkajaq96.js → node-mn1zs7an.js} +2 -1
- package/dist/{oauth-refresh-sweep-3c6f8vyy.js → oauth-refresh-sweep-cpdnfwdj.js} +11 -10
- package/dist/{onboard-8hzdhc1k.js → onboard-fh8qs4r3.js} +11 -3
- package/dist/{opencode-adapter-qbchbcfz.js → opencode-adapter-nm42wwj8.js} +18 -13
- package/dist/{otel-impl-e6mnvc4v.js → otel-impl-x6c1c9g4.js} +242 -868
- package/dist/{pi-mono-adapter-3c9g9snh.js → pi-mono-adapter-9n2ya240.js} +244 -106
- package/dist/{pricing-refresh-gedkjdw0.js → pricing-refresh-qb0rv0ap.js} +12 -11
- package/dist/{rbac-roles-dt4vh1yy.js → rbac-roles-1gzdwbh1.js} +11 -10
- package/dist/{rbac-roles-eszdg6pm.js → rbac-roles-3mraey52.js} +12 -11
- package/dist/{render-v2-907cy6sa.js → render-v2-agpa45zz.js} +18 -14
- package/dist/{resume-42stcaz0.js → resume-x0ydy9s9.js} +25 -21
- package/dist/{runtime-qfry88v2.js → runtime-sa2vr16g.js} +2 -2
- package/dist/{setup-adbc1c10.js → setup-as9ga0d6.js} +2 -2
- package/dist/{virtual-modules-rtmt494q.js → virtual-modules-9gkxqrap.js} +6 -5
- package/dist/{work-objects-wpj8ck95.js → work-objects-y5g1y7ev.js} +41 -36
- package/dist/{worker-bzdra9rf.js → worker-860c3e38.js} +61 -56
- package/dist/{x-qfmygqqv.js → x-m477fr0h.js} +3 -2
- package/openapi.json +523 -344
- package/package.json +10 -11
- 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 +52 -7
- 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-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 +0 -18
- package/src/be/seed-skills/index.ts +104 -3
- package/src/be/swarm-config-guard.ts +52 -0
- package/src/cli.tsx +0 -16
- package/src/commands/onboard/non-interactive.ts +4 -2
- package/src/commands/provider-credentials.ts +52 -6
- package/src/commands/runner.ts +190 -54
- 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.ts +15 -5
- package/src/http/page-proxy.ts +2 -4
- package/src/http/pages-public.ts +3 -3
- package/src/http/pages.ts +3 -13
- package/src/http/poll.ts +28 -68
- package/src/http/requested-by.ts +54 -0
- package/src/http/scripts.ts +1 -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 +159 -1
- 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/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 +55 -2
- package/src/providers/amp-adapter.ts +1 -1
- package/src/providers/claude-adapter.ts +1 -1
- package/src/providers/claude-flag-settings.ts +17 -5
- package/src/providers/claude-managed-adapter.ts +1 -1
- package/src/providers/claude-session-events.ts +6 -1
- package/src/providers/dsh-adapter.ts +1 -1
- package/src/providers/opencode-adapter.ts +243 -26
- package/src/providers/opencode-swarm-plugin.ts +31 -0
- package/src/providers/pi-mono-adapter.ts +398 -153
- package/src/providers/pricing-sources.md +33 -2
- package/src/providers/reasoning-effort.ts +27 -0
- package/src/providers/types.ts +6 -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/loader.ts +19 -7
- 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/event-dedup.ts +1 -1
- package/src/tasks/additive-buffer.ts +4 -2
- package/src/tasks/context-key.ts +60 -1
- package/src/tasks/model-pin.ts +67 -0
- package/src/tasks/worker-follow-up.ts +4 -31
- package/src/tests/acp-adapter.test.ts +42 -0
- package/src/tests/api-drain-worker-e2e.test.ts +1 -6
- 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 +9 -4
- package/src/tests/claude-flag-settings.test.ts +11 -3
- package/src/tests/claude-managed-adapter.test.ts +1 -1
- package/src/tests/claude-sdk-lifecycle.test.ts +9 -3
- 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 +19 -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/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/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-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/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 +216 -2
- 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-legacy-resume-prompt.test.ts +64 -0
- package/src/tests/script-connections.test.ts +2 -0
- 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 +46 -0
- package/src/tests/skill-get-name-resolution.test.ts +137 -0
- package/src/tests/sonnet-5-5-registry.test.ts +9 -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/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/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/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/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 +8 -1
- package/templates/skills/apps/content.md +8 -1
- 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/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-bd957dgb.js +0 -153
- package/dist/cli-q51yprfq.js +0 -1104
- package/dist/cli-t1ze64h3.js +0 -811
- package/dist/cli-tcc4wd5d.js +0 -56471
- 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/src/{artifact-sdk → pages}/browser-sdk.ts +0 -0
- /package/src/{artifact-sdk → pages}/feedback-overlay.ts +0 -0
|
@@ -0,0 +1,244 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
import { getPublicMcpBaseUrl } from "@/utils/constants";
|
|
3
|
+
import { isEnvFlagEnabled } from "@/utils/env-flag";
|
|
4
|
+
import { scrubSecrets } from "@/utils/secret-scrubber";
|
|
5
|
+
import { resolveAdapterApiUrl } from "./adapters";
|
|
6
|
+
import { CHAT_SDK_ADAPTER_NAMES, type ChatSdkAdapterName, getChatSdkConfig } from "./config";
|
|
7
|
+
import { fetchDiscordGuilds } from "./discord-api";
|
|
8
|
+
import { getChatSdkHealth } from "./runtime";
|
|
9
|
+
import { isAdapterEntryEnabled } from "./schema";
|
|
10
|
+
import { callTelegramBotApi, TelegramBotApiError } from "./telegram-api";
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* Read-only Chat SDK status for the dashboard card, and the Telegram
|
|
14
|
+
* register-webhook action. Never returns secret values: configured state comes
|
|
15
|
+
* from adapter names and the non-secret `mode` field only.
|
|
16
|
+
*/
|
|
17
|
+
|
|
18
|
+
export const ChatSdkStatusSchema = z.object({
|
|
19
|
+
enabled: z.boolean(),
|
|
20
|
+
slackClash: z.boolean(),
|
|
21
|
+
/** Config problem that stops every adapter from starting (bad JSON, missing `$config` row). */
|
|
22
|
+
error: z.string().optional(),
|
|
23
|
+
adapters: z.array(
|
|
24
|
+
z.object({
|
|
25
|
+
name: z.enum(CHAT_SDK_ADAPTER_NAMES),
|
|
26
|
+
configured: z.boolean(),
|
|
27
|
+
/** False when the entry carries `enabled: false`: configured and kept, not started. */
|
|
28
|
+
enabled: z.boolean(),
|
|
29
|
+
/** `incomplete`: present in `CHAT_SDK_ADAPTERS` with a missing field or an unresolvable `$config`; `error` names it. */
|
|
30
|
+
health: z.enum(["ok", "error", "disabled", "incomplete"]),
|
|
31
|
+
error: z.string().optional(),
|
|
32
|
+
webhookUrl: z.string(),
|
|
33
|
+
mode: z.string().optional(),
|
|
34
|
+
gateway: z
|
|
35
|
+
.object({
|
|
36
|
+
state: z.enum(["running", "backoff", "stopped"]),
|
|
37
|
+
since: z.string(),
|
|
38
|
+
restarts: z.number(),
|
|
39
|
+
})
|
|
40
|
+
.optional(),
|
|
41
|
+
/** Discord only, while the Gateway runs: the servers the bot was invited to. */
|
|
42
|
+
guilds: z.object({ count: z.number(), names: z.array(z.string()) }).optional(),
|
|
43
|
+
}),
|
|
44
|
+
),
|
|
45
|
+
});
|
|
46
|
+
|
|
47
|
+
export type ChatSdkStatus = z.infer<typeof ChatSdkStatusSchema>;
|
|
48
|
+
export type ChatSdkAdapterStatus = ChatSdkStatus["adapters"][number];
|
|
49
|
+
|
|
50
|
+
/** Schema defaults, repeated here because the raw JSON may omit `mode`. */
|
|
51
|
+
const DEFAULT_MODES: Partial<Record<ChatSdkAdapterName, string>> = {
|
|
52
|
+
telegram: "webhook",
|
|
53
|
+
slack: "webhook",
|
|
54
|
+
};
|
|
55
|
+
|
|
56
|
+
const NOT_RUNNING =
|
|
57
|
+
"The adapter is starting, or the last configuration save is still applying. Refresh in a few seconds.";
|
|
58
|
+
|
|
59
|
+
const SLACK_CLASH_CAUSE =
|
|
60
|
+
"the Slack adapter conflicts with the built-in Slack integration (set SLACK_DISABLE=true)";
|
|
61
|
+
|
|
62
|
+
/** Bound on error text in the status payload. Adapter and parser errors can be long. */
|
|
63
|
+
const MAX_STATUS_ERROR_CHARS = 300;
|
|
64
|
+
|
|
65
|
+
function statusError(message: string): string {
|
|
66
|
+
return scrubSecrets(message).slice(0, MAX_STATUS_ERROR_CHARS);
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
/**
|
|
70
|
+
* The raw `CHAT_SDK_ADAPTERS` object, without validation or `$config`
|
|
71
|
+
* resolution. Lets the card show which adapters an operator configured even
|
|
72
|
+
* while the config fails to resolve. Empty on invalid JSON.
|
|
73
|
+
*/
|
|
74
|
+
function readRawAdapters(): Partial<Record<ChatSdkAdapterName, Record<string, unknown>>> {
|
|
75
|
+
const raw = process.env.CHAT_SDK_ADAPTERS?.trim();
|
|
76
|
+
if (!raw) return {};
|
|
77
|
+
try {
|
|
78
|
+
const parsed = JSON.parse(raw) as unknown;
|
|
79
|
+
if (parsed === null || typeof parsed !== "object" || Array.isArray(parsed)) return {};
|
|
80
|
+
const out: Partial<Record<ChatSdkAdapterName, Record<string, unknown>>> = {};
|
|
81
|
+
for (const name of CHAT_SDK_ADAPTER_NAMES) {
|
|
82
|
+
const value = (parsed as Record<string, unknown>)[name];
|
|
83
|
+
if (value !== null && typeof value === "object" && !Array.isArray(value)) {
|
|
84
|
+
out[name] = value as Record<string, unknown>;
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
return out;
|
|
88
|
+
} catch {
|
|
89
|
+
return {};
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
export function chatSdkWebhookUrl(name: ChatSdkAdapterName): string {
|
|
94
|
+
return `${getPublicMcpBaseUrl()}/api/integrations/chat/${name}/webhook`;
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
/**
|
|
98
|
+
* The Discord bot's servers, only while its Gateway runs (a bot that is in no
|
|
99
|
+
* server is the usual "it does not answer" cause). The token comes from the
|
|
100
|
+
* resolved config, never from the status payload.
|
|
101
|
+
*/
|
|
102
|
+
async function discordGuildsFor(
|
|
103
|
+
config: Awaited<ReturnType<typeof getChatSdkConfig>>,
|
|
104
|
+
health: ReturnType<typeof getChatSdkHealth>["discord"],
|
|
105
|
+
): Promise<ChatSdkAdapterStatus["guilds"]> {
|
|
106
|
+
if (health?.status !== "ok" || health.gateway?.state !== "running") return undefined;
|
|
107
|
+
const raw = config.adapters.discord?.botToken;
|
|
108
|
+
const token = typeof raw === "function" ? await (raw as () => Promise<unknown>)() : raw;
|
|
109
|
+
if (typeof token !== "string" || token.length === 0) return undefined;
|
|
110
|
+
return fetchDiscordGuilds(token);
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
export async function getChatSdkStatus(): Promise<ChatSdkStatus> {
|
|
114
|
+
const config = await getChatSdkConfig();
|
|
115
|
+
const raw = readRawAdapters();
|
|
116
|
+
const runtimeHealth = getChatSdkHealth();
|
|
117
|
+
const slackClash = raw.slack !== undefined && !isEnvFlagEnabled("SLACK_DISABLE", false);
|
|
118
|
+
// A config error or a Slack clash stops `startChatSdk` before any adapter.
|
|
119
|
+
const blockedCause =
|
|
120
|
+
config.error ?? (config.enabled && slackClash ? SLACK_CLASH_CAUSE : undefined);
|
|
121
|
+
const discordGuilds = await discordGuildsFor(config, runtimeHealth.discord);
|
|
122
|
+
const adapters = CHAT_SDK_ADAPTER_NAMES.map((name): ChatSdkAdapterStatus => {
|
|
123
|
+
const configured = raw[name] !== undefined;
|
|
124
|
+
const enabled = isAdapterEntryEnabled(raw[name]);
|
|
125
|
+
const current = runtimeHealth[name];
|
|
126
|
+
const incomplete = config.incomplete?.[name];
|
|
127
|
+
const rawMode = raw[name]?.mode;
|
|
128
|
+
const mode =
|
|
129
|
+
typeof rawMode === "string" ? rawMode : configured ? DEFAULT_MODES[name] : undefined;
|
|
130
|
+
let health: ChatSdkAdapterStatus["health"] = "disabled";
|
|
131
|
+
let error: string | undefined;
|
|
132
|
+
if (current) {
|
|
133
|
+
health = current.status;
|
|
134
|
+
// A Discord Gateway in backoff keeps health `ok` (the webhook still
|
|
135
|
+
// works); its last warning says why the listener keeps failing.
|
|
136
|
+
const showWarning = current.status === "error" || current.gateway?.state === "backoff";
|
|
137
|
+
error = current.error ?? (showWarning ? current.lastWarning : undefined);
|
|
138
|
+
} else if (incomplete) {
|
|
139
|
+
// Saved with a missing field, or a `$config` row that does not exist.
|
|
140
|
+
health = "incomplete";
|
|
141
|
+
error = incomplete;
|
|
142
|
+
} else if (configured && !enabled) {
|
|
143
|
+
error = "Turned off on the card. The configuration is kept.";
|
|
144
|
+
} else if (config.enabled && configured) {
|
|
145
|
+
error = blockedCause ? `Not started: ${blockedCause}` : NOT_RUNNING;
|
|
146
|
+
}
|
|
147
|
+
return {
|
|
148
|
+
name,
|
|
149
|
+
configured,
|
|
150
|
+
enabled,
|
|
151
|
+
health,
|
|
152
|
+
...(error ? { error: statusError(error) } : {}),
|
|
153
|
+
webhookUrl: chatSdkWebhookUrl(name),
|
|
154
|
+
...(mode ? { mode } : {}),
|
|
155
|
+
...(current?.gateway ? { gateway: current.gateway } : {}),
|
|
156
|
+
...(name === "discord" && discordGuilds ? { guilds: discordGuilds } : {}),
|
|
157
|
+
};
|
|
158
|
+
});
|
|
159
|
+
return {
|
|
160
|
+
enabled: config.enabled,
|
|
161
|
+
slackClash,
|
|
162
|
+
...(config.error ? { error: statusError(config.error) } : {}),
|
|
163
|
+
adapters,
|
|
164
|
+
};
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
/** `getWebhookInfo` result as Telegram returns it (subset; unknown keys pass through). */
|
|
168
|
+
export type TelegramWebhookInfo = Record<string, unknown> & { url: string };
|
|
169
|
+
|
|
170
|
+
export class ChatSdkActionError extends Error {
|
|
171
|
+
constructor(
|
|
172
|
+
message: string,
|
|
173
|
+
readonly status: 400 | 502,
|
|
174
|
+
) {
|
|
175
|
+
super(message);
|
|
176
|
+
this.name = "ChatSdkActionError";
|
|
177
|
+
}
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
async function callBotApi(
|
|
181
|
+
baseUrl: string,
|
|
182
|
+
token: string,
|
|
183
|
+
method: string,
|
|
184
|
+
body: Record<string, unknown>,
|
|
185
|
+
): Promise<unknown> {
|
|
186
|
+
try {
|
|
187
|
+
return await callTelegramBotApi(baseUrl, token, method, body);
|
|
188
|
+
} catch (error) {
|
|
189
|
+
if (error instanceof TelegramBotApiError) throw new ChatSdkActionError(error.message, 502);
|
|
190
|
+
throw error;
|
|
191
|
+
}
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
/**
|
|
195
|
+
* Point the Telegram bot at this swarm's public webhook URL with the
|
|
196
|
+
* configured secret token, then return `getWebhookInfo`. The Bot API base is
|
|
197
|
+
* the one the adapter uses (`resolveAdapterApiUrl`), so tests and local QA hit
|
|
198
|
+
* the fake Bot API.
|
|
199
|
+
*/
|
|
200
|
+
export async function registerTelegramWebhook(): Promise<{
|
|
201
|
+
webhookUrl: string;
|
|
202
|
+
webhookInfo: TelegramWebhookInfo;
|
|
203
|
+
}> {
|
|
204
|
+
const config = await getChatSdkConfig();
|
|
205
|
+
if (!config.enabled) {
|
|
206
|
+
throw new ChatSdkActionError(
|
|
207
|
+
"The Chat SDK channel is disabled (CHAT_SDK_ENABLED is not true). Enable it and restart the API first",
|
|
208
|
+
400,
|
|
209
|
+
);
|
|
210
|
+
}
|
|
211
|
+
if (config.error) throw new ChatSdkActionError(statusError(config.error), 400);
|
|
212
|
+
const telegram = config.adapters.telegram;
|
|
213
|
+
if (!telegram) {
|
|
214
|
+
throw new ChatSdkActionError("Telegram is not configured in CHAT_SDK_ADAPTERS", 400);
|
|
215
|
+
}
|
|
216
|
+
if (telegram.mode !== "webhook") {
|
|
217
|
+
throw new ChatSdkActionError(
|
|
218
|
+
"Telegram runs in polling mode; a registered webhook would stop polling",
|
|
219
|
+
400,
|
|
220
|
+
);
|
|
221
|
+
}
|
|
222
|
+
const secretToken = telegram.secretToken;
|
|
223
|
+
if (typeof secretToken !== "string" || secretToken.length === 0) {
|
|
224
|
+
throw new ChatSdkActionError("Telegram webhook mode needs a secretToken", 400);
|
|
225
|
+
}
|
|
226
|
+
const rawToken = telegram.botToken;
|
|
227
|
+
const token =
|
|
228
|
+
typeof rawToken === "function" ? await (rawToken as () => Promise<string>)() : rawToken;
|
|
229
|
+
if (typeof token !== "string" || token.length === 0) {
|
|
230
|
+
throw new ChatSdkActionError("Telegram botToken is empty", 400);
|
|
231
|
+
}
|
|
232
|
+
const baseUrl = resolveAdapterApiUrl("telegram");
|
|
233
|
+
const webhookUrl = chatSdkWebhookUrl("telegram");
|
|
234
|
+
await callBotApi(baseUrl, token, "setWebhook", { url: webhookUrl, secret_token: secretToken });
|
|
235
|
+
const info = await callBotApi(baseUrl, token, "getWebhookInfo", {});
|
|
236
|
+
if (
|
|
237
|
+
info === null ||
|
|
238
|
+
typeof info !== "object" ||
|
|
239
|
+
typeof (info as { url?: unknown }).url !== "string"
|
|
240
|
+
) {
|
|
241
|
+
throw new ChatSdkActionError("Telegram getWebhookInfo returned an unexpected result", 502);
|
|
242
|
+
}
|
|
243
|
+
return { webhookUrl, webhookInfo: info as TelegramWebhookInfo };
|
|
244
|
+
}
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
import { getLatestLeadTaskByContextKey } from "@/be/db";
|
|
2
|
+
import { requestSteering, SteeringRequestError } from "@/be/steering";
|
|
3
|
+
import type { AgentTask, SteerResult } from "@/types";
|
|
4
|
+
import { scrubSecrets } from "@/utils/secret-scrubber";
|
|
5
|
+
import { isSteeringEnabled } from "@/utils/steering-enabled";
|
|
6
|
+
|
|
7
|
+
export interface ChatThreadSteeringResult {
|
|
8
|
+
task: AgentTask;
|
|
9
|
+
result: SteerResult;
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* Steer the lead's in-progress task on this thread with a new message. Same
|
|
14
|
+
* rule as the Slack default (`SLACK_THREAD_STEERING=lead`, queue mode) as
|
|
15
|
+
* constants: `isSteeringEnabled()` is the only switch. Returns null when the
|
|
16
|
+
* caller should buffer the message into a follow-up task instead.
|
|
17
|
+
*/
|
|
18
|
+
export async function requestChatThreadSteering(args: {
|
|
19
|
+
contextKey: string;
|
|
20
|
+
message: string;
|
|
21
|
+
}): Promise<ChatThreadSteeringResult | null> {
|
|
22
|
+
if (!isSteeringEnabled()) return null;
|
|
23
|
+
|
|
24
|
+
const task = await getLatestLeadTaskByContextKey(args.contextKey, "chat");
|
|
25
|
+
if (!task || task.status !== "in_progress") return null;
|
|
26
|
+
|
|
27
|
+
try {
|
|
28
|
+
const result = await requestSteering({
|
|
29
|
+
taskId: task.id,
|
|
30
|
+
message: args.message,
|
|
31
|
+
mode: "queue",
|
|
32
|
+
onUnsupported: "degrade",
|
|
33
|
+
source: "chat",
|
|
34
|
+
createdByKind: "user",
|
|
35
|
+
});
|
|
36
|
+
return { task, result };
|
|
37
|
+
} catch (error) {
|
|
38
|
+
const detail = error instanceof Error ? error.message : String(error);
|
|
39
|
+
const what = error instanceof SteeringRequestError ? "was refused" : "failed";
|
|
40
|
+
console.warn(
|
|
41
|
+
scrubSecrets(
|
|
42
|
+
`[chat-sdk] Steering task ${task.id} ${what} (${detail}); creating a follow-up task`,
|
|
43
|
+
),
|
|
44
|
+
);
|
|
45
|
+
return null;
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
/** Thread acknowledgement for the steering outcome. Constants, not prompts. */
|
|
50
|
+
export function formatChatSteeringAck(result: SteerResult): string {
|
|
51
|
+
if (result.outcome === "promoted") return "_Your message was queued as a follow-up task._";
|
|
52
|
+
if (result.degradedFrom) {
|
|
53
|
+
return "_Interrupt steering is unavailable for this task, so your message was queued._";
|
|
54
|
+
}
|
|
55
|
+
return result.outcome === "steered"
|
|
56
|
+
? "_Your message was sent to the active task._"
|
|
57
|
+
: "_Your message was queued for the active task._";
|
|
58
|
+
}
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
import { readApiUrlOverrides } from "./config";
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Bot Framework Connector hosts for Teams: public cloud, GCC, GCC High, DoD
|
|
5
|
+
* and the China cloud. The same set as `TRUSTED_CONNECTOR_HOSTS` in
|
|
6
|
+
* `@chat-adapter/teams` (its standalone connector API), minus the loopback
|
|
7
|
+
* Emulator exception. The adapter's own send path does not check the host.
|
|
8
|
+
*/
|
|
9
|
+
const TRUSTED_TEAMS_SERVICE_HOSTS = new Set([
|
|
10
|
+
"smba.trafficmanager.net",
|
|
11
|
+
"smba.infra.gcc.teams.microsoft.com",
|
|
12
|
+
"smba.infra.gov.teams.microsoft.us",
|
|
13
|
+
"smba.infra.dod.teams.microsoft.us",
|
|
14
|
+
"msteams.botframework.azure.cn",
|
|
15
|
+
]);
|
|
16
|
+
|
|
17
|
+
/** A Teams service URL that is not a Bot Framework host. Nothing was sent. */
|
|
18
|
+
export class UntrustedTeamsServiceUrlError extends Error {
|
|
19
|
+
constructor(detail: string) {
|
|
20
|
+
super(`Refusing to send the Teams bot token: ${detail}`);
|
|
21
|
+
this.name = "UntrustedTeamsServiceUrlError";
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
function originOf(value: string): string | undefined {
|
|
26
|
+
try {
|
|
27
|
+
return new URL(value).origin;
|
|
28
|
+
} catch {
|
|
29
|
+
return undefined;
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
/**
|
|
34
|
+
* The Teams adapter sends the bot token to the service URL of the target
|
|
35
|
+
* conversation. Trusted: https on a Bot Framework host, or the origin of the
|
|
36
|
+
* env-only `CHAT_SDK_API_URL_OVERRIDES.teams` (tests and emulators). Every
|
|
37
|
+
* other origin, loopback included, is refused.
|
|
38
|
+
*/
|
|
39
|
+
export function isTrustedTeamsServiceUrl(serviceUrl: string): boolean {
|
|
40
|
+
let url: URL;
|
|
41
|
+
try {
|
|
42
|
+
url = new URL(serviceUrl);
|
|
43
|
+
} catch {
|
|
44
|
+
return false;
|
|
45
|
+
}
|
|
46
|
+
if (url.protocol === "https:" && TRUSTED_TEAMS_SERVICE_HOSTS.has(url.hostname.toLowerCase())) {
|
|
47
|
+
return true;
|
|
48
|
+
}
|
|
49
|
+
const override = readApiUrlOverrides().teams;
|
|
50
|
+
return override !== undefined && originOf(override) === url.origin;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
/**
|
|
54
|
+
* Throw `UntrustedTeamsServiceUrlError` unless the Teams thread or channel id
|
|
55
|
+
* (`teams:<base64url conversation>:<base64url serviceUrl>[:<type>]`) carries a
|
|
56
|
+
* trusted service URL. Call it before every Teams call that takes an id from
|
|
57
|
+
* outside the adapter. Ids of other adapters pass.
|
|
58
|
+
*/
|
|
59
|
+
export function assertTrustedChatTarget(id: string): void {
|
|
60
|
+
const parts = id.split(":");
|
|
61
|
+
if (parts[0] !== "teams") return;
|
|
62
|
+
const encoded = parts[2];
|
|
63
|
+
if ((parts.length !== 3 && parts.length !== 4) || !encoded) {
|
|
64
|
+
throw new UntrustedTeamsServiceUrlError("the Teams id is malformed");
|
|
65
|
+
}
|
|
66
|
+
assertTrustedTeamsServiceUrl(Buffer.from(encoded, "base64url").toString("utf-8"));
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
export function assertTrustedTeamsServiceUrl(serviceUrl: string): void {
|
|
70
|
+
if (isTrustedTeamsServiceUrl(serviceUrl)) return;
|
|
71
|
+
const origin = originOf(serviceUrl);
|
|
72
|
+
throw new UntrustedTeamsServiceUrlError(
|
|
73
|
+
origin && origin !== "null"
|
|
74
|
+
? `${origin} is not a Microsoft Bot Framework service host`
|
|
75
|
+
: "the service URL is not a valid URL",
|
|
76
|
+
);
|
|
77
|
+
}
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
import { scrubSecrets } from "@/utils/secret-scrubber";
|
|
2
|
+
import { errorMessage } from "./logger";
|
|
3
|
+
|
|
4
|
+
const TELEGRAM_TIMEOUT_MS = 10_000;
|
|
5
|
+
|
|
6
|
+
/** A Bot API call that did not return `ok: true`. The message never carries the token. */
|
|
7
|
+
export class TelegramBotApiError extends Error {
|
|
8
|
+
constructor(message: string) {
|
|
9
|
+
super(message);
|
|
10
|
+
this.name = "TelegramBotApiError";
|
|
11
|
+
}
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* One Telegram Bot API call. Shared by the register-webhook action and the
|
|
16
|
+
* command registration at adapter start, so both hit the same base URL
|
|
17
|
+
* (`resolveAdapterApiUrl`) and scrub the token out of every error.
|
|
18
|
+
*/
|
|
19
|
+
export async function callTelegramBotApi(
|
|
20
|
+
baseUrl: string,
|
|
21
|
+
token: string,
|
|
22
|
+
method: string,
|
|
23
|
+
body: Record<string, unknown>,
|
|
24
|
+
): Promise<unknown> {
|
|
25
|
+
let response: Response;
|
|
26
|
+
try {
|
|
27
|
+
response = await fetch(`${baseUrl.replace(/\/+$/, "")}/bot${token}/${method}`, {
|
|
28
|
+
method: "POST",
|
|
29
|
+
headers: { "content-type": "application/json" },
|
|
30
|
+
body: JSON.stringify(body),
|
|
31
|
+
signal: AbortSignal.timeout(TELEGRAM_TIMEOUT_MS),
|
|
32
|
+
});
|
|
33
|
+
} catch (error) {
|
|
34
|
+
if (error instanceof Error && error.name === "TimeoutError") {
|
|
35
|
+
throw new TelegramBotApiError(
|
|
36
|
+
`Telegram ${method} failed: no response from the Bot API within ${TELEGRAM_TIMEOUT_MS / 1000} s`,
|
|
37
|
+
);
|
|
38
|
+
}
|
|
39
|
+
// Fetch errors can echo the URL, which carries the bot token.
|
|
40
|
+
throw new TelegramBotApiError(
|
|
41
|
+
`Telegram ${method} failed: ${scrubSecrets(errorMessage(error).replaceAll(token, "[REDACTED]"))}`,
|
|
42
|
+
);
|
|
43
|
+
}
|
|
44
|
+
const payload = (await response.json().catch(() => null)) as {
|
|
45
|
+
ok?: boolean;
|
|
46
|
+
result?: unknown;
|
|
47
|
+
description?: string;
|
|
48
|
+
} | null;
|
|
49
|
+
if (!response.ok || !payload?.ok) {
|
|
50
|
+
const description = payload?.description ?? `HTTP ${response.status}`;
|
|
51
|
+
throw new TelegramBotApiError(
|
|
52
|
+
`Telegram ${method} failed: ${scrubSecrets(description.replaceAll(token, "[REDACTED]"))}`,
|
|
53
|
+
);
|
|
54
|
+
}
|
|
55
|
+
return payload.result;
|
|
56
|
+
}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import { registerTemplate } from "@/prompts/registry";
|
|
2
|
+
|
|
3
|
+
registerTemplate({
|
|
4
|
+
eventType: "chat.message.thread_context",
|
|
5
|
+
header: "",
|
|
6
|
+
defaultBody: `<thread_context>
|
|
7
|
+
{{thread_messages}}
|
|
8
|
+
</thread_context>`,
|
|
9
|
+
variables: [
|
|
10
|
+
{
|
|
11
|
+
name: "thread_messages",
|
|
12
|
+
description: "Formatted Chat SDK thread messages (user: text pairs)",
|
|
13
|
+
},
|
|
14
|
+
],
|
|
15
|
+
category: "system",
|
|
16
|
+
});
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Test-only seams for the Chat SDK adapters. Production code never calls
|
|
3
|
+
* `setChatSdkTestHooks`, so every hook is unset in a running API. The
|
|
4
|
+
* operator schema rejects `webhookVerifier`, and this module is the only
|
|
5
|
+
* other way to set one.
|
|
6
|
+
*/
|
|
7
|
+
export interface ChatSdkTestHooks {
|
|
8
|
+
/**
|
|
9
|
+
* Replaces the Bot Framework JWT check of the Teams adapter. Tests cannot
|
|
10
|
+
* mint a Microsoft-signed token, so they inject `() => true`.
|
|
11
|
+
*/
|
|
12
|
+
teamsWebhookVerifier?: (request: Request, body: string) => boolean | Promise<boolean>;
|
|
13
|
+
/**
|
|
14
|
+
* Bot token factory for the Teams adapter, so an outbound send needs no
|
|
15
|
+
* MSAL call to login.microsoftonline.com.
|
|
16
|
+
*/
|
|
17
|
+
teamsToken?: () => string;
|
|
18
|
+
/**
|
|
19
|
+
* Skips the Discord Gateway loop. The loop logs in through `discord.js`
|
|
20
|
+
* against the real Discord API, which `apiUrl` does not redirect.
|
|
21
|
+
*/
|
|
22
|
+
disableDiscordGateway?: boolean;
|
|
23
|
+
/** Typing keepalive interval, shortened so a test sees several renewals. */
|
|
24
|
+
typingIntervalMs?: number;
|
|
25
|
+
/** Typing keepalive hard cap, shortened so a test can reach it. */
|
|
26
|
+
typingMaxMs?: number;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
let hooks: ChatSdkTestHooks = {};
|
|
30
|
+
|
|
31
|
+
export function setChatSdkTestHooks(next: ChatSdkTestHooks): void {
|
|
32
|
+
hooks = { ...next };
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
export function getChatSdkTestHooks(): Readonly<ChatSdkTestHooks> {
|
|
36
|
+
return hooks;
|
|
37
|
+
}
|
|
@@ -0,0 +1,100 @@
|
|
|
1
|
+
import type { Attachment } from "chat";
|
|
2
|
+
import { type AdditiveBuffer, createAdditiveBuffer } from "@/tasks/additive-buffer";
|
|
3
|
+
import { trackBackground } from "./background";
|
|
4
|
+
import { flushChatThread } from "./inbound";
|
|
5
|
+
import { stopTypingKeepalive } from "./typing";
|
|
6
|
+
|
|
7
|
+
/** One inbound message waiting in its thread's buffer. */
|
|
8
|
+
export interface ChatBufferedMessage {
|
|
9
|
+
messageId: string;
|
|
10
|
+
channelId: string;
|
|
11
|
+
/** Mention-stripped text. */
|
|
12
|
+
text: string;
|
|
13
|
+
userId: string;
|
|
14
|
+
userName?: string;
|
|
15
|
+
botMentioned: boolean;
|
|
16
|
+
isDirectMessage: boolean;
|
|
17
|
+
attachments: Attachment[];
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
const DEFAULT_BUFFER_MS = 10_000;
|
|
21
|
+
|
|
22
|
+
function configuredBufferMs(): number {
|
|
23
|
+
const value = Number(process.env.CHAT_SDK_BUFFER_MS);
|
|
24
|
+
return Number.isFinite(value) && value > 0 ? value : DEFAULT_BUFFER_MS;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
let buffer: AdditiveBuffer<ChatBufferedMessage> | undefined;
|
|
28
|
+
let bufferMs = 0;
|
|
29
|
+
/** Set while shutdown flushes every waiting thread; those flushes keep `dependsOn`. */
|
|
30
|
+
let draining = false;
|
|
31
|
+
/**
|
|
32
|
+
* Tail of each thread's flush chain. The additive buffer clears a key before
|
|
33
|
+
* it awaits `onFlush`, so a second flush of one thread can start while the
|
|
34
|
+
* first still routes, downloads files or creates its task. Chaining keeps the
|
|
35
|
+
* task order (and `dependsOn`) of one thread; different threads run in parallel.
|
|
36
|
+
*/
|
|
37
|
+
const flushChains = new Map<string, Promise<void>>();
|
|
38
|
+
|
|
39
|
+
/** Run `work` after every earlier flush of `threadId`. A failed flush does not block the next one. */
|
|
40
|
+
function runInThreadOrder(threadId: string, work: () => Promise<void>): Promise<void> {
|
|
41
|
+
const previous = flushChains.get(threadId) ?? Promise.resolve();
|
|
42
|
+
const run = previous.then(work);
|
|
43
|
+
const tail = run.catch(() => {});
|
|
44
|
+
flushChains.set(threadId, tail);
|
|
45
|
+
void tail.then(() => {
|
|
46
|
+
if (flushChains.get(threadId) === tail) flushChains.delete(threadId);
|
|
47
|
+
});
|
|
48
|
+
return run;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
/**
|
|
52
|
+
* Keyed by the raw SDK `thread.id` (`<adapter>:<platform id>`, colons
|
|
53
|
+
* included); the key is never split. A changed `CHAT_SDK_BUFFER_MS` applies
|
|
54
|
+
* once no thread is waiting on the old timer.
|
|
55
|
+
*/
|
|
56
|
+
function getBuffer(): AdditiveBuffer<ChatBufferedMessage> {
|
|
57
|
+
const ms = configuredBufferMs();
|
|
58
|
+
if (!buffer || (ms !== bufferMs && buffer.keys().length === 0)) {
|
|
59
|
+
bufferMs = ms;
|
|
60
|
+
buffer = createAdditiveBuffer<ChatBufferedMessage>({
|
|
61
|
+
timeoutMs: ms,
|
|
62
|
+
label: "chat",
|
|
63
|
+
onFlush: async (items, threadId, reason) => {
|
|
64
|
+
// Read now: `draining` can change while this flush waits in the chain.
|
|
65
|
+
const immediate = reason === "manual" && !draining;
|
|
66
|
+
// Tracked so shutdown drains timer-driven flushes before the DB closes.
|
|
67
|
+
const work = runInThreadOrder(threadId, () => flushChatThread(threadId, items, immediate));
|
|
68
|
+
trackBackground(work);
|
|
69
|
+
// A failed flush created no task, so no relay will stop the indicator.
|
|
70
|
+
await work.catch(() => stopTypingKeepalive(threadId));
|
|
71
|
+
},
|
|
72
|
+
});
|
|
73
|
+
}
|
|
74
|
+
return buffer;
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
/** Add a message to its thread's buffer and reset the debounce timer. */
|
|
78
|
+
export function bufferChatMessage(threadId: string, item: ChatBufferedMessage): void {
|
|
79
|
+
getBuffer().enqueue(threadId, item);
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
/** Flush now with no `dependsOn` (the `!now` command). */
|
|
83
|
+
export async function instantFlushChat(threadId: string): Promise<void> {
|
|
84
|
+
await getBuffer().instantFlush(threadId);
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
export function isChatThreadBuffered(threadId: string): boolean {
|
|
88
|
+
return buffer?.isBuffered(threadId) ?? false;
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
/** Shutdown: turn every waiting thread into its task instead of dropping it. */
|
|
92
|
+
export async function flushAllChatBuffers(): Promise<void> {
|
|
93
|
+
if (!buffer) return;
|
|
94
|
+
draining = true;
|
|
95
|
+
try {
|
|
96
|
+
for (const threadId of buffer.keys()) await buffer.instantFlush(threadId);
|
|
97
|
+
} finally {
|
|
98
|
+
draining = false;
|
|
99
|
+
}
|
|
100
|
+
}
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
import { errorMessage } from "./logger";
|
|
2
|
+
import { getChatInstance, isChatSdkStopping } from "./runtime";
|
|
3
|
+
import { assertTrustedChatTarget } from "./teams-origin";
|
|
4
|
+
import { getChatSdkTestHooks } from "./test-hooks";
|
|
5
|
+
|
|
6
|
+
/** Telegram shows one typing action for about 5 s, so renew it before that. */
|
|
7
|
+
const TYPING_INTERVAL_MS = 4_000;
|
|
8
|
+
/** Hard cap: a task that never relays must not show typing forever. */
|
|
9
|
+
const TYPING_MAX_MS = 15 * 60 * 1000;
|
|
10
|
+
|
|
11
|
+
const keepalives = new Map<string, ReturnType<typeof setInterval>>();
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* Show the typing indicator in `threadId` until `stopTypingKeepalive`, the
|
|
15
|
+
* hard cap, or the adapter stops. Idempotent per thread id. Adapters without
|
|
16
|
+
* `startTyping` and untrusted Teams service URLs are skipped.
|
|
17
|
+
*/
|
|
18
|
+
export function startTypingKeepalive(adapter: string, threadId: string): void {
|
|
19
|
+
if (keepalives.has(threadId)) return;
|
|
20
|
+
// A shutdown flush creates tasks too; no indicator for a reply that comes after a restart.
|
|
21
|
+
if (isChatSdkStopping()) return;
|
|
22
|
+
// Typing sends the Teams bot token to the service URL in the thread id.
|
|
23
|
+
try {
|
|
24
|
+
assertTrustedChatTarget(threadId);
|
|
25
|
+
} catch {
|
|
26
|
+
return;
|
|
27
|
+
}
|
|
28
|
+
if (typeof getChatInstance(adapter)?.getAdapter(adapter)?.startTyping !== "function") return;
|
|
29
|
+
|
|
30
|
+
const hooks = getChatSdkTestHooks();
|
|
31
|
+
const maxMs = hooks.typingMaxMs ?? TYPING_MAX_MS;
|
|
32
|
+
const startedAt = Date.now();
|
|
33
|
+
let warned = false;
|
|
34
|
+
const sendTyping = async (): Promise<void> => {
|
|
35
|
+
const target = getChatInstance(adapter)?.getAdapter(adapter);
|
|
36
|
+
if (Date.now() - startedAt >= maxMs || typeof target?.startTyping !== "function") {
|
|
37
|
+
stopTypingKeepalive(threadId);
|
|
38
|
+
return;
|
|
39
|
+
}
|
|
40
|
+
try {
|
|
41
|
+
await target.startTyping(threadId);
|
|
42
|
+
} catch (error) {
|
|
43
|
+
if (warned) return;
|
|
44
|
+
warned = true;
|
|
45
|
+
console.warn(`[chat-sdk] Typing indicator failed for ${threadId}: ${errorMessage(error)}`);
|
|
46
|
+
}
|
|
47
|
+
};
|
|
48
|
+
const timer = setInterval(() => void sendTyping(), hooks.typingIntervalMs ?? TYPING_INTERVAL_MS);
|
|
49
|
+
timer.unref();
|
|
50
|
+
keepalives.set(threadId, timer);
|
|
51
|
+
void sendTyping();
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
export function stopTypingKeepalive(threadId: string): void {
|
|
55
|
+
const timer = keepalives.get(threadId);
|
|
56
|
+
if (!timer) return;
|
|
57
|
+
clearInterval(timer);
|
|
58
|
+
keepalives.delete(threadId);
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
/** Stop every keepalive. For `stopChatSdk`. */
|
|
62
|
+
export function stopAllTypingKeepalives(): void {
|
|
63
|
+
for (const timer of keepalives.values()) clearInterval(timer);
|
|
64
|
+
keepalives.clear();
|
|
65
|
+
}
|
package/src/pages/version.ts
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
|
-
import { createPageVersion, getPage
|
|
1
|
+
import { createPageVersion, getPage } from "../be/db";
|
|
2
|
+
import { getPageEditCounter } from "../be/db/page-versions";
|
|
2
3
|
import type { PageSnapshot, PageVersion } from "../types";
|
|
3
4
|
|
|
4
5
|
/**
|
|
@@ -8,7 +9,7 @@ import type { PageSnapshot, PageVersion } from "../types";
|
|
|
8
9
|
* Mirrors `snapshotWorkflow` (src/workflows/version.ts:13-44).
|
|
9
10
|
*
|
|
10
11
|
* 1. Load current page state
|
|
11
|
-
* 2. Get max version number for this page (page_versions
|
|
12
|
+
* 2. Get max version number for this page (MAX(page_versions.version))
|
|
12
13
|
* 3. Insert page_versions row with version+1 and the pre-update snapshot
|
|
13
14
|
*
|
|
14
15
|
* Throws on missing parent. Callers in HTTP handlers wrap this in a try/catch
|
|
@@ -24,9 +25,7 @@ export async function snapshotPage(
|
|
|
24
25
|
throw new Error(`Page ${pageId} not found — cannot create snapshot`);
|
|
25
26
|
}
|
|
26
27
|
|
|
27
|
-
const
|
|
28
|
-
const maxVersion = existingVersions.length > 0 ? existingVersions[0]!.version : 0;
|
|
29
|
-
const nextVersion = maxVersion + 1;
|
|
28
|
+
const nextVersion = await getPageEditCounter(pageId);
|
|
30
29
|
|
|
31
30
|
const snapshot: PageSnapshot = {
|
|
32
31
|
title: page.title,
|