@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,489 @@
|
|
|
1
|
+
import { type ApiCtx, block, type SwarmExtension } from "swarm-extension";
|
|
2
|
+
import { z } from "zod";
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* comms-adherence-gate
|
|
6
|
+
*
|
|
7
|
+
* When an agent calls `store-progress` with status "completed" on a task that
|
|
8
|
+
* reaches a human in Slack, judge the `output` against the requesting user's
|
|
9
|
+
* stored comms preferences (users.metadata.$.comms) with Jev, and against what
|
|
10
|
+
* that user actually asked for in their latest message. An explicit request
|
|
11
|
+
* ("tell me a long joke", "explain in detail") outranks the stored default, so
|
|
12
|
+
* a requested long answer is not a verbosity failure.
|
|
13
|
+
*
|
|
14
|
+
* Transport (config.provider): Jev is served by TypeSafe directly and by
|
|
15
|
+
* OpenRouter's Decisions endpoint. Both take the same {model, state, questions}
|
|
16
|
+
* body and return the same `answers` shape. `auto` uses TYPESAFE_API_KEY when
|
|
17
|
+
* set, else OPENROUTER_API_KEY.
|
|
18
|
+
*
|
|
19
|
+
* modes (config.mode):
|
|
20
|
+
* off -> never evaluate
|
|
21
|
+
* shadow -> evaluate + record, never block (default)
|
|
22
|
+
* enforce -> record, and block the completion up to maxBlocksPerTask times
|
|
23
|
+
* per task with actionable feedback
|
|
24
|
+
*
|
|
25
|
+
* Fails OPEN on every error: a judge outage must never wall the swarm.
|
|
26
|
+
* Every verdict lands in KV namespace `ext:comms-gate`.
|
|
27
|
+
*/
|
|
28
|
+
|
|
29
|
+
export const config = z.object({
|
|
30
|
+
mode: z.enum(["off", "shadow", "enforce"]).default("shadow"),
|
|
31
|
+
/** A dimension scoring below this fails. */
|
|
32
|
+
threshold: z.number().min(0).max(1).default(0.5),
|
|
33
|
+
timeoutMs: z.number().int().positive().default(4000),
|
|
34
|
+
maxBlocksPerTask: z.number().int().min(0).default(1),
|
|
35
|
+
provider: z.enum(["auto", "typesafe", "openrouter"]).default("auto"),
|
|
36
|
+
/** Jev model on the TypeSafe transport. */
|
|
37
|
+
model: z.string().default("jev-latest"),
|
|
38
|
+
/** Jev model on the OpenRouter transport. */
|
|
39
|
+
openrouterModel: z.string().default("typesafe/jev-1.13"),
|
|
40
|
+
/** Explicit TypeSafe key; otherwise TYPESAFE_API_KEY from the API process env. */
|
|
41
|
+
apiKey: z.string().optional(),
|
|
42
|
+
});
|
|
43
|
+
|
|
44
|
+
type Cfg = z.infer<typeof config>;
|
|
45
|
+
type Swarm = ApiCtx["swarm"];
|
|
46
|
+
|
|
47
|
+
const NS = "ext:comms-gate";
|
|
48
|
+
const TYPESAFE_ENDPOINT = "https://api.typesafe.ai/v1/systemone";
|
|
49
|
+
const OPENROUTER_ENDPOINT = "https://openrouter.ai/api/alpha/decisions";
|
|
50
|
+
const UUID_RE = /^[0-9a-fA-F-]{36}$/;
|
|
51
|
+
// The dispatcher cancels a handler after 5 s; leave room for the DB read and KV writes.
|
|
52
|
+
const MAX_TIMEOUT_MS = 4000;
|
|
53
|
+
|
|
54
|
+
export const cfgOf = (raw: unknown): Cfg => {
|
|
55
|
+
const parsed = config.safeParse(raw ?? {});
|
|
56
|
+
return parsed.success ? parsed.data : config.parse({});
|
|
57
|
+
};
|
|
58
|
+
|
|
59
|
+
// kv keys accept [a-zA-Z0-9._:/%-]
|
|
60
|
+
const safeKey = (raw: string | undefined | null): string =>
|
|
61
|
+
(raw && raw.length > 0 ? raw : "unknown").replace(/[^a-zA-Z0-9._-]/g, "_").slice(0, 120);
|
|
62
|
+
|
|
63
|
+
/** SDK envelopes: {success, data:{...}} or the bare payload. */
|
|
64
|
+
type Envelope = {
|
|
65
|
+
data?: { rows?: unknown[]; value?: unknown };
|
|
66
|
+
rows?: unknown[];
|
|
67
|
+
value?: unknown;
|
|
68
|
+
} | null;
|
|
69
|
+
|
|
70
|
+
const cell = (row: unknown, i: number, key: string): unknown =>
|
|
71
|
+
Array.isArray(row) ? row[i] : (row as Record<string, unknown>)?.[key];
|
|
72
|
+
|
|
73
|
+
/**
|
|
74
|
+
* The newest human message sits at the END of a Slack task: the
|
|
75
|
+
* `<thread_context>` blob comes first and can run to thousands of chars, so a
|
|
76
|
+
* leading slice of `agent_tasks.task` shows the judge everything except the
|
|
77
|
+
* actual request. Take the tail, then the part after the last `</thread_context>`.
|
|
78
|
+
*/
|
|
79
|
+
const CTX_END = "</thread_context>";
|
|
80
|
+
export const latestAsk = (tail: string): string => {
|
|
81
|
+
const end = tail.lastIndexOf(CTX_END);
|
|
82
|
+
let out = (end >= 0 ? tail.slice(end + CTX_END.length) : tail)
|
|
83
|
+
.replace(/\[Thread follow-up[^\]]*\]/g, "")
|
|
84
|
+
.trim();
|
|
85
|
+
// Some tasks end with the context block; fall back to the last messages in it.
|
|
86
|
+
if (out.length < 8) out = (end >= 0 ? tail.slice(0, end) : tail).trim().slice(-800);
|
|
87
|
+
return out.slice(0, 900);
|
|
88
|
+
};
|
|
89
|
+
|
|
90
|
+
// Preferences are the DEFAULT. A direct request outranks them.
|
|
91
|
+
const JUDGING_RULES = [
|
|
92
|
+
"The requester's explicit instruction in latest_request OUTRANKS requester_preferences. Preferences describe the DEFAULT; they never override a direct request.",
|
|
93
|
+
"If latest_request asks for something long, detailed, creative, a story, a joke, a full explanation or a document, then length and structure serving that request are CORRECT and must NOT be penalized.",
|
|
94
|
+
"Judge whether agent_message serves what latest_request actually asked for, written in the requester's manner. Do not judge it against the default preference when the request overrides it.",
|
|
95
|
+
];
|
|
96
|
+
|
|
97
|
+
type Prefs = { verbosity?: unknown; tone?: unknown; language?: unknown } & Record<string, unknown>;
|
|
98
|
+
|
|
99
|
+
/** One `noul` question per stated preference the gate knows how to judge. */
|
|
100
|
+
export const buildQuestions = (prefs: Prefs): Record<string, unknown> => {
|
|
101
|
+
const questions: Record<string, unknown> = {};
|
|
102
|
+
if (prefs.verbosity) {
|
|
103
|
+
questions.verbosity = {
|
|
104
|
+
type: "noul",
|
|
105
|
+
instructions:
|
|
106
|
+
"Does agent_message respect the requester's verbosity preference, AS OVERRIDDEN by any explicit instruction in latest_request? Apply judging_rules.",
|
|
107
|
+
criteria: {
|
|
108
|
+
true: "Length and structure fit what latest_request asked for (or the stated preference when the request does not override it), and it leads with the result.",
|
|
109
|
+
false:
|
|
110
|
+
"Too long for what was asked, or it opens with preamble, recaps the request, narrates the process, or ends with an offer to elaborate. Length explicitly requested in latest_request is NOT a failure.",
|
|
111
|
+
},
|
|
112
|
+
};
|
|
113
|
+
}
|
|
114
|
+
if (prefs.tone) {
|
|
115
|
+
questions.tone = {
|
|
116
|
+
type: "noul",
|
|
117
|
+
instructions:
|
|
118
|
+
"Does agent_message match the requester's stated tone preference, as overridden by any explicit instruction in latest_request? Apply judging_rules.",
|
|
119
|
+
criteria: {
|
|
120
|
+
true: "The tone matches what the requester asked for.",
|
|
121
|
+
false:
|
|
122
|
+
"The tone conflicts with it (for example hedging, flattery or filler when the requester asked for direct and plain). A voice appropriate to an explicitly requested creative or narrative form is NOT a failure.",
|
|
123
|
+
},
|
|
124
|
+
};
|
|
125
|
+
}
|
|
126
|
+
if (prefs.language) {
|
|
127
|
+
questions.language = {
|
|
128
|
+
type: "noul",
|
|
129
|
+
instructions: "Is agent_message written in the requester's stated language?",
|
|
130
|
+
criteria: {
|
|
131
|
+
true: "Written in the stated language.",
|
|
132
|
+
false: "Written in a different language.",
|
|
133
|
+
},
|
|
134
|
+
};
|
|
135
|
+
}
|
|
136
|
+
return questions;
|
|
137
|
+
};
|
|
138
|
+
|
|
139
|
+
const secretCache = new Map<string, { value: string; at: number }>();
|
|
140
|
+
const KEY_TTL_MS = 5 * 60 * 1000;
|
|
141
|
+
const usable = (v: string): boolean => Boolean(v) && !v.startsWith("[REDACTED");
|
|
142
|
+
const envOf = (name: string): string => String(process.env[name] ?? "");
|
|
143
|
+
|
|
144
|
+
/**
|
|
145
|
+
* Resolve a secret: explicit value, then the API process env. The API process loads
|
|
146
|
+
* global swarm_config rows into process.env at boot and on config reload.
|
|
147
|
+
* ctx.swarm.config_get cannot serve it: the SDK scrubs secrets to "[REDACTED:<name>]".
|
|
148
|
+
*/
|
|
149
|
+
const secret = (name: string, explicit?: string): string => {
|
|
150
|
+
const cached = secretCache.get(name);
|
|
151
|
+
if (cached && Date.now() - cached.at < KEY_TTL_MS) return cached.value;
|
|
152
|
+
const value = [String(explicit ?? ""), envOf(name)].find(usable) ?? "";
|
|
153
|
+
if (usable(value)) secretCache.set(name, { value, at: Date.now() });
|
|
154
|
+
return value;
|
|
155
|
+
};
|
|
156
|
+
|
|
157
|
+
/** Test hook: forget cached secrets. */
|
|
158
|
+
export const resetKeyCache = (): void => {
|
|
159
|
+
secretCache.clear();
|
|
160
|
+
};
|
|
161
|
+
|
|
162
|
+
export type JevTransport = {
|
|
163
|
+
provider: "typesafe" | "openrouter";
|
|
164
|
+
endpoint: string;
|
|
165
|
+
key: string;
|
|
166
|
+
model: string;
|
|
167
|
+
};
|
|
168
|
+
|
|
169
|
+
/**
|
|
170
|
+
* Pick the Jev transport. `auto` prefers TypeSafe and falls back to OpenRouter.
|
|
171
|
+
* Throws when the chosen provider has no usable key; the handler fails open.
|
|
172
|
+
*/
|
|
173
|
+
export const resolveTransport = (cfg: Cfg): JevTransport => {
|
|
174
|
+
const typesafe = (): JevTransport | null => {
|
|
175
|
+
const key = secret("TYPESAFE_API_KEY", cfg.apiKey);
|
|
176
|
+
return usable(key)
|
|
177
|
+
? { provider: "typesafe", endpoint: TYPESAFE_ENDPOINT, key, model: cfg.model }
|
|
178
|
+
: null;
|
|
179
|
+
};
|
|
180
|
+
const openrouter = (): JevTransport | null => {
|
|
181
|
+
const key = secret("OPENROUTER_API_KEY");
|
|
182
|
+
return usable(key)
|
|
183
|
+
? { provider: "openrouter", endpoint: OPENROUTER_ENDPOINT, key, model: cfg.openrouterModel }
|
|
184
|
+
: null;
|
|
185
|
+
};
|
|
186
|
+
const transport =
|
|
187
|
+
cfg.provider === "typesafe"
|
|
188
|
+
? typesafe()
|
|
189
|
+
: cfg.provider === "openrouter"
|
|
190
|
+
? openrouter()
|
|
191
|
+
: (typesafe() ?? openrouter());
|
|
192
|
+
if (transport) return transport;
|
|
193
|
+
throw new Error(
|
|
194
|
+
cfg.provider === "typesafe"
|
|
195
|
+
? "no TYPESAFE_API_KEY"
|
|
196
|
+
: cfg.provider === "openrouter"
|
|
197
|
+
? "no OPENROUTER_API_KEY"
|
|
198
|
+
: "no TYPESAFE_API_KEY or OPENROUTER_API_KEY",
|
|
199
|
+
);
|
|
200
|
+
};
|
|
201
|
+
|
|
202
|
+
/** One Jev call; returns the `noul` score per question. Throws on any failure. */
|
|
203
|
+
export const askJev = async (
|
|
204
|
+
state: Record<string, unknown>,
|
|
205
|
+
questions: Record<string, unknown>,
|
|
206
|
+
transport: JevTransport,
|
|
207
|
+
cfg: Cfg,
|
|
208
|
+
outer?: AbortSignal,
|
|
209
|
+
): Promise<{ scores: Record<string, number>; latencyMs: number }> => {
|
|
210
|
+
const controller = new AbortController();
|
|
211
|
+
const timer = setTimeout(() => controller.abort(), Math.min(cfg.timeoutMs, MAX_TIMEOUT_MS));
|
|
212
|
+
const onOuterAbort = () => controller.abort();
|
|
213
|
+
outer?.addEventListener("abort", onOuterAbort);
|
|
214
|
+
const t0 = Date.now();
|
|
215
|
+
try {
|
|
216
|
+
const res = await fetch(transport.endpoint, {
|
|
217
|
+
method: "POST",
|
|
218
|
+
headers: { "Content-Type": "application/json", Authorization: `Bearer ${transport.key}` },
|
|
219
|
+
body: JSON.stringify({ state, model: transport.model, questions }),
|
|
220
|
+
signal: controller.signal,
|
|
221
|
+
});
|
|
222
|
+
if (!res.ok) throw new Error(`${transport.provider} ${res.status}`);
|
|
223
|
+
const body = (await res.json()) as { answers?: Record<string, { noul?: unknown }> } | null;
|
|
224
|
+
const answers = body?.answers;
|
|
225
|
+
if (!answers || typeof answers !== "object") throw new Error("no answers in response");
|
|
226
|
+
const scores: Record<string, number> = {};
|
|
227
|
+
for (const [id, a] of Object.entries(answers)) {
|
|
228
|
+
if (typeof a?.noul === "number") scores[id] = a.noul;
|
|
229
|
+
}
|
|
230
|
+
if (Object.keys(scores).length === 0) throw new Error("no scores in response");
|
|
231
|
+
return { scores, latencyMs: Date.now() - t0 };
|
|
232
|
+
} finally {
|
|
233
|
+
clearTimeout(timer);
|
|
234
|
+
outer?.removeEventListener("abort", onOuterAbort);
|
|
235
|
+
}
|
|
236
|
+
};
|
|
237
|
+
|
|
238
|
+
const SELFTEST = "comms-gate-selftest";
|
|
239
|
+
const SELFTEST_PREFS = {
|
|
240
|
+
tone: "direct, plain, no hedging",
|
|
241
|
+
language: "English",
|
|
242
|
+
verbosity:
|
|
243
|
+
"very terse - answer in the first sentence, ~400 chars max. No preamble, no recap, no closing offer.",
|
|
244
|
+
};
|
|
245
|
+
const SELFTEST_BAD =
|
|
246
|
+
"Great question! Let me walk you through what I did here. First I looked at the task, then I reviewed the relevant files, and after careful consideration I decided to go with option B. I hope this helps - let me know if you would like me to elaborate further on any of the above!";
|
|
247
|
+
const SELFTEST_GOOD =
|
|
248
|
+
"Went with option B: it is the only one that keeps the migration reversible. PR #123.";
|
|
249
|
+
|
|
250
|
+
/**
|
|
251
|
+
* Run the judge on a known-bad (positive control, must score below threshold) and a
|
|
252
|
+
* known-good (negative control, must score at or above it) message and record both
|
|
253
|
+
* under `selftest:last`. Proves key, egress and KV from inside the API runtime
|
|
254
|
+
* without touching real traffic.
|
|
255
|
+
*/
|
|
256
|
+
const selfTest = async (swarm: Swarm, cfg: Cfg, signal?: AbortSignal): Promise<void> => {
|
|
257
|
+
const started = Date.now();
|
|
258
|
+
try {
|
|
259
|
+
const transport = resolveTransport(cfg);
|
|
260
|
+
const questions = { verbosity: buildQuestions(SELFTEST_PREFS).verbosity };
|
|
261
|
+
const judge = (message: string) =>
|
|
262
|
+
askJev(
|
|
263
|
+
{ requester_preferences: SELFTEST_PREFS, agent_message: message },
|
|
264
|
+
questions,
|
|
265
|
+
transport,
|
|
266
|
+
cfg,
|
|
267
|
+
signal,
|
|
268
|
+
).then((r) => r.scores.verbosity ?? null);
|
|
269
|
+
const [bad, good] = await Promise.all([judge(SELFTEST_BAD), judge(SELFTEST_GOOD)]);
|
|
270
|
+
await swarm.kv_set({
|
|
271
|
+
namespace: NS,
|
|
272
|
+
key: "selftest:last",
|
|
273
|
+
value: {
|
|
274
|
+
at: new Date().toISOString(),
|
|
275
|
+
provider: transport.provider,
|
|
276
|
+
threshold: cfg.threshold,
|
|
277
|
+
positiveControlBadScore: bad,
|
|
278
|
+
negativeControlGoodScore: good,
|
|
279
|
+
controlsOk:
|
|
280
|
+
typeof bad === "number" &&
|
|
281
|
+
typeof good === "number" &&
|
|
282
|
+
bad < cfg.threshold &&
|
|
283
|
+
good >= cfg.threshold,
|
|
284
|
+
latencyMs: Date.now() - started,
|
|
285
|
+
},
|
|
286
|
+
});
|
|
287
|
+
} catch (err) {
|
|
288
|
+
await swarm
|
|
289
|
+
.kv_set({
|
|
290
|
+
namespace: NS,
|
|
291
|
+
key: "selftest:last",
|
|
292
|
+
value: {
|
|
293
|
+
at: new Date().toISOString(),
|
|
294
|
+
error: String(err).slice(0, 400),
|
|
295
|
+
latencyMs: Date.now() - started,
|
|
296
|
+
},
|
|
297
|
+
})
|
|
298
|
+
.catch(() => {});
|
|
299
|
+
}
|
|
300
|
+
};
|
|
301
|
+
|
|
302
|
+
const TASK_SQL = `SELECT t.source, t.slackThreadTs, t.agentId, t.requestedByUserId,
|
|
303
|
+
json_extract(u.metadata, '$.comms') AS comms, u.name,
|
|
304
|
+
substr(t.task, 1, 400) AS askHead,
|
|
305
|
+
substr(t.task, -1400) AS askTail
|
|
306
|
+
FROM agent_tasks t
|
|
307
|
+
LEFT JOIN users u ON u.id = t.requestedByUserId
|
|
308
|
+
WHERE t.id = ?`;
|
|
309
|
+
|
|
310
|
+
const LEAD_SQL = "SELECT isLead FROM agents WHERE id = ?";
|
|
311
|
+
|
|
312
|
+
/**
|
|
313
|
+
* Mirrors the `task.progress.write` rule store-progress enforces
|
|
314
|
+
* (src/rbac/legacy-policy.ts): the caller owns the task, the task is
|
|
315
|
+
* unassigned, or the caller is a lead. Lead status comes from the caller's
|
|
316
|
+
* agents row, never from tool args. Extension callers return before this.
|
|
317
|
+
*/
|
|
318
|
+
const mayWriteProgress = async (
|
|
319
|
+
swarm: Swarm,
|
|
320
|
+
callerId: string | undefined,
|
|
321
|
+
taskAgentId: string,
|
|
322
|
+
): Promise<boolean> => {
|
|
323
|
+
if (!callerId) return false;
|
|
324
|
+
if (!taskAgentId || taskAgentId === callerId) return true;
|
|
325
|
+
const q = (await swarm.db_query({ sql: LEAD_SQL, params: [callerId] })) as Envelope;
|
|
326
|
+
const row = (q?.data?.rows ?? q?.rows ?? [])[0];
|
|
327
|
+
return Number(cell(row, 0, "isLead") ?? 0) === 1;
|
|
328
|
+
};
|
|
329
|
+
|
|
330
|
+
const extension: SwarmExtension = (api) => {
|
|
331
|
+
api.on("pre.tool.call", async (event, ctx) => {
|
|
332
|
+
if (event.tool !== "store-progress") return;
|
|
333
|
+
if (event.requestInfo?.callOrigin === "extension") return;
|
|
334
|
+
|
|
335
|
+
const cfg = cfgOf(ctx.config);
|
|
336
|
+
if (cfg.mode === "off") return;
|
|
337
|
+
|
|
338
|
+
const args = (event.args ?? {}) as Record<string, unknown>;
|
|
339
|
+
|
|
340
|
+
// Self-test hatch: a progress call whose text starts with the sentinel runs
|
|
341
|
+
// the controls and records them under `selftest:last`.
|
|
342
|
+
if (args.status !== "completed") {
|
|
343
|
+
if (typeof args.progress === "string" && args.progress.startsWith(SELFTEST)) {
|
|
344
|
+
await selfTest(ctx.swarm, cfg, ctx.signal);
|
|
345
|
+
}
|
|
346
|
+
return;
|
|
347
|
+
}
|
|
348
|
+
|
|
349
|
+
const output = typeof args.output === "string" ? args.output.trim() : "";
|
|
350
|
+
if (!output) return;
|
|
351
|
+
|
|
352
|
+
const taskId = String(args.taskId ?? event.requestInfo?.sourceTaskId ?? "");
|
|
353
|
+
if (!UUID_RE.test(taskId)) return;
|
|
354
|
+
|
|
355
|
+
try {
|
|
356
|
+
const q = (await ctx.swarm.db_query({ sql: TASK_SQL, params: [taskId] })) as Envelope;
|
|
357
|
+
const row = (q?.data?.rows ?? q?.rows ?? [])[0];
|
|
358
|
+
if (!row) return;
|
|
359
|
+
|
|
360
|
+
const source = String(cell(row, 0, "source") ?? "");
|
|
361
|
+
const slackThreadTs = cell(row, 1, "slackThreadTs");
|
|
362
|
+
const agentId = String(cell(row, 2, "agentId") ?? "");
|
|
363
|
+
const requesterId = String(cell(row, 3, "requestedByUserId") ?? "");
|
|
364
|
+
const commsRaw = cell(row, 4, "comms");
|
|
365
|
+
const requesterName = String(cell(row, 5, "name") ?? requesterId);
|
|
366
|
+
const threadOpening = String(cell(row, 6, "askHead") ?? "");
|
|
367
|
+
const ask = latestAsk(String(cell(row, 7, "askTail") ?? ""));
|
|
368
|
+
|
|
369
|
+
// Judge only a completion store-progress will accept, so no agent can ship
|
|
370
|
+
// another task's thread and preferences to the external judge.
|
|
371
|
+
if (!(await mayWriteProgress(ctx.swarm, event.requestInfo?.agentId, agentId))) return;
|
|
372
|
+
|
|
373
|
+
// Only where the text actually reaches a human in Slack.
|
|
374
|
+
if (source !== "slack" && !slackThreadTs) return;
|
|
375
|
+
if (!commsRaw) return; // no stated preferences -> nothing to judge against
|
|
376
|
+
const prefs = (typeof commsRaw === "string" ? JSON.parse(commsRaw) : commsRaw) as Prefs;
|
|
377
|
+
if (!prefs || typeof prefs !== "object") return;
|
|
378
|
+
|
|
379
|
+
const questions = buildQuestions(prefs);
|
|
380
|
+
if (Object.keys(questions).length === 0) return;
|
|
381
|
+
|
|
382
|
+
const transport = resolveTransport(cfg);
|
|
383
|
+
const { scores, latencyMs } = await askJev(
|
|
384
|
+
{
|
|
385
|
+
role: "You audit an AI agent's final message to the human who requested the work.",
|
|
386
|
+
requester: requesterName,
|
|
387
|
+
requester_preferences: prefs,
|
|
388
|
+
latest_request: ask,
|
|
389
|
+
thread_opening: threadOpening,
|
|
390
|
+
judging_rules: JUDGING_RULES,
|
|
391
|
+
agent_message: output,
|
|
392
|
+
},
|
|
393
|
+
questions,
|
|
394
|
+
transport,
|
|
395
|
+
cfg,
|
|
396
|
+
ctx.signal,
|
|
397
|
+
);
|
|
398
|
+
|
|
399
|
+
const failed = Object.entries(scores)
|
|
400
|
+
.filter(([, v]) => v < cfg.threshold)
|
|
401
|
+
.map(([k]) => k);
|
|
402
|
+
|
|
403
|
+
const verdict = {
|
|
404
|
+
at: new Date().toISOString(),
|
|
405
|
+
taskId,
|
|
406
|
+
agentId,
|
|
407
|
+
source,
|
|
408
|
+
requester: requesterName,
|
|
409
|
+
requesterId,
|
|
410
|
+
provider: transport.provider,
|
|
411
|
+
scores,
|
|
412
|
+
failed,
|
|
413
|
+
threshold: cfg.threshold,
|
|
414
|
+
mode: cfg.mode,
|
|
415
|
+
latencyMs,
|
|
416
|
+
preferences: prefs,
|
|
417
|
+
askPreview: ask.slice(0, 400),
|
|
418
|
+
outputPreview: output.slice(0, 700),
|
|
419
|
+
};
|
|
420
|
+
|
|
421
|
+
await ctx.swarm.kv_incr({ namespace: NS, key: "total" });
|
|
422
|
+
await ctx.swarm.kv_set({ namespace: NS, key: "last", value: verdict });
|
|
423
|
+
|
|
424
|
+
if (failed.length === 0) {
|
|
425
|
+
await ctx.swarm.kv_incr({ namespace: NS, key: "pass" });
|
|
426
|
+
return;
|
|
427
|
+
}
|
|
428
|
+
|
|
429
|
+
await ctx.swarm.kv_incr({ namespace: NS, key: "fail" });
|
|
430
|
+
for (const dim of failed) {
|
|
431
|
+
await ctx.swarm.kv_incr({ namespace: NS, key: `dim:${safeKey(dim)}` });
|
|
432
|
+
}
|
|
433
|
+
|
|
434
|
+
const blocksKey = `blocks:${safeKey(taskId)}`;
|
|
435
|
+
// kv_get answers {success, data:{value}}, and {success:false} when absent.
|
|
436
|
+
const prior = (await ctx.swarm.kv_get({ namespace: NS, key: blocksKey })) as Envelope;
|
|
437
|
+
const priorBlocks = Number(prior?.data?.value ?? prior?.value ?? 0) || 0;
|
|
438
|
+
const willBlock = cfg.mode === "enforce" && priorBlocks < cfg.maxBlocksPerTask;
|
|
439
|
+
|
|
440
|
+
await ctx.swarm.kv_set({
|
|
441
|
+
namespace: NS,
|
|
442
|
+
key: `fail:${Date.now()}:${safeKey(taskId)}`,
|
|
443
|
+
value: { ...verdict, action: willBlock ? "blocked" : "recorded" },
|
|
444
|
+
});
|
|
445
|
+
|
|
446
|
+
if (!willBlock) {
|
|
447
|
+
await ctx.swarm.kv_incr({
|
|
448
|
+
namespace: NS,
|
|
449
|
+
key: cfg.mode === "enforce" ? "escaped" : "shadowed",
|
|
450
|
+
});
|
|
451
|
+
return;
|
|
452
|
+
}
|
|
453
|
+
|
|
454
|
+
await ctx.swarm.kv_incr({ namespace: NS, key: "blocked" });
|
|
455
|
+
await ctx.swarm.kv_incr({ namespace: NS, key: blocksKey });
|
|
456
|
+
|
|
457
|
+
const detail = failed
|
|
458
|
+
.map(
|
|
459
|
+
(d) =>
|
|
460
|
+
`${d} (${scores[d]!.toFixed(2)} vs ${cfg.threshold}) — requested: ${String(prefs[d] ?? "").slice(0, 220)}`,
|
|
461
|
+
)
|
|
462
|
+
.join("\n");
|
|
463
|
+
|
|
464
|
+
return block(
|
|
465
|
+
[
|
|
466
|
+
`Comms gate: this output does not match ${requesterName}'s stated preferences.`,
|
|
467
|
+
detail,
|
|
468
|
+
"Rewrite the output to match and call store-progress again. Do not change the facts, only how they are said.",
|
|
469
|
+
].join("\n"),
|
|
470
|
+
);
|
|
471
|
+
} catch (err) {
|
|
472
|
+
// Fail open, always.
|
|
473
|
+
try {
|
|
474
|
+
await ctx.swarm.kv_incr({ namespace: NS, key: "error" });
|
|
475
|
+
await ctx.swarm.kv_set({
|
|
476
|
+
namespace: NS,
|
|
477
|
+
key: "error:last",
|
|
478
|
+
value: { at: new Date().toISOString(), taskId, error: String(err).slice(0, 400) },
|
|
479
|
+
});
|
|
480
|
+
} catch {
|
|
481
|
+
/* ignore */
|
|
482
|
+
}
|
|
483
|
+
ctx.log.warn("comms-adherence-gate: failing open", { error: String(err) });
|
|
484
|
+
return;
|
|
485
|
+
}
|
|
486
|
+
});
|
|
487
|
+
};
|
|
488
|
+
|
|
489
|
+
export default extension;
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
{
|
|
2
|
+
"$schema": "../manifest.schema.json",
|
|
3
|
+
"name": "comms-adherence-gate",
|
|
4
|
+
"description": "Judges every Slack-facing store-progress completion against the requester's comms preferences and latest request with Jev (TypeSafe or OpenRouter), and in enforce blocks a non-adherent completion once per task",
|
|
5
|
+
"version": "1.5.0",
|
|
6
|
+
"runtime": "api",
|
|
7
|
+
"assets": { "hooks": "hooks.ts" }
|
|
8
|
+
}
|
|
@@ -84,7 +84,7 @@ agent-fs share-create thoughts/<agent-id>/qa/<topic>-screenshots/<name>.png --js
|
|
|
84
84
|
- In `store-progress`, list the upload in the `attachments` field with `kind: "agent-fs"` and the path, and paste the `share-create` link for people who need to open the file.
|
|
85
85
|
- If `agent-fs auth whoami` fails, report the local path, say the upload was skipped, and continue.
|
|
86
86
|
|
|
87
|
-
The `
|
|
87
|
+
The `agent-fs` skill holds the full agent-fs recipe.
|
|
88
88
|
|
|
89
89
|
## Gotchas
|
|
90
90
|
|
|
@@ -79,7 +79,7 @@ agent-fs share-create thoughts/<agent-id>/qa/<topic>-screenshots/<name>.png --js
|
|
|
79
79
|
- In `store-progress`, list the upload in the `attachments` field with `kind: "agent-fs"` and the path, and paste the `share-create` link for people who need to open the file.
|
|
80
80
|
- If `agent-fs auth whoami` fails, report the local path, say the upload was skipped, and continue.
|
|
81
81
|
|
|
82
|
-
The `
|
|
82
|
+
The `agent-fs` skill holds the full agent-fs recipe.
|
|
83
83
|
|
|
84
84
|
## Gotchas
|
|
85
85
|
|
|
@@ -5,7 +5,7 @@ description: Build and iterate on schema-backed Swarm Apps with live models, nam
|
|
|
5
5
|
|
|
6
6
|
# Swarm Apps
|
|
7
7
|
|
|
8
|
-
Swarm Apps are persistent, agent-authored internal applications: a schema-backed row store, named queries, custom script/task actions, and a validated json-render interface served at `/apps/<id>`. Use an app when people need to view and change live records; use `create_page` for a static snapshot
|
|
8
|
+
Swarm Apps are persistent, agent-authored internal applications: a schema-backed row store, named queries, custom script/task actions, and a validated json-render interface served at `/apps/<id>`. Use an app when people need to view and change live records; use `create_page` for a static snapshot.
|
|
9
9
|
|
|
10
10
|
> **Capability gate**: app tools are available when the server's `CAPABILITIES` includes `pages` (for example `CAPABILITIES=core,task-pool,pages`). There is no separate `apps` capability. If these tools are absent from your MCP list, this is why.
|
|
11
11
|
|
|
@@ -740,3 +740,10 @@ Create this with `app-upsert`:
|
|
|
740
740
|
```
|
|
741
741
|
|
|
742
742
|
The tool returns `{ appId, url: "/apps/<id>" }`. Open that URL to verify the live UI, then continue with the read-patch-validate loop above.
|
|
743
|
+
|
|
744
|
+
## View and screenshot a live app
|
|
745
|
+
|
|
746
|
+
- An app renders at `<dashboard>/apps/<id>`. Two view modes are set by query string: `?mode=full` (full-viewport overlay with a slim header) and `?mode=chromeless` (only the rendered page, the embed surface). `app.navigate` keeps only `?mode`.
|
|
747
|
+
- Logged-in browser: a fresh dashboard has no connection and redirects to /setup. Seed the localStorage key `agent-swarm-connections` with `{connections:[{id,name,apiUrl,apiKey}],activeId}`. Build the value from an env var and pipe it to `agent-browser eval --stdin`, so the key never reaches a command line. Reload, then pick a user in the "Who are you?" dialog.
|
|
748
|
+
- Apps scroll inside an inner canvas, so a full-page screenshot captures only the viewport. Scroll the canvas or raise the viewport height. For mobile, set a 375px-wide viewport before the screenshot.
|
|
749
|
+
- App definitions: `GET /api/apps` and `GET /api/apps/<id>` with the API key as Bearer.
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# Swarm Apps
|
|
2
2
|
|
|
3
|
-
Swarm Apps are persistent, agent-authored internal applications: a schema-backed row store, named queries, custom script/task actions, and a validated json-render interface served at `/apps/<id>`. Use an app when people need to view and change live records; use `create_page` for a static snapshot
|
|
3
|
+
Swarm Apps are persistent, agent-authored internal applications: a schema-backed row store, named queries, custom script/task actions, and a validated json-render interface served at `/apps/<id>`. Use an app when people need to view and change live records; use `create_page` for a static snapshot.
|
|
4
4
|
|
|
5
5
|
> **Capability gate**: app tools are available when the server's `CAPABILITIES` includes `pages` (for example `CAPABILITIES=core,task-pool,pages`). There is no separate `apps` capability. If these tools are absent from your MCP list, this is why.
|
|
6
6
|
|
|
@@ -735,3 +735,10 @@ Create this with `app-upsert`:
|
|
|
735
735
|
```
|
|
736
736
|
|
|
737
737
|
The tool returns `{ appId, url: "/apps/<id>" }`. Open that URL to verify the live UI, then continue with the read-patch-validate loop above.
|
|
738
|
+
|
|
739
|
+
## View and screenshot a live app
|
|
740
|
+
|
|
741
|
+
- An app renders at `<dashboard>/apps/<id>`. Two view modes are set by query string: `?mode=full` (full-viewport overlay with a slim header) and `?mode=chromeless` (only the rendered page, the embed surface). `app.navigate` keeps only `?mode`.
|
|
742
|
+
- Logged-in browser: a fresh dashboard has no connection and redirects to /setup. Seed the localStorage key `agent-swarm-connections` with `{connections:[{id,name,apiUrl,apiKey}],activeId}`. Build the value from an env var and pipe it to `agent-browser eval --stdin`, so the key never reaches a command line. Reload, then pick a user in the "Who are you?" dialog.
|
|
743
|
+
- Apps scroll inside an inner canvas, so a full-page screenshot captures only the viewport. Scroll the canvas or raise the viewport height. For mobile, set a 375px-wide viewport before the screenshot.
|
|
744
|
+
- App definitions: `GET /api/apps` and `GET /api/apps/<id>` with the API key as Bearer.
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
{
|
|
2
|
+
"kind": "skill",
|
|
3
|
+
"name": "chat-sdk",
|
|
4
|
+
"displayName": "Chat SDK",
|
|
5
|
+
"slug": "chat-sdk",
|
|
6
|
+
"title": "Chat SDK",
|
|
7
|
+
"description": "Chat threads from Telegram, Slack (Chat SDK), Discord, and Teams. Use when a task carries a task:chat-sdk contextKey or a user asked through Telegram, Slack, Discord, or Teams. Covers what the swarm posts for you, `chat-reply`, markdown and length per platform, attachments, `chat-read-thread` limits, and channel routes with `register-chat-channel`.",
|
|
8
|
+
"version": "1.0.0",
|
|
9
|
+
"category": "skills",
|
|
10
|
+
"placeholders": [],
|
|
11
|
+
"runAllSeedersCandidate": true,
|
|
12
|
+
"systemDefault": false,
|
|
13
|
+
"tags": ["chat", "telegram", "discord", "teams", "slack", "communication"]
|
|
14
|
+
}
|
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
# Chat SDK
|
|
2
|
+
|
|
3
|
+
The swarm receives messages from Telegram, Slack, Discord, and Microsoft Teams through the Chat SDK channel. A person mentions the bot or sends it a direct message, and the swarm creates a task for it. This skill tells you how to work on such a task.
|
|
4
|
+
|
|
5
|
+
## How you know you are on a chat task
|
|
6
|
+
|
|
7
|
+
- The task has `source: "chat"`, or it is a lead follow-up of one.
|
|
8
|
+
- The task `contextKey` starts with `task:chat-sdk:`. The full form is `task:chat-sdk:<adapter>:<encoded thread id>`, for example `task:chat-sdk:telegram:555001`.
|
|
9
|
+
- The task text holds the user's message. Later replies in the same thread arrive as follow-up tasks with the same `contextKey`, or as steering messages on your running task.
|
|
10
|
+
- The user has four slash commands that never reach you as a task: `/help`, `/status`, `/cancel` and `/reset`. `/cancel` ends the thread's active tasks, yours included, with status `cancelled`. After `/reset` the next task has no parent task and no earlier thread context, so treat it as a new conversation.
|
|
11
|
+
|
|
12
|
+
Use `get-task-details` to read the `contextKey` when you are not sure.
|
|
13
|
+
|
|
14
|
+
## Reply with `chat-reply`
|
|
15
|
+
|
|
16
|
+
`chat-reply { text }` posts to the thread your current task came from. It finds the thread from your task, so you do not pass ids.
|
|
17
|
+
|
|
18
|
+
- Use it to ask the user a question you cannot answer yourself. Ask once, with all your questions together.
|
|
19
|
+
- Use it for one short progress note when work takes more than a few minutes, such as "Started. This takes about 10 minutes."
|
|
20
|
+
- Do not use it for receipts ("Got it", "On it"), and do not send many progress notes.
|
|
21
|
+
- It works only on a task with a `task:chat-sdk:` contextKey. A session bound to a non-chat task does not see `chat-reply` at all, because the tool is left out of its tool list.
|
|
22
|
+
|
|
23
|
+
## The final summary is posted automatically
|
|
24
|
+
|
|
25
|
+
When your task ends, the swarm posts the task output to the thread:
|
|
26
|
+
|
|
27
|
+
- Completed: your `store-progress` output.
|
|
28
|
+
- Failed: a "Task failed." line, then the failure reason.
|
|
29
|
+
- Cancelled: a "Task cancelled." line, then the reason when there is one.
|
|
30
|
+
|
|
31
|
+
Do not post your final answer with `chat-reply` as well. The user would get it twice. Put the answer in the `store-progress` output and stop.
|
|
32
|
+
|
|
33
|
+
The post is durable. If the platform is down, the swarm retries with backoff for up to 24 hours.
|
|
34
|
+
|
|
35
|
+
## Markdown per platform
|
|
36
|
+
|
|
37
|
+
Write plain, short markdown: bold, italic, inline code, code blocks, bullet lists, and links. Avoid tables, nested lists, and HTML.
|
|
38
|
+
|
|
39
|
+
| Platform | What renders | Limit per message |
|
|
40
|
+
|---|---|---|
|
|
41
|
+
| Telegram | Bold, italic, code, code blocks, links, lists. No tables. | 4096 characters |
|
|
42
|
+
| Discord | Bold, italic, code, code blocks, lists, headings, links. No tables. | 2000 characters |
|
|
43
|
+
| Slack | Converted to Slack mrkdwn: bold, italic, code, lists, links. Tables do not render well. | 4000 characters |
|
|
44
|
+
| Teams | Basic markdown: bold, italic, code, lists, links. | 20000 characters |
|
|
45
|
+
|
|
46
|
+
The swarm splits longer text into several messages at line breaks. Long answers are hard to read in chat, so keep the answer short and put long material in an artifact or page, then post the link.
|
|
47
|
+
|
|
48
|
+
## Attachments arrive as task attachments
|
|
49
|
+
|
|
50
|
+
Files and images the user sends in the thread are stored as attachments of the task. The task text names them. Download them with the `download-task-attachment` skill. You cannot send files back to the thread in this version. Share a link instead.
|
|
51
|
+
|
|
52
|
+
## `chat-read-thread` limits per platform
|
|
53
|
+
|
|
54
|
+
`chat-read-thread { threadId, limit? }` returns the most recent messages of a thread, oldest first. The thread id is the adapter name plus the URI-decoded remainder of the contextKey. Telegram ids have no escaping, so `telegram:555001` stays as it is. Slack, Discord, and Teams ids contain `%3A` in the contextKey, so do not build the id by hand. `chat-reply` resolves the thread itself. For `chat-read-thread`, use the thread id returned by `chat-reply`, `chat-post`, or `chat-open-dm`. You can read the thread of your current task. Any other thread needs lead privileges.
|
|
55
|
+
|
|
56
|
+
- Telegram has no history API. The SDK keeps the Telegram thread history it saw in the swarm database, capped at 100 messages and 7 days.
|
|
57
|
+
- Teams needs the `Chat.Read.*` Graph permission on the bot. Without it, the call fails.
|
|
58
|
+
- Slack and Discord return the platform history.
|
|
59
|
+
|
|
60
|
+
The task text already holds the earlier swarm messages of the thread, so read the thread only when you need more.
|
|
61
|
+
|
|
62
|
+
## Other tools
|
|
63
|
+
|
|
64
|
+
- `chat-post { adapter, threadId | channelId, text }` posts outside your task's thread. Lead only. Use it only when the task asks you to message another thread or channel.
|
|
65
|
+
- `chat-open-dm { adapter, userId }` returns the direct-message thread id for a user. Lead only. Then post to it with `chat-post`.
|
|
66
|
+
|
|
67
|
+
## Leads: `register-chat-channel`
|
|
68
|
+
|
|
69
|
+
By default every message addressed to the swarm goes to the lead. A lead can route one channel to a fixed agent or workflow:
|
|
70
|
+
|
|
71
|
+
```
|
|
72
|
+
register-chat-channel { adapter: "telegram", channelId: "telegram:-100123", agentId: "<agent id>" }
|
|
73
|
+
register-chat-channel { adapter: "slack", channelId: "slack:C123", workflowId: "<workflow id>" }
|
|
74
|
+
unregister-chat-channel { adapter: "telegram", channelId: "telegram:-100123" }
|
|
75
|
+
```
|
|
76
|
+
|
|
77
|
+
Give exactly one of `agentId` or `workflowId`. A `swarm#<agentId>` token in a message still wins over the route. A workflow mapping fires only for a bot mention or a DM; a plain reply in the subscribed thread does not start another run. Only the lead can call these tools.
|
|
78
|
+
|
|
79
|
+
## Etiquette
|
|
80
|
+
|
|
81
|
+
- Keep messages short. Chat is read on phones.
|
|
82
|
+
- Never post secrets, tokens, internal URLs with credentials, or private data from other users. The swarm scrubs known secrets, but do not depend on it.
|
|
83
|
+
- When you ask a question in a group, start with the user's name so they see it.
|
|
84
|
+
- One question message is better than several.
|