Repository navigation
[upstream pr #498] feat(sandbox): 统一 Sandbox provider 接口,收编 Docker / Linux 原生 / macOS 原生(B1/B2/B3,叠在 #427 上) #52
Description
[upstream PR Ephemeral-AI-Lab#498] feat(sandbox): 统一 Sandbox provider 接口,收编 Docker / Linux 原生 / macOS 原生(B1/B2/B3,叠在 Ephemeral-AI-Lab#427 上)
- Upstream: feat(sandbox): 用具体的 Runtime enum 统一 Docker / Linux 原生 / macOS 原生,并新增 Linux namespaces 后端(B1/B2/B3,按 #515,叠在 #427 上) Ephemeral-AI-Lab/layerfs#498
- Type: pull request
- State: OPEN
- Draft: False
- Mergeable: MERGEABLE
- Updated: 2026-10-29
- Upstream labels: -
Original body (excerpt)
概要
对应 Ephemeral-AI-Lab#446 B 区的 B1(统一 Sandbox provider 接口)、B2(Linux 原生) 和 B3(macOS 原生) 的接口部分。这里把 Docker、Linux 原生(root daemon + FUSE)、macOS 原生(loopback NFSv3)三种后端收进同一套 SandboxProvider / SandboxInstance 生命周期接口,layerfs-api 和 layerfs-node 只走 provider 这一条路径。
本 PR 叠在 Ephemeral-AI-Lab#427 上面:分支从 Ephemeral-AI-Lab#427 的头 d55acc15f 开始,在它后面加了 11 个提交(其中 1 个是 merge)。评审时只看 d55acc15f.. 之后的提交就行。Ephemeral-AI-Lab#427 合并后,这部分 diff 会只剩下本 PR 自己的改动。main 目前是 582bc786f,不需要再 merge。
提交
| 提交 | 内容 |
|---|---|
| 559bff5 | 定义 provider 生命周期 trait、命令类型、ExecRequest |
| 8ce4697 | macOS 原生改成 NativeMacosProvider / NativeMacosInstance |
| 59c0da2 | 抽出共用的 daemon 启动、握手、Hello、Store 安装流程 |
| c82a903 | Linux 原生 provider,复用 NativeProvider<const LINUX: bool> |
| 7a6952c | Node 的 Linux native 部署接到 provider 流水线 |
| e5004d6 | DockerProvider / DockerInstance(create/inspect/run/log/stop/remove,只尝试一次,typed refusal) |
| 2e4e3cf | node 测试里的假 Docker Engine 补上 GET /version 应答(上游 73d974d 加版本协商之后,夹具的回复错位,测试报 ConnectionRefused) |
| abed25d | merge 上面的夹具修复 |
| 7922044 | ProviderLaunchOptions::Docker;用 SandboxRecord 枚举统一 local/docker 记录(磁盘格式不变);去掉 reopen_docker |
| 70ec59e | SandboxApi 只走 provider |
| 8cffb6b | node 的 Docker 路径走 DockerProvider,删掉旧入口(ManagedSandbox、SandboxCreate、SandboxApi::new(Docker)、internal::split/exec 等) |
保持不变的行为
sandbox.record磁盘格式和 label 不变,不做迁移- typed refusal 和错误码不变;Docker 版本协商不变
- 关闭宽限时间:用户显式
close仍是 5 秒,创建失败清理仍是 1 秒 - exec 流式输出、1 MiB 以上转存 spool、退出码处理不变
- Linux 原生保留 root daemon、FUSE、命令身份 501:20、
/proc检查和进程组清理 - daemon 二进制和 feat: macOS 原生适配——daemon 内置 loopback NFSv3 导出,普通用户 mount_nfs 挂载 Workspace(无 Docker / 无 macFUSE / 无 root) Ephemeral-AI-Lab/layerfs#427 头逐字节相同(只改 api/node/sandbox 库)
验证(本地;上游 CI 结果以本 PR 页面为准)
macOS(M2,macOS 27.2,rustc 1.85.1)
cargo fmt --all -- --check、cargo clippy --locked --all-targets -- -D warnings:通过layerfs-sandbox、受影响的layerfs-api测试目标、layerfs-node:通过(跳过 10 万文件规模的 sdk_merge_scale)node --test api/cli/test/cli.test.mjs:75/75- release(
CARGO_PROFILE_RELEASE_STRIP=none)SDK smoke 48/48,PATH inherit 7/7、fallback 4/4
Linux(Debian 13,root,真实 FUSE)
- 全仓 clippy
-D warnings、layerfs-sandbox全过 layerfs-node:native_discovery 6/6、native_admission 2/2、native_local 7/7- 原生 SDK smoke 48/48(N6b 真实 FUSE 挂载、N8 exec 身份 501、Z1 没有挂载、daemon 或目录残留)
真实 Docker(Docker 29.8.2,runtime/sandbox 镜像 layerfs-352-runtime:node24)
在 runtime 镜像里用 node api/agent/test/verify/<场景>.mjs inner 跑(环境变量和 tools/worktree_run.py 一样,--network host):
| 场景 | 本 PR | 基线 d55acc1 |
|---|---|---|
| sandbox_real(16 个挂载到 Capacity、MountsActive、close) | ok | ok |
| idle_close_real | ok | ok |
| calls_real store / workspace | PASS 28 / PASS 15 | PASS 28 / PASS 15 |
| reap_real | ok | ok |
| sandbox_image_real | ok | ok |
| fs_mode_real | ok | ok |
| exec_mount_lookup_real | ok | ok |
| exec_output_real check(每路 64 MiB,SHA-256 一致) | ok | ok |
两边的残留容器数也一致(idle_close 等场景按设计留下一个已停止的 Sandbox)。
需要作者确认
- 接口形状:trait 的划分、
SandboxRecord枚举、ProviderLaunchOptions按后端分变体,这样设计是否认可 - Linux native 在本 PR 里重新可以对外选择(feat: macOS 原生适配——daemon 内置 loopback NFSv3 导出,普通用户 mount_nfs 挂载 Workspace(无 Docker / 无 macFUSE / 无 root) Ephemeral-AI-Lab/layerfs#427 本身只开放 macOS),这一点是否符合 B2 的预期
- 本 PR 不包含 B8/B9/B10
Sync note
这是只读镜像,不是上游仓库的替代。上游 issue/PR 仍是唯一真源;本镜像不复制评论、不复制代码分支。
Activity