diff --git a/package-lock.json b/package-lock.json index 74451ff75..d24535499 100644 --- a/package-lock.json +++ b/package-lock.json @@ -757,6 +757,15 @@ "resolved": "packages/chord", "link": true }, + "node_modules/@earendil-works/dquickts": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/@earendil-works/dquickts/-/dquickts-0.2.0.tgz", + "integrity": "sha512-Wlg2tN0xHgWNf3yS6KWenzlLY/EQ0ifS0Qj0wbYi4/7cT+X5saQkVFNyPzRHYvo0kuF4e6hWQJsyvG+0pb27lQ==", + "license": "MIT", + "engines": { + "node": ">=22.18.0" + } + }, "node_modules/@earendil-works/gondolin": { "version": "0.14.0", "resolved": "https://registry.npmjs.org/@earendil-works/gondolin/-/gondolin-0.14.0.tgz", @@ -4720,12 +4729,6 @@ "once": "^1.3.1" } }, - "node_modules/quickjs-wasi": { - "version": "3.6.2", - "resolved": "https://registry.npmjs.org/quickjs-wasi/-/quickjs-wasi-3.6.2.tgz", - "integrity": "sha512-FCqGtGOrMgzUiIrMNMA2YnsOxCNwo31dzqXvclXUC6xeT35NJLKXQJsvbeCTjvoFAwZgEAPg8U6+KAPDGXn8Mg==", - "license": "MIT" - }, "node_modules/rc": { "version": "1.2.8", "resolved": "https://registry.npmjs.org/rc/-/rc-1.2.8.tgz", @@ -5830,7 +5833,7 @@ "version": "1.4.0", "license": "MIT", "dependencies": { - "quickjs-wasi": "3.6.2" + "@earendil-works/dquickts": "0.2.0" }, "devDependencies": { "shx": "0.3.4", @@ -5845,6 +5848,7 @@ "version": "1.4.0", "license": "MIT", "dependencies": { + "@earendil-works/dquickts": "0.2.0", "@earendil-works/pi-agent-core": "1.4.0", "@earendil-works/pi-ai": "1.4.0", "@earendil-works/pi-codemode": "1.4.0", @@ -5862,7 +5866,6 @@ "jiti": "2.7.0", "minimatch": "10.2.6", "proper-lockfile": "4.1.2", - "quickjs-wasi": "3.6.2", "semver": "7.8.5", "typebox": "1.3.27", "undici": "8.12.3", diff --git a/packages/codemode/CHANGELOG.md b/packages/codemode/CHANGELOG.md index decd59f9e..cde6c3066 100644 --- a/packages/codemode/CHANGELOG.md +++ b/packages/codemode/CHANGELOG.md @@ -2,6 +2,12 @@ ## [Unreleased] +### Breaking Changes + +- Scripts now run on dquickts (`@earendil-works/dquickts`, Bellard's QuickJS compiled to WebAssembly) instead of `quickjs-wasi` (QuickJS-NG). Hosts that pass their own `wasm` must pass `@earendil-works/dquickts/quickjs.wasm`; `loadQuickJSWasm()`, `cloudflareQuickJSWasm`, and `createInlineCodemodeSandbox()` load it already. Scripts run about 1.3 to 1.9 times faster, and the Wrangler bundle grows from about 640 KB to 850 KB +- Deep recursion in a script now throws a catchable `InternalError: stack overflow` instead of `RangeError: Maximum call stack size exceeded`. The inline and remote sandboxes allow about 950 calls instead of 1,000 +- Deterministic runs: the engine is part of the VM fingerprint, so a run started before this version reports a different fingerprint and is not replayed on it. `Math.random()` gives other values for the same seed + ## [1.4.0] - 2026-10-21 ## [1.3.0] - 2026-10-15 diff --git a/packages/codemode/README.md b/packages/codemode/README.md index e4fa87786..64162512e 100644 --- a/packages/codemode/README.md +++ b/packages/codemode/README.md @@ -105,7 +105,7 @@ Supported fields are `max_output_tokens`, a token budget for the output, and `ti ## Bundled Node and Bun hosts -By default `CodemodeSandbox` loads `quickjs-wasi/quickjs.wasm` from the installed package and starts the worker file that sits next to this package's module. Neither exists on disk when the host is bundled, so pass both: +By default `CodemodeSandbox` loads `@earendil-works/dquickts/quickjs.wasm` from the installed package and starts the worker file that sits next to this package's module. Neither exists on disk when the host is bundled, so pass both: ```ts import { CodemodeSandbox, loadQuickJSWasm } from "@earendil-works/pi-codemode"; @@ -170,7 +170,7 @@ export default { } ``` -Install `@earendil-works/pi-codemode` in the sandbox Worker's project and run `npx wrangler deploy`. Wrangler bundles QuickJS (about 640 KB) as a compiled WebAssembly module. The object has no routes and keeps no state, and it never uses its storage. +Install `@earendil-works/pi-codemode` in the sandbox Worker's project and run `npx wrangler deploy`. Wrangler bundles QuickJS (about 850 KB) as a compiled WebAssembly module. The object has no routes and keeps no state, and it never uses its storage. ### Using it from the agent's Worker @@ -369,6 +369,6 @@ The worker keeps script execution off the host thread: QuickJS runs synchronousl ## Runtime notes -- Node and Bun support memory limits, interrupts, and a catchable `RangeError` for deep recursion (QuickJS's stack guard is enabled; without it the wasm stack overflows and traps). Bun may delay worker exit during non-interruptible built-ins; results do not wait for exit. +- Node and Bun support memory limits, interrupts, and a catchable `InternalError: stack overflow` for deep recursion (QuickJS's stack guard is enabled; without it the wasm stack overflows and traps). Bun may delay worker exit during non-interruptible built-ins; results do not wait for exit. - Stack traces use QuickJS frames (`at f (codemode.js:2:31)`), prefixed with `Name: message` like V8. The wrapper prefix shares line 1 with the script, so line numbers are exact; column numbers on line 1 are shifted. - QuickJS is an interpreter. Glue code and filtering tool results are fast enough; heavy computation is slower than in V8. diff --git a/packages/codemode/package.json b/packages/codemode/package.json index b1f0fea9c..7397b319b 100644 --- a/packages/codemode/package.json +++ b/packages/codemode/package.json @@ -67,7 +67,7 @@ "node": ">=22.19.0" }, "dependencies": { - "quickjs-wasi": "3.6.2" + "@earendil-works/dquickts": "0.2.0" }, "devDependencies": { "shx": "0.3.4", diff --git a/packages/codemode/src/cloudflare-modules.d.ts b/packages/codemode/src/cloudflare-modules.d.ts index e6cb6faed..045f78bb2 100644 --- a/packages/codemode/src/cloudflare-modules.d.ts +++ b/packages/codemode/src/cloudflare-modules.d.ts @@ -10,7 +10,7 @@ declare module "cloudflare:workers" { } } -// A pattern, not the exact specifier: coding-agent declares "quickjs-wasi/quickjs.wasm" as a path +// A pattern, not the exact specifier: coding-agent declares "@earendil-works/dquickts/quickjs.wasm" as a path // for Bun, and an exact declaration takes precedence where both are visible. cloudflare.ts casts the // import to the compiled module Wrangler actually produces. declare module "*/quickjs.wasm" { diff --git a/packages/codemode/src/cloudflare.ts b/packages/codemode/src/cloudflare.ts index e7a5bc11b..cd27464e7 100644 --- a/packages/codemode/src/cloudflare.ts +++ b/packages/codemode/src/cloudflare.ts @@ -13,7 +13,7 @@ * that computes blocks the caller until it awaits a tool or uses up its interrupt budget. */ import { DurableObject } from "cloudflare:workers"; -import quickjsWasmImport from "quickjs-wasi/quickjs.wasm"; +import quickjsWasmImport from "@earendil-works/dquickts/quickjs.wasm"; import { DEFAULT_INTERRUPT_BUDGET, InlineCodemodeSandbox, @@ -31,7 +31,7 @@ const quickjsWasm = quickjsWasmImport as unknown as CodemodeWasmModule; /** Default VM memory limit of {@link CodemodeSandboxDurableObject}. Isolates have 128 MB in total. */ export const DEFAULT_REMOTE_MEMORY_LIMIT_BYTES = 32 * 1024 * 1024; -/** `quickjs-wasi/quickjs.wasm`, compiled by Wrangler. */ +/** `@earendil-works/dquickts/quickjs.wasm`, compiled by Wrangler. */ export const cloudflareQuickJSWasm: CodemodeWasmModule = quickjsWasm; /** `InlineCodemodeSandbox` with the QuickJS module Wrangler bundled. */ diff --git a/packages/codemode/src/runtime/inline.ts b/packages/codemode/src/runtime/inline.ts index dd183a6c5..5716f418f 100644 --- a/packages/codemode/src/runtime/inline.ts +++ b/packages/codemode/src/runtime/inline.ts @@ -38,12 +38,12 @@ import { startVm } from "./vm.ts"; export const DEFAULT_INTERRUPT_BUDGET = 100_000; -/** Allows recursion about 1,000 calls deep and trips well before a 1 MB host stack overflows. */ +/** Allows recursion about 950 calls deep and trips well before a 1 MB host stack overflows. */ export const INLINE_MAX_STACK_SIZE = 256 * 1024; export interface InlineCodemodeSandboxOptions extends CodemodeSandboxBaseOptions { /** - * Compiled `quickjs-wasi/quickjs.wasm`. On Cloudflare, import it with Wrangler's `CompiledWasm` + * Compiled `@earendil-works/dquickts/quickjs.wasm`. On Cloudflare, import it with Wrangler's `CompiledWasm` * rule (`@earendil-works/pi-codemode/cloudflare` does this); on Node, `loadQuickJSWasm()`. */ wasm: CodemodeWasmModule | Promise; diff --git a/packages/codemode/src/runtime/protocol.ts b/packages/codemode/src/runtime/protocol.ts index 0f1e126a4..0063a04b2 100644 --- a/packages/codemode/src/runtime/protocol.ts +++ b/packages/codemode/src/runtime/protocol.ts @@ -28,7 +28,7 @@ export interface VmStartData { } export interface WorkerData extends VmStartData { - /** Compiled `quickjs-wasi` module. Structured clone shares the compiled code with the worker. */ + /** Compiled `@earendil-works/dquickts` module. Structured clone shares the compiled code with the worker. */ wasm: CodemodeWasmModule; /** * One Int32 the host sets to non-zero before terminating the worker. The VM's interrupt handler diff --git a/packages/codemode/src/runtime/vm.ts b/packages/codemode/src/runtime/vm.ts index 2618eef9b..988f5ee35 100644 --- a/packages/codemode/src/runtime/vm.ts +++ b/packages/codemode/src/runtime/vm.ts @@ -35,7 +35,7 @@ * The VM reports `started` with its fingerprint before the script runs, and `idle` with the number of calls issued * after each drain, so the host can check a replay step by step. */ -import { Intrinsics, JSException, type JSValueHandle, MAX_STACK_SIZE, QuickJS } from "quickjs-wasi"; +import { Intrinsics, JSException, type JSValueHandle, MAX_STACK_SIZE, QuickJS } from "@earendil-works/dquickts"; import type { CodemodeWasmModule } from "../wasm.ts"; import { PRELUDE_SOURCE } from "./prelude-source.ts"; import type { HostToWorkerMessage, VmStartData, WorkerToHostMessage } from "./protocol.ts"; @@ -50,7 +50,8 @@ export interface StartVmOptions { data: VmStartData; wasm: CodemodeWasmModule; /** - * QuickJS's guard on its own stack, which turns deep recursion into a catchable `RangeError`. + * QuickJS's guard on its own stack, which turns deep recursion into a catchable + * `InternalError: stack overflow`. * The wasm frames also use the host thread's native stack, so the guard must trip before that * stack runs out: a host overflow fails the whole execution instead. Default: `MAX_STACK_SIZE` * (512 KiB), which fits the 4 MB stack of a Node worker thread but not a 1 MB main thread. @@ -171,7 +172,7 @@ export async function startVm(options: StartVmOptions): Promise { wasm: options.wasm, memoryLimit: data.memoryLimitBytes, // Without a guard, deep recursion overflows the wasm stack and traps instead of throwing a - // catchable RangeError. + // catchable InternalError. maxStackSize, interruptHandler: options.shouldInterrupt, wasi: clock ? deterministicWasi(clock) : discardOutput, @@ -235,15 +236,13 @@ export async function startVm(options: StartVmOptions): Promise { let api: JSValueHandle; let settle: JSValueHandle; let stalled: JSValueHandle; - // `executePendingJobs()` runs the queue to empty without a chance to interrupt, so jobs run one at a - // time through the (internal, but public) exports; quickjs-wasi is pinned to an exact version. - const exports = vm._getExports(); + // `executePendingJobs()` runs the queue to empty without a chance to interrupt, so jobs run one at a time. /** Run queued jobs, then fail a script that waits on nothing that can ever resume it. */ const drain = () => { let jobs = 0; - while (exports.qjs_is_job_pending()) { - // A job only fails for uncatchable errors, such as an interrupt. - if (exports.qjs_execute_pending_job() < 0) throw new Error("Promise job failed"); + while (vm.isJobPending()) { + // Script errors, interruptions included, reject their promise; this only throws if the job itself fails. + vm.executePendingJob(); if (++jobs % JOBS_PER_POLL === 0 && options.shouldInterrupt()) throw new Error("Execution interrupted"); } vm.callFunction(stalled, api).dispose(); @@ -316,7 +315,7 @@ export async function startVm(options: StartVmOptions): Promise { const budget = options.interruptBudget ?? "none"; post({ type: "started", - fingerprint: `quickjs-wasi ${versions["quickjs-wasi"]}, quickjs ${versions.quickjs}, prelude ${PRELUDE_HASH}, deterministic ${DETERMINISTIC_VERSION}, stack ${maxStackSize}, heap ${data.memoryLimitBytes ?? "none"}, budget ${budget}`, + fingerprint: `dquickts ${versions.dquickts}, quickjs ${versions.quickjs}, prelude ${PRELUDE_HASH}, deterministic ${DETERMINISTIC_VERSION}, stack ${maxStackSize}, heap ${data.memoryLimitBytes ?? "none"}, budget ${budget}`, }); } diff --git a/packages/codemode/src/types.ts b/packages/codemode/src/types.ts index e69cd290f..9a8b72312 100644 --- a/packages/codemode/src/types.ts +++ b/packages/codemode/src/types.ts @@ -134,8 +134,8 @@ export interface CodemodeSandboxBaseOptions { /** Options of `CodemodeSandbox`, which runs each VM in a Node or Bun worker thread. */ export interface CodemodeSandboxOptions extends CodemodeSandboxBaseOptions { /** - * Compiled `quickjs-wasi/quickjs.wasm`, usually from {@link loadQuickJSWasm}. Default: - * `loadQuickJSWasm()`, the file in the installed `quickjs-wasi` package. Pass it when that file + * Compiled `@earendil-works/dquickts/quickjs.wasm`, usually from {@link loadQuickJSWasm}. Default: + * `loadQuickJSWasm()`, the file in the installed `@earendil-works/dquickts` package. Pass it when that file * is not on disk, for example in a Bun compiled executable. */ wasm?: CodemodeWasmModule | Promise; diff --git a/packages/codemode/src/wasm.ts b/packages/codemode/src/wasm.ts index 7e2edec34..197e8b2fa 100644 --- a/packages/codemode/src/wasm.ts +++ b/packages/codemode/src/wasm.ts @@ -2,7 +2,7 @@ import { readFile } from "node:fs/promises"; import { createRequire } from "node:module"; /** - * A compiled `WebAssembly.Module` of `quickjs-wasi/quickjs.wasm`. Typed opaquely because the Node + * A compiled `WebAssembly.Module` of `@earendil-works/dquickts/quickjs.wasm`. Typed opaquely because the Node * type definitions do not declare the WebAssembly globals (they live in the DOM lib). */ export type CodemodeWasmModule = object; @@ -15,11 +15,11 @@ const modules = new Map>(); /** * Read and compile the QuickJS wasm once per path. `path` defaults to the file in the installed - * `quickjs-wasi` package; pass it when that file lives elsewhere, for example embedded in a Bun + * `@earendil-works/dquickts` package; pass it when that file lives elsewhere, for example embedded in a Bun * compiled executable. A failed load is retried on the next call. */ export function loadQuickJSWasm(path?: string): Promise { - const resolved = path ?? createRequire(import.meta.url).resolve("quickjs-wasi/quickjs.wasm"); + const resolved = path ?? createRequire(import.meta.url).resolve("@earendil-works/dquickts/quickjs.wasm"); let module = modules.get(resolved); if (!module) { const { WebAssembly } = globalThis as unknown as { WebAssembly: WebAssemblyGlobal }; diff --git a/packages/codemode/test/deterministic.test.ts b/packages/codemode/test/deterministic.test.ts index cb5d7895a..b54bff8e0 100644 --- a/packages/codemode/test/deterministic.test.ts +++ b/packages/codemode/test/deterministic.test.ts @@ -194,7 +194,7 @@ for (const [backend, create] of Object.entries(BACKENDS)) { ]); const fingerprint = recorded.fingerprint()!; expect(fingerprint).toMatch( - /^quickjs-wasi \S+, quickjs \S+, prelude [0-9a-f]{8}, deterministic \d+, stack \d+, heap 67108864, budget \S+$/, + /^dquickts \S+, quickjs \S+, prelude [0-9a-f]{8}, deterministic \d+, stack \d+, heap 67108864, budget \S+$/, ); if (backend === "remote") expect(fingerprint).toContain(`budget ${BUDGET}`); else expect(fingerprint).toContain("budget none"); @@ -404,7 +404,7 @@ async function waitFor(check: () => boolean): Promise { } /** The first two `Math.random()` values with seed 0. */ -const PINNED_SEED_ZERO = [0.6711372530266764, 0.7258461452833667]; +const PINNED_SEED_ZERO = [0.28083505005035936, 0.6711372530266764]; /** The first `Math.random()` with seed 1. */ -const PINNED_FIRST_RANDOM = 0.34227450605335275; +const PINNED_FIRST_RANDOM = 0.5616701001007189; diff --git a/packages/codemode/test/sandbox.test.ts b/packages/codemode/test/sandbox.test.ts index 27d36bc4e..f9c338fb9 100644 --- a/packages/codemode/test/sandbox.test.ts +++ b/packages/codemode/test/sandbox.test.ts @@ -584,6 +584,24 @@ describe.each(Object.keys(BACKENDS))("%s backend", (name) => { expect(result).toMatchObject({ ok: false, error: { kind: "timeout" } }); }); + it("calls no tool after an interrupted async function", async () => { + // The interrupted function's rejection handler and the code after its call must not run. + let calls = 0; + const sandbox = createSandbox([{ name: "side", execute: () => ++calls }]); + const result = await sandbox.execute( + "const p = (async () => { while (true) {} })(); p.catch(() => tools.side()); tools.side(); await p", + { timeoutMs: 200 }, + ); + expect(result).toMatchObject({ ok: false, error: { kind: "timeout" } }); + expect(calls).toBe(0); + }); + + it("terminates a loop of interrupted async functions on timeout", async () => { + const sandbox = createSandbox(); + const result = await sandbox.execute("while (true) (async () => { while (true) {} })()", { timeoutMs: 200 }); + expect(result).toMatchObject({ ok: false, error: { kind: "timeout" } }); + }); + it("aborts via signal and cancels in-flight calls", async () => { const controller = new AbortController(); let toolSignal: AbortSignal | undefined; @@ -671,14 +689,14 @@ describe.each(Object.keys(BACKENDS))("%s backend", (name) => { expect(results.map((result) => (result.ok ? result.value : result.error))).toEqual(["a", "b", "undefined"]); }); - it("turns deep recursion into a catchable RangeError", async () => { + it("turns deep recursion into a catchable InternalError", async () => { const sandbox = createSandbox(); const result = await sandbox.execute(` let depth = 0; function dive() { depth++; dive(); } - try { dive(); } catch (error) { return [error.name, depth > 1000]; } + try { dive(); } catch (error) { return [error.name, depth > 900]; } `); - expect(result).toMatchObject({ ok: true, value: ["RangeError", true] }); + expect(result).toMatchObject({ ok: true, value: ["InternalError", true] }); }); }); diff --git a/packages/coding-agent/CHANGELOG.md b/packages/coding-agent/CHANGELOG.md index 0af338b96..264b5c0ad 100644 --- a/packages/coding-agent/CHANGELOG.md +++ b/packages/coding-agent/CHANGELOG.md @@ -12,6 +12,7 @@ - `pi update` for installs from the pi.dev installer no longer passes `--min-release-age=0` to `npm ci`. The release lockfile pins every package, so the flag had no effect, and security scanners that block it stopped the update ([#11262](https://github.com/earendil-works/pi/issues/11262)) - `/model` and `/scoped-models` now show a model's name next to its ID when the name adds words the ID lacks, such as a renamed `models.json` override ([#5916](https://github.com/earendil-works/pi/issues/5916), [#11347](https://github.com/earendil-works/pi/pull/11347)) +- Code mode scripts now run on dquickts (`@earendil-works/dquickts`) instead of `quickjs-wasi`, about 1.3 to 1.9 times faster. Deep recursion in a script now fails with `InternalError: stack overflow` instead of `RangeError` ### Fixed diff --git a/packages/coding-agent/install-lock/package-lock.json b/packages/coding-agent/install-lock/package-lock.json index 25e273cf6..081f3fe33 100644 --- a/packages/coding-agent/install-lock/package-lock.json +++ b/packages/coding-agent/install-lock/package-lock.json @@ -385,6 +385,15 @@ "node": ">=6.9.0" } }, + "node_modules/@earendil-works/dquickts": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/@earendil-works/dquickts/-/dquickts-0.2.0.tgz", + "integrity": "sha512-Wlg2tN0xHgWNf3yS6KWenzlLY/EQ0ifS0Qj0wbYi4/7cT+X5saQkVFNyPzRHYvo0kuF4e6hWQJsyvG+0pb27lQ==", + "license": "MIT", + "engines": { + "node": ">=22.18.0" + } + }, "node_modules/@earendil-works/pi-agent-core": { "version": "1.4.0", "resolved": "https://registry.npmjs.org/@earendil-works/pi-agent-core/-/pi-agent-core-1.4.0.tgz", @@ -485,7 +494,7 @@ "resolved": "https://registry.npmjs.org/@earendil-works/pi-codemode/-/pi-codemode-1.4.0.tgz", "license": "MIT", "dependencies": { - "quickjs-wasi": "3.6.2" + "@earendil-works/dquickts": "0.2.0" }, "engines": { "node": ">=22.19.0" @@ -496,6 +505,7 @@ "resolved": "https://registry.npmjs.org/@earendil-works/pi-coding-agent/-/pi-coding-agent-1.4.0.tgz", "license": "MIT", "dependencies": { + "@earendil-works/dquickts": "0.2.0", "@earendil-works/pi-agent-core": "1.4.0", "@earendil-works/pi-ai": "1.4.0", "@earendil-works/pi-codemode": "1.4.0", @@ -513,7 +523,6 @@ "jiti": "2.7.0", "minimatch": "10.2.6", "proper-lockfile": "4.1.2", - "quickjs-wasi": "3.6.2", "semver": "7.8.5", "typebox": "1.3.27", "undici": "8.12.3", @@ -1326,12 +1335,6 @@ "node": ">= 20" } }, - "node_modules/quickjs-wasi": { - "version": "3.6.2", - "resolved": "https://registry.npmjs.org/quickjs-wasi/-/quickjs-wasi-3.6.2.tgz", - "integrity": "sha512-FCqGtGOrMgzUiIrMNMA2YnsOxCNwo31dzqXvclXUC6xeT35NJLKXQJsvbeCTjvoFAwZgEAPg8U6+KAPDGXn8Mg==", - "license": "MIT" - }, "node_modules/retry": { "version": "0.13.1", "resolved": "https://registry.npmjs.org/retry/-/retry-0.13.1.tgz", diff --git a/packages/coding-agent/package.json b/packages/coding-agent/package.json index 0e452d9b5..3321d1806 100644 --- a/packages/coding-agent/package.json +++ b/packages/coding-agent/package.json @@ -41,6 +41,7 @@ "prepublishOnly": "npm run clean && npm run build" }, "dependencies": { + "@earendil-works/dquickts": "0.2.0", "@earendil-works/pi-agent-core": "1.4.0", "@earendil-works/pi-ai": "1.4.0", "@earendil-works/pi-codemode": "1.4.0", @@ -58,7 +59,6 @@ "jiti": "2.7.0", "minimatch": "10.2.6", "proper-lockfile": "4.1.2", - "quickjs-wasi": "3.6.2", "semver": "7.8.5", "typebox": "1.3.27", "undici": "8.12.3", diff --git a/packages/coding-agent/src/bun/quickjs-wasm.d.ts b/packages/coding-agent/src/bun/quickjs-wasm.d.ts index 27d94a780..a6e3bb7f3 100644 --- a/packages/coding-agent/src/bun/quickjs-wasm.d.ts +++ b/packages/coding-agent/src/bun/quickjs-wasm.d.ts @@ -1,5 +1,5 @@ // Imported by runtime-setup.ts. Bun's default loader for .wasm evaluates to the file's path. -declare module "quickjs-wasi/quickjs.wasm" { +declare module "@earendil-works/dquickts/quickjs.wasm" { const path: string; export default path; } diff --git a/packages/coding-agent/src/bun/runtime-setup.ts b/packages/coding-agent/src/bun/runtime-setup.ts index 20b66365f..702802866 100644 --- a/packages/coding-agent/src/bun/runtime-setup.ts +++ b/packages/coding-agent/src/bun/runtime-setup.ts @@ -1,8 +1,8 @@ +// Bun loads .wasm imports as files: embedded in compiled executables, evaluating to a readable path. +import quickjsWasmPath from "@earendil-works/dquickts/quickjs.wasm"; import { bedrockProviderModule } from "@earendil-works/pi-ai/bedrock-provider"; import { registerBunOAuthFlows } from "@earendil-works/pi-ai/bun-oauth"; import { setBedrockProviderModule } from "@earendil-works/pi-ai/compat"; -// Bun loads .wasm imports as files: embedded in compiled executables, evaluating to a readable path. -import quickjsWasmPath from "quickjs-wasi/quickjs.wasm"; import { APP_NAME, setEmbeddedQuickJSWasmPath } from "../config.ts"; process.title = APP_NAME; diff --git a/packages/coding-agent/src/config.ts b/packages/coding-agent/src/config.ts index 61dd63f38..0b332b590 100644 --- a/packages/coding-agent/src/config.ts +++ b/packages/coding-agent/src/config.ts @@ -488,11 +488,11 @@ export function setEmbeddedQuickJSWasmPath(path: string): void { } /** - * Get path to `quickjs-wasi/quickjs.wasm`, the VM that runs codemode scripts. Resolved once so the + * Get path to `@earendil-works/dquickts/quickjs.wasm`, the VM that runs codemode scripts. Resolved once so the * compiled module cached per path keeps working after an update removes this install (#10439). */ export function getQuickJSWasmPath(): string { - quickJSWasmPath ??= createRequire(import.meta.url).resolve("quickjs-wasi/quickjs.wasm"); + quickJSWasmPath ??= createRequire(import.meta.url).resolve("@earendil-works/dquickts/quickjs.wasm"); return quickJSWasmPath; } diff --git a/packages/durable/test/codemode-imports.test.ts b/packages/durable/test/codemode-imports.test.ts index df74da0e2..1d8f85557 100644 --- a/packages/durable/test/codemode-imports.test.ts +++ b/packages/durable/test/codemode-imports.test.ts @@ -2,8 +2,8 @@ import { expect, test, vi } from "vitest"; import { createCodemodeExtension } from "../src/codemode/codemode.ts"; // The extension runs scripts in the sandbox the host passes, so loading it must not load the WASM VM (#11254). -vi.mock("quickjs-wasi", () => { - throw new Error("the Code Mode extension loaded quickjs-wasi"); +vi.mock("@earendil-works/dquickts", () => { + throw new Error("the Code Mode extension loaded @earendil-works/dquickts"); }); test("the Code Mode extension loads without pi-codemode's WASM runtime", () => {