@desplega.ai/agent-swarm 2.5.4 → 2.7.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +4 -2
- package/dist/{acp-adapter-ek38cwsv.js → acp-adapter-ma1js5n7.js} +9 -9
- package/dist/{actions-92mepq03.js → actions-1vhmtvg9.js} +21 -21
- package/dist/{amp-adapter-9gaxg015.js → amp-adapter-sxqqvmdk.js} +4 -4
- package/dist/{app-ctn8gsgx.js → app-mwx1d5a9.js} +3 -3
- package/dist/{assistant-zyr5kz47.js → assistant-yx1p6g82.js} +30 -26
- package/dist/{boot-reembed-w75maga2.js → boot-reembed-5h37a2c2.js} +1 -1
- package/dist/{boot-reembed-dwzfttdr.js → boot-reembed-7h78vmvv.js} +2 -1
- package/dist/{boot-scrub-sweep-e9kq43kf.js → boot-scrub-sweep-8mawd4gw.js} +4 -4
- package/dist/{boot-seeds-x28v3fhb.js → boot-seeds-ca84dhnn.js} +4 -4
- package/dist/{claude-adapter-f0afqnne.js → claude-adapter-wqt4hfkq.js} +9 -9
- package/dist/{claude-managed-adapter-x81a7r65.js → claude-managed-adapter-6fjqtr2z.js} +2 -2
- package/dist/{claude-managed-setup-bqzwjd9x.js → claude-managed-setup-rqv98sea.js} +10 -3
- package/dist/{claude-sdk-session-7b7whkaq.js → claude-sdk-session-f29f3b2x.js} +11 -11
- package/dist/{cli-sxgtf5fw.js → cli-07q03xm2.js} +1 -1
- package/dist/{cli-kxpfx2xr.js → cli-0z1xjzwa.js} +2 -2
- package/dist/{cli-g9vd9ece.js → cli-1hhbzkbb.js} +14 -16
- package/dist/{cli-qqzmjxkf.js → cli-1m9x8dr5.js} +11 -6
- package/dist/{cli-jbnn4vxb.js → cli-2pvzmas1.js} +3 -3
- package/dist/{cli-x77gen2p.js → cli-2y7v133v.js} +7 -7
- package/dist/{cli-dew6x255.js → cli-39aet655.js} +2 -2
- package/dist/{cli-g1cw7xtt.js → cli-39rrw8yp.js} +1 -1
- package/dist/{cli-jb30dzvk.js → cli-48gxjmcz.js} +530 -190
- package/dist/{cli-pzatvw94.js → cli-54sj64fc.js} +2 -2
- package/dist/{cli-bvpgr9ny.js → cli-60sty27p.js} +5 -5
- package/dist/{cli-asq5ydm4.js → cli-6a9qfa8b.js} +411 -69
- package/dist/{cli-277c31dw.js → cli-6mgdba42.js} +225 -3
- package/dist/{cli-19202kew.js → cli-6srv653c.js} +5 -5
- package/dist/{cli-n4c8e3s5.js → cli-708wdy32.js} +1 -1
- package/dist/{cli-pasyvjmm.js → cli-737pyqxr.js} +1 -1
- package/dist/{cli-6x9ma5w5.js → cli-803188pb.js} +1 -1
- package/dist/{cli-hh5we352.js → cli-86ehn4s5.js} +7 -7
- package/dist/{cli-9s05422w.js → cli-8mbc9za9.js} +1 -1
- package/dist/{cli-ff1tm9n9.js → cli-8wmj18cz.js} +1 -1
- package/dist/{cli-1r5ad7j5.js → cli-9gsm2rzp.js} +12 -12
- package/dist/{cli-mz9afxxf.js → cli-9pshx6rd.js} +17 -3
- package/dist/{cli-s682zgqz.js → cli-a6ds2epe.js} +1 -1
- package/dist/{cli-6a1xg1p7.js → cli-a89zdqz9.js} +2 -2
- package/dist/{cli-03vpq45j.js → cli-ay40dnj3.js} +2 -2
- package/dist/{cli-tsma7p8v.js → cli-aypd09nf.js} +2 -2
- package/dist/{cli-ethgy7ha.js → cli-b3yzyc57.js} +2 -2
- package/dist/{cli-bn0rewsy.js → cli-c081bj3b.js} +2 -2
- package/dist/{cli-mmhj8vd7.js → cli-c668vvcf.js} +11 -2
- package/dist/{cli-sbwsxvtd.js → cli-d20qvpr2.js} +2 -2
- package/dist/{cli-c952c7ac.js → cli-e1595bgx.js} +3 -3
- package/dist/{cli-621ce7kw.js → cli-f12mr20z.js} +3 -3
- package/dist/{cli-dqygeeh5.js → cli-f1e0ptjj.js} +10 -1
- package/dist/{cli-xzfq4at3.js → cli-f4dbh7a5.js} +16 -12
- package/dist/{cli-ha9rmwgq.js → cli-fb8m699c.js} +2 -2
- package/dist/{cli-5bnsc0e1.js → cli-gsfk58nw.js} +5 -5
- package/dist/cli-gt4h8cxw.js +895 -0
- package/dist/{cli-nwyw3zhg.js → cli-gxee0bqa.js} +1 -1
- package/dist/{cli-25n3mv5d.js → cli-h5jeg46p.js} +6 -6
- package/dist/{cli-gn5af0x5.js → cli-he9gbdx0.js} +62 -12
- package/dist/{cli-gbfxp48k.js → cli-j0ppywdr.js} +42 -35
- package/dist/{cli-k4tjsdx9.js → cli-j3y0x2n4.js} +277 -199
- package/dist/{cli-7p4jxgtt.js → cli-j9bpmykh.js} +75 -27
- package/dist/{cli-dabehxhp.js → cli-kt907da6.js} +1 -1
- package/dist/{cli-wx2nj19m.js → cli-kv7sbc2c.js} +6 -6
- package/dist/{cli-abs8ndtr.js → cli-m30k9777.js} +26 -26
- package/dist/{cli-cz5nmdxt.js → cli-m5e802r7.js} +2 -2
- package/dist/{cli-0shx9j93.js → cli-pfk92eqz.js} +2 -2
- package/dist/{cli-xtrz6bzs.js → cli-ptfq202x.js} +5 -3
- package/dist/{cli-6n8axjv1.js → cli-qj442fws.js} +2 -2
- package/dist/{cli-c61mbdds.js → cli-rd5m70gj.js} +4 -4
- package/dist/{cli-eftzyh9y.js → cli-s09j9vp5.js} +2 -2
- package/dist/{cli-m4ecdxvz.js → cli-s0mgx214.js} +7 -7
- package/dist/{cli-wghjkr0e.js → cli-sgqm2582.js} +10 -7
- package/dist/{cli-2655k16q.js → cli-sjctjr48.js} +2 -2
- package/dist/{cli-nzfnt2gs.js → cli-tf1n0r0b.js} +1 -1
- package/dist/{cli-x0f2v69c.js → cli-twctnkng.js} +12 -7
- package/dist/{cli-j6rktc91.js → cli-v6shg7na.js} +1 -1
- package/dist/{cli-gx4rm0qx.js → cli-vz78s5wm.js} +1 -1
- package/dist/{cli-gwhjmh8g.js → cli-wqr7cea4.js} +5 -5
- package/dist/{cli-e42vss7b.js → cli-x37r7qwf.js} +16 -8
- package/dist/{cli-4b2z6n0m.js → cli-yd2nreqw.js} +17 -8
- package/dist/{cli-64mh9mf3.js → cli-ymjpkxgn.js} +69 -17
- package/dist/{cli-5kqnemen.js → cli-yvy48785.js} +1 -1
- package/dist/{cli-9j129brq.js → cli-yxbsmx56.js} +6 -6
- package/dist/cli.js +22 -20
- package/dist/{codex-adapter-wch12q99.js → codex-adapter-zk59x2zh.js} +7 -7
- package/dist/{codex-hook-brpnycn5.js → codex-hook-xyjhvxwd.js} +1 -1
- package/dist/{codex-login-rn7c5gxt.js → codex-login-8cg0vhf2.js} +1 -1
- package/dist/{codex-session-runner-41pfcr95.js → codex-session-runner-kh6s4j3n.js} +8 -8
- package/dist/{commands-9849dkj6.js → commands-68et1qv2.js} +2 -2
- package/dist/{controller-ja0sd95p.js → controller-fyrbnzg4.js} +4 -4
- package/dist/{controllers-connect-xp05dj6x.js → controllers-connect-nep9h2xv.js} +23 -23
- package/dist/{cursor-adapter-623qxqew.js → cursor-adapter-yss3pg7t.js} +2 -2
- package/dist/{db-w759qmx0.js → db-28gjyvxj.js} +4 -2
- package/dist/{db-47vj40dy.js → db-8ftwvzba.js} +3 -3
- package/dist/{devin-adapter-4ycy4vrf.js → devin-adapter-byen2cxt.js} +1 -1
- package/dist/{dispatcher-7b8e4z2j.js → dispatcher-hayexmkr.js} +20 -20
- package/dist/{docker-s84hjs91.js → docker-fdzyewpz.js} +2 -2
- package/dist/{dsh-adapter-2ezw3xea.js → dsh-adapter-46wawff9.js} +2 -2
- package/dist/{e2b-9gkdhm3c.js → e2b-222hsajc.js} +2 -2
- package/dist/{e2b-cjtf8k1z.js → e2b-fn3zacvt.js} +2 -2
- package/dist/{easter-egg-3d-e4qv8dxj.js → easter-egg-3d-8d1e0h4m.js} +1 -1
- package/dist/{event-bus-3z78ep5n.js → event-bus-pghg8hqy.js} +1 -1
- package/dist/{execute-0xpz8950.js → execute-cjrfxfnm.js} +1 -1
- package/dist/{external-channel-e2s028da.js → external-channel-xbmxcg09.js} +2 -2
- package/dist/{grok-adapter-ys7gaqee.js → grok-adapter-ag4wks2a.js} +10 -10
- package/dist/handlers-tzmevqb5.js +41 -0
- package/dist/{heartbeat-w3mntghj.js → heartbeat-psfvcgwp.js} +23 -23
- package/dist/{hook-g7kvv6x9.js → hook-8pz04697.js} +6 -6
- package/dist/{hook-3jjywnpb.js → hook-cyq4mryn.js} +8 -8
- package/dist/{http-tkp7d4kb.js → http-haj1nd6k.js} +921 -169
- package/dist/{index-q7m26kvs.js → index-jwt2bj6f.js} +265 -97
- package/dist/{index-3n8m9ea2.js → index-q52f47pe.js} +1 -1
- package/dist/{index-31ceyf42.js → index-syvhnvtw.js} +1 -1
- package/dist/index-y1ajx04a.js +43 -0
- package/dist/{index-1ef1myq2.js → index-z5h22pes.js} +20 -20
- package/dist/{index-xdaahthb.js → index-zyq8ct52.js} +20 -20
- package/dist/{keepalive-v4ta8wgg.js → keepalive-xdvsamvj.js} +4 -4
- package/dist/lead-hqg57kdp.js +79 -0
- package/dist/{maintenance-84n1ex21.js → maintenance-4y2xzh3a.js} +1 -1
- package/dist/{oauth-refresh-sweep-vjnnandw.js → oauth-refresh-sweep-jd4r7zbs.js} +9 -4
- package/dist/{onboard-sfmpe4np.js → onboard-18447q9q.js} +6 -6
- package/dist/{opencode-adapter-jmpst4q4.js → opencode-adapter-26b2frdf.js} +7 -7
- package/dist/{otel-impl-v6cc0zve.js → otel-impl-f9tgr8yw.js} +2 -2
- package/dist/{pi-mono-adapter-fk8fkcf6.js → pi-mono-adapter-qn6qbga1.js} +38 -17
- package/dist/{pricing-refresh-fsk1e1zc.js → pricing-refresh-dw2tgb53.js} +3 -3
- package/dist/{rbac-roles-prmkdnkw.js → rbac-roles-aygm2760.js} +2 -2
- package/dist/{rbac-roles-zxmxqa39.js → rbac-roles-s4nwj2m3.js} +1 -1
- package/dist/render-v2-yfvdfr0x.js +16 -0
- package/dist/{resume-frdtgfa0.js → resume-1bmvjbf3.js} +9 -9
- package/dist/{runtime-bt5pkpm1.js → runtime-f7tpzedy.js} +1 -1
- package/dist/{session-access-fpez4y5q.js → session-access-r5garej6.js} +4 -4
- package/dist/session-source-backfill-424wzv0r.js +110 -0
- package/dist/{setup-mq6m3m8q.js → setup-8tygvxkq.js} +1 -1
- package/dist/{virtual-modules-mnfbh5mr.js → virtual-modules-xsw323b3.js} +2 -2
- package/dist/{work-objects-kfahhjsq.js → work-objects-ggnghpgf.js} +23 -23
- package/dist/worker-sqjd1nqf.js +79 -0
- package/dist/{x-pbpezr0s.js → x-wxrzvzx8.js} +2 -2
- package/openapi.json +1324 -14
- package/package.json +5 -3
- package/src/assets/source-key.ts +95 -0
- package/src/be/automation-preflight.ts +2 -0
- package/src/be/db/api-keys.ts +89 -0
- package/src/be/db/session-labels.ts +28 -0
- package/src/be/db/session-moves.ts +5 -6
- package/src/be/db/session-tree.ts +153 -14
- package/src/be/db/tasks/read.ts +11 -0
- package/src/be/db/tasks/write.ts +6 -1
- package/src/be/db/workflow-runs.ts +219 -6
- package/src/be/db-retention.ts +61 -6
- package/src/be/db.ts +135 -132
- package/src/be/memory/providers/openai-embedding.ts +5 -1
- package/src/be/memory/task-learning.ts +155 -0
- package/src/be/migrations/244_api_key_validation.sql +4 -0
- package/src/be/migrations/245_script_executor.sql +3 -0
- package/src/be/oauth-refresh-sweep.ts +2 -1
- package/src/be/onboarding.ts +4 -0
- package/src/be/scripts/boot-reembed.ts +2 -0
- package/src/be/scripts/db.ts +14 -4
- package/src/be/scripts/embeddings.ts +2 -0
- package/src/be/scripts/run-saved.ts +1 -0
- package/src/be/scripts/typecheck.ts +10 -4
- package/src/be/seed-skills/index.ts +7 -0
- package/src/be/session-source-backfill.ts +163 -0
- package/src/be/swarm-config-guard.ts +1 -0
- package/src/comb/review-batch.ts +28 -1
- package/src/comb/templates.ts +7 -2
- package/src/commands/claude-managed-setup.ts +7 -1
- package/src/commands/credential-refresh.ts +76 -1
- package/src/commands/credential-wait.ts +2 -2
- package/src/commands/onboard/compose-generator.ts +5 -3
- package/src/commands/provider-credentials.ts +212 -171
- package/src/commands/runner.ts +25 -4
- package/src/extensions/apply-task-create.ts +8 -1
- package/src/extensions/contract-types.generated.ts +1 -1
- package/src/extensions/contract.ts +5 -0
- package/src/heartbeat/heartbeat.ts +14 -1
- package/src/heartbeat/script-runner-probe.ts +47 -0
- package/src/http/all-routes.ts +2 -0
- package/src/http/api-keys.ts +77 -9
- package/src/http/assets.ts +15 -0
- package/src/http/codex-oauth-accounts.ts +191 -0
- package/src/http/comb.ts +15 -2
- package/src/http/config.ts +1 -1
- package/src/http/core.ts +6 -0
- package/src/http/fleet-benchmark.ts +5 -2
- package/src/http/idempotency-key.ts +1 -1
- package/src/http/index.ts +14 -3
- package/src/http/integrations.ts +5 -1
- package/src/http/kv.ts +8 -3
- package/src/http/onboarding.ts +6 -1
- package/src/http/pages-public.ts +38 -5
- package/src/http/pool-keys.ts +354 -0
- package/src/http/scripts.ts +27 -0
- package/src/http/sessions.ts +37 -12
- package/src/http/stats.ts +1 -0
- package/src/http/status.ts +26 -12
- package/src/http/tasks.ts +3 -0
- package/src/http/used-by.ts +7 -2
- package/src/http/workflows.ts +88 -9
- package/src/http/x.ts +5 -0
- package/src/integrations/chat-sdk/inbound.ts +27 -0
- package/src/kv-reserved-namespaces.ts +19 -0
- package/src/prompts/base-prompt.ts +21 -1
- package/src/prompts/session-templates.ts +17 -3
- package/src/providers/acp-targets.ts +1 -1
- package/src/providers/amp-adapter.ts +1 -1
- package/src/providers/claude-adapter.ts +5 -1
- package/src/providers/claude-managed-adapter.ts +16 -9
- package/src/providers/codex-adapter.ts +71 -11
- package/src/providers/dsh-adapter.ts +1 -1
- package/src/providers/pi-mono-adapter.ts +41 -11
- package/src/scheduler/scheduler.ts +5 -3
- package/src/scripts-runtime/eval-harness.ts +8 -1
- package/src/scripts-runtime/executors/native.ts +22 -16
- package/src/scripts-runtime/executors/quickjs-prelude.ts +103 -1
- package/src/scripts-runtime/executors/quickjs-runner.ts +2 -1
- package/src/scripts-runtime/executors/quickjs.ts +7 -0
- package/src/scripts-runtime/executors/registry.ts +32 -0
- package/src/scripts-runtime/executors/types.ts +7 -0
- package/src/scripts-runtime/host-validated-args.ts +133 -0
- package/src/scripts-runtime/loader.ts +30 -3
- package/src/scripts-runtime/swarm-sdk.ts +16 -3
- package/src/scripts-runtime/types/stdlib.d.ts +10 -2
- package/src/scripts-runtime/types/swarm-extension.d.ts +17 -8
- package/src/scripts-runtime/types/swarm-sdk.d.ts +10 -2
- package/src/slack/assistant.ts +7 -0
- package/src/slack/channel-labels.ts +74 -0
- package/src/slack/handlers.ts +8 -0
- package/src/slack/inbound-files.ts +5 -2
- package/src/slack/thread-buffer.ts +4 -0
- package/src/tasks/sibling-awareness.ts +12 -3
- package/src/telemetry-context.ts +5 -6
- package/src/telemetry-identity.ts +3 -3
- package/src/tests/api-key-status-by-suffix.test.ts +242 -0
- package/src/tests/api-keys-validation.test.ts +170 -0
- package/src/tests/base-prompt.test.ts +70 -2
- package/src/tests/claude-managed-adapter.test.ts +46 -0
- package/src/tests/claude-routes-e2e.test.ts +7 -2
- package/src/tests/codex-credential-pools.test.ts +120 -0
- package/src/tests/codex-gateway-route.test.ts +165 -0
- package/src/tests/codex-oauth-accounts.test.ts +357 -0
- package/src/tests/comb-review-batch.test.ts +63 -2
- package/src/tests/community-linear-delivery-loop.test.ts +2099 -0
- package/src/tests/config-pool-keys.test.ts +533 -0
- package/src/tests/credential-check.test.ts +294 -4
- package/src/tests/credential-refresh.test.ts +172 -1
- package/src/tests/db-retention.test.ts +9 -3
- package/src/tests/db-task-read-contract.test.ts +1 -0
- package/src/tests/db-task-write-contract.test.ts +1 -3
- package/src/tests/docs-index-footprint.test.js +3 -3
- package/src/tests/docs-index.test.ts +135 -0
- package/src/tests/dsh-adapter.test.ts +1 -1
- package/src/tests/extensions-pre-task.test.ts +53 -1
- package/src/tests/fetch-resolved-env.test.ts +16 -2
- package/src/tests/fixtures/extensions/record-task-create/hooks.ts +9 -0
- package/src/tests/fixtures/extensions/record-task-create/manifest.json +7 -0
- package/src/tests/fleet-benchmark.test.ts +8 -0
- package/src/tests/get-workflow-run-tool.test.ts +98 -0
- package/src/tests/heartbeat-checklist.test.ts +89 -0
- package/src/tests/heartbeat-script-runner-probe.test.ts +83 -0
- package/src/tests/integrations-http.test.ts +32 -2
- package/src/tests/internal-ai/complete-structured.test.ts +37 -1
- package/src/tests/internal-ai/credentials.test.ts +23 -0
- package/src/tests/kv-http.test.ts +32 -0
- package/src/tests/kv-tool.test.ts +25 -0
- package/src/tests/multi-runtime-registration.test.ts +12 -12
- package/src/tests/oauth-refresh-sweep.test.ts +34 -1
- package/src/tests/onboard-compose.test.ts +20 -0
- package/src/tests/onboarding.test.ts +108 -1
- package/src/tests/openai-embedding-credentials.test.ts +35 -0
- package/src/tests/opencode-adapter.test.ts +2 -1
- package/src/tests/openrouter-attribution-call-sites.test.ts +2 -2
- package/src/tests/org-website.test.ts +36 -0
- package/src/tests/pages-frame-ancestors.test.ts +162 -0
- package/src/tests/pi-codemode.test.ts +2 -1
- package/src/tests/pi-mono-adapter.test.ts +54 -0
- package/src/tests/pi-native-mcp.test.ts +13 -1
- package/src/tests/pi-tool-exposure.test.ts +8 -8
- package/src/tests/probe-tool-schema-tokens.test.ts +144 -0
- package/src/tests/prompt-template-session.test.ts +4 -3
- package/src/tests/provider-key-probes.test.ts +181 -0
- package/src/tests/rbac-e2e-helpers.test.ts +88 -1
- package/src/tests/rbac-e2e-helpers.ts +36 -11
- package/src/tests/request-composition-derive.test.ts +329 -0
- package/src/tests/script-connections.test.ts +4 -0
- package/src/tests/script-executor-conformance.test.ts +36 -1
- package/src/tests/script-executor-quickjs-pool.test.ts +50 -0
- package/src/tests/script-executor-quickjs.test.ts +140 -0
- package/src/tests/script-host-validated-args-native.test.ts +196 -0
- package/src/tests/script-per-script-executor.test.ts +415 -0
- package/src/tests/script-runs-retention.test.ts +212 -0
- package/src/tests/scripts-external-api.test.ts +45 -0
- package/src/tests/sdk-allowlist.test.ts +42 -2
- package/src/tests/self-improvement.test.ts +4 -4
- package/src/tests/session-source-backfill.test.ts +429 -0
- package/src/tests/session-source-keys.test.ts +236 -0
- package/src/tests/session-source-labels.test.ts +419 -0
- package/src/tests/sessions-detail-batch.test.ts +2 -0
- package/src/tests/sessions-move.test.ts +131 -0
- package/src/tests/sessions-tree.test.ts +299 -2
- package/src/tests/slack-inbound-files.test.ts +103 -23
- package/src/tests/status.test.ts +101 -18
- package/src/tests/store-progress-learning.test.ts +288 -0
- package/src/tests/tasks-workflow-run-filter.test.ts +146 -0
- package/src/tests/test-net.ts +31 -0
- package/src/tests/transcription.test.ts +8 -0
- package/src/tests/ui-logs-parser.test.ts +180 -0
- package/src/tests/used-by-route.test.ts +11 -0
- package/src/tests/workflow-cascade-cancel.test.ts +172 -0
- package/src/tests/workflow-engine-v2.test.ts +116 -1
- package/src/tests/workflow-http-v2.test.ts +131 -0
- package/src/tests/workflow-mcp-trigger-schema.test.ts +209 -0
- package/src/tests/workflow-run-finished-event.test.ts +263 -0
- package/src/tests/workflow-step-order.test.ts +92 -0
- package/src/tests/workflow-sub-workflow.test.ts +30 -0
- package/src/tests/workflow-swarm-script.test.ts +34 -0
- package/src/tools/get-tasks.ts +8 -0
- package/src/tools/kv/kv-read-auth.ts +4 -2
- package/src/tools/script-common.ts +1 -0
- package/src/tools/script-run.ts +7 -0
- package/src/tools/script-upsert.ts +7 -0
- package/src/tools/store-progress.ts +67 -1
- package/src/tools/task-action.ts +1 -1
- package/src/tools/workflows/get-workflow-run.ts +14 -4
- package/src/tools/workflows/trigger-workflow.ts +104 -10
- package/src/types.ts +36 -1
- package/src/utils/company-profile.ts +51 -1
- package/src/utils/credentials.ts +10 -4
- package/src/utils/internal-ai/complete-structured.ts +5 -0
- package/src/utils/internal-ai/credentials.ts +4 -3
- package/src/utils/provider-key-probes.ts +328 -0
- package/src/utils/transcription.ts +4 -1
- package/src/workflows/engine.ts +18 -16
- package/src/workflows/executors/sub-workflow.ts +8 -1
- package/src/workflows/executors/swarm-script.ts +5 -0
- package/src/workflows/foreach-driver.ts +5 -11
- package/src/workflows/node-timeout.ts +30 -0
- package/templates/ai-toolbox.manifest.json +2 -2
- package/templates/community/linear-delivery-loop/PLAYBOOK.md +323 -0
- package/templates/community/linear-delivery-loop/README.md +38 -0
- package/templates/community/linear-delivery-loop/config.example.json +31 -0
- package/templates/community/linear-delivery-loop/lead-prompt.md +34 -0
- package/templates/community/linear-delivery-loop/schedule.json +10 -0
- package/templates/community/linear-delivery-loop/scripts/linear-delivery-setup.ts +148 -0
- package/templates/community/linear-delivery-loop/scripts/linear-delivery-tick.ts +1781 -0
- package/templates/skills/researching/content.md +2 -0
- package/templates/skills/swarm-docs/config.json +14 -0
- package/templates/skills/swarm-docs/content.md +144 -0
- package/templates/skills/swarm-scripts/SKILL.md +1 -0
- package/templates/skills/swarm-scripts/content.md +1 -0
- package/dist/cli-7421jzsw.js +0 -84
- package/dist/cli-e45e4m6b.js +0 -413
- package/dist/cli-zw88zjba.js +0 -307
- package/dist/handlers-wy1dr6ja.js +0 -41
- package/dist/index-nmm79qgr.js +0 -43
- package/dist/lead-sawnyqxk.js +0 -79
- package/dist/render-v2-pjwxvnkw.js +0 -12
- package/dist/worker-v5e5rxj3.js +0 -79
|
@@ -30,6 +30,13 @@
|
|
|
30
30
|
|
|
31
31
|
import { existsSync as nodeExistsSync } from "node:fs";
|
|
32
32
|
import { join } from "node:path";
|
|
33
|
+
import {
|
|
34
|
+
CODEX_GATEWAY_API_KEY,
|
|
35
|
+
deriveDefaultRoute,
|
|
36
|
+
type ModelRoute,
|
|
37
|
+
routeCredentialStatus,
|
|
38
|
+
routeUnsetEnv,
|
|
39
|
+
} from "@desplega/model-routing";
|
|
33
40
|
import { buildRatingsFromLlm, fetchRetrievalsForTask, postRatings } from "../be/memory/raters/llm";
|
|
34
41
|
import { contextBoundNoteThreshold, createContextBoundNote } from "../hooks/context-bound-note";
|
|
35
42
|
import { getApiKey } from "../utils/api-key";
|
|
@@ -38,6 +45,7 @@ import {
|
|
|
38
45
|
clampContextPercent,
|
|
39
46
|
computeContextUsedUnified,
|
|
40
47
|
} from "../utils/context-window";
|
|
48
|
+
import { selectCredential } from "../utils/credentials";
|
|
41
49
|
import { isEnvFlagEnabled, isSessionSummarySkipped } from "../utils/env-flag";
|
|
42
50
|
import { SessionErrorTracker } from "../utils/error-tracker";
|
|
43
51
|
import { summarizeSession as runSummarize } from "../utils/internal-ai";
|
|
@@ -82,6 +90,32 @@ import type {
|
|
|
82
90
|
|
|
83
91
|
type CodexConfig = Record<string, CodexConfigValue | undefined>;
|
|
84
92
|
|
|
93
|
+
/** `model_provider` id of the generated gateway block. */
|
|
94
|
+
const CODEX_GATEWAY_PROVIDER_ID = "swarm-gateway";
|
|
95
|
+
|
|
96
|
+
/**
|
|
97
|
+
* Config overrides that send Codex through the gateway route. Codex 0.164
|
|
98
|
+
* ignores `OPENAI_BASE_URL` and env keys for its built-in `openai` provider, so
|
|
99
|
+
* the gateway needs its own provider block. Verified on 0.164 against a local
|
|
100
|
+
* gateway: Codex sends `Authorization: Bearer $CODEX_GATEWAY_API_KEY` to
|
|
101
|
+
* `<base_url>/responses` and never the auth.json OAuth token. `wire_api`
|
|
102
|
+
* defaults to `responses` (`chat` is rejected); it is set to state intent.
|
|
103
|
+
*/
|
|
104
|
+
export function codexGatewayConfig(route: ModelRoute): CodexConfig {
|
|
105
|
+
return {
|
|
106
|
+
model_provider: CODEX_GATEWAY_PROVIDER_ID,
|
|
107
|
+
model_providers: {
|
|
108
|
+
[CODEX_GATEWAY_PROVIDER_ID]: {
|
|
109
|
+
name: "Swarm gateway",
|
|
110
|
+
base_url: route.baseUrl,
|
|
111
|
+
env_key: CODEX_GATEWAY_API_KEY,
|
|
112
|
+
wire_api: "responses",
|
|
113
|
+
requires_openai_auth: false,
|
|
114
|
+
},
|
|
115
|
+
},
|
|
116
|
+
};
|
|
117
|
+
}
|
|
118
|
+
|
|
85
119
|
/** Plugin id the worker image installs (`codex plugin add context-mode@context-mode`). */
|
|
86
120
|
const CONTEXT_MODE_CODEX_PLUGIN = "context-mode@context-mode";
|
|
87
121
|
|
|
@@ -187,7 +221,10 @@ export type CodexThreadFactory = (options: {
|
|
|
187
221
|
}) => Promise<CodexThread> | CodexThread;
|
|
188
222
|
|
|
189
223
|
/**
|
|
190
|
-
*
|
|
224
|
+
* A gateway route (`CODEX_GATEWAY_BASE_URL` + `CODEX_GATEWAY_API_KEY`) wins
|
|
225
|
+
* and alone decides readiness: a gateway URL without its key is not ready,
|
|
226
|
+
* whatever OAuth or auth.json is present. Otherwise Codex satisfies its
|
|
227
|
+
* credential requirement by ANY of:
|
|
191
228
|
* 1. `~/.codex/auth.json` already exists on disk (the canonical state once
|
|
192
229
|
* `codex login` has run).
|
|
193
230
|
* 2. `OPENAI_API_KEY` is set — the entrypoint will run
|
|
@@ -204,6 +241,13 @@ export function checkCodexCredentials(
|
|
|
204
241
|
env: Record<string, string | undefined>,
|
|
205
242
|
opts: CredCheckOptions = {},
|
|
206
243
|
): CredStatus {
|
|
244
|
+
const gateway = deriveDefaultRoute("codex", env);
|
|
245
|
+
if (gateway) {
|
|
246
|
+
const status = routeCredentialStatus(gateway, env);
|
|
247
|
+
return status.ready
|
|
248
|
+
? { ready: true, missing: [], satisfiedBy: "env", hint: status.hint }
|
|
249
|
+
: { ready: false, missing: status.missing, hint: status.hint };
|
|
250
|
+
}
|
|
207
251
|
const homeDir = opts.homeDir ?? env.HOME ?? "/root";
|
|
208
252
|
const existsSync = opts.fs?.existsSync ?? nodeExistsSync;
|
|
209
253
|
const authFile = `${homeDir}/.codex/auth.json`;
|
|
@@ -1965,14 +2009,21 @@ export async function createInProcessCodexSession(
|
|
|
1965
2009
|
preSessionEvents.push(event);
|
|
1966
2010
|
};
|
|
1967
2011
|
|
|
1968
|
-
const
|
|
2012
|
+
const routeEnv = config.env ?? process.env;
|
|
2013
|
+
const gateway = deriveDefaultRoute("codex", routeEnv);
|
|
2014
|
+
const mergedConfig = {
|
|
2015
|
+
...(await buildCodexConfig(config, resolvedModel, bufferedEmit)),
|
|
2016
|
+
...(gateway ? codexGatewayConfig(gateway) : {}),
|
|
2017
|
+
};
|
|
1969
2018
|
|
|
1970
2019
|
// Auth resolution. `codex_oauth` (in the swarm config store) wins over
|
|
1971
2020
|
// `OPENAI_API_KEY` so users can keep an OpenAI key set for embeddings
|
|
1972
2021
|
// without it shadowing their ChatGPT login. The entrypoint already runs
|
|
1973
2022
|
// this same precedence at boot — this block handles local dev (where
|
|
1974
2023
|
// the entrypoint didn't run) and any case where auth.json is stale.
|
|
1975
|
-
|
|
2024
|
+
// A gateway route skips it: no OAuth is restored for a session whose traffic
|
|
2025
|
+
// goes to the gateway.
|
|
2026
|
+
const authMode = gateway ? null : await resolveCodexAuthMode(config, bufferedEmit);
|
|
1976
2027
|
|
|
1977
2028
|
// The app-server receives an explicit environment. Construct a
|
|
1978
2029
|
// minimal env explicitly so the spawned Codex CLI can find its binary
|
|
@@ -1999,7 +2050,14 @@ export async function createInProcessCodexSession(
|
|
|
1999
2050
|
...buildOtelTraceparentEnv(config.env ?? process.env),
|
|
2000
2051
|
SWARM_CODEX_APP_SERVER: "1",
|
|
2001
2052
|
};
|
|
2002
|
-
if (
|
|
2053
|
+
if (gateway) {
|
|
2054
|
+
for (const key of routeUnsetEnv("codex", gateway)) delete env[key];
|
|
2055
|
+
const gatewayKey = routeEnv[CODEX_GATEWAY_API_KEY];
|
|
2056
|
+
if (gatewayKey) env[CODEX_GATEWAY_API_KEY] = gatewayKey;
|
|
2057
|
+
} else if (authMode === "chatgpt") delete env.OPENAI_API_KEY;
|
|
2058
|
+
else if (env.OPENAI_API_KEY) {
|
|
2059
|
+
env.OPENAI_API_KEY = selectCredential(env.OPENAI_API_KEY, undefined, "OPENAI_API_KEY").selected;
|
|
2060
|
+
}
|
|
2003
2061
|
|
|
2004
2062
|
const codexPathOverride = process.env.CODEX_PATH_OVERRIDE;
|
|
2005
2063
|
|
|
@@ -2151,6 +2209,11 @@ class CodexSubprocessSession implements ProviderSession {
|
|
|
2151
2209
|
};
|
|
2152
2210
|
|
|
2153
2211
|
const apiKey = getApiKey();
|
|
2212
|
+
const providerKeys: Record<string, string> = {};
|
|
2213
|
+
for (const keyVar of ["OPENAI_API_KEY", "OPENROUTER_API_KEY", "ANTHROPIC_API_KEY"]) {
|
|
2214
|
+
const value = config.env?.[keyVar] ?? process.env[keyVar];
|
|
2215
|
+
if (value) providerKeys[keyVar] = selectCredential(value, undefined, keyVar).selected;
|
|
2216
|
+
}
|
|
2154
2217
|
|
|
2155
2218
|
this.proc = registerProcessGroup(
|
|
2156
2219
|
Bun.spawn(argv, {
|
|
@@ -2168,13 +2231,7 @@ class CodexSubprocessSession implements ProviderSession {
|
|
|
2168
2231
|
...(process.env.MCP_BASE_URL ? { MCP_BASE_URL: process.env.MCP_BASE_URL } : {}),
|
|
2169
2232
|
...(apiKey ? { AGENT_SWARM_API_KEY: apiKey, API_KEY: apiKey } : {}),
|
|
2170
2233
|
// Embedding / summarization paths read these:
|
|
2171
|
-
...
|
|
2172
|
-
...(process.env.OPENROUTER_API_KEY
|
|
2173
|
-
? { OPENROUTER_API_KEY: process.env.OPENROUTER_API_KEY }
|
|
2174
|
-
: {}),
|
|
2175
|
-
...(process.env.ANTHROPIC_API_KEY
|
|
2176
|
-
? { ANTHROPIC_API_KEY: process.env.ANTHROPIC_API_KEY }
|
|
2177
|
-
: {}),
|
|
2234
|
+
...providerKeys,
|
|
2178
2235
|
...(process.env.CODEX_PATH_OVERRIDE
|
|
2179
2236
|
? { CODEX_PATH_OVERRIDE: process.env.CODEX_PATH_OVERRIDE }
|
|
2180
2237
|
: {}),
|
|
@@ -2475,6 +2532,9 @@ export class CodexAdapter implements ProviderAdapter {
|
|
|
2475
2532
|
}
|
|
2476
2533
|
|
|
2477
2534
|
async createSession(config: ProviderSessionConfig): Promise<ProviderSession> {
|
|
2535
|
+
if (deriveDefaultRoute("codex", config.env ?? process.env)) {
|
|
2536
|
+
console.log("\x1b[2m[codex]\x1b[0m Using credential: gateway");
|
|
2537
|
+
}
|
|
2478
2538
|
if (this.bypassSubprocess) {
|
|
2479
2539
|
return createInProcessCodexSession(config, {
|
|
2480
2540
|
skillsDir: this.skillsDir,
|
|
@@ -37,7 +37,7 @@ import type {
|
|
|
37
37
|
} from "./types";
|
|
38
38
|
|
|
39
39
|
// npm's `latest` is older and lacks the JSON/stdin contract used here.
|
|
40
|
-
export const DSH_PACKAGE = "@deepseek-ai/dsh@0.2.1-rc.
|
|
40
|
+
export const DSH_PACKAGE = "@deepseek-ai/dsh@0.2.1-rc.4";
|
|
41
41
|
|
|
42
42
|
export function checkDshCredentials(env: Record<string, string | undefined>): CredStatus {
|
|
43
43
|
return env.DEEPSEEK_API_KEY?.trim() || env.OPENROUTER_API_KEY?.trim()
|
|
@@ -382,14 +382,15 @@ type ToolStructuredContent = NonNullable<AgentToolResult["structuredContent"]>;
|
|
|
382
382
|
export const SWARM_TOOL_NAMESPACE = { name: "agent-swarm" } as const;
|
|
383
383
|
|
|
384
384
|
/**
|
|
385
|
-
* `PI_TOOL_DEFERRAL`: hide non-core swarm tools
|
|
386
|
-
*
|
|
387
|
-
*
|
|
388
|
-
*
|
|
389
|
-
*
|
|
385
|
+
* `PI_TOOL_DEFERRAL`: hide non-core swarm tools, and installed MCP server
|
|
386
|
+
* tools, behind pi's `tool_search`. On by default: the deferred schemas cost
|
|
387
|
+
* more on every request than the one partial cache miss a load causes.
|
|
388
|
+
* `false` declares every tool up front. Read from `process.env` by both the
|
|
389
|
+
* adapter traits (which pick the prompt's tool-discovery line) and
|
|
390
|
+
* `createSession`, so the prompt and the session always agree.
|
|
390
391
|
*/
|
|
391
392
|
export function isPiToolDeferralEnabled(env: NodeJS.ProcessEnv = process.env): boolean {
|
|
392
|
-
return parseEnvFlag(env.PI_TOOL_DEFERRAL,
|
|
393
|
+
return parseEnvFlag(env.PI_TOOL_DEFERRAL, true);
|
|
393
394
|
}
|
|
394
395
|
|
|
395
396
|
/**
|
|
@@ -919,6 +920,28 @@ function requestContextTokens(message: unknown): number | undefined {
|
|
|
919
920
|
return usage.input + cacheRead + cacheWrite;
|
|
920
921
|
}
|
|
921
922
|
|
|
923
|
+
/**
|
|
924
|
+
* The session-log line for one LLM call's usage (token counts only, never
|
|
925
|
+
* content). The dashboard reads it as that call's cache-hit chip.
|
|
926
|
+
*/
|
|
927
|
+
function callUsageLine(message: unknown): string | undefined {
|
|
928
|
+
const usage = (message as { usage?: Record<string, unknown> } | null)?.usage;
|
|
929
|
+
if (typeof usage?.input !== "number") return undefined;
|
|
930
|
+
const count = (value: unknown) =>
|
|
931
|
+
typeof value === "number" && Number.isFinite(value) ? value : 0;
|
|
932
|
+
return JSON.stringify({
|
|
933
|
+
type: "call_usage",
|
|
934
|
+
harness: "pi",
|
|
935
|
+
usage: {
|
|
936
|
+
input: count(usage.input),
|
|
937
|
+
cacheRead: count(usage.cacheRead),
|
|
938
|
+
cacheWrite: count(usage.cacheWrite),
|
|
939
|
+
output: count(usage.output),
|
|
940
|
+
reasoning: count(usage.reasoning),
|
|
941
|
+
},
|
|
942
|
+
});
|
|
943
|
+
}
|
|
944
|
+
|
|
922
945
|
function describeAssistantTurn(message: unknown): AssistantTurnShape {
|
|
923
946
|
const msg = (message ?? {}) as {
|
|
924
947
|
content?: unknown;
|
|
@@ -1116,6 +1139,10 @@ export class PiMonoSession implements ProviderSession {
|
|
|
1116
1139
|
if (endMsg.role === "assistant") {
|
|
1117
1140
|
const turn = describeAssistantTurn(event.message);
|
|
1118
1141
|
this.lastAssistantTurn = turn;
|
|
1142
|
+
// Ahead of the turn's text and tool lines: the chip rides on the
|
|
1143
|
+
// call's first row.
|
|
1144
|
+
const usageLine = callUsageLine(event.message);
|
|
1145
|
+
if (usageLine) this.emit({ type: "raw_log", content: usageLine });
|
|
1119
1146
|
if (endMsg.stopReason === "error") {
|
|
1120
1147
|
// Candidate terminal failure. May still be cleared by a successful
|
|
1121
1148
|
// retry (auto_retry_end success / a later good message_end).
|
|
@@ -1684,12 +1711,15 @@ function escapeValues(values: unknown): Record<string, string> {
|
|
|
1684
1711
|
/**
|
|
1685
1712
|
* Map installed servers (`fetchInstalledMcpServers(..., "claude")` entries)
|
|
1686
1713
|
* to pi MCP configs. pi speaks stdio and streamable HTTP; `sse` entries map to
|
|
1687
|
-
* HTTP, which is how the previous pi client already reached them.
|
|
1688
|
-
* `
|
|
1714
|
+
* HTTP, which is how the previous pi client already reached them. With
|
|
1715
|
+
* `deferred`, their tools load through `tool_search` like non-core swarm
|
|
1716
|
+
* tools; otherwise they are declared up front.
|
|
1689
1717
|
*/
|
|
1690
1718
|
export function toPiMcpServers(
|
|
1691
1719
|
installed: Record<string, Record<string, unknown>> | null,
|
|
1720
|
+
options: { deferred?: boolean } = {},
|
|
1692
1721
|
): Record<string, McpServerConfig> {
|
|
1722
|
+
const exposure = options.deferred ? "deferred" : "direct";
|
|
1693
1723
|
const servers: Record<string, McpServerConfig> = {};
|
|
1694
1724
|
for (const [name, entry] of Object.entries(installed ?? {})) {
|
|
1695
1725
|
if (typeof entry.command === "string") {
|
|
@@ -1698,14 +1728,14 @@ export function toPiMcpServers(
|
|
|
1698
1728
|
command: entry.command,
|
|
1699
1729
|
args: Array.isArray(entry.args) ? entry.args.map(String) : [],
|
|
1700
1730
|
env: escapeValues(entry.env),
|
|
1701
|
-
exposure
|
|
1731
|
+
exposure,
|
|
1702
1732
|
};
|
|
1703
1733
|
} else if (typeof entry.url === "string") {
|
|
1704
1734
|
servers[name] = {
|
|
1705
1735
|
type: "http",
|
|
1706
1736
|
url: entry.url,
|
|
1707
1737
|
headers: escapeValues(entry.headers),
|
|
1708
|
-
exposure
|
|
1738
|
+
exposure,
|
|
1709
1739
|
};
|
|
1710
1740
|
}
|
|
1711
1741
|
}
|
|
@@ -1860,7 +1890,7 @@ export class PiMonoAdapter implements ProviderAdapter {
|
|
|
1860
1890
|
config.apiUrl && config.apiKey && config.agentId
|
|
1861
1891
|
? await fetchInstalledMcpServers(config.apiUrl, config.apiKey, config.agentId, "claude")
|
|
1862
1892
|
: null;
|
|
1863
|
-
const piMcpServers = toPiMcpServers(installedMcpServers);
|
|
1893
|
+
const piMcpServers = toPiMcpServers(installedMcpServers, { deferred: deferTools });
|
|
1864
1894
|
if (Object.keys(piMcpServers).length > 0) {
|
|
1865
1895
|
console.log(
|
|
1866
1896
|
`\x1b[2m[${config.role}]\x1b[0m Registering ${Object.keys(piMcpServers).length} installed MCP server(s) with pi`,
|
|
@@ -75,7 +75,7 @@ function resolveExecutorRegistry(): ExecutorRegistry | null {
|
|
|
75
75
|
* scripts-runtime — no agent/LLM in the loop. Reuses the same `runScriptWithRunCredential()`
|
|
76
76
|
* path as the `swarm-script` workflow executor.
|
|
77
77
|
*/
|
|
78
|
-
async function executeScheduleScript(schedule: ScheduledTask): Promise<void> {
|
|
78
|
+
export async function executeScheduleScript(schedule: ScheduledTask): Promise<void> {
|
|
79
79
|
if (!schedule.scriptName) {
|
|
80
80
|
throw new Error(`Schedule "${schedule.name}" has no scriptName (targetType=script)`);
|
|
81
81
|
}
|
|
@@ -91,8 +91,9 @@ async function executeScheduleScript(schedule: ScheduledTask): Promise<void> {
|
|
|
91
91
|
agentId,
|
|
92
92
|
scriptName: schedule.scriptName,
|
|
93
93
|
// A pointer, not a copy: script schedules fire as often as every minute and
|
|
94
|
-
// script_runs
|
|
95
|
-
// would grow the table by tens of MB a
|
|
94
|
+
// script_runs retention (SCRIPT_RUNS_RETENTION_DAYS) is off by default, so
|
|
95
|
+
// copying the source on every fire would grow the table by tens of MB a
|
|
96
|
+
// day. script_versions keeps the text.
|
|
96
97
|
source: `// Scheduled run of '${script.name}' v${script.version} (global, contentHash ${script.contentHash}) by schedule '${schedule.name}'.\n// Full source: script_versions row for this version.\n`,
|
|
97
98
|
args,
|
|
98
99
|
startedAt: new Date().toISOString(),
|
|
@@ -105,6 +106,7 @@ async function executeScheduleScript(schedule: ScheduledTask): Promise<void> {
|
|
|
105
106
|
source: script.source,
|
|
106
107
|
args,
|
|
107
108
|
fsMode: "none",
|
|
109
|
+
executor: script.executor,
|
|
108
110
|
agentId,
|
|
109
111
|
egressSecrets: credentials.egressSecrets,
|
|
110
112
|
userConfig: credentials.userConfig,
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { buildCtx } from "./ctx";
|
|
2
2
|
import { patchFetchWithEgressSubstitution } from "./egress-secrets";
|
|
3
3
|
import type { SwarmConfigPayload } from "./executors/types";
|
|
4
|
+
import { isHostValidationEquivalent } from "./host-validated-args";
|
|
4
5
|
import { ctxSignatureHintFor } from "./script-error-hints";
|
|
5
6
|
import { SwarmConfig } from "./swarm-config";
|
|
6
7
|
|
|
@@ -131,8 +132,14 @@ try {
|
|
|
131
132
|
);
|
|
132
133
|
}
|
|
133
134
|
|
|
135
|
+
const hostValidatedArgs = process.env.SWARM_SCRIPT_HOST_VALIDATED_ARGS === "1";
|
|
134
136
|
let validatedArgs = parsedArgs;
|
|
135
|
-
if (
|
|
137
|
+
if (
|
|
138
|
+
mod.argsSchema &&
|
|
139
|
+
typeof mod.argsSchema === "object" &&
|
|
140
|
+
"parse" in mod.argsSchema &&
|
|
141
|
+
!(hostValidatedArgs && isHostValidationEquivalent(mod.argsSchema, parsedArgs))
|
|
142
|
+
) {
|
|
136
143
|
try {
|
|
137
144
|
// biome-ignore lint/suspicious/noExplicitAny: argsSchema is a Zod schema at runtime
|
|
138
145
|
validatedArgs = (mod.argsSchema as any).parse(parsedArgs);
|
|
@@ -173,23 +173,21 @@ export class NativeScriptExecutor implements ScriptExecutor {
|
|
|
173
173
|
let removeAbortListener: (() => void) | undefined;
|
|
174
174
|
let proc: Bun.Subprocess<"pipe", "pipe", "pipe"> | undefined;
|
|
175
175
|
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
error: "killed",
|
|
186
|
-
};
|
|
187
|
-
}
|
|
176
|
+
const killedOutput = (): ExecutorOutput => ({
|
|
177
|
+
result: undefined,
|
|
178
|
+
stdout: "",
|
|
179
|
+
stderr: "",
|
|
180
|
+
truncated: { stdout: false, stderr: false },
|
|
181
|
+
durationMs: Date.now() - start,
|
|
182
|
+
exitCode: 1,
|
|
183
|
+
error: "killed",
|
|
184
|
+
});
|
|
188
185
|
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
await writeBareImportShims(tmpdir);
|
|
186
|
+
try {
|
|
187
|
+
if (killed) return killedOutput();
|
|
192
188
|
|
|
189
|
+
// Listen before the first await: an abort during the file writes below
|
|
190
|
+
// must stop the spawn, not land between the writes and the listener.
|
|
193
191
|
const onExternalAbort = () => {
|
|
194
192
|
killed = true;
|
|
195
193
|
if (proc) void terminateProcessGroup(proc.pid);
|
|
@@ -197,12 +195,19 @@ export class NativeScriptExecutor implements ScriptExecutor {
|
|
|
197
195
|
input.signal?.addEventListener("abort", onExternalAbort, { once: true });
|
|
198
196
|
removeAbortListener = () => input.signal?.removeEventListener("abort", onExternalAbort);
|
|
199
197
|
|
|
198
|
+
await Bun.write(argsFile, JSON.stringify(input.args ?? null));
|
|
199
|
+
await Bun.write(sourceFile, input.source);
|
|
200
|
+
await writeBareImportShims(tmpdir);
|
|
201
|
+
|
|
202
|
+
// No await between this check and the spawn below.
|
|
203
|
+
if (killed) return killedOutput();
|
|
204
|
+
|
|
200
205
|
const timeout = setTimeout(() => {
|
|
201
206
|
timedOut = true;
|
|
202
207
|
if (proc) void terminateProcessGroup(proc.pid);
|
|
203
208
|
}, input.resources.wallClockMs);
|
|
204
209
|
|
|
205
|
-
const harnessEnv = {
|
|
210
|
+
const harnessEnv: Record<string, string> = {
|
|
206
211
|
PATH: process.env.PATH ?? "/usr/bin:/bin",
|
|
207
212
|
HOME: process.env.HOME ?? "/tmp",
|
|
208
213
|
LANG: process.env.LANG ?? "C.UTF-8",
|
|
@@ -214,6 +219,7 @@ export class NativeScriptExecutor implements ScriptExecutor {
|
|
|
214
219
|
SWARM_SCRIPT_RESULT_FILE: resultFile,
|
|
215
220
|
SWARM_SCRIPT_ERROR_FILE: errorFile,
|
|
216
221
|
SWARM_SCRIPT_STARTED_FILE: startedFile,
|
|
222
|
+
...(input.hostValidatedArgs ? { SWARM_SCRIPT_HOST_VALIDATED_ARGS: "1" } : {}),
|
|
217
223
|
};
|
|
218
224
|
|
|
219
225
|
proc = registerProcessGroup(
|
|
@@ -308,14 +308,116 @@ define("__swarm_ctx", ctx);
|
|
|
308
308
|
* zod `argsSchema` check, the same contract as `eval-harness.ts`.
|
|
309
309
|
*/
|
|
310
310
|
export const SANDBOX_EPILOGUE = String.raw`(async () => {
|
|
311
|
+
const isStructuralSchemaNode = (schema) => {
|
|
312
|
+
if (!schema || typeof schema !== "object") return false;
|
|
313
|
+
const def = schema._zod && schema._zod.def;
|
|
314
|
+
if (!def || typeof def !== "object") return false;
|
|
315
|
+
if (def.coerce) return false;
|
|
316
|
+
if (Array.isArray(def.checks) && def.checks.length > 0) return false;
|
|
317
|
+
switch (def.type) {
|
|
318
|
+
case "string":
|
|
319
|
+
case "number":
|
|
320
|
+
return !def.format;
|
|
321
|
+
case "boolean":
|
|
322
|
+
case "literal":
|
|
323
|
+
case "enum":
|
|
324
|
+
return true;
|
|
325
|
+
case "optional":
|
|
326
|
+
case "nullable":
|
|
327
|
+
return isStructuralSchemaNode(def.innerType);
|
|
328
|
+
case "array":
|
|
329
|
+
return isStructuralSchemaNode(def.element);
|
|
330
|
+
case "object": {
|
|
331
|
+
const rawShape = typeof def.shape === "function" ? def.shape() : def.shape;
|
|
332
|
+
if (!rawShape || typeof rawShape !== "object") return false;
|
|
333
|
+
return Object.values(rawShape).every(isStructuralSchemaNode);
|
|
334
|
+
}
|
|
335
|
+
default:
|
|
336
|
+
return false;
|
|
337
|
+
}
|
|
338
|
+
};
|
|
339
|
+
const isHostValidationEquivalent = (schema, value) => {
|
|
340
|
+
if (!schema || typeof schema !== "object") return false;
|
|
341
|
+
const def = schema._zod && schema._zod.def;
|
|
342
|
+
if (!def || typeof def !== "object") return false;
|
|
343
|
+
if (def.coerce) return false;
|
|
344
|
+
if (Array.isArray(def.checks) && def.checks.length > 0) return false;
|
|
345
|
+
switch (def.type) {
|
|
346
|
+
case "string":
|
|
347
|
+
return typeof value === "string" && !def.format;
|
|
348
|
+
case "number":
|
|
349
|
+
return typeof value === "number" && Number.isFinite(value) && !def.format;
|
|
350
|
+
case "boolean":
|
|
351
|
+
return typeof value === "boolean";
|
|
352
|
+
case "literal": {
|
|
353
|
+
const rawValues = def.values;
|
|
354
|
+
const values = Array.isArray(rawValues)
|
|
355
|
+
? rawValues
|
|
356
|
+
: rawValues instanceof Set
|
|
357
|
+
? Array.from(rawValues)
|
|
358
|
+
: [];
|
|
359
|
+
return (
|
|
360
|
+
values.length > 0 &&
|
|
361
|
+
values.some((v) => v === value && typeof v !== "bigint" && typeof v !== "symbol")
|
|
362
|
+
);
|
|
363
|
+
}
|
|
364
|
+
case "enum": {
|
|
365
|
+
if (schema._zod && schema._zod.values instanceof Set) {
|
|
366
|
+
return schema._zod.values.has(value);
|
|
367
|
+
}
|
|
368
|
+
const entriesObj = def.entries && typeof def.entries === "object" ? def.entries : null;
|
|
369
|
+
if (!entriesObj) return false;
|
|
370
|
+
const entries = Object.values(entriesObj).filter(
|
|
371
|
+
(v) => !(typeof v === "string" && typeof entriesObj[v] === "number"),
|
|
372
|
+
);
|
|
373
|
+
return entries.length > 0 && entries.includes(value);
|
|
374
|
+
}
|
|
375
|
+
case "optional":
|
|
376
|
+
return value === undefined
|
|
377
|
+
? isStructuralSchemaNode(def.innerType)
|
|
378
|
+
: isHostValidationEquivalent(def.innerType, value);
|
|
379
|
+
case "nullable":
|
|
380
|
+
return value === null
|
|
381
|
+
? isStructuralSchemaNode(def.innerType)
|
|
382
|
+
: isHostValidationEquivalent(def.innerType, value);
|
|
383
|
+
case "array":
|
|
384
|
+
return (
|
|
385
|
+
Array.isArray(value) &&
|
|
386
|
+
isStructuralSchemaNode(def.element) &&
|
|
387
|
+
value.every((item) => isHostValidationEquivalent(def.element, item))
|
|
388
|
+
);
|
|
389
|
+
case "object": {
|
|
390
|
+
if (typeof value !== "object" || value === null || Array.isArray(value)) return false;
|
|
391
|
+
const shape = typeof def.shape === "function" ? def.shape() : def.shape;
|
|
392
|
+
if (!shape || typeof shape !== "object") return false;
|
|
393
|
+
for (const key of Object.keys(value)) {
|
|
394
|
+
if (!Object.hasOwn(shape, key)) return false;
|
|
395
|
+
}
|
|
396
|
+
for (const [key, propSchema] of Object.entries(shape)) {
|
|
397
|
+
if (!Object.hasOwn(value, key) && key in value) return false;
|
|
398
|
+
const propValue = value[key];
|
|
399
|
+
if (!isHostValidationEquivalent(propSchema, propValue)) return false;
|
|
400
|
+
}
|
|
401
|
+
return true;
|
|
402
|
+
}
|
|
403
|
+
default:
|
|
404
|
+
return false;
|
|
405
|
+
}
|
|
406
|
+
};
|
|
311
407
|
const mod = globalThis.__swarm_module;
|
|
312
408
|
if (!mod || typeof mod.default !== "function") {
|
|
313
409
|
throw new Error(
|
|
314
410
|
"Swarm script must export a default function. Script must export default async function (args, ctx): args FIRST, ctx second.",
|
|
315
411
|
);
|
|
316
412
|
}
|
|
413
|
+
const hostValidatedArgs = globalThis.__swarm_host_validated_args === true;
|
|
317
414
|
let args = globalThis.__swarm_args;
|
|
318
|
-
if (
|
|
415
|
+
if (
|
|
416
|
+
mod.argsSchema &&
|
|
417
|
+
typeof mod.argsSchema === "object" &&
|
|
418
|
+
"parse" in mod.argsSchema &&
|
|
419
|
+
!(hostValidatedArgs && isHostValidationEquivalent(mod.argsSchema, args))
|
|
420
|
+
) {
|
|
319
421
|
try {
|
|
320
422
|
args = mod.argsSchema.parse(args);
|
|
321
423
|
} catch (error) {
|
|
@@ -29,6 +29,7 @@ import type {
|
|
|
29
29
|
export type QuickJSJob = {
|
|
30
30
|
source: string;
|
|
31
31
|
args: unknown;
|
|
32
|
+
hostValidatedArgs?: boolean;
|
|
32
33
|
configPayload: SwarmConfigPayload;
|
|
33
34
|
resources: ScriptResourcePolicy;
|
|
34
35
|
network: ExecutorInput["network"];
|
|
@@ -656,7 +657,7 @@ async function evaluateJob(
|
|
|
656
657
|
|
|
657
658
|
let timer: ReturnType<typeof setTimeout> | undefined;
|
|
658
659
|
try {
|
|
659
|
-
const init = `globalThis.__swarm_shape = ${JSON.stringify(shape)};\nglobalThis.__swarm_args = ${JSON.stringify(args)};`;
|
|
660
|
+
const init = `globalThis.__swarm_shape = ${JSON.stringify(shape)};\nglobalThis.__swarm_args = ${JSON.stringify(args)};\nglobalThis.__swarm_host_validated_args = ${Boolean(job.hostValidatedArgs)};`;
|
|
660
661
|
const ok =
|
|
661
662
|
evaluate(init, "swarm-init.js") &&
|
|
662
663
|
evaluate(SANDBOX_WEB_SHIMS, "swarm-web-shims.js") &&
|
|
@@ -146,6 +146,12 @@ export class QuickJSScriptExecutor implements ScriptExecutor {
|
|
|
146
146
|
`quickjs worker failed to start: ${error instanceof Error ? error.message : String(error)}`,
|
|
147
147
|
);
|
|
148
148
|
}
|
|
149
|
+
// An abort while the worker booted fired before the listener below
|
|
150
|
+
// exists. From here to postMessage nothing awaits.
|
|
151
|
+
if (input.signal?.aborted) {
|
|
152
|
+
pool.release(slot);
|
|
153
|
+
return emptyOutput("killed");
|
|
154
|
+
}
|
|
149
155
|
|
|
150
156
|
const id = ++requestSeq;
|
|
151
157
|
const start = performance.now();
|
|
@@ -196,6 +202,7 @@ export class QuickJSScriptExecutor implements ScriptExecutor {
|
|
|
196
202
|
job: {
|
|
197
203
|
source: input.source,
|
|
198
204
|
args: input.args,
|
|
205
|
+
...(input.hostValidatedArgs ? { hostValidatedArgs: true } : {}),
|
|
199
206
|
configPayload: input.configPayload,
|
|
200
207
|
resources: input.resources,
|
|
201
208
|
network: input.network,
|
|
@@ -7,6 +7,27 @@ const EXECUTORS: Record<string, () => ScriptExecutor> = {
|
|
|
7
7
|
quickjs: () => new QuickJSScriptExecutor(),
|
|
8
8
|
};
|
|
9
9
|
|
|
10
|
+
export function getDeploymentScriptExecutorName(env: NodeJS.ProcessEnv = process.env): string {
|
|
11
|
+
return (env.SCRIPT_EXECUTOR ?? "native").trim();
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* Operator sandbox ceiling: when the deployment sets `SCRIPT_EXECUTOR=quickjs`,
|
|
16
|
+
* individual scripts or agent calls may not escalate to the less-restricted
|
|
17
|
+
* `native` executor. When the deployment runs `native` (the default), scripts
|
|
18
|
+
* may opt down into `quickjs`.
|
|
19
|
+
*/
|
|
20
|
+
export function isExecutorAllowedByDeployment(
|
|
21
|
+
requested: string | null | undefined,
|
|
22
|
+
env: NodeJS.ProcessEnv = process.env,
|
|
23
|
+
): boolean {
|
|
24
|
+
if (!requested) return true;
|
|
25
|
+
if (getDeploymentScriptExecutorName(env) === "quickjs" && requested.trim() === "native") {
|
|
26
|
+
return false;
|
|
27
|
+
}
|
|
28
|
+
return true;
|
|
29
|
+
}
|
|
30
|
+
|
|
10
31
|
export function getScriptExecutor(name = process.env.SCRIPT_EXECUTOR ?? "native"): ScriptExecutor {
|
|
11
32
|
const factory = EXECUTORS[name.trim()];
|
|
12
33
|
if (!factory) {
|
|
@@ -16,3 +37,14 @@ export function getScriptExecutor(name = process.env.SCRIPT_EXECUTOR ?? "native"
|
|
|
16
37
|
}
|
|
17
38
|
return factory();
|
|
18
39
|
}
|
|
40
|
+
|
|
41
|
+
export function resolveScriptExecutor(
|
|
42
|
+
requested?: string | null,
|
|
43
|
+
env: NodeJS.ProcessEnv = process.env,
|
|
44
|
+
): ScriptExecutor {
|
|
45
|
+
if (!isExecutorAllowedByDeployment(requested, env)) {
|
|
46
|
+
throw new Error('Executor "native" is not permitted when deployment SCRIPT_EXECUTOR="quickjs"');
|
|
47
|
+
}
|
|
48
|
+
const effective = requested ?? env.SCRIPT_EXECUTOR ?? "native";
|
|
49
|
+
return getScriptExecutor(effective);
|
|
50
|
+
}
|
|
@@ -3,6 +3,7 @@ import type { ScriptApiConnectionDescriptor, ScriptMcpConnectionDescriptor } fro
|
|
|
3
3
|
import type { FailedCredentialBinding, ResolvedCredentialBinding } from "../credential-broker";
|
|
4
4
|
|
|
5
5
|
export type ScriptFsMode = "none" | "workspace-rw";
|
|
6
|
+
export type ScriptExecutorName = "native" | "quickjs";
|
|
6
7
|
|
|
7
8
|
export type EgressSecretEntry = ResolvedCredentialBinding;
|
|
8
9
|
|
|
@@ -49,6 +50,12 @@ export type ScriptResourcePolicy = {
|
|
|
49
50
|
export type ExecutorInput = {
|
|
50
51
|
source: string;
|
|
51
52
|
args: unknown;
|
|
53
|
+
/**
|
|
54
|
+
* Trusted host-only flag indicating `args` already passed host JSON Schema
|
|
55
|
+
* validation for this run. Executors may skip `argsSchema.parse(args)` only
|
|
56
|
+
* when this flag is true AND the runtime `argsSchema` is purely structural.
|
|
57
|
+
*/
|
|
58
|
+
hostValidatedArgs?: boolean;
|
|
52
59
|
configPayload: SwarmConfigPayload;
|
|
53
60
|
resources: ScriptResourcePolicy;
|
|
54
61
|
fsMode: ScriptFsMode;
|