@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
|
@@ -0,0 +1,415 @@
|
|
|
1
|
+
import { afterEach, beforeAll, describe, expect, test } from "bun:test";
|
|
2
|
+
import type { IncomingMessage, ServerResponse } from "node:http";
|
|
3
|
+
import os from "node:os";
|
|
4
|
+
import { Readable } from "node:stream";
|
|
5
|
+
import { createAgent, getScriptRun, initDb, listScriptRuns } from "../be/db";
|
|
6
|
+
import { getScriptById, insertScript, upsertScriptByName } from "../be/scripts/db";
|
|
7
|
+
import { setScriptEmbeddingProviderForTests } from "../be/scripts/embeddings";
|
|
8
|
+
import { runSavedScriptAsAgent } from "../be/scripts/run-saved";
|
|
9
|
+
import { typecheckScript } from "../be/scripts/typecheck";
|
|
10
|
+
import { handleScripts } from "../http/scripts";
|
|
11
|
+
import {
|
|
12
|
+
isExecutorAllowedByDeployment,
|
|
13
|
+
resolveScriptExecutor,
|
|
14
|
+
} from "../scripts-runtime/executors/registry";
|
|
15
|
+
import { runScript } from "../scripts-runtime/loader";
|
|
16
|
+
import { type ScheduledTask, ScriptExecutorNameSchema, ScriptRecordSchema } from "../types";
|
|
17
|
+
import { SwarmScriptExecutor } from "../workflows/executors/swarm-script";
|
|
18
|
+
|
|
19
|
+
process.env.TMPDIR ??= os.tmpdir().replaceAll("\\", "/");
|
|
20
|
+
process.env.API_KEY ??= "test-per-script-executor-api-key";
|
|
21
|
+
process.env.MCP_BASE_URL ??= "http://127.0.0.1:19999";
|
|
22
|
+
|
|
23
|
+
const RUNTIME_PROBE_SOURCE =
|
|
24
|
+
'export default async () => ({ hasProcess: typeof (globalThis as { process?: unknown }).process !== "undefined" });';
|
|
25
|
+
|
|
26
|
+
const savedScriptExecutorEnv = process.env.SCRIPT_EXECUTOR;
|
|
27
|
+
|
|
28
|
+
afterEach(() => {
|
|
29
|
+
if (savedScriptExecutorEnv === undefined) {
|
|
30
|
+
delete process.env.SCRIPT_EXECUTOR;
|
|
31
|
+
} else {
|
|
32
|
+
process.env.SCRIPT_EXECUTOR = savedScriptExecutorEnv;
|
|
33
|
+
}
|
|
34
|
+
});
|
|
35
|
+
|
|
36
|
+
describe("per-script executor schema (#2013)", () => {
|
|
37
|
+
test("ScriptExecutorNameSchema accepts native and quickjs and rejects invalid names", () => {
|
|
38
|
+
expect(ScriptExecutorNameSchema.safeParse("native").success).toBe(true);
|
|
39
|
+
expect(ScriptExecutorNameSchema.safeParse("quickjs").success).toBe(true);
|
|
40
|
+
expect(ScriptExecutorNameSchema.safeParse("e2b").success).toBe(false);
|
|
41
|
+
expect(ScriptExecutorNameSchema.safeParse("").success).toBe(false);
|
|
42
|
+
});
|
|
43
|
+
|
|
44
|
+
test("ScriptRecordSchema defaults executor to null and accepts explicit executor", () => {
|
|
45
|
+
const base = {
|
|
46
|
+
id: "00000000-0000-0000-0000-000000000001",
|
|
47
|
+
name: "demo",
|
|
48
|
+
scope: "agent" as const,
|
|
49
|
+
scopeId: "00000000-0000-0000-0000-000000000002",
|
|
50
|
+
source: RUNTIME_PROBE_SOURCE,
|
|
51
|
+
description: "",
|
|
52
|
+
intent: "",
|
|
53
|
+
signatureJson: "{}",
|
|
54
|
+
argsJsonSchema: null,
|
|
55
|
+
contentHash: "abc",
|
|
56
|
+
version: 1,
|
|
57
|
+
isScratch: false,
|
|
58
|
+
typeChecked: true,
|
|
59
|
+
fsMode: "none" as const,
|
|
60
|
+
createdByAgentId: null,
|
|
61
|
+
createdAt: "2026-10-26T00:00:00.000Z",
|
|
62
|
+
updatedAt: "2026-10-26T00:00:00.000Z",
|
|
63
|
+
};
|
|
64
|
+
|
|
65
|
+
expect(ScriptRecordSchema.parse(base).executor).toBeNull();
|
|
66
|
+
expect(ScriptRecordSchema.parse({ ...base, executor: "quickjs" }).executor).toBe("quickjs");
|
|
67
|
+
expect(ScriptRecordSchema.parse({ ...base, executor: "native" }).executor).toBe("native");
|
|
68
|
+
});
|
|
69
|
+
});
|
|
70
|
+
|
|
71
|
+
describe("runScript per-script executor precedence (#2013)", () => {
|
|
72
|
+
test("explicit executor: 'quickjs' overrides SCRIPT_EXECUTOR=native", async () => {
|
|
73
|
+
process.env.SCRIPT_EXECUTOR = "native";
|
|
74
|
+
const out = await runScript({
|
|
75
|
+
source: RUNTIME_PROBE_SOURCE,
|
|
76
|
+
agentId: "00000000-0000-0000-0000-000000000001",
|
|
77
|
+
executor: "quickjs",
|
|
78
|
+
egressSecrets: [],
|
|
79
|
+
});
|
|
80
|
+
expect(out.exitCode).toBe(0);
|
|
81
|
+
expect(out.error).toBeUndefined();
|
|
82
|
+
expect(out.result).toEqual({ hasProcess: false });
|
|
83
|
+
});
|
|
84
|
+
|
|
85
|
+
test("explicit executor: 'native' is denied when deployment SCRIPT_EXECUTOR=quickjs", async () => {
|
|
86
|
+
process.env.SCRIPT_EXECUTOR = "quickjs";
|
|
87
|
+
expect(isExecutorAllowedByDeployment("native")).toBe(false);
|
|
88
|
+
expect(isExecutorAllowedByDeployment("quickjs")).toBe(true);
|
|
89
|
+
expect(isExecutorAllowedByDeployment(null)).toBe(true);
|
|
90
|
+
expect(() => resolveScriptExecutor("native")).toThrow(
|
|
91
|
+
'Executor "native" is not permitted when deployment SCRIPT_EXECUTOR="quickjs"',
|
|
92
|
+
);
|
|
93
|
+
|
|
94
|
+
let canaryHits = 0;
|
|
95
|
+
const server = Bun.serve({
|
|
96
|
+
hostname: "127.0.0.1",
|
|
97
|
+
port: 0,
|
|
98
|
+
fetch() {
|
|
99
|
+
canaryHits += 1;
|
|
100
|
+
return new Response("reached");
|
|
101
|
+
},
|
|
102
|
+
});
|
|
103
|
+
try {
|
|
104
|
+
const out = await runScript({
|
|
105
|
+
source: `export default async () => { await fetch("http://127.0.0.1:${server.port}/canary"); return { ok: true }; };`,
|
|
106
|
+
agentId: "00000000-0000-0000-0000-000000000001",
|
|
107
|
+
executor: "native",
|
|
108
|
+
egressSecrets: [],
|
|
109
|
+
});
|
|
110
|
+
expect(out.exitCode).toBe(1);
|
|
111
|
+
expect(out.error).toBe("executor_error");
|
|
112
|
+
expect(out.stderr).toContain(
|
|
113
|
+
'Executor "native" is not permitted when deployment SCRIPT_EXECUTOR="quickjs"',
|
|
114
|
+
);
|
|
115
|
+
expect(canaryHits).toBe(0);
|
|
116
|
+
} finally {
|
|
117
|
+
server.stop(true);
|
|
118
|
+
}
|
|
119
|
+
});
|
|
120
|
+
|
|
121
|
+
test("null or omitted executor falls back to SCRIPT_EXECUTOR", async () => {
|
|
122
|
+
process.env.SCRIPT_EXECUTOR = "quickjs";
|
|
123
|
+
const quickjsDefault = await runScript({
|
|
124
|
+
source: RUNTIME_PROBE_SOURCE,
|
|
125
|
+
agentId: "00000000-0000-0000-0000-000000000001",
|
|
126
|
+
executor: null,
|
|
127
|
+
egressSecrets: [],
|
|
128
|
+
});
|
|
129
|
+
expect(quickjsDefault.exitCode).toBe(0);
|
|
130
|
+
expect(quickjsDefault.result).toEqual({ hasProcess: false });
|
|
131
|
+
});
|
|
132
|
+
|
|
133
|
+
test("null or omitted executor falls back to native when SCRIPT_EXECUTOR is unset", async () => {
|
|
134
|
+
delete process.env.SCRIPT_EXECUTOR;
|
|
135
|
+
const nativeDefault = await runScript({
|
|
136
|
+
source: RUNTIME_PROBE_SOURCE,
|
|
137
|
+
agentId: "00000000-0000-0000-0000-000000000001",
|
|
138
|
+
executor: null,
|
|
139
|
+
egressSecrets: [],
|
|
140
|
+
});
|
|
141
|
+
expect(nativeDefault.exitCode).toBe(0);
|
|
142
|
+
expect(nativeDefault.result).toEqual({ hasProcess: true });
|
|
143
|
+
}, 15_000);
|
|
144
|
+
});
|
|
145
|
+
|
|
146
|
+
describe("scripts DB & runSavedScriptAsAgent executor persistence (#2013)", () => {
|
|
147
|
+
let agentId: string;
|
|
148
|
+
|
|
149
|
+
beforeAll(async () => {
|
|
150
|
+
initDb(":memory:");
|
|
151
|
+
setScriptEmbeddingProviderForTests({
|
|
152
|
+
name: "test/noop",
|
|
153
|
+
dimensions: 1,
|
|
154
|
+
async embed() {
|
|
155
|
+
return null;
|
|
156
|
+
},
|
|
157
|
+
async embedBatch(texts) {
|
|
158
|
+
return texts.map(() => null);
|
|
159
|
+
},
|
|
160
|
+
});
|
|
161
|
+
const agent = await createAgent({ name: "exec-select-worker", isLead: false, status: "idle" });
|
|
162
|
+
agentId = agent.id;
|
|
163
|
+
});
|
|
164
|
+
|
|
165
|
+
test("insertScript and upsertScriptByName persist executor and runSavedScriptAsAgent uses it", async () => {
|
|
166
|
+
const inserted = await insertScript({
|
|
167
|
+
name: `exec-probe-${crypto.randomUUID().slice(0, 8)}`,
|
|
168
|
+
scope: "agent",
|
|
169
|
+
scopeId: agentId,
|
|
170
|
+
source: RUNTIME_PROBE_SOURCE,
|
|
171
|
+
description: "probe",
|
|
172
|
+
intent: "test",
|
|
173
|
+
signatureJson: "{}",
|
|
174
|
+
executor: "quickjs",
|
|
175
|
+
agentId,
|
|
176
|
+
typeChecked: true,
|
|
177
|
+
});
|
|
178
|
+
expect(inserted.executor).toBe("quickjs");
|
|
179
|
+
expect((await getScriptById(inserted.id))?.executor).toBe("quickjs");
|
|
180
|
+
|
|
181
|
+
process.env.SCRIPT_EXECUTOR = "native";
|
|
182
|
+
const quickjsOut = await runSavedScriptAsAgent(
|
|
183
|
+
{ script: inserted, input: null, agentId },
|
|
184
|
+
{
|
|
185
|
+
buildCredentials: async () => ({
|
|
186
|
+
egressSecrets: [],
|
|
187
|
+
failedBindings: [],
|
|
188
|
+
userConfig: {},
|
|
189
|
+
}),
|
|
190
|
+
},
|
|
191
|
+
);
|
|
192
|
+
expect(quickjsOut.exitCode).toBe(0);
|
|
193
|
+
expect(quickjsOut.result).toEqual({ hasProcess: false });
|
|
194
|
+
|
|
195
|
+
// Metadata-only upsert changing executor from "quickjs" to "native"
|
|
196
|
+
const updated = await upsertScriptByName({
|
|
197
|
+
name: inserted.name,
|
|
198
|
+
scope: "agent",
|
|
199
|
+
scopeId: agentId,
|
|
200
|
+
source: RUNTIME_PROBE_SOURCE,
|
|
201
|
+
description: "probe",
|
|
202
|
+
intent: "test",
|
|
203
|
+
signatureJson: "{}",
|
|
204
|
+
executor: "native",
|
|
205
|
+
agentId,
|
|
206
|
+
typeChecked: true,
|
|
207
|
+
});
|
|
208
|
+
expect(updated.contentDeduped).toBe(true);
|
|
209
|
+
expect(updated.script.executor).toBe("native");
|
|
210
|
+
|
|
211
|
+
process.env.SCRIPT_EXECUTOR = "native";
|
|
212
|
+
const nativeOut = await runSavedScriptAsAgent(
|
|
213
|
+
{ script: updated.script, input: null, agentId },
|
|
214
|
+
{
|
|
215
|
+
buildCredentials: async () => ({
|
|
216
|
+
egressSecrets: [],
|
|
217
|
+
failedBindings: [],
|
|
218
|
+
userConfig: {},
|
|
219
|
+
}),
|
|
220
|
+
},
|
|
221
|
+
);
|
|
222
|
+
expect(nativeOut.exitCode).toBe(0);
|
|
223
|
+
expect(nativeOut.result).toEqual({ hasProcess: true });
|
|
224
|
+
|
|
225
|
+
// Under operator ceiling SCRIPT_EXECUTOR=quickjs, saved native script is denied
|
|
226
|
+
process.env.SCRIPT_EXECUTOR = "quickjs";
|
|
227
|
+
const deniedOut = await runSavedScriptAsAgent(
|
|
228
|
+
{ script: updated.script, input: null, agentId },
|
|
229
|
+
{
|
|
230
|
+
buildCredentials: async () => ({
|
|
231
|
+
egressSecrets: [],
|
|
232
|
+
failedBindings: [],
|
|
233
|
+
userConfig: {},
|
|
234
|
+
}),
|
|
235
|
+
},
|
|
236
|
+
);
|
|
237
|
+
expect(deniedOut.exitCode).toBe(1);
|
|
238
|
+
expect(deniedOut.error).toBe("executor_error");
|
|
239
|
+
expect(deniedOut.stderr).toContain(
|
|
240
|
+
'Executor "native" is not permitted when deployment SCRIPT_EXECUTOR="quickjs"',
|
|
241
|
+
);
|
|
242
|
+
});
|
|
243
|
+
|
|
244
|
+
test("HTTP /api/scripts/upsert, /api/scripts/run, and GET /api/scripts enforce ceiling and expose executor", async () => {
|
|
245
|
+
async function invokeScriptsHttp(opts: {
|
|
246
|
+
method: string;
|
|
247
|
+
pathSegments: string[];
|
|
248
|
+
queryParams?: URLSearchParams;
|
|
249
|
+
body?: unknown;
|
|
250
|
+
agentId?: string;
|
|
251
|
+
}): Promise<{ status: number; body: unknown }> {
|
|
252
|
+
const payload = opts.body !== undefined ? JSON.stringify(opts.body) : "";
|
|
253
|
+
const req = Object.assign(Readable.from(payload ? [Buffer.from(payload)] : []), {
|
|
254
|
+
method: opts.method,
|
|
255
|
+
url: `/${opts.pathSegments.join("/")}`,
|
|
256
|
+
headers: {
|
|
257
|
+
"content-type": "application/json",
|
|
258
|
+
...(opts.agentId ? { "x-agent-id": opts.agentId } : {}),
|
|
259
|
+
},
|
|
260
|
+
}) as unknown as IncomingMessage;
|
|
261
|
+
|
|
262
|
+
let status = 200;
|
|
263
|
+
const chunks: Buffer[] = [];
|
|
264
|
+
const res = {
|
|
265
|
+
writeHead(code: number) {
|
|
266
|
+
status = code;
|
|
267
|
+
return this;
|
|
268
|
+
},
|
|
269
|
+
setHeader() {
|
|
270
|
+
return this;
|
|
271
|
+
},
|
|
272
|
+
end(chunk?: string | Buffer) {
|
|
273
|
+
if (chunk) chunks.push(Buffer.isBuffer(chunk) ? chunk : Buffer.from(chunk));
|
|
274
|
+
return this;
|
|
275
|
+
},
|
|
276
|
+
} as unknown as ServerResponse;
|
|
277
|
+
|
|
278
|
+
await handleScripts(
|
|
279
|
+
req,
|
|
280
|
+
res,
|
|
281
|
+
opts.pathSegments,
|
|
282
|
+
opts.queryParams ?? new URLSearchParams(),
|
|
283
|
+
opts.agentId,
|
|
284
|
+
);
|
|
285
|
+
const text = Buffer.concat(chunks).toString("utf8");
|
|
286
|
+
return { status, body: text ? JSON.parse(text) : null };
|
|
287
|
+
}
|
|
288
|
+
|
|
289
|
+
process.env.SCRIPT_EXECUTOR = "quickjs";
|
|
290
|
+
|
|
291
|
+
const upsertDenied = await invokeScriptsHttp({
|
|
292
|
+
method: "POST",
|
|
293
|
+
pathSegments: ["api", "scripts", "upsert"],
|
|
294
|
+
agentId,
|
|
295
|
+
body: {
|
|
296
|
+
name: "escape-attempt",
|
|
297
|
+
scope: "agent",
|
|
298
|
+
source: RUNTIME_PROBE_SOURCE,
|
|
299
|
+
executor: "native",
|
|
300
|
+
},
|
|
301
|
+
});
|
|
302
|
+
expect(upsertDenied.status).toBe(400);
|
|
303
|
+
expect((upsertDenied.body as { error?: string }).error).toContain(
|
|
304
|
+
'Executor "native" is not permitted when deployment SCRIPT_EXECUTOR="quickjs"',
|
|
305
|
+
);
|
|
306
|
+
|
|
307
|
+
const runDenied = await invokeScriptsHttp({
|
|
308
|
+
method: "POST",
|
|
309
|
+
pathSegments: ["api", "scripts", "run"],
|
|
310
|
+
agentId,
|
|
311
|
+
body: {
|
|
312
|
+
source: RUNTIME_PROBE_SOURCE,
|
|
313
|
+
executor: "native",
|
|
314
|
+
},
|
|
315
|
+
});
|
|
316
|
+
expect(runDenied.status).toBe(400);
|
|
317
|
+
expect((runDenied.body as { error?: string }).error).toContain(
|
|
318
|
+
'Executor "native" is not permitted when deployment SCRIPT_EXECUTOR="quickjs"',
|
|
319
|
+
);
|
|
320
|
+
|
|
321
|
+
const listRes = await invokeScriptsHttp({
|
|
322
|
+
method: "GET",
|
|
323
|
+
pathSegments: ["api", "scripts"],
|
|
324
|
+
queryParams: new URLSearchParams("scope=agent"),
|
|
325
|
+
});
|
|
326
|
+
expect(listRes.status).toBe(200);
|
|
327
|
+
const listed = (listRes.body as { scripts: Array<{ executor: string | null }> }).scripts;
|
|
328
|
+
expect(listed.length).toBeGreaterThan(0);
|
|
329
|
+
expect(listed.some((s) => s.executor === "native" || s.executor === "quickjs")).toBe(true);
|
|
330
|
+
});
|
|
331
|
+
|
|
332
|
+
test("workflow SwarmScriptExecutor and scheduler dispatchScheduleTarget forward script.executor", async () => {
|
|
333
|
+
process.env.SCRIPT_EXECUTOR = "native";
|
|
334
|
+
const globalQuickjs = await insertScript({
|
|
335
|
+
name: `global-qjs-${crypto.randomUUID().slice(0, 8)}`,
|
|
336
|
+
scope: "global",
|
|
337
|
+
scopeId: null,
|
|
338
|
+
source: RUNTIME_PROBE_SOURCE,
|
|
339
|
+
description: "global quickjs script",
|
|
340
|
+
intent: "test",
|
|
341
|
+
signatureJson: "{}",
|
|
342
|
+
executor: "quickjs",
|
|
343
|
+
agentId,
|
|
344
|
+
typeChecked: true,
|
|
345
|
+
});
|
|
346
|
+
|
|
347
|
+
const wfExecutor = new SwarmScriptExecutor({
|
|
348
|
+
db: {
|
|
349
|
+
getWorkflow: async () => null,
|
|
350
|
+
} as never,
|
|
351
|
+
eventBus: { emit: () => {} } as never,
|
|
352
|
+
});
|
|
353
|
+
const wfResult = await wfExecutor.run({
|
|
354
|
+
config: { scriptName: globalQuickjs.name, scope: "global", fsMode: "none" },
|
|
355
|
+
context: { agentId },
|
|
356
|
+
meta: {
|
|
357
|
+
runId: crypto.randomUUID(),
|
|
358
|
+
stepId: crypto.randomUUID(),
|
|
359
|
+
nodeId: "n1",
|
|
360
|
+
workflowId: crypto.randomUUID(),
|
|
361
|
+
},
|
|
362
|
+
});
|
|
363
|
+
expect(wfResult.status).toBe("success");
|
|
364
|
+
if (wfResult.status === "success") {
|
|
365
|
+
expect(wfResult.output.result).toEqual({ hasProcess: false });
|
|
366
|
+
}
|
|
367
|
+
|
|
368
|
+
const { executeScheduleScript } = await import("../scheduler/scheduler");
|
|
369
|
+
const schedule: ScheduledTask = {
|
|
370
|
+
id: crypto.randomUUID(),
|
|
371
|
+
name: `sched-${globalQuickjs.name}`,
|
|
372
|
+
taskTemplate: "",
|
|
373
|
+
targetType: "script",
|
|
374
|
+
scriptName: globalQuickjs.name,
|
|
375
|
+
scriptArgs: {},
|
|
376
|
+
tags: [],
|
|
377
|
+
priority: 50,
|
|
378
|
+
enabled: true,
|
|
379
|
+
scheduleType: "recurring",
|
|
380
|
+
consecutiveErrors: 0,
|
|
381
|
+
createdByAgentId: agentId,
|
|
382
|
+
createdAt: new Date().toISOString(),
|
|
383
|
+
lastUpdatedAt: new Date().toISOString(),
|
|
384
|
+
};
|
|
385
|
+
await executeScheduleScript(schedule);
|
|
386
|
+
const runs = await listScriptRuns({ limit: 10 });
|
|
387
|
+
const matchedRun = runs.find((r) => r.scriptName === globalQuickjs.name);
|
|
388
|
+
expect(matchedRun?.status).toBe("completed");
|
|
389
|
+
const fullRun = matchedRun ? await getScriptRun(matchedRun.id) : null;
|
|
390
|
+
expect(fullRun?.output).toEqual({ hasProcess: false });
|
|
391
|
+
});
|
|
392
|
+
|
|
393
|
+
test("typecheckScript accepts executor on ctx.swarm.script_upsert and ctx.swarm.script_run", async () => {
|
|
394
|
+
const valid = await typecheckScript(
|
|
395
|
+
`import type { ScriptContext } from "swarm-sdk";
|
|
396
|
+
export default async (_args: unknown, ctx: ScriptContext) => {
|
|
397
|
+
await ctx.swarm.script_upsert({ name: "demo", source: "export default async () => 1;", executor: "quickjs" });
|
|
398
|
+
await ctx.swarm.script_run({ name: "demo", executor: "quickjs" });
|
|
399
|
+
return { ok: true };
|
|
400
|
+
};`,
|
|
401
|
+
{ agentId },
|
|
402
|
+
);
|
|
403
|
+
expect(valid.ok).toBe(true);
|
|
404
|
+
|
|
405
|
+
const invalid = await typecheckScript(
|
|
406
|
+
`import type { ScriptContext } from "swarm-sdk";
|
|
407
|
+
export default async (_args: unknown, ctx: ScriptContext) => {
|
|
408
|
+
await ctx.swarm.script_run({ name: "demo", executor: "e2b" });
|
|
409
|
+
return { ok: true };
|
|
410
|
+
};`,
|
|
411
|
+
{ agentId },
|
|
412
|
+
);
|
|
413
|
+
expect(invalid.ok).toBe(false);
|
|
414
|
+
});
|
|
415
|
+
});
|
|
@@ -0,0 +1,212 @@
|
|
|
1
|
+
import { afterAll, beforeAll, beforeEach, describe, expect, test } from "bun:test";
|
|
2
|
+
import { unlink } from "node:fs/promises";
|
|
3
|
+
import { closeDb, getDbClient, initDb } from "../be/db";
|
|
4
|
+
import {
|
|
5
|
+
DB_RETENTION_TABLES,
|
|
6
|
+
getDbRetentionStats,
|
|
7
|
+
resetDbRetentionForTests,
|
|
8
|
+
runDbRetentionTick,
|
|
9
|
+
sweepStatements,
|
|
10
|
+
} from "../be/db-retention";
|
|
11
|
+
|
|
12
|
+
const TEST_DB_PATH = "./test-script-runs-retention.sqlite";
|
|
13
|
+
const DAYS_KEY = "SCRIPT_RUNS_RETENTION_DAYS";
|
|
14
|
+
// With 1 day of retention the cutoff is 2026-08-22T12:00:00.000Z.
|
|
15
|
+
const NOW = new Date("2026-08-23T12:00:00.000Z");
|
|
16
|
+
const OLD_ISO = "2026-08-01T00:00:00.000Z";
|
|
17
|
+
const OLD_SPACED = "2026-08-01 00:00:00";
|
|
18
|
+
const RECENT_ISO = "2026-08-23T11:00:00.000Z";
|
|
19
|
+
|
|
20
|
+
const SCRIPT_RUNS = DB_RETENTION_TABLES.find((table) => table.table === "script_runs");
|
|
21
|
+
if (!SCRIPT_RUNS) throw new Error("script_runs is not in the retention allowlist");
|
|
22
|
+
|
|
23
|
+
async function removeDbFiles(): Promise<void> {
|
|
24
|
+
for (const suffix of ["", "-wal", "-shm"])
|
|
25
|
+
await unlink(`${TEST_DB_PATH}${suffix}`).catch(() => undefined);
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
async function insertRun(
|
|
29
|
+
id: string,
|
|
30
|
+
status: string,
|
|
31
|
+
startedAt: string,
|
|
32
|
+
options: { kind?: "inline" | "workflow"; idempotencyKey?: string } = {},
|
|
33
|
+
): Promise<void> {
|
|
34
|
+
await getDbClient().run(
|
|
35
|
+
`INSERT INTO script_runs (id, agentId, scriptName, source, args, status, startedAt, kind, idempotencyKey)
|
|
36
|
+
VALUES (?, 'agent-1', 'retention-test', 'export default () => 1', '{}', ?, ?, ?, ?)`,
|
|
37
|
+
[id, status, startedAt, options.kind ?? "inline", options.idempotencyKey ?? null],
|
|
38
|
+
);
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
async function insertJournal(runId: string, stepKey: string): Promise<void> {
|
|
42
|
+
await getDbClient().run(
|
|
43
|
+
"INSERT INTO script_run_journal (id, runId, stepKey, stepType, status) VALUES (?, ?, ?, 'tool', 'completed')",
|
|
44
|
+
[`${runId}-${stepKey}`, runId, stepKey],
|
|
45
|
+
);
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
async function runIds(): Promise<string[]> {
|
|
49
|
+
const rows = await getDbClient().query<{ id: string }>("SELECT id FROM script_runs ORDER BY id");
|
|
50
|
+
return rows.map((row) => row.id);
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
async function journalRunIds(): Promise<string[]> {
|
|
54
|
+
const rows = await getDbClient().query<{ runId: string }>(
|
|
55
|
+
"SELECT DISTINCT runId FROM script_run_journal ORDER BY runId",
|
|
56
|
+
);
|
|
57
|
+
return rows.map((row) => row.runId);
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
beforeAll(async () => {
|
|
61
|
+
closeDb();
|
|
62
|
+
await removeDbFiles();
|
|
63
|
+
initDb(TEST_DB_PATH);
|
|
64
|
+
});
|
|
65
|
+
|
|
66
|
+
afterAll(async () => {
|
|
67
|
+
await resetDbRetentionForTests();
|
|
68
|
+
delete process.env[DAYS_KEY];
|
|
69
|
+
delete process.env.DB_RETENTION_DRY_RUN;
|
|
70
|
+
closeDb();
|
|
71
|
+
await removeDbFiles();
|
|
72
|
+
});
|
|
73
|
+
|
|
74
|
+
beforeEach(async () => {
|
|
75
|
+
await resetDbRetentionForTests();
|
|
76
|
+
delete process.env[DAYS_KEY];
|
|
77
|
+
delete process.env.DB_RETENTION_DRY_RUN;
|
|
78
|
+
await getDbClient().run("DELETE FROM script_run_journal");
|
|
79
|
+
await getDbClient().run("DELETE FROM script_runs");
|
|
80
|
+
});
|
|
81
|
+
|
|
82
|
+
describe("script_runs age-based retention", () => {
|
|
83
|
+
test("is opt-in: an unset key deletes nothing", async () => {
|
|
84
|
+
await insertRun("old-completed", "completed", OLD_ISO);
|
|
85
|
+
|
|
86
|
+
await runDbRetentionTick({ now: NOW });
|
|
87
|
+
|
|
88
|
+
expect(await runIds()).toEqual(["old-completed"]);
|
|
89
|
+
expect(getDbRetentionStats().scriptRuns).toBeUndefined();
|
|
90
|
+
});
|
|
91
|
+
|
|
92
|
+
test("deletes old finished runs and keeps running, paused, recent and keyed runs", async () => {
|
|
93
|
+
for (const status of ["completed", "failed", "cancelled", "aborted_limit"]) {
|
|
94
|
+
await insertRun(`old-${status}-iso`, status, OLD_ISO);
|
|
95
|
+
await insertRun(`old-${status}-spaced`, status, OLD_SPACED, { kind: "workflow" });
|
|
96
|
+
}
|
|
97
|
+
await insertRun("old-running", "running", OLD_SPACED, { kind: "workflow" });
|
|
98
|
+
await insertRun("old-paused", "paused", OLD_SPACED, { kind: "workflow" });
|
|
99
|
+
await insertRun("old-keyed", "completed", OLD_SPACED, {
|
|
100
|
+
kind: "workflow",
|
|
101
|
+
idempotencyKey: "launch-once",
|
|
102
|
+
});
|
|
103
|
+
await insertRun("recent-completed", "completed", RECENT_ISO);
|
|
104
|
+
process.env[DAYS_KEY] = "1";
|
|
105
|
+
|
|
106
|
+
await runDbRetentionTick({ now: NOW });
|
|
107
|
+
|
|
108
|
+
expect(await runIds()).toEqual(["old-keyed", "old-paused", "old-running", "recent-completed"]);
|
|
109
|
+
expect(getDbRetentionStats().scriptRuns).toMatchObject({
|
|
110
|
+
rowsDeleted: 8,
|
|
111
|
+
drained: true,
|
|
112
|
+
backlogRemaining: 0,
|
|
113
|
+
dryRun: false,
|
|
114
|
+
});
|
|
115
|
+
});
|
|
116
|
+
|
|
117
|
+
test("journal rows leave with their run and stay with a surviving one", async () => {
|
|
118
|
+
await insertRun("old-done", "completed", OLD_SPACED, { kind: "workflow" });
|
|
119
|
+
await insertRun("old-paused", "paused", OLD_SPACED, { kind: "workflow" });
|
|
120
|
+
for (const step of ["a", "b", "c"]) {
|
|
121
|
+
await insertJournal("old-done", step);
|
|
122
|
+
await insertJournal("old-paused", step);
|
|
123
|
+
}
|
|
124
|
+
process.env[DAYS_KEY] = "1";
|
|
125
|
+
|
|
126
|
+
await runDbRetentionTick({ now: NOW });
|
|
127
|
+
|
|
128
|
+
expect(await runIds()).toEqual(["old-paused"]);
|
|
129
|
+
expect(await journalRunIds()).toEqual(["old-paused"]);
|
|
130
|
+
const journal = await getDbClient().get<{ n: number }>(
|
|
131
|
+
"SELECT COUNT(*) AS n FROM script_run_journal",
|
|
132
|
+
);
|
|
133
|
+
expect(journal?.n).toBe(3);
|
|
134
|
+
});
|
|
135
|
+
|
|
136
|
+
test("never deletes a row early across the two stored timestamp formats", async () => {
|
|
137
|
+
// Cutoff: 2026-08-22T12:00:00.000Z, compared as '2026-08-22 12:00:00'.
|
|
138
|
+
await insertRun("spaced-before", "completed", "2026-08-22 11:59:59", { kind: "workflow" });
|
|
139
|
+
await insertRun("spaced-at", "completed", "2026-08-22 12:00:00", { kind: "workflow" });
|
|
140
|
+
await insertRun("iso-previous-day", "completed", "2026-08-21T23:59:59.999Z");
|
|
141
|
+
// Older than the cutoff, but on the cutoff's own date: kept until a later tick.
|
|
142
|
+
await insertRun("iso-cutoff-day", "completed", "2026-08-22T11:00:00.000Z");
|
|
143
|
+
await insertRun("iso-after", "completed", "2026-08-22T12:00:00.001Z");
|
|
144
|
+
process.env[DAYS_KEY] = "1";
|
|
145
|
+
|
|
146
|
+
await runDbRetentionTick({ now: NOW });
|
|
147
|
+
|
|
148
|
+
expect(await runIds()).toEqual(["iso-after", "iso-cutoff-day", "spaced-at"]);
|
|
149
|
+
|
|
150
|
+
// A day later every one of them is past the cutoff's date.
|
|
151
|
+
await runDbRetentionTick({ now: new Date("2026-08-24T00:00:00.000Z") });
|
|
152
|
+
|
|
153
|
+
expect(await runIds()).toEqual([]);
|
|
154
|
+
});
|
|
155
|
+
|
|
156
|
+
test("dry run counts the would-delete rows without deleting", async () => {
|
|
157
|
+
await insertRun("old-completed", "completed", OLD_ISO);
|
|
158
|
+
await insertRun("old-failed", "failed", OLD_SPACED, { kind: "workflow" });
|
|
159
|
+
await insertRun("old-running", "running", OLD_SPACED, { kind: "workflow" });
|
|
160
|
+
process.env[DAYS_KEY] = "1";
|
|
161
|
+
process.env.DB_RETENTION_DRY_RUN = "true";
|
|
162
|
+
|
|
163
|
+
await runDbRetentionTick({ now: NOW });
|
|
164
|
+
|
|
165
|
+
expect(await runIds()).toHaveLength(3);
|
|
166
|
+
expect(getDbRetentionStats().scriptRuns).toMatchObject({
|
|
167
|
+
rowsDeleted: 0,
|
|
168
|
+
backlogRemaining: 2,
|
|
169
|
+
dryRun: true,
|
|
170
|
+
});
|
|
171
|
+
});
|
|
172
|
+
|
|
173
|
+
test("drains a backlog larger than one batch within the batch ceiling", async () => {
|
|
174
|
+
await getDbClient().run(
|
|
175
|
+
`WITH RECURSIVE n(v) AS (SELECT 1 UNION ALL SELECT v + 1 FROM n WHERE v < 1200)
|
|
176
|
+
INSERT INTO script_runs (id, agentId, source, status, startedAt)
|
|
177
|
+
SELECT 'bulk-' || v, 'agent-1', 'x', 'completed', ? FROM n`,
|
|
178
|
+
[OLD_ISO],
|
|
179
|
+
);
|
|
180
|
+
process.env[DAYS_KEY] = "1";
|
|
181
|
+
|
|
182
|
+
await runDbRetentionTick({ now: NOW });
|
|
183
|
+
|
|
184
|
+
const stats = getDbRetentionStats().scriptRuns;
|
|
185
|
+
expect(await runIds()).toEqual([]);
|
|
186
|
+
expect(stats?.rowsDeleted).toBe(1200);
|
|
187
|
+
expect(stats?.batches).toBeGreaterThan(1);
|
|
188
|
+
expect(stats?.batchSize).toBeLessThanOrEqual(500);
|
|
189
|
+
});
|
|
190
|
+
|
|
191
|
+
test("the delete, the backlog count and the journal cascade all use an index", async () => {
|
|
192
|
+
const statements = sweepStatements(SCRIPT_RUNS, 30, NOW);
|
|
193
|
+
const client = getDbClient();
|
|
194
|
+
|
|
195
|
+
for (const [sql, params] of [
|
|
196
|
+
[statements.deleteSql, [...statements.params, 100]],
|
|
197
|
+
[statements.backlogSql, statements.params],
|
|
198
|
+
] as const) {
|
|
199
|
+
const plan = await client.query<{ detail: string }>(`EXPLAIN QUERY PLAN ${sql}`, [...params]);
|
|
200
|
+
const detail = plan.map((row) => row.detail).join("\n");
|
|
201
|
+
expect(detail).toContain("USING COVERING INDEX idx_script_runs_status_startedAt");
|
|
202
|
+
expect(detail).not.toMatch(/SCAN script_runs(?! USING)/);
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
// ON DELETE CASCADE looks up journal rows of every deleted run by runId.
|
|
206
|
+
const fkPlan = await client.query<{ detail: string }>(
|
|
207
|
+
"EXPLAIN QUERY PLAN SELECT rowid FROM script_run_journal WHERE runId = ?",
|
|
208
|
+
["x"],
|
|
209
|
+
);
|
|
210
|
+
expect(fkPlan.map((row) => row.detail).join("\n")).toContain("idx_srj_runId");
|
|
211
|
+
});
|
|
212
|
+
});
|
|
@@ -421,6 +421,51 @@ describe("public execution route", () => {
|
|
|
421
421
|
expect(body.error.type).toBe("args_validation");
|
|
422
422
|
});
|
|
423
423
|
|
|
424
|
+
test("POST {} against z.object with inherited Object.prototype keys (constructor, toString) retains Zod rejection under quickjs", async () => {
|
|
425
|
+
const prevExecutor = process.env.SCRIPT_EXECUTOR;
|
|
426
|
+
process.env.SCRIPT_EXECUTOR = "quickjs";
|
|
427
|
+
try {
|
|
428
|
+
for (const propName of ["constructor", "toString"] as const) {
|
|
429
|
+
const script = await insertScript({
|
|
430
|
+
name: `proto-${propName}-${crypto.randomUUID().slice(0, 8)}`,
|
|
431
|
+
scope: "agent",
|
|
432
|
+
scopeId: workerId,
|
|
433
|
+
source: `import { z } from "zod";\nexport const argsSchema = z.object({ ${propName}: z.string().nullable().optional() });\nexport default async function (args: z.infer<typeof argsSchema>) { return { ok: true, val: args.${propName} ?? null }; }`,
|
|
434
|
+
description: "Prototype key test",
|
|
435
|
+
intent: "test fixture",
|
|
436
|
+
signatureJson: "{}",
|
|
437
|
+
argsJsonSchema: JSON.stringify({
|
|
438
|
+
type: "object",
|
|
439
|
+
properties: { [propName]: { type: ["string", "null"] } },
|
|
440
|
+
}),
|
|
441
|
+
agentId: workerId,
|
|
442
|
+
typeChecked: true,
|
|
443
|
+
});
|
|
444
|
+
const endpoint = await createScriptApi({
|
|
445
|
+
scriptId: script.id,
|
|
446
|
+
agentId: workerId,
|
|
447
|
+
authMode: "none",
|
|
448
|
+
});
|
|
449
|
+
|
|
450
|
+
const res = await dispatch(`/api/x/script/${endpoint.id}`, {
|
|
451
|
+
method: "POST",
|
|
452
|
+
body: JSON.stringify({}),
|
|
453
|
+
auth: null,
|
|
454
|
+
});
|
|
455
|
+
expect(res.status).toBe(200);
|
|
456
|
+
const body = (await res.json()) as {
|
|
457
|
+
ok: boolean;
|
|
458
|
+
error: { type: string; message: string } | null;
|
|
459
|
+
};
|
|
460
|
+
expect(body.ok).toBe(false);
|
|
461
|
+
expect(body.error?.type).toBe("runtime_error");
|
|
462
|
+
}
|
|
463
|
+
} finally {
|
|
464
|
+
if (prevExecutor === undefined) delete process.env.SCRIPT_EXECUTOR;
|
|
465
|
+
else process.env.SCRIPT_EXECUTOR = prevExecutor;
|
|
466
|
+
}
|
|
467
|
+
});
|
|
468
|
+
|
|
424
469
|
test("disabled endpoint → 404", async () => {
|
|
425
470
|
const script = await insertDoubler();
|
|
426
471
|
const endpoint = await createScriptApi({
|