diff --git a/apps/host-cloudflare/package.json b/apps/host-cloudflare/package.json index d7159620d..695e2eca7 100644 --- a/apps/host-cloudflare/package.json +++ b/apps/host-cloudflare/package.json @@ -30,6 +30,7 @@ "@executor-js/plugin-provider-service-split": "workspace:*", "@executor-js/plugin-toolkits": "workspace:*", "@executor-js/react": "workspace:*", + "@executor-js/runtime-dynamic-worker": "workspace:*", "@executor-js/runtime-quickjs": "workspace:*", "@executor-js/sdk": "workspace:*", "@jitl/quickjs-wasmfile-release-sync": "catalog:", diff --git a/apps/host-cloudflare/src/execution.ts b/apps/host-cloudflare/src/execution.ts index a3b91e651..1ee0e4b1e 100644 --- a/apps/host-cloudflare/src/execution.ts +++ b/apps/host-cloudflare/src/execution.ts @@ -10,7 +10,9 @@ import { PluginsProvider, type ExecutorDbHandle, } from "@executor-js/api/server"; +import { makeDynamicWorkerExecutor } from "@executor-js/runtime-dynamic-worker"; import { makeQuickJsExecutor } from "@executor-js/runtime-quickjs"; +import { env } from "cloudflare:workers"; import type { CloudflareConfig } from "./config"; import { makeCloudflarePlugins } from "./plugins"; @@ -20,10 +22,10 @@ import { makeCloudflarePlugins } from "./plugins"; // substrate, no-op engine decorator), with the plugins + host config built from // the per-request `env`-derived config rather than process.env. // -// QuickJS-wasm is the default code substrate because it runs in a single Worker -// with no extra binding. When Cloudflare's dynamic Worker Loader leaves closed -// beta, swap CodeExecutorProvider for the dynamic-worker executor (cloud's) — -// it's a one-Layer change behind this same seam. +// Code substrate: when the Worker declares a `worker_loaders` LOADER binding, +// use the dynamic-worker executor (cloud's substrate) — real isolates with +// `globalOutbound: null`. Without the binding, fall back to QuickJS-wasm, +// which runs in a single Worker with no extra binding. // --------------------------------------------------------------------------- export { makeExecutionStack } from "@executor-js/api/server"; @@ -31,7 +33,10 @@ export { EngineDecoratorNoop }; export const CloudflareCodeExecutorProvider: Layer.Layer = Layer.sync( CodeExecutorProvider, - () => makeQuickJsExecutor(), + () => { + const loader = (env as { LOADER?: WorkerLoader }).LOADER; + return loader ? makeDynamicWorkerExecutor({ loader }) : makeQuickJsExecutor(); + }, ); export const makeCloudflarePluginsProvider = ( diff --git a/apps/host-cloudflare/wrangler.jsonc b/apps/host-cloudflare/wrangler.jsonc index 6b3a12b7f..c3b010669 100644 --- a/apps/host-cloudflare/wrangler.jsonc +++ b/apps/host-cloudflare/wrangler.jsonc @@ -34,6 +34,15 @@ "bucket_name": "executor-blobs", }, ], + // Dynamic Worker Loader binding: when present, sandboxed code runs in real + // workerd isolates with `globalOutbound: null` (platform-enforced zero egress) + // via @executor-js/runtime-dynamic-worker; without it the host falls back to + // the QuickJS-wasm substrate. Remove this block to stay on QuickJS. + "worker_loaders": [ + { + "binding": "LOADER", + }, + ], // The MCP session Durable Object: one addressable isolate per MCP session (the // DO id IS the session id) so a session survives across the Worker's stateless // isolates — without it, `tools/list` after `initialize` can land on a fresh diff --git a/bun.lock b/bun.lock index bc5ca40ea..9bbc6b68b 100644 --- a/bun.lock +++ b/bun.lock @@ -182,6 +182,7 @@ "@executor-js/plugin-provider-service-split": "workspace:*", "@executor-js/plugin-toolkits": "workspace:*", "@executor-js/react": "workspace:*", + "@executor-js/runtime-dynamic-worker": "workspace:*", "@executor-js/runtime-quickjs": "workspace:*", "@executor-js/sdk": "workspace:*", "@jitl/quickjs-wasmfile-release-sync": "catalog:",