@desplega.ai/agent-swarm 2.4.4 → 2.5.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 +4 -2
- package/dist/{acp-adapter-thq04adh.js → acp-adapter-11gp67nr.js} +8 -6
- package/dist/{actions-j6ywh6x5.js → actions-8p2kze8j.js} +24 -22
- package/dist/{amp-adapter-k8b5xmj8.js → amp-adapter-z4qvhacj.js} +2 -2
- package/dist/{anthropic-messages-g6pqmqk4.js → anthropic-messages-txs190dd.js} +17 -20
- package/dist/{app-rb7vc02s.js → app-0qp0a2gk.js} +3 -3
- package/dist/{assistant-ftta1zjf.js → assistant-jedd3ktm.js} +27 -25
- package/dist/{azure-openai-responses-9dbmj346.js → azure-openai-responses-9kfwhmn7.js} +18 -13
- package/dist/{boot-reembed-xns7h5h9.js → boot-reembed-4xwq5tg0.js} +1 -1
- package/dist/{boot-reembed-srp6v6af.js → boot-reembed-n4wq5dd2.js} +1 -1
- package/dist/{boot-scrub-sweep-3qqrh2mz.js → boot-scrub-sweep-69h90xcq.js} +4 -4
- package/dist/{boot-seeds-9ks8hce7.js → boot-seeds-3vn0m9sg.js} +4 -4
- package/dist/{claude-adapter-k5ksyres.js → claude-adapter-221mvad5.js} +8 -6
- package/dist/{claude-managed-adapter-h2y889ge.js → claude-managed-adapter-gt6pa35d.js} +2 -2
- package/dist/{claude-managed-setup-n5a5eqks.js → claude-managed-setup-c3hp36ph.js} +4 -4
- package/dist/{claude-sdk-session-2j3j4kwn.js → claude-sdk-session-8thjsk5e.js} +9729 -9628
- package/dist/{cli-b9fcxxev.js → cli-007cnhm1.js} +50 -4
- package/dist/{cli-j4rqdf2n.js → cli-03vpq45j.js} +2 -2
- package/dist/{cli-amyhm3gj.js → cli-0y6f0ckq.js} +1 -1
- package/dist/{cli-4dg55wsk.js → cli-0z3vjrpm.js} +1 -1
- package/dist/{cli-pccfmtb5.js → cli-179xcyky.js} +11 -11
- package/dist/{cli-3r8samv3.js → cli-1f0gp9vz.js} +3 -2
- package/dist/{cli-hj3debgw.js → cli-1pr672hz.js} +3 -3
- package/dist/{cli-0bqteszy.js → cli-2nf3b08t.js} +328 -25
- package/dist/{cli-t7cyef5g.js → cli-36j4jnk3.js} +4 -4
- package/dist/{cli-er83cjbn.js → cli-3qv8naq8.js} +527 -231
- package/dist/cli-3zwye9sn.js +1193 -0
- package/dist/{cli-60cgh1rv.js → cli-459fk97x.js} +7 -7
- package/dist/{cli-xj0re5tm.js → cli-4w24f7z3.js} +1 -1
- package/dist/{cli-v3yr2gbg.js → cli-5kzk3472.js} +2198 -968
- package/dist/{cli-h7gc0b4r.js → cli-5m84zd2k.js} +55 -18
- package/dist/{cli-w6nadkh4.js → cli-5rvg1ddk.js} +5 -5
- package/dist/{cli-dvkmp9bt.js → cli-6ctwrf1z.js} +20 -10
- package/dist/{cli-9qtjfd60.js → cli-6rg2jn2y.js} +3 -2
- package/dist/{cli-2v045xyf.js → cli-7bwtc6yx.js} +3 -3
- package/dist/{cli-3cgngyd8.js → cli-7g40g8hq.js} +35 -17
- package/dist/{cli-q6r7t299.js → cli-92mqenkc.js} +2379 -154
- package/dist/{cli-cynvc9fj.js → cli-9ce2xw98.js} +8 -8
- package/dist/{cli-gqvh8d8a.js → cli-bgdtv62r.js} +36 -22
- package/dist/{cli-wxbn3yfx.js → cli-cz5nmdxt.js} +2 -2
- package/dist/{cli-wt645v7q.js → cli-dabehxhp.js} +1 -1
- package/dist/{cli-vfha21c9.js → cli-e5bxnjps.js} +2 -2
- package/dist/{cli-p20r8w42.js → cli-fcc5qe3f.js} +763 -268
- package/dist/{cli-1ephgwnq.js → cli-fes51ya2.js} +3 -3
- package/dist/{cli-1d70crsy.js → cli-ftmq4nvk.js} +2 -2
- package/dist/{cli-qkhc5aqy.js → cli-fy1jyyym.js} +13 -13
- package/dist/{cli-s95dknxh.js → cli-g1cw7xtt.js} +1 -1
- package/dist/{cli-qje9k2nh.js → cli-g9ypd5en.js} +12 -2
- package/dist/{cli-74c1wpm0.js → cli-gpv60278.js} +1 -1
- package/dist/{cli-pemywhfq.js → cli-ha9rmwgq.js} +2 -1
- package/dist/{cli-bnz0r3kc.js → cli-j1eghqfm.js} +5 -5
- package/dist/{cli-2zqpkvtt.js → cli-jpxsrxcb.js} +1 -1
- package/dist/{cli-ejs3m1sx.js → cli-kxpfx2xr.js} +1 -1
- package/dist/{cli-37yb7307.js → cli-mbfj11fq.js} +1 -1
- package/dist/{cli-pnp2j5zg.js → cli-mmhj8vd7.js} +2 -2
- package/dist/{cli-xf234ws8.js → cli-n4c8e3s5.js} +2 -0
- package/dist/{cli-a0kcty51.js → cli-n6kmqngw.js} +2 -2
- package/dist/{cli-te6b5wvg.js → cli-nnvjb17s.js} +3 -3
- package/dist/{cli-1dmcq2eh.js → cli-nzfnt2gs.js} +1 -1
- package/dist/{cli-625tb7aq.js → cli-p859hcc8.js} +2 -2
- package/dist/{cli-smvcw9z5.js → cli-pasyvjmm.js} +1 -1
- package/dist/cli-pkaqk57w.js +128 -0
- package/dist/{cli-8nyhf7za.js → cli-qqzmjxkf.js} +129 -105
- package/dist/{cli-zrwpctp4.js → cli-r25qpaxt.js} +161 -63
- package/dist/{cli-nc35pxb7.js → cli-rh5smt8t.js} +19 -4
- package/dist/{cli-qwkcwjdn.js → cli-rycdaj0a.js} +1 -1
- package/dist/{cli-esxerzg7.js → cli-s4sntwt4.js} +2 -2
- package/dist/{cli-b8k7epj1.js → cli-segs7hx9.js} +8 -8
- package/dist/{cli-36ckw0wx.js → cli-stxdrqs2.js} +2 -2
- package/dist/{cli-vdm20wej.js → cli-sy8f9705.js} +4 -4
- package/dist/{cli-1m17dc9d.js → cli-ta8bd7th.js} +2 -2
- package/dist/{cli-2y91wnbh.js → cli-we0ra057.js} +222 -10
- package/dist/{cli-55qmbp6z.js → cli-x21dqn18.js} +5 -5
- package/dist/{cli-dqb4n629.js → cli-x6vf4sad.js} +11 -11
- package/dist/{cli-d2mb5s8a.js → cli-x77gen2p.js} +680 -440
- package/dist/{cli-vq97g1jq.js → cli-xawhpgc9.js} +95 -195
- package/dist/{cli-82d3nkc3.js → cli-xd4eqtr2.js} +2 -2
- package/dist/{cli-9fhkkpj5.js → cli-xt768r5m.js} +2 -2
- package/dist/{cli-zrk6jxv8.js → cli-y4x8ww6s.js} +2 -2
- package/dist/{cli-xc27fwzg.js → cli-y9jvxfhk.js} +3 -3
- package/dist/{cli-44159k28.js → cli-z63a269p.js} +2 -2
- package/dist/{cli-sm8rses9.js → cli-zfdp01hn.js} +8 -5
- package/dist/cli.js +31 -29
- package/dist/{cloudflare-workers-ai-system-one-ch2wxfcp.js → cloudflare-workers-ai-system-one-43v9qkjk.js} +7 -2
- package/dist/{codex-adapter-968xwfy7.js → codex-adapter-tcg1q3jk.js} +6 -4
- package/dist/{codex-login-m819c41r.js → codex-login-rn7c5gxt.js} +1 -1
- package/dist/{codex-session-runner-ht4z99fa.js → codex-session-runner-xfx4wd9x.js} +7 -5
- package/dist/{commands-pcymaeg9.js → commands-ew83nj70.js} +2 -2
- package/dist/{controller-fpjyk46r.js → controller-gnq1k1mf.js} +4 -4
- package/dist/{controllers-connect-mwzx1fh6.js → controllers-connect-tw1dan9r.js} +26 -24
- package/dist/{cursor-adapter-cp4s2e2f.js → cursor-adapter-yq5xgfcg.js} +1 -1
- package/dist/{db-xsjg9jqq.js → db-7gbc53fd.js} +14 -2
- package/dist/{db-sgd12bt9.js → db-t86xrnes.js} +3 -3
- package/dist/{devin-adapter-zgqadvnm.js → devin-adapter-4ycy4vrf.js} +1 -1
- package/dist/{dispatcher-x0jfw2c9.js → dispatcher-64bdyhps.js} +23 -21
- package/dist/{docker-fkgtr75t.js → docker-nfy2gc8t.js} +2 -2
- package/dist/{dsh-adapter-jrq7ma7h.js → dsh-adapter-tvhc4k2d.js} +1 -1
- package/dist/{e2b-16d6ch39.js → e2b-9gkdhm3c.js} +2 -2
- package/dist/{e2b-zn0b4w5w.js → e2b-cjtf8k1z.js} +2 -2
- package/dist/{easter-egg-3d-5cgynaja.js → easter-egg-3d-e4qv8dxj.js} +1 -1
- package/dist/{event-bus-9vpk8gmq.js → event-bus-3z78ep5n.js} +1 -1
- package/dist/{execute-t4e6m3rw.js → execute-0xpz8950.js} +85 -23
- package/dist/{external-channel-8njj4h3b.js → external-channel-e9nw24zv.js} +2 -2
- package/dist/{google-generative-ai-bmq8gkf3.js → google-generative-ai-mk9vcpe1.js} +15 -15
- package/dist/{google-vertex-b1rvp52s.js → google-vertex-q580kz8k.js} +20 -20
- package/dist/{grok-adapter-kkr97v2f.js → grok-adapter-v82srhrx.js} +9 -7
- package/dist/handlers-c9ajzhbn.js +41 -0
- package/dist/{heartbeat-wd03zhn0.js → heartbeat-2vqs1319.js} +26 -24
- package/dist/{hook-xzj2ts2p.js → hook-01nk13wx.js} +7 -5
- package/dist/{hook-f0vghj42.js → hook-jzpheegg.js} +9 -7
- package/dist/{http-4hwxv62p.js → http-fc1k6ec8.js} +859 -252
- package/dist/index-07ef7nkm.js +42 -0
- package/dist/{index-g6tbb6ve.js → index-31ceyf42.js} +1 -1
- package/dist/index-7yts8n9s.js +43 -0
- package/dist/{index-c4ykd0f1.js → index-dr94zbnh.js} +13348 -11611
- package/dist/index-q3t40ryv.js +46 -0
- package/dist/{index-ww2vznkp.js → index-sepja052.js} +1 -1
- package/dist/{index-97q4xn03.js → index-zdycv3ja.js} +135 -79
- package/dist/{index-g216q6jr.js → index-zsyx51ap.js} +1 -4
- package/dist/{keepalive-hrh7xnr9.js → keepalive-5nh2rhmr.js} +4 -4
- package/dist/lead-aayq64jn.js +79 -0
- package/dist/{llama-cpp-classify-vv43gswy.js → llama-cpp-classify-ee7vmd7q.js} +4 -1
- package/dist/{maintenance-e8wnwnqx.js → maintenance-hjhzdt3k.js} +1 -1
- package/dist/{mistral-conversations-1medfwv7.js → mistral-conversations-sd0xf1tn.js} +13 -13
- package/dist/{node-3v3f08f7.js → node-jzjj7r9x.js} +10 -9
- package/dist/{oauth-refresh-sweep-zm0yj4sj.js → oauth-refresh-sweep-y3n1knmd.js} +3 -3
- package/dist/{onboard-dtwcq603.js → onboard-1zq3m7db.js} +2 -2
- package/dist/{openai-codex-responses-de9pvs4d.js → openai-codex-responses-79s8xnbh.js} +29 -23
- package/dist/{openai-completions-mtqge3nf.js → openai-completions-tdbyts0x.js} +21 -20
- package/dist/{openai-decisions-ygk4kkxd.js → openai-decisions-bqhnjyfz.js} +2 -1
- package/dist/{openai-responses-dkyng71g.js → openai-responses-dm3t5vqp.js} +26 -17
- package/dist/{opencode-adapter-x4tqzhkh.js → opencode-adapter-e56086m1.js} +7 -5
- package/dist/{openrouter-images-wvv7camj.js → openrouter-images-ssttr1y4.js} +6 -3
- package/dist/{otel-impl-w3fbhpn0.js → otel-impl-h9yyza47.js} +2 -2
- package/dist/{pi-messages-nhh1pkz2.js → pi-messages-3nf8bq1a.js} +6 -4
- package/dist/{pi-mono-adapter-mcfkn9vf.js → pi-mono-adapter-tzps8f38.js} +9 -37
- package/dist/{pricing-refresh-j38dehfs.js → pricing-refresh-nevhat8h.js} +3 -3
- package/dist/{rbac-roles-ae8yttq4.js → rbac-roles-4y6ngvzq.js} +1 -1
- package/dist/{rbac-roles-ejxx9b35.js → rbac-roles-exbg8xb6.js} +2 -2
- package/dist/{registry-rxw10xzg.js → registry-ajhexe44.js} +1 -1
- package/dist/{render-v2-n16n0phe.js → render-v2-cfsh13z3.js} +3 -3
- package/dist/{resume-z0fgjc38.js → resume-5s2t0dbs.js} +10 -8
- package/dist/{runtime-xe3aqy4x.js → runtime-bt5pkpm1.js} +1 -1
- package/dist/{session-access-cv3mq6mc.js → session-access-dnbyp3cn.js} +6 -4
- package/dist/{setup-mafhj3yt.js → setup-1wj1y77j.js} +1 -1
- package/dist/{typesafe-system-one-2hgpayzy.js → typesafe-system-one-bre38hvs.js} +4 -2
- package/dist/{virtual-modules-4r3zkc20.js → virtual-modules-mnfbh5mr.js} +8 -2
- package/dist/{work-objects-8b86mgjp.js → work-objects-7mg6216g.js} +26 -24
- package/dist/worker-nwb789mg.js +79 -0
- package/dist/{x-y45mthwn.js → x-pbpezr0s.js} +2 -2
- package/openapi.json +793 -6
- package/package.json +11 -11
- package/src/apps/catalog.generated.json +206 -4
- package/src/apps/page-validator.ts +13 -1
- package/src/assets/key.ts +17 -0
- package/src/azure-devops/handlers.ts +1 -1
- package/src/be/chunking.ts +42 -9
- package/src/be/db/approvals.ts +18 -0
- package/src/be/db/session-moves.ts +164 -0
- package/src/be/db/session-tree.ts +384 -0
- package/src/be/db/tasks/draft-text.ts +15 -0
- package/src/be/db/tasks/read.ts +54 -5
- package/src/be/db/workflow-runs.ts +96 -0
- package/src/be/db.ts +85 -237
- package/src/be/memory/index-content.ts +36 -17
- package/src/be/memory/providers/openai-embedding.ts +115 -61
- package/src/be/memory/providers/sqlite-store.ts +69 -23
- package/src/be/memory/reranker.ts +31 -1
- package/src/be/memory/search-candidates.ts +47 -0
- package/src/be/memory/types.ts +14 -0
- package/src/be/onboarding.ts +3 -0
- package/src/be/scripts/typecheck.ts +2 -0
- package/src/be/seed-scripts/catalog/heartbeat-jev-gate.ts +106 -54
- package/src/be/seed-scripts/index.ts +2 -2
- package/src/be/skill-sync.ts +43 -2
- package/src/be/swarm-config-guard.ts +25 -1
- package/src/be/task-attachment-store.ts +30 -0
- package/src/bitbucket/api.ts +81 -0
- package/src/bitbucket/auth.ts +64 -0
- package/src/bitbucket/handlers.ts +434 -0
- package/src/bitbucket/index.ts +28 -0
- package/src/bitbucket/templates.ts +134 -0
- package/src/bitbucket/types.ts +65 -0
- package/src/commands/runner.ts +55 -3
- package/src/e2b/env.ts +1 -0
- package/src/extensions/catalog.generated.json +4 -4
- package/src/extensions/contract-types.generated.ts +1 -1
- package/src/extensions/contract.ts +9 -2
- package/src/extensions/post-bridge.ts +1 -0
- package/src/fs/provider.ts +13 -1
- package/src/github/handlers.ts +6 -6
- package/src/gitlab/handlers.ts +5 -5
- package/src/heartbeat/heartbeat.ts +31 -11
- package/src/http/assets.ts +14 -2
- package/src/http/core.ts +12 -0
- package/src/http/fs.ts +54 -34
- package/src/http/index.ts +4 -0
- package/src/http/mcp-body.ts +37 -0
- package/src/http/mcp-user.ts +3 -5
- package/src/http/mcp.ts +6 -7
- package/src/http/memory.ts +26 -15
- package/src/http/onboarding.ts +1 -0
- package/src/http/page-proxy.ts +61 -5
- package/src/http/pages-public.ts +27 -44
- package/src/http/pages.ts +4 -4
- package/src/http/script-runs.ts +2 -0
- package/src/http/sessions.ts +366 -12
- package/src/http/skills.ts +48 -1
- package/src/http/tasks.ts +1 -1
- package/src/http/users.ts +13 -1
- package/src/http/webhooks.ts +141 -0
- package/src/integrations/chat-sdk/adapters.ts +24 -4
- package/src/integrations/chat-sdk/attachments.ts +90 -12
- package/src/integrations/chat-sdk/facts.ts +138 -0
- package/src/integrations/chat-sdk/inbound.ts +156 -8
- package/src/integrations/chat-sdk/limits.ts +24 -0
- package/src/integrations/chat-sdk/outbound-files.ts +186 -0
- package/src/integrations/chat-sdk/outbound.ts +272 -5
- package/src/integrations/chat-sdk/relay.ts +4 -1
- package/src/integrations/chat-sdk/router.ts +28 -2
- package/src/integrations/chat-sdk/steering.ts +9 -0
- package/src/integrations/chat-sdk/templates.ts +14 -0
- package/src/integrations/chat-sdk/thread-buffer.ts +4 -0
- package/src/mcp-limits.ts +20 -0
- package/src/pages/browser-sdk.ts +8 -3
- package/src/pages/feedback-overlay.ts +1 -1
- package/src/providers/acp-targets.ts +1 -1
- package/src/providers/amp-adapter.ts +1 -1
- package/src/providers/codex-adapter.ts +106 -119
- package/src/providers/codex-agents-md.ts +35 -229
- package/src/providers/cursor-adapter.ts +1 -1
- package/src/providers/pi-mono-adapter.ts +5 -53
- package/src/realtime/transport.ts +1 -2
- package/src/script-workflows/workflow-ctx.ts +1 -0
- package/src/scripts-runtime/types/stdlib.d.ts +2 -0
- package/src/scripts-runtime/types/swarm-extension.d.ts +14 -4
- package/src/scripts-runtime/types/swarm-sdk.d.ts +2 -0
- package/src/server-user.ts +2 -0
- package/src/server.ts +2 -0
- package/src/slack/assistant.ts +2 -2
- package/src/slack/files.ts +2 -0
- package/src/slack/handlers.test.ts +158 -0
- package/src/slack/handlers.ts +23 -32
- package/src/slack/inbound-files.ts +39 -6
- package/src/slack/thread-buffer.ts +2 -1
- package/src/tasks/context-key.ts +61 -1
- package/src/tasks/task-terminal-effects.ts +21 -17
- package/src/tests/apps-card-grid.test.ts +197 -0
- package/src/tests/apps-catalog-additions.test.ts +120 -0
- package/src/tests/apps-tree.test.ts +129 -0
- package/src/tests/bitbucket-auth.test.ts +156 -0
- package/src/tests/bitbucket-handlers.test.ts +425 -0
- package/src/tests/bitbucket-webhook.test.ts +220 -0
- package/src/tests/chat-sdk-attachments-reply.test.ts +52 -0
- package/src/tests/chat-sdk-config.test.ts +11 -0
- package/src/tests/chat-sdk-facts.test.ts +23 -0
- package/src/tests/chat-sdk-inbound.test.ts +597 -4
- package/src/tests/chat-sdk-outbound-files.test.ts +652 -0
- package/src/tests/chat-sdk-telegram-markdown.test.ts +92 -0
- package/src/tests/codex-adapter.test.ts +167 -242
- package/src/tests/config-api-only-keys.test.ts +9 -0
- package/src/tests/context-key.test.ts +50 -0
- package/src/tests/core-auth.test.ts +5 -0
- package/src/tests/cors-allowlist.test.ts +1 -1
- package/src/tests/db-task-read-contract.test.ts +7 -0
- package/src/tests/db-task-write-contract.test.ts +6 -0
- package/src/tests/docs-index-footprint.test.js +3 -3
- package/src/tests/extensions-example-slack-reply-gate.test.ts +262 -91
- package/src/tests/fixtures/bitbucket/pullrequest-comment-created-inline.json +174 -0
- package/src/tests/fixtures/bitbucket/pullrequest-comment-created.json +170 -0
- package/src/tests/fixtures/bitbucket/pullrequest-created-mention.json +132 -0
- package/src/tests/fixtures/bitbucket/pullrequest-created-reviewer.json +140 -0
- package/src/tests/fixtures/bitbucket/pullrequest-fulfilled.json +134 -0
- package/src/tests/fixtures/bitbucket/pullrequest-rejected.json +133 -0
- package/src/tests/generate-mcp-docs.test.ts +101 -0
- package/src/tests/gitlab-handlers.test.ts +3 -3
- package/src/tests/gitlab-vcs-db.test.ts +4 -4
- package/src/tests/heartbeat-checklist.test.ts +33 -0
- package/src/tests/heartbeat-jev-gate-credentials.test.ts +44 -0
- package/src/tests/heartbeat-jev-gate.test.ts +242 -1
- package/src/tests/integration-disable-flags.test.ts +36 -0
- package/src/tests/kv-page-proxy.test.ts +91 -5
- package/src/tests/launch-password-rejection.test.ts +2 -2
- package/src/tests/mcp-input-caps.test.ts +294 -0
- package/src/tests/mcp-tools.test.ts +1 -1
- package/src/tests/memory-chunked-search-starvation.test.ts +200 -0
- package/src/tests/memory-embedding-input-cap.test.ts +114 -0
- package/src/tests/memory.test.ts +14 -0
- package/src/tests/onboarding.test.ts +12 -0
- package/src/tests/page-guest-session.test.ts +19 -8
- package/src/tests/page-proxy-authed.test.ts +1 -1
- package/src/tests/page-proxy.test.ts +17 -10
- package/src/tests/page-session.test.ts +102 -1
- package/src/tests/pages-authed-mode.test.ts +82 -12
- package/src/tests/pages-password-mode.test.ts +5 -5
- package/src/tests/pi-codemode.test.ts +8 -2
- package/src/tests/preload-env.test.ts +57 -0
- package/src/tests/preload.ts +21 -0
- package/src/tests/prompt-template-remaining.test.ts +38 -0
- package/src/tests/realtime-transport.test.ts +13 -7
- package/src/tests/run-root-coverage.test.ts +39 -0
- package/src/tests/runner-bitbucket-vcs.test.ts +191 -0
- package/src/tests/runner-skills-refresh.test.ts +58 -0
- package/src/tests/script-runs-http.test.ts +84 -0
- package/src/tests/scripts-http.test.ts +10 -1
- package/src/tests/secret-scrubber.test.ts +15 -0
- package/src/tests/sessions-move.test.ts +576 -0
- package/src/tests/sessions-tree.test.ts +493 -0
- package/src/tests/skill-files-http.test.ts +50 -0
- package/src/tests/skills-bundle.test.ts +99 -0
- package/src/tests/swarm-config-export-to-env.test.ts +23 -3
- package/src/tests/task-audio-transcription.test.ts +299 -0
- package/src/tests/task-completion-memory-chunking.test.ts +434 -0
- package/src/tests/task-terminal-secret-scrub.test.ts +6 -1
- package/src/tests/transcription.test.ts +299 -0
- package/src/tests/vcs-bitbucket.test.ts +91 -0
- package/src/tests/vcs-provider-isolation.test.ts +123 -0
- package/src/tests/vcs-provider.test.ts +16 -1
- package/src/tests/vcs-tracking.test.ts +3 -3
- package/src/tests/workflow-foreach-bodies.test.ts +637 -0
- package/src/tests/workflow-foreach-concurrency.test.ts +342 -0
- package/src/tests/workflow-foreach-retry.test.ts +537 -0
- package/src/tests/workflow-foreach.test.ts +44 -23
- package/src/tests/workflow-step-claims.test.ts +6 -6
- package/src/tests/workflow-system-one-decision-providers.test.ts +98 -1
- package/src/tests/workflow-system-one-decision.test.ts +2 -1
- package/src/tests/workflow-tla-races.test.ts +2 -2
- package/src/tools/chat-access.ts +10 -0
- package/src/tools/chat-post.ts +31 -11
- package/src/tools/chat-reply.ts +32 -10
- package/src/tools/memory-search.ts +3 -1
- package/src/tools/script-delete.ts +1 -1
- package/src/tools/system-one-decide.ts +17 -4
- package/src/tools/workflows/create-workflow.ts +5 -3
- package/src/types.ts +9 -2
- package/src/utils/config-env-export.ts +4 -1
- package/src/utils/page-session.ts +133 -20
- package/src/utils/secret-scrubber.ts +4 -0
- package/src/utils/skills-refresh.ts +83 -43
- package/src/utils/transcription.ts +246 -0
- package/src/vcs/bitbucket.ts +58 -0
- package/src/vcs/index.ts +2 -0
- package/src/vcs/types.ts +2 -2
- package/src/workflows/approval-resolution.ts +4 -1
- package/src/workflows/checkpoint.ts +9 -1
- package/src/workflows/constants.ts +3 -0
- package/src/workflows/definition.ts +48 -17
- package/src/workflows/engine.ts +25 -15
- package/src/workflows/executors/foreach.ts +45 -99
- package/src/workflows/executors/system-one-decision.ts +28 -7
- package/src/workflows/executors/system-one-providers.ts +39 -4
- package/src/workflows/foreach-body.ts +146 -0
- package/src/workflows/foreach-driver.ts +552 -0
- package/src/workflows/foreach-join.ts +206 -7
- package/src/workflows/node-timeout.ts +21 -0
- package/src/workflows/recovery.ts +68 -3
- package/src/workflows/resume.ts +221 -43
- package/src/workflows/retry-poller.ts +39 -24
- package/src/workflows/step-input.ts +59 -0
- package/src/workflows/task-step-routing.ts +114 -56
- package/templates/extensions/slack-reply-gate/README.md +24 -22
- package/templates/extensions/slack-reply-gate/hooks.ts +146 -95
- package/templates/extensions/slack-reply-gate/manifest.json +2 -2
- package/templates/skills/apps/SKILL.md +28 -6
- package/templates/skills/apps/content.md +28 -6
- package/templates/skills/chat-sdk/content.md +6 -2
- package/templates/skills/code-quality/content.md +2 -1
- package/templates/skills/heartbeat-runbook/content.md +5 -5
- package/templates/skills/pages/content.md +6 -4
- package/templates/skills/slack-interaction/content.md +5 -0
- package/templates/skills/swarm-scripts/SKILL.md +2 -2
- package/templates/skills/swarm-scripts/content.md +2 -2
- package/templates/skills/work-on-task/SKILL.md +1 -1
- package/templates/skills/work-on-task/content.md +1 -1
- package/dist/cli-51y09c6p.js +0 -2657
- package/dist/cli-yw91rnmw.js +0 -13
- package/dist/handlers-0pvee9nw.js +0 -39
- package/dist/index-gdh8jrdm.js +0 -43
- package/dist/index-hg0x2cdv.js +0 -40
- package/dist/index-thm0y6a8.js +0 -41
- package/dist/lead-ee8vazz2.js +0 -77
- package/dist/worker-16s75hm5.js +0 -77
package/src/http/webhooks.ts
CHANGED
|
@@ -31,6 +31,17 @@ import {
|
|
|
31
31
|
isAzureDevOpsEnabled,
|
|
32
32
|
verifyAzureDevOpsWebhook,
|
|
33
33
|
} from "../azure-devops";
|
|
34
|
+
import {
|
|
35
|
+
pullRequestCommentEventOf as bitbucketCommentEventOf,
|
|
36
|
+
pullRequestEventOf as bitbucketPullRequestEventOf,
|
|
37
|
+
handlePullRequestClosed as handleBitbucketPullRequestClosed,
|
|
38
|
+
handlePullRequestCommentCreated as handleBitbucketPullRequestCommentCreated,
|
|
39
|
+
handlePullRequestCreated as handleBitbucketPullRequestCreated,
|
|
40
|
+
isBitbucketEnabled,
|
|
41
|
+
isDuplicateDelivery as isDuplicateBitbucketDelivery,
|
|
42
|
+
markDeliveryProcessed as markBitbucketDeliveryProcessed,
|
|
43
|
+
verifyBitbucketWebhook,
|
|
44
|
+
} from "../bitbucket";
|
|
34
45
|
import type {
|
|
35
46
|
CheckRunEvent,
|
|
36
47
|
CheckSuiteEvent,
|
|
@@ -180,6 +191,28 @@ const azureDevOpsWebhook = route({
|
|
|
180
191
|
},
|
|
181
192
|
});
|
|
182
193
|
|
|
194
|
+
const bitbucketWebhook = route({
|
|
195
|
+
method: "post",
|
|
196
|
+
path: "/api/bitbucket/webhook",
|
|
197
|
+
pattern: ["api", "bitbucket", "webhook"],
|
|
198
|
+
summary: "Handle Bitbucket Cloud webhook events",
|
|
199
|
+
tags: ["Webhooks"],
|
|
200
|
+
auth: { apiKey: false },
|
|
201
|
+
rbac: {
|
|
202
|
+
ungated:
|
|
203
|
+
"provider webhook: authenticated by the per-webhook HMAC secret, not a swarm principal",
|
|
204
|
+
},
|
|
205
|
+
responses: {
|
|
206
|
+
200: { description: "Event processed", schema: WebhookDispatchResultSchema },
|
|
207
|
+
401: { description: "Invalid signature" },
|
|
208
|
+
413: { description: "Body exceeds 1 MiB" },
|
|
209
|
+
503: { description: "Bitbucket integration not configured" },
|
|
210
|
+
},
|
|
211
|
+
});
|
|
212
|
+
|
|
213
|
+
/** Bitbucket Cloud event payloads are far below this. */
|
|
214
|
+
const BITBUCKET_WEBHOOK_MAX_BODY_BYTES = 1024 * 1024;
|
|
215
|
+
|
|
183
216
|
const agentmailWebhook = route({
|
|
184
217
|
method: "post",
|
|
185
218
|
path: "/api/agentmail/webhook",
|
|
@@ -710,6 +743,114 @@ export async function handleWebhooks(
|
|
|
710
743
|
return true;
|
|
711
744
|
}
|
|
712
745
|
|
|
746
|
+
// Bitbucket Cloud webhook — HMAC over the raw body, event keyed by X-Event-Key
|
|
747
|
+
if (bitbucketWebhook.match(req.method, pathSegments)) {
|
|
748
|
+
if (!isBitbucketEnabled()) {
|
|
749
|
+
res.writeHead(503, { "Content-Type": "application/json" });
|
|
750
|
+
res.end(JSON.stringify({ error: "Bitbucket integration not configured" }));
|
|
751
|
+
return true;
|
|
752
|
+
}
|
|
753
|
+
|
|
754
|
+
// The body is read before the HMAC check, so cap it like the Chat SDK
|
|
755
|
+
// route: reject a declared or streamed body over the limit with 413.
|
|
756
|
+
if (enforceContentLengthCap(req, res, BITBUCKET_WEBHOOK_MAX_BODY_BYTES) === BODY_TOO_LARGE) {
|
|
757
|
+
return true;
|
|
758
|
+
}
|
|
759
|
+
const chunks: Buffer[] = [];
|
|
760
|
+
let receivedBytes = 0;
|
|
761
|
+
let tooLarge = false;
|
|
762
|
+
for await (const chunk of req) {
|
|
763
|
+
if (tooLarge) continue;
|
|
764
|
+
receivedBytes += (chunk as Buffer).byteLength;
|
|
765
|
+
if (receivedBytes > BITBUCKET_WEBHOOK_MAX_BODY_BYTES) {
|
|
766
|
+
tooLarge = true;
|
|
767
|
+
chunks.length = 0;
|
|
768
|
+
jsonError(res, `Payload too large (max ${BITBUCKET_WEBHOOK_MAX_BODY_BYTES} bytes)`, 413);
|
|
769
|
+
continue;
|
|
770
|
+
}
|
|
771
|
+
chunks.push(chunk as Buffer);
|
|
772
|
+
}
|
|
773
|
+
if (tooLarge) return true;
|
|
774
|
+
const rawBody = Buffer.concat(chunks).toString();
|
|
775
|
+
|
|
776
|
+
const signature = req.headers["x-hub-signature"] as string | undefined;
|
|
777
|
+
if (!verifyBitbucketWebhook(rawBody, signature)) {
|
|
778
|
+
console.log("[Bitbucket] Invalid webhook signature");
|
|
779
|
+
res.writeHead(401, { "Content-Type": "application/json" });
|
|
780
|
+
res.end(JSON.stringify({ error: "Invalid signature" }));
|
|
781
|
+
return true;
|
|
782
|
+
}
|
|
783
|
+
|
|
784
|
+
let body: unknown;
|
|
785
|
+
try {
|
|
786
|
+
body = JSON.parse(rawBody);
|
|
787
|
+
} catch {
|
|
788
|
+
res.writeHead(400, { "Content-Type": "application/json" });
|
|
789
|
+
res.end(JSON.stringify({ error: "Invalid JSON body" }));
|
|
790
|
+
return true;
|
|
791
|
+
}
|
|
792
|
+
|
|
793
|
+
const eventKey = req.headers["x-event-key"] as string | undefined;
|
|
794
|
+
const requestUuid = req.headers["x-request-uuid"] as string | undefined;
|
|
795
|
+
if (isDuplicateBitbucketDelivery(requestUuid)) {
|
|
796
|
+
console.log(`[Bitbucket] Skipping duplicate delivery ${requestUuid}`);
|
|
797
|
+
bitbucketWebhook.respond(res, 200, { created: false });
|
|
798
|
+
return true;
|
|
799
|
+
}
|
|
800
|
+
console.log(`[Bitbucket] Received ${eventKey} event`);
|
|
801
|
+
|
|
802
|
+
try {
|
|
803
|
+
const result = await runBlockableHandler("Bitbucket", async () => {
|
|
804
|
+
switch (eventKey) {
|
|
805
|
+
case "pullrequest:created":
|
|
806
|
+
return await handleBitbucketPullRequestCreated(body);
|
|
807
|
+
case "pullrequest:comment_created":
|
|
808
|
+
return await handleBitbucketPullRequestCommentCreated(body);
|
|
809
|
+
case "pullrequest:fulfilled":
|
|
810
|
+
return await handleBitbucketPullRequestClosed(body, "merged");
|
|
811
|
+
case "pullrequest:rejected":
|
|
812
|
+
return await handleBitbucketPullRequestClosed(body, "declined");
|
|
813
|
+
default:
|
|
814
|
+
console.log(`[Bitbucket] Ignoring unsupported event type: ${eventKey}`);
|
|
815
|
+
return { created: false };
|
|
816
|
+
}
|
|
817
|
+
});
|
|
818
|
+
|
|
819
|
+
// Emit workflow trigger events for Bitbucket
|
|
820
|
+
const workflowEvent = {
|
|
821
|
+
"pullrequest:created": "created",
|
|
822
|
+
"pullrequest:comment_created": "commented",
|
|
823
|
+
"pullrequest:fulfilled": "merged",
|
|
824
|
+
"pullrequest:rejected": "declined",
|
|
825
|
+
}[eventKey ?? ""];
|
|
826
|
+
const prEvent = workflowEvent ? bitbucketPullRequestEventOf(body) : null;
|
|
827
|
+
if (workflowEvent && prEvent) {
|
|
828
|
+
const pr = prEvent.pullrequest;
|
|
829
|
+
const comment =
|
|
830
|
+
workflowEvent === "commented" ? bitbucketCommentEventOf(body)?.comment : undefined;
|
|
831
|
+
const user = comment?.user ?? prEvent.actor;
|
|
832
|
+
workflowEventBus.emit(`bitbucket.pull_request.${workflowEvent}`, {
|
|
833
|
+
repo: prEvent.repository.full_name,
|
|
834
|
+
number: pr.id,
|
|
835
|
+
...(workflowEvent === "commented"
|
|
836
|
+
? { body: comment?.content.raw }
|
|
837
|
+
: { title: pr.title, body: pr.description ?? undefined }),
|
|
838
|
+
action: workflowEvent,
|
|
839
|
+
user_login: user.account_id ?? user.nickname ?? user.display_name,
|
|
840
|
+
});
|
|
841
|
+
}
|
|
842
|
+
|
|
843
|
+
markBitbucketDeliveryProcessed(requestUuid);
|
|
844
|
+
bitbucketWebhook.respond(res, 200, result);
|
|
845
|
+
} catch (err) {
|
|
846
|
+
const errorMessage = err instanceof Error ? err.message : String(err);
|
|
847
|
+
console.error(`[Bitbucket] Error handling ${eventKey} event: ${scrubSecrets(errorMessage)}`);
|
|
848
|
+
res.writeHead(500, { "Content-Type": "application/json" });
|
|
849
|
+
res.end(JSON.stringify({ error: "Internal server error" }));
|
|
850
|
+
}
|
|
851
|
+
return true;
|
|
852
|
+
}
|
|
853
|
+
|
|
713
854
|
// AgentMail webhook — needs raw body for Svix signature verification
|
|
714
855
|
if (agentmailWebhook.match(req.method, pathSegments)) {
|
|
715
856
|
if (!isAgentMailEnabled()) {
|
|
@@ -1,5 +1,7 @@
|
|
|
1
|
+
import type { TelegramMessage } from "@chat-adapter/telegram";
|
|
1
2
|
import type { Adapter, Logger } from "chat";
|
|
2
3
|
import { type ChatSdkAdapterName, type ResolvedAdapterConfig, readApiUrlOverrides } from "./config";
|
|
4
|
+
import { isTelegramReplyToBot, type TelegramRawMessage } from "./facts";
|
|
3
5
|
import { withInfoFilter } from "./logger";
|
|
4
6
|
import { getChatSdkTestHooks } from "./test-hooks";
|
|
5
7
|
|
|
@@ -61,11 +63,29 @@ export async function loadAdapter(
|
|
|
61
63
|
const base = { ...config, apiUrl, logger };
|
|
62
64
|
switch (name) {
|
|
63
65
|
case "telegram": {
|
|
64
|
-
const {
|
|
66
|
+
const { TelegramAdapter } = await import("@chat-adapter/telegram");
|
|
67
|
+
// The adapter's `mentionOnReply` skips a reply whose target id equals
|
|
68
|
+
// `message_thread_id`, meant for forum topic roots. A plain supergroup
|
|
69
|
+
// reply to a bot message that starts a reply chain has that same shape,
|
|
70
|
+
// so it was never a mention and never reached a handler. Core decides
|
|
71
|
+
// with `is_topic_message`, as the task header facts do.
|
|
72
|
+
class SwarmTelegramAdapter extends TelegramAdapter {
|
|
73
|
+
protected override isBotMentioned(message: TelegramMessage, text: string): boolean {
|
|
74
|
+
return (
|
|
75
|
+
super.isBotMentioned(message, text) ||
|
|
76
|
+
(String(message.from?.id) !== this.botUserId &&
|
|
77
|
+
isTelegramReplyToBot(message as TelegramRawMessage, this.botUserId))
|
|
78
|
+
);
|
|
79
|
+
}
|
|
80
|
+
}
|
|
65
81
|
// Explicit `false` blocks the TELEGRAM_ALLOW_UNVERIFIED_WEBHOOKS env fallback.
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
82
|
+
// Explicit `true` makes a reply to the bot a mention whatever
|
|
83
|
+
// TELEGRAM_MENTION_ON_REPLY says.
|
|
84
|
+
return new SwarmTelegramAdapter({
|
|
85
|
+
...base,
|
|
86
|
+
allowUnverifiedWebhooks: false,
|
|
87
|
+
mentionOnReply: true,
|
|
88
|
+
} as ConstructorParameters<typeof TelegramAdapter>[0]);
|
|
69
89
|
}
|
|
70
90
|
case "slack": {
|
|
71
91
|
const { createSlackAdapter } = await import("@chat-adapter/slack");
|
|
@@ -9,7 +9,9 @@
|
|
|
9
9
|
* row. The task is created in `draft` while its uploads run, with its lease
|
|
10
10
|
* renewed, so no worker can claim it before its attachments exist. Every file
|
|
11
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.
|
|
12
|
+
* flagged there; upload and record failures are logged. Audio files (voice
|
|
13
|
+
* notes) get their transcript in the task text when transcription is
|
|
14
|
+
* configured, and keep the audio attached.
|
|
13
15
|
*/
|
|
14
16
|
import { mkdtemp, rm } from "node:fs/promises";
|
|
15
17
|
import { tmpdir } from "node:os";
|
|
@@ -21,12 +23,28 @@ import { providerPath } from "@/fs/provider";
|
|
|
21
23
|
import { getFileStorageProvider } from "@/fs/registry";
|
|
22
24
|
import { createTaskWithSiblingAwareness } from "@/tasks/sibling-awareness";
|
|
23
25
|
import type { AgentTask, CreateTaskOptions } from "@/types";
|
|
26
|
+
import { transcribeAudioNotes } from "@/utils/transcription";
|
|
24
27
|
import { errorMessage } from "./logger";
|
|
25
28
|
|
|
26
29
|
/** How often a draft's lease is renewed while its uploads run; the sweep waits 5 minutes. */
|
|
27
30
|
const DRAFT_LEASE_REFRESH_MS = 60_000;
|
|
28
31
|
/** A file a user sent the bot, like one uploaded from the dashboard composer. */
|
|
29
|
-
const SHARED_FILE_INTENT = "user-upload";
|
|
32
|
+
export const SHARED_FILE_INTENT = "user-upload";
|
|
33
|
+
|
|
34
|
+
const MB = 1024 * 1024;
|
|
35
|
+
/**
|
|
36
|
+
* Per-file inbound cap by adapter. A Telegram bot can download at most 20 MB
|
|
37
|
+
* through `getFile`; a larger file fails at Telegram before our own cap.
|
|
38
|
+
* Other adapters use the task attachment cap.
|
|
39
|
+
*/
|
|
40
|
+
const CHAT_INBOUND_FILE_LIMITS: Record<string, number> = { telegram: 20 * MB };
|
|
41
|
+
|
|
42
|
+
export function chatInboundFileLimit(adapter: string): number {
|
|
43
|
+
return Math.min(
|
|
44
|
+
CHAT_INBOUND_FILE_LIMITS[adapter] ?? MAX_TASK_ATTACHMENT_BYTES,
|
|
45
|
+
MAX_TASK_ATTACHMENT_BYTES,
|
|
46
|
+
);
|
|
47
|
+
}
|
|
30
48
|
|
|
31
49
|
/**
|
|
32
50
|
* One attachment of a buffered chat message. `id` is `<messageId>-<n>` with
|
|
@@ -43,7 +61,8 @@ export interface ChatFile {
|
|
|
43
61
|
attachment: Attachment;
|
|
44
62
|
}
|
|
45
63
|
|
|
46
|
-
|
|
64
|
+
/** `tooLarge` marks a file over the inbound cap; the user gets a reply naming it. */
|
|
65
|
+
export type ChatFileFailure = { file: ChatFile; reason: string; tooLarge?: boolean };
|
|
47
66
|
|
|
48
67
|
export type FetchedChatFile = { file: ChatFile; path: string; sizeBytes: number; sha256: string };
|
|
49
68
|
|
|
@@ -55,7 +74,7 @@ export type InboundChatFiles = {
|
|
|
55
74
|
[Symbol.asyncDispose](): Promise<void>;
|
|
56
75
|
};
|
|
57
76
|
|
|
58
|
-
function formatFileSize(bytes: number): string {
|
|
77
|
+
export function formatFileSize(bytes: number): string {
|
|
59
78
|
if (bytes < 1024) return `${bytes} B`;
|
|
60
79
|
if (bytes < 1024 * 1024) return `${(bytes / 1024).toFixed(1)} KB`;
|
|
61
80
|
if (bytes < 1024 * 1024 * 1024) return `${(bytes / (1024 * 1024)).toFixed(1)} MB`;
|
|
@@ -81,20 +100,71 @@ export function toChatFiles(
|
|
|
81
100
|
|
|
82
101
|
/**
|
|
83
102
|
* `[File: <name> (<mime>, <size>) id=<id>]` per file, followed by
|
|
84
|
-
* `(not attached: <reason>)` when the file never reached the task
|
|
103
|
+
* `(not attached: <reason>)` when the file never reached the task, and by the
|
|
104
|
+
* file's note (a voice-note transcript) on the next line.
|
|
85
105
|
*/
|
|
86
|
-
export function buildChatAttachmentText(
|
|
106
|
+
export function buildChatAttachmentText(
|
|
107
|
+
files: ChatFile[],
|
|
108
|
+
failed: ChatFileFailure[] = [],
|
|
109
|
+
notes: Map<string, string> = new Map(),
|
|
110
|
+
): string {
|
|
87
111
|
const reasons = new Map(failed.map((f) => [f.file.id, f.reason]));
|
|
88
112
|
return files
|
|
89
113
|
.map((f) => {
|
|
90
114
|
const size = typeof f.size === "number" ? formatFileSize(f.size) : "unknown size";
|
|
91
115
|
const line = `[File: ${f.name} (${f.mimeType ?? "unknown type"}, ${size}) id=${f.id}]`;
|
|
92
116
|
const reason = reasons.get(f.id);
|
|
93
|
-
|
|
117
|
+
const note = notes.get(f.id);
|
|
118
|
+
return [reason ? `${line} (not attached: ${reason})` : line, note].filter(Boolean).join("\n");
|
|
94
119
|
})
|
|
95
120
|
.join("\n");
|
|
96
121
|
}
|
|
97
122
|
|
|
123
|
+
/**
|
|
124
|
+
* A sender-chosen file name as an inline code span, so the bot's reply shows it
|
|
125
|
+
* as literal text and never renders a link, image or heading from it. Backslash
|
|
126
|
+
* escapes are not enough: the Chat SDK parser still autolinks bare URLs.
|
|
127
|
+
*/
|
|
128
|
+
function markdownLiteral(value: string): string {
|
|
129
|
+
const text = value.replace(/[\r\n]+/g, " ");
|
|
130
|
+
const longestRun = Math.max(0, ...(text.match(/`+/g) ?? []).map((run) => run.length));
|
|
131
|
+
const fence = "`".repeat(longestRun + 1);
|
|
132
|
+
// CommonMark strips one space from each end of a padded span.
|
|
133
|
+
const pad = /^[` ]|[` ]$/.test(text) ? " " : "";
|
|
134
|
+
return `${fence}${pad}${text}${pad}${fence}`;
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
/** The reply posted to the thread when files were over the inbound cap. */
|
|
138
|
+
export function formatTooLargeReply(failed: ChatFileFailure[]): string {
|
|
139
|
+
const lines = failed.map((f) => `- ${markdownLiteral(f.file.name)}: ${f.reason}`);
|
|
140
|
+
return [
|
|
141
|
+
`Too large to read, so the swarm did not get ${failed.length === 1 ? "this file" : "these files"}:`,
|
|
142
|
+
...lines,
|
|
143
|
+
"Send a smaller file or a link.",
|
|
144
|
+
].join("\n");
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
/**
|
|
148
|
+
* Transcribe every fetched audio file. Returns one task-text note per file:
|
|
149
|
+
* the transcript, the failure, or (when no provider is configured) a line
|
|
150
|
+
* saying so. Never throws.
|
|
151
|
+
*/
|
|
152
|
+
export async function transcribeChatAudio(
|
|
153
|
+
fetched: FetchedChatFile[],
|
|
154
|
+
): Promise<Map<string, string>> {
|
|
155
|
+
return transcribeAudioNotes(
|
|
156
|
+
fetched
|
|
157
|
+
.filter(({ file }) => file.attachment.type === "audio")
|
|
158
|
+
.map(({ file, path }) => ({
|
|
159
|
+
key: file.id,
|
|
160
|
+
name: file.name,
|
|
161
|
+
data: Bun.file(path),
|
|
162
|
+
mimeType: file.mimeType,
|
|
163
|
+
})),
|
|
164
|
+
"[chat-sdk]",
|
|
165
|
+
);
|
|
166
|
+
}
|
|
167
|
+
|
|
98
168
|
async function readAttachment(attachment: Attachment): Promise<Uint8Array> {
|
|
99
169
|
if (attachment.data instanceof Blob) return new Uint8Array(await attachment.data.arrayBuffer());
|
|
100
170
|
if (attachment.data) return new Uint8Array(attachment.data);
|
|
@@ -119,17 +189,25 @@ export async function fetchChatFiles(files: ChatFile[]): Promise<InboundChatFile
|
|
|
119
189
|
};
|
|
120
190
|
if (files.length === 0) return result;
|
|
121
191
|
|
|
122
|
-
const
|
|
192
|
+
const tooLarge = (file: ChatFile, size: number): ChatFileFailure => {
|
|
193
|
+
const limit = chatInboundFileLimit(file.adapter);
|
|
194
|
+
const platform = file.adapter.charAt(0).toUpperCase() + file.adapter.slice(1);
|
|
195
|
+
return {
|
|
196
|
+
file,
|
|
197
|
+
reason: `too large (${formatFileSize(size)}, the limit for files sent to the ${platform} bot is ${formatFileSize(limit)})`,
|
|
198
|
+
tooLarge: true,
|
|
199
|
+
};
|
|
200
|
+
};
|
|
123
201
|
dir = await mkdtemp(join(tmpdir(), "chat-files-"));
|
|
124
202
|
for (const [index, file] of files.entries()) {
|
|
125
|
-
if (typeof file.size === "number" && file.size >
|
|
126
|
-
result.failed.push(
|
|
203
|
+
if (typeof file.size === "number" && file.size > chatInboundFileLimit(file.adapter)) {
|
|
204
|
+
result.failed.push(tooLarge(file, file.size));
|
|
127
205
|
continue;
|
|
128
206
|
}
|
|
129
207
|
try {
|
|
130
208
|
const bytes = await readAttachment(file.attachment);
|
|
131
|
-
if (bytes.byteLength >
|
|
132
|
-
result.failed.push(
|
|
209
|
+
if (bytes.byteLength > chatInboundFileLimit(file.adapter)) {
|
|
210
|
+
result.failed.push(tooLarge(file, bytes.byteLength));
|
|
133
211
|
continue;
|
|
134
212
|
}
|
|
135
213
|
const path = join(dir, String(index));
|
|
@@ -0,0 +1,138 @@
|
|
|
1
|
+
import type { Message, Thread } from "chat";
|
|
2
|
+
|
|
3
|
+
/** Why a message counts as meant for the swarm. `none`: it reached the router unaddressed. */
|
|
4
|
+
export type ChatAddressedReason =
|
|
5
|
+
| "dm"
|
|
6
|
+
| "mention"
|
|
7
|
+
| "reply-to-bot"
|
|
8
|
+
| "requester-follow-up"
|
|
9
|
+
| "none";
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* Facts about one inbound message that only core can read: the chat it came
|
|
13
|
+
* from (Telegram `message.raw.chat`) and whether it answers the bot.
|
|
14
|
+
*/
|
|
15
|
+
export interface ChatMessageFacts {
|
|
16
|
+
/** Platform chat type, such as Telegram `private`, `group` or `supergroup`. */
|
|
17
|
+
chatType?: string;
|
|
18
|
+
isForum?: boolean;
|
|
19
|
+
/** Group or channel title. */
|
|
20
|
+
title?: string;
|
|
21
|
+
/** Forum topic: `"<name>"` when the update carries it, else `#<thread id>`. */
|
|
22
|
+
topic?: string;
|
|
23
|
+
/** The sender as people know them: `Name (@username)`. */
|
|
24
|
+
from: string;
|
|
25
|
+
/** The message replies to one of the bot's own messages. */
|
|
26
|
+
replyToBot: boolean;
|
|
27
|
+
/** Start of the message it replies to. */
|
|
28
|
+
replyToSnippet?: string;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
const SNIPPET_MAX = 80;
|
|
32
|
+
|
|
33
|
+
export interface TelegramRawMessage {
|
|
34
|
+
message_id?: number;
|
|
35
|
+
message_thread_id?: number;
|
|
36
|
+
is_topic_message?: boolean;
|
|
37
|
+
chat?: { type?: string; title?: string; is_forum?: boolean };
|
|
38
|
+
from?: { first_name?: string; last_name?: string; username?: string };
|
|
39
|
+
reply_to_message?: {
|
|
40
|
+
message_id?: number;
|
|
41
|
+
from?: { id?: number | string };
|
|
42
|
+
text?: string;
|
|
43
|
+
caption?: string;
|
|
44
|
+
forum_topic_created?: { name?: string };
|
|
45
|
+
};
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
function snippet(text: string | undefined): string | undefined {
|
|
49
|
+
const flat = text?.replace(/\s+/g, " ").trim();
|
|
50
|
+
if (!flat) return undefined;
|
|
51
|
+
return flat.length > SNIPPET_MAX ? `${flat.slice(0, SNIPPET_MAX)}...` : flat;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
function personLabel(name: string | undefined, username: string | undefined, fallback: string) {
|
|
55
|
+
if (name && username && name !== username) return `${name} (@${username})`;
|
|
56
|
+
if (username) return `@${username}`;
|
|
57
|
+
return name || fallback;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
/**
|
|
61
|
+
* In a forum topic every plain message replies to the topic's creation
|
|
62
|
+
* message, so that reply is the topic, not an answer to anyone. Only
|
|
63
|
+
* `is_topic_message` marks a topic: a plain supergroup reply also carries
|
|
64
|
+
* `message_thread_id`, the id of its reply chain's first message.
|
|
65
|
+
*/
|
|
66
|
+
function replyIsTopicRoot(raw: TelegramRawMessage): boolean {
|
|
67
|
+
const reply = raw.reply_to_message;
|
|
68
|
+
return (
|
|
69
|
+
reply !== undefined &&
|
|
70
|
+
(reply.forum_topic_created !== undefined ||
|
|
71
|
+
(raw.is_topic_message === true && reply.message_id === raw.message_thread_id))
|
|
72
|
+
);
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
/** The raw Telegram message replies to one of the bot's own messages, not to a forum topic root. */
|
|
76
|
+
export function isTelegramReplyToBot(raw: TelegramRawMessage, botUserId?: string): boolean {
|
|
77
|
+
const fromId = raw.reply_to_message?.from?.id;
|
|
78
|
+
return (
|
|
79
|
+
!replyIsTopicRoot(raw) &&
|
|
80
|
+
fromId !== undefined &&
|
|
81
|
+
botUserId !== undefined &&
|
|
82
|
+
String(fromId) === botUserId
|
|
83
|
+
);
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
function telegramFacts(raw: TelegramRawMessage, message: Message, botUserId?: string) {
|
|
87
|
+
const reply = raw.reply_to_message;
|
|
88
|
+
const topicRoot = replyIsTopicRoot(raw);
|
|
89
|
+
const realReply = reply && !topicRoot ? reply : undefined;
|
|
90
|
+
const topicName = topicRoot ? reply?.forum_topic_created?.name : undefined;
|
|
91
|
+
const name = [raw.from?.first_name, raw.from?.last_name].filter(Boolean).join(" ").trim();
|
|
92
|
+
return {
|
|
93
|
+
chatType: raw.chat?.type,
|
|
94
|
+
isForum: raw.chat?.is_forum === true ? true : undefined,
|
|
95
|
+
title: raw.chat?.title,
|
|
96
|
+
topic:
|
|
97
|
+
raw.is_topic_message === true && raw.message_thread_id !== undefined
|
|
98
|
+
? topicName
|
|
99
|
+
? `"${topicName}"`
|
|
100
|
+
: `#${raw.message_thread_id}`
|
|
101
|
+
: undefined,
|
|
102
|
+
from: personLabel(name || undefined, raw.from?.username, message.author.userId),
|
|
103
|
+
replyToBot: isTelegramReplyToBot(raw, botUserId),
|
|
104
|
+
replyToSnippet: snippet(realReply?.text ?? realReply?.caption),
|
|
105
|
+
} satisfies ChatMessageFacts;
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
/** Read the chat facts of one message. Telegram reads the raw update; other adapters the SDK message. */
|
|
109
|
+
export function readChatMessageFacts(thread: Thread, message: Message): ChatMessageFacts {
|
|
110
|
+
const raw = message.raw as TelegramRawMessage | undefined;
|
|
111
|
+
if (thread.adapter.name === "telegram" && raw && typeof raw === "object") {
|
|
112
|
+
return telegramFacts(raw, message, thread.adapter.botUserId);
|
|
113
|
+
}
|
|
114
|
+
const { fullName, userName, userId } = message.author;
|
|
115
|
+
return {
|
|
116
|
+
chatType: thread.isDM ? "dm" : undefined,
|
|
117
|
+
from: personLabel(fullName || undefined, userName || undefined, userId),
|
|
118
|
+
replyToBot: message.replyTo?.author.isMe === true,
|
|
119
|
+
replyToSnippet: snippet(message.replyTo?.text),
|
|
120
|
+
};
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
/** One line of facts for the task header: `telegram · supergroup "Life" · from: ... · addressed: ...`. */
|
|
124
|
+
export function formatChatHeaderFacts(
|
|
125
|
+
adapter: string,
|
|
126
|
+
facts: ChatMessageFacts,
|
|
127
|
+
addressed: ChatAddressedReason,
|
|
128
|
+
): string {
|
|
129
|
+
// Title, topic and sender are user-set: one line each, so none can forge a header line.
|
|
130
|
+
const oneLine = (text: string) => text.replace(/\s+/g, " ").trim();
|
|
131
|
+
const parts = [adapter];
|
|
132
|
+
if (facts.chatType)
|
|
133
|
+
parts.push(facts.title ? `${facts.chatType} "${oneLine(facts.title)}"` : facts.chatType);
|
|
134
|
+
if (facts.isForum) parts.push(facts.topic ? `forum topic ${oneLine(facts.topic)}` : "forum");
|
|
135
|
+
parts.push(`from: ${oneLine(facts.from)}`, `addressed: ${addressed}`);
|
|
136
|
+
if (facts.replyToSnippet) parts.push(`reply-to: "${facts.replyToSnippet}"`);
|
|
137
|
+
return parts.join(" · ");
|
|
138
|
+
}
|