@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
package/src/commands/runner.ts
CHANGED
|
@@ -11,7 +11,14 @@ import {
|
|
|
11
11
|
withSpan,
|
|
12
12
|
withSpanContext,
|
|
13
13
|
} from "../otel.ts";
|
|
14
|
-
import {
|
|
14
|
+
import {
|
|
15
|
+
type BasePromptArgs,
|
|
16
|
+
getBasePrompt,
|
|
17
|
+
getTaskContextPrompt,
|
|
18
|
+
type RepoContext,
|
|
19
|
+
type SlackTaskContext,
|
|
20
|
+
type TaskContextPromptArgs,
|
|
21
|
+
} from "../prompts/base-prompt.ts";
|
|
15
22
|
import {
|
|
16
23
|
generateDefaultClaudeMd,
|
|
17
24
|
generateDefaultIdentityMd,
|
|
@@ -40,6 +47,7 @@ import {
|
|
|
40
47
|
type ProviderSession,
|
|
41
48
|
type ProviderSessionConfig,
|
|
42
49
|
} from "../providers/index.ts";
|
|
50
|
+
import { effectiveReasoningEffort } from "../providers/reasoning-effort.ts";
|
|
43
51
|
import {
|
|
44
52
|
createSkillInvokeTracker,
|
|
45
53
|
isSkillLoaderCall,
|
|
@@ -48,6 +56,7 @@ import {
|
|
|
48
56
|
} from "../providers/skill-invoke.ts";
|
|
49
57
|
import type { SteerDeliveryResult } from "../providers/types.ts";
|
|
50
58
|
import { WorkerRealtimeClient } from "../realtime/worker-client";
|
|
59
|
+
import { resumedTaskModelSelection, type TaskModelPinFields } from "../tasks/model-pin.ts";
|
|
51
60
|
import { initTelemetry, telemetry } from "../telemetry.ts";
|
|
52
61
|
import { mapTriggerSurface } from "../telemetry-context.ts";
|
|
53
62
|
import {
|
|
@@ -66,12 +75,14 @@ import { getApiKey } from "../utils/api-key.ts";
|
|
|
66
75
|
import { computeBudgetBackoffMs } from "../utils/budget-backoff.ts";
|
|
67
76
|
import { ensure, initialize } from "../utils/business-use";
|
|
68
77
|
import { isCodexAuthFailureReason } from "../utils/codex-auth-failure.ts";
|
|
78
|
+
import { isEnvExportRestrictedKey } from "../utils/config-env-export.ts";
|
|
69
79
|
import { MASKED_SECRET_VALUE } from "../utils/config-secrets.ts";
|
|
70
80
|
import { getMcpBaseUrl } from "../utils/constants.ts";
|
|
71
81
|
import { getContextWindowSize } from "../utils/context-window.ts";
|
|
72
82
|
import {
|
|
73
83
|
type CredentialSelection,
|
|
74
84
|
ModelWindowExhaustedError,
|
|
85
|
+
normalizeProviderKeyAliases,
|
|
75
86
|
resolveCredentialPools,
|
|
76
87
|
} from "../utils/credentials.ts";
|
|
77
88
|
import { resolveCodexCreditsExhaustedCooldownMs } from "../utils/error-tracker.ts";
|
|
@@ -81,11 +92,16 @@ import { prettyPrintLine, prettyPrintStderr } from "../utils/pretty-print.ts";
|
|
|
81
92
|
import { terminateRegisteredProcessGroups } from "../utils/process-group.ts";
|
|
82
93
|
import { refreshRuntimeModelCatalog } from "../utils/runtime-model-catalog.ts";
|
|
83
94
|
import { resolveScriptsOnlyMode } from "../utils/scripts-only-mode.ts";
|
|
84
|
-
import {
|
|
95
|
+
import {
|
|
96
|
+
registerEnvSecrets,
|
|
97
|
+
registerSensitiveKeyName,
|
|
98
|
+
scrubSecrets,
|
|
99
|
+
} from "../utils/secret-scrubber.ts";
|
|
85
100
|
import { refreshSkillsIfChanged } from "../utils/skills-refresh.ts";
|
|
86
101
|
import { guardSpawnModel } from "../utils/spawn-model-guard.ts";
|
|
87
102
|
import { isSteeringEnabled } from "../utils/steering-enabled.ts";
|
|
88
103
|
import { interpolate } from "../utils/template.ts";
|
|
104
|
+
import { vcsRepoName, vcsRepoUrlMatches } from "../utils/vcs-repo-match.ts";
|
|
89
105
|
import {
|
|
90
106
|
canonicalAzureDevOpsRepoUrl,
|
|
91
107
|
isAzureDevOpsUrl,
|
|
@@ -181,17 +197,6 @@ function scheduleRunnerGc(reason: string): boolean {
|
|
|
181
197
|
return true;
|
|
182
198
|
}
|
|
183
199
|
|
|
184
|
-
/** Save PM2 process list for persistence across container restarts */
|
|
185
|
-
async function savePm2State(role: string): Promise<void> {
|
|
186
|
-
try {
|
|
187
|
-
console.log(`[${role}] Saving PM2 process list...`);
|
|
188
|
-
await Bun.$`pm2 save`.quiet();
|
|
189
|
-
console.log(`[${role}] PM2 state saved`);
|
|
190
|
-
} catch {
|
|
191
|
-
// PM2 not available or no processes - silently ignore
|
|
192
|
-
}
|
|
193
|
-
}
|
|
194
|
-
|
|
195
200
|
/** Fetch repo config for a task's vcsRepo (e.g., "desplega-ai/agent-swarm") */
|
|
196
201
|
export async function fetchRepoConfig(
|
|
197
202
|
apiUrl: string,
|
|
@@ -208,11 +213,7 @@ export async function fetchRepoConfig(
|
|
|
208
213
|
guidelines?: RepoGuidelines | null;
|
|
209
214
|
} | null> {
|
|
210
215
|
try {
|
|
211
|
-
const
|
|
212
|
-
.trim()
|
|
213
|
-
.replace(/\/+$/, "")
|
|
214
|
-
.replace(/\.git$/, "");
|
|
215
|
-
const repoName = requested.split("/").pop() || requested;
|
|
216
|
+
const repoName = vcsRepoName(vcsRepo);
|
|
216
217
|
const resp = await fetch(`${apiUrl}/api/repos?name=${encodeURIComponent(repoName)}`, {
|
|
217
218
|
headers: { Authorization: `Bearer ${apiKey}` },
|
|
218
219
|
});
|
|
@@ -229,18 +230,7 @@ export async function fetchRepoConfig(
|
|
|
229
230
|
}>;
|
|
230
231
|
};
|
|
231
232
|
if (requireExactMatch) {
|
|
232
|
-
return (
|
|
233
|
-
data.repos.find((r) => {
|
|
234
|
-
const normalized = r.url
|
|
235
|
-
.trim()
|
|
236
|
-
.replace(/\/+$/, "")
|
|
237
|
-
.replace(/\.git$/, "");
|
|
238
|
-
if (normalized === requested) return true;
|
|
239
|
-
// Qualified URLs must retain their host. Only shorthand references use suffix matching.
|
|
240
|
-
if (requested.includes(":")) return false;
|
|
241
|
-
return normalized.endsWith(`/${requested}`) || normalized.endsWith(`:${requested}`);
|
|
242
|
-
}) ?? null
|
|
243
|
-
);
|
|
233
|
+
return data.repos.find((r) => vcsRepoUrlMatches(r.url, vcsRepo)) ?? null;
|
|
244
234
|
}
|
|
245
235
|
return data.repos.find((r) => r.url.includes(vcsRepo)) ?? data.repos[0] ?? null;
|
|
246
236
|
} catch {
|
|
@@ -846,6 +836,19 @@ const deploymentMemoryRaters = process.env.MEMORY_RATERS;
|
|
|
846
836
|
*/
|
|
847
837
|
const rowOverriddenBootEnv = new Map<string, string | undefined>();
|
|
848
838
|
|
|
839
|
+
let lastWithheldEnvKeys = "";
|
|
840
|
+
|
|
841
|
+
/** Names only, and only when the set changes, so per-spawn reloads stay quiet. */
|
|
842
|
+
function logWithheldEnvKeys(keys: string[]): void {
|
|
843
|
+
const summary = [...keys].sort().join(", ");
|
|
844
|
+
if (summary === lastWithheldEnvKeys) return;
|
|
845
|
+
lastWithheldEnvKeys = summary;
|
|
846
|
+
if (!summary) return;
|
|
847
|
+
console.log(
|
|
848
|
+
`[env-reload] Kept ${keys.length} config entries with exportToEnv=false out of the harness env: ${summary}`,
|
|
849
|
+
);
|
|
850
|
+
}
|
|
851
|
+
|
|
849
852
|
export async function fetchResolvedEnv(
|
|
850
853
|
apiUrl: string,
|
|
851
854
|
apiKey: string,
|
|
@@ -895,7 +898,12 @@ export async function fetchResolvedEnv(
|
|
|
895
898
|
console.warn(`[env-reload] Failed to fetch config: ${response.status}`);
|
|
896
899
|
} else {
|
|
897
900
|
const data = (await response.json()) as {
|
|
898
|
-
configs: Array<{
|
|
901
|
+
configs: Array<{
|
|
902
|
+
key: string;
|
|
903
|
+
value: string;
|
|
904
|
+
isSecret?: boolean;
|
|
905
|
+
exportToEnv?: boolean;
|
|
906
|
+
}>;
|
|
899
907
|
};
|
|
900
908
|
|
|
901
909
|
// A deleted row restores the deployment value (including unset), while
|
|
@@ -914,7 +922,25 @@ export async function fetchResolvedEnv(
|
|
|
914
922
|
scriptsOnlyConfigValue = data.configs.find(
|
|
915
923
|
(config) => config.key === "SCRIPTS_ONLY_MCP",
|
|
916
924
|
)?.value;
|
|
925
|
+
const withheldEnvKeys: string[] = [];
|
|
917
926
|
for (const config of data.configs) {
|
|
927
|
+
// Every harness and its shell tool inherit this env. A row with
|
|
928
|
+
// exportToEnv=false stays out of it, and so does a copy of it in
|
|
929
|
+
// the base env (container env, or an older entrypoint's export).
|
|
930
|
+
// Checked before the masked-value fallback so a masked response
|
|
931
|
+
// can never keep a withheld boot value alive. An API without the
|
|
932
|
+
// field (pre-215) leaves it undefined, which keeps exporting.
|
|
933
|
+
if (config.exportToEnv === false || isEnvExportRestrictedKey(config.key)) {
|
|
934
|
+
if (config.isSecret) registerSensitiveKeyName(config.key);
|
|
935
|
+
delete env[config.key];
|
|
936
|
+
// Several adapters build the child env as
|
|
937
|
+
// `{ ...process.env, ...config.env }`, so a boot-time export
|
|
938
|
+
// revoked later must leave process.env too. It stays out until
|
|
939
|
+
// the worker restarts, even if the row is deleted afterwards.
|
|
940
|
+
if (baseEnv === process.env) delete process.env[config.key];
|
|
941
|
+
withheldEnvKeys.push(config.key);
|
|
942
|
+
continue;
|
|
943
|
+
}
|
|
918
944
|
if (config.isSecret && config.value === MASKED_SECRET_VALUE) {
|
|
919
945
|
skippedSecretKeys.push(config.key);
|
|
920
946
|
continue;
|
|
@@ -957,6 +983,7 @@ export async function fetchResolvedEnv(
|
|
|
957
983
|
scrubSecrets(`[env-reload] Skipped masked secrets: ${skippedSecretKeys.join(", ")}`),
|
|
958
984
|
);
|
|
959
985
|
}
|
|
986
|
+
logWithheldEnvKeys(withheldEnvKeys);
|
|
960
987
|
console.log(`[env-reload] Loaded ${data.configs.length} config entries from API`);
|
|
961
988
|
}
|
|
962
989
|
// Non-secret stand-in for the pool rows skipped above, so credential
|
|
@@ -969,6 +996,16 @@ export async function fetchResolvedEnv(
|
|
|
969
996
|
}
|
|
970
997
|
}
|
|
971
998
|
|
|
999
|
+
// Registry key aliases (`HELMCODE_API_NAN` for `NAN_API_KEY`) are copied
|
|
1000
|
+
// into their canonical var once here, so pooling, credential checks, pi's
|
|
1001
|
+
// runtime key and the opencode key splice only ever read the canonical
|
|
1002
|
+
// name. An explicit canonical value wins.
|
|
1003
|
+
normalizeProviderKeyAliases(env);
|
|
1004
|
+
// Rows admitted after boot and pool members live only in this env, never in
|
|
1005
|
+
// process.env, so the scrubber's env pass cannot see them. Register them
|
|
1006
|
+
// before any adapter can echo one into a log or failure reason.
|
|
1007
|
+
registerEnvSecrets(env);
|
|
1008
|
+
|
|
972
1009
|
// A task already has an adapter. Repository configuration must not select
|
|
973
1010
|
// credentials or configure hooks for a different harness.
|
|
974
1011
|
const resolvedProvider = sessionContext?.provider ?? resolveHarnessProvider(env, baseEnv);
|
|
@@ -1355,11 +1392,6 @@ const SWARM_TOOL_LABELS: Record<string, string | null> = {
|
|
|
1355
1392
|
"create-channel": "📢 Creating channel",
|
|
1356
1393
|
"list-channels": "📢 Listing channels",
|
|
1357
1394
|
"delete-channel": "📢 Deleting channel",
|
|
1358
|
-
// Services
|
|
1359
|
-
"register-service": "🔌 Registering service",
|
|
1360
|
-
"list-services": "🔌 Listing services",
|
|
1361
|
-
"unregister-service": "🔌 Unregistering service",
|
|
1362
|
-
"update-service-status": "🔌 Updating service status",
|
|
1363
1395
|
// Reusable scripts
|
|
1364
1396
|
"script-search": "📜 Searching scripts",
|
|
1365
1397
|
"script-run": "📜 Running script",
|
|
@@ -2343,6 +2375,7 @@ export async function getPausedTasksFromAPI(config: ApiConfig): Promise<
|
|
|
2343
2375
|
output?: string;
|
|
2344
2376
|
status?: string;
|
|
2345
2377
|
contextKey?: string;
|
|
2378
|
+
requestedBy?: Trigger["requestedBy"];
|
|
2346
2379
|
}>
|
|
2347
2380
|
> {
|
|
2348
2381
|
const headers: Record<string, string> = {
|
|
@@ -2379,6 +2412,7 @@ export async function getPausedTasksFromAPI(config: ApiConfig): Promise<
|
|
|
2379
2412
|
finishedAt?: string;
|
|
2380
2413
|
output?: string;
|
|
2381
2414
|
status?: string;
|
|
2415
|
+
requestedBy?: Trigger["requestedBy"];
|
|
2382
2416
|
}>;
|
|
2383
2417
|
};
|
|
2384
2418
|
return data.tasks || [];
|
|
@@ -2537,7 +2571,6 @@ export function setupShutdownHandlers(
|
|
|
2537
2571
|
});
|
|
2538
2572
|
await closeAgent(apiConfig, role);
|
|
2539
2573
|
}
|
|
2540
|
-
await savePm2State(role);
|
|
2541
2574
|
process.exit(exitCode);
|
|
2542
2575
|
};
|
|
2543
2576
|
|
|
@@ -2805,10 +2838,13 @@ async function saveProviderSessionId(
|
|
|
2805
2838
|
model?: string,
|
|
2806
2839
|
harnessVariant?: string,
|
|
2807
2840
|
harnessVariantMeta?: Record<string, unknown>,
|
|
2841
|
+
effort?: ReasoningEffort | null,
|
|
2808
2842
|
): Promise<void> {
|
|
2809
2843
|
const headers: Record<string, string> = { "Content-Type": "application/json" };
|
|
2810
2844
|
if (apiKey) headers.Authorization = `Bearer ${apiKey}`;
|
|
2811
2845
|
const body: Record<string, unknown> = { claudeSessionId };
|
|
2846
|
+
// Devin's body variant has no effort field.
|
|
2847
|
+
if (effort !== undefined && provider !== "devin") body.effort = effort;
|
|
2812
2848
|
if (provider !== undefined) body.provider = provider;
|
|
2813
2849
|
if (providerMeta !== undefined) body.providerMeta = providerMeta;
|
|
2814
2850
|
if (model !== undefined && model !== "") body.model = model;
|
|
@@ -3284,6 +3320,64 @@ export async function buildRequesterProfilePrompt(
|
|
|
3284
3320
|
return result.skipped ? "" : result.text.trim();
|
|
3285
3321
|
}
|
|
3286
3322
|
|
|
3323
|
+
/**
|
|
3324
|
+
* Warning appended to the task prompt when the task's `dir` was requested but
|
|
3325
|
+
* the session could not start there. Shared by the fresh and resume paths so
|
|
3326
|
+
* both send the model the same text.
|
|
3327
|
+
*/
|
|
3328
|
+
export function missingTaskDirWarning(
|
|
3329
|
+
taskDir: string | undefined,
|
|
3330
|
+
effectiveCwd: string | undefined,
|
|
3331
|
+
): string | undefined {
|
|
3332
|
+
return taskDir && !effectiveCwd
|
|
3333
|
+
? `Note: The task requested working directory "${taskDir}" but it does not exist. Falling back to default directory.`
|
|
3334
|
+
: undefined;
|
|
3335
|
+
}
|
|
3336
|
+
|
|
3337
|
+
/** Per-task context that rides in the task prompt, never the system prompt. */
|
|
3338
|
+
export type TaskSessionContext = Omit<TaskContextPromptArgs, keyof BasePromptArgs> & {
|
|
3339
|
+
requestedBy?: RequesterProfile;
|
|
3340
|
+
/** Set when the task's `dir` does not exist and the session falls back. */
|
|
3341
|
+
cwdWarning?: string;
|
|
3342
|
+
};
|
|
3343
|
+
|
|
3344
|
+
/**
|
|
3345
|
+
* The two prompts of one task session. The system prompt depends only on the
|
|
3346
|
+
* agent (profile, harness, deployment), so it is byte-identical across that
|
|
3347
|
+
* agent's sessions and the provider reads it from cache instead of re-writing
|
|
3348
|
+
* it. Everything that varies per task (repository block, scripts-only Slack
|
|
3349
|
+
* note, requester profile, cwd warning) is appended to the task prompt.
|
|
3350
|
+
*/
|
|
3351
|
+
export async function buildTaskSessionPrompts(input: {
|
|
3352
|
+
basePromptArgs: BasePromptArgs;
|
|
3353
|
+
additionalSystemPrompt?: string;
|
|
3354
|
+
taskPrompt: string;
|
|
3355
|
+
context: TaskSessionContext;
|
|
3356
|
+
}): Promise<{ systemPrompt: string; prompt: string }> {
|
|
3357
|
+
const { basePromptArgs, additionalSystemPrompt, context } = input;
|
|
3358
|
+
const basePrompt = await getBasePrompt(basePromptArgs);
|
|
3359
|
+
const systemPrompt = additionalSystemPrompt
|
|
3360
|
+
? `${basePrompt}\n\n${additionalSystemPrompt}`
|
|
3361
|
+
: basePrompt;
|
|
3362
|
+
|
|
3363
|
+
const taskContext = [
|
|
3364
|
+
await getTaskContextPrompt({
|
|
3365
|
+
...basePromptArgs,
|
|
3366
|
+
repoContext: context.repoContext,
|
|
3367
|
+
slackContext: context.slackContext,
|
|
3368
|
+
}),
|
|
3369
|
+
await buildRequesterProfilePrompt(context.requestedBy),
|
|
3370
|
+
context.cwdWarning?.trim(),
|
|
3371
|
+
]
|
|
3372
|
+
.filter((part): part is string => Boolean(part))
|
|
3373
|
+
.join("\n\n");
|
|
3374
|
+
|
|
3375
|
+
return {
|
|
3376
|
+
systemPrompt,
|
|
3377
|
+
prompt: taskContext ? `${input.taskPrompt}\n\n${taskContext}` : input.taskPrompt,
|
|
3378
|
+
};
|
|
3379
|
+
}
|
|
3380
|
+
|
|
3287
3381
|
/** A non-2xx answer from `POST /api/agents`; carries the status for retry triage. */
|
|
3288
3382
|
export class AgentRegistrationHttpError extends Error {
|
|
3289
3383
|
readonly status: number;
|
|
@@ -4128,6 +4222,9 @@ async function spawnProviderProcess(
|
|
|
4128
4222
|
}
|
|
4129
4223
|
if (spawnModel.warning) console.warn(spawnModel.warning);
|
|
4130
4224
|
const model = spawnModel.model;
|
|
4225
|
+
// Recorded on the task at session start, like the model: what the adapter
|
|
4226
|
+
// applies, not just what was asked for.
|
|
4227
|
+
const sessionEffort = effectiveReasoningEffort(adapter.name, model, reasoningEffortOverride);
|
|
4131
4228
|
|
|
4132
4229
|
// Resolve Codex OAuth pool slot BEFORE building ProviderSessionConfig so we
|
|
4133
4230
|
// can pass codexSlot through and the adapter writes token refreshes back to
|
|
@@ -4431,6 +4528,7 @@ async function spawnProviderProcess(
|
|
|
4431
4528
|
model,
|
|
4432
4529
|
event.harnessVariant,
|
|
4433
4530
|
event.harnessVariantMeta,
|
|
4531
|
+
sessionEffort,
|
|
4434
4532
|
).catch((err) => console.warn(`[runner] Failed to save session ID: ${err}`));
|
|
4435
4533
|
} else {
|
|
4436
4534
|
// Pool task: save provider session ID on active session so it can be
|
|
@@ -5690,17 +5788,13 @@ export async function runAgent(config: RunnerConfig, opts: RunnerOptions) {
|
|
|
5690
5788
|
let agentSkillsSummary: { name: string; description: string }[] | undefined;
|
|
5691
5789
|
let agentMcpServers: string[] | undefined;
|
|
5692
5790
|
|
|
5693
|
-
// Per-task repo context — set when processing a task with githubRepo
|
|
5694
|
-
let currentRepoContext: BasePromptArgs["repoContext"] | undefined;
|
|
5695
|
-
// Slack context for current task (gates Slack instructions in prompt)
|
|
5696
|
-
let currentTaskSlackContext: BasePromptArgs["slackContext"] | undefined;
|
|
5697
|
-
|
|
5698
5791
|
// Generate base prompt (identity fields injected after profile fetch below).
|
|
5699
5792
|
// Traits are read fresh on each call so a live adapter swap (Section 4)
|
|
5700
|
-
// produces a prompt matching the new provider's capabilities.
|
|
5701
|
-
|
|
5793
|
+
// produces a prompt matching the new provider's capabilities. Per-task
|
|
5794
|
+
// context never enters it; see buildTaskSessionPrompts.
|
|
5795
|
+
const basePromptArgs = (): BasePromptArgs => {
|
|
5702
5796
|
const { traits } = adapter;
|
|
5703
|
-
return
|
|
5797
|
+
return {
|
|
5704
5798
|
role,
|
|
5705
5799
|
agentId,
|
|
5706
5800
|
capabilities,
|
|
@@ -5711,20 +5805,19 @@ export async function runAgent(config: RunnerConfig, opts: RunnerOptions) {
|
|
|
5711
5805
|
name: agentProfileName,
|
|
5712
5806
|
description: agentDescription,
|
|
5713
5807
|
// SOUL.md is the persona and travels to every provider. base-prompt
|
|
5714
|
-
// gates IDENTITY.md (inject when edited)
|
|
5715
|
-
// non-claude providers, inject when edited) itself.
|
|
5808
|
+
// gates IDENTITY.md and CLAUDE.md (inject when edited) itself.
|
|
5716
5809
|
soulMd: agentSoulMd,
|
|
5717
5810
|
identityMd: agentIdentityMd,
|
|
5718
5811
|
claudeMd: agentClaudeMd,
|
|
5719
|
-
repoContext: currentRepoContext,
|
|
5720
|
-
slackContext: currentTaskSlackContext,
|
|
5721
5812
|
...(traits.hasMcp && {
|
|
5722
5813
|
skillsSummary: agentSkillsSummary,
|
|
5723
5814
|
mcpServers: agentMcpServers,
|
|
5724
5815
|
}),
|
|
5725
|
-
}
|
|
5816
|
+
};
|
|
5726
5817
|
};
|
|
5727
5818
|
|
|
5819
|
+
const buildSystemPrompt = async () => getBasePrompt(basePromptArgs());
|
|
5820
|
+
|
|
5728
5821
|
let basePrompt = await buildSystemPrompt();
|
|
5729
5822
|
|
|
5730
5823
|
// Resolve additional system prompt: CLI flag > env var
|
|
@@ -6657,7 +6750,8 @@ export async function runAgent(config: RunnerConfig, opts: RunnerOptions) {
|
|
|
6657
6750
|
}
|
|
6658
6751
|
}
|
|
6659
6752
|
|
|
6660
|
-
|
|
6753
|
+
let resumeRepoContext: RepoContext | undefined;
|
|
6754
|
+
if (task.vcsRepo && apiUrl) {
|
|
6661
6755
|
const repoConfig = await fetchRepoConfig(apiUrl, apiKey, task.vcsRepo);
|
|
6662
6756
|
const effectiveConfig = repoConfig ?? {
|
|
6663
6757
|
url: task.vcsRepo,
|
|
@@ -6668,11 +6762,34 @@ export async function runAgent(config: RunnerConfig, opts: RunnerOptions) {
|
|
|
6668
6762
|
// This is the paused-task-resume-after-restart path — never a first
|
|
6669
6763
|
// kickoff, so never force-reset the base branch here.
|
|
6670
6764
|
const repoContext = await ensureRepoForTask(effectiveConfig, role, false);
|
|
6671
|
-
|
|
6765
|
+
resumeRepoContext = { ...repoContext, guidelines: repoConfig?.guidelines ?? null };
|
|
6766
|
+
if (!resumeCwd && repoContext?.clonePath) {
|
|
6672
6767
|
resumeCwd = repoContext.clonePath;
|
|
6673
6768
|
}
|
|
6674
6769
|
}
|
|
6675
6770
|
|
|
6771
|
+
// Same split as the main task path: stable system prompt, per-task
|
|
6772
|
+
// context (repository, scripts-only Slack note, requester profile,
|
|
6773
|
+
// cwd warning) in the task prompt.
|
|
6774
|
+
const resumeSlackChannelId = (task as { slackChannelId?: string }).slackChannelId;
|
|
6775
|
+
const resumeSession = await buildTaskSessionPrompts({
|
|
6776
|
+
basePromptArgs: basePromptArgs(),
|
|
6777
|
+
additionalSystemPrompt,
|
|
6778
|
+
taskPrompt: resumePrompt,
|
|
6779
|
+
context: {
|
|
6780
|
+
repoContext: resumeRepoContext,
|
|
6781
|
+
slackContext: resumeSlackChannelId
|
|
6782
|
+
? {
|
|
6783
|
+
channelId: resumeSlackChannelId,
|
|
6784
|
+
threadTs: (task as { slackThreadTs?: string }).slackThreadTs,
|
|
6785
|
+
}
|
|
6786
|
+
: undefined,
|
|
6787
|
+
requestedBy: task.requestedBy,
|
|
6788
|
+
cwdWarning: missingTaskDirWarning(task.dir, resumeCwd),
|
|
6789
|
+
},
|
|
6790
|
+
});
|
|
6791
|
+
resumePrompt = resumeSession.prompt;
|
|
6792
|
+
|
|
6676
6793
|
// Per-task runner session ID so session logs are scoped to this task
|
|
6677
6794
|
const resumeRunnerSessionId = crypto.randomUUID();
|
|
6678
6795
|
|
|
@@ -6693,7 +6810,7 @@ export async function runAgent(config: RunnerConfig, opts: RunnerOptions) {
|
|
|
6693
6810
|
{
|
|
6694
6811
|
prompt: resumePrompt,
|
|
6695
6812
|
logFile,
|
|
6696
|
-
systemPrompt:
|
|
6813
|
+
systemPrompt: resumeSession.systemPrompt,
|
|
6697
6814
|
additionalArgs: opts.additionalArgs,
|
|
6698
6815
|
// Native resume deprecated: always undefined. Follow-up continuity flows through
|
|
6699
6816
|
// the context preamble injected above (see context-preamble.ts).
|
|
@@ -6706,10 +6823,11 @@ export async function runAgent(config: RunnerConfig, opts: RunnerOptions) {
|
|
|
6706
6823
|
runnerSessionId: resumeRunnerSessionId,
|
|
6707
6824
|
iteration,
|
|
6708
6825
|
taskId: task.id,
|
|
6709
|
-
|
|
6826
|
+
// Only the task's own pin carries over. `model` and the claim
|
|
6827
|
+
// record describe the run before the pause; without a pin the
|
|
6828
|
+
// resumed run resolves from the agent's current config.
|
|
6829
|
+
...resumedTaskModelSelection(task as TaskModelPinFields),
|
|
6710
6830
|
modelTier: (task as { modelTier?: string }).modelTier,
|
|
6711
|
-
resolvedModel: (task as { resolvedModel?: string }).resolvedModel,
|
|
6712
|
-
modelSource: (task as { modelSource?: string }).modelSource,
|
|
6713
6831
|
effort: (task as { effort?: ReasoningEffort }).effort,
|
|
6714
6832
|
harnessProvider: state.harnessProvider,
|
|
6715
6833
|
cwd: resumeCwd,
|
|
@@ -7102,11 +7220,12 @@ export async function runAgent(config: RunnerConfig, opts: RunnerOptions) {
|
|
|
7102
7220
|
?.slackChannelId;
|
|
7103
7221
|
const taskSlackThreadTs = (trigger.task as { slackThreadTs?: string } | undefined)
|
|
7104
7222
|
?.slackThreadTs;
|
|
7105
|
-
|
|
7223
|
+
const taskSlackContext: SlackTaskContext | undefined = taskSlackChannelId
|
|
7106
7224
|
? { channelId: taskSlackChannelId, threadTs: taskSlackThreadTs }
|
|
7107
7225
|
: undefined;
|
|
7108
7226
|
|
|
7109
7227
|
// Handle repo context for tasks with vcsRepo (GitHub/GitLab)
|
|
7228
|
+
let currentRepoContext: RepoContext | undefined;
|
|
7110
7229
|
const taskVcsRepo = (trigger.task as { vcsRepo?: string } | undefined)?.vcsRepo;
|
|
7111
7230
|
if (taskVcsRepo && apiUrl) {
|
|
7112
7231
|
const repoConfig = await fetchRepoConfig(apiUrl, apiKey, taskVcsRepo);
|
|
@@ -7130,8 +7249,6 @@ export async function runAgent(config: RunnerConfig, opts: RunnerOptions) {
|
|
|
7130
7249
|
...repoResult,
|
|
7131
7250
|
guidelines: repoConfig?.guidelines ?? null,
|
|
7132
7251
|
};
|
|
7133
|
-
} else {
|
|
7134
|
-
currentRepoContext = undefined;
|
|
7135
7252
|
}
|
|
7136
7253
|
|
|
7137
7254
|
// Resolve effective working directory (priority: task.dir > repoContext.clonePath > process.cwd())
|
|
@@ -7170,11 +7287,8 @@ export async function runAgent(config: RunnerConfig, opts: RunnerOptions) {
|
|
|
7170
7287
|
" You can still access any path on the filesystem — this is just your starting directory.";
|
|
7171
7288
|
}
|
|
7172
7289
|
|
|
7173
|
-
// Warn
|
|
7174
|
-
|
|
7175
|
-
if (taskDir && !effectiveCwd) {
|
|
7176
|
-
cwdWarning = `\n\nNote: The task requested working directory "${taskDir}" but it does not exist. Falling back to default directory.`;
|
|
7177
|
-
}
|
|
7290
|
+
// Warn when task dir was specified but doesn't exist
|
|
7291
|
+
const cwdWarning = missingTaskDirWarning(taskDir, effectiveCwd);
|
|
7178
7292
|
|
|
7179
7293
|
// Per-task skill hot-reload. Reuses the boot-time helper; signature
|
|
7180
7294
|
// probe short-circuits when nothing changed. Skipped for
|
|
@@ -7220,13 +7334,21 @@ export async function runAgent(config: RunnerConfig, opts: RunnerOptions) {
|
|
|
7220
7334
|
);
|
|
7221
7335
|
}
|
|
7222
7336
|
|
|
7223
|
-
// Rebuild system prompt
|
|
7224
|
-
|
|
7225
|
-
const
|
|
7226
|
-
|
|
7227
|
-
|
|
7228
|
-
|
|
7229
|
-
|
|
7337
|
+
// Rebuild the system prompt (profile or skills may have changed) and
|
|
7338
|
+
// append the per-task context to the task prompt.
|
|
7339
|
+
const taskSession = await buildTaskSessionPrompts({
|
|
7340
|
+
basePromptArgs: basePromptArgs(),
|
|
7341
|
+
additionalSystemPrompt,
|
|
7342
|
+
taskPrompt: triggerPrompt,
|
|
7343
|
+
context: {
|
|
7344
|
+
repoContext: currentRepoContext,
|
|
7345
|
+
slackContext: taskSlackContext,
|
|
7346
|
+
requestedBy: trigger.requestedBy,
|
|
7347
|
+
cwdWarning,
|
|
7348
|
+
},
|
|
7349
|
+
});
|
|
7350
|
+
const taskSystemPrompt = taskSession.systemPrompt;
|
|
7351
|
+
triggerPrompt = taskSession.prompt;
|
|
7230
7352
|
|
|
7231
7353
|
// The server refused the task's explicit model for this worker's CLI
|
|
7232
7354
|
// version (harness_model_support = unsupported): fail fast, no spawn.
|
package/src/e2b/env.ts
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { resolve } from "node:path";
|
|
2
|
+
import { openAICompatibleBaseUrlVars, openAICompatibleKeyVars } from "@desplega/model-catalog";
|
|
2
3
|
import { getApiKey } from "../utils/api-key";
|
|
3
4
|
import { isSensitiveKey, scrubSecrets } from "../utils/secret-scrubber";
|
|
4
5
|
|
|
@@ -20,6 +21,10 @@ export const DEFAULT_E2B_FORWARD_KEYS = [
|
|
|
20
21
|
"CLAUDE_CODE_OAUTH_TOKEN",
|
|
21
22
|
"OPENAI_API_KEY",
|
|
22
23
|
"OPENROUTER_API_KEY",
|
|
24
|
+
// OpenAI-compatible registry providers: key vars (canonical and alias) and
|
|
25
|
+
// their `<ID>_BASE_URL` overrides.
|
|
26
|
+
...openAICompatibleKeyVars(),
|
|
27
|
+
...openAICompatibleBaseUrlVars(),
|
|
23
28
|
"GITHUB_TOKEN",
|
|
24
29
|
"GITLAB_TOKEN",
|
|
25
30
|
"AZURE_DEVOPS_TOKEN",
|
|
@@ -1,4 +1,21 @@
|
|
|
1
1
|
{
|
|
2
|
+
"comms-adherence-gate": {
|
|
3
|
+
"manifestFile": "manifest.json",
|
|
4
|
+
"manifest": {
|
|
5
|
+
"$schema": "../manifest.schema.json",
|
|
6
|
+
"name": "comms-adherence-gate",
|
|
7
|
+
"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",
|
|
8
|
+
"version": "1.5.0",
|
|
9
|
+
"runtime": "api",
|
|
10
|
+
"assets": {
|
|
11
|
+
"hooks": "hooks.ts"
|
|
12
|
+
}
|
|
13
|
+
},
|
|
14
|
+
"files": {
|
|
15
|
+
"hooks.ts": "import { type ApiCtx, block, type SwarmExtension } from \"swarm-extension\";\nimport { z } from \"zod\";\n\n/**\n * comms-adherence-gate\n *\n * When an agent calls `store-progress` with status \"completed\" on a task that\n * reaches a human in Slack, judge the `output` against the requesting user's\n * stored comms preferences (users.metadata.$.comms) with Jev, and against what\n * that user actually asked for in their latest message. An explicit request\n * (\"tell me a long joke\", \"explain in detail\") outranks the stored default, so\n * a requested long answer is not a verbosity failure.\n *\n * Transport (config.provider): Jev is served by TypeSafe directly and by\n * OpenRouter's Decisions endpoint. Both take the same {model, state, questions}\n * body and return the same `answers` shape. `auto` uses TYPESAFE_API_KEY when\n * set, else OPENROUTER_API_KEY.\n *\n * modes (config.mode):\n * off -> never evaluate\n * shadow -> evaluate + record, never block (default)\n * enforce -> record, and block the completion up to maxBlocksPerTask times\n * per task with actionable feedback\n *\n * Fails OPEN on every error: a judge outage must never wall the swarm.\n * Every verdict lands in KV namespace `ext:comms-gate`.\n */\n\nexport const config = z.object({\n mode: z.enum([\"off\", \"shadow\", \"enforce\"]).default(\"shadow\"),\n /** A dimension scoring below this fails. */\n threshold: z.number().min(0).max(1).default(0.5),\n timeoutMs: z.number().int().positive().default(4000),\n maxBlocksPerTask: z.number().int().min(0).default(1),\n provider: z.enum([\"auto\", \"typesafe\", \"openrouter\"]).default(\"auto\"),\n /** Jev model on the TypeSafe transport. */\n model: z.string().default(\"jev-latest\"),\n /** Jev model on the OpenRouter transport. */\n openrouterModel: z.string().default(\"typesafe/jev-1.13\"),\n /** Explicit TypeSafe key; otherwise TYPESAFE_API_KEY from the API process env. */\n apiKey: z.string().optional(),\n});\n\ntype Cfg = z.infer<typeof config>;\ntype Swarm = ApiCtx[\"swarm\"];\n\nconst NS = \"ext:comms-gate\";\nconst TYPESAFE_ENDPOINT = \"https://api.typesafe.ai/v1/systemone\";\nconst OPENROUTER_ENDPOINT = \"https://openrouter.ai/api/alpha/decisions\";\nconst UUID_RE = /^[0-9a-fA-F-]{36}$/;\n// The dispatcher cancels a handler after 5 s; leave room for the DB read and KV writes.\nconst MAX_TIMEOUT_MS = 4000;\n\nexport const cfgOf = (raw: unknown): Cfg => {\n const parsed = config.safeParse(raw ?? {});\n return parsed.success ? parsed.data : config.parse({});\n};\n\n// kv keys accept [a-zA-Z0-9._:/%-]\nconst safeKey = (raw: string | undefined | null): string =>\n (raw && raw.length > 0 ? raw : \"unknown\").replace(/[^a-zA-Z0-9._-]/g, \"_\").slice(0, 120);\n\n/** SDK envelopes: {success, data:{...}} or the bare payload. */\ntype Envelope = {\n data?: { rows?: unknown[]; value?: unknown };\n rows?: unknown[];\n value?: unknown;\n} | null;\n\nconst cell = (row: unknown, i: number, key: string): unknown =>\n Array.isArray(row) ? row[i] : (row as Record<string, unknown>)?.[key];\n\n/**\n * The newest human message sits at the END of a Slack task: the\n * `<thread_context>` blob comes first and can run to thousands of chars, so a\n * leading slice of `agent_tasks.task` shows the judge everything except the\n * actual request. Take the tail, then the part after the last `</thread_context>`.\n */\nconst CTX_END = \"</thread_context>\";\nexport const latestAsk = (tail: string): string => {\n const end = tail.lastIndexOf(CTX_END);\n let out = (end >= 0 ? tail.slice(end + CTX_END.length) : tail)\n .replace(/\\[Thread follow-up[^\\]]*\\]/g, \"\")\n .trim();\n // Some tasks end with the context block; fall back to the last messages in it.\n if (out.length < 8) out = (end >= 0 ? tail.slice(0, end) : tail).trim().slice(-800);\n return out.slice(0, 900);\n};\n\n// Preferences are the DEFAULT. A direct request outranks them.\nconst JUDGING_RULES = [\n \"The requester's explicit instruction in latest_request OUTRANKS requester_preferences. Preferences describe the DEFAULT; they never override a direct request.\",\n \"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.\",\n \"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.\",\n];\n\ntype Prefs = { verbosity?: unknown; tone?: unknown; language?: unknown } & Record<string, unknown>;\n\n/** One `noul` question per stated preference the gate knows how to judge. */\nexport const buildQuestions = (prefs: Prefs): Record<string, unknown> => {\n const questions: Record<string, unknown> = {};\n if (prefs.verbosity) {\n questions.verbosity = {\n type: \"noul\",\n instructions:\n \"Does agent_message respect the requester's verbosity preference, AS OVERRIDDEN by any explicit instruction in latest_request? Apply judging_rules.\",\n criteria: {\n 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.\",\n false:\n \"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.\",\n },\n };\n }\n if (prefs.tone) {\n questions.tone = {\n type: \"noul\",\n instructions:\n \"Does agent_message match the requester's stated tone preference, as overridden by any explicit instruction in latest_request? Apply judging_rules.\",\n criteria: {\n true: \"The tone matches what the requester asked for.\",\n false:\n \"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.\",\n },\n };\n }\n if (prefs.language) {\n questions.language = {\n type: \"noul\",\n instructions: \"Is agent_message written in the requester's stated language?\",\n criteria: {\n true: \"Written in the stated language.\",\n false: \"Written in a different language.\",\n },\n };\n }\n return questions;\n};\n\nconst secretCache = new Map<string, { value: string; at: number }>();\nconst KEY_TTL_MS = 5 * 60 * 1000;\nconst usable = (v: string): boolean => Boolean(v) && !v.startsWith(\"[REDACTED\");\nconst envOf = (name: string): string => String(process.env[name] ?? \"\");\n\n/**\n * Resolve a secret: explicit value, then the API process env. The API process loads\n * global swarm_config rows into process.env at boot and on config reload.\n * ctx.swarm.config_get cannot serve it: the SDK scrubs secrets to \"[REDACTED:<name>]\".\n */\nconst secret = (name: string, explicit?: string): string => {\n const cached = secretCache.get(name);\n if (cached && Date.now() - cached.at < KEY_TTL_MS) return cached.value;\n const value = [String(explicit ?? \"\"), envOf(name)].find(usable) ?? \"\";\n if (usable(value)) secretCache.set(name, { value, at: Date.now() });\n return value;\n};\n\n/** Test hook: forget cached secrets. */\nexport const resetKeyCache = (): void => {\n secretCache.clear();\n};\n\nexport type JevTransport = {\n provider: \"typesafe\" | \"openrouter\";\n endpoint: string;\n key: string;\n model: string;\n};\n\n/**\n * Pick the Jev transport. `auto` prefers TypeSafe and falls back to OpenRouter.\n * Throws when the chosen provider has no usable key; the handler fails open.\n */\nexport const resolveTransport = (cfg: Cfg): JevTransport => {\n const typesafe = (): JevTransport | null => {\n const key = secret(\"TYPESAFE_API_KEY\", cfg.apiKey);\n return usable(key)\n ? { provider: \"typesafe\", endpoint: TYPESAFE_ENDPOINT, key, model: cfg.model }\n : null;\n };\n const openrouter = (): JevTransport | null => {\n const key = secret(\"OPENROUTER_API_KEY\");\n return usable(key)\n ? { provider: \"openrouter\", endpoint: OPENROUTER_ENDPOINT, key, model: cfg.openrouterModel }\n : null;\n };\n const transport =\n cfg.provider === \"typesafe\"\n ? typesafe()\n : cfg.provider === \"openrouter\"\n ? openrouter()\n : (typesafe() ?? openrouter());\n if (transport) return transport;\n throw new Error(\n cfg.provider === \"typesafe\"\n ? \"no TYPESAFE_API_KEY\"\n : cfg.provider === \"openrouter\"\n ? \"no OPENROUTER_API_KEY\"\n : \"no TYPESAFE_API_KEY or OPENROUTER_API_KEY\",\n );\n};\n\n/** One Jev call; returns the `noul` score per question. Throws on any failure. */\nexport const askJev = async (\n state: Record<string, unknown>,\n questions: Record<string, unknown>,\n transport: JevTransport,\n cfg: Cfg,\n outer?: AbortSignal,\n): Promise<{ scores: Record<string, number>; latencyMs: number }> => {\n const controller = new AbortController();\n const timer = setTimeout(() => controller.abort(), Math.min(cfg.timeoutMs, MAX_TIMEOUT_MS));\n const onOuterAbort = () => controller.abort();\n outer?.addEventListener(\"abort\", onOuterAbort);\n const t0 = Date.now();\n try {\n const res = await fetch(transport.endpoint, {\n method: \"POST\",\n headers: { \"Content-Type\": \"application/json\", Authorization: `Bearer ${transport.key}` },\n body: JSON.stringify({ state, model: transport.model, questions }),\n signal: controller.signal,\n });\n if (!res.ok) throw new Error(`${transport.provider} ${res.status}`);\n const body = (await res.json()) as { answers?: Record<string, { noul?: unknown }> } | null;\n const answers = body?.answers;\n if (!answers || typeof answers !== \"object\") throw new Error(\"no answers in response\");\n const scores: Record<string, number> = {};\n for (const [id, a] of Object.entries(answers)) {\n if (typeof a?.noul === \"number\") scores[id] = a.noul;\n }\n if (Object.keys(scores).length === 0) throw new Error(\"no scores in response\");\n return { scores, latencyMs: Date.now() - t0 };\n } finally {\n clearTimeout(timer);\n outer?.removeEventListener(\"abort\", onOuterAbort);\n }\n};\n\nconst SELFTEST = \"comms-gate-selftest\";\nconst SELFTEST_PREFS = {\n tone: \"direct, plain, no hedging\",\n language: \"English\",\n verbosity:\n \"very terse - answer in the first sentence, ~400 chars max. No preamble, no recap, no closing offer.\",\n};\nconst SELFTEST_BAD =\n \"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!\";\nconst SELFTEST_GOOD =\n \"Went with option B: it is the only one that keeps the migration reversible. PR #123.\";\n\n/**\n * Run the judge on a known-bad (positive control, must score below threshold) and a\n * known-good (negative control, must score at or above it) message and record both\n * under `selftest:last`. Proves key, egress and KV from inside the API runtime\n * without touching real traffic.\n */\nconst selfTest = async (swarm: Swarm, cfg: Cfg, signal?: AbortSignal): Promise<void> => {\n const started = Date.now();\n try {\n const transport = resolveTransport(cfg);\n const questions = { verbosity: buildQuestions(SELFTEST_PREFS).verbosity };\n const judge = (message: string) =>\n askJev(\n { requester_preferences: SELFTEST_PREFS, agent_message: message },\n questions,\n transport,\n cfg,\n signal,\n ).then((r) => r.scores.verbosity ?? null);\n const [bad, good] = await Promise.all([judge(SELFTEST_BAD), judge(SELFTEST_GOOD)]);\n await swarm.kv_set({\n namespace: NS,\n key: \"selftest:last\",\n value: {\n at: new Date().toISOString(),\n provider: transport.provider,\n threshold: cfg.threshold,\n positiveControlBadScore: bad,\n negativeControlGoodScore: good,\n controlsOk:\n typeof bad === \"number\" &&\n typeof good === \"number\" &&\n bad < cfg.threshold &&\n good >= cfg.threshold,\n latencyMs: Date.now() - started,\n },\n });\n } catch (err) {\n await swarm\n .kv_set({\n namespace: NS,\n key: \"selftest:last\",\n value: {\n at: new Date().toISOString(),\n error: String(err).slice(0, 400),\n latencyMs: Date.now() - started,\n },\n })\n .catch(() => {});\n }\n};\n\nconst TASK_SQL = `SELECT t.source, t.slackThreadTs, t.agentId, t.requestedByUserId,\n json_extract(u.metadata, '$.comms') AS comms, u.name,\n substr(t.task, 1, 400) AS askHead,\n substr(t.task, -1400) AS askTail\nFROM agent_tasks t\nLEFT JOIN users u ON u.id = t.requestedByUserId\nWHERE t.id = ?`;\n\nconst LEAD_SQL = \"SELECT isLead FROM agents WHERE id = ?\";\n\n/**\n * Mirrors the `task.progress.write` rule store-progress enforces\n * (src/rbac/legacy-policy.ts): the caller owns the task, the task is\n * unassigned, or the caller is a lead. Lead status comes from the caller's\n * agents row, never from tool args. Extension callers return before this.\n */\nconst mayWriteProgress = async (\n swarm: Swarm,\n callerId: string | undefined,\n taskAgentId: string,\n): Promise<boolean> => {\n if (!callerId) return false;\n if (!taskAgentId || taskAgentId === callerId) return true;\n const q = (await swarm.db_query({ sql: LEAD_SQL, params: [callerId] })) as Envelope;\n const row = (q?.data?.rows ?? q?.rows ?? [])[0];\n return Number(cell(row, 0, \"isLead\") ?? 0) === 1;\n};\n\nconst extension: SwarmExtension = (api) => {\n api.on(\"pre.tool.call\", async (event, ctx) => {\n if (event.tool !== \"store-progress\") return;\n if (event.requestInfo?.callOrigin === \"extension\") return;\n\n const cfg = cfgOf(ctx.config);\n if (cfg.mode === \"off\") return;\n\n const args = (event.args ?? {}) as Record<string, unknown>;\n\n // Self-test hatch: a progress call whose text starts with the sentinel runs\n // the controls and records them under `selftest:last`.\n if (args.status !== \"completed\") {\n if (typeof args.progress === \"string\" && args.progress.startsWith(SELFTEST)) {\n await selfTest(ctx.swarm, cfg, ctx.signal);\n }\n return;\n }\n\n const output = typeof args.output === \"string\" ? args.output.trim() : \"\";\n if (!output) return;\n\n const taskId = String(args.taskId ?? event.requestInfo?.sourceTaskId ?? \"\");\n if (!UUID_RE.test(taskId)) return;\n\n try {\n const q = (await ctx.swarm.db_query({ sql: TASK_SQL, params: [taskId] })) as Envelope;\n const row = (q?.data?.rows ?? q?.rows ?? [])[0];\n if (!row) return;\n\n const source = String(cell(row, 0, \"source\") ?? \"\");\n const slackThreadTs = cell(row, 1, \"slackThreadTs\");\n const agentId = String(cell(row, 2, \"agentId\") ?? \"\");\n const requesterId = String(cell(row, 3, \"requestedByUserId\") ?? \"\");\n const commsRaw = cell(row, 4, \"comms\");\n const requesterName = String(cell(row, 5, \"name\") ?? requesterId);\n const threadOpening = String(cell(row, 6, \"askHead\") ?? \"\");\n const ask = latestAsk(String(cell(row, 7, \"askTail\") ?? \"\"));\n\n // Judge only a completion store-progress will accept, so no agent can ship\n // another task's thread and preferences to the external judge.\n if (!(await mayWriteProgress(ctx.swarm, event.requestInfo?.agentId, agentId))) return;\n\n // Only where the text actually reaches a human in Slack.\n if (source !== \"slack\" && !slackThreadTs) return;\n if (!commsRaw) return; // no stated preferences -> nothing to judge against\n const prefs = (typeof commsRaw === \"string\" ? JSON.parse(commsRaw) : commsRaw) as Prefs;\n if (!prefs || typeof prefs !== \"object\") return;\n\n const questions = buildQuestions(prefs);\n if (Object.keys(questions).length === 0) return;\n\n const transport = resolveTransport(cfg);\n const { scores, latencyMs } = await askJev(\n {\n role: \"You audit an AI agent's final message to the human who requested the work.\",\n requester: requesterName,\n requester_preferences: prefs,\n latest_request: ask,\n thread_opening: threadOpening,\n judging_rules: JUDGING_RULES,\n agent_message: output,\n },\n questions,\n transport,\n cfg,\n ctx.signal,\n );\n\n const failed = Object.entries(scores)\n .filter(([, v]) => v < cfg.threshold)\n .map(([k]) => k);\n\n const verdict = {\n at: new Date().toISOString(),\n taskId,\n agentId,\n source,\n requester: requesterName,\n requesterId,\n provider: transport.provider,\n scores,\n failed,\n threshold: cfg.threshold,\n mode: cfg.mode,\n latencyMs,\n preferences: prefs,\n askPreview: ask.slice(0, 400),\n outputPreview: output.slice(0, 700),\n };\n\n await ctx.swarm.kv_incr({ namespace: NS, key: \"total\" });\n await ctx.swarm.kv_set({ namespace: NS, key: \"last\", value: verdict });\n\n if (failed.length === 0) {\n await ctx.swarm.kv_incr({ namespace: NS, key: \"pass\" });\n return;\n }\n\n await ctx.swarm.kv_incr({ namespace: NS, key: \"fail\" });\n for (const dim of failed) {\n await ctx.swarm.kv_incr({ namespace: NS, key: `dim:${safeKey(dim)}` });\n }\n\n const blocksKey = `blocks:${safeKey(taskId)}`;\n // kv_get answers {success, data:{value}}, and {success:false} when absent.\n const prior = (await ctx.swarm.kv_get({ namespace: NS, key: blocksKey })) as Envelope;\n const priorBlocks = Number(prior?.data?.value ?? prior?.value ?? 0) || 0;\n const willBlock = cfg.mode === \"enforce\" && priorBlocks < cfg.maxBlocksPerTask;\n\n await ctx.swarm.kv_set({\n namespace: NS,\n key: `fail:${Date.now()}:${safeKey(taskId)}`,\n value: { ...verdict, action: willBlock ? \"blocked\" : \"recorded\" },\n });\n\n if (!willBlock) {\n await ctx.swarm.kv_incr({\n namespace: NS,\n key: cfg.mode === \"enforce\" ? \"escaped\" : \"shadowed\",\n });\n return;\n }\n\n await ctx.swarm.kv_incr({ namespace: NS, key: \"blocked\" });\n await ctx.swarm.kv_incr({ namespace: NS, key: blocksKey });\n\n const detail = failed\n .map(\n (d) =>\n `${d} (${scores[d]!.toFixed(2)} vs ${cfg.threshold}) — requested: ${String(prefs[d] ?? \"\").slice(0, 220)}`,\n )\n .join(\"\\n\");\n\n return block(\n [\n `Comms gate: this output does not match ${requesterName}'s stated preferences.`,\n detail,\n \"Rewrite the output to match and call store-progress again. Do not change the facts, only how they are said.\",\n ].join(\"\\n\"),\n );\n } catch (err) {\n // Fail open, always.\n try {\n await ctx.swarm.kv_incr({ namespace: NS, key: \"error\" });\n await ctx.swarm.kv_set({\n namespace: NS,\n key: \"error:last\",\n value: { at: new Date().toISOString(), taskId, error: String(err).slice(0, 400) },\n });\n } catch {\n /* ignore */\n }\n ctx.log.warn(\"comms-adherence-gate: failing open\", { error: String(err) });\n return;\n }\n });\n};\n\nexport default extension;\n"
|
|
16
|
+
},
|
|
17
|
+
"readme": "# comms-adherence-gate\n\nAgents finish Slack tasks with a final message a human reads. This extension judges that message against the requester's stated comms preferences and their latest request with Jev, [TypeSafe](https://typesafe.ai)'s decision model. In `enforce` it blocks a non-adherent completion once and tells the agent what to fix.\n\nInstall config:\n\n```json\n{ \"mode\": \"shadow\" }\n```\n\nStart in `shadow`, read the verdicts in KV for a few days, then switch to `enforce`.\n\n## Modes\n\n| `mode` | Effect |\n|---|---|\n| `off` | Never evaluates. |\n| `shadow` (default) | Asks Jev and records the verdict. Never blocks. |\n| `enforce` | Asks Jev, records the verdict, and blocks the `store-progress` call when a dimension fails, at most `maxBlocksPerTask` times per task. The agent gets the failed dimensions, their scores and the stated preference, and calls again. |\n\n## What it gates\n\nHook: `pre.tool.call` on `store-progress` with `status: \"completed\"` and a non-empty `output`. Calls made by extensions are skipped. The task must:\n\n1. Come from Slack (`source = \"slack\"` or a `slackThreadTs`). Other tasks pass without a Jev call.\n2. Have a requester with stated preferences in `users.metadata.comms`. A requester with none passes. Set them with the `manage-user` tool.\n\n## Decision\n\nThe gate reads the task row and asks one `noul` question per stated preference it knows: `verbosity`, `tone`, `language`. Any other key in `comms` is context only.\n\nJev sees the preferences, the requester's latest message (the text after the last `</thread_context>`, from the last 1,400 characters of the task), the first 400 characters of the task, and the output. Its judging rules say an explicit request outranks the stored default. A requested long answer, story or document is not a verbosity failure.\n\nA dimension fails when its score is below `threshold`. Any failed dimension fails the output.\n\n## Jev transport\n\nJev is reachable two ways. Both take the same request and return the same answer.\n\n| `provider` | Endpoint | Key | Model |\n|---|---|---|---|\n| `typesafe` | `POST https://api.typesafe.ai/v1/systemone` | `TYPESAFE_API_KEY` (or `apiKey`) | `model`, default `jev-latest` |\n| `openrouter` | `POST https://openrouter.ai/api/alpha/decisions` | `OPENROUTER_API_KEY` | `openrouterModel`, default `typesafe/jev-1.13` |\n| `auto` (default) | TypeSafe when its key is set, else OpenRouter | | |\n\nEach verdict row records the `provider` that answered.\n\n## Fail open\n\nEvery error lets the completion through: a missing key, an HTTP error, a network error, a timeout, a malformed answer, or a failed DB read. The handler never throws, so an outage cannot auto-disable the extension. Each failure bumps the `error` counter and overwrites `error:last`.\n\n## Self-test\n\nCall `store-progress` with a non-completed status and `progress` starting with `comms-gate-selftest`. The gate judges a known-bad and a known-good message against a terse preference and writes `selftest:last`: both scores, the threshold, the provider, and `controlsOk` (bad below threshold and good at or above it). Real traffic is untouched.\n\n## Config\n\n| Key | Default | Notes |\n|---|---|---|\n| `mode` | `shadow` | `off`, `shadow`, or `enforce` |\n| `threshold` | `0.5` | A dimension scoring below this fails |\n| `timeoutMs` | `4000` | Jev call timeout, capped at 4000 (the dispatcher cancels a handler at 5 s) |\n| `maxBlocksPerTask` | `1` | Blocks per task in `enforce`; after that the gate records and lets it through |\n| `provider` | `auto` | `auto`, `typesafe`, or `openrouter`. See [Jev transport](#jev-transport) |\n| `model` | `jev-latest` | Jev model on TypeSafe |\n| `openrouterModel` | `typesafe/jev-1.13` | Jev model on OpenRouter |\n| `apiKey` | unset | Explicit TypeSafe key. Prefer the `TYPESAFE_API_KEY` secret below |\n\n## Required secrets\n\nRead from the API process environment at call time. Set them as global `swarm_config` secrets: the API process loads global rows into `process.env` at boot and on config reload.\n\n- `TYPESAFE_API_KEY` or `OPENROUTER_API_KEY`: one is required for any evaluation. Under `auto`, TypeSafe wins when both are set. Without either, every completion fails open.\n\nNo key is written to config, KV, logs, or a block reason.\n\n## KV namespace `ext:comms-gate`\n\n- `last`: the latest verdict, with `scores`, `failed`, `provider`, `latencyMs`, the preferences, a 400-character `askPreview` and a 700-character `outputPreview`.\n- `fail:<epochMs>:<taskId>`: one row per failed verdict, with `action` (`blocked` or `recorded`).\n- `blocks:<taskId>`: blocks issued for that task.\n- `error:last`, `selftest:last`.\n- Counters: `total`, `pass`, `fail`, `blocked`, `escaped` (enforce, block budget spent), `shadowed`, `error`, and `dim:<dimension>`.\n\n## Known limits\n\n- Only the last 1,400 characters of the task are searched for the latest request. A very long final message is cut to its last 900 characters.\n- The block budget is per task, so a retried completion after a block goes through once the budget is spent, even if it still fails.\n"
|
|
18
|
+
},
|
|
2
19
|
"deploy-awareness": {
|
|
3
20
|
"manifestFile": "manifest.json",
|
|
4
21
|
"manifest": {
|