跳转到内容

输入关键词开始搜索文档。

沙盒适配器 API

最后更新 查看 Markdown 源文件

沙盒适配器将执行环境(提供者 SDK、容器、主机、内存模拟)封装为 useSandbox(...) 接受的工厂契约。本页文档记录该契约:SandboxFactory 接口、适配器必须产生的 Sandbox 表面、从更简单形状产生它的帮助函数、适配器工具工厂和内置工厂。关于选择和使用沙盒,请参阅 沙盒指南;关于支持的提供者目录,请参阅生态系统中的沙盒。

模型从不调用此表面。 智能体的模型面向文件和 shell 功能是内置工具 — 带有偏移/限制分页的 read、字符串替换的 edit、grep、glob — 构建在其之上,并在 智能体行为 中记录。本页适用于两个受众:实现提供者的适配器作者,以及通过 harness.sandbox 脚本化环境的应用代码。

本页所有符号均从 @flue/runtime 导出,除了 local()(来自 @flue/runtime/node)和 cloudflareSandbox()(来自 @flue/runtime/cloudflare)。以前的名称 — SessionEnv、SandboxApi、SessionToolFactory、createSandboxSessionEnv 和工厂方法 createSessionEnv — 仍然作为弃用别名可用,因此现有适配器可以继续编译和运行而无需更改。

SandboxFactory

interface SandboxFactory {
  createSandbox(options: { id: string }): Promise<Sandbox>;
  tools?: SandboxToolFactory;
}

传递给 useSandbox(...) 或组合到智能体 sandbox: 配置中的值。工厂对象本身构造成本低廉 — 智能体在每次渲染时构建一个新的。所有昂贵的工作都属于 createSandbox() 内部。

  • createSandbox(options) — 构建环境。每个初始化的运行时调用一次 — 每个 init() 调用一次 — 该运行时的每个会话和任务会话共享返回的沙盒。重新渲染从不重建环境。拒绝会使智能体的初始化失败。
  • options.id — 智能体实例 ID (ctx.id)。在同一上下文中初始化的多个运行时接收相同的 id,因此在 id 上键控提供者资源的适配器必须容忍使用相同值的重复调用。在 id 上键控提供者工作空间是对话如何在消息和重启之间获得持久文件系统的方式。
  • tools — 可选。存在时,替换此沙盒的框架默认模型面向工具集。参见 SandboxToolFactory。

使用 sandboxFromDriver 提供通用路径和中止管道的基于提供者 SDK 的最小适配器:

import { sandboxFromDriver, type SandboxDriver, type SandboxFactory } from '@flue/runtime';

export function myProvider(client: MyProviderClient): SandboxFactory {
  return {
    async createSandbox({ id }) {
      const sandbox = await client.findOrCreate(id);
      // 将每个 SandboxDriver 方法映射到提供者 SDK
      const driver: SandboxDriver = {/* map each SandboxDriver method to the provider SDK */};
      return sandboxFromDriver(driver, '/workspace');
    },
  };
}

契约故意不包含的内容:

  • 没有拆解动词。 没有 dispose() 或生命周期回调。Flue 连接到工厂提供给它的东西,从不自行创建、重用或销毁提供者基础设施 — 配置和删除属于应用程序(通常在工厂内部或围绕它的应用代码中)。适配器不得代表应用程序调用提供者的 delete()/terminate()/kill()。
  • 没有每消息重建。 环境在每个初始化的运行时解析一次。适配器无法观察单个消息或轮次。
  • 遗留方法名称。 实现重命名前 createSessionEnv 的工厂仍然有效:当 createSandbox 缺失时,运行时会调用它(带有一次弃用警告)。新适配器应实现 createSandbox。
  • 除了 id 没有其他标识。 工厂只接收实例 ID,没有其他内容 — 没有对话内容,没有请求数据。适配器需要的任何其他内容都必须在构建工厂的闭包中捕获。

useSandbox cwd 作用域

当智能体传递 useSandbox(factory, { cwd }) 时,运行时会在 createSandbox() 解析后将适配器的沙盒包装在作用域层中。适配器不参与其中,不得自行应用智能体的 cwd:

  • cwd 值通过适配器环境自己的 resolvePath 解析(因此相对值会根据适配器的基础目录解析),然后进行 POSIX 标准化。
  • 包装器将所有相对文件路径根据作用域 cwd 解析,将 exec 的工作目录默认设置为它,并将相对的每次调用 exec cwd 根据它解析。
  • 包装器只暴露标准的 Sandbox 成员。适附加到其沙盒上的额外属性(原生表面)不会被转发 — 需要原生表面的智能体不得在 useSandbox 上设置 cwd 覆盖。

Sandbox

interface Sandbox {
  exec(
    command: string,
    options?: {
      cwd?: string;
      env?: Record<string, string>;
      timeoutMs?: number;
      signal?: AbortSignal;
    },
  ): Promise<ShellResult>;

  readFile(path: string): Promise<string>;
  readFileBuffer(path: string): Promise<Uint8Array>;
  writeFile(path: string, content: string | Uint8Array): Promise<void>;
  stat(path: string): Promise<FileStat>;
  readdir(path: string): Promise<string[]>;
  exists(path: string): Promise<boolean>;
  mkdir(path: string, options?: { recursive?: boolean }): Promise<void>;
  rm(path: string, options?: { recursive?: boolean; force?: boolean }): Promise<void>;

  cwd: string;
  resolvePath(p: string): string;
}

智能体的实时沙盒:通用环境接口。每种沙盒模式 — 虚拟、本地、远程 — 都实现了它,因此核心逻辑从不根据模式分支。同一个对象作为 harness.sandbox 暴露给应用代码,标准模型面向工具通过它操作。对其进行的操作从不记录在对话中。

大多数适配器不应手动实现此接口:sandboxFromDriver(基于提供者 SDK)和 bash()(基于 just-bash 实例)从较小的表面产生符合要求的沙盒。下面的契约是这些包装器保证的内容,也是手写实现必须重现的内容。

路径语义

  • 路径是 POSIX 风格,/ 分隔。(Windows 上的 local() 使用主机路径语义。)
  • 每个文件方法都接受绝对和相对路径。相对路径根据 cwd 解析。
  • cwd — 环境的工作目录,作为绝对路径。工作区发现(目录列表、AGENTS.md、.agents/skills/)和默认命令执行在此处发生。
  • resolvePath(p) — 根据 cwd 解析相对路径而不访问文件系统;绝对路径直接通过。文件方法在内部解析 — 只有当调用者自己的逻辑需要绝对路径时才需要 resolvePath。标准 write/edit 工具也使用它来键控每个文件的变异锁,因此同一路径的两种拼写必须解析为相同的字符串。

exec

运行 shell 命令并解析其输出。

  • 对于任何完成的命令(包括非零退出码)解析为 ShellResult。拒绝保留用于传输失败和中止。
  • options.cwd — 此命令的工作目录。相对值根据 env.cwd 解析;省略时,命令在 env.cwd 中运行。
  • options.env — 提供给命令的环境变量,层叠在适配器定义的任何基础环境之上。
  • options.timeoutMs — 毫秒级的挂钟截止时间提示,以及主要的取消契约。将其转发到提供者的原生超时选项(E2B timeoutMs、Daytona timeout、Modal timeout 等),以便信号盲提供者仍然遵守截止时间。粒度较粗的提供者可以向上取整,但从不向下取整。
  • options.signal — 取消。中止会立即用 AbortError(DOMException)拒绝返回的 promise,将信号的原因作为 cause — 从不依赖远程命令的解决。提供者可以在飞行中取消的适配器会这样做,因此拒绝是精确的;不能这样做的适配器会让命令作为孤儿继续执行(并变异工作区),在拒绝后继续执行,其最终结果被丢弃而不是稍后出现。AbortError 消息会说明这一点。参见 sandboxFromDriver 了解包装器如何实现这一点以及如何观察孤儿的解决。
  • timeoutMs 和 signal 是独立的。有截止时间还想要临时取消的调用者传递两者;支持两者的适配器应观察先触发的那个。标准 bash 工具在模型请求超时时传递两者。

文件动词

  • readFile(path) — 读取 UTF-8 文件。如果路径不存在或不是文件则抛出异常。
  • readFileBuffer(path) — 读取原始字节。
  • writeFile(path, content) — 创建或替换文件。必须创建缺失的父目录 — 这是跨模式保证(fs.writeFile('out/nested/report.md', …) 从不需要先执行 mkdir)。sandboxFromDriver、bash() 和 local() 都通过在对父目录执行 mkdir -p 后重试一次失败的写入来实现它;手写沙盒必须提供相同的保证。
  • stat(path) — 文件元数据。如果路径不存在则抛出异常。
  • readdir(path) — 目录条目名称(仅名称,没有路径)。如果路径不是目录则抛出异常。
  • exists(path) — 如果文件或目录存在则为 true。从不抛出异常。
  • mkdir(path, options) — 创建目录;recursive 创建缺失的父目录并容忍现有目录。
  • rm(path, options) — 删除文件或目录;recursive 删除目录内容,force 抑制缺失路径错误。提供者无法满足请求选项的适配器必须在修改任何内容之前抛出 SandboxOperationUnsupportedError — 永远不要静默忽略选项或将其行为留为提供者定义。

文件动词抛出的错误会作为工具错误出现在模型面前,因此消息应该是事实性的和自包含的(标准工具会传递它们)。

ShellResult

interface ShellResult {
  stdout: string;
  stderr: string;
  exitCode: number;
}

FileStat

interface FileStat {
  isFile: boolean;
  isDirectory: boolean;
  isSymbolicLink?: boolean;
  size?: number;
  mtime?: Date;
}
  • 当提供者不暴露 isSymbolicLink、size 和 mtime 时,它们会被省略。适配器绝不能伪造占位符值(new Date()、0、false)— 调用者无法将它们与真实元数据区分开来。
  • 对于符号链接,isFile/isDirectory/size/mtime 描述目标,isSymbolicLink 描述路径本身(stat -L 的语义加上非跟随检查;local() 和 Cloudflare Sandbox 适配器都实现了这一点)。

扩展 Sandbox

适配器可以返回带有额外属性的沙盒 — 超出通用动词的原生表面。harness.sandbox 暴露的对象与返回的完全相同,因此适配器包可以提供运行时检查的访问器来缩小到它(Cloudflare Computer 适配器的 computerWorkspace(harness.sandbox) 以这种方式返回其 Workspace)。两个约束:

  • useSandbox 上的 cwd 覆盖会包装沙盒并丢弃额外属性(上文)。
  • 不能执行命令的沙盒仍然应该提供所有文件动词并从 exec 抛出异常 — 并将沙盒与省略 exec 支持的标准工具的 tools 列表配对。

sandboxFromDriver(driver, cwd, options?)

function sandboxFromDriver(
  driver: SandboxDriver,
  cwd: string,
  options?: { onOrphanSettled?: (settlement: OrphanedExecSettlement) => void },
): Sandbox;

将 SandboxDriver(远程提供者适配器实现的最小接口)包装成符合要求的 Sandbox。包装器提供:

  • 路径解析:相对文件路径和相对/缺失的 exec 工作目录根据 cwd 解析,POSIX 标准化。driver 方法总是接收绝对路径。
  • writeFile 父创建保证:失败的写入在 driver.mkdir(parent, { recursive: true }) 后重试一次;当重试仍然失败时,重试写入的错误会传播。
  • exec 中止竞争:已中止的信号在调用 driver.exec 之前用 AbortError 拒绝。在飞行中触发的中止也会立即拒绝 — 包装器从不等待 driver.exec 自己的解决来决定调者的结果,无论适配器是否将 signal 连接到其 SDK 中。适配器只需要在其 SDK 有真正的取消原语时转发 signal;中止竞争和下面的 promise 消耗无论哪种方式都适用。

孤儿命令

当中止在 driver.exec 的 promise 解决之前触发时,该 promise 变成孤儿命令:调用者已经用 AbortError 释放,但提供者调用继续运行直到自行解决。转发 signal 的支持取消的适配器仍然会产生这种情况 — 窗口只是从命令的剩余持续时间缩小到 SDK 的取消延迟,因为包装器的拒绝总是领先于该确认。

孤儿的最终解决(履行或拒绝)从不附加到对话中;中止已经作为命令的终止结果,稍后到达的第二个结果会违反 reducer 不变量。它在这里被消耗,因此不能作为未处理的拒绝出现,并且只通过 options.onOrphanSettled 报告:

interface OrphanedExecSettlement {
  command: string;
  startedAt: Date;
  abortedAt: Date;
  settledAt: Date;
  result?: ShellResult;
  error?: unknown;
}

error carries whatever the orphaned call eventually rejected with — a late SandboxDiedError included. Without onOrphanSettled, the settlement is simply discarded; adapters that want to log, bill, or reap an orphaned remote process out-of-band use the callback to do so.

SandboxDriver

interface SandboxDriver {
  readFile(path: string): Promise<string>;
  readFileBuffer(path: string): Promise<Uint8Array>;
  writeFile(path: string, content: string | Uint8Array): Promise<void>;
  stat(path: string): Promise<FileStat>;
  readdir(path: string): Promise<string[]>;
  exists(path: string): Promise<boolean>;
  mkdir(path: string, options?: { recursive?: boolean }): Promise<void>;
  rm(path: string, options?: { recursive?: boolean; force?: boolean }): Promise<void>;
  exec(
    command: string,
    options?: {
      cwd?: string;
      env?: Record<string, string>;
      timeoutMs?: number;
      signal?: AbortSignal;
    },
  ): Promise<ShellResult>;
}

与相应的 Sandbox 成员相同,只是路径预先解析(绝对),并且 writeFile 父保证由包装器处理。

文件动词实现说明:

  • writeFile — 接受 string 和 Uint8Array;对于只接受缓冲区的提供者,将字符串转换为 UTF-8 字节。让缺失父错误传播 — 包装器在 mkdir(parent, { recursive: true }) 后重试,因此适配器端的父创建是冗余的。
  • readFileBuffer — 返回 Uint8Array;用 new Uint8Array(buffer) 包装 Node Buffer。
  • exists — 不能抛出异常。大多数提供者 SDK 对缺失路径抛出异常;捕获并返回 false。
  • mkdir — 只支持单级创建的提供者 SDK 可以用 exec('mkdir -p …') 实现 recursive。
  • rm — 精确实现 recursive 和 force,或者在任何变异之前抛出 SandboxOperationUnsupportedError。直接文件系统适配器不能仅仅为了模拟不支持的删除标志而 shell 出去;已经通过 shell 运行其他动词的适配器也在那里用 rm 实现这些标志 — shell 语义与 Node 的 fs.rm 完全匹配(-f 在缺失路径上解析,-r 不带 -f 在缺失路径上失败,-f 在目录上仍然错误)。

exec 实现契约:

  • 通过将 timeoutMs 转发到提供者 SDK 的原生超时选项来遵守它,当提供者粒度较粗时转换单位并向上取整 — 从不向下取整(整秒提供者转发 Math.ceil(timeoutMs / 1000))。无论 signal 如何,它都是提供者主要截止时间:它是保护信号盲调用者的东西,也是从不中止的调用者。
  • 自己强制执行截止时间的适配器将过期命令解析为 exitCode: 124 的 ShellResult,并在 stderr 上放置超时详情 — 已交付适配器遵循的 timeout(1) 约定。拒绝保留用于 signal 中止。
  • 当 SDK 有真正的取消原语(AbortSignal 选项、进程杀死、取消令牌)时转发 signal — 这样做将孤儿窗口从命令的剩余持续时间缩小到 SDK 的取消延迟。确认取消实际生效;被接受但未遵守的 signal 比不转发更糟糕,因为它宣传了命令从未收到的杀死。不要在调用周围实现第二个中止竞争(本地的 Promise.race,或适配器自己的前后 signal.aborted 检查)— sandboxFromDriver 已经将 signal 与 driver.exec 的 promise 竞争并拥有面向调用者的拒绝;第二个竞争只会复制它,同时让孤儿簿记无法分辨哪个触发了。
  • 当提供者不单独暴露 stderr 时,为其返回 ''。仅对明确成功的调用报告 exitCode: 0。

存活性契约(所有 SandboxDriver 方法):

  • 适配器应确保在沙盒死亡时在途操作得到解决,通过其提供者 SDK 支持的任何机制 — 在途调用的原生拒绝,或在调用挂起时轮询廉价的控制平面状态读取。第一方 Cloudflare 适配器内部实现了轮询形状。
  • 没有这种机制的适配器承担一个接受的限制:当提供者传输在沙盒死亡后从不解决调用时,该调用可能挂起直到周围操作被中止。
  • 此契约中故意没有每命令截止时间。智能体命令是合法无界的;timeoutMs 是命令自己的截止时间,不是基础设施存活性边界。
  • 检测沙盒死亡的适配器应该用 SandboxDiedError(type: 'sandbox_died',从 @flue/runtime 导出)拒绝,以便 shell 分类报告基础设施失败而不是调用者取消。
  • 存活性检测和调用者中止是在不同层实现的独立关注点,死亡检测器不得模糊它们:它将沙盒的存活性信号与在途提供者调用竞争,没有其他。sandboxFromDriver 已经在上一层竞争 signal,并在调用者释放后消耗提供者 promise 的最终解决,因此同时监听 signal 的死亡检测器会产生两个拒绝竞争同一个 promise。第一方 Cloudflare 适配器的死亡检测器遵循此拆分 — 仅存活性,调用者中止竞争完全留给它构建的包装器。

bash(factory)

function bash(factory: BashFactory): SandboxFactory;

type BashFactory = () => BashLike | Promise<BashLike>;

将 just-bash Bash 实例包装成 SandboxFactory — 内存中的虚拟沙盒(种子文件、网络允许列表、自定义命令)。

  • 工厂函数在运行时初始化智能体时调用一次。
  • 返回值进行鸭子类型检查(exec、getCwd 和 fs 对象)。错误的值抛出 Error('[flue] BashFactory must return a Bash-like object.')。
  • 沙盒的 cwd 是实例的 getCwd()(just-bash 在没有 cwd 或 files 构造时默认为 /home/user)。
  • just-bash 没有原生超时选项,因此包装器将 exec 的 timeoutMs 转换为 AbortSignal 并与调用者的信号合并。调用前后的中止检查与 sandboxFromDriver 中一样适用。
  • writeFile 父创建保证应用于实例的 fs。

BashLike is a structural type (no just-bash import in @flue/runtime), exported for adapter authors who construct compatible runtimes:

interface BashLike {
  exec(
    command: string,
    options?: { cwd?: string; env?: Record<string, string>; signal?: AbortSignal },
  ): Promise<ShellResult>;
  getCwd(): string;
  fs: {
    readFile(path: string, options?: any): Promise<string>;
    readFileBuffer(path: string): Promise<Uint8Array>;
    writeFile(path: string, content: string | Uint8Array, options?: any): Promise<void>;
    stat(path: string): Promise<any>;
    readdir(path: string): Promise<string[]>;
    exists(path: string): Promise<boolean>;
    mkdir(path: string, options?: { recursive?: boolean }): Promise<void>;
    rm(path: string, options?: { recursive?: boolean; force?: boolean }): Promise<void>;
    resolvePath(base: string, path: string): string;
  };
}

SandboxToolFactory

type SandboxToolFactory = (
  sandbox: Sandbox,
  options: SandboxToolFactoryOptions,
) => AgentTool<any>[];

interface SandboxToolFactoryOptions {
  subagents: Record<string, SubagentDefinition>;
}

SandboxFactory 上的可选 tools 函数。存在时,其返回值替换此沙盒上智能体的框架默认六工具集(read、write、edit、bash、grep、glob)。从标准工具工厂加上沙盒自己的原生工具组合替换,而不是从头重建 — 例如,无 exec 的沙盒列出三个文件工具和它自己的执行器工具。

  • 必须是同步的,并且每次调用返回新数组。它在运行时每次组装模型工具列表时调用 — 在初始化时和每个轮次边界再次调用 — 而不是一次。
  • sandbox — 实时沙盒,带有层叠到 readFile 上的打包技能覆盖层。这不是 harness.sandbox 暴露的相同对象;在闭包中持有沙盒的工具透明地读取打包技能路径。
  • options.subagents — 智能体当前的子智能体名册,按键名索引。为描述或约束委派的适配器提供。

替换仅覆盖框架的内置组。不受其影响:

  • 框架组 — task(始终存在)、activate_skill(当任何技能挂载时)和 read_skill_resource(当挂载的打包技能携带支持文件时)— 单独附加。
  • 来自 useTool(...) / defineTool(...) 的自定义工具和每次调用的结果工具单独添加。

工具名称在所有组中必须唯一,名称 task、activate_skill、read_skill_resource、finish 和 give_up 是框架保留的。当工具列表组装时,冲突会抛出 ToolNameConflictError。

The element type is AgentTool from @earendil-works/pi-agent-core (a dependency of @flue/runtime; the type is not re-exported). Structurally:

interface AgentTool<TParameters extends TSchema = TSchema, TDetails = any> {
  name: string;
  label: string;
  description: string;
  parameters: TParameters; // TypeBox 模式
  execute(
    toolCallId: string,
    params: Static<TParameters>,
    signal?: AbortSignal,
    onUpdate?: (partial: AgentToolResult<TDetails>) => void,
  ): Promise<AgentToolResult<TDetails>>; // { content, details, terminate? }
}

execute throws on failure rather than encoding errors in content. The standard factories return values of this type; typing a factory as SandboxToolFactory checks a hand-written tool against it.

标准工具工厂

function createReadTool(sandbox: Sandbox): AgentTool;
function createWriteTool(sandbox: Sandbox): AgentTool;
function createEditTool(sandbox: Sandbox): AgentTool;
function createBashTool(sandbox: Sandbox): AgentTool;
function createGrepTool(sandbox: Sandbox): AgentTool;
function createGlobTool(sandbox: Sandbox): AgentTool;

每个标准模型面向工具一个工厂,每个都闭包在 Sandbox 上。这些正是沙盒没有 tools 函数时框架安装的工具;按工具导出它们让适配器的 SandboxToolFactory 可以添加、删除或交换成员而无需重建集合。工具的模型面向行为 — 参数、截断限制、错误形状、继续标记 — 在智能体行为中记录。组合它们时重要的事项:

  • createReadTool、createWriteTool 和 createEditTool 只需要文件动词;createBashTool、createGrepTool 和 createGlobTool 需要工作的 exec — 对于无 exec 的沙盒将它们排除。
  • read 通过 readFile 获取并在运行时切片;edit 是完整的读取 → 替换 → 写入事务。来自 write/edit 的同文件变异在一个并行工具批次内通过键控在 resolvePath 上的每路径锁序列化,因此同一路径的两种拼写必须解析为相同的字符串。bash 命令并发变异同一文件未同步。
  • createBashTool 将模型的 timeout(秒)转换为沙盒的 timeoutMs,并额外将其组合到中止信号中,作为忽略两个取消字段的沙盒的后备;纯超时作为可恢复的 exitCode: 124 结果出现,而主机中止重新抛出。
  • createGrepTool 每个环境探测一次 rg(rg --version,10 秒截止时间,缓存),然后回退到 grep -rnH;createGlobTool shell 出去到 find -name。

打包技能覆盖层

打包技能的支持文件位于应用程序包中,而不是沙盒中。运行时在 /.flue/packaged-skills/<skill-id>/… 下的虚拟路径提供它们,它通过将覆盖层层叠到它交给工具工厂的环境上来实现 — 从不写入适配器的文件系统。

  • 传递给每个工具工厂(标准和适配器一样)的环境都有 readFile 包装:/.flue/packaged-skills/ 下的路径从内存中的技能目录解析,其他一切都委托给适配器。适配器不需要特殊处理;任何通过其环境读取的工具都透明地解析技能路径。
  • 该根下的未知路径抛出 Error('[flue] Packaged skill file not found: <path>') 而不是到达适配器。
  • 二进制技能文件作为 base64 文本提供,包装为 76 字符行。
  • 覆盖层是会话内部的。harness.sandbox 和 useTool 处理程序看到适配器的真实环境;虚拟根在那里不可见。
  • 只有 readFile 被覆盖。exec、exists、stat 和其他动词直接通过,因此 shell 命令看不到虚拟根 — 标准 read 工具(或框架的 read_skill_resource 工具)是访问路径。

内置工厂

local(options?)

import { local } from '@flue/runtime/node';

function local(options?: LocalSandboxOptions): SandboxFactory;

interface LocalSandboxOptions {
  cwd?: string;
  env?: Record<string, string | undefined>;
}

仅限 Node 目标。将智能体直接绑定到主机:文件动词调用 node:fs/promises,exec 生成真实进程。没有隔离 — 参见 Node 目标指南 了解何时适用。

  • cwd — 工作目录。默认为 process.cwd();解析为绝对主机路径。
  • env — 层叠在默认允许列表之上的变量。将键设置为 undefined 以删除默认值。非记录值(数组、true)在构造时抛出 TypeError。每次调用的 exec env 层叠在结果之上。

环境允许列表:模型的 shell 不继承 process.env。默认只有 PATH、HOME、USER、LOGNAME、HOSTNAME、SHELL、LANG、LC_ALL、LC_CTYPE、TZ、TERM、TMPDIR、TMP 和 TEMP 通过;其他一切都通过 options.env 按变量选择加入。快照在构造时获取一次 — 后续对 process.env 的变异不会被拾取。env: { ...process.env } 继承所有内容,包括主机密钥。

exec 行为:

  • 当存在真实 bash 时命令通过它运行(每个进程探测一次,解析为绝对路径),不存在时回退到平台默认 shell(/bin/sh 或 Windows 上的系统 shell)。
  • 在 POSIX 上子进程领导自己的进程组;中止和超时向整个组发送信号 — SIGTERM,2 秒宽限期后升级为 SIGKILL — 因此复合命令不能使孙进程成为孤儿。杀死是真实的,因此 local() 在该宽限期之后没有孤儿命令窗口。
  • 非零退出和生成失败解析为 ShellResult(生成失败为 exitCode: 1,错误消息在 stderr 上)。timeoutMs 过期也解析为 ShellResult,exitCode: 124 — timeout(1) 约定。调用者发起的 signal 中止用 AbortError 拒绝,即使杀死采用相同的进程组路径并在内部产生相同的 124 退出 — 拒绝获胜,调用者从不观察 ShellResult。local() 自己未发起的信号死亡保持通用的 exitCode: 1。
  • 捕获的输出上限为 64 MiB;超出会杀死进程树并解析为 exitCode: 1,并在 stderr 上放置截断说明。
  • timeoutMs 被组合到调用者的 signal 中(没有单独的原生超时),因此纯截止时间过期和调用者中止都通过相同的杀死路径传递,只在上面的点分歧。

stat 为符号链接目标报告 isFile/isDirectory/size/mtime,为路径本身报告 isSymbolicLink。所有 FileStat 字段都被填充。

cloudflareSandbox(sandbox, options?)

import { cloudflareSandbox } from '@flue/runtime/cloudflare';

function cloudflareSandbox(
  sandbox: CloudflareSandboxStub,
  options?: CloudflareSandboxOptions,
): SandboxFactory;

interface CloudflareSandboxOptions {
  cwd?: string;
}

Cloudflare 目标。将 @cloudflare/sandbox Durable Object 存根(getSandbox() 返回的值)包装成 SandboxFactory。CloudflareSandboxStub 是结构化的,因此 @flue/runtime 不依赖于 @cloudflare/sandbox。

  • cwd — 容器内的工作目录。默认为 /workspace。

See Cloudflare Sandbox in the target guide and the ecosystem entry.

SandboxOperationUnsupportedError

class SandboxOperationUnsupportedError extends FlueError {
  constructor(input: { operation: string; provider: string; options: readonly string[] });
}

当调用者请求提供者无法满足选项的操作时适配器抛出的错误(type: 'sandbox_operation_unsupported')。在修改文件系统之前抛出它,因此拒绝保证没有任何更改。operation 命名动词,provider 命名沙盒产品,options 命名无法满足的具体选项名称;三者都保存在错误的 meta 上。参见 错误 — SandboxOperationUnsupportedError。