@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
|
@@ -653,9 +653,22 @@ export function createSwarmHooksExtension(config: SwarmHooksConfig): ExtensionFa
|
|
|
653
653
|
// === session_compact → PreCompact ===
|
|
654
654
|
// Re-anchor the model on its task once compaction has summarized the
|
|
655
655
|
// history. `context` fires before every LLM call, so it cannot carry this.
|
|
656
|
-
pi.on("session_compact", async () => {
|
|
656
|
+
pi.on("session_compact", async (event, ctx) => {
|
|
657
657
|
if (!config.taskId) return;
|
|
658
658
|
|
|
659
|
+
// The one real compaction signal: shaped like the claude runner's rows.
|
|
660
|
+
fireAndForget(`${config.apiUrl}/api/tasks/${config.taskId}/context`, {
|
|
661
|
+
method: "POST",
|
|
662
|
+
headers: apiHeaders(config),
|
|
663
|
+
body: JSON.stringify({
|
|
664
|
+
eventType: "compaction",
|
|
665
|
+
sessionId: `pi-${config.taskId}`,
|
|
666
|
+
compactTrigger: event.reason === "manual" ? "manual" : "auto",
|
|
667
|
+
preCompactTokens: event.compactionEntry?.tokensBefore,
|
|
668
|
+
contextTotalTokens: ctx.getContextUsage?.()?.contextWindow,
|
|
669
|
+
}),
|
|
670
|
+
});
|
|
671
|
+
|
|
659
672
|
const taskDetails = await fetchTaskDetails(config);
|
|
660
673
|
if (taskDetails) {
|
|
661
674
|
const reminder = await renderGoalReminder(taskDetails);
|
|
@@ -667,14 +680,15 @@ export function createSwarmHooksExtension(config: SwarmHooksConfig): ExtensionFa
|
|
|
667
680
|
pi.on("context", async (_event, ctx) => {
|
|
668
681
|
if (!config.taskId) return undefined;
|
|
669
682
|
|
|
670
|
-
//
|
|
683
|
+
// Per-call usage snapshot: "progress", as the claude/codex runner posts
|
|
684
|
+
// per-turn usage. Only session_compact reports a compaction.
|
|
671
685
|
const usage = ctx.getContextUsage?.();
|
|
672
686
|
if (usage) {
|
|
673
687
|
fireAndForget(`${config.apiUrl}/api/tasks/${config.taskId}/context`, {
|
|
674
688
|
method: "POST",
|
|
675
689
|
headers: apiHeaders(config),
|
|
676
690
|
body: JSON.stringify({
|
|
677
|
-
eventType: "
|
|
691
|
+
eventType: "progress",
|
|
678
692
|
sessionId: `pi-${config.taskId}`,
|
|
679
693
|
contextUsedTokens: usage.tokens ?? undefined,
|
|
680
694
|
contextTotalTokens: usage.contextWindow,
|
|
@@ -22,8 +22,8 @@ rate by hand should also update this file.
|
|
|
22
22
|
- **Runtime module**: `src/be/models-catalog.ts`; route in
|
|
23
23
|
`src/http/models-catalog.ts`.
|
|
24
24
|
- Every successful models.dev fetch in the runtime refresh above also updates
|
|
25
|
-
an in-memory slim catalog (openrouter / anthropic / openai / amazon-bedrock
|
|
26
|
-
only, picker-relevant fields only). The UI model picker
|
|
25
|
+
an in-memory slim catalog (openrouter / anthropic / openai / amazon-bedrock /
|
|
26
|
+
nan / helmcode only, picker-relevant fields only). The UI model picker
|
|
27
27
|
(`apps/ui/src/lib/agent-runtime-models.ts` via `useModelsCatalog()`) prefers
|
|
28
28
|
this over its build-time snapshot, so new models appear without a deploy.
|
|
29
29
|
- Pinned limited-availability entries (`PINNED_MODELSDEV_ENTRIES`) are
|
|
@@ -84,6 +84,25 @@ rate by hand should also update this file.
|
|
|
84
84
|
`inputTokens`, clamped to zero, so fresh input is priced separately.
|
|
85
85
|
Cursor's own models are not in models.dev: see
|
|
86
86
|
`CURSOR_FIRST_PARTY_PRICING` below.
|
|
87
|
+
- OpenAI-compatible registry providers: NaN models (models.dev `nan`
|
|
88
|
+
section) and Helmcode models (the registry-synthesized `helmcode`
|
|
89
|
+
section, below) → rows under `provider='pi'` and
|
|
90
|
+
`provider='opencode'`, keyed `nan/<id>` and `helmcode/<id>`, at $0 input
|
|
91
|
+
and $0 output. Both providers bill a flat rate per key, so a session's
|
|
92
|
+
marginal cost is $0. `buildModelsDevSeedRows()` projects them, so the rows
|
|
93
|
+
land at boot seed and on refresh, and sessions record $0 with
|
|
94
|
+
`costSource: "pricing-table"` rather than `unpriced`.
|
|
95
|
+
|
|
96
|
+
- **Registry-synthesized `helmcode` section**: Helmcode is not on models.dev.
|
|
97
|
+
Its five agent models (`deepseek-v4-flash`, `glm5.3`, `glm5.3-flash`,
|
|
98
|
+
`qwen3.6`, `gemma4`) live in the provider registry
|
|
99
|
+
(`packages/model-catalog/src/openai-compatible-providers.ts`,
|
|
100
|
+
`catalog: "registry"`). `withRegistryCatalogSections` adds the section,
|
|
101
|
+
at $0, to the snapshot loader (`loadModelsDevCache`), the worker runtime
|
|
102
|
+
catalog and the dashboard bundle, and the live catalog refills it
|
|
103
|
+
(`setLiveModelsCatalog`). Model limits are copied from Helmcode's own
|
|
104
|
+
opencode and pi config pages. Helmcode's resold Claude, GPT and Gemini
|
|
105
|
+
models are not included.
|
|
87
106
|
|
|
88
107
|
- **Snapshot refresh procedure**:
|
|
89
108
|
- Run `bun run scripts/refresh-modelsdev-pricing.ts` (Phase 2 — adds the
|
|
@@ -167,6 +186,18 @@ and heartbeat-classified roots are omitted, and the metrics remain separate.
|
|
|
167
186
|
scheduled increase to $3/M input and $15/M output on 2026-09-01. Claude
|
|
168
187
|
Code's bundled local rate table may be stale-high, so treat its reported USD
|
|
169
188
|
as advisory and use the server-recomputed row for the canonical total.
|
|
189
|
+
- **Claude 5.5 cache reads:** Opus 5.5 and Sonnet 5.5 cache hits cost 0.05x
|
|
190
|
+
input ($0.20/M and $0.10/M), not the usual 0.1x. Claude Code builds in use
|
|
191
|
+
around 2026-10-09 still priced Sonnet 5.5 hits at $0.20/M, so their
|
|
192
|
+
`harnessCostUsd` runs high by `cacheReadTokens × $0.10/M`.
|
|
193
|
+
- **Claude Haiku 5.5 context tier:** a request whose prompt (input + cache
|
|
194
|
+
reads + cache writes) is over 100k tokens bills every class at 5x: $0.50/M
|
|
195
|
+
input, $0.05/M cache hit, $0.625/M 5m write, $1/M 1h write, $2.50/M output.
|
|
196
|
+
It is the only Claude 5.x model with a long-context tier; the others bill the
|
|
197
|
+
full 1M window at standard rates. The recompute prices Haiku 5.5 at the base
|
|
198
|
+
tier, so sessions with over-100k requests under-count, and the harness total
|
|
199
|
+
is the closer figure for them. Same per-request accounting gap as GPT-5.6
|
|
200
|
+
below.
|
|
170
201
|
- **GPT-5.6 context tier:** above 272k context, the GPT-5.6 family bills
|
|
171
202
|
2× input/cache rates and 1.5× output rates. The current recompute receives
|
|
172
203
|
aggregate session token counts, not enough per-request context information
|
|
@@ -245,3 +245,40 @@ export function applyReasoningEffort(
|
|
|
245
245
|
}
|
|
246
246
|
}
|
|
247
247
|
}
|
|
248
|
+
|
|
249
|
+
const REASONING_HARNESSES: ReadonlySet<string> = new Set<ReasoningHarness>([
|
|
250
|
+
"claude",
|
|
251
|
+
"codex",
|
|
252
|
+
"pi",
|
|
253
|
+
"opencode",
|
|
254
|
+
"dsh",
|
|
255
|
+
"cursor",
|
|
256
|
+
"amp",
|
|
257
|
+
"grok",
|
|
258
|
+
]);
|
|
259
|
+
|
|
260
|
+
/**
|
|
261
|
+
* The levels a session on this harness and model accepts; empty for a harness
|
|
262
|
+
* this feature does not cover. Unlike `effectiveReasoningEffort`, a supported
|
|
263
|
+
* level with no transport effect (Opencode `off`) is still listed.
|
|
264
|
+
*/
|
|
265
|
+
export function supportedReasoningLevels(harness: string, model: string): ReasoningEffort[] {
|
|
266
|
+
if (!REASONING_HARNESSES.has(harness)) return [];
|
|
267
|
+
return reasoningCapability(harness as ReasoningHarness, model).levels;
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
/**
|
|
271
|
+
* The effort a session on this harness and model actually runs at: `level`
|
|
272
|
+
* when the adapter applies it, null when it is unset or dropped (the harness
|
|
273
|
+
* default applies). Same decision the adapters make in `applyReasoningEffort`.
|
|
274
|
+
*/
|
|
275
|
+
export function effectiveReasoningEffort(
|
|
276
|
+
harness: string,
|
|
277
|
+
model: string,
|
|
278
|
+
level: ReasoningEffort | undefined,
|
|
279
|
+
): ReasoningEffort | null {
|
|
280
|
+
if (!level || !REASONING_HARNESSES.has(harness)) return null;
|
|
281
|
+
return applyReasoningEffort(harness as ReasoningHarness, model, level).kind === "noop"
|
|
282
|
+
? null
|
|
283
|
+
: level;
|
|
284
|
+
}
|
package/src/providers/types.ts
CHANGED
|
@@ -33,7 +33,11 @@ export interface CostData {
|
|
|
33
33
|
cacheWrite1hTokens?: number;
|
|
34
34
|
/** Migration 063: codex reasoning_output_tokens (and similar) for reasoning models. */
|
|
35
35
|
reasoningOutputTokens?: number;
|
|
36
|
-
/**
|
|
36
|
+
/**
|
|
37
|
+
* Migration 063: claude extended-thinking tokens from the result line's
|
|
38
|
+
* `usage.output_tokens_details.thinking_tokens`. Informational: they are a
|
|
39
|
+
* subset of `outputTokens` and already priced there.
|
|
40
|
+
*/
|
|
37
41
|
thinkingTokens?: number;
|
|
38
42
|
/** Complete per-model usage, including sidechain/subagent tokens when available. */
|
|
39
43
|
models?: CostModelUsage[];
|
|
@@ -263,7 +267,7 @@ export interface ProviderTraits {
|
|
|
263
267
|
* agent never learns they exist. Defaults to true when omitted.
|
|
264
268
|
*/
|
|
265
269
|
nativeSkillDiscovery?: boolean;
|
|
266
|
-
/** Provider runs in the local Docker container with /workspace, identity files, agent-fs,
|
|
270
|
+
/** Provider runs in the local Docker container with /workspace, identity files, agent-fs, etc. */
|
|
267
271
|
hasLocalEnvironment: boolean;
|
|
268
272
|
/** Live steering modes supported by this provider. Absent means none. */
|
|
269
273
|
steerModes?: SteerMode[];
|
|
@@ -49,6 +49,13 @@ const operatorOrUser: LegacyRule = {
|
|
|
49
49
|
evaluate: (principal) => principal.kind === "operator" || principal.kind === "user",
|
|
50
50
|
};
|
|
51
51
|
|
|
52
|
+
/** The shared API key only: swarm-wide settings that no session user or agent may change. */
|
|
53
|
+
const operatorOnly: LegacyRule = {
|
|
54
|
+
name: "operator-only",
|
|
55
|
+
denyReason: "requires operator authentication",
|
|
56
|
+
evaluate: (principal) => principal.kind === "operator",
|
|
57
|
+
};
|
|
58
|
+
|
|
52
59
|
const leadOrOperatorOrUser: LegacyRule = {
|
|
53
60
|
name: "lead-or-operator-or-user",
|
|
54
61
|
denyReason: "requires lead agent, operator, or user authentication",
|
|
@@ -206,8 +213,9 @@ const memoryDelete: LegacyRule = {
|
|
|
206
213
|
};
|
|
207
214
|
|
|
208
215
|
/**
|
|
209
|
-
* task.fs.mutate
|
|
210
|
-
* (src/http/fs.ts canMutateTask
|
|
216
|
+
* task.fs.mutate and task.parent.attach: operator OR user OR lead OR
|
|
217
|
+
* task-assignee OR task-creator (src/http/fs.ts canMutateTask; send-task and
|
|
218
|
+
* POST /api/tasks for a parentTaskId). Order-independent OR: today's early
|
|
211
219
|
* operator/user returns short-circuit before agent identity, but no input
|
|
212
220
|
* satisfies one branch while failing another, so a flat OR is equivalent
|
|
213
221
|
* (plan Appendix A row 36 note).
|
|
@@ -276,6 +284,7 @@ export const LEGACY_POLICY = {
|
|
|
276
284
|
"task.steer.own": requesterOwnsTask,
|
|
277
285
|
"task.action.own": requesterOwnsTask,
|
|
278
286
|
"task.fs.mutate": taskFsMutate,
|
|
287
|
+
"task.parent.attach": taskFsMutate,
|
|
279
288
|
"task.progress.write": taskProgressWrite,
|
|
280
289
|
"favorite.write.own": anyAuthenticated,
|
|
281
290
|
"memory.learning.inject": leadOnly,
|
|
@@ -286,6 +295,10 @@ export const LEGACY_POLICY = {
|
|
|
286
295
|
"channel.delete": leadOnly,
|
|
287
296
|
"repo.merge-policy.write": leadOrOperatorOrUser,
|
|
288
297
|
"integration.kapso.manage": leadOnly,
|
|
298
|
+
"integration.chat.manage": leadOnly,
|
|
299
|
+
"integration.chat.post": leadOnly,
|
|
300
|
+
"integration.chat.read": leadOnly,
|
|
301
|
+
"integration.chat.dm": leadOnly,
|
|
289
302
|
"integration.slack.post": leadOnly,
|
|
290
303
|
"integration.slack.read": leadOnly,
|
|
291
304
|
"integration.slack.thread.start": leadOnly,
|
|
@@ -307,6 +320,7 @@ export const LEGACY_POLICY = {
|
|
|
307
320
|
// Lead tokens retain config.read.secrets. This exception does not grant config administration.
|
|
308
321
|
"models.catalog.write": leadOrOperator,
|
|
309
322
|
"models.harness-support.write": agentOrOperator,
|
|
323
|
+
"models.profiles.write": operatorOnly,
|
|
310
324
|
"skill.create.swarm": leadOnly,
|
|
311
325
|
"skill.install.any": leadOnly,
|
|
312
326
|
"skill.install.global": leadOnly,
|
package/src/rbac/permissions.ts
CHANGED
|
@@ -79,6 +79,11 @@ export const PERMISSIONS = {
|
|
|
79
79
|
description: "Mutate a task's filesystem artifacts and attachments.",
|
|
80
80
|
namespace: "task",
|
|
81
81
|
},
|
|
82
|
+
"task.parent.attach": {
|
|
83
|
+
description:
|
|
84
|
+
"Create a task under an existing parent task. The child inherits the parent's thread context (contextKey, Slack thread, requester).",
|
|
85
|
+
namespace: "task",
|
|
86
|
+
},
|
|
82
87
|
"task.progress.write": {
|
|
83
88
|
description:
|
|
84
89
|
"Write a task's progress, status, or output (its assignee, a lead, a human, or anyone while unassigned).",
|
|
@@ -124,6 +129,22 @@ export const PERMISSIONS = {
|
|
|
124
129
|
description: "Register or unregister a Kapso inbound number.",
|
|
125
130
|
namespace: "integration",
|
|
126
131
|
},
|
|
132
|
+
"integration.chat.manage": {
|
|
133
|
+
description: "Register or unregister a Chat SDK channel route.",
|
|
134
|
+
namespace: "integration",
|
|
135
|
+
},
|
|
136
|
+
"integration.chat.post": {
|
|
137
|
+
description: "Post to a Chat SDK thread or channel other than the thread of the current task.",
|
|
138
|
+
namespace: "integration",
|
|
139
|
+
},
|
|
140
|
+
"integration.chat.read": {
|
|
141
|
+
description: "Read a Chat SDK thread other than the thread of the current task.",
|
|
142
|
+
namespace: "integration",
|
|
143
|
+
},
|
|
144
|
+
"integration.chat.dm": {
|
|
145
|
+
description: "Open a direct-message thread with a user on a Chat SDK adapter.",
|
|
146
|
+
namespace: "integration",
|
|
147
|
+
},
|
|
127
148
|
"integration.slack.post": {
|
|
128
149
|
description: "Post to a direct Slack channel.",
|
|
129
150
|
namespace: "integration",
|
|
@@ -198,6 +219,11 @@ export const PERMISSIONS = {
|
|
|
198
219
|
"Record whether a pinned harness CLI version accepts a model (a worker reports its own probe results; the operator may correct any row).",
|
|
199
220
|
namespace: "models",
|
|
200
221
|
},
|
|
222
|
+
"models.profiles.write": {
|
|
223
|
+
description:
|
|
224
|
+
"Create, update, or delete model profiles (named harness + model + effort). Operator (shared API key) only.",
|
|
225
|
+
namespace: "models",
|
|
226
|
+
},
|
|
201
227
|
"config.read.secrets": {
|
|
202
228
|
description: "Read unmasked secret config values.",
|
|
203
229
|
namespace: "config",
|
|
@@ -98,13 +98,12 @@ async function prepareDeferredWakeTask(
|
|
|
98
98
|
*/
|
|
99
99
|
export const DEFERRED_WAIT_WOKE_EVENT = "task_deferred_wait_woke";
|
|
100
100
|
|
|
101
|
-
/** Undefined means a normal schedule; an empty
|
|
102
|
-
export async function
|
|
101
|
+
/** Undefined means a normal schedule; an empty preparation means the wait is not ready. */
|
|
102
|
+
export async function prepareDeferredTaskWait(
|
|
103
103
|
scheduleId: string,
|
|
104
104
|
reason: WakeReason,
|
|
105
105
|
extraTags: string[] = [],
|
|
106
|
-
|
|
107
|
-
): Promise<{ task?: AgentTask } | undefined> {
|
|
106
|
+
): Promise<{ task?: PreparedTaskCreate } | undefined> {
|
|
108
107
|
const client = getDbClient();
|
|
109
108
|
// Keep ordinary schedule dispatch off the write-lock path.
|
|
110
109
|
if (
|
|
@@ -117,8 +116,22 @@ export async function dispatchDeferredTaskWait(
|
|
|
117
116
|
// first; the transaction below re-reads the wait and its member set before claiming.
|
|
118
117
|
// The prepared description carries the cause seen here; for an `any` wait a member
|
|
119
118
|
// that settles between this read and the claim is missing from that text only.
|
|
120
|
-
|
|
119
|
+
return { task: await prepareDeferredWakeTask(scheduleId, reason, extraTags) };
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
/** Undefined means a normal schedule; an empty result means another caller won. */
|
|
123
|
+
export async function dispatchDeferredTaskWait(
|
|
124
|
+
scheduleId: string,
|
|
125
|
+
reason: WakeReason,
|
|
126
|
+
extraTags: string[] = [],
|
|
127
|
+
triggerTaskId?: string,
|
|
128
|
+
prepared?: { task?: PreparedTaskCreate },
|
|
129
|
+
): Promise<{ task?: AgentTask } | undefined> {
|
|
130
|
+
const preparation = prepared ?? (await prepareDeferredTaskWait(scheduleId, reason, extraTags));
|
|
131
|
+
if (!preparation) return undefined;
|
|
132
|
+
const preparedForWait = preparation.task;
|
|
121
133
|
if (!preparedForWait) return {};
|
|
134
|
+
const client = getDbClient();
|
|
122
135
|
return client.transaction(async () => {
|
|
123
136
|
const resolved = await resolveWaitCause(scheduleId, reason);
|
|
124
137
|
if (!resolved) return {};
|
|
@@ -23,7 +23,7 @@ import {
|
|
|
23
23
|
getScriptApiConnectionDescriptors,
|
|
24
24
|
getScriptMcpConnectionDescriptors,
|
|
25
25
|
} from "@/be/script-connections";
|
|
26
|
-
import {
|
|
26
|
+
import { buildScriptCredentialBindingsWithFailures } from "@/be/script-credential-broker";
|
|
27
27
|
import { getScript } from "@/be/scripts/db";
|
|
28
28
|
import { runScript } from "@/scripts-runtime/loader";
|
|
29
29
|
import { telemetry } from "@/telemetry";
|
|
@@ -38,6 +38,7 @@ import { scrubObject, scrubSecrets } from "../utils/secret-scrubber";
|
|
|
38
38
|
import {
|
|
39
39
|
dispatchDeferredTaskWait,
|
|
40
40
|
initDeferredTaskWaits,
|
|
41
|
+
prepareDeferredTaskWait,
|
|
41
42
|
reapSpentDeferredSchedules,
|
|
42
43
|
reconcileDeferredTaskWaits,
|
|
43
44
|
stopDeferredTaskWaits,
|
|
@@ -99,12 +100,14 @@ async function executeScheduleScript(schedule: ScheduledTask): Promise<void> {
|
|
|
99
100
|
|
|
100
101
|
let output: Awaited<ReturnType<typeof runScript>>;
|
|
101
102
|
try {
|
|
103
|
+
const credentials = await buildScriptCredentialBindingsWithFailures({ agentId });
|
|
102
104
|
output = await runScript({
|
|
103
105
|
source: script.source,
|
|
104
106
|
args,
|
|
105
107
|
fsMode: "none",
|
|
106
108
|
agentId,
|
|
107
|
-
egressSecrets:
|
|
109
|
+
egressSecrets: credentials.egressSecrets,
|
|
110
|
+
userConfig: credentials.userConfig,
|
|
108
111
|
apiConnections: getScriptApiConnectionDescriptors({ agentId }),
|
|
109
112
|
mcpConnections: getScriptMcpConnectionDescriptors({ agentId }),
|
|
110
113
|
timeoutMs: 60_000,
|
|
@@ -301,6 +304,9 @@ async function throwIfWorkflowNeedsSetup(scheduleId: string, runIds: string[]):
|
|
|
301
304
|
* recovery pass, or schedule edit invalidates it before any target is dispatched.
|
|
302
305
|
*/
|
|
303
306
|
async function claimScheduleOccurrence(snapshot: ScheduledTask, extraTags: string[]) {
|
|
307
|
+
// Run extension pre-hooks before opening the claim transaction. The snapshot
|
|
308
|
+
// guard below still prevents a moved ceiling from consuming this preparation.
|
|
309
|
+
const preparedWait = await prepareDeferredTaskWait(snapshot.id, "ceiling", extraTags);
|
|
304
310
|
return getDbClient().transaction(async () => {
|
|
305
311
|
const current = await getScheduledTaskById(snapshot.id);
|
|
306
312
|
if (!current?.enabled || !current.nextRunAt || current.nextRunAt !== snapshot.nextRunAt) {
|
|
@@ -311,7 +317,9 @@ async function claimScheduleOccurrence(snapshot: ScheduledTask, extraTags: strin
|
|
|
311
317
|
// Event-driven waits already atomically create their child and consume the
|
|
312
318
|
// ceiling. Keep that transaction (and its afterCommit emits) intact: disabling
|
|
313
319
|
// a one-time schedule first would prevent its wake-up task from being created.
|
|
314
|
-
const deferred =
|
|
320
|
+
const deferred = preparedWait
|
|
321
|
+
? await dispatchDeferredTaskWait(schedule.id, "ceiling", extraTags, undefined, preparedWait)
|
|
322
|
+
: undefined;
|
|
315
323
|
if (deferred) {
|
|
316
324
|
return {
|
|
317
325
|
schedule,
|
|
@@ -5,11 +5,14 @@
|
|
|
5
5
|
*
|
|
6
6
|
* - `__host_call(path, argsJson)`: async host call, resolves to a JSON envelope.
|
|
7
7
|
* - `__host_sync(op, json)`: sync host call, returns a JSON envelope.
|
|
8
|
+
* `crypto.random` returns that many random bytes.
|
|
8
9
|
* - `__host_log(stream, text)`: append to the captured stdout or stderr.
|
|
9
10
|
* - `__host_sleep(ms)`: resolves after `ms` on the host clock.
|
|
10
11
|
* - `__host_done(json)`: reports the final outcome of the run.
|
|
11
12
|
*
|
|
12
13
|
* Every value crosses the boundary as JSON. The sandbox has no other way out.
|
|
14
|
+
* `SANDBOX_WEB_SHIMS` (quickjs-web-shims.ts) runs first and adds URL,
|
|
15
|
+
* URLSearchParams, TextEncoder and TextDecoder.
|
|
13
16
|
* `__swarm_shape` (injected before this file) lists the configured api/mcp
|
|
14
17
|
* connections so `ctx.api.<slug>` matches the native executor exactly.
|
|
15
18
|
*/
|
|
@@ -64,6 +67,39 @@ const setTimeout = (fn, ms, ...args) => {
|
|
|
64
67
|
const clearTimeout = (id) => {
|
|
65
68
|
timers.delete(id);
|
|
66
69
|
};
|
|
70
|
+
// Shares the timer ids with setTimeout, so either clear function stops either.
|
|
71
|
+
const setInterval = (fn, ms, ...args) => {
|
|
72
|
+
const id = ++timerSeq;
|
|
73
|
+
const delay = Math.max(1, Number(ms) || 0);
|
|
74
|
+
timers.add(id);
|
|
75
|
+
const tick = () =>
|
|
76
|
+
__host_sleep(delay).then(() => {
|
|
77
|
+
if (!timers.has(id)) return;
|
|
78
|
+
tick();
|
|
79
|
+
fn(...args);
|
|
80
|
+
});
|
|
81
|
+
tick();
|
|
82
|
+
return id;
|
|
83
|
+
};
|
|
84
|
+
const clearInterval = clearTimeout;
|
|
85
|
+
|
|
86
|
+
// ── crypto (bytes come from the host CSPRNG) ──
|
|
87
|
+
const INTEGER_ARRAYS = [Int8Array, Uint8Array, Uint8ClampedArray, Int16Array, Uint16Array, Int32Array, Uint32Array, BigInt64Array, BigUint64Array];
|
|
88
|
+
const getRandomValues = (array) => {
|
|
89
|
+
if (!INTEGER_ARRAYS.some((type) => array instanceof type)) {
|
|
90
|
+
throw new TypeError("crypto.getRandomValues: the argument must be an integer TypedArray");
|
|
91
|
+
}
|
|
92
|
+
new Uint8Array(array.buffer, array.byteOffset, array.byteLength).set(hostSync("crypto.random", array.byteLength));
|
|
93
|
+
return array;
|
|
94
|
+
};
|
|
95
|
+
const randomUUID = () => {
|
|
96
|
+
const bytes = hostSync("crypto.random", 16);
|
|
97
|
+
bytes[6] = (bytes[6] & 0x0f) | 0x40;
|
|
98
|
+
bytes[8] = (bytes[8] & 0x3f) | 0x80;
|
|
99
|
+
const hex = bytes.map((b) => (b + 256).toString(16).slice(1)).join("");
|
|
100
|
+
return hex.slice(0, 8) + "-" + hex.slice(8, 12) + "-" + hex.slice(12, 16) + "-" + hex.slice(16, 20) + "-" + hex.slice(20);
|
|
101
|
+
};
|
|
102
|
+
const crypto = Object.freeze({ getRandomValues, randomUUID });
|
|
67
103
|
|
|
68
104
|
// ── Redacted ──
|
|
69
105
|
const redactedEntries = new WeakMap();
|
|
@@ -115,7 +151,7 @@ const config = Object.freeze({
|
|
|
115
151
|
},
|
|
116
152
|
});
|
|
117
153
|
|
|
118
|
-
// ── fetch
|
|
154
|
+
// ── fetch ──
|
|
119
155
|
class Headers {
|
|
120
156
|
constructor(init) {
|
|
121
157
|
this._map = new Map();
|
|
@@ -165,14 +201,44 @@ class Response {
|
|
|
165
201
|
return JSON.parse(this._body);
|
|
166
202
|
}
|
|
167
203
|
}
|
|
204
|
+
const BASE64 = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
|
|
205
|
+
const toBase64 = (bytes) => {
|
|
206
|
+
let out = "";
|
|
207
|
+
let i = 0;
|
|
208
|
+
for (; i + 2 < bytes.length; i += 3) {
|
|
209
|
+
const n = (bytes[i] << 16) | (bytes[i + 1] << 8) | bytes[i + 2];
|
|
210
|
+
out += BASE64[n >> 18] + BASE64[(n >> 12) & 63] + BASE64[(n >> 6) & 63] + BASE64[n & 63];
|
|
211
|
+
}
|
|
212
|
+
if (i < bytes.length) {
|
|
213
|
+
const n = (bytes[i] << 16) | ((bytes[i + 1] || 0) << 8);
|
|
214
|
+
out += BASE64[n >> 18] + BASE64[(n >> 12) & 63] + (i + 1 < bytes.length ? BASE64[(n >> 6) & 63] : "=") + "=";
|
|
215
|
+
}
|
|
216
|
+
return out;
|
|
217
|
+
};
|
|
218
|
+
// Binary bodies cross the host boundary as base64; the host decodes them.
|
|
219
|
+
const encodeBody = (body, headers) => {
|
|
220
|
+
if (body == null) return {};
|
|
221
|
+
if (typeof body === "string") return { body };
|
|
222
|
+
if (body instanceof globalThis.URLSearchParams) {
|
|
223
|
+
if (!headers.has("content-type")) headers.set("content-type", "application/x-www-form-urlencoded;charset=UTF-8");
|
|
224
|
+
return { body: body.toString() };
|
|
225
|
+
}
|
|
226
|
+
if (body instanceof ArrayBuffer) return { bodyBase64: toBase64(new Uint8Array(body)) };
|
|
227
|
+
if (ArrayBuffer.isView(body)) {
|
|
228
|
+
return { bodyBase64: toBase64(new Uint8Array(body.buffer, body.byteOffset, body.byteLength)) };
|
|
229
|
+
}
|
|
230
|
+
throw new TypeError(
|
|
231
|
+
"quickjs executor: fetch body must be a string, Uint8Array, ArrayBuffer or URLSearchParams (JSON.stringify objects first)",
|
|
232
|
+
);
|
|
233
|
+
};
|
|
168
234
|
const toRequest = (input, init) => {
|
|
169
235
|
const options = init || {};
|
|
170
236
|
const url = typeof input === "string" ? input : input && (input.href || input.url) ? input.href || input.url : String(input);
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
const headers = options.headers == null ? undefined :
|
|
175
|
-
const request = { url, method: options.method, headers, body
|
|
237
|
+
const headerMap = new Headers(options.headers);
|
|
238
|
+
const body = encodeBody(options.body, headerMap);
|
|
239
|
+
const headerList = Array.from(headerMap.entries());
|
|
240
|
+
const headers = options.headers == null && headerList.length === 0 ? undefined : headerList;
|
|
241
|
+
const request = { url, method: options.method, headers, ...body };
|
|
176
242
|
if (options.retries !== undefined) request.retries = options.retries;
|
|
177
243
|
if (options.timeoutMs !== undefined) request.timeoutMs = options.timeoutMs;
|
|
178
244
|
return request;
|
|
@@ -223,6 +289,9 @@ const define = (name, value) =>
|
|
|
223
289
|
define("console", console);
|
|
224
290
|
define("setTimeout", setTimeout);
|
|
225
291
|
define("clearTimeout", clearTimeout);
|
|
292
|
+
define("setInterval", setInterval);
|
|
293
|
+
define("clearInterval", clearInterval);
|
|
294
|
+
define("crypto", crypto);
|
|
226
295
|
define("fetch", fetch);
|
|
227
296
|
define("Headers", Headers);
|
|
228
297
|
define("Response", Response);
|
|
@@ -13,6 +13,7 @@ import { table } from "../stdlib/table";
|
|
|
13
13
|
import { SwarmConfig } from "../swarm-config";
|
|
14
14
|
import { bundleForQuickJS, USER_SCRIPT_FILE } from "./quickjs-bundle";
|
|
15
15
|
import { SANDBOX_EPILOGUE, SANDBOX_PRELUDE } from "./quickjs-prelude";
|
|
16
|
+
import { SANDBOX_WEB_SHIMS } from "./quickjs-web-shims";
|
|
16
17
|
import type { SourceMapReader } from "./sourcemap";
|
|
17
18
|
import type {
|
|
18
19
|
ExecutorOutput,
|
|
@@ -37,6 +38,13 @@ const BUNDLE_FILE = USER_SCRIPT_FILE.replace(/\.ts$/, ".js");
|
|
|
37
38
|
const MAX_CONCURRENT_HOST_CALLS = 16;
|
|
38
39
|
// Host calls and timers that one run can have in flight or queued.
|
|
39
40
|
const MAX_PENDING_HOST_CALLS = 1024;
|
|
41
|
+
// A binary fetch body crosses the bridge as base64 and is decoded on the host.
|
|
42
|
+
// One body decodes to at most this many bytes, and the binary bodies a run has
|
|
43
|
+
// queued or in flight total at most MAX_PENDING_BINARY_BODY_BYTES. Both are
|
|
44
|
+
// checked before decoding, so a rejected request never reaches fetch.
|
|
45
|
+
const MAX_BINARY_BODY_BYTES = 16 * 1024 * 1024;
|
|
46
|
+
const MAX_BINARY_BODY_BASE64_LENGTH = Math.ceil(MAX_BINARY_BODY_BYTES / 3) * 4;
|
|
47
|
+
const MAX_PENDING_BINARY_BODY_BYTES = 64 * 1024 * 1024;
|
|
40
48
|
// After a run ends, its host calls are aborted. Wait this long for them to settle.
|
|
41
49
|
const HOST_CALL_SETTLE_MS = 1_000;
|
|
42
50
|
|
|
@@ -46,6 +54,9 @@ export type QuickJSJobResult = {
|
|
|
46
54
|
leakedHostCalls: number;
|
|
47
55
|
};
|
|
48
56
|
const MAX_STACK_BYTES = 1024 * 1024;
|
|
57
|
+
// Bun's getRandomValues has no 64 KiB quota, so neither does the sandbox. This
|
|
58
|
+
// bound only keeps one sync host call's JSON reply small.
|
|
59
|
+
const MAX_RANDOM_BYTES = 1024 * 1024;
|
|
49
60
|
const TIMEOUT_EXIT_CODE = 124;
|
|
50
61
|
|
|
51
62
|
type ErrorPayload = { name: string; message: string; stack: string };
|
|
@@ -126,12 +137,51 @@ type SandboxRequest = {
|
|
|
126
137
|
method?: string;
|
|
127
138
|
headers?: [string, string][];
|
|
128
139
|
body?: string;
|
|
140
|
+
/** Binary body, base64-encoded by the sandbox. */
|
|
141
|
+
bodyBase64?: string;
|
|
129
142
|
retries?: number;
|
|
130
143
|
timeoutMs?: number;
|
|
131
144
|
};
|
|
132
145
|
|
|
146
|
+
const FETCH_PATHS = new Set(["fetch", "stdlib.fetch", "stdlib.fetchJson"]);
|
|
147
|
+
const BASE64_RE = /^[A-Za-z0-9+/]*={0,2}$/;
|
|
148
|
+
|
|
149
|
+
/**
|
|
150
|
+
* Checks a sandbox fetch request's base64 body without decoding it and returns
|
|
151
|
+
* its decoded size (0 when there is none). The sandbox controls the value, so
|
|
152
|
+
* the type, alphabet and size are checked before the host allocates anything.
|
|
153
|
+
*/
|
|
154
|
+
function binaryBodySize(request: unknown): number {
|
|
155
|
+
if (!request || typeof request !== "object") return 0;
|
|
156
|
+
const encoded = (request as { bodyBase64?: unknown }).bodyBase64;
|
|
157
|
+
if (encoded === undefined) return 0;
|
|
158
|
+
if (typeof encoded !== "string") throw new TypeError("fetch bodyBase64 must be a string");
|
|
159
|
+
if (encoded.length > MAX_BINARY_BODY_BASE64_LENGTH) {
|
|
160
|
+
throw new RangeError(
|
|
161
|
+
`fetch binary body exceeds the ${MAX_BINARY_BODY_BYTES}-byte limit of the quickjs executor`,
|
|
162
|
+
);
|
|
163
|
+
}
|
|
164
|
+
if (encoded.length % 4 !== 0 || !BASE64_RE.test(encoded)) {
|
|
165
|
+
throw new TypeError("fetch bodyBase64 is not valid base64");
|
|
166
|
+
}
|
|
167
|
+
const padding = encoded.endsWith("==") ? 2 : encoded.endsWith("=") ? 1 : 0;
|
|
168
|
+
const size = (encoded.length / 4) * 3 - padding;
|
|
169
|
+
if (size > MAX_BINARY_BODY_BYTES) {
|
|
170
|
+
throw new RangeError(
|
|
171
|
+
`fetch binary body exceeds the ${MAX_BINARY_BODY_BYTES}-byte limit of the quickjs executor`,
|
|
172
|
+
);
|
|
173
|
+
}
|
|
174
|
+
return size;
|
|
175
|
+
}
|
|
176
|
+
|
|
133
177
|
function requestInit(request: SandboxRequest): RequestInit {
|
|
134
|
-
|
|
178
|
+
if (request.bodyBase64 === undefined) {
|
|
179
|
+
return { method: request.method, headers: request.headers, body: request.body };
|
|
180
|
+
}
|
|
181
|
+
// Already checked by binaryBodySize when the call was accepted.
|
|
182
|
+
const bytes = Buffer.from(request.bodyBase64, "base64");
|
|
183
|
+
const body = new Uint8Array(bytes.buffer, bytes.byteOffset, bytes.byteLength);
|
|
184
|
+
return { method: request.method, headers: request.headers, body };
|
|
135
185
|
}
|
|
136
186
|
|
|
137
187
|
async function serializeResponse(response: Response, operation: string) {
|
|
@@ -223,6 +273,16 @@ function dispatchSync(config: SwarmConfig, op: string, value: unknown): unknown
|
|
|
223
273
|
return Redacted.meta(redactedFor(config, value));
|
|
224
274
|
case "config.has":
|
|
225
275
|
return typeof value === "string" && config.get(value) !== undefined;
|
|
276
|
+
case "crypto.random": {
|
|
277
|
+
if (
|
|
278
|
+
!Number.isInteger(value) ||
|
|
279
|
+
(value as number) < 0 ||
|
|
280
|
+
(value as number) > MAX_RANDOM_BYTES
|
|
281
|
+
) {
|
|
282
|
+
throw new Error(`crypto.random needs a byte count between 0 and ${MAX_RANDOM_BYTES}`);
|
|
283
|
+
}
|
|
284
|
+
return Array.from(crypto.getRandomValues(new Uint8Array(value as number)));
|
|
285
|
+
}
|
|
226
286
|
case "table":
|
|
227
287
|
return table(value as Array<Record<string, unknown>>);
|
|
228
288
|
default:
|
|
@@ -287,6 +347,7 @@ class HostCallScope {
|
|
|
287
347
|
private readonly waiters: Array<() => void> = [];
|
|
288
348
|
private readonly outerFetch = globalThis.fetch;
|
|
289
349
|
private active = 0;
|
|
350
|
+
private bodyBytes = 0;
|
|
290
351
|
|
|
291
352
|
constructor() {
|
|
292
353
|
const outer = this.outerFetch;
|
|
@@ -306,8 +367,11 @@ class HostCallScope {
|
|
|
306
367
|
return this.controller.signal;
|
|
307
368
|
}
|
|
308
369
|
|
|
309
|
-
/**
|
|
310
|
-
|
|
370
|
+
/**
|
|
371
|
+
* Track a host call. `limited` calls share the per-run concurrency budget.
|
|
372
|
+
* `bodyBytes` counts against the per-run binary body budget until it settles.
|
|
373
|
+
*/
|
|
374
|
+
call<T>(run: () => Promise<T>, limited: boolean, bodyBytes = 0): Promise<T> {
|
|
311
375
|
if (this.ops.size >= MAX_PENDING_HOST_CALLS) {
|
|
312
376
|
return Promise.reject(
|
|
313
377
|
new Error(
|
|
@@ -315,12 +379,21 @@ class HostCallScope {
|
|
|
315
379
|
),
|
|
316
380
|
);
|
|
317
381
|
}
|
|
382
|
+
if (this.bodyBytes + bodyBytes > MAX_PENDING_BINARY_BODY_BYTES) {
|
|
383
|
+
return Promise.reject(
|
|
384
|
+
new RangeError(
|
|
385
|
+
`Pending fetch binary bodies in one script run exceed ${MAX_PENDING_BINARY_BODY_BYTES} bytes`,
|
|
386
|
+
),
|
|
387
|
+
);
|
|
388
|
+
}
|
|
389
|
+
this.bodyBytes += bodyBytes;
|
|
318
390
|
const op = limited ? this.limited(run) : run();
|
|
319
391
|
this.ops.add(op);
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
392
|
+
const settle = () => {
|
|
393
|
+
this.ops.delete(op);
|
|
394
|
+
this.bodyBytes -= bodyBytes;
|
|
395
|
+
};
|
|
396
|
+
void op.then(settle, settle);
|
|
324
397
|
return op;
|
|
325
398
|
}
|
|
326
399
|
|
|
@@ -491,7 +564,10 @@ async function evaluateJob(
|
|
|
491
564
|
define("__host_call", (pathHandle, argsHandle) => {
|
|
492
565
|
const path = ctx.getString(pathHandle);
|
|
493
566
|
const callArgs = JSON.parse(ctx.getString(argsHandle)) as unknown[];
|
|
494
|
-
return bridge(
|
|
567
|
+
return bridge(async () => {
|
|
568
|
+
const bodyBytes = FETCH_PATHS.has(path) ? binaryBodySize(callArgs?.[0]) : 0;
|
|
569
|
+
return host.call(() => dispatchCall(hostCtx, path, callArgs, host.signal), true, bodyBytes);
|
|
570
|
+
});
|
|
495
571
|
});
|
|
496
572
|
define("__host_sync", (opHandle, valueHandle) => {
|
|
497
573
|
let envelope: unknown;
|
|
@@ -538,6 +614,7 @@ async function evaluateJob(
|
|
|
538
614
|
const init = `globalThis.__swarm_shape = ${JSON.stringify(shape)};\nglobalThis.__swarm_args = ${JSON.stringify(args)};`;
|
|
539
615
|
const ok =
|
|
540
616
|
evaluate(init, "swarm-init.js") &&
|
|
617
|
+
evaluate(SANDBOX_WEB_SHIMS, "swarm-web-shims.js") &&
|
|
541
618
|
evaluate(SANDBOX_PRELUDE, "swarm-prelude.js") &&
|
|
542
619
|
evaluate(bundle.code, BUNDLE_FILE) &&
|
|
543
620
|
evaluate(SANDBOX_EPILOGUE, "swarm-epilogue.js");
|