跳到正文

OpenMausBot

一个开源聊天应用:侧栏里每个 bot 都是通过你机器上已有的 CLI 跑起来的真 agent,而且每一个都能分到一台电脑。这台电脑可以是云端 Linux 桌面、同一台主机上的 Docker 或 Podman 容器、你自己 VPS 上的容器,或者在平台能证明安全的前提下,就是你面前这台机器。

Screenshot of OpenMausBot
编辑截图, 1 Oct 2026OpenMausBot ↗

这是什么

一个 Grok Bot 的开源替代品:在这个聊天应用里,侧栏每个 bot 都是一个真实的 agent 进程——就是你机器上已经装好的 Claude、Codex 或 Grok CLI——各自有性格、模型、记忆、接入的应用,以及本条目真正要讲的那部分:一台自己的电脑。它可以是一台第三方服务提供的云端 Linux 桌面,可以是同一台主机上由 Docker 或 Podman 起的隔离容器,可以是你自己 VPS 上的一个容器,也可以在平台安全边界已被认证的地方,就是你这台机器。bot 通过一根 stdio 的 MCP 管道接到随包分发的 Cua driver 上去操作这些桌面。容器边界写得很明确:内存、CPU 与进程数上限,除三项之外全部能力被丢弃,私有命名空间,只挂载一个工作目录,外加一把租约,保证同一时刻只有一个回合握着鼠标。它用七周做完,第七周末已经发了五十个版本。

谁做的他的 GitHub 账号 2018-12-30 注册,现有 144 个公开仓库、175 个关注者。仓库的 3103 次提交里有 1562 次记在他账号名下,其余分散在 102 位贡献者手里——最多的四位是 robinsonnblack 的 345 次、Brad Hallett 的 273 次、aivsomkar 的 175 次和 KesleyDavid 的 105 次。他以个人而非公司名义署名,仓库里带着一份贡献者许可协议。

它是怎么搭起来的

组成 · 6

一个绑在回环地址上的 harness 进程掌管所有 agent 进程;应用是一层聊天外壳,通过 HTTP 发出带类型的命令,并把一条 SSE 事件流折叠成状态。bot 的电脑是挂在某个回合上的 provider,而不是 bot 进程的属性;同一个回合可以落在云端 Linux 桌面、本机上的容器、通过 docker-over-SSH 够到的 VPS 容器,或者在平台安全边界已获认证的地方——就是人坐着的那台机器。有三样东西把这一切撑住。边界是被核验的而不是被信任的:容器在创建时被限定,事后又拿同一组数字复核,不合格的会被拒绝而不是被修好。所有权是带 TTL 的租约而不是锁,所以死掉的服务商占不住桌面,而接管了桌面的人可以在管道上拒掉 bot。以及没有任何快照:容器是一次性的,挂载的工作区才是状态,重建桌面就是修复手段。最后一个选择的代价,对每一种后端都直接写明,而不是藏在「持久」这个词后面。

server/
harness:597 个文件、9.6 MB。各引擎的 driver、它们汇入的事件总线、HTTP 与 SSE 接口,以及 container、VPS、host、shared 四个电脑 provider,旁边还有决定谁能驱动桌面的租约、座位池与 MCP 桥模块。
src/ 与 electron/
聊天外壳:383 个组件文件、3.5 MB,架在一个由服务端驱动的 store 上,客户端自己不持有任何传输;以及桌面外壳:160 个文件,单个 165 KB 的主进程掌握操作系统权限边界、拉起 Cua driver,并写出一个 0600 的描述文件供 harness 读取。
android/、ios/ 与 companion/
第一方手机客户端——Android 应用 229 个 Kotlin 文件、共享核心 85 个,iOS 应用 78 个 Swift 文件外加 38 个源文件与 76 个测试文件——再加上把手机通过多播 DNS、Tailscale 或托管隧道配到某台宿主上的 companion 进程。
cloudflare/
两个 Worker:一个 23 文件的控制面,带 D1 迁移、better-auth、邮件一次性验证码与自定义域名;一个 Composio broker,其 28 KB 的入口负责翻页拉取一个此前被卡在头 500 个工具包的市场目录。
docs/verification/
216 个文件、7.8 MB:每一条用户可见行为配一份配方,各自跑在带临时 home 与数据目录的一次性夹具上。写下来的规矩是「单测全绿并不等于用户流程被证明」,这个目录同时还存着带日期的验收记录和改动前后的证据。
.github/workflows/、third_party/ 与 deploy/
14 个工作流文件共 123 KB,领头的是一条 35 KB 的发布流水线和一份 26 KB 的持续集成文件;随包分发 Cua driver 的复核再分发记录,含钉死的提交、归档与内部二进制哈希、生成的声明与一份 CycloneDX 清单;以及 Docker Compose、Podman、Fly.io 和本地 Caddy 前端的部署配方。

取舍,以及它替代了什么

  • 本地桌面控制只走 Cua 一条路 替代 在它旁边再开一条——cliclick、robotjs 一类的库,或者一个 Python 的 computer server

    这件事被写成一份 2026-08-12 的决定,并且不留后备:一个权限身份、一个需要签名与公证的二进制、一份行为契约,比在本地方便地补上一个 driver 缺失的能力更值钱。缺什么就去上游加,或者做成 driver 的工具。被否掉的选项在一张表里逐个写了落败的理由。

  • 不合格的容器直接拒绝,而不是修好它 替代 把容器调到符合要求为止

    检查会审视正在运行的容器,拿它和创建时设定的内存、CPU、进程数与能力边界以及那些标签逐项比对;不合格时的提示是让用户重建。别人用同一个受管名字创建的容器会被直接拒绝。VPS 那边形状一致:发布过端口的容器在每一次接入前都会被拒,而不只是创建时检查一次。

  • 不做快照也不做恢复;重建桌面,保住工作区 替代 保存并还原容器状态

    启动这个动作被拒绝并附带解释,真正持久的是 bind 进容器的那份宿主目录以及里面的浏览器配置。VPS 指南把设计本身蕴含的结论写明,而不是把它说软:把容器文件系统当作一次性的,容器被删之前先把值得留的东西送出服务器。

  • 把整个机群的容器数量当成运维策略,并把它调高 替代 把每 bot 的上限压在四,当成一道安全阀

    每个容器本来就已经被单独限定并复核过,所以那条把上限提到八的 pull request 论证说:全应用层面的数量不是安全阀;而座位池的设计本来也需要先把上限抬高。它还带了一条回滚说明,因为旧版本读到超范围的值会整份配置退回首次运行的默认值。

  • 让最高批准级别走桌面端的私有通道,而不是 HTTP 接口 替代 让 bot 通过自己本来就在用的那套接口抵达这个放行模式

    Full access 只能从打包好的本地桌面应用里开启,文档同时写明它不绕过什么:操作系统隐私控制、身份验证、服务权限,以及工作区或电脑共享的授权。批准级别是原样透传给服务商的,应用自己不做判断、也不维护白名单或分类器;文档里还把这一套级别的来源记到了另一个项目的权限模式上。

  • 座位池只在配置文件后面发布,并把缺口写进 pull request 替代 等设置界面和删除入口都做好再发

    那条 pull request 明说:选择器仍然只提供另外两种模式,电脑面板把 pool 渲染成 shared;从 pool 切走没有删除围栏,座位容器可能一直留在实例上限内而界面上没有删除入口;座位亲和性表是按进程存的,重启即清空。每一条都被标为已知后续,而不是被当成疏忽。

依据docs/computer-use-integration.md(一份标注 2026-08-12 的决定文档)、apps/docs/content/docs/computers/local-vm.mdx、docs/byo-vps.md、docs/verification/group-local-vm.md、docs/approval-levels.md、server/container-computer.ts、server/local-vm-lease.ts、server/local-vm-seat-pool.ts、server/local-vm-idle.ts、server/mcp-bridge.ts、server/container-mcp.ts、electron/cua-connection.cjs,以及引入座位池和抬高实例上限的两个 pull request。

制作过程

6 个阶段
  1. 01

    七周、3103 次提交,以及第二天的一次改名

    仓库建于 2026-08-11 的 18:58(UTC),十九分钟后第一次提交落地:一次脚手架,描述为「OpenGrokBot 的 Vite、React、TypeScript 与 Tailwind v4 底座」。改名发生在第二天早上,紧接着一条提交的说明是「Own the identity: scrub pre-rename product references」——而旧名字只在一个地方活了下来:桌面端写进 driver 描述文件、供 harness 读取的那个 bundle 标识 com.opengrokbot.app。此后的节奏再没平下来:八月 1168 次提交、九月 1935 次,总共 3103 次,而这个项目才七周大。主仓库发了五十个 release,从 2026-09-01 的 v0.1.46 到 2026-09-29 的 v0.1.91,另有到 1.5.0 的 Android 版本,以及一个只为「仓库迁移前装上的旧版本还能更新」而存在的公开镜像——历史上最新的一次提交是把版本号推到 0.1.92,它身后还没有对应的 release。周围是 3862 个星、654 个 fork、1692 个 pull request(其中 1231 个已合并)、408 个 issue(212 个已关闭),以及 102 位贡献者。875 条共同作者尾注里最大的一群是 Claude Fable 5 的 217 条,其次是百万上下文版 Claude Opus 5 的 178 条、Claude Fable 5.1 的 144 条和 Claude Opus 5.5 的 77 条。

  2. 02

    隔离单位是容器,而容器的数量是一个设置项

    README 承诺每个 bot 都有一台电脑,实现方式则是一个容器加一个计数。共有三种模式。shared 是名为 openmausbot-computer 的单个容器,最早发布的版本就是它,现在仍是默认。per-bot 让每个 bot 拿到自己的容器,名字取自 bot id 的 SHA-256 前十六位十六进制字符,而不是任何人手打的字符串,并且有各自的宿主目录。pool 则给出一批可配置的座位,由各会话轮流使用、按序号寻址。镜像是按 digest 钉死的官方 Cua XFCE 桌面,挑的是一份确切的多架构 manifest;OpenMausBot 在用户机器上构建它的派生物:从 Python 包索引装确切版本的 Cua driver wheel 并先核对 SHA-256,装一个同样核过哈希的 CJK 字体让日文在 guest 里正常显示,然后用 supervisor 以非特权用户在 display :1 上把 driver 拉起来。构建会在任何东西使用它之前拒掉有缺陷的基底镜像——某些已发布的 ARM64 层里塞的是零字节的 OpenSSL 库,而症状要到很久以后才出现,是一条读起来像网络故障的 curl 报错。运行时的容器拿到 4 GB 内存和等量 swap、2 个 CPU、512 的进程数上限、全部能力丢弃后只加回三项——set-user-id、set-group-id,以及在 Podman 上 Firefox 建立自身沙箱所需的 chroot 能力——私有的 IPC 与 cgroup 命名空间,以及一条从宿主工作目录进 guest 的 bind 挂载。看图端口只发布在回环地址上。随后还有一次检查:把跑着的容器和这些数字、这些标签逐项比对,不合格的容器会被拒绝,并提示用户重建,而不是被悄悄调好。

  3. 03

    没有快照,所以工作区就是状态本身

    这个领域的多数产品会做快照,它偏偏不做。生命周期动词是创建、启动、停止、删除,而「启动」会被直接拒掉,理由是这张桌面镜像无法安全地恢复——于是陈旧的运行时状态靠删掉容器、重建一个来修。能活下来的只有那个 bind 挂进 guest 的宿主目录,以及里面的浏览器配置:镜像会把找到的配置目录搬进挂载的工作区、再把软链接挂回去,并先删掉陈旧的单例锁,所以 bot 在重建之后仍然保持网站的登录状态。文档没有把后果埋起来,而是直接写明。对本地容器:重建桌面可以修复陈旧的运行时状态,且不会删掉持久工作区。对 VPS:把容器文件系统当作一次性的,bot 必须留下的东西要在容器被删除之前送出服务器,因为删除——以及镜像升级后必然发生的那次重建——都会把它清空。空闲由一个定时器管,活动会重新武装它。默认窗口是八小时,2026-10-01 合入的一处改动把它变成 5 分钟到 24 小时之间的设置;改值会重新武装已经在计时的定时器,并从最后一次活动重新计时,于是更短的窗口立刻生效,更长的窗口则延长当前截止时间。有活在跑的桌面永不被回收,而是再等满一个窗口;挂起失败也只是再等一个完整窗口重试,而不是把这道成本闸门关掉。删除 VPS 上的容器从来不是这个应用会替你做的事。

  4. 04

    bot 与它那台桌面之间的管道,以及三个例外

    bot 并不是调用某个 API 去移动鼠标的。harness 在 agent CLI 的配置里加一条 MCP 条目,命令是一个随包分发的小 Node 脚本;这个脚本先校验参数,再拉起容器运行时,在 guest 里跑 driver,然后把标准输入输出原样对接。它自己不定义任何工具、也不解析任何协议消息,只有三处刻意的例外。它会自己回答 ping 方法,因为随包分发的 driver 不实现它,否则握手会直接断掉。它会把工具列表的响应改写成纯对象的根,因为严格的模型服务商拒绝根不是对象的 schema,进而让整个回合失败。它还执行「谁在操作」这条规则:当人已经接管了桌面,agent 发来的工具调用在近端就被拒掉,永远不会到达 driver。控制端点及其每次启动生成的一次性令牌走环境变量而不是参数,因为参数列表对机器上任何进程都可读。还有两个细节,是只有被烫过才会写出来的那种。这个桥从不从 close 回调里调用进程退出函数,因为那会丢掉缓冲区里还没写出的内容、把最后一帧协议切成两半;它改为设置退出码、解除管道,让流自己排空。另外,本地容器这边没有活性看门狗——它的运行时 CLI 跟本地守护进程说话、失败得很快——VPS 那一边有,因为传输助手不接受连接超时,断掉的连接会让一个回合悄无声息地挂住:整整四十五秒没有任何字节,才触发一次探测,而只有探测失败才会结束这座桥。

  5. 05

    同一时刻只有一只手,和一个可以回来的座位

    所有权是一把短期可续的租约,而不是握满整个会话的锁,它的形状是由它要裁决的那场竞态决定的。它的方法刻意做成同步,好让生命周期路由和回合派发各自在任何一方 await 之前先抢到自己那一侧。租约会自己过期,也会在持有它的 bot 不再忙碌的那一刻自行消失,于是半路死掉的服务商没法永远占住一台桌面。每个目标各有一条租约通道——shared 一条,per-bot 模式每 bot 一条——所以不同的桌面互不阻塞,而每个桌面自身仍是严格的单例。pool 模式在这之上又加了一个更软的决定:一个会话该落到哪个座位。三十分钟的亲和性让线程留在持有它登录状态的那台桌面上,代码注释把排序理由讲成关于人的判断——长到够跨过两条消息之间的停顿,短到被放弃的会话会在一小时内不再左右分配。分配优先考虑仍然有效的亲和性,也就是说线程宁可排在当前持有者后面,也不换到一台空闲桌面去丢掉自己的会话;其次是既没人持有、也没人有亲和性的座位;再其次是任何空着的座位;当所有座位都被占,就选租约最早到期的那个。只有真正抢到租约的那次声明会续期亲和性,因为还没抢到的重试不该延长自己的等待。人看到的是结果而不是机制:一个线程去碰另一线程正在用的屏幕时会等待,会报出持有者的名字,并在三十分钟后放弃。缺口写在 pull request 里而不是留给别人去发现——pool 模式这一版只在配置文件后面,设置界面的选择器仍然只提供另外两种模式;从 pool 切走没有删除围栏,于是座位容器可能一直留在实例上限之内、界面上却没有删除入口;亲和性表存在内存里,重启即清空。

  6. 06

    仓库如何记录自己的失败

    这个项目的发布文档有个少见之处:每一条校验步骤旁边都注着它是哪次事故换来的。事故来自 0.1.15 到 0.1.25 那段手工发版的日子——构建产物过期弄坏了代码签名;一处裸导入让打包后的服务器一启动就死,而所有检查都是绿的;打包后辅助程序路径解析到了应用之外;staple 这一步悄悄让所有已发布的哈希失效;还有一个做完的 release 静静地以草稿状态躺着没人看见。它们上方那句话是:不先读注释,就不要删掉任何一道闸门。更小的失败也按同样的颗粒度记着。一个 Windows 测试失败,因为 NTFS 上报目录修改时间偏晚,于是那里跳过它并注明它覆盖的追踪器只在 Linux 上跑;另一个测试在关掉把它当工作目录的那个服务器之前就先删了夹具目录,于是在 Windows 上收到权限错误;还有一次 npm 安装明明静默丢掉了平台相关的可选依赖却返回 0,现在服务器会先把刚装上的命令探一次版本,才敢说安装成功。拥塞是被量出来的,不是猜的:一个 pull request 过去要排七个 macOS 任务,而账号同时最多只跑五个,在 25 个待合并 PR 的情况下这些任务的中位等待时间到了六个半小时,于是重活只在 PR 上跑 Ubuntu 与 Windows。2026-09-07 项目租了一台一次性 Hetzner 服务器,把一次空白主机安装证明了什么、没证明什么全写了下来,包括一个需要按确切路径修 profile 才能过的浏览器沙箱失败、一次自行恢复的重启,以及一张「这次运行不背书什么」的清单。安全策略写的是当下的边界,包括开着的问题:harness 只绑回环,而在共享工作区上,没有会话的调用方——包括每个 bot 的 shell——只能用一份白名单里的路由,旁边注明这个调用方目前仍保留着某个 worker 的受保护路由,而给那个 worker 单独发一枚中继令牌是计划中的修法。

相关档案

全部档案 →