news 2026/9/16 11:05:35

使用 WorkerJavaScriptBackend 隔离运行 JavaScript 模块:@cloudflare/computer 隔离运行时实战指南

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
使用 WorkerJavaScriptBackend 隔离运行 JavaScript 模块:@cloudflare/computer 隔离运行时实战指南

使用 WorkerJavaScriptBackend 隔离运行 JavaScript 模块:@cloudflare/computer 隔离运行时实战指南

【免费下载链接】computerGive your agent a computer 👾项目地址: https://gitcode.com/GitHub_Trending/computer1/computer

导读

@cloudflare/computer为 Durable Object 提供了一套开箱即用的虚拟文件系统与执行后端,而 WorkerJavaScriptBackend 是其核心能力之一:在每次执行时创建一个全新的 Cloudflare Dynamic Worker,在隔离的 ECMAScript 模块运行时中运行用户代码。它支持静态导入、字面量动态导入、顶层 await、持久化相对导入、宿主安装的配置模块,以及由 Workspace 提供的持久化node:fs/promises和受信任的ws:git/ws:artifacts能力模块。阅读本文后,你将掌握该后端的完整配置项、执行模型、资源限制、取消语义与隔离边界,能够为你的 Agent 搭建安全、可回放、受约束的 JavaScript 代码执行环境。

前置说明:@cloudflare/computer目前处于PREVIEW(预览)阶段(见 docs/README.md),API 不稳定、设计可能调整,适合实验与原型,暂不适合生产环境;本文描述的是当前仓库(spec 目录为前瞻性说明,代码以 packages/computer 实际实现为准)。


一、整体架构:一次执行 = 一个全新的 Dynamic Worker

WorkerJavaScriptBackend(源码位于 packages/computer/src/backends/worker-javascript/worker-javascript.ts,类型定义与导出见 packages/computer/src/backends/worker-javascript/index.ts)的运行模型是:

  1. 调用方通过Workspace.runtime.exec()传入一段真实 ES 模块源码
  2. 后端先用 acorn 解析模块图(见 packages/computer/src/backends/worker-javascript/module-graph.ts),收集所有静态导入、导出与字面量动态导入;
  3. 把源码与依赖打包成一个模块图,通过loader.load()启动一个全新的 Dynamic Worker
  4. Worker 加载宿主生成的workspace-runtime-runner.js运行器,动态import入口模块并调用其default导出函数;
  5. 运行器通过WorkspaceRuntimeBridge(见 packages/computer/src/runtime/bridge.ts)以 RPC 方式访问宿主侧的 Workspace 文件系统与能力模块;
  6. stdout/stderr 以帧的形式实时流回宿主,追加到执行事件流;结果与退出事件在输出流关闭后发布。

最小可运行示例

import { Workspace } from "@cloudflare/computer"; import { WorkerJavaScriptBackend } from "@cloudflare/computer/backends/worker-javascript"; const workspace = new Workspace({ storage: ctx.storage, backends: [ new WorkerJavaScriptBackend({ loader: env.LOADER, // 动态 Worker 加载器(Worker Loader binding) root: "/workspace", // 代码可见的文件系统根 access: "read-write", // "read" | "read-write" defaultTimeoutMs: 60_000, // 默认超时 maxTimeoutMs: 180_000, // 单次执行允许的最大超时 globalOutbound: null, // 默认关闭出站网络 modules: { "math-kit": `export const double = value => value * 2;`, // 配置模块(bare import) }, }), ], });

执行一个模块:

const handle = await workspace.runtime.exec( ` import { double } from "math-kit"; import fs from "node:fs/promises"; export default async function main(input) { const value = double(input.value); await fs.writeFile("/workspace/result.txt", String(value)); return { value, persisted: await fs.readFile("/workspace/result.txt", "utf8") }; } `, { backend: "worker-javascript", input: { value: 21 }, encoding: "utf8", }, ); const result = await handle.result(); // result.value = { value: 42, persisted: "42" }

这里的源码是真正的 ES 模块import { double } from "math-kit"命中后端构造时配置的modules映射,import fs from "node:fs/promises"命中宿主安装的持久化文件系统。如果模块default导出一个函数,Workspace 会用options.input调用它;否则模块求值完成时返回一个null的结构化结果(对应运行器源码中的typeof module.default === "function" ? await module.default(input) : module.default ?? null,见 worker-javascript.ts)。

调用返回时机与执行生命周期

runtime.exec()在 Dynamic Worker完成之前就会返回:运行会在其事件流被消费、且宿主对 Dynamic Worker 的调用仍处于 in-flight 状态时持续推进。这部分挂起的工作本身就能让 Durable Object 保持驻留(resident)。

  • 如果拿到了 handle 却从不读取事件流,一旦对象转为空闲,运行可能被驱逐;
  • 因此:持续排空事件流(或调用result())来保持运行存活
  • 对于必须跨驱逐存活的持久化工作,通过ctx.storage.setAlarm()调度 alarm,而不是依赖挂起调用。

执行事件流的类型定义见 packages/computer/src/runtime/types.ts:包含stdoutstderr与携带code(以及可选result)的exit事件。


二、持久化相对导入:模块图在宿主侧解析

相对导入从cwd出发,通过持久化的 Workspace 文件系统解析。先在 Workspace 里写入一个任务模块,再通过相对路径导入它:

await workspace.fs.writeFile( "/workspace/task.js", ` import fs from "node:fs/promises"; export default input => fs.writeFile("/workspace/value.txt", String(input.value)); `, ); await workspace.runtime.exec( `import task from "./task.js"; export default task;`, { backend: "worker-javascript", cwd: "/workspace", input: { value: 42 }, }, );

模块图构建(module-graph.ts)在加载 Worker 之前就完成了全部静态分析:

  • 加载前解析整个图:用 acorn 把源码解析成 AST,遍历ImportDeclarationExportNamedDeclarationExportAllDeclarationImportExpression
  • 约束每个持久化路径:所有相对导入都先经WorkspaceRuntimeCapability.resolveConfined()归一化并约束在root之内(见 capability.ts),越界路径直接抛错;
  • 拒绝符号链接穿越#assertSafeComponents会逐段lstat检查路径组件,任何中间符号链接都会被拒绝(见 capability.ts);
  • 动态导入必须使用字符串字面量import(\./${name}.js`)` 这类模板字符串在解析阶段就会抛错(见 module-graph.ts);
  • 绝对导入不被支持import "/abs/path.js"会被拒绝,必须改用相对 Workspace 导入。

此外,模块图受聚合源码大小(maxSourceBytes)、模块数量(默认maxModules128,见 module-graph.ts)与导入深度(默认maxDepth32)约束;加载到 Dynamic Worker 的完整模块图还被assertLoaderGraph限制为最多 256 个模块(见 worker-javascript.ts)。


三、执行限额与记录保留

并发执行上限

后端默认同时接纳最多24 个执行maxConcurrentExecutions,构造器默认值见 worker-javascript.ts)。超过上限的并发启动会以EEXEC_BUSY错误失败,而不是无限创建 Dynamic Worker。同一执行 id 已存在也会抛出EEXEC_BUSY/EEXEC_EXISTS(见 worker-javascript.ts)。

在调整maxConcurrentExecutions之前,请先实测部署环境的 Durable Object 与 Worker Loader 限额,再据此上调。

单次执行的字节与数量限额

每次执行都会约束以下资源(可分别下调,公共负载场景建议收紧):

选项默认值作用
maxSourceBytes1 MiB源码与模块图聚合大小
maxInputBytes1 MiB结构化输入序列化后的字节数
maxResultBytes1 MiB结构化结果字节数(宿主侧assertResult校验)
maxStdinBytes256 KiB标准输入字节数
maxEnvBytes1 MiBenv记录总字节数
maxStdioBytes1 MiBstdout + stderr 合计字节数
maxCapabilityBytes1 MiB单次能力调用请求/响应载荷(最小 256 字节)
maxHostCallMs默认maxTimeoutMs单次宿主能力调用的调用方可见期限
maxConcurrentCapabilityCalls32并发的宿主能力调用数
maxCapabilityCalls256累计能力调用次数
maxCapabilityRequestBytes8 MiB能力调用累计请求字节
maxCapabilityResponseBytes8 MiB能力调用累计响应字节
maxDirectoryEntries1024单次目录读取返回的最大条目数
maxExecutionSubscribers8单次执行的实时事件订阅者数

(以上默认值全部来自 worker-javascript.ts 的构造器解析逻辑。)

实现细节值得注意:

  • 目录读取限额在 SQLite 层生效readdir会以maxDirectoryEntries + 1作为 limit 请求,超过即抛错,避免物化超量行(见 capability.ts);
  • 请求在隔离内、Workers RPC 前检查一次,宿主再检查一次:隔离侧workspace-capabilities.js先校验序列化后的请求大小(见 module-graph.ts),宿主侧WorkspaceRuntimeBridge.call()再对载荷、并发、总数与累计字节做二次校验(见 bridge.ts)。

已完成执行的保留与回放

  • 默认保留60 分钟retentionMs),同一后端最多保留100 条完成记录(maxRetainedExecutions,见 worker-javascript.ts);
  • 完成记录立即离开内存中的活跃集合,回放时从 SQLite 读取:后端在连接时创建workspace_runtime_executionsworkspace_runtime_events两张表,事件按(backend, execution_id, seq)持久化(见 worker-javascript.ts);
  • 内存清理与 SQLite 清理都由#prune()驱动,按finished_at与条数双条件删除过期记录(见 worker-javascript.ts)。

取消语义

取消(killExec/disposeExec/ Workspace 关闭)会:

  1. 停止新的宿主能力调用cancelAndDrain()#cancelled = true并 abort 所有在途AbortController(见 bridge.ts);
  2. dispose 掉 Dynamic Worker
  3. 等待已接受的宿主调用 settle 之后才发布 exit 130。

正常完成同样遵循这条 drain 规则,因此未 await 的能力调用不可能在 exit 0 之后继续改动 Workspace。宿主调用存在调用方可见的截止期限(maxHostCallMs):错过期限会让该能力调用失败并把执行标记为 failed——即使调用方代码 catch 了这个错误。但执行仍会等待已接受的宿主操作本身完成后再发布终态事件,因为许多宿主 API 在派发后无法回滚外部副作用。

受信任模块会收到可选的{ signal, deadline }上下文,必须在 signal abort 时及时停止;一个无视取消、永不 settle 的受信任模块会让执行停留在 finalizing 状态。

运行期配置

compatibilityDatecompatibilityFlags控制 Dynamic Worker 运行时,默认分别为"2026-05-23"["nodejs_compat"](见 worker-javascript.ts),即包内测试过的设置;日期必须符合YYYY-MM-DD格式,否则构造抛错。


四、环境变量、标准输入与processshim

每次执行都会安装一个极小的node:processshim,让普通模块代码可以读取自己的环境与标准流。shim 只暴露调用方为该次执行提供的内容,宿主环境完全不可见

  • process.env:是 exec options 中env记录的快照。未传入的变量不存在,Durable Object 自身环境从不合并进去——模块无法通过process.env读取宿主 binding 或 secrets;
  • process.stdin:一个非交互式async-iterable,按for await消费调用方传入的stdin字节一次后即结束;由于 evaluate-once 执行没有会话可等待,不存在阻塞读取更多输入isTTY恒为false。输入受maxStdinBytes约束,超限会以明确错误失败;
  • process.stdout/process.stderr:可写流,写入会流向实时输出(见下文"隔离与生命周期")。console.log/console.info路由到 stdout,console.warn/console.error路由到 stderr,两者共享同一个maxStdioBytes上限;
  • process.argvprocess.cwd()process.platform:返回惰性值——cwd()反映该次执行的cwd,而argv(固定为["workspace", <entryName>])与platform(固定为"linux")是占位符,不描述宿主进程

上述行为对应运行器源码(worker-javascript.ts):nextProcess只包含env、固定argvcwd()、固定platform与一次性stdin迭代器,且安装时会尽力覆盖globalThis.process(不可覆盖时则只回填env/stdin/stdout/stderr)。

示例:读取 stdin 与环境

const handle = await workspace.runtime.exec( ` export default async function main() { let piped = ""; for await (const chunk of process.stdin) piped += new TextDecoder().decode(chunk); console.log("received", piped.length, "bytes"); return { who: process.env.WHO, piped }; } `, { backend: "worker-javascript", env: { WHO: "demo" }, stdin: "hello", encoding: "utf8", }, );

env必须是 string-to-string 记录,超出maxEnvBytes会抛错(assertEnv,见 worker-javascript.ts);stdin只接受字符串或Uint8ArraynormalizeStdin)。


五、配置模块(Configured modules)

bare imports 在后端构造时安装,而不是在单次执行时传递:

new WorkerJavaScriptBackend({ loader: env.LOADER, modules: { "tar-stream": TAR_STREAM_BUNDLE, }, });
  • 未知的 bare import 会在创建 Worker 之前失败:模块图构建时,凡是既非相对路径、又非node:*/ws:*/ 内部模块名的 specifier,都必须命中configuredModules,否则抛Module "X" is not configured for the worker-javascript backend.(见 module-graph.ts);
  • node:fsnode:fs/promises宿主安装的例外,由持久化 Workspace 背书;
  • 配置模块只是代码,不是宿主权限:它们不得使用保留的ws:命名空间,也不得遮蔽node:fsnode:fs/promises两个文件系统 specifier(配置模块名含/ws:前缀或与内部模块名冲突都会在 module-graph.ts 中被拒绝)。

六、受信任的 Workspace 模块

node:fs/node:fs/promises:持久化文件系统

文件系统访问使用熟悉的异步 Node API,但底层是持久化的 Workspace而非 isolate 本地文件系统。两种写法都自动安装:

import fs from "node:fs/promises"; // or: import { promises as fs } from "node:fs"; const text = await fs.readFile("/workspace/input.txt", "utf8"); await fs.writeFile("/workspace/output.txt", text.toUpperCase());

受支持的 promise API 为:readFilewriteFilemkdirrmchmodsymlinkreadlinkreaddirstatlstataccess(实现见 module-graph.ts)。行为边界:

  • readFile省略 encoding 时返回字节Uint8Array),仅支持"utf8"/"utf-8"文本编码,其他编码(如"base64")被拒绝;
  • writeFile支持默认"w"标志与独占"wx"其他 Node 标志被拒绝;与 Node 一致,父目录必须已存在writeFileNode只放行w/wx,见 capability.ts);
  • readlink保留相对符号链接目标;通过符号链接的读写被 Workspace 约束边界拒绝
  • 同步与回调式 Node 文件系统 API刻意不可用,因为每个操作都要跨越 isolate → Workspace 的能力边界(隔离侧通过workspace-capabilities.js安装的全局分发器路由到宿主WorkspaceRuntimeBridge.call,见 module-graph.ts)。

ws:命名空间:宿主能力模块

整个ws:命名空间为 Workspace 维护的宿主能力保留;内置运行时安装ws:gitws:artifacts。调用方模块与持久化文件都不能遮蔽node:fsnode:fs/promisesws:*。未知的ws:导入在模块图构建时即失败(Unknown trusted Workspace module,见 module-graph.ts)。

ws:git
import { clone, diff, status, log, cli } from "ws:git";

ws:git显式宿主权限而非环境化的隔离区联网。clone、fetch、pull、push、ls-remote与 submodule 等命令即使 Dynamic Worker 配置了globalOutbound: null也能发起宿主侧请求,因此默认一律拒绝;只有在受信任的后端构造上显式设置allowGitNetwork: true才可用,本地 Git 操作无需该权限。实现上,bridge.#callGit对网络命令调用#requireGitNetwork,对写操作调用#requireWrite,并对cli参数做-C/--git-dir/--work-tree路径覆盖防护(见 bridge.ts)。

ws:artifacts
import { create, get, list, importArtifact, deleteArtifact, } from "ws:artifacts";

这些模块是沙箱侧的 shim,背后是宿主 RPC。Loader binding、凭据、Durable Object 存储与不受限的 Workspace 对象永不进入用户代码。宿主桥接层在每次变更上都校验后端固定的 read / read-write 权限(#requireWrite);未配置 Artifacts binding 时,Artifacts 方法会明确失败(Workspace Artifacts are not configured for this execution.,见 bridge.ts)。远程ws:artifacts.importArtifact()独立于 Git 网络权限,默认也被拒绝,需allowArtifactNetwork: true

路径约束的安全边界

路径约束会拒绝词法逃逸(..、NUL 字节等)与操作前路径中的每一个符号链接组件(见 capability.ts)。但要注意:这些检查不是原子化的 inode 式"root 之下"原语——不要把单个 isolate 能力当作针对另一个、可在同一可变 Workspace 中并发替换路径的更高权限主体的安全边界。需要对抗这种并发替换的部署,应等待未来的事务性 DOFS 原语,或使用独立的 Workspace 身份。


七、隔离与生命周期

每次执行都会获得一个全新的 Dynamic Worker,并伴随:

  • 显式的 Worker Loader CPU 限额(limits: { cpuMs: timeoutMs },见 worker-javascript.ts);
  • 宿主墙钟期限(timeoutMsmaxTimeoutMs约束);
  • 默认globalOutbound: null(无出站网络;也可通过egress策略或globalOutboundFetcher 配置网关);
  • 有限、无环、JSON 兼容的结构化输入与结果校验:assertRuntimeValue拒绝循环、非平凡原型与非有限数值(见 capability.ts);
  • 可配置的 source / module graph / input / result / stdin / stdio / 文件与能力调用请求 / 响应字节限额;
  • 显式的 entrypoint 与 Worker dispose(disposeQuietly,见 worker-javascript.ts);
  • 宿主持有的取消(host-owned cancellation);
  • 事件与结果行保留在 Workspace 数据库中。

实时输出管道

stdout / stderr实时流式输出

  1. 隔离侧运行器把IdentityTransformStream的可读端通过host.attachOutput(readable)交给宿主(见 worker-javascript.ts);
  2. 宿主在用户代码仍在运行时逐帧排空该流(#pumpFrames,见 worker-javascript.ts),每帧到达即追加到执行事件流(帧编解码见 packages/computer/src/backends/worker-javascript/frames.ts),而不是缓存到运行结束再发布
  3. 结构化结果与退出事件在输出流关闭后 settle,因此终态事件总是跟随最后一行输出
  4. 输出由maxStdioBytes在两条流上共同限定;超过后隔离侧会以...[stdio truncated]标记截断并停止记录(见 worker-javascript.ts)。

已完成写入立即可持久化;失败或取消不会回滚已完成的文件系统效果


八、受信任的集成扩展(Trusted integrations)

宿主可以通过WorkerJavaScriptBackend.trustedModules配置额外的保留能力模块:

new WorkerJavaScriptBackend({ loader: env.LOADER, trustedModules: { "ws:my-service": { async call(method, args, context) { // 实现你的宿主能力 }, }, }, });

这些模块在后端构造时固定,调用方源码无法提供或替换(类型定义WorkspaceTrustedModule见 packages/computer/src/runtime/types.ts)。受信任模块名必须以ws:开头、使用唯一简单的保留名,且不得与内置的node:fsnode:fs/promisesws:gitws:artifacts冲突(校验见 module-graph.ts)。宿主桥接层在分发trusted/<specifier>.call时同样会校验参数与返回值均为 JSON 兼容的 Workspace 值(assertBridgeValues),并为受信任模块传入{ signal, deadline }取消上下文(见 bridge.ts)。


九、相关文档与源码索引

  • 运行时入口与后端路由:05. Runtime Interface、16. Execution runtime architecture
  • 后端类型与选项定义:packages/computer/src/runtime/types.ts、packages/computer/src/backends/worker-javascript/worker-javascript.ts
  • 模块图构建与能力 shim:packages/computer/src/backends/worker-javascript/module-graph.ts
  • 帧编解码:packages/computer/src/backends/worker-javascript/frames.ts
  • 宿主能力桥接与限额执行:packages/computer/src/runtime/bridge.ts
  • 路径约束与结构化值校验:packages/computer/src/runtime/capability.ts
  • 后端行为测试:packages/computer/src/backends/worker-javascript/worker-javascript.test.ts
  • 包级总览与安装方式:docs/README.md(npm install @cloudflare/computer,入口子路径@cloudflare/computer/backends/worker-javascript

综上所述,WorkerJavaScriptBackend的价值在于把"运行一段不可信模块代码"与"访问持久化 Workspace 能力"之间,建立了一条显式、可配限额、可审计、可回放的受控边界:模块图在加载前静态解析并约束,能力调用在隔离内与宿主双重校验,输出实时回流且终态严格落库。它是构建 Agent 代码执行、插件沙箱、脚本化任务等场景的可靠基础件。

【免费下载链接】computerGive your agent a computer 👾项目地址: https://gitcode.com/GitHub_Trending/computer1/computer

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/9/16 11:04:35

拆解微信小程序电商源码:启动流程、接口封装与渲染实践

简介&#xff1a;这份微信小程序电商源码合集&#xff0c;聚焦小程序开发场景&#xff0c;覆盖外卖、门店、展示、批发商城、分销等多种电商业务模板&#xff0c;适合从初学者到进阶开发的各类人群用于学习、参考或直接二次开发。压缩包共1287个文件&#xff0c;大小仅1.96MB&a…

作者头像 李华
网站建设 2026/9/16 11:04:04

2023年AI领域三大核心争议与技术突破解析

1. 今年AI领域的关键争议点全景扫描2023年的AI行业就像一场永不停歇的技术辩论赛&#xff0c;每天都有新观点在学术会议、科技媒体和社交平台上激烈碰撞。作为全程跟踪这场辩论的观察者&#xff0c;我把核心争议归纳为三个主战场&#xff1a;1.1 大模型军备竞赛的边界争议当GPT…

作者头像 李华
网站建设 2026/9/16 11:01:06

PanEval评测体系:AI开源生态的标准化与合规创新

1. 项目背景与行业意义PanEval项目的诞生标志着中欧科技合作进入新阶段。这个由北京智源人工智能研究院与Eclipse基金会联合推出的评测体系&#xff0c;正在重新定义AI开源生态的协作模式。作为长期关注AI基础设施发展的从业者&#xff0c;我观察到这个项目至少解决了三个行业痛…

作者头像 李华
网站建设 2026/9/16 11:01:05

审批自动过了以后,谁来补那次判断

摘要&#xff1a;审批超时后自动通过&#xff0c;最容易把责任藏进系统状态。肯耐珂萨提醒 HR&#xff0c;超时规则要说明谁曾经判断、谁接续处理、哪些例外必须回看。“系统已经自动通过了&#xff0c;你找我也没用。”员工听完这句话&#xff0c;通常会更生气。申请确实已经通…

作者头像 李华