@desplega.ai/agent-swarm 1.165.0 → 1.167.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/dist/acp-adapter-zpvy6321.js +34 -0
- package/dist/{actions-jna0b3wm.js → actions-fq2zrwtf.js} +30 -34
- package/dist/{amp-adapter-0eve81pz.js → amp-adapter-5gbk7501.js} +7 -7
- package/dist/{anthropic-messages-hst62ws9.js → anthropic-messages-13yn502s.js} +4 -4
- package/dist/{app-75hm5cfa.js → app-nctetn1e.js} +11 -11
- package/dist/{assistant-yzd51j33.js → assistant-0aq8gjvj.js} +31 -35
- package/dist/{azure-openai-responses-mh8tvf78.js → azure-openai-responses-9st34pfz.js} +5 -5
- package/dist/{boot-reembed-z158xq32.js → boot-reembed-1ze5853n.js} +14 -16
- package/dist/{boot-reembed-2ex2mcsh.js → boot-reembed-fmp5ww69.js} +15 -16
- package/dist/boot-scrub-sweep-cbp8xd4e.js +212 -0
- package/dist/{boot-seeds-2bmnh18p.js → boot-seeds-22tx8a53.js} +13 -12
- package/dist/{claude-adapter-jbj1mycz.js → claude-adapter-0jhgeh1d.js} +25 -25
- package/dist/{claude-managed-adapter-17ggz09t.js → claude-managed-adapter-tzwxy803.js} +5 -5
- package/dist/{claude-managed-setup-ptm1nzm2.js → claude-managed-setup-nrhfx5b0.js} +5 -5
- package/dist/{claude-sdk-session-9re1g7r2.js → claude-sdk-session-wh77rh83.js} +2632 -2614
- package/dist/{cli-zsh093hk.js → cli-014vns2y.js} +63 -1
- package/dist/{cli-qvfk83fy.js → cli-13xwcqvj.js} +2 -2
- package/dist/{cli-8gpbwy09.js → cli-1yrcfa5s.js} +5 -5
- package/dist/{cli-xtgtyafv.js → cli-577bxkf6.js} +212 -451
- package/dist/{cli-ma3zvh39.js → cli-5qkcdv8c.js} +6118 -5874
- package/dist/{cli-cwxhbf43.js → cli-5t5my0h8.js} +85 -2
- package/dist/{cli-zvckv750.js → cli-69egj654.js} +2 -2
- package/dist/{cli-9xb89xca.js → cli-6bpvv8h4.js} +9 -9
- package/dist/{cli-90v942q1.js → cli-6qxmjpem.js} +8 -8
- package/dist/{cli-9866bd07.js → cli-6vfz44sr.js} +1 -1
- package/dist/{cli-32bca7nr.js → cli-7avy4v8w.js} +1 -1
- package/dist/{cli-xb3c6aat.js → cli-7mhy3ths.js} +21 -18
- package/dist/{cli-q38n4bq0.js → cli-95zs84qw.js} +3 -3
- package/dist/cli-a35hzdve.js +33 -0
- package/dist/{cli-x3x0qwky.js → cli-bxqez1p0.js} +51 -23
- package/dist/{cli-prt9emct.js → cli-c2tpw9ck.js} +3 -3
- package/dist/{cli-0n4fasz0.js → cli-c8r99akr.js} +16 -16
- package/dist/{cli-5anb0ev3.js → cli-cexctgn5.js} +89 -26
- package/dist/{cli-bns5c8k9.js → cli-cf2vgn9d.js} +105 -91
- package/dist/{cli-ng97fjq7.js → cli-cmxkbzzs.js} +33 -7
- package/dist/{cli-nexwh8zg.js → cli-g7k7zbcz.js} +12 -5
- package/dist/{cli-mmzwwptd.js → cli-hf5s9pgf.js} +1 -1
- package/dist/{cli-nvvfd8bq.js → cli-jbzbr36n.js} +4091 -3793
- package/dist/{cli-zn2v3t8v.js → cli-jvmtxqtc.js} +27 -15
- package/dist/{cli-pkke6geq.js → cli-km4x66j0.js} +83 -65
- package/dist/cli-kw0jcfje.js +516 -0
- package/dist/{cli-28smfvbn.js → cli-m7vmeepg.js} +32 -19
- package/dist/{cli-hpryr6em.js → cli-mb75wpxf.js} +1 -1
- package/dist/{cli-qdv8kgz7.js → cli-mkdsy1nq.js} +14018 -3333
- package/dist/{cli-b0f70z24.js → cli-mn8qkd8v.js} +1 -1
- package/dist/{cli-420bdgw0.js → cli-mv2520nm.js} +1 -1
- package/dist/{cli-n9ehc28t.js → cli-mxry91sa.js} +31 -10
- package/dist/{cli-wfajtmf7.js → cli-mxvnnv5q.js} +596 -13
- package/dist/cli-nkqjj42q.js +104 -0
- package/dist/{cli-e7t4snpa.js → cli-p8rt8spp.js} +5 -5
- package/dist/{cli-gh5yqzpe.js → cli-pkj1w46r.js} +12 -7
- package/dist/cli-q1vnz8wv.js +96 -0
- package/dist/{cli-fj86xr3c.js → cli-qd97v6z2.js} +2 -2
- package/dist/{cli-ba66wyhy.js → cli-qzmzwegq.js} +60 -20
- package/dist/{cli-5b2fjjzh.js → cli-r7eeddkq.js} +14 -9
- package/dist/{cli-8ssjez7e.js → cli-rhy7z1rp.js} +22 -11
- package/dist/{cli-4mevd1hf.js → cli-s9y81s7j.js} +5 -5
- package/dist/{cli-59jn5twa.js → cli-tc5zec7v.js} +7 -7
- package/dist/{cli-ttbsse3f.js → cli-tmhsxhb8.js} +1 -1
- package/dist/{cli-k6xx3pve.js → cli-tx2zvn23.js} +4 -4
- package/dist/{cli-pjxnr1jn.js → cli-ty4bzkqr.js} +5 -5
- package/dist/{cli-nfygw434.js → cli-v1pwxreq.js} +55 -52
- package/dist/{cli-ftry7975.js → cli-v1w7pbd8.js} +3 -2
- package/dist/{cli-10zmp1p7.js → cli-vr5avem2.js} +27 -32
- package/dist/{cli-bzr0ftht.js → cli-wnb9b2g3.js} +19 -0
- package/dist/cli-wt7ccy4r.js +2654 -0
- package/dist/{cli-50eg13rj.js → cli-wydz3feg.js} +549 -321
- package/dist/{cli-krqm5f11.js → cli-xezn9nkh.js} +16 -13
- package/dist/{cli-8kt0bmgx.js → cli-xf713zz0.js} +2 -2
- package/dist/{cli-x7afk5wm.js → cli-xvc3q9br.js} +1 -1
- package/dist/{cli-9s3h836g.js → cli-xvdgasgy.js} +1 -1
- package/dist/{cli-j0pdrmwg.js → cli-y0kjpfm9.js} +3 -3
- package/dist/{cli-6zcfw0x7.js → cli-y195hv1a.js} +1 -1
- package/dist/{cli-amwhs8h2.js → cli-ykt8f7va.js} +22 -9
- package/dist/{cli-1kzfvd1z.js → cli-ymmc1zxg.js} +15 -11
- package/dist/{cli-xnzrzets.js → cli-ypgba1a8.js} +2 -2
- package/dist/{cli-bdv39kgj.js → cli-z1dqhc9y.js} +31 -20
- package/dist/{cli-fw6vdcq5.js → cli-zp67qg5x.js} +34 -1
- package/dist/{cli-qbksc292.js → cli-zy39d433.js} +2 -0
- package/dist/cli.js +36 -32
- package/dist/{cloudflare-workers-ai-system-one-tt484t61.js → cloudflare-workers-ai-system-one-sta45jf9.js} +7 -5
- package/dist/{codex-adapter-3qnxshje.js → codex-adapter-cgesnjkr.js} +20 -20
- package/dist/{codex-login-brkxhhdg.js → codex-login-g1cyt5zm.js} +2 -2
- package/dist/{codex-session-runner-612wnapp.js → codex-session-runner-8kd7ddhr.js} +20 -20
- package/dist/{commands-2ybfcxkj.js → commands-h6zaaxym.js} +11 -10
- package/dist/{cursor-adapter-p3jgdehv.js → cursor-adapter-v8mm6bp5.js} +7 -7
- package/dist/{db-f4ans0jj.js → db-qg3myn53.js} +13 -10
- package/dist/{devin-adapter-dhzt01ff.js → devin-adapter-bffjj5n4.js} +2 -2
- package/dist/{dispatcher-pk182apd.js → dispatcher-7vnwj9qw.js} +28 -32
- package/dist/{dsh-adapter-7twaeps1.js → dsh-adapter-tz6hfp5r.js} +7 -7
- package/dist/{e2b-y8t3jzyz.js → e2b-nd50eccg.js} +1 -1
- package/dist/{easter-egg-3d-mtpnw06s.js → easter-egg-3d-66zxervv.js} +2 -2
- package/dist/{event-bus-w7ac4n3c.js → event-bus-vnfthafg.js} +2 -2
- package/dist/{execute-s4ccjq3n.js → execute-h4jgevyw.js} +61 -11
- package/dist/{google-generative-ai-d6nphg3q.js → google-generative-ai-b8zxrpax.js} +5 -5
- package/dist/{google-vertex-cpshkref.js → google-vertex-nrdd697t.js} +5 -5
- package/dist/grok-adapter-jdt2dz2s.js +39 -0
- package/dist/{handlers-g2wnr4t9.js → handlers-2qtkpncr.js} +31 -35
- package/dist/{hook-f7jt9vnm.js → hook-51yfcgsv.js} +48 -39
- package/dist/{hook-bbdpnkb4.js → hook-qnbdgwvd.js} +15 -17
- package/dist/{http-w40q4w94.js → http-jsmtag08.js} +536 -231
- package/dist/{index-056j5kzn.js → index-2t9kesn1.js} +13 -12
- package/dist/{index-cj1r9q83.js → index-bt8wkv1w.js} +29 -33
- package/dist/{index-7n70jwbb.js → index-cx1sb9bq.js} +42 -35
- package/dist/{index-9bfssf54.js → index-ekywyv2v.js} +28 -32
- package/dist/{index-fdk1t4d6.js → index-n0rxa4ca.js} +11962 -9900
- package/dist/{index-k8xc083e.js → index-t4g06w93.js} +28 -32
- package/dist/{keepalive-1tj10tf4.js → keepalive-4aezx4bj.js} +16 -12
- package/dist/{lead-st0bg7v4.js → lead-r44bkpm5.js} +52 -56
- package/dist/{llama-cpp-classify-tgj35k8d.js → llama-cpp-classify-6erb7wwv.js} +3 -1
- package/dist/{maintenance-nz71zats.js → maintenance-grz5jrzh.js} +12 -13
- package/dist/{mistral-conversations-8445ddjn.js → mistral-conversations-059y51zt.js} +4 -4
- package/dist/{node-f0ykebbv.js → node-qm9eb6z0.js} +25 -7
- package/dist/{oauth-refresh-sweep-aefzwvvq.js → oauth-refresh-sweep-wm4ctjfj.js} +15 -11
- package/dist/{onboard-kgpr9mbn.js → onboard-v60zgqf0.js} +2 -2
- package/dist/{openai-codex-responses-9ynm88zy.js → openai-codex-responses-jzxkta9b.js} +8 -7
- package/dist/{openai-completions-nhqwjmaw.js → openai-completions-663yncfq.js} +4 -4
- package/dist/openai-decisions-vj9nkpf8.js +162 -0
- package/dist/{openai-responses-xx1c4kgm.js → openai-responses-m3g43rrt.js} +5 -5
- package/dist/{opencode-adapter-0c8e70dk.js → opencode-adapter-yjyqynzf.js} +8 -8
- package/dist/{openrouter-images-8yx3487g.js → openrouter-images-kr60n6b9.js} +1 -1
- package/dist/{otel-impl-x8zp4y5a.js → otel-impl-ghh4dyf8.js} +2 -2
- package/dist/{pi-messages-vxdt8v7j.js → pi-messages-xszk613n.js} +1 -1
- package/dist/{pi-mono-adapter-t280ah5c.js → pi-mono-adapter-0eg8kw2c.js} +69 -52
- package/dist/{pricing-refresh-9e0q7cwk.js → pricing-refresh-mjrqvnvw.js} +12 -11
- package/dist/{rbac-roles-790j3ga8.js → rbac-roles-axv4d74x.js} +11 -10
- package/dist/{rbac-roles-kcp6zge2.js → rbac-roles-fd5tskj9.js} +11 -10
- package/dist/{render-v2-tch4nefj.js → render-v2-k50j33qd.js} +11 -11
- package/dist/{runtime-v2t1r79e.js → runtime-h9ha1mq6.js} +4 -4
- package/dist/{setup-rcm303ns.js → setup-4syh0pd2.js} +2 -2
- package/dist/{typesafe-system-one-datfr82p.js → typesafe-system-one-e988vty7.js} +7 -5
- package/dist/{virtual-modules-wm8mxdxx.js → virtual-modules-ftr9kwe8.js} +11 -11
- package/dist/{work-objects-ctg7t70w.js → work-objects-jcdbg8rr.js} +32 -36
- package/dist/{worker-5dp0xm55.js → worker-x8j8c9sx.js} +52 -56
- package/dist/{x-v3yw66zs.js → x-ma4gnfgy.js} +2 -2
- package/openapi.json +5 -2
- package/package.json +15 -11
- package/src/be/automation-preflight.ts +9 -4
- package/src/be/boot-scrub-sweep.ts +302 -0
- package/src/be/crypto/key-bootstrap.ts +1 -1
- package/src/be/db/approvals.ts +15 -1
- package/src/be/db/codex-oauth-device-flows.ts +40 -0
- package/src/be/db/config-secrets.ts +31 -0
- package/src/be/db/runtime.ts +4 -0
- package/src/be/db/script-runs.ts +15 -0
- package/src/be/db/tasks/write.ts +1 -1
- package/src/be/db-queries/mcp-oauth.ts +5 -0
- package/src/be/db-queries/oauth.ts +77 -0
- package/src/be/db.ts +162 -59
- package/src/be/events.ts +17 -1
- package/src/be/memory/index-content.ts +2 -2
- package/src/be/memory/providers/openai-embedding.ts +7 -2
- package/src/be/memory/providers/sqlite-store.ts +63 -11
- package/src/be/memory/raters/llm-summarizer.ts +7 -2
- package/src/be/memory/types.ts +14 -0
- package/src/be/migrations/201_workflow_replay_columns.sql +10 -0
- package/src/be/migrations/202_codex_oauth_device_flows.sql +24 -0
- package/src/be/modelsdev-cache.json +61 -0
- package/src/be/scripts/db.ts +28 -4
- package/src/be/scripts/source-secrets.ts +37 -0
- package/src/be/scrub-json.ts +32 -0
- package/src/be/sealed-json.ts +59 -0
- package/src/be/secret-registry.ts +131 -0
- package/src/be/seed-pricing.ts +84 -53
- package/src/be/swarm-config-guard.ts +13 -0
- package/src/be/workflow-replay.ts +77 -0
- package/src/commands/provider-credentials.ts +28 -5
- package/src/commands/runner.ts +7 -12
- package/src/heartbeat/heartbeat.ts +91 -19
- package/src/hooks/hook.ts +72 -27
- package/src/http/codex-oauth-device.ts +8 -10
- package/src/http/db-query-bounded.ts +3 -0
- package/src/http/db-query-shared.ts +107 -15
- package/src/http/db-query.ts +10 -2
- package/src/http/index.ts +23 -6
- package/src/http/mcp.ts +5 -0
- package/src/http/onboarding.ts +2 -0
- package/src/http/script-runs.ts +9 -1
- package/src/http/scripts.ts +15 -0
- package/src/http/workflows.ts +3 -2
- package/src/oauth/mcp-wrapper.ts +1 -1
- package/src/providers/acp-adapter.ts +25 -11
- package/src/providers/acp-target-catalog.ts +35 -1
- package/src/providers/acp-targets.ts +78 -7
- package/src/providers/amp-adapter.ts +1 -1
- package/src/providers/claude-adapter.ts +26 -0
- package/src/providers/codex-adapter.ts +30 -3
- package/src/providers/codex-oauth/auth-json.ts +21 -4
- package/src/providers/codex-oauth/types.ts +2 -0
- package/src/providers/cursor-adapter.ts +1 -1
- package/src/providers/dsh-adapter.ts +10 -1
- package/src/providers/grok-adapter.ts +18 -2
- package/src/providers/opencode-adapter.ts +21 -5
- package/src/providers/pi-mono-extension.ts +56 -25
- package/src/providers/pricing-sources.md +2 -0
- package/src/script-workflows/supervisor.ts +11 -2
- package/src/scripts-runtime/eval-harness.ts +2 -12
- package/src/scripts-runtime/executors/quickjs-bundle.ts +152 -0
- package/src/scripts-runtime/executors/quickjs-prelude.ts +285 -0
- package/src/scripts-runtime/executors/quickjs-runner.ts +580 -0
- package/src/scripts-runtime/executors/quickjs-worker.ts +81 -0
- package/src/scripts-runtime/executors/quickjs.ts +196 -0
- package/src/scripts-runtime/executors/registry.ts +2 -0
- package/src/scripts-runtime/executors/sourcemap.ts +79 -0
- package/src/scripts-runtime/script-error-hints.ts +14 -0
- package/src/server.ts +66 -4
- package/src/tests/acp-adapter.test.ts +277 -12
- package/src/tests/boot-scrub-sweep.test.ts +390 -0
- package/src/tests/check-text-columns.test.ts +145 -0
- package/src/tests/ci-unit-test-report.test.ts +73 -0
- package/src/tests/claude-default-route.test.ts +75 -0
- package/src/tests/claude-hook-output.test.ts +233 -0
- package/src/tests/claude-sdk-process.fixture.ts +13 -2
- package/src/tests/codex-adapter-pool-auth-revalidate.test.ts +4 -2
- package/src/tests/codex-adapter.test.ts +166 -26
- package/src/tests/codex-oauth-auth-timestamps.test.ts +76 -0
- package/src/tests/codex-oauth-device.test.ts +23 -37
- package/src/tests/codex-oauth.test.ts +4 -1
- package/src/tests/context-window.test.ts +2 -2
- package/src/tests/credential-check.test.ts +51 -1
- package/src/tests/db-query-denylist.test.ts +202 -0
- package/src/tests/db-task-read-contract.test.ts +1 -0
- package/src/tests/db-task-write-contract.test.ts +1 -0
- package/src/tests/dsh-adapter.test.ts +23 -1
- package/src/tests/enabled-tools.test.ts +269 -0
- package/src/tests/entrypoint-acp-target-binary.test.ts +31 -1
- package/src/tests/entrypoint-codex-oauth-seed.test.ts +12 -0
- package/src/tests/events-scrub.test.ts +88 -0
- package/src/tests/fixtures/secret-corpus.ts +320 -0
- package/src/tests/grok-adapter.test.ts +13 -0
- package/src/tests/haiku-5-5-pricing.test.ts +38 -0
- package/src/tests/heartbeat-checklist.test.ts +158 -1
- package/src/tests/onboarding.test.ts +44 -0
- package/src/tests/opencode-adapter.test.ts +66 -3
- package/src/tests/opencode-plugin.test.ts +37 -0
- package/src/tests/openrouter-attribution-call-sites.test.ts +204 -0
- package/src/tests/openrouter-base-url.test.ts +30 -39
- package/src/tests/pi-mono-extension.test.ts +161 -2
- package/src/tests/pricing-refresh.test.ts +70 -1
- package/src/tests/reasoning-effort.test.ts +3 -1
- package/src/tests/script-connections.test.ts +5 -0
- package/src/tests/script-executor-conformance.test.ts +3 -0
- package/src/tests/script-executor-quickjs.test.ts +442 -0
- package/src/tests/script-executor-registry.test.ts +6 -1
- package/src/tests/script-runs-http.test.ts +88 -1
- package/src/tests/script-runs-scrub.test.ts +275 -0
- package/src/tests/scripts-http.test.ts +82 -1
- package/src/tests/scrubbed-text-12e.test.ts +289 -0
- package/src/tests/sealed-key-guard.test.ts +166 -0
- package/src/tests/secret-canary-sweep.test.ts +958 -0
- package/src/tests/secret-registry.test.ts +355 -0
- package/src/tests/secret-rules.test.ts +230 -0
- package/src/tests/secret-scrubber.test.ts +3 -2
- package/src/tests/telemetry-trigger.test.ts +12 -1
- package/src/tests/workflow-llm.test.ts +14 -0
- package/src/tests/workflow-replay.test.ts +518 -0
- package/src/tests/workflow-scrub.test.ts +242 -0
- package/src/tests/workflow-system-one-decision-providers.test.ts +33 -0
- package/src/tools/db-query.ts +1 -1
- package/src/tools/inject-learning.ts +1 -1
- package/src/tools/request-human-input.ts +7 -2
- package/src/tools/workflows/get-workflow-run.ts +3 -2
- package/src/types.ts +1 -1
- package/src/utils/enabled-tools.ts +27 -0
- package/src/utils/internal-ai/complete-structured.ts +11 -3
- package/src/utils/openrouter-base-url.ts +23 -0
- package/src/utils/secret-rules.generated.ts +2055 -0
- package/src/utils/secret-scrubber.ts +363 -29
- package/src/workflows/engine.ts +39 -4
- package/src/workflows/executors/raw-llm.ts +1 -0
- package/src/workflows/executors/system-one-decision.ts +2 -0
- package/src/workflows/executors/validate.ts +1 -0
- package/src/workflows/executors/workflow-llm.ts +9 -2
- package/src/workflows/recovery.ts +10 -1
- package/src/workflows/resume.ts +62 -3
- package/src/workflows/retry-poller.ts +3 -0
- package/templates/skills/agent-browser/SKILL.md +2 -0
- package/templates/skills/agent-browser/content.md +2 -0
- package/templates/skills/code-quality/content.md +1 -0
- package/templates/skills/heartbeat-runbook/content.md +1 -1
- package/templates/skills/work-on-task/SKILL.md +4 -0
- package/templates/skills/work-on-task/content.md +4 -0
- package/templates/skills/workflow-iterate/content.md +9 -0
- package/dist/acp-adapter-w6zws6me.js +0 -23
- package/dist/boot-scrub-logs-246yn076.js +0 -99
- package/dist/cli-3a4m1aws.js +0 -572
- package/dist/cli-3dpj7atn.js +0 -11
- package/dist/cli-a87b04k8.js +0 -951
- package/dist/cli-cvk6yzw5.js +0 -40
- package/dist/cli-gvcgmx69.js +0 -183
- package/dist/cli-h1spv8v4.js +0 -427
- package/dist/cli-jqt9hrnz.js +0 -168
- package/dist/cli-yhmwmv0g.js +0 -9281
- package/dist/grok-adapter-yt9pab3k.js +0 -29
- package/src/be/boot-scrub-logs.ts +0 -132
- package/dist/{codex-hook-d2rkvf39.js → codex-hook-15dgc4cb.js} +1 -1
|
@@ -2,8 +2,9 @@ import { afterAll, afterEach, beforeAll, describe, expect, test } from "bun:test
|
|
|
2
2
|
import { unlink } from "node:fs/promises";
|
|
3
3
|
import { closeDb, getActivePricingRow, getDbClient, getLogsByEventType, initDb } from "../be/db";
|
|
4
4
|
import { getModelsCatalog, resetModelsCatalogForTests } from "../be/models-catalog";
|
|
5
|
-
import type
|
|
5
|
+
import { loadModelsDevCache, type ModelsDevCache } from "../be/modelsdev-cache";
|
|
6
6
|
import { refreshPricingFromModelsDev } from "../be/pricing-refresh";
|
|
7
|
+
import { buildModelsDevSeedRows, buildPricingSeedRows } from "../be/seed-pricing";
|
|
7
8
|
|
|
8
9
|
const TEST_DB_PATH = "./test-pricing-refresh.sqlite";
|
|
9
10
|
|
|
@@ -201,6 +202,74 @@ describe("models.dev runtime pricing refresh", () => {
|
|
|
201
202
|
expect(rows.map((row) => row.effective_from)).toEqual([2_000, 3_000]);
|
|
202
203
|
});
|
|
203
204
|
|
|
205
|
+
test("refreshes when dated snapshots collapse onto one amp key at different prices", async () => {
|
|
206
|
+
// models.dev's live gpt-4o family. Amp strips the dated suffix, so all four
|
|
207
|
+
// land on amp|gpt-4o; the 2024-05-13 snapshot is priced higher. Its key
|
|
208
|
+
// comes first, as upstream serves it, so a first-wins builder would pick it.
|
|
209
|
+
const cache: ModelsDevCache = {
|
|
210
|
+
openai: {
|
|
211
|
+
models: {
|
|
212
|
+
"gpt-4o-2024-05-13": { cost: { input: 5, output: 15 } },
|
|
213
|
+
"gpt-4o": { cost: { input: 2.5, output: 10, cache_read: 1.25 } },
|
|
214
|
+
"gpt-4o-2024-08-06": { cost: { input: 2.5, output: 10, cache_read: 1.25 } },
|
|
215
|
+
"gpt-4o-2024-11-20": { cost: { input: 2.5, output: 10, cache_read: 1.25 } },
|
|
216
|
+
},
|
|
217
|
+
},
|
|
218
|
+
};
|
|
219
|
+
|
|
220
|
+
const result = await refreshPricingFromModelsDev({
|
|
221
|
+
now: 1_000,
|
|
222
|
+
fetchImpl: async () => responseFor(cache, '"etag-collapse"'),
|
|
223
|
+
});
|
|
224
|
+
|
|
225
|
+
expect(result.status).toBe("refreshed");
|
|
226
|
+
expect(await getLogsByEventType("pricing.refresh.failed")).toHaveLength(0);
|
|
227
|
+
// The undated alias wins every token class.
|
|
228
|
+
expect((await getActivePricingRow("amp", "gpt-4o", "input", 1_000))?.pricePerMillionUsd).toBe(
|
|
229
|
+
2.5,
|
|
230
|
+
);
|
|
231
|
+
expect((await getActivePricingRow("amp", "gpt-4o", "output", 1_000))?.pricePerMillionUsd).toBe(
|
|
232
|
+
10,
|
|
233
|
+
);
|
|
234
|
+
expect(
|
|
235
|
+
(await getActivePricingRow("amp", "gpt-4o", "cached_input", 1_000))?.pricePerMillionUsd,
|
|
236
|
+
).toBe(1.25);
|
|
237
|
+
// Providers that keep the dated id still price each snapshot on its own.
|
|
238
|
+
expect(
|
|
239
|
+
(await getActivePricingRow("codex", "gpt-4o-2024-05-13", "input", 1_000))?.pricePerMillionUsd,
|
|
240
|
+
).toBe(5);
|
|
241
|
+
});
|
|
242
|
+
|
|
243
|
+
test("picks the newest dated snapshot when no undated alias exists", () => {
|
|
244
|
+
const rows = buildModelsDevSeedRows({
|
|
245
|
+
openai: {
|
|
246
|
+
models: {
|
|
247
|
+
"gpt-x-2025-08-07": { cost: { input: 2, output: 8 } },
|
|
248
|
+
"gpt-x-2025-01-01": { cost: { input: 1, output: 4 } },
|
|
249
|
+
},
|
|
250
|
+
},
|
|
251
|
+
}).filter((row) => row.provider === "amp");
|
|
252
|
+
|
|
253
|
+
expect(rows).toEqual([
|
|
254
|
+
{ provider: "amp", model: "gpt-x", tokenClass: "input", pricePerMillionUsd: 2 },
|
|
255
|
+
{ provider: "amp", model: "gpt-x", tokenClass: "output", pricePerMillionUsd: 8 },
|
|
256
|
+
]);
|
|
257
|
+
});
|
|
258
|
+
|
|
259
|
+
test("the vendored models.dev snapshot yields one row per (provider, model, tokenClass)", () => {
|
|
260
|
+
const cache = loadModelsDevCache();
|
|
261
|
+
expect(cache).not.toBeNull();
|
|
262
|
+
const rows = buildPricingSeedRows(cache);
|
|
263
|
+
const seen = new Set<string>();
|
|
264
|
+
const duplicates: string[] = [];
|
|
265
|
+
for (const row of rows) {
|
|
266
|
+
const key = `${row.provider}|${row.model}|${row.tokenClass}`;
|
|
267
|
+
if (seen.has(key)) duplicates.push(key);
|
|
268
|
+
seen.add(key);
|
|
269
|
+
}
|
|
270
|
+
expect(duplicates).toEqual([]);
|
|
271
|
+
});
|
|
272
|
+
|
|
204
273
|
test("writes scrubbed audit log entries for successful refreshes", async () => {
|
|
205
274
|
await refreshPricingFromModelsDev({
|
|
206
275
|
now: 1_000,
|
|
@@ -305,7 +305,9 @@ describe("Claude CLI shortnames (the tier defaults) take effort like the model t
|
|
|
305
305
|
expect(reasoningCapability("claude", "sonnet")).toEqual(
|
|
306
306
|
reasoningCapability("claude", "claude-sonnet-5-5"),
|
|
307
307
|
);
|
|
308
|
-
expect(reasoningCapability("claude", "haiku")
|
|
308
|
+
expect(reasoningCapability("claude", "haiku")).toEqual(
|
|
309
|
+
reasoningCapability("claude", "claude-haiku-5-5"),
|
|
310
|
+
);
|
|
309
311
|
expect(reasoningCapability("claude", "fable").supported).toBe(true);
|
|
310
312
|
});
|
|
311
313
|
|
|
@@ -610,6 +610,11 @@ describe("script connections", () => {
|
|
|
610
610
|
markMigrationApplied(database, "198_permission_audit_guest.sql");
|
|
611
611
|
// 200 backfills agent_memory, which this migration-112-only fixture does not create.
|
|
612
612
|
markMigrationApplied(database, "200_backfill_auto_memory_expiry.sql");
|
|
613
|
+
// 201 alters workflow_runs and workflow_run_steps, which this
|
|
614
|
+
// migration-112-only fixture does not create.
|
|
615
|
+
markMigrationApplied(database, "201_workflow_replay_columns.sql");
|
|
616
|
+
// 202 moves rows out of kv_entries, which this migration-112-only fixture does not create.
|
|
617
|
+
markMigrationApplied(database, "202_codex_oauth_device_flows.sql");
|
|
613
618
|
|
|
614
619
|
const id = crypto.randomUUID();
|
|
615
620
|
const now = new Date().toISOString();
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { describe, expect, test } from "bun:test";
|
|
2
2
|
import { classifyExit, NativeScriptExecutor } from "../scripts-runtime/executors/native";
|
|
3
|
+
import { QuickJSScriptExecutor } from "../scripts-runtime/executors/quickjs";
|
|
3
4
|
import type {
|
|
4
5
|
ExecutorInput,
|
|
5
6
|
ExecutorOutput,
|
|
@@ -128,6 +129,8 @@ function conformance(name: string, makeExecutor: () => ScriptExecutor, spawns: b
|
|
|
128
129
|
|
|
129
130
|
conformance("native", () => new NativeScriptExecutor(), true);
|
|
130
131
|
conformance("fake", () => new FakeScriptExecutor(), false);
|
|
132
|
+
// Worker threads and WASM, no Bun.spawn: runs even when the spawn probe fails.
|
|
133
|
+
conformance("quickjs", () => new QuickJSScriptExecutor(), false);
|
|
131
134
|
|
|
132
135
|
describe("native-only executor behavior", () => {
|
|
133
136
|
test.skipIf(SKIP_SANDBOX_SPAWN_TESTS)("timeout maps to timeout", async () => {
|
|
@@ -0,0 +1,442 @@
|
|
|
1
|
+
import { afterAll, describe, expect, test } from "bun:test";
|
|
2
|
+
import { QuickJSScriptExecutor, quickJSWorkerCount } from "../scripts-runtime/executors/quickjs";
|
|
3
|
+
import type { ExecutorInput } from "../scripts-runtime/executors/types";
|
|
4
|
+
import { DEFAULT_SCRIPT_RESOURCES } from "../scripts-runtime/executors/types";
|
|
5
|
+
|
|
6
|
+
// Behavior specific to the quickjs executor. The shared contract lives in
|
|
7
|
+
// script-executor-conformance.test.ts. No test here spawns a process.
|
|
8
|
+
|
|
9
|
+
const requests: Array<{ method: string; path: string; auth: string | null; agent: string | null }> =
|
|
10
|
+
[];
|
|
11
|
+
const kv = new Map<string, unknown>();
|
|
12
|
+
// Requests to /hang (or kv key "hang") never get a response. The test reads
|
|
13
|
+
// how many of them the client aborted.
|
|
14
|
+
const hang = { started: 0, aborted: 0 };
|
|
15
|
+
const slow = { inFlight: 0, maxInFlight: 0 };
|
|
16
|
+
function hangUntilAborted(req: Request): Promise<Response> {
|
|
17
|
+
hang.started++;
|
|
18
|
+
return new Promise((resolve) => {
|
|
19
|
+
req.signal.addEventListener("abort", () => {
|
|
20
|
+
hang.aborted++;
|
|
21
|
+
resolve(new Response("aborted"));
|
|
22
|
+
});
|
|
23
|
+
});
|
|
24
|
+
}
|
|
25
|
+
async function waitFor(check: () => boolean, timeoutMs = 3_000): Promise<boolean> {
|
|
26
|
+
const until = Date.now() + timeoutMs;
|
|
27
|
+
while (!check()) {
|
|
28
|
+
if (Date.now() > until) return false;
|
|
29
|
+
await Bun.sleep(10);
|
|
30
|
+
}
|
|
31
|
+
return true;
|
|
32
|
+
}
|
|
33
|
+
const server = Bun.serve({
|
|
34
|
+
port: 0,
|
|
35
|
+
async fetch(req) {
|
|
36
|
+
const url = new URL(req.url);
|
|
37
|
+
requests.push({
|
|
38
|
+
method: req.method,
|
|
39
|
+
path: url.pathname,
|
|
40
|
+
auth: req.headers.get("authorization"),
|
|
41
|
+
agent: req.headers.get("x-agent-id"),
|
|
42
|
+
});
|
|
43
|
+
if (url.pathname === "/hang" || url.pathname === "/api/kv/hang") return hangUntilAborted(req);
|
|
44
|
+
if (url.pathname === "/slow") {
|
|
45
|
+
slow.inFlight++;
|
|
46
|
+
slow.maxInFlight = Math.max(slow.maxInFlight, slow.inFlight);
|
|
47
|
+
await Bun.sleep(30);
|
|
48
|
+
slow.inFlight--;
|
|
49
|
+
return Response.json({ ok: true });
|
|
50
|
+
}
|
|
51
|
+
if (url.pathname.startsWith("/api/kv/")) {
|
|
52
|
+
const key = decodeURIComponent(url.pathname.slice("/api/kv/".length));
|
|
53
|
+
if (req.method === "PUT") {
|
|
54
|
+
const body = (await req.json()) as { value: unknown };
|
|
55
|
+
kv.set(key, body.value);
|
|
56
|
+
return Response.json({ key, value: body.value });
|
|
57
|
+
}
|
|
58
|
+
return Response.json({ key, value: kv.get(key) ?? null });
|
|
59
|
+
}
|
|
60
|
+
if (url.pathname === "/echo") {
|
|
61
|
+
return Response.json(
|
|
62
|
+
{ method: req.method, body: await req.text(), custom: req.headers.get("x-custom") },
|
|
63
|
+
{ headers: { "x-reply": "yes" } },
|
|
64
|
+
);
|
|
65
|
+
}
|
|
66
|
+
return Response.json({ error: "not found" }, { status: 404 });
|
|
67
|
+
},
|
|
68
|
+
});
|
|
69
|
+
afterAll(() => server.stop(true));
|
|
70
|
+
|
|
71
|
+
const baseUrl = `http://127.0.0.1:${server.port}`;
|
|
72
|
+
const executor = new QuickJSScriptExecutor();
|
|
73
|
+
|
|
74
|
+
function input(source: string, overrides: Partial<ExecutorInput> = {}): ExecutorInput {
|
|
75
|
+
return {
|
|
76
|
+
source,
|
|
77
|
+
args: null,
|
|
78
|
+
configPayload: {
|
|
79
|
+
system: {
|
|
80
|
+
apiKey: { value: "quickjs-test-secret", isSecret: true },
|
|
81
|
+
agentId: { value: "agent-q", isSecret: false },
|
|
82
|
+
mcpBaseUrl: { value: baseUrl, isSecret: false },
|
|
83
|
+
},
|
|
84
|
+
user: { REGION: { value: "eu", isSecret: false } },
|
|
85
|
+
},
|
|
86
|
+
resources: { ...DEFAULT_SCRIPT_RESOURCES, wallClockMs: 5_000, ...overrides.resources },
|
|
87
|
+
fsMode: "none",
|
|
88
|
+
network: "open",
|
|
89
|
+
...overrides,
|
|
90
|
+
};
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
describe("quickjs executor", () => {
|
|
94
|
+
test("runs TypeScript with a tree-shaken zod argsSchema", async () => {
|
|
95
|
+
const source = `import { z } from "zod";
|
|
96
|
+
export const argsSchema = z.object({ email: z.string().email(), n: z.number().int() });
|
|
97
|
+
type Args = z.infer<typeof argsSchema>;
|
|
98
|
+
export default async function (args: Args) {
|
|
99
|
+
return { domain: args.email.split("@")[1], double: args.n * 2 };
|
|
100
|
+
}`;
|
|
101
|
+
const ok = await executor.run({ ...input(source), args: { email: "a@b.co", n: 3 } });
|
|
102
|
+
expect(ok.error).toBeUndefined();
|
|
103
|
+
expect(ok.result).toEqual({ domain: "b.co", double: 6 });
|
|
104
|
+
|
|
105
|
+
const bad = await executor.run({ ...input(source), args: { email: "nope", n: 1.5 } });
|
|
106
|
+
expect(bad.error).toBe("eval_error");
|
|
107
|
+
expect(bad.runtimeError?.message).toContain("argsSchema validation failed:");
|
|
108
|
+
expect(bad.runtimeError?.message).toContain("email");
|
|
109
|
+
});
|
|
110
|
+
|
|
111
|
+
test("accepts args that arrive as a JSON string", async () => {
|
|
112
|
+
const output = await executor.run({
|
|
113
|
+
...input("export default async (args) => args.x + 1;"),
|
|
114
|
+
args: JSON.stringify({ x: 41 }),
|
|
115
|
+
});
|
|
116
|
+
expect(output.result).toBe(42);
|
|
117
|
+
});
|
|
118
|
+
|
|
119
|
+
test("maps runtime errors back to the original TypeScript line", async () => {
|
|
120
|
+
const source = `type T = { a: number };
|
|
121
|
+
|
|
122
|
+
function explode(value: T): never {
|
|
123
|
+
throw new Error("boom " + value.a);
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
export default async (args: T) => explode(args);
|
|
127
|
+
`;
|
|
128
|
+
const output = await executor.run({ ...input(source), args: { a: 7 } });
|
|
129
|
+
expect(output.error).toBe("eval_error");
|
|
130
|
+
expect(output.runtimeError?.message).toBe("boom 7");
|
|
131
|
+
expect(output.runtimeError?.userScriptLine).toBe(4);
|
|
132
|
+
expect(output.runtimeError?.userFrames[0]?.file).toBe("user-script.ts");
|
|
133
|
+
expect(output.stderr).toContain("user-script.ts:4:");
|
|
134
|
+
});
|
|
135
|
+
|
|
136
|
+
test("adds the (args, ctx) hint when ctx members are read off undefined", async () => {
|
|
137
|
+
const output = await executor.run(
|
|
138
|
+
input("export default async (ctx) => { const { log } = ctx.api; return log; };"),
|
|
139
|
+
);
|
|
140
|
+
expect(output.error).toBe("eval_error");
|
|
141
|
+
expect((output.runtimeError as { ctxSignatureHint?: string }).ctxSignatureHint).toContain(
|
|
142
|
+
"args comes first",
|
|
143
|
+
);
|
|
144
|
+
});
|
|
145
|
+
|
|
146
|
+
test("reports syntax errors as eval_error with the build diagnostic", async () => {
|
|
147
|
+
const output = await executor.run(input("export default async () => { return (; };"));
|
|
148
|
+
expect(output.error).toBe("eval_error");
|
|
149
|
+
expect(output.runtimeError?.name).toBe("BuildError");
|
|
150
|
+
expect(output.stderr.length).toBeGreaterThan(0);
|
|
151
|
+
});
|
|
152
|
+
|
|
153
|
+
test("interrupts a synchronous busy loop and keeps the worker usable", async () => {
|
|
154
|
+
const looped = await executor.run(
|
|
155
|
+
input("export default async () => { while (true) {} };", {
|
|
156
|
+
resources: { ...DEFAULT_SCRIPT_RESOURCES, wallClockMs: 200 },
|
|
157
|
+
}),
|
|
158
|
+
);
|
|
159
|
+
expect(looped.error).toBe("timeout");
|
|
160
|
+
expect(looped.exitCode).toBe(124);
|
|
161
|
+
|
|
162
|
+
const after = await executor.run(input("export default async () => 'alive';"));
|
|
163
|
+
expect(after.result).toBe("alive");
|
|
164
|
+
});
|
|
165
|
+
|
|
166
|
+
test("times out a promise that never settles", async () => {
|
|
167
|
+
const output = await executor.run(
|
|
168
|
+
input("export default async () => new Promise(() => {});", {
|
|
169
|
+
resources: { ...DEFAULT_SCRIPT_RESOURCES, wallClockMs: 150 },
|
|
170
|
+
}),
|
|
171
|
+
);
|
|
172
|
+
expect(output.error).toBe("timeout");
|
|
173
|
+
});
|
|
174
|
+
|
|
175
|
+
test("enforces the heap limit as oom", async () => {
|
|
176
|
+
const output = await executor.run(
|
|
177
|
+
// Growing one array hits the limit in ~100 ms. Many small strings make
|
|
178
|
+
// QuickJS's GC thrash near the limit, which takes seconds to fail.
|
|
179
|
+
input(
|
|
180
|
+
"export default async () => { const a = []; for (let i = 0; i < 1e9; i++) a.push(i); };",
|
|
181
|
+
{
|
|
182
|
+
resources: { ...DEFAULT_SCRIPT_RESOURCES, memoryMb: 16, wallClockMs: 10_000 },
|
|
183
|
+
},
|
|
184
|
+
),
|
|
185
|
+
);
|
|
186
|
+
expect(output.error).toBe("oom");
|
|
187
|
+
});
|
|
188
|
+
|
|
189
|
+
test("an abort kills a busy script and the pool recovers", async () => {
|
|
190
|
+
const controller = new AbortController();
|
|
191
|
+
setTimeout(() => controller.abort(), 100);
|
|
192
|
+
const output = await executor.run(
|
|
193
|
+
input("export default async () => { while (true) {} };", {
|
|
194
|
+
signal: controller.signal,
|
|
195
|
+
resources: { ...DEFAULT_SCRIPT_RESOURCES, wallClockMs: 10_000 },
|
|
196
|
+
}),
|
|
197
|
+
);
|
|
198
|
+
expect(output.error).toBe("killed");
|
|
199
|
+
|
|
200
|
+
const after = await executor.run(input("export default async () => 'recovered';"));
|
|
201
|
+
expect(after.result).toBe("recovered");
|
|
202
|
+
});
|
|
203
|
+
|
|
204
|
+
test("exposes no process, filesystem, or module globals", async () => {
|
|
205
|
+
// Read off globalThis: Bun.build constant-folds a bare `typeof require` to "function".
|
|
206
|
+
const output = await executor.run(
|
|
207
|
+
input(`export default async () => ({
|
|
208
|
+
process: typeof globalThis.process,
|
|
209
|
+
bun: typeof globalThis.Bun,
|
|
210
|
+
require: typeof globalThis.require,
|
|
211
|
+
})`),
|
|
212
|
+
);
|
|
213
|
+
expect(output.result).toEqual({ process: "undefined", bun: "undefined", require: "undefined" });
|
|
214
|
+
});
|
|
215
|
+
|
|
216
|
+
test("glob and grep fail with a clear message", async () => {
|
|
217
|
+
const output = await executor.run(
|
|
218
|
+
input("export default async (_args, ctx) => ctx.stdlib.glob('*.ts');"),
|
|
219
|
+
);
|
|
220
|
+
expect(output.error).toBe("eval_error");
|
|
221
|
+
expect(output.runtimeError?.message).toContain("not available in the quickjs executor");
|
|
222
|
+
});
|
|
223
|
+
|
|
224
|
+
test("routes console output to stdout and stderr", async () => {
|
|
225
|
+
const output = await executor.run(
|
|
226
|
+
input(
|
|
227
|
+
"export default async (_args, ctx) => { console.log('hello', { a: 1 }); ctx.logger.error('bad'); return 1; };",
|
|
228
|
+
),
|
|
229
|
+
);
|
|
230
|
+
expect(output.stdout).toBe('hello {"a":1}\n');
|
|
231
|
+
expect(output.stderr).toBe("bad\n");
|
|
232
|
+
});
|
|
233
|
+
|
|
234
|
+
test("setTimeout runs callbacks through the host clock", async () => {
|
|
235
|
+
const output = await executor.run(
|
|
236
|
+
input(`export default async () => {
|
|
237
|
+
const order: string[] = [];
|
|
238
|
+
const cancelled = setTimeout(() => order.push("cancelled"), 5);
|
|
239
|
+
clearTimeout(cancelled);
|
|
240
|
+
await new Promise((resolve) => setTimeout(() => { order.push("fired"); resolve(null); }, 10));
|
|
241
|
+
return order;
|
|
242
|
+
};`),
|
|
243
|
+
);
|
|
244
|
+
expect(output.result).toEqual(["fired"]);
|
|
245
|
+
});
|
|
246
|
+
|
|
247
|
+
test("ctx.swarm calls go through the host with the swarm identity", async () => {
|
|
248
|
+
requests.length = 0;
|
|
249
|
+
const output = await executor.run(
|
|
250
|
+
input(`export default async (_args, ctx) => {
|
|
251
|
+
await ctx.swarm.kv_set({ key: "greeting", value: "hi" });
|
|
252
|
+
const got = await ctx.swarm.kv_get({ key: "greeting" });
|
|
253
|
+
return got.data.value;
|
|
254
|
+
};`),
|
|
255
|
+
);
|
|
256
|
+
expect(output.error).toBeUndefined();
|
|
257
|
+
expect(output.result).toBe("hi");
|
|
258
|
+
expect(requests.map((r) => r.method)).toEqual(["PUT", "GET"]);
|
|
259
|
+
expect(requests.every((r) => r.auth === "Bearer quickjs-test-secret")).toBe(true);
|
|
260
|
+
expect(requests.every((r) => r.agent === "agent-q")).toBe(true);
|
|
261
|
+
});
|
|
262
|
+
|
|
263
|
+
test("fetch returns a Response-like object with headers and a text body", async () => {
|
|
264
|
+
const output = await executor.run(
|
|
265
|
+
input(`export default async () => {
|
|
266
|
+
const response = await fetch("${baseUrl}/echo", {
|
|
267
|
+
method: "POST",
|
|
268
|
+
headers: { "X-Custom": "c1" },
|
|
269
|
+
body: JSON.stringify({ n: 1 }),
|
|
270
|
+
});
|
|
271
|
+
return { status: response.status, ok: response.ok, reply: response.headers.get("x-reply"), body: await response.json() };
|
|
272
|
+
};`),
|
|
273
|
+
);
|
|
274
|
+
expect(output.result).toEqual({
|
|
275
|
+
status: 200,
|
|
276
|
+
ok: true,
|
|
277
|
+
reply: "yes",
|
|
278
|
+
body: { method: "POST", body: '{"n":1}', custom: "c1" },
|
|
279
|
+
});
|
|
280
|
+
});
|
|
281
|
+
|
|
282
|
+
test("fetch rejects non-string bodies", async () => {
|
|
283
|
+
const output = await executor.run(
|
|
284
|
+
input(
|
|
285
|
+
`export default async () => fetch("${baseUrl}/echo", { method: "POST", body: { n: 1 } });`,
|
|
286
|
+
),
|
|
287
|
+
);
|
|
288
|
+
expect(output.error).toBe("eval_error");
|
|
289
|
+
expect(output.runtimeError?.message).toContain("fetch body must be a string");
|
|
290
|
+
});
|
|
291
|
+
|
|
292
|
+
test("user config values stay redacted until unwrapped", async () => {
|
|
293
|
+
const output = await executor.run(
|
|
294
|
+
input(`export default async (_args, ctx) => {
|
|
295
|
+
const region = ctx.swarm.config.get("REGION");
|
|
296
|
+
return {
|
|
297
|
+
wrapped: region,
|
|
298
|
+
text: String(region),
|
|
299
|
+
value: ctx.stdlib.Redacted.value(region),
|
|
300
|
+
meta: ctx.stdlib.Redacted.meta(region),
|
|
301
|
+
missing: ctx.swarm.config.get("NOPE") === undefined,
|
|
302
|
+
};
|
|
303
|
+
};`),
|
|
304
|
+
);
|
|
305
|
+
expect(output.result).toEqual({
|
|
306
|
+
wrapped: "<redacted>",
|
|
307
|
+
text: "<redacted>",
|
|
308
|
+
value: "eu",
|
|
309
|
+
meta: { type: "user", isSecret: false },
|
|
310
|
+
missing: true,
|
|
311
|
+
});
|
|
312
|
+
});
|
|
313
|
+
|
|
314
|
+
test("a non-serializable result is an eval_error", async () => {
|
|
315
|
+
const output = await executor.run(
|
|
316
|
+
input("export default async () => { const a: any = {}; a.self = a; return a; };"),
|
|
317
|
+
);
|
|
318
|
+
expect(output.error).toBe("eval_error");
|
|
319
|
+
expect(output.runtimeError?.message).toContain("not JSON-serializable");
|
|
320
|
+
});
|
|
321
|
+
|
|
322
|
+
test("recycles a worker whose WASM memory grew past the threshold", async () => {
|
|
323
|
+
await executor.run(input("export default async () => 1;"));
|
|
324
|
+
const before = quickJSWorkerCount();
|
|
325
|
+
const big = await executor.run(
|
|
326
|
+
input(
|
|
327
|
+
"export default async () => { const a = []; for (let i = 0; i < 3e6; i++) a.push({ i }); return a.length; };",
|
|
328
|
+
),
|
|
329
|
+
);
|
|
330
|
+
expect(big.result).toBe(3e6);
|
|
331
|
+
expect(quickJSWorkerCount()).toBe(before - 1);
|
|
332
|
+
|
|
333
|
+
const after = await executor.run(input("export default async () => 'fresh';"));
|
|
334
|
+
expect(after.result).toBe("fresh");
|
|
335
|
+
});
|
|
336
|
+
|
|
337
|
+
test("host calls only reach own api/mcp/room members", async () => {
|
|
338
|
+
const output = await executor.run(
|
|
339
|
+
input(`export default async () => {
|
|
340
|
+
const call = (path: string) => (globalThis as any).__host_call(path, "[]");
|
|
341
|
+
return [
|
|
342
|
+
JSON.parse(await call("swarm.room.constructor")).error?.message,
|
|
343
|
+
JSON.parse(await call("swarm.constructor")).error?.message,
|
|
344
|
+
JSON.parse(await call("api.__proto__.x")).error?.message,
|
|
345
|
+
];
|
|
346
|
+
};`),
|
|
347
|
+
);
|
|
348
|
+
expect(output.result).toEqual([
|
|
349
|
+
"ctx.swarm.room.constructor is not a function",
|
|
350
|
+
"ctx.swarm.constructor is not a function",
|
|
351
|
+
"ctx.api.__proto__.x is not a function",
|
|
352
|
+
]);
|
|
353
|
+
});
|
|
354
|
+
|
|
355
|
+
test("aborts a host fetch the script did not await when the run returns", async () => {
|
|
356
|
+
await executor.run(input("export default async () => 0;"));
|
|
357
|
+
const workers = quickJSWorkerCount();
|
|
358
|
+
const before = { ...hang };
|
|
359
|
+
const output = await executor.run(
|
|
360
|
+
// The 100 ms wait lets the request reach the server before the run ends.
|
|
361
|
+
input(`export default async () => {
|
|
362
|
+
void fetch("${baseUrl}/hang");
|
|
363
|
+
await new Promise((resolve) => setTimeout(resolve, 100));
|
|
364
|
+
return 1;
|
|
365
|
+
};`),
|
|
366
|
+
);
|
|
367
|
+
expect(output.result).toBe(1);
|
|
368
|
+
expect(hang.started).toBe(before.started + 1);
|
|
369
|
+
expect(await waitFor(() => hang.aborted > before.aborted)).toBe(true);
|
|
370
|
+
// The run aborted and settled the fetch itself, so the pool kept the worker.
|
|
371
|
+
// Terminating the worker is only the fallback for calls that do not settle.
|
|
372
|
+
expect(quickJSWorkerCount()).toBe(workers);
|
|
373
|
+
});
|
|
374
|
+
|
|
375
|
+
test("aborts a pending host fetch when the run times out", async () => {
|
|
376
|
+
const before = hang.aborted;
|
|
377
|
+
const output = await executor.run(
|
|
378
|
+
input(`export default async () => (await fetch("${baseUrl}/hang")).text();`, {
|
|
379
|
+
resources: { ...DEFAULT_SCRIPT_RESOURCES, wallClockMs: 200 },
|
|
380
|
+
}),
|
|
381
|
+
);
|
|
382
|
+
expect(output.error).toBe("timeout");
|
|
383
|
+
expect(await waitFor(() => hang.aborted > before)).toBe(true);
|
|
384
|
+
|
|
385
|
+
const after = await executor.run(input("export default async () => 'next';"));
|
|
386
|
+
expect(after.result).toBe("next");
|
|
387
|
+
});
|
|
388
|
+
|
|
389
|
+
test("aborts ctx.swarm host calls too", async () => {
|
|
390
|
+
await executor.run(input("export default async () => 0;"));
|
|
391
|
+
const workers = quickJSWorkerCount();
|
|
392
|
+
const before = { ...hang };
|
|
393
|
+
const output = await executor.run(
|
|
394
|
+
input(
|
|
395
|
+
`export default async (_args, ctx) => {
|
|
396
|
+
void ctx.swarm.kv_get({ key: "hang" });
|
|
397
|
+
await new Promise((resolve) => setTimeout(resolve, 100));
|
|
398
|
+
return 1;
|
|
399
|
+
};`,
|
|
400
|
+
),
|
|
401
|
+
);
|
|
402
|
+
expect(output.result).toBe(1);
|
|
403
|
+
expect(hang.started).toBe(before.started + 1);
|
|
404
|
+
expect(await waitFor(() => hang.aborted > before.aborted)).toBe(true);
|
|
405
|
+
expect(quickJSWorkerCount()).toBe(workers);
|
|
406
|
+
});
|
|
407
|
+
|
|
408
|
+
test("caps concurrent host calls per run and queues the rest", async () => {
|
|
409
|
+
slow.maxInFlight = 0;
|
|
410
|
+
const output = await executor.run(
|
|
411
|
+
input(`export default async () => {
|
|
412
|
+
const replies = await Promise.all(
|
|
413
|
+
Array.from({ length: 40 }, () => fetch("${baseUrl}/slow").then((r) => r.json())),
|
|
414
|
+
);
|
|
415
|
+
return replies.filter((r: any) => r.ok).length;
|
|
416
|
+
};`),
|
|
417
|
+
);
|
|
418
|
+
expect(output.result).toBe(40);
|
|
419
|
+
expect(slow.maxInFlight).toBeGreaterThan(1);
|
|
420
|
+
expect(slow.maxInFlight).toBeLessThanOrEqual(16);
|
|
421
|
+
});
|
|
422
|
+
|
|
423
|
+
test("rejects host calls beyond the per-run pending limit", async () => {
|
|
424
|
+
const output = await executor.run(
|
|
425
|
+
input(`export default async () => {
|
|
426
|
+
const calls = Array.from({ length: 1100 }, () => fetch("${baseUrl}/slow").then(() => "ok", (e) => e.message));
|
|
427
|
+
const results = await Promise.all(calls);
|
|
428
|
+
return results.filter((r) => r !== "ok").length > 0 ? results.find((r) => r !== "ok") : "none";
|
|
429
|
+
};`),
|
|
430
|
+
);
|
|
431
|
+
expect(output.result).toContain("Too many pending host calls");
|
|
432
|
+
});
|
|
433
|
+
|
|
434
|
+
test("queues runs beyond the pool size", async () => {
|
|
435
|
+
const outputs = await Promise.all(
|
|
436
|
+
Array.from({ length: 12 }, (_, i) =>
|
|
437
|
+
executor.run({ ...input("export default async (args) => args.i * 2;"), args: { i } }),
|
|
438
|
+
),
|
|
439
|
+
);
|
|
440
|
+
expect(outputs.map((o) => o.result)).toEqual(Array.from({ length: 12 }, (_, i) => i * 2));
|
|
441
|
+
});
|
|
442
|
+
});
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { describe, expect, test } from "bun:test";
|
|
2
2
|
import { NativeScriptExecutor } from "../scripts-runtime/executors/native";
|
|
3
|
+
import { QuickJSScriptExecutor } from "../scripts-runtime/executors/quickjs";
|
|
3
4
|
import { getScriptExecutor } from "../scripts-runtime/executors/registry";
|
|
4
5
|
|
|
5
6
|
describe("getScriptExecutor", () => {
|
|
@@ -11,7 +12,11 @@ describe("getScriptExecutor", () => {
|
|
|
11
12
|
expect(getScriptExecutor("native")).toBeInstanceOf(NativeScriptExecutor);
|
|
12
13
|
});
|
|
13
14
|
|
|
15
|
+
test("returns quickjs when requested", () => {
|
|
16
|
+
expect(getScriptExecutor("quickjs")).toBeInstanceOf(QuickJSScriptExecutor);
|
|
17
|
+
});
|
|
18
|
+
|
|
14
19
|
test("throws for unknown executors", () => {
|
|
15
|
-
expect(() => getScriptExecutor("e2b")).toThrow("Available: native");
|
|
20
|
+
expect(() => getScriptExecutor("e2b")).toThrow("Available: native, quickjs");
|
|
16
21
|
});
|
|
17
22
|
});
|