☰
OpenChamber Isolated Spaces 设计深度解析:容器化隔离开发环境的边界、网关与代码进出机制
2026/9/25 4:49:07 网站建设 项目流程
  • AI Agent
  • 人工智能
  • 代码智能体
  • 交互助手

【免费下载链接】openchamber

Agentic Development Environment based on OpenCode AI agent

项目地址:https://gitcode.com/gh_mirrors/op/openchamber
点击查看免费下载

本文基于 docs/isolated-spaces/DESIGN.md 展开,结合 packages/web/server/lib/spaces/DOCUMENTATION.md、STAGES.md、TESTING.md 及 stage-0 四份实验记录,系统讲解 OpenChamber 的 Isolated Spaces(隔离空间)功能:它如何在容器副本中运行 AI Agent、如何用 Gatekeeper 把守唯一出口、如何通过exec通道搬运代码,以及这套设计背后"限制必须是 Agent 无法撤销的边界"这一核心原则。读完你将掌握该特性的整体架构、九个产品决策要点、Place 契约八种操作、网关的走廊/窗口/控制三层机制与代码进出(Code in/out)的完整实现路径。

什么是隔离空间

隔离空间是 OpenChamber 的一项核心安全特性:用户让 Agent 在一个容器内、对项目的一份副本工作。任务完成后,用户把结果应用到自己的代码库,或者丢弃它。它的动机全部在范围内:

  • 保护用户机器,Agent 无法直接接触宿主;
  • 让 Agent 无需权限弹窗即可运行(因为它在不可信的副本里);
  • 让宿主密钥与 Agent 隔离;
  • 让项目工具链不落在宿主上。

与 OpenCode 上游实验性的 Workspaces 路由/同步层不同,隔离空间是一条独立路线:Workspaces 只覆盖 OpenCode 的 API,而文件、git、终端、预览都属于 OpenChamber,必须自己转发(见 LESSONS.md 中 "OpenCode workspaces" 一节,该实验层对进程隔离、出口流量、凭据作用域与 apply/discard 都无能为力)。

边界规则:承诺在这里一文不值

设计文档的第一条铁律是:

每一条限制,都是 Agent 从内部无法撤销的。承诺在这里一文不值。任何越过边界的东西,都是用户逐项决定的。如果某个选择依赖 Agent"自觉表现",那它就是错的。

宿主把来自空间的一切都当作不可信数据来展示,绝不当作命令执行。这一原则贯穿全部 19 条产品决策与整个实现——例如 gatekeeper 程序把空间发来的每一条输入都视为敌意输入来防御(DOCUMENTATION.md 的 "The program" 一节)。

核心词汇

术语含义
Space(空间)一个装有 Agent 和代码副本的容器,可容纳多个会话,类似 worktree。新空间总是新容器,只有镜像与每项目的包缓存是共享的
Place(场所)空间运行的地方:本地 Docker、通过 SSH 访问的另一台机器上的 Docker、Kubernetes 集群、Applecontainer,未来还有沙箱服务。用户可配置多个场所和一个默认场所
Gatekeeper(守门人)每个空间旁边的一个独立小容器,是空间唯一的出路
Space manager(空间管理器)packages/web中的新服务端模块,负责创建、查找、停止、移除空间,搬运代码,交付 grant
Dispatcher(分发器)现有服务器前的一层薄转发层,把属于某空间请求转发到该空间内的服务器
Grant(授权)用户给空间的一个凭据或一个放行的域名,分两档,用大白话向用户展示

Grant 的两档:

  • Uses without seeing(用而不见):gatekeeper 持有密钥并把它加进请求。适用于模型 API key、https 上的 git、私有 npm。
  • Handed over(直接交出):空间内一个 Agent 可读的文件或变量。如.env、SSH 密钥、云 CLI 密钥、短期的 OpenAI 登录 token、Copilot token。

十九条产品决策:从"代码怎么进"到"开关怎么设计"

DESIGN.md 的第 32–52 行给出了完整的 19 条产品决策,是理解整个特性的总纲:

  1. 代码以副本进入。宿主任何东西都不挂载进空间。
  2. 从干净提交或用户未提交的改动出发。.gitignore匹配的文件绝不传输。
  3. 网络是用户按空间选择:白名单或开放互联网。两种模式下 gatekeeper 都拦截用户私网、link-local 段与云元数据地址;只取名字、不取地址,只放行 443 端口——这样空间永远无法从用户地址攻击第三方其他服务。
  4. Grant 可在创建时与工作期间给予,不可收回,随空间消失。
  5. OpenChamber 不存任何密钥值,只记住宿主来源的名字(环境变量、文件、ghtoken),实现"和上次一样"一键复用。
  6. 空间内 Agent默认不需要任何工具权限,现有按会话开关保留;grant 对话框会明确说"Agent 可无确认使用该授权"。
  7. 两种应用方式:作为带 Agent 提交的分支,或作为未提交改动;未提交变体若无法干净应用,则什么都不碰,转而提供分支方案。
  8. Apply 对话框带"之后删除空间"选项,默认开启;Discard 总是删除(需确认)。
  9. Apply 与 discard 之后,聊天保留为只读归档,直到用户删除。
  10. 关闭 OpenChamber 不会停止空间;OpenChamber 可以重启其中卡死的 OpenCode。
  11. 空间在闲置数小时后自行停止,阈值是可设置项且可关闭;停止保留文件。
  12. 用户永远不需要终端管理容器;OpenChamber 只展示并管理自己创建的东西。
  13. 模型登录:发 API key 的提供商(包括发 key 的 coding plan)走 gatekeeper;OpenAI 浏览器登录只交出短期 access token,宿主保持唯一 refresher;Copilot token 长期有效故警告更强;Claude Pro/Max 浏览器登录因 OpenCode 已移除而不提供。
  14. 预览空间内运行的 dev server 是首发功能。
  15. 默认镜像优先,项目自定义镜像(devcontainer.json、Dockerfile)是后置阶段。
  16. 承载面:web、桌面、移动端;VS Code 永不提供此功能,其入口故意缺席且必须在代码中可见。
  17. 用户主动开启该功能——Settings 中一个默认关闭的开关。构建期间功能藏在同一开关后;首次发布时它变成用户的选项而非被移除。功能需要容器运行时、会在用户机器上造容器并下载约1.6 GB镜像,不该挡着不用它的人。
  18. 关闭开关会停止所有空间但保留文件,重新打开时文件仍在原位。开关旁有横幅:仅在确有东西要停时才出现,说明有几个空间、文件保留、会回来;无法停止的空间(运行时没跑或场所不可达)报告为"仍在运行"而不是悄悄算作已停。删除空间绝不并入关开关——它是独立动作,且必须经过列出会丢失内容的确认。
  19. 开关关闭时功能完全不做任何事:无进程、无docker或运行时命令、无连接、无运行时状态读取、无路由、无入口。第一个进程只能是用户打开开关或在该功能自身界面操作的后果。特别是启动时绝不探测运行时来决定是否显示入口——入口因开关开启而存在,探测只存在于漏斗内部。

用户旅程:从设场所到善后

DESIGN.md 的 "User journey" 一节给出了完整的 9 步流程:

  • 0. 设场所(一次性):选择"Agent 在哪里工作"——本机、另一台 SSH 机器、集群。OpenChamber 自己检查选择并用大白话回答;它会说明何时某场所无法限制网络、何时密钥会离开用户机器。设场所会在后台拉取镜像。
  • 1. 创建:新会话目标选择器中,"New worktree" 旁边就是 "New isolated space"。四项选择:场所、起点、网络、预置 grant;每个 grant 显示其档位。
  • 2. 准备:项目下立刻出现带容器徽章的分组和状态行;会话立即打开,消息先排队、空间就绪后自动发出;代码一到 Agent 就开始,依赖可能仍在安装;错误会指名要修的设置。
  • 3. 工作:分组内会话与普通会话无异;关闭 OpenChamber 后 Agent 继续工作;OpenAI 浏览器登录模式约一小时后短 token 过期则等待用户打开 OpenChamber。
  • 4. Agent 缺东西:分组显示 gatekeeper 日志中被拦截的尝试,带 "Open" 动作;"Grant access" 位于分组与会话头部,授权经由宿主。
  • 5. 审查:常规 git 面板和 diff;空间里的 dev server 在内置浏览器打开。
  • 6. 决定:以分支应用、以未提交改动应用、或丢弃。
  • 7. 之后:聊天保持只读归档。
  • 8. 出问题时:分组上有状态。三个从软到硬的动作:重启 OpenCode、重启容器、删除;不可达空间会明说,应用其余部分继续工作;重启后 OpenChamber 自行找回其空间。
  • 9. 总览:项目菜单中(worktree 旁)打开项目空间;Settings 的 places 页面展示每个场所上运行了什么、孤儿空间、镜像和缓存占用磁盘及清理动作;不可达场所显示其空间为不可达,绝不显示为空列表。

总体架构

组成部分

DESIGN.md 给出如下架构图:

HOST PLACE ┌────────────────────────┐ ┌─────────────────────────────────────┐ │ OpenChamber server │ manages │ ┌────────────┐ ┌───────────┐ │ │ ┌──────────────────┐ │────────►│ │ gatekeeper │◄─────│ space │ │ │ │ space manager │ │ │ │ keys, │ only │ agent, │ │ │ ├──────────────────┤ │ forwards│ │ allowlist │ exit │ code copy │ │ │ │ dispatcher │ │◄───────►│ └─────┬──────┘ └───────────┘ │ │ └──────────────────┘ │ │ ▼ internet │ └────────────────────────┘ └─────────────────────────────────────┘

空间运行与宿主相同的组合:一个 OpenChamber 服务器加一个 OpenCode。宿主只与空间内的 OpenChamber 服务器对话。只有三个狭窄的触点直接接触 OpenCode,且都集中在一个小模块里(这样 OpenCode 格式变化只需改一个文件):指向 gatekeeper 的 provider 配置、短期 OpenAI token 的登录记录、把聊天搬到宿主做归档。

空间以非 root 用户运行,具备:只读根文件系统、全部 capability 丢弃、无特权提升、引擎 seccomp profile、进程数限制、无额外 swap 的内存限制、封顶日志、无容器运行时 socket、无 bind mount。顺序是create、verify、start:场所先重读真实容器状态,任何不符就拒绝启动。开关与检查的归属清单见 packages/web/server/lib/spaces/DOCUMENTATION.md。

宿主资源也是边界的一部分:没有内存限制和日志上限,Agent 就能耗尽宿主内存或通过自己的输出填满磁盘。每个场所在其设置里有默认空间尺寸,不逐个询问用户;命名 Docker 卷默认驱动无尺寸上限——这是已记录的已知限制。

内部 Docker 网络本身仍会让空间通过网桥网关触及监听 Docker 宿主所有接口的服务(在原生 Linux 上那个宿主就是用户机器)。因此 Docker 场所用gateway_mode_ipv4=isolated创建网络并关闭 IPv6,把宿主的地址从网桥移除;引擎忽略该选项则验证失败。这要求Docker Engine 28 或更新。一个常驻的逃逸测试会在宿主上启动监听器并证明空间无法连接,每个后来的场所都必须通过同一测试。

Place 契约:八个操作

场所契约只有八个操作(DESIGN.md 第 95–104 行),其他一切都在它们之上编写一次:

操作含义
check此场所是否可用,真正能做什么
create内网、gatekeeper、空间,全部打标签
list按标签找到我们的空间
exec在空间或 gatekeeper 内运行命令
exec argv为自行启动进程的调用方(如 git 经ext::推代码)提供在空间内跑命令的 argv,发出前与exec一样被检查
connect给宿主一条通到空间内服务器的通道
stop、start、remove生命周期操作
verify对照请求的加固要求重新检查已创建容器

exec是控制通道:代码传输、grant 投递、token 刷新、修复全都走它。空间自身的网络看不见它。能力是探测出来的,不是声明的——场所通过一次真实的"出网尝试"来证明它确实限制了网络。

实现上,各场所从服务器调用系统 CLI:docker、带 SSH 目标的docker、每次都显式传 context 与 namespace 的kubectl、以及container。SSH 变体用系统ssh及用户自己的密钥与配置,要求密钥认证,且不碰 Electron SSH 管理器。实现细节见 places/docker.js、places/registry.js(遵循packages/web/server/lib/tunnels/的隧道提供者注册表模式)。

运行时的标签是空间唯一的事实来源,宿主不为它们维护状态文件;宿主只存场所设置、密钥来源名与归档聊天。用户仓库refs/openchamber/下的服务 ref 被允许——因为起始快照必须躲过 git 的垃圾回收,才能继续作为结果补丁的基线。

版本与工具卷:空间如何零下载启动

  • 基础镜像是公开镜像、按 digest 固定、独立于 OpenChamber 发布。需要 glibc 上的 Node(两个终端库都发布预编译 glibc 二进制);stage 0 用node:22-bookworm。它必须是多架构索引 digest(docker manifest inspect验证,见 DOCUMENTATION.md "Base image")。
  • 场所用一个可信一次性容器填充 tools 卷(该容器能访问 npm registry),然后只读挂载进空间。卷内容 = 宿主 OpenChamber 服务器版本 + 匹配的 OpenCode + OpenCode 的插件包,所以空间启动零下载。服务器拒绝在没有 OpenCode 相伴时启动。已填充的卷永不改变;新内容 = 新卷,运行中的空间保留它启动时的程序。
  • 实测(stage 1b):一次填充约36 秒、磁盘 438 MB;从 create 到健康服务器2.3 秒;每个空闲空间约370 MiB 内存,大部分是 OpenCode。
  • OpenCode 需要项目工具通过.opencode/tool导入插件包,其工具列表会等待插件后台安装(无网络时那次等待实测 131 秒)。把插件链接到项目目录上方可消除该等待——create会在/spaces/<id>/node_modules/@opencode/plugin建一个指向 tools 卷中插件的符号链接。
  • 空间必须带 init 进程并以openchamber serve --foreground运行,否则容器无法干净停止。
  • 无新发布产物:宿主更新后,停止的空间在下次 start 时迁到新 tools——场所用新卷重做容器,空间文件留在自己的卷里。运行中的空间绝不被触碰。
  • 开发构建不能复用 registry 安装(同名本地包与已发布的不同),改为安装本地web与sdk的打包 tarball,并用 npm override 让本地 sdk 全局胜出。判定宿主是否为开发构建属于尚未构建的接线。

代码进出(Code in / Code out):git 走控制通道

git 经控制通道与 git 对话:无网络、无端口、无归档、无共享文件夹(DESIGN.md 第 130–142 行)。宿主始终是驱动方;精确命令序列、时序与恶意容器结果在 stage-0/e4-git-over-exec.md。

  • 空间内:空间路径下一个普通非裸仓库。宿主只推refs/openchamber/*(base、start-index、start),永不推refs/heads/*。
  • 进:先发当前快照、后台再发历史。push 无法加深浅接收端,所以历史先进空间内的一个侧边裸仓库,再本地 unshallow。未提交改动以两个快照提交传输(从索引副本构造、用用户正常 git 配置),工作树、索引与全局 ignore 文件都被尊重且不被改动;在空间内展开为已暂存/未暂存改动。作者名与邮箱会传输,签名密钥留在宿主。
  • 未忽略的未跟踪文件会传输;创建对话框在确认前列出随未提交改动传输的文件。
  • 出:快照空间内一切。先抓入一个一次性 quarantine 仓库——宿主自己强制大小上限与超时(git 两者都没有)。再带对象检查提升结果到refs/openchamber/spaces/<id>/result:无标签、无子模块递归、空 refmap。被杀死的 fetch 在临时仓库留下部分 pack,空间内遗留进程需要自己的清理。
  • 宿主从抓取的两个树构建补丁,空间从不提供补丁文本。应用 = 普通 dry run 后跟普通 apply,两者都支持二进制。三路模式在带未暂存编辑的工作树上会失败。
  • 被应用的就是宿主抓取到的,不管空间屏幕上显示了什么。
  • 首次发布限制(创建时各有警告):子模块保持为空,Git LFS 文件以指针到达。
  • 现在实现于 code-in.js(六个函数:takeSnapshot、listTravellingFiles、readTransferLimits、bringCodeIn、sendHistory、removeSpaceRefs),宿主 git 环境过滤规则集中在 host-git.js(剥离GIT_DIR、GIT_INDEX_FILE、GIT_NAMESPACE等重定向变量与GIT_TRACE家族,注入GIT_OPTIONAL_LOCKS=0与空core.hooksPath,保证用户的钩子/fsmonitor/自动 gc 一概不运行,同时保留用户的全局 ignore 与 Docker 环境)。
  • 恶意容器结果(已实测):越界 refspec、标签、hasDotgit树、..路径、.gitmodulesext url、60 MB 大 blob、假 upload-pack 挂起——全部有对应防御(显式 refspec +--refmap=、--no-tags、fetch.fsckObjects=true、quarantine + 大小上限、进程组超时)。关键结论:git apply是全有或全无,冲突时 dry run 与真实 apply 都失败且宿主状态逐字节不变。
  • 实测(93 MB、3687 提交的仓库):完整历史+快照 push 3.76 秒、纯快照 0.98 秒(30 MB);"先快照后历史"在 Docker over SSH 与 Kubernetes 上尤其划算。E2E 实测(stage 3a):代码进 1.1 秒、历史 0.6 秒;本仓库克隆(3711 提交、约 5200 文件)2.0 秒进、103 MiB 历史 9.9 秒。

Gatekeeper:空间唯一的出路

空间位于一条内网,其唯一其他成员就是 gatekeeper。Docker 上 gatekeeper 不跑 DNS 服务器(stage 2 在 Engine 29.2.1 实测:内嵌 DNS 能解析 gatekeeper 的网络别名,公共名仍不解析);需要别名解析的场所(如 Applecontainer的 host-only 网络)则由 gatekeeper 为空间解析。

三个监听器(DOCUMENTATION.md "The gatekeeper" 的拓扑图):

inner network (internal, no gateway) outer network (ordinary bridge) space ──────────► gatekeeper ──────────► the internet corridor 3128 window 8080 control 127.0.0.1:9099

Corridor(走廊):CONNECT 隧道

  • 通向允许域名的 CONNECT 隧道,gatekeeper 只看到目的地。
  • 两种模式,宿主实时切换、无需重启:allowlist(名字精确匹配,无通配、无后缀匹配;列表上的名字用与目标相同的谓词检查,宿主不可能放入走廊恒拒的名字)与open(任何名字过名字检查)。
  • 两种模式都只放行 443:能到任意端口的空间就能从用户地址攻击第三方(端口扫描、SSH 爆破、邮件滥用);数据总能经 443 离开,但攻击一个不在 443 监听的服务的路径被真正关闭。需要其他端口时,正确途径是 grant 对话框里显式address:port。
  • auth.openai.com两种模式下都拒绝(白名单说了也不算),窗口也拒绝它作为 upstream。诚实的表述是三层:保证的是用户的长期 refresh token 永不进入空间;白名单模式下该域名不可达;开放模式下只是更难而非防住——空间可达任意 443 公共主机,可经第三方中介,走廊只见中介名字。实测(stage 2):仅拒名字不是锁——空间能按地址到达同一主机,所以走廊取名字,两种模式都拒绝地址目标。
  • 两种模式下拒绝都按解析后的地址:走廊自己解析名字,任一答案是 loopback、私网、link-local、CGN、组播、保留或云元数据地址即拒(含::ffff:0:0/96、NAT6464:ff9b::/96、6to42002::/16)。"名字"指末标签既非数字也非十六进制的域名——这关闭了1746020849、0x01010101、1.1.257、0x1.0x1.0x1.0x1、0177.0.0.1等拼写(实测有人用其中一个拼写穿过纯地址检查)。
  • 连接去往被检查的那个地址:名字绝不解析第二次,这就是防 DNS rebinding 的一行代码。
  • 拒绝原因只进日志、绝不告诉空间(区分deny:blocked-address与deny:unresolved会让 Agent 遍历域名映射出用户内网 DNS)。
  • 连接数也封顶:走廊 128、窗口 64、控制通道 8(都是程序参数);拒绝客户端会被放行(drain 而非立即 close,避免 RST 丢掉刚送达的 403 文本);隧道 300 秒无字节即关闭;隧道建立后按 TCP 语义逐边关闭(destroySoon),保证大下载字节级完整——旧实现实测 4 MB 传输丢 1.1–1.6 MB,新实现四次全量 sha256 相等。

Window(窗口):反向代理注入密钥

  • 服务需要密钥时的反向代理模式:空间内工具与 gatekeeper 对话,gatekeeper 加密钥并经 TLS 转发。OpenCode 的 provider base URL、git 的 URL 重写、npm 的 registry 设置都指向它。空间内不安证书、不拦截 TLS。
  • /model/<grant id>/<rest>去往该 grant 的 upstream,按 provider 设头部:OpenAI 风格用Authorization: Bearer <key>,Anthropic 用x-api-key: <key>(空间发的authorization/x-api-key/proxy-authorization先被移除)。OpenCode 侧用options.baseURL+ dummyoptions.apiKey且无存储 auth 条目,内建 auth 插件即让路(stage 0 用假 key 与假模型服务器验证,完整可运行的opencode.json在 stage-0/e3-model-window-and-short-token.md)。
  • 路径穿越防护:..逐轮解码(直到一轮解码回自身),每轮按/、\、;、?、#切分后比较;固定轮数是有洞的(四重编码实测穿过三轮)。拒绝一切带%的 rest 又太宽(scoped npm 包@scope%2fname正是该窗口的一半用途)。
  • 窗口对 grant upstream 故意不套私网规则——用户自己的私有 registry 正是设计想要的;唯一拒绝是 gatekeeper 自身地址。由此宿主必须守住一条规则:永远不要用空间说过的东西构造 grant。
  • 实测:upstream 300 秒无字节即 504 +failed:timeout记录,slot 归还,64 个挂起请求全部恢复。

控制通道与日志

  • 控制通道是 gatekeeper 自身 loopback 上的 HTTP 监听(空间不可达),只接受宿主的四种请求:GET /health、POST /network({mode, domains}实时替换)、POST /grants({id, upstream, header, secret})、GET /journal。
  • 实现为单一自包含 CommonJS 脚本 gatekeeper-program.cjs,经exec的 stdin 写入 tmpfs,密钥只在程序内存里,永不在容器设置、标签、env 或磁盘上。机器重启后空间显示 "needs access",用户一键重新授权。
  • 日志(journal):内存 500 条环形缓冲,每条 = 时间、监听器、目标主机与端口、决策;永不记录路径、query、body、头部值或密钥。UI 用它看拦截尝试与"Agent 去过哪里"。决策词包括allow、failed:<code>、deny:not-a-name、deny:not-on-allowlist、deny:port、deny:blocked-address、deny:too-many-tunnels、deny:malformed等(DOCUMENTATION.md "Journal" 完整列出)。

空间内环境变量(stage 2 实测定稿)

变量作用
HTTPS_PROXY、https_proxy、HTTP_PROXY、http_proxy(四个都http://gatekeeper:3128)走廊。curl 7.88.1 故意忽略大写HTTP_PROXY(httpoxy 防护),小写拼写不是可选项
NO_PROXY、no_proxy(gatekeeper,localhost,127.0.0.1)窗口流量与空间自身 loopback 跳过走廊。curl 与 Node 都会把 loopback 送去代理,不在 NO_PROXY 里则空间内宿主健康检查会进走廊
NODE_USE_ENV_PROXY=1Node 22 没有它完全忽略代理变量(实测EAI_AGAIN)
OPENCODE_DISABLE_MODELS_FETCH=1、OPENCODE_DISABLE_AUTOUPDATE=1关掉模型目录与更新检查的走廊尝试
OPENCHAMBER_RELAY_HOST=off空间永不被动托管 relay

忽略代理的工具(如 ssh)因无路由而失败——这正是边界规则想要的。npm_config_fetch_retries=0已移除:走廊对未允许请求立即 403,而 npm 不重试 403(实测npm view无路由 70 秒,走廊立即 403 时 0.3 秒),Agent 自己的npm install拿回重试。空间里绝不设OPENCODE_AUTH_CONTENT——它优先于文件直到重启。

短期 OpenAI token

  • 记录是oauth条目:真实短期access、dummyrefresh、真实expires。管理器经exec用PUT /auth/openai推新 token,下一次请求生效;过期时用户看到干净的一次性 "Token refresh failed",无重试循环。
  • 保护用户登录的是:长期 refresh token 永不进入空间(两种网络模式下都成立)。实测 stage 2:拒绝名字本身不是锁(可按地址达同一主机),所以走廊取名字并在两种模式拒绝地址目标。
  • 若空间首次 OpenAI 记录到达时项目实例已加载,需POST /instance/dispose后再推。opencode run --attach在 provider 错误时退出码为 0——从 session 事件读失败,绝不读退出码。带 OpenAI 浏览器登录的空间必须让provider.openai.options.baseURL保持未设(两者是否冲突未验证)。

Dispatcher、会话与事件

  • 空间代码位于每空间唯一、两侧相同的路径,如/spaces/<id>/<repo>;UI 把它当又一个项目目录。
  • 客户端寻址,dispatcher 从不猜测:请求只在路径以/api/spaces/<id>/开头且 id 在管理器标签派生列表内时才进空间。dispatcher 认证用户、剥离前缀与用户凭据(cookies、bearer、URL token)、加上空间自己的 token、其余原样流式转发。它不读任何目录、body、session id 或 terminal id(stage 0 拒绝了服务端识别方案——终端 socket 多路复用所有终端、dev tunnel 只具名端口、约十条路由族只在 body 或 session id 里携带目录,详见 stage-0/e2-dispatcher-recognition.md 的完整清点)。
  • UI 侧:一个纯函数在调用时把目录变成前缀,应用于 SDK fetch wrapper、runtimeFetch和 socket/asset URL 助手;什么都不缓存。两个服务端守卫只拒绝:前缀请求的目录越出该空间根返回 400;无前缀但目录落在/spaces/下的请求在路径门和 last-directory 回退之前得到稳定 4xx。
  • 会话键控动作从 session 记录取目录,无服务器确认目录就失败;空间存在后 current-directory 回退消失。
  • 仅宿主、永不加前缀:provider 与 auth 页面、设置、GitHub、Linear、voice、guests、项目路由。跨边界拒绝:移动会话、worktree 与 git-integrate 动作、/api/fs/serve、/api/preview/proxy。来自空间的/api/fs/raw仅以nosniff和安全内容类型转发。
  • 新 socket 与 asset 路径必须加入 relay、URL-token 认证与 Electron realtime proxy 的精确匹配白名单,否则移动端静默失败。
  • 管理器每次宿主启动都经exec签发新空间 token(stage 1b 起 create 时写入空间 HOME 下文件,服务器启动时读取;Agent 能读该 token,所以它防别人、永不防 Agent)。空间响应不能设置 cookie,空间文件绝不渲染为应用 origin 下的页面。
  • 会话列表 = 宿主列表 + 每个可达空间列表(每空间一个完整性标记);不可达空间保留上次已知会话并标记 stale;缺失答案绝不等于删除。合并把空间列表与事件视为不可信:丢掉目录越出该空间根的记录,绝不覆盖宿主 session id。
  • 实测(stage 1b,OpenCode 1.18.31):为/spaces/<id>/<repo>创建的会话精确报告该目录;经符号链接到达的目录以真实路径返回。所以宿主只给 OpenCode 真实路径。
  • 每个空间有自己事件连接与状态(v2 事件翻译层之后附加、指数退避),间隙后客户端重读该一个空间。活动点、未读标记、通知对空间会话生效,因为它们喂同一 watcher。闲置计时器在空间内部,所以 OpenChamber 关闭时它仍工作。

Skills 加载与质量门槛

设计文档要求按变更区域加载对应技能:openchamber-change-discipline恒加载;信任边界类变更(加固、网络与 gatekeeper 策略、exec 与生命周期、grant 与凭据、代码进出与 apply、dispatcher 隔离、预览内容、防护测试)加载isolated-space-boundary;dispatcher/会话/事件加载ui-api-decoupling、relay-transport、sync-state-invariants;每个子进程加载desktop-shell;UI 加载theme-system、locale-ui-patterns、settings-ui-patterns;所有面向用户文本走每语言的 locale 系统。

TESTING.md 定义了五层测试:无运行时逻辑单测、对每个场所实现的固定契约套件、真实 Docker 全路径、从空间内部以攻击者身份运行的逃逸测试(目标是"Agent 不能",绝不是"它能"),以及各平台手工清单。逃逸测试覆盖:绕过 gatekeeper 直连互联网(按 IP 与名字)、不经 gatekeeper 解析 DNS、访问白名单外域名、访问宿主服务/本网/link-local/云元数据地址(含 DNS rebinding 与 IPv6-mapped 形式)、在 env/文件/进程列表/容器元数据中找到模型密钥、找到被忽略.env中的 bait 密钥、触及运行时 socket 或提权、写只读文件系统越界路径、改/换/重挂 tools 卷、在容器元数据中找到空间内服务器 token、从空间网络达空间内服务器、达 gatekeeper 控制通道或另一空间的 gatekeeper、给自己签发 grant、让宿主经 apply 运行东西(git hooks、标签、quarantine 外 refs)、让空间文件在应用 origin 下渲染为页面或设 cookie、在任何转发请求里看到用户 OpenChamber 凭据、在会话列表/事件中声称越界目录或宿主 session id、无限挂起结果 fetch 或填满宿主磁盘、让.git/hooks/x、.GIT、..、a/../../x等树路径穿过对象检查或在 apply 时经符号链接写入、在空间任何位置找到用户长期凭据(两种网络模式下都成立)。

运行逃逸测试的命令(DOCUMENTATION.md "Tests"):

bun run --cwd packages/web test -- server/lib/spaces OPENCHAMBER_TEST_DOCKER=1 bun run --cwd packages/web test -- server/lib/spaces OPENCHAMBER_TEST_DOCKER_PACKED=1 bun run --cwd packages/web test -- server/lib/spaces/places/packed

第二行还会对本地 Docker 守护进程跑 live 文件(contract/escape/server/code-in),第三行跑 packed live 测试(需编译 sdk 并打包本地 web 与 sdk)。注意:真实集群不可触碰——每个测试显式具名自己的 context 与 namespace,拒绝针对任何其他目标运行。

分期交付与后续

STAGES.md 把特性拆成小 PR 分期交付:stage 0 四个实验(2026-09-19 完成,均带修改通过);stage 1a Docker 场所与空间管理器;1b tools 卷与空间内服务器;2 gatekeeper;3a 代码进(2026-09-22 完成)+ 3b 代码出与 apply(自带隔离审查);4 dispatcher/会话/事件;5 UI 旅程;6 dev server 预览;7 浏览器登录;首次发布(本地 Docker,Settings 中默认关闭的 opt-in);8 Docker over SSH;9 集群;10 Apple container。每阶段完成 = 真实运行时上 "works afterwards" 为真且检查有证据,清单行只有"passed, with evidence"或"blocked, with the reason"两种状态。

对首个把真实凭据放到窗口后面的阶段有一项强制义务:Linux 上任何本地进程都能达走廊与窗口(Linux Docker 宿主路由进网桥子网、无需发布端口、窗口不认证——Debian 13/Engine 29.8.1 实测),macOS/Windows 有虚拟机挡着。两个候选方案(宿主随 grant 给窗口一个每次请求携带的密钥,或窗口只绑定空间内网地址),都必须与首个真实凭据同 PR 关闭。

DESIGN.md 的 "Later" 列出后续方向:项目自定义镜像、子模块、沙箱服务(不同信任模型,因为代码与 grant 交给第三方)、Copilot 经 gatekeeper、把新的宿主变更带进运行中的空间。

关于本设计的验证边界

必须诚实说明未验证项(DESIGN.md 与各阶段文档明确记录):stage 0 未验证经窗口走 TLS 的真实模型调用、真实 OpenAI token、docker exec以外的传输与 Windows 宿主;stage 2 未验证任何 Docker 以外的场所与真实 provider/真实 Agent turn;stage 3a 未验证部分克隆、sparse checkout、超过 1 GB 的仓库与慢链路;宿主 git 2.27.0 下限来自 git 手册页而非实测;空间内存约 370 MiB 是空闲值、gatekeeper 约 24 MiB 是空闲值。这些数字是选定值而非真实工作负载下的测量,阅读与引用时请以仓库内记录为准。上游(OpenCode、Docker、Apple container)移动很快,设计文档也提醒在据其构建前重新核查事实。

  • AI Agent
  • 人工智能
  • 代码智能体
  • 交互助手

【免费下载链接】openchamber

Agentic Development Environment based on OpenCode AI agent

项目地址:https://gitcode.com/gh_mirrors/op/openchamber
点击查看免费下载

相关推荐

上一篇:一份脚本搞定 osv-scanner 离线漏洞库定时同步
下一篇:Haven自定义CSS与字体教程:打造独一无二的博客外观

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

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询