
在 Flue 中使用 Mirage Sandbox 适配器把应用自有的 Workspace 挂载为 Agent 沙箱【免费下载链接】flueThe sandbox agent framework.项目地址: https://gitcode.com/GitHub_Trending/flue1/flue导读本文围绕 Flue 生态中的 Mirage sandbox 适配器展开它把由应用自行创建并持有生命周期的 MirageWorkspace包装进 Flue 的SandboxFactory接口从而让 Agent 通过统一的文件与 Shell 工具访问挂载好的资源。读完本文你将掌握如何通过flue add sandbox mirage一键接入、生成的适配器源码每个关键方法的语义、Node 与 Cloudflare 两种运行时的选型规则以及 Mirage 相对其他沙箱方案如 E2B、Daytona的差异化定位。快速开始为一个已有的 Flue 项目添加挂载式工作区沙箱能力只需在终端或你惯用的 coding agent 中执行一条命令flue add sandbox mirage该命令背后的 blueprintflue-blueprint: sandbox/mirage1会引导你的编码 Agent 完成三件事按构建目标安装对应的 Mirage 运行时包Node 目标安装struktoai/mirage-node^0.0.2Cloudflare 目标安装struktoai/mirage-browser^0.0.2在源码根目录优先root/.flue/其次root/src/再次root/创建sandboxes/mirage.ts将适配器接入到你的 Agent 中。Mirage 适配器也是 Ecosystem 沙箱目录 中登记在册的官方集成项。适配器设计应用拥有 WorkspaceFlue 只做适配Mirage 适配器最核心的设计原则是所有权分离应用负责用 Mirage SDK 构造Workspace并直接控制资源挂载mounts、凭据credentials、可写边界writable boundaries与工作区生命周期lifetimeFlue只负责把已经初始化好的Workspace适配进自己的沙箱接口不创建、不保留、不销毁任何 Mirage 侧的提供商资源。这从生成文件的顶层注释可以看得一清二楚——它明确写着Wraps an already-initialized MirageWorkspace… into FluesSandboxFactoryinterface. The user owns the root and its mounts; this adapter just adapts the root见 blueprints/sandbox--mirage.md。运行时选择上Mirage 提供两个共享同一WorkspaceAPI 的运行时包运行时包适用目标说明struktoai/mirage-nodeNode.js提供 Node 兼容的Workspace资源含 SSH、数据库等 Node 专属资源struktoai/mirage-browserCloudflare提供浏览器兼容的Workspace资源Cloudflare Workers 属于 browser-class 运行时适配器文件本身只从struktoai/mirage-core两个包都会再导出引入类型因此同一份sandboxes/mirage.ts可以同时服务于两种目标具体用哪个运行时包由你在 Agent 代码里按构建目标决定。生成文件全貌以下是 blueprint 生成的sandboxes/mirage.ts的完整实现blueprints/sandbox--mirage.md 中为逐字写入的版本先给出骨架// flue-blueprint: sandbox/mirage1 import { sandboxFromDriver, SandboxOperationUnsupportedError } from flue/runtime; import type { SandboxDriver, SandboxFactory, Sandbox, FileStat } from flue/runtime; import type { Workspace as MirageWorkspace } from struktoai/mirage-core; export interface MirageAdapterOptions { /** * exec() 未显式传 cwd 时的默认工作目录。 * Mirage 工作区以 / 为根挂载点都挂在根下因此 / 是安全默认值 * 想默认落在某个可写挂载如 /data可显式指定。 */ cwd?: string; } class MirageSandboxDriver implements SandboxDriver { constructor( private workspace: MirageWorkspace, private flueContextId: string, ) {} /* ... 文件操作走 workspace.fs.*rm 在变更前拒绝 recursive/force ... */ /* ... exec()/runShell() 走 workspace.execute()见下文 ... */ } export function mirage(workspace: MirageWorkspace, options?: MirageAdapterOptions): SandboxFactory { return { async createSandbox({ id }: { id: string }): PromiseSandbox { try { workspace.createSession(id); } catch { workspace.getSession(id); } const sandboxCwd options?.cwd ?? /; const driver new MirageSandboxDriver(workspace, id); return sandboxFromDriver(driver, sandboxCwd); }, }; }上下文与会话的映射以 Flue context id 为键createSandbox({ id })中的id是 Agent 实例 idctx.id。适配器把它直接当作 Miragesession 的 id每个 Flue context 对应一个 Mirage session从而让 cwd、env、命令历史、lastExitCode在不同的 Agent 实例和会话之间保持隔离同一个 context 内初始化的多个 harness 会有意复用同一个 Mirage session。实现上先尝试workspace.createSession(id)若抛错说明 id 已注册比如同 context 内另一个 harness 或断点续跑后的 context则回退到workspace.getSession(id)——这正是同一 context 重复初始化拿到同一 session的关键。stat保留未知元数据的语义async stat(path: string): PromiseFileStat { const s await this.workspace.fs.stat(path); return { isFile: s.type file, isDirectory: s.type directory, ...(s.size null ? {} : { size: s.size }), ...(s.modified null ? {} : { mtime: new Date(s.modified) }), }; }Mirage 的FileStat形如{ name, size: number|null, modified: string|null, type: FileType|null }。适配器遵循 Sandbox Adapter API 中的FileStat契约当 Mirage 未提供size或modified时直接省略对应字段而不是伪造占位值——因为调用方无法区分伪造值与真实元数据这会影响stat的真实性判断。exec与超时/取消语义private async runShell(command, options) { const timeoutSignal typeof options?.timeoutMs number ? AbortSignal.timeout(options.timeoutMs) : undefined; const callerSignal options?.signal; const signal callerSignal timeoutSignal ? AbortSignal.any([callerSignal, timeoutSignal]) : (callerSignal ?? timeoutSignal); try { const result await this.workspace.execute(command, { sessionId: this.flueContextId, cwd: options?.cwd, env: options?.env, signal, }); return { stdout: result.stdoutText, stderr: result.stderrText, exitCode: result.exitCode }; } catch (err) { if (callerSignal?.aborted) throw err; // 调用方主动取消优先直接重抛 const isTimeout timeoutSignal?.aborted (err timeoutSignal.reason || (err instanceof Error (err.name AbortError || err.name TimeoutError))); if (isTimeout) { return { stdout: , stderr: [flue:mirage] Command timed out after ${options?.timeoutMs} milliseconds., exitCode: 124, }; } throw err; } }这段代码对应了 Sandbox Adapter API 中exec的完整契约timeoutMs用AbortSignal.timeout(timeoutMs)生成毫秒级超时信号转发给workspace.execute()的signal参数优先级调用方signal与超时信号通过AbortSignal.any([...])组合谁先触发谁生效若调用方信号先触发则throw err让宿主取消语义胜出只有超时被转换为exitCode: 124遵循timeout(1)惯例并返回带提示的 stderr其余异常正常重抛。Mirage 的执行器会在 LIST/PIPELINE/循环边界协作式地观察signal因此无需 shell 前缀之类的 workaround——cwd、env、signal 都是直接透传给ExecuteOptions的。rm直接文件系统 API 不支持递归/强制删除async rm(path: string, options?: { recursive?: boolean; force?: boolean }): Promisevoid { const unsupported [ options?.recursive ? recursive : undefined, options?.force ? force : undefined, ].filter((option): option is string option ! undefined); if (unsupported.length 0) { throw new SandboxOperationUnsupportedError({ operation: rm, provider: Mirage, options: unsupported, }); } try { await this.workspace.fs.unlink(path); } catch { await this.workspace.fs.rmdir(path); } }Mirage 的直接 VFS APIWorkspaceFS没有递归或 force 删除能力。适配器的处理完全符合 Sandbox Adapter API 的约定在任何变更之前抛出SandboxOperationUnsupportedErrortype: sandbox_operation_unsupported可从flue/runtime导入见 packages/runtime/src/errors.ts绝不静默忽略选项或留下 provider 自定义行为。unlink失败时回退到rmdir实现单层删除。mkdir -p与readdir的细节处理由于WorkspaceFS.mkdir只支持单层创建递归的mkdir -p通过 shell 转发实现Mirage 执行器原生支持mkdir -p路径用shellQuote()做单引号转义以安全嵌入 POSIX 风格命令行。readdir则对 Mirage 返回的绝对路径做处理用p.slice(p.lastIndexOf(/) 1)取 basename并过滤掉目录路径尾随/可能产生的空字符串。sandboxFromDriver包装层的职责mirage()最终把MirageSandboxDriver交给sandboxFromDriver(driver, sandboxCwd)实现见 packages/runtime/src/sandbox.ts#L379。这一包装层自动补齐了适配器不需要重复实现的部分路径解析相对路径与缺失/相对的execcwd 统一按cwd解析并做 POSIX 规范化driver 方法永远收到绝对路径writeFile父目录保证首次写入失败后自动重试一次先mkdir(parent, { recursive: true })再写满足跨模式约定abort 竞态已中止的信号在调用driver.exec前就拒绝中途 abort 也会立即拒绝调用方不等driver.exec自身的结算。Mirage 支持真实的取消原语因此转发signal能把孤儿命令窗口从命令剩余时长压缩到 SDK 取消延迟。配置要求原文档给出的配置要求清单如下要求用途struktoai/mirage-node包Node.js 上必需—— 提供 Node 兼容的 Mirage Workspace 资源struktoai/mirage-browser包Cloudflare 上必需—— 仅提供浏览器兼容的 Workspace 资源应用自有的资源配置必需—— 定义挂载、凭据、可写边界与生命周期环境变量凭据不需要—— Mirage 资源的凭据由应用自行配置运行时包与构建目标的匹配blueprint 会通过vite.config.ts中是否同时存在cloudflare()插件与flue()、flue.config.ts中的target: cloudflare、项目根是否存在wrangler.jsonc/.toml/.json来判断目标无法判断时会向你确认。值得特别警惕的是部分 Mirage 资源是 Node 专属的——SSHResource、PostgresResource、MongoDBResource、EmailResource、FUSE 等。从struktoai/mirage-browser导入它们是构建错误因此只要用到其中任何一个就锁定 Node 目标。另外如果 Mirage 文档中出现struktoai/mirage-agents不要为 Flue 安装它——那是面向其他 Agent 框架的适配器与 Flue 无关。认证方式无 API Key凭据按资源配置Mirage 本身没有 API Key——它是进程内运行的没有需要认证的远程服务。认证是按挂载资源的每个后端S3Resource、SlackResource、GitHubResource、PostgresResource等在构造资源时由你配置各自凭据适配器完全不接触它们。凭据的存储遵循项目既有约定AGENTS.md、.env、.dev.vars、密钥管理器或 CI 变量。作为参考flue run默认加载项目的.env--env file可指定备用文件vite dev与构建后的服务器读取 shell 环境变量。将适配器接入 Agent在 Agent 中接线的方式是标准的useSandbox()模式use agent; import { Workspace, RAMResource, MountMode } from struktoai/mirage-node; import { useModel, useSandbox } from flue/runtime; import { mirage } from ../sandboxes/mirage; // 按实际布局调整路径 export function Assistant() { useModel(anthropic/claude-sonnet-4-6); const ws new Workspace({ /data: new RAMResource() }, { mode: MountMode.WRITE }); useSandbox(mirage(ws, { cwd: /data })); return You are a helpful assistant with a full sandbox.; }几个要点顶部的use agent指令负责把模块注册进应用mirage(ws, { cwd: /data })中的cwd让 Agent 默认工作目录落在可写挂载/data上只有当 Agent 需要 HTTP 端点时才需要在app.ts中挂载createAgentRouter(...)来自flue/runtime/routingflue run与dispatch()无需挂载即可工作沙箱工厂是惰性的构造工厂对象很廉价真正昂贵的createSandbox()只在初始化时调用一次重渲染时不会重建。接入后Agent 会获得一整套基于沙箱的工具read带 offset/limit 分页、write、edit、bash、grep、glob以及基于工作目录的 workspace 上下文目录列表、AGENTS.md与.agents/skills/下的 workspace skills。要验证接入是否成功可先运行类型检查npx tsc --noEmit再确认适配器导入路径与实际文件位置一致最后用flue run agent模块路径 --message ...或vite dev启动完整应用实测。何时选择 Mirage根据原文档的定位当你的应用希望从显式挂载的资源组装出一个工作区并通过单一沙箱边界把它呈现给 Agent时Mirage 是合适的选择。此时资源挂载、凭据、可写边界与工作区生命周期全部由应用掌控Flue 只负责适配边界。相比 E2Bprovider 托管的 Linux 沙箱需要E2B_API_KEY等远程沙箱Mirage 完全进程内运行无 API Key隔离边界来自你对挂载资源的组合与可写边界设定Mirage 的 SDK 支持真实取消因此 abort 时命令会被真正停止这一点在 Sandboxes 指南 中被明确提及区别于那些无法中断、只能让孤儿进程在后台继续跑的 provider适用于宿主环境本身可信、但你需要给 Agent 一个受控的可写工作区的场景例如基于挂载的 S3/Slack/GitHub/数据库资源构建的自托管 Agent。进一步阅读Sandboxes 指南 ——useSandbox()钩子、沙箱工具集、workspace 上下文与子代理继承关系Sandbox Adapter API ——SandboxFactory/Sandbox/SandboxDriver完整契约、sandboxFromDriver与SandboxOperationUnsupportedErrorDeploy on Node.js 与 Deploy on Cloudflare —— 两种运行时的部署指南对应struktoai/mirage-node与struktoai/mirage-browser的选择Mirage blueprint 原文 —— 含逐字可用的完整生成文件与验证步骤Ecosystem 沙箱目录 —— Mirage 及其他已支持沙箱提供商的索引【免费下载链接】flueThe sandbox agent framework.项目地址: https://gitcode.com/GitHub_Trending/flue1/flue创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考