@desplega.ai/agent-swarm 1.173.0 → 2.1.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-kr4abrsk.js} +17 -16
- package/dist/{actions-sgeyv08k.js → actions-eh4g6w2r.js} +39 -34
- package/dist/{amp-adapter-gpxp3kc9.js → amp-adapter-7h7wdt8q.js} +8 -7
- package/dist/{app-km9qnv2f.js → app-7k8s9w5p.js} +18 -14
- package/dist/{assistant-5r8tcvvd.js → assistant-wswt2y7p.js} +40 -35
- package/dist/{boot-reembed-16rv9843.js → boot-reembed-9apm9qw6.js} +10 -9
- package/dist/{boot-reembed-swwzdgd7.js → boot-reembed-whzbdthj.js} +10 -9
- package/dist/{boot-scrub-sweep-zkr6vkqb.js → boot-scrub-sweep-ndzmvmgt.js} +11 -10
- package/dist/{boot-seeds-d8sp49t2.js → boot-seeds-e9sta0yt.js} +16 -13
- package/dist/{claude-adapter-yzzzh8jp.js → claude-adapter-4mqd3r6r.js} +19 -18
- package/dist/{claude-managed-adapter-xgxhqrt2.js → claude-managed-adapter-9wswb52m.js} +7 -5
- package/dist/{claude-managed-setup-bnmjarjc.js → claude-managed-setup-tpe1effx.js} +6 -4
- package/dist/{claude-sdk-session-4whxere4.js → claude-sdk-session-q49ghmh9.js} +12402 -11850
- package/dist/{cli-ne7smj1b.js → cli-01xvj1rq.js} +10 -10
- package/dist/{cli-nwrdx840.js → cli-035vekcw.js} +3 -3
- package/dist/{cli-9xf7fdtp.js → cli-0pd8hzhc.js} +29 -6
- package/dist/{cli-tmhsxhb8.js → cli-0veb92a3.js} +28 -6
- package/dist/{cli-c3rk1v0x.js → cli-10jesr25.js} +3 -3
- package/dist/{cli-6thk4jz3.js → cli-1y7yywmj.js} +6 -6
- package/dist/{cli-6r2f7edk.js → cli-20krdhz5.js} +47 -19
- package/dist/{cli-rfs4vddv.js → cli-29rja109.js} +39 -20
- package/dist/cli-2m1ktfzz.js +17795 -0
- package/dist/{cli-na2pxe4f.js → cli-2pwa0q4d.js} +15 -4
- package/dist/{cli-7a93cv8c.js → cli-4mengw46.js} +441 -43
- package/dist/{cli-94b48jhk.js → cli-5twdke5x.js} +16 -14
- package/dist/{cli-7a0sde2x.js → cli-6cagzc96.js} +9 -0
- package/dist/cli-6cvfxs8r.js +47 -0
- package/dist/{cli-w1apjecb.js → cli-711vwbxn.js} +2 -2
- package/dist/{cli-r4fqfpwf.js → cli-78vv7446.js} +50 -27
- package/dist/{cli-srqq3gc5.js → cli-7n6s5man.js} +5 -5
- package/dist/{cli-m89t9jdf.js → cli-7x0g4sn1.js} +15416 -16024
- package/dist/{cli-2bw3w47j.js → cli-8wwxw8a3.js} +1 -1
- package/dist/{cli-yatxqynm.js → cli-9ha6hs2d.js} +1 -290
- package/dist/{cli-ywm1azg9.js → cli-9jzdj90z.js} +8 -4
- package/dist/cli-9traj3dn.js +638 -0
- package/dist/{cli-smcrrmhy.js → cli-agzebbd5.js} +9 -9
- package/dist/{cli-c5km5znr.js → cli-c8r3kzzb.js} +1 -1
- package/dist/cli-cvv9k0hh.js +10143 -0
- package/dist/{cli-anzdk0z4.js → cli-d9286m9h.js} +2 -2
- package/dist/cli-e3g5mbkz.js +776 -0
- package/dist/cli-f527ce74.js +781 -0
- package/dist/{cli-gmdajfw5.js → cli-gxzkg1de.js} +5020 -1729
- package/dist/{cli-451ks6aw.js → cli-h1arqn31.js} +1 -1
- package/dist/cli-hjwmhcg1.js +10622 -0
- package/dist/{cli-jpc4r224.js → cli-hqr88ew6.js} +19 -3
- package/dist/{cli-h7k0xtry.js → cli-hvqbtna4.js} +4 -1256
- package/dist/{cli-cd0hv9nh.js → cli-j67mh2vg.js} +2 -2
- package/dist/cli-jta2eyzc.js +619 -0
- package/dist/{cli-wd0hz6ah.js → cli-kbrt8gqa.js} +9 -91
- package/dist/cli-kceah1eh.js +92 -0
- package/dist/{cli-d603qf86.js → cli-ksphgh48.js} +10 -8
- package/dist/{cli-3nxdmjrc.js → cli-mgz4nbgg.js} +4 -4
- package/dist/{cli-saqyage0.js → cli-mz5as5ha.js} +179 -27
- package/dist/cli-n2wq4j7m.js +6985 -0
- package/dist/{cli-ppy4nnnh.js → cli-n8ndw8j7.js} +2 -2
- package/dist/{cli-1w0vj3gw.js → cli-naqzr17j.js} +7 -7
- package/dist/{cli-0a7mm3zp.js → cli-nm1p8jfr.js} +25 -21
- package/dist/{cli-wrh08zjd.js → cli-p3zbc1rw.js} +5 -18
- package/dist/{cli-hsem16hb.js → cli-pe05f8yk.js} +29 -6
- package/dist/{cli-5wpw4rab.js → cli-qmzsbkvy.js} +5 -5
- package/dist/{cli-bv2etw2y.js → cli-qnm4e9e4.js} +99 -590
- package/dist/{cli-1x7da0de.js → cli-qqy4q48b.js} +8 -8
- package/dist/{cli-r44t9wqc.js → cli-r106725k.js} +2 -2
- package/dist/{cli-5er5gjbm.js → cli-r35rw5de.js} +15 -15
- package/dist/{cli-v3zhnxsb.js → cli-r610p9bs.js} +2 -2
- package/dist/{cli-7jykv7ef.js → cli-rc6pf5kv.js} +1 -1
- package/dist/{cli-x119kdba.js → cli-rdxm77k3.js} +1 -1
- package/dist/cli-rhs9pz33.js +294 -0
- package/dist/{cli-0p3enk35.js → cli-s69nfyeh.js} +9 -1
- package/dist/{cli-0kxf81jj.js → cli-sawv63e8.js} +2 -2
- package/dist/{cli-e3pmyt39.js → cli-sg19gbf0.js} +1 -1
- package/dist/{cli-d161g69q.js → cli-shk7r5cs.js} +57 -6
- package/dist/{cli-7pz3b7x5.js → cli-sksetb2q.js} +36 -1
- package/dist/{cli-014vns2y.js → cli-stfs0k5s.js} +208 -525
- package/dist/{cli-db3m6zdt.js → cli-t02z59f2.js} +1 -1
- package/dist/{cli-b07hr3bt.js → cli-txqb9s38.js} +2 -2
- package/dist/{cli-s8z6x4b4.js → cli-v7g551te.js} +225 -113
- package/dist/{cli-rfz2ve55.js → cli-vgqzp4em.js} +3341 -955
- package/dist/{cli-7avy4v8w.js → cli-w1q7h2r2.js} +5 -3
- package/dist/{cli-rcp5aqdw.js → cli-w7m81tb7.js} +1 -1
- package/dist/cli-wfnemr1t.js +86 -0
- package/dist/cli-wmgrkscj.js +23254 -0
- package/dist/{cli-qm1448f9.js → cli-wms6x9db.js} +8 -8
- package/dist/{cli-tadfgd0t.js → cli-x1r545x8.js} +33 -4
- package/dist/{cli-xc7p7s3g.js → cli-xczsgshn.js} +6 -6
- package/dist/{cli-fmhj1ehj.js → cli-y3167wt1.js} +882 -271
- package/dist/{cli-r0byd61h.js → cli-yjmqpxt3.js} +17 -16
- package/dist/{cli-81r1wjyv.js → cli-z780ec7c.js} +1 -1
- package/dist/{cli-phcqp1b9.js → cli-zm9t2vde.js} +2 -2
- package/dist/cli.js +34 -49
- package/dist/{codex-adapter-r3dn4fp5.js → codex-adapter-4z16v9n0.js} +15 -14
- package/dist/{codex-login-841kv5fk.js → codex-login-pkepy2j1.js} +3 -2
- package/dist/{codex-session-runner-fwt793h4.js → codex-session-runner-rvms9y2x.js} +15 -14
- package/dist/{commands-z8fq9qna.js → commands-2a9rtyat.js} +11 -10
- package/dist/{controller-hq5ggpcp.js → controller-3k3madbn.js} +5 -4
- package/dist/{controllers-connect-3mf2btyj.js → controllers-connect-v9yn8zb9.js} +42 -37
- package/dist/{cursor-adapter-q8pgv5qa.js → cursor-adapter-98gbhyzn.js} +7 -6
- package/dist/{db-qfpq4bt7.js → db-eywwsnjw.js} +30 -9
- package/dist/{db-yyhqqrbf.js → db-ncypk9np.js} +12 -11
- package/dist/{devin-adapter-xgtj680j.js → devin-adapter-zeq8b9gz.js} +3 -2
- package/dist/{dispatcher-x0x7qfcq.js → dispatcher-w94kc575.js} +38 -33
- package/dist/{docker-f503k9p0.js → docker-b1e4cqab.js} +3 -2
- package/dist/{dsh-adapter-34etvb5m.js → dsh-adapter-47ztej36.js} +7 -6
- package/dist/{e2b-66xra4az.js → e2b-bbt8ey45.js} +4 -3
- package/dist/{e2b-xwhyj8cw.js → e2b-dp804sen.js} +4 -3
- package/dist/{event-bus-vjtv82gm.js → event-bus-j2jdg3qb.js} +3 -2
- package/dist/{external-channel-h1fjecwe.js → external-channel-zrkg8ar9.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-s9ncp9vy.js} +18 -17
- package/dist/{handlers-znb9n7d4.js → handlers-kqftvjgw.js} +41 -36
- package/dist/{heartbeat-bm4qkn8d.js → heartbeat-9q96p8v9.js} +43 -38
- package/dist/{hook-254r4mza.js → hook-56qe10dp.js} +11 -23
- package/dist/{hook-xaa3q80s.js → hook-yy6rw42k.js} +12 -11
- package/dist/{http-3dgrgp8y.js → http-r2dnb8f8.js} +2411 -275
- package/dist/{index-fx3jqq1e.js → index-3gtmkhc6.js} +38 -33
- package/dist/{index-bdsqa799.js → index-3k5hkz7r.js} +43 -38
- package/dist/index-52j1aja4.js +3465 -0
- package/dist/{index-g2byveez.js → index-5e101240.js} +38 -33
- package/dist/{index-2ybk7khy.js → index-5wyfjc2c.js} +845 -683
- 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-jd7sejm2.js → index-vxvhxxbn.js} +10 -9
- package/dist/index-we27kd31.js +12810 -0
- package/dist/{keepalive-6ywhpja1.js → keepalive-psj6j54c.js} +12 -11
- package/dist/{lead-cw45n21s.js → lead-hajz4688.js} +61 -56
- package/dist/{maintenance-ss1a1ge9.js → maintenance-ps02sb5m.js} +10 -9
- package/dist/{node-fkajaq96.js → node-mwb5r697.js} +2 -1
- package/dist/{oauth-refresh-sweep-3c6f8vyy.js → oauth-refresh-sweep-0284h93b.js} +11 -10
- package/dist/{onboard-8hzdhc1k.js → onboard-rbf6bg01.js} +11 -3
- package/dist/{opencode-adapter-qbchbcfz.js → opencode-adapter-13dcatgq.js} +18 -13
- package/dist/{otel-impl-e6mnvc4v.js → otel-impl-zvqcbhp7.js} +242 -868
- package/dist/{pi-mono-adapter-3c9g9snh.js → pi-mono-adapter-xxy47q4v.js} +244 -106
- package/dist/{pricing-refresh-gedkjdw0.js → pricing-refresh-kefx9v3t.js} +12 -11
- package/dist/{rbac-roles-dt4vh1yy.js → rbac-roles-3m36rxm2.js} +11 -10
- package/dist/{rbac-roles-eszdg6pm.js → rbac-roles-z76xv0hq.js} +12 -11
- package/dist/{render-v2-907cy6sa.js → render-v2-wz73bs25.js} +18 -14
- package/dist/{resume-42stcaz0.js → resume-k4wzge8w.js} +25 -21
- package/dist/{runtime-qfry88v2.js → runtime-sa2vr16g.js} +2 -2
- package/dist/{setup-adbc1c10.js → setup-2fp2pypp.js} +2 -2
- package/dist/{virtual-modules-rtmt494q.js → virtual-modules-9gkxqrap.js} +6 -5
- package/dist/{work-objects-wpj8ck95.js → work-objects-6k0ywz3m.js} +41 -36
- package/dist/{worker-bzdra9rf.js → worker-5v3rxsdh.js} +61 -56
- package/dist/{x-qfmygqqv.js → x-4mtdeym1.js} +3 -2
- package/openapi.json +1279 -329
- package/package.json +15 -15
- package/src/agentmail/handlers.ts +21 -11
- package/src/apps/definition.ts +16 -0
- package/src/azure-devops/api.ts +52 -2
- package/src/azure-devops/boards-sync.ts +50 -20
- package/src/azure-devops/index.ts +1 -0
- package/src/azure-devops/outbound.ts +95 -0
- package/src/be/boot-seeds.ts +11 -0
- package/src/be/config-refs.ts +60 -0
- package/src/be/db/model-profiles.ts +315 -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 +237 -0
- package/src/be/db/tasks/write.ts +7 -0
- package/src/be/db.ts +87 -36
- 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/migrations/219_relabel_pi_context_compactions.sql +22 -0
- package/src/be/migrations/220_model_profiles.sql +137 -0
- package/src/be/model-catalog-store.ts +5 -1
- package/src/be/model-tier-resolution.ts +155 -10
- package/src/be/model-validation.ts +12 -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 +22 -8
- package/src/be/secret-registry.ts +6 -2
- 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-model-profiles.ts +41 -0
- package/src/be/seed-pricing.ts +19 -2
- package/src/be/seed-scripts/catalog/heartbeat-jev-gate.ts +368 -0
- 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-scripts/index.ts +9 -0
- 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 +53 -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 +193 -71
- package/src/e2b/env.ts +5 -0
- package/src/extensions/catalog.generated.json +17 -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/loader.ts +10 -5
- package/src/extensions/post-bridge.ts +40 -0
- package/src/github/handlers.ts +11 -4
- package/src/heartbeat/heartbeat.ts +2 -1
- package/src/heartbeat/templates.ts +8 -5
- package/src/hooks/hook.ts +0 -26
- package/src/http/agents.ts +49 -2
- package/src/http/all-routes.ts +1 -1
- package/src/http/apps.ts +30 -3
- package/src/http/config.ts +62 -19
- package/src/http/core.ts +8 -0
- package/src/http/index.ts +25 -3
- package/src/http/integrations.ts +88 -1
- package/src/http/mcp.ts +15 -5
- package/src/http/model-profiles.ts +303 -0
- package/src/http/page-proxy.ts +2 -4
- package/src/http/pages-public.ts +13 -7
- package/src/http/pages.ts +3 -13
- package/src/http/poll.ts +42 -71
- package/src/http/requested-by.ts +54 -0
- package/src/http/script-runs.ts +1 -1
- package/src/http/scripts.ts +26 -2
- package/src/http/stats.ts +1 -30
- package/src/http/tasks.ts +79 -5
- package/src/http/utils.ts +14 -0
- package/src/http/webhooks.ts +160 -3
- 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/{artifact-sdk → pages}/feedback-overlay.ts +65 -6
- package/src/pages/version.ts +4 -5
- package/src/prompts/base-prompt.ts +111 -45
- package/src/providers/acp-target-catalog.ts +6 -0
- package/src/providers/acp-targets.ts +56 -3
- package/src/providers/amp-adapter.ts +1 -1
- package/src/providers/claude-adapter.ts +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/pi-mono-extension.ts +17 -3
- package/src/providers/pricing-sources.md +33 -2
- package/src/providers/reasoning-effort.ts +37 -0
- package/src/providers/types.ts +6 -2
- package/src/rbac/legacy-policy.ts +16 -2
- package/src/rbac/permissions.ts +26 -0
- package/src/scheduler/deferred-task-waits.ts +18 -5
- package/src/scheduler/scheduler.ts +11 -3
- package/src/scripts-runtime/executors/quickjs-prelude.ts +75 -6
- package/src/scripts-runtime/executors/quickjs-runner.ts +85 -8
- package/src/scripts-runtime/executors/quickjs-web-shims.ts +1041 -0
- package/src/scripts-runtime/extract-args-schema.ts +9 -8
- package/src/scripts-runtime/loader.ts +19 -7
- package/src/scripts-runtime/quickjs-compat.ts +464 -0
- package/src/scripts-runtime/sdk-allowlist.ts +12 -6
- package/src/scripts-runtime/swarm-sdk.ts +0 -11
- package/src/scripts-runtime/types/stdlib.d.ts +39 -15
- package/src/scripts-runtime/types/swarm-extension.d.ts +66 -1
- package/src/scripts-runtime/types/swarm-sdk.d.ts +39 -15
- package/src/server.ts +62 -19
- package/src/slack/blocks.ts +4 -2
- package/src/slack/event-dedup.ts +1 -1
- package/src/slack/reaction-shortcode.ts +14 -0
- package/src/slack/render-v2.ts +12 -7
- package/src/slack/responses.ts +5 -4
- package/src/slack/thread-buffer.ts +41 -22
- 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 +15 -33
- package/src/tests/acp-adapter.test.ts +42 -0
- package/src/tests/agentmail-filters.test.ts +47 -0
- package/src/tests/api-drain-worker-e2e.test.ts +1 -6
- package/src/tests/apps-spike2.test.ts +35 -0
- package/src/tests/azure-devops-outbound.test.ts +420 -0
- package/src/tests/base-prompt.test.ts +58 -54
- package/src/tests/chat-sdk-commands.test.ts +191 -0
- package/src/tests/chat-sdk-config.test.ts +334 -0
- package/src/tests/chat-sdk-discord-api.test.ts +68 -0
- package/src/tests/chat-sdk-discord-gateway.test.ts +252 -0
- package/src/tests/chat-sdk-discord-webhook.test.ts +126 -0
- package/src/tests/chat-sdk-fields-sync.test.ts +50 -0
- package/src/tests/chat-sdk-harness.ts +146 -0
- package/src/tests/chat-sdk-inbound.test.ts +363 -0
- package/src/tests/chat-sdk-relay.test.ts +470 -0
- package/src/tests/chat-sdk-reload.test.ts +128 -0
- package/src/tests/chat-sdk-shutdown.test.ts +220 -0
- package/src/tests/chat-sdk-slack.test.ts +268 -0
- package/src/tests/chat-sdk-state.test.ts +163 -0
- package/src/tests/chat-sdk-status-route.test.ts +382 -0
- package/src/tests/chat-sdk-teams.test.ts +277 -0
- package/src/tests/chat-sdk-tools.test.ts +520 -0
- package/src/tests/chat-sdk-typing.test.ts +120 -0
- package/src/tests/chat-sdk-webhook-telegram.test.ts +200 -0
- package/src/tests/claude-adapter-binary.test.ts +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 +22 -0
- package/src/tests/db-task-write-contract.test.ts +11 -0
- package/src/tests/deferred-task-wake.test.ts +78 -1
- 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/env-flag.test.ts +1 -0
- package/src/tests/explicit-model-validation.test.ts +105 -1
- package/src/tests/extensions-catalog.test.ts +1 -0
- package/src/tests/extensions-example-comms-adherence-gate.test.ts +413 -0
- package/src/tests/extensions-example-route-chat-channel.test.ts +154 -0
- package/src/tests/extensions-loader.test.ts +15 -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/heartbeat-jev-gate-credentials.test.ts +168 -0
- package/src/tests/heartbeat-jev-gate.test.ts +385 -0
- 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-profile-backcompat.test.ts +327 -0
- package/src/tests/model-profile-providers.test.ts +302 -0
- package/src/tests/model-profile-runtime-secrets.test.ts +223 -0
- package/src/tests/model-profiles-api.test.ts +614 -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-keys.test.ts +121 -0
- package/src/tests/pages-feedback.test.ts +4 -1
- package/src/tests/paused-tasks-requested-by.test.ts +102 -0
- package/src/tests/pi-context-compaction.test.ts +193 -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/profile-claim.test.ts +269 -0
- package/src/tests/prompt-template-remaining.test.ts +35 -0
- package/src/tests/quickjs-compat.test.ts +213 -0
- package/src/tests/rbac-engine.test.ts +32 -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 +6 -0
- package/src/tests/script-executor-conformance.test.ts +252 -1
- package/src/tests/script-executor-quickjs.test.ts +96 -0
- package/src/tests/script-runs-http.test.ts +89 -0
- package/src/tests/scripts-http.test.ts +217 -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 +96 -0
- package/src/tests/seed-model-profiles.test.ts +174 -0
- package/src/tests/seed-scripts.test.ts +2 -2
- 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/skill-invocations.test.ts +4 -2
- package/src/tests/slack-blocks.test.ts +36 -0
- package/src/tests/slack-inline-output.test.ts +14 -0
- package/src/tests/slack-reaction-config.test.ts +20 -0
- package/src/tests/slack-render-v2.test.ts +37 -0
- package/src/tests/slack-thread-buffer.test.ts +98 -1
- 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-llm.test.ts +61 -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/model-profiles/list-model-profiles.ts +54 -0
- package/src/tools/register-chat-channel.ts +139 -0
- package/src/tools/request-human-input.ts +11 -3
- package/src/tools/script-apis.ts +5 -1
- package/src/tools/script-upsert.ts +4 -1
- package/src/tools/send-task.ts +48 -1
- 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/system-one-decide.ts +104 -0
- package/src/tools/task-action.ts +22 -1
- package/src/tools/tool-config.ts +14 -6
- package/src/types.ts +48 -3
- 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 +86 -10
- package/src/utils/task-tool-manifest.ts +19 -2
- package/src/utils/vcs-repo-match.ts +29 -0
- package/src/workflows/executors/raw-llm.ts +8 -3
- package/src/workflows/executors/swarm-script.ts +1 -0
- package/src/workflows/executors/system-one-decision.ts +21 -11
- package/src/workflows/executors/workflow-llm.ts +22 -0
- package/src/workflows/index.ts +21 -12
- package/templates/extensions/README.md +1 -0
- package/templates/extensions/comms-adherence-gate/README.md +88 -0
- package/templates/extensions/comms-adherence-gate/hooks.ts +489 -0
- package/templates/extensions/comms-adherence-gate/manifest.json +8 -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-42qhyvwx.js +0 -33
- 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
|
@@ -0,0 +1,470 @@
|
|
|
1
|
+
import { afterAll, beforeAll, beforeEach, describe, expect, test } from "bun:test";
|
|
2
|
+
import { unlink } from "node:fs/promises";
|
|
3
|
+
import {
|
|
4
|
+
cancelTask,
|
|
5
|
+
closeDb,
|
|
6
|
+
completeTask,
|
|
7
|
+
createAgent,
|
|
8
|
+
createTaskExtended,
|
|
9
|
+
failTask,
|
|
10
|
+
getDbClient,
|
|
11
|
+
initDb,
|
|
12
|
+
startTask,
|
|
13
|
+
supersedeTask,
|
|
14
|
+
} from "../be/db";
|
|
15
|
+
import { installExtension, setExtensionState } from "../be/extensions/db";
|
|
16
|
+
import { listRegistered, registerLoaded, unregister } from "../extensions/dispatcher";
|
|
17
|
+
import { ensureExtensionAgent } from "../extensions/identity";
|
|
18
|
+
import { cleanExtensionTmpRoot, loadExtension } from "../extensions/loader";
|
|
19
|
+
import { initExtensionPostBridge, teardownExtensionPostBridge } from "../extensions/post-bridge";
|
|
20
|
+
import { splitChatMessage } from "../integrations/chat-sdk/limits";
|
|
21
|
+
import {
|
|
22
|
+
formatChatRelayText,
|
|
23
|
+
processPendingChatRelays,
|
|
24
|
+
RELAY_GIVE_UP_AFTER_MS,
|
|
25
|
+
stopChatRelayWatcher,
|
|
26
|
+
} from "../integrations/chat-sdk/relay";
|
|
27
|
+
import { chatSdkContextKey } from "../tasks/context-key";
|
|
28
|
+
import { createResumeFollowUp } from "../tasks/worker-follow-up";
|
|
29
|
+
import type { Agent, AgentTask } from "../types";
|
|
30
|
+
import { startTelegramHarness, type TelegramHarness } from "./chat-sdk-harness";
|
|
31
|
+
import { loadBundleFixture } from "./fixtures/extensions/load";
|
|
32
|
+
|
|
33
|
+
const TEST_DB_PATH = "./test-chat-sdk-relay.sqlite";
|
|
34
|
+
|
|
35
|
+
let harness: TelegramHarness;
|
|
36
|
+
let lead: Agent;
|
|
37
|
+
let nextChatId = 880_000;
|
|
38
|
+
|
|
39
|
+
type ObligationRow = {
|
|
40
|
+
task_id: string;
|
|
41
|
+
delivered_at: string | null;
|
|
42
|
+
attempt_count: number;
|
|
43
|
+
last_attempt_at: string | null;
|
|
44
|
+
given_up_at: string | null;
|
|
45
|
+
last_error: string | null;
|
|
46
|
+
};
|
|
47
|
+
|
|
48
|
+
type Received = { event: string; payload: Record<string, unknown> };
|
|
49
|
+
const received: Received[] = [];
|
|
50
|
+
|
|
51
|
+
async function removeDbFiles(): Promise<void> {
|
|
52
|
+
for (const suffix of ["", "-wal", "-shm"]) {
|
|
53
|
+
await unlink(`${TEST_DB_PATH}${suffix}`).catch(() => {});
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
async function startHarness(): Promise<void> {
|
|
58
|
+
harness = await startTelegramHarness();
|
|
59
|
+
// Ticks are driven by the tests with an injected clock.
|
|
60
|
+
await stopChatRelayWatcher();
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
async function obligation(taskId: string): Promise<ObligationRow | null> {
|
|
64
|
+
return getDbClient().get<ObligationRow>(
|
|
65
|
+
"SELECT * FROM chat_relay_obligations WHERE task_id = ?",
|
|
66
|
+
[taskId],
|
|
67
|
+
);
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
/** Telegram send calls (rich or plain) addressed to `chatId`. */
|
|
71
|
+
function sendsTo(chatId: number): Array<Record<string, unknown>> {
|
|
72
|
+
return harness.mock.calls
|
|
73
|
+
.filter((call) => /^send(Rich)?Message$/.test(call.method))
|
|
74
|
+
.map((call) => call.body as Record<string, unknown>)
|
|
75
|
+
.filter((body) => String(body.chat_id) === String(chatId));
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
function sentText(body: Record<string, unknown>): string {
|
|
79
|
+
const rich = body.rich_message as { markdown?: string } | undefined;
|
|
80
|
+
return String(rich?.markdown ?? body.text ?? "");
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
async function createChatTask(
|
|
84
|
+
chatId: number,
|
|
85
|
+
options: { source?: "chat" | "system"; taskType?: string } = {},
|
|
86
|
+
): Promise<AgentTask> {
|
|
87
|
+
return createTaskExtended(`chat task for ${chatId}`, {
|
|
88
|
+
agentId: lead.id,
|
|
89
|
+
source: options.source ?? "chat",
|
|
90
|
+
taskType: options.taskType,
|
|
91
|
+
contextKey: chatSdkContextKey(`telegram:${chatId}`),
|
|
92
|
+
});
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
beforeAll(async () => {
|
|
96
|
+
await removeDbFiles();
|
|
97
|
+
initDb(TEST_DB_PATH);
|
|
98
|
+
lead = await createAgent({
|
|
99
|
+
id: crypto.randomUUID(),
|
|
100
|
+
name: "ChatRelayLead",
|
|
101
|
+
isLead: true,
|
|
102
|
+
status: "idle",
|
|
103
|
+
});
|
|
104
|
+
await startHarness();
|
|
105
|
+
});
|
|
106
|
+
|
|
107
|
+
afterAll(async () => {
|
|
108
|
+
teardownExtensionPostBridge();
|
|
109
|
+
for (const loaded of listRegistered()) {
|
|
110
|
+
unregister(loaded.record.id);
|
|
111
|
+
await loaded.dispose();
|
|
112
|
+
}
|
|
113
|
+
await cleanExtensionTmpRoot();
|
|
114
|
+
await harness.stop();
|
|
115
|
+
closeDb();
|
|
116
|
+
await removeDbFiles();
|
|
117
|
+
});
|
|
118
|
+
|
|
119
|
+
beforeEach(() => {
|
|
120
|
+
nextChatId += 1;
|
|
121
|
+
harness.mock.failSends(0);
|
|
122
|
+
});
|
|
123
|
+
|
|
124
|
+
describe("chat relay text", () => {
|
|
125
|
+
test("status prefix for failed and cancelled tasks, default for an empty completion", () => {
|
|
126
|
+
const base = { output: undefined, failureReason: undefined } as unknown as AgentTask;
|
|
127
|
+
expect(formatChatRelayText({ ...base, status: "completed" })).toBe("Task completed.");
|
|
128
|
+
expect(formatChatRelayText({ ...base, status: "completed", output: "done" })).toBe("done");
|
|
129
|
+
expect(formatChatRelayText({ ...base, status: "failed", failureReason: "boom" })).toBe(
|
|
130
|
+
"**Task failed.**\n\nboom",
|
|
131
|
+
);
|
|
132
|
+
expect(formatChatRelayText({ ...base, status: "cancelled" })).toBe("**Task cancelled.**");
|
|
133
|
+
});
|
|
134
|
+
|
|
135
|
+
test("splitChatMessage keeps every chunk within the limit and loses no text", () => {
|
|
136
|
+
const words = Array.from({ length: 2000 }, (_, i) => `word${i}`).join(" ");
|
|
137
|
+
const chunks = splitChatMessage(words, 4096);
|
|
138
|
+
expect(chunks.every((chunk) => chunk.length <= 4096)).toBe(true);
|
|
139
|
+
expect(chunks.join(" ")).toBe(words);
|
|
140
|
+
expect(splitChatMessage("short", 4096)).toEqual(["short"]);
|
|
141
|
+
});
|
|
142
|
+
});
|
|
143
|
+
|
|
144
|
+
describe("chat relay watcher", () => {
|
|
145
|
+
test("a completed chat task gets one obligation and one tick posts its output", async () => {
|
|
146
|
+
const chatId = nextChatId;
|
|
147
|
+
const task = await createChatTask(chatId);
|
|
148
|
+
expect(await obligation(task.id)).toBeNull();
|
|
149
|
+
await completeTask(task.id, "The answer is 42.");
|
|
150
|
+
|
|
151
|
+
const rows = await getDbClient().query<{ n: number }>(
|
|
152
|
+
"SELECT COUNT(*) AS n FROM chat_relay_obligations WHERE task_id = ?",
|
|
153
|
+
[task.id],
|
|
154
|
+
);
|
|
155
|
+
expect(rows[0]?.n).toBe(1);
|
|
156
|
+
|
|
157
|
+
await processPendingChatRelays();
|
|
158
|
+
const sends = sendsTo(chatId);
|
|
159
|
+
expect(sends).toHaveLength(1);
|
|
160
|
+
expect(sentText(sends[0] as Record<string, unknown>)).toContain("The answer is 42.");
|
|
161
|
+
const row = await obligation(task.id);
|
|
162
|
+
expect(row?.delivered_at).not.toBeNull();
|
|
163
|
+
expect(row?.attempt_count).toBe(1);
|
|
164
|
+
|
|
165
|
+
// Delivered rows never post again.
|
|
166
|
+
await processPendingChatRelays();
|
|
167
|
+
expect(sendsTo(chatId)).toHaveLength(1);
|
|
168
|
+
});
|
|
169
|
+
|
|
170
|
+
test("failed and cancelled tasks post with a status line", async () => {
|
|
171
|
+
const failedChat = nextChatId;
|
|
172
|
+
const failed = await createChatTask(failedChat);
|
|
173
|
+
await failTask(failed.id, "the build broke");
|
|
174
|
+
const cancelledChat = nextChatId + 1_000;
|
|
175
|
+
const cancelled = await createTaskExtended("cancel me", {
|
|
176
|
+
agentId: lead.id,
|
|
177
|
+
source: "chat",
|
|
178
|
+
contextKey: chatSdkContextKey(`telegram:${cancelledChat}`),
|
|
179
|
+
});
|
|
180
|
+
await cancelTask(cancelled.id, "user changed their mind");
|
|
181
|
+
|
|
182
|
+
await processPendingChatRelays();
|
|
183
|
+
expect(sentText(sendsTo(failedChat)[0] ?? {})).toContain("Task failed.");
|
|
184
|
+
expect(sentText(sendsTo(failedChat)[0] ?? {})).toContain("the build broke");
|
|
185
|
+
expect(sentText(sendsTo(cancelledChat)[0] ?? {})).toContain("Task cancelled.");
|
|
186
|
+
});
|
|
187
|
+
|
|
188
|
+
test("a platform error leaves the row pending with backoff, then a later tick delivers", async () => {
|
|
189
|
+
const chatId = nextChatId;
|
|
190
|
+
const task = await createChatTask(chatId);
|
|
191
|
+
await completeTask(task.id, "retry me");
|
|
192
|
+
harness.mock.failSends(100);
|
|
193
|
+
|
|
194
|
+
const now = Date.now();
|
|
195
|
+
await processPendingChatRelays(now);
|
|
196
|
+
let row = await obligation(task.id);
|
|
197
|
+
expect(row?.delivered_at).toBeNull();
|
|
198
|
+
expect(row?.attempt_count).toBe(1);
|
|
199
|
+
expect(row?.last_error).toBeTruthy();
|
|
200
|
+
const failedCalls = sendsTo(chatId).length;
|
|
201
|
+
expect(failedCalls).toBeGreaterThanOrEqual(1);
|
|
202
|
+
|
|
203
|
+
// 1 s later the row is still in backoff (3 s * 2^1 = 6 s): no call, no attempt.
|
|
204
|
+
harness.mock.failSends(0);
|
|
205
|
+
await processPendingChatRelays(now + 1_000);
|
|
206
|
+
row = await obligation(task.id);
|
|
207
|
+
expect(row?.attempt_count).toBe(1);
|
|
208
|
+
expect(sendsTo(chatId)).toHaveLength(failedCalls);
|
|
209
|
+
|
|
210
|
+
await processPendingChatRelays(now + 7_000);
|
|
211
|
+
row = await obligation(task.id);
|
|
212
|
+
expect(row?.delivered_at).not.toBeNull();
|
|
213
|
+
expect(row?.attempt_count).toBe(2);
|
|
214
|
+
expect(sentText(sendsTo(chatId).at(-1) ?? {})).toContain("retry me");
|
|
215
|
+
});
|
|
216
|
+
|
|
217
|
+
test("lead delegation: the chat task and its follow-up both reach the thread", async () => {
|
|
218
|
+
const chatId = nextChatId;
|
|
219
|
+
const leadTask = await createChatTask(chatId);
|
|
220
|
+
await completeTask(leadTask.id, "Delegated to a worker.");
|
|
221
|
+
// No explicit contextKey: the follow-up inherits it from its parent.
|
|
222
|
+
const followUp = await createTaskExtended("worker finished", {
|
|
223
|
+
agentId: lead.id,
|
|
224
|
+
source: "system",
|
|
225
|
+
taskType: "follow-up",
|
|
226
|
+
parentTaskId: leadTask.id,
|
|
227
|
+
});
|
|
228
|
+
expect(followUp.contextKey).toBe(leadTask.contextKey);
|
|
229
|
+
await completeTask(followUp.id, "Worker result: all green.");
|
|
230
|
+
// A system task that is not a follow-up stays silent.
|
|
231
|
+
const other = await createChatTask(chatId, { source: "system", taskType: "maintenance" });
|
|
232
|
+
await completeTask(other.id, "internal");
|
|
233
|
+
|
|
234
|
+
await processPendingChatRelays();
|
|
235
|
+
const texts = sendsTo(chatId).map(sentText);
|
|
236
|
+
expect(texts).toHaveLength(2);
|
|
237
|
+
expect(texts.some((text) => text.includes("Delegated to a worker."))).toBe(true);
|
|
238
|
+
expect(texts.some((text) => text.includes("Worker result: all green."))).toBe(true);
|
|
239
|
+
expect(await obligation(other.id)).toBeNull();
|
|
240
|
+
});
|
|
241
|
+
|
|
242
|
+
test("a 9000-character output arrives as three Telegram messages", async () => {
|
|
243
|
+
const chatId = nextChatId;
|
|
244
|
+
const task = await createChatTask(chatId);
|
|
245
|
+
const output = Array.from({ length: 1000 }, (_, i) => `line${String(i).padStart(4, "0")}`).join(
|
|
246
|
+
"\n",
|
|
247
|
+
);
|
|
248
|
+
expect(output.length).toBeGreaterThan(8_500);
|
|
249
|
+
expect(output.length).toBeLessThan(9_500);
|
|
250
|
+
await completeTask(task.id, output);
|
|
251
|
+
await processPendingChatRelays();
|
|
252
|
+
const sends = sendsTo(chatId);
|
|
253
|
+
expect(sends).toHaveLength(3);
|
|
254
|
+
for (const body of sends) expect(sentText(body).length).toBeLessThanOrEqual(4096);
|
|
255
|
+
expect((await obligation(task.id))?.delivered_at).not.toBeNull();
|
|
256
|
+
});
|
|
257
|
+
|
|
258
|
+
test("post.chat.message outbound reaches an extension", async () => {
|
|
259
|
+
const bundle = await loadBundleFixture("minimal");
|
|
260
|
+
bundle.manifest = { ...bundle.manifest, name: "chat-relay-capture" };
|
|
261
|
+
const installed = await installExtension({ ...bundle, priority: 100 });
|
|
262
|
+
const record = await setExtensionState(installed.extension.id, {
|
|
263
|
+
agentId: await ensureExtensionAgent(installed.extension.name),
|
|
264
|
+
});
|
|
265
|
+
const loaded = await loadExtension({ record: record!, ...bundle });
|
|
266
|
+
loaded.handlers = [
|
|
267
|
+
{
|
|
268
|
+
event: "post.chat.message",
|
|
269
|
+
priority: 100,
|
|
270
|
+
handler: (payload: never) => {
|
|
271
|
+
received.push({ event: "post.chat.message", payload });
|
|
272
|
+
},
|
|
273
|
+
},
|
|
274
|
+
];
|
|
275
|
+
registerLoaded(loaded);
|
|
276
|
+
initExtensionPostBridge();
|
|
277
|
+
|
|
278
|
+
const chatId = nextChatId;
|
|
279
|
+
const task = await createChatTask(chatId);
|
|
280
|
+
await completeTask(task.id, "observed by an extension");
|
|
281
|
+
await processPendingChatRelays();
|
|
282
|
+
|
|
283
|
+
const deadline = Date.now() + 5_000;
|
|
284
|
+
while (!received.some((r) => r.payload.taskId === task.id) && Date.now() < deadline) {
|
|
285
|
+
await Bun.sleep(10);
|
|
286
|
+
}
|
|
287
|
+
const event = received.find((r) => r.payload.taskId === task.id);
|
|
288
|
+
expect(event?.payload).toMatchObject({
|
|
289
|
+
adapter: "telegram",
|
|
290
|
+
threadId: `telegram:${chatId}`,
|
|
291
|
+
direction: "outbound",
|
|
292
|
+
text: "observed by an extension",
|
|
293
|
+
taskId: task.id,
|
|
294
|
+
});
|
|
295
|
+
});
|
|
296
|
+
|
|
297
|
+
test("an adapter that is not running skips the row without counting an attempt", async () => {
|
|
298
|
+
await harness.stop();
|
|
299
|
+
const chatId = nextChatId;
|
|
300
|
+
const task = await createChatTask(chatId);
|
|
301
|
+
await completeTask(task.id, "after the restart");
|
|
302
|
+
await processPendingChatRelays();
|
|
303
|
+
let row = await obligation(task.id);
|
|
304
|
+
expect(row?.attempt_count).toBe(0);
|
|
305
|
+
expect(row?.given_up_at).toBeNull();
|
|
306
|
+
|
|
307
|
+
await startHarness();
|
|
308
|
+
// The watcher's first tick on boot may already have delivered it.
|
|
309
|
+
await processPendingChatRelays();
|
|
310
|
+
row = await obligation(task.id);
|
|
311
|
+
expect(row?.delivered_at).not.toBeNull();
|
|
312
|
+
expect(row?.attempt_count).toBe(1);
|
|
313
|
+
expect(sendsTo(chatId)).toHaveLength(1);
|
|
314
|
+
});
|
|
315
|
+
|
|
316
|
+
test("a healthy adapter is delivered even when stopped-adapter rows pile up", async () => {
|
|
317
|
+
// Discord is never started in this harness, so its rows stay undelivered.
|
|
318
|
+
for (let i = 0; i < 3; i++) {
|
|
319
|
+
const stuck = await createTaskExtended(`stuck ${i}`, {
|
|
320
|
+
agentId: lead.id,
|
|
321
|
+
source: "chat",
|
|
322
|
+
contextKey: chatSdkContextKey(`discord:${nextChatId}${i}`),
|
|
323
|
+
});
|
|
324
|
+
await completeTask(stuck.id, "stuck output");
|
|
325
|
+
}
|
|
326
|
+
const chatId = nextChatId;
|
|
327
|
+
const task = await createChatTask(chatId);
|
|
328
|
+
await completeTask(task.id, "healthy output");
|
|
329
|
+
|
|
330
|
+
await processPendingChatRelays();
|
|
331
|
+
expect((await obligation(task.id))?.delivered_at).not.toBeNull();
|
|
332
|
+
expect(sendsTo(chatId)).toHaveLength(1);
|
|
333
|
+
const stuckRows = await getDbClient().query<ObligationRow>(
|
|
334
|
+
`SELECT o.* FROM chat_relay_obligations o JOIN agent_tasks t ON t.id = o.task_id
|
|
335
|
+
WHERE t.contextKey LIKE 'task:chat-sdk:discord:%' AND o.delivered_at IS NULL`,
|
|
336
|
+
);
|
|
337
|
+
expect(stuckRows.length).toBeGreaterThanOrEqual(3);
|
|
338
|
+
expect(stuckRows.every((row) => row.attempt_count === 0)).toBe(true);
|
|
339
|
+
});
|
|
340
|
+
|
|
341
|
+
test("two concurrent ticks deliver one obligation once", async () => {
|
|
342
|
+
const chatId = nextChatId;
|
|
343
|
+
const task = await createChatTask(chatId);
|
|
344
|
+
await completeTask(task.id, "only once");
|
|
345
|
+
const now = Date.now();
|
|
346
|
+
await Promise.all([processPendingChatRelays(now), processPendingChatRelays(now)]);
|
|
347
|
+
expect(sendsTo(chatId)).toHaveLength(1);
|
|
348
|
+
const row = await obligation(task.id);
|
|
349
|
+
expect(row?.delivered_at).not.toBeNull();
|
|
350
|
+
expect(row?.attempt_count).toBe(1);
|
|
351
|
+
});
|
|
352
|
+
|
|
353
|
+
test("backoff is capped at 10 minutes", async () => {
|
|
354
|
+
const chatId = nextChatId;
|
|
355
|
+
const task = await createChatTask(chatId);
|
|
356
|
+
await completeTask(task.id, "capped backoff");
|
|
357
|
+
const base = Date.now();
|
|
358
|
+
await getDbClient().run(
|
|
359
|
+
"UPDATE chat_relay_obligations SET attempt_count = 9, last_attempt_at = ? WHERE task_id = ?",
|
|
360
|
+
[new Date(base).toISOString(), task.id],
|
|
361
|
+
);
|
|
362
|
+
|
|
363
|
+
// 3 s * 2^9 would be 25 min; the cap makes the row eligible after 10 min.
|
|
364
|
+
await processPendingChatRelays(base + 599_000);
|
|
365
|
+
expect(sendsTo(chatId)).toHaveLength(0);
|
|
366
|
+
expect((await obligation(task.id))?.attempt_count).toBe(9);
|
|
367
|
+
|
|
368
|
+
await processPendingChatRelays(base + 601_000);
|
|
369
|
+
expect(sendsTo(chatId)).toHaveLength(1);
|
|
370
|
+
expect((await obligation(task.id))?.delivered_at).not.toBeNull();
|
|
371
|
+
});
|
|
372
|
+
|
|
373
|
+
test("a resumed lead chat task relays its final answer", async () => {
|
|
374
|
+
// A fresh lead with spare capacity, so the graceful-shutdown resume pins to it.
|
|
375
|
+
const resumeLead = await createAgent({
|
|
376
|
+
id: crypto.randomUUID(),
|
|
377
|
+
name: "ChatRelayResumeLead",
|
|
378
|
+
isLead: true,
|
|
379
|
+
status: "idle",
|
|
380
|
+
maxTasks: 5,
|
|
381
|
+
});
|
|
382
|
+
const chatId = nextChatId;
|
|
383
|
+
const parent = await createTaskExtended("chat task to resume", {
|
|
384
|
+
agentId: resumeLead.id,
|
|
385
|
+
source: "chat",
|
|
386
|
+
contextKey: chatSdkContextKey(`telegram:${chatId}`),
|
|
387
|
+
});
|
|
388
|
+
await startTask(parent.id);
|
|
389
|
+
await supersedeTask(parent.id, { reason: "graceful_shutdown", resumeTaskId: null });
|
|
390
|
+
const result = await createResumeFollowUp({ parentId: parent.id, reason: "graceful_shutdown" });
|
|
391
|
+
expect(result.kind).toBe("created");
|
|
392
|
+
if (result.kind !== "created") return;
|
|
393
|
+
const resume = result.task;
|
|
394
|
+
expect(resume.source).toBe("system");
|
|
395
|
+
expect(resume.taskType).toBe("resume");
|
|
396
|
+
expect(resume.agentId).toBe(resumeLead.id);
|
|
397
|
+
expect(resume.contextKey).toBe(parent.contextKey);
|
|
398
|
+
|
|
399
|
+
await completeTask(resume.id, "Resumed answer: 7.");
|
|
400
|
+
// The superseded parent never relays; the resume carries the answer.
|
|
401
|
+
expect(await obligation(parent.id)).toBeNull();
|
|
402
|
+
expect(await obligation(resume.id)).not.toBeNull();
|
|
403
|
+
|
|
404
|
+
await processPendingChatRelays();
|
|
405
|
+
const texts = sendsTo(chatId).map(sentText);
|
|
406
|
+
expect(texts).toHaveLength(1);
|
|
407
|
+
expect(texts[0]).toContain("Resumed answer: 7.");
|
|
408
|
+
expect((await obligation(resume.id))?.delivered_at).not.toBeNull();
|
|
409
|
+
});
|
|
410
|
+
|
|
411
|
+
test("delegated worker tasks and their resumes stay silent", async () => {
|
|
412
|
+
const worker = await createAgent({
|
|
413
|
+
id: crypto.randomUUID(),
|
|
414
|
+
name: "ChatRelayWorker",
|
|
415
|
+
isLead: false,
|
|
416
|
+
status: "idle",
|
|
417
|
+
maxTasks: 5,
|
|
418
|
+
});
|
|
419
|
+
const chatId = nextChatId;
|
|
420
|
+
const leadTask = await createChatTask(chatId);
|
|
421
|
+
// Inherits the chat contextKey from its parent.
|
|
422
|
+
const delegated = await createTaskExtended("worker part", {
|
|
423
|
+
agentId: worker.id,
|
|
424
|
+
source: "system",
|
|
425
|
+
taskType: "delegated",
|
|
426
|
+
parentTaskId: leadTask.id,
|
|
427
|
+
});
|
|
428
|
+
expect(delegated.contextKey).toBe(leadTask.contextKey);
|
|
429
|
+
await completeTask(delegated.id, "raw worker output");
|
|
430
|
+
|
|
431
|
+
// A worker resume reports back through a lead follow-up, not on its own.
|
|
432
|
+
const interrupted = await createTaskExtended("worker part 2", {
|
|
433
|
+
agentId: worker.id,
|
|
434
|
+
source: "system",
|
|
435
|
+
taskType: "delegated",
|
|
436
|
+
parentTaskId: leadTask.id,
|
|
437
|
+
});
|
|
438
|
+
await startTask(interrupted.id);
|
|
439
|
+
await supersedeTask(interrupted.id, { reason: "graceful_shutdown", resumeTaskId: null });
|
|
440
|
+
const result = await createResumeFollowUp({
|
|
441
|
+
parentId: interrupted.id,
|
|
442
|
+
reason: "graceful_shutdown",
|
|
443
|
+
});
|
|
444
|
+
expect(result.kind).toBe("created");
|
|
445
|
+
if (result.kind !== "created") return;
|
|
446
|
+
expect(result.task.agentId).toBe(worker.id);
|
|
447
|
+
expect(result.task.contextKey).toBe(leadTask.contextKey);
|
|
448
|
+
await completeTask(result.task.id, "raw resumed worker output");
|
|
449
|
+
|
|
450
|
+
expect(await obligation(delegated.id)).toBeNull();
|
|
451
|
+
expect(await obligation(interrupted.id)).toBeNull();
|
|
452
|
+
expect(await obligation(result.task.id)).toBeNull();
|
|
453
|
+
await processPendingChatRelays();
|
|
454
|
+
expect(sendsTo(chatId)).toHaveLength(0);
|
|
455
|
+
});
|
|
456
|
+
|
|
457
|
+
// Last on purpose: the give-up sweep retires every pending row older than 24h
|
|
458
|
+
// at the injected clock, so it must not run before other cases create rows.
|
|
459
|
+
test("a row older than RELAY_GIVE_UP_AFTER_MS is given up without a send", async () => {
|
|
460
|
+
const chatId = nextChatId;
|
|
461
|
+
const task = await createChatTask(chatId);
|
|
462
|
+
await completeTask(task.id, "too late");
|
|
463
|
+
await processPendingChatRelays(Date.now() + RELAY_GIVE_UP_AFTER_MS + 60 * 60 * 1000);
|
|
464
|
+
const row = await obligation(task.id);
|
|
465
|
+
expect(row?.given_up_at).not.toBeNull();
|
|
466
|
+
expect(row?.delivered_at).toBeNull();
|
|
467
|
+
expect(row?.last_error).toBe("gave up: older than 24h");
|
|
468
|
+
expect(sendsTo(chatId)).toHaveLength(0);
|
|
469
|
+
});
|
|
470
|
+
});
|
|
@@ -0,0 +1,128 @@
|
|
|
1
|
+
import { afterAll, beforeAll, describe, expect, test } from "bun:test";
|
|
2
|
+
import { unlink } from "node:fs/promises";
|
|
3
|
+
import {
|
|
4
|
+
startTelegramBotApiMock,
|
|
5
|
+
type TelegramBotApiMock,
|
|
6
|
+
} from "../../scripts/dev/telegram-bot-api-mock";
|
|
7
|
+
import { closeDb, deleteSwarmConfig, initDb, upsertSwarmConfig } from "../be/db";
|
|
8
|
+
import { __resetInjectedEnvTracking, reloadGlobalConfigsAndIntegrations } from "../http/core";
|
|
9
|
+
import { getChatInstance, stopChatSdk } from "../integrations/chat-sdk/runtime";
|
|
10
|
+
import { snapshotChatSdkEnv, TELEGRAM_SECRET } from "./chat-sdk-harness";
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* A dashboard save writes swarm_config rows and triggers the integrations
|
|
14
|
+
* reload. The reload restarts the Chat SDK adapters only when their resolved
|
|
15
|
+
* config changed.
|
|
16
|
+
*/
|
|
17
|
+
|
|
18
|
+
const TEST_DB_PATH = "./test-chat-sdk-reload.sqlite";
|
|
19
|
+
const DISABLE_KEYS = [
|
|
20
|
+
"AGENTMAIL_DISABLE",
|
|
21
|
+
"GITHUB_DISABLE",
|
|
22
|
+
"JIRA_DISABLE",
|
|
23
|
+
"LINEAR_DISABLE",
|
|
24
|
+
"SLACK_DISABLE",
|
|
25
|
+
];
|
|
26
|
+
const restoreChatEnv = snapshotChatSdkEnv();
|
|
27
|
+
const savedDisable = new Map(DISABLE_KEYS.map((key) => [key, process.env[key]]));
|
|
28
|
+
let mock: TelegramBotApiMock;
|
|
29
|
+
const rowIds: string[] = [];
|
|
30
|
+
|
|
31
|
+
async function removeDbFiles(): Promise<void> {
|
|
32
|
+
for (const suffix of ["", "-wal", "-shm"]) {
|
|
33
|
+
await unlink(`${TEST_DB_PATH}${suffix}`).catch(() => {});
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
function getMeCalls(): number {
|
|
38
|
+
return mock.calls.filter((call) => call.method === "getMe").length;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
beforeAll(async () => {
|
|
42
|
+
await removeDbFiles();
|
|
43
|
+
initDb(TEST_DB_PATH);
|
|
44
|
+
__resetInjectedEnvTracking();
|
|
45
|
+
for (const key of DISABLE_KEYS) process.env[key] = "true";
|
|
46
|
+
delete process.env.CHAT_SDK_ENABLED;
|
|
47
|
+
delete process.env.CHAT_SDK_ADAPTERS;
|
|
48
|
+
mock = startTelegramBotApiMock();
|
|
49
|
+
process.env.CHAT_SDK_API_URL_OVERRIDES = JSON.stringify({ telegram: mock.url });
|
|
50
|
+
});
|
|
51
|
+
|
|
52
|
+
afterAll(async () => {
|
|
53
|
+
await stopChatSdk();
|
|
54
|
+
mock.stop();
|
|
55
|
+
closeDb();
|
|
56
|
+
await removeDbFiles();
|
|
57
|
+
__resetInjectedEnvTracking();
|
|
58
|
+
restoreChatEnv();
|
|
59
|
+
for (const [key, value] of savedDisable) {
|
|
60
|
+
if (value === undefined) delete process.env[key];
|
|
61
|
+
else process.env[key] = value;
|
|
62
|
+
}
|
|
63
|
+
});
|
|
64
|
+
|
|
65
|
+
describe("Chat SDK restart on config reload", () => {
|
|
66
|
+
test("saving the config rows starts the Telegram adapter without a restart", async () => {
|
|
67
|
+
expect(getChatInstance("telegram")).toBeUndefined();
|
|
68
|
+
for (const [key, value, isSecret] of [
|
|
69
|
+
["CHAT_SDK_TELEGRAM_BOT_TOKEN", "token-one", true],
|
|
70
|
+
["CHAT_SDK_ENABLED", "true", false],
|
|
71
|
+
[
|
|
72
|
+
"CHAT_SDK_ADAPTERS",
|
|
73
|
+
JSON.stringify({
|
|
74
|
+
telegram: {
|
|
75
|
+
botToken: { $config: "CHAT_SDK_TELEGRAM_BOT_TOKEN" },
|
|
76
|
+
secretToken: TELEGRAM_SECRET,
|
|
77
|
+
mode: "webhook",
|
|
78
|
+
},
|
|
79
|
+
}),
|
|
80
|
+
false,
|
|
81
|
+
],
|
|
82
|
+
] as const) {
|
|
83
|
+
rowIds.push((await upsertSwarmConfig({ scope: "global", key, value, isSecret })).id);
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
const result = await reloadGlobalConfigsAndIntegrations();
|
|
87
|
+
|
|
88
|
+
expect(result.integrationsReinitialized).toContain("chat-sdk");
|
|
89
|
+
expect(getChatInstance("telegram")).toBeDefined();
|
|
90
|
+
});
|
|
91
|
+
|
|
92
|
+
test("a reload with no Chat SDK change leaves the running adapter alone", async () => {
|
|
93
|
+
const before = getChatInstance("telegram");
|
|
94
|
+
const getMeBefore = getMeCalls();
|
|
95
|
+
|
|
96
|
+
const result = await reloadGlobalConfigsAndIntegrations();
|
|
97
|
+
|
|
98
|
+
expect(result.integrationsReinitialized).not.toContain("chat-sdk");
|
|
99
|
+
expect(getChatInstance("telegram")).toBe(before);
|
|
100
|
+
expect(getMeCalls()).toBe(getMeBefore);
|
|
101
|
+
});
|
|
102
|
+
|
|
103
|
+
test("rotating a $config secret restarts the adapter", async () => {
|
|
104
|
+
const before = getChatInstance("telegram");
|
|
105
|
+
await upsertSwarmConfig({
|
|
106
|
+
scope: "global",
|
|
107
|
+
key: "CHAT_SDK_TELEGRAM_BOT_TOKEN",
|
|
108
|
+
value: "token-two",
|
|
109
|
+
isSecret: true,
|
|
110
|
+
});
|
|
111
|
+
|
|
112
|
+
const result = await reloadGlobalConfigsAndIntegrations();
|
|
113
|
+
|
|
114
|
+
expect(result.integrationsReinitialized).toContain("chat-sdk");
|
|
115
|
+
const after = getChatInstance("telegram");
|
|
116
|
+
expect(after).toBeDefined();
|
|
117
|
+
expect(after).not.toBe(before);
|
|
118
|
+
});
|
|
119
|
+
|
|
120
|
+
test("deleting the config rows stops the adapter", async () => {
|
|
121
|
+
for (const id of rowIds) await deleteSwarmConfig(id);
|
|
122
|
+
|
|
123
|
+
const result = await reloadGlobalConfigsAndIntegrations();
|
|
124
|
+
|
|
125
|
+
expect(result.integrationsReinitialized).not.toContain("chat-sdk");
|
|
126
|
+
expect(getChatInstance("telegram")).toBeUndefined();
|
|
127
|
+
});
|
|
128
|
+
});
|