机台
只讲事实。要了解它们背后的模型,请阅读机台与隔离舱;要打开一台机器,请遵循本指南。
机台与后端
标题为“机台与后端”的部分| 机台 | 驱动内容 | 后端 | 取值 | 隔离式出站访问 |
|---|---|---|---|---|
| 终端(exec) | VM exec 通道之上的一层 shell | microVM | smolvm |
是 |
| 浏览器 | 无头 Chromium(CDP)、Firefox(WebDriver BiDi)或 WebKit(W3C WebDriver) | microVM | smolvm |
是 |
| 电脑 | X11 桌面:输入经由虚拟机管理程序,采集经由客户机代理 | QEMU | qemu-hvf(macOS)、qemu-kvm(Linux)、qemu-tcg(无加速的回退项,绝不会被自动选中) |
是 |
| Android | 经由 ADB 连接的 Android | Google 的模拟器 | android-emulator |
否——该模拟器自行管理其网络 |
| iOS | 经由 WebDriverAgent 连接的 iOS 模拟器 | CoreSimulator | ios-simulator |
否——其网络跟随宿主机 Mac |
终端与 computer_use 共用 computer 这个机台值;终端靠它的 exec 镜像标记加以区分,绝不会被当作可操控的桌面复用(反之亦然)。一个对话会复用一个终端机器。
浏览器引擎
标题为“浏览器引擎”的部分| 引擎 | 取值 | 协议 | 实时画面 | 静态画面 |
|---|---|---|---|---|
| Chromium | chromium(默认) |
Chrome DevTools Protocol | 屏幕串流——页面重绘时由浏览器推送帧 | JPEG |
| Firefox | firefox |
WebDriver BiDi | 轮询式静态画面(BiDi 没有屏幕串流),上限 6 fps | JPEG |
| WebKit | webkit |
W3C WebDriver(经典) | 轮询式静态画面,由宿主机的 ffmpeg 转码 | PNG |
一台机台终其一生只运行一个引擎。一个对话可以按引擎各持有一台机台,这正是并排比较得以成立的原因:在“Firefox(VM)”旁边打开“Chromium(VM)”就是两台机器,关闭其中一台,另一台仍在运行。
把 engine 传给 rig.open 或 browser_use 工具。未知名称会被拒绝,而不是回退到默认值——请求一种浏览器却悄悄拿到另一种,等于用一个错误答案回答了一个兼容性问题。
各引擎特有的限制,如实说明,而不是绕开:
- Firefox 没有绕过缓存的重新加载(它直接拒绝 BiDi 的
ignoreCache),因此强制重新加载就是一次普通重新加载。 - WebKit 同样没有绕过缓存的重新加载,也没有控制台数据流(其输出是在每次导航之后通过挂钩
console.*捕获的,因此加载期间记录的任何内容都会丢失——每一次控制台提取都会说明这一点),并且其实时画面需要宿主机上有ffmpeg。 - 两者都不会经由各自的协议暴露平台的无障碍树,因此
extract (a11y)返回的是一个由 DOM 推导出的近似结果,并标注为近似结果。 - 两者都无法接收真正的文件投放;但文件输入仍可被指向机器内部的路径。
机器镜像
标题为“机器镜像”的部分| 机台 | 镜像 | 获取方式 |
|---|---|---|
| 终端 | ubuntu:24.04,以摘要锁定 |
首次启动时自动获取,经由该机器自身的受控网络 |
| 浏览器(Chromium、Firefox、WebKit) | debian:trixie-slim,以摘要锁定,外加引擎自身的软件包 |
首次启动时自动获取,随后缓存为预热包 |
| 电脑 | 一个 qcow2 桌面镜像,内含 Chromium、Firefox、WebKit,以及终端所预热的同一套 GitHub 托管运行器 CLI 基线 | 在设置 → 服务器 → 机台下导入(在已发布的情况下为校验和锁定的下载) |
| Android | Google 的系统镜像 | 通过 Android SDK |
| iOS | 一个 Xcode 模拟器运行时,外加以校验和锁定的 WebDriverAgent | 运行时通过 Xcode;自动化桥接来自设置 → 服务器 → 机台 |
每一种 microVM 首次启动之后,服务器都会在数据目录下缓存一份预先解压的副本,并据此启动之后每一台机器——包括软件包已安装好的预热镜像。终端会预热一套 GitHub 托管运行器风格的 CLI 工具集(git、gcc、python3、jq、rsync、归档工具,以及 apt 基线的其余部分;不包含 Docker 或语言工具缓存)。电脑的 qcow2 在构建镜像时就把同一批 CLI 名称烘焙进去,与 XFCE 和这三种浏览器放在一起。每一种浏览器都会预热它的引擎。三种引擎共用同一个 Debian 基础,但绝不共用一个包:缓存以镜像、引擎以及一个音频修订版(audio-v1-<engine>)为键,因为为某一个引擎——或在 PulseAudio 与 ffmpeg 尚未成为工作负载一部分之前——预热出来的机器,里面没有另一个引擎的任何东西。终端包出于同样的原因以工具集修订版(tools-v1)为键:一个在编译器尚未成为镜像一部分之前预热出来的包,绝不能服务于一台本应装有编译器的机器。
没有任何现成的自动化镜像值得拿来用——Chromium 的 chromedp/headless-shell 把 ALSA 和 PulseAudio 都编译掉了,因此它永远发不出声音;唯一还在维护的 Firefox 镜像是 Debian 基础镜像的 33 倍大,而且仍不附带 socat;至于 WebKit,则根本没有还在维护的镜像。所以每一种引擎都在首次启动时安装(chromium、firefox-esr 或 webkit2gtk-driver),并在安装过程中访问 Debian 归档源(deb.debian.org、security.debian.org、cloudfront.debian.net)。Chromium 使用的是发行版自带的那个二进制文件,以 chromium --headless=new 启动。
按工作区的自定义镜像
标题为“按工作区的自定义镜像”的部分| 设置键 | 覆盖对象 |
|---|---|
rigs.smolvm.execImage |
终端(VM)镜像 |
rigs.smolvm.browserImage |
Chromium 浏览器(VM)镜像 |
经由受管理员门控的工作区设置存储写入(设置 → 服务器 → 机台 → 自定义镜像(此工作区))。该值必须是一个镜像仓库引用——本地路径、归档文件和形似标志的字符串都会被拒绝,在字段处拒绝一次,启动时再拒绝一次。已存储的值若无效,会被响亮地忽略,转而启动默认镜像。该镜像自身的镜像仓库(外加 Docker Hub、GHCR、Quay、GitLab 和 GCR 已知的 blob CDN 配套域名)会为这次拉取经由出站访问关卡放行。
终端镜像契约——存在 bash;git、curl、socat 已预装,或者 是一个 Debian/Ubuntu apt 基础(隔离舱会在首次启动时装上这三者,外加一套 GitHub 托管运行器风格的 CLI 工具集:编译器、Python 3、jq、归档工具、rsync。语言工具缓存与 Docker 不会安装)。
浏览器镜像契约——一个 Debian/Ubuntu apt 基础(或已装好这些软件包),带有 bash 和 socat,外加一个由服务器自己以 chromium --headless=new 启动的 chromium 二进制文件,其 DevTools 位于客户机回环地址 9223,并转发到客户机端口 9222。PulseAudio 和 ffmpeg 会在首次启动时为音频通道装上。不要构建 FROM chromedp/headless-shell:上游的 headless.gn 把 ALSA 和 PulseAudio 都编译掉了,因此那个二进制文件发不出声音。该覆盖只适用于 Chromium 机台——它启动的工作负载命令是 Chromium 的,把它交给 Firefox 或 WebKit 会启动一个并不存在的二进制文件。Firefox 或 WebKit 机台会响亮地忽略它,并启动自己锁定的镜像。
自动化端口——每一台浏览器机器都把恰好一个宿主机回环端口转发到客户机端口 9222。它背后:Chromium 的 DevTools 和 Firefox 的远程代理位于客户机回环地址 9223,前面有一层 socat 中继(两者都无条件绑定回环);WebKit 的驱动接收一个 --host,并自行绑定客户机网卡。Firefox 还会拿它自己所在的端口来校验 Host 头,因此客户端发送的是客户机侧的 authority,而不是它拨号时连接的那个。
动作动词
标题为“动作动词”的部分每一个动作都会被完全解析:格式错误的载荷会返回一条指明该字段的消息,而绝不会抛出异常。观察类动词(以 ○ 标记)在人类持有控制权时仍然允许;变更类动词则会被拒绝。
computer_use
标题为“computer_use”的部分screenshot○、cursor_position○、mouse_move、left_click、
right_click、middle_click、double_click、triple_click、
left_mouse_down、left_mouse_up、left_click_drag、scroll、key、
hold_key(0–60 秒)、type、wait○(0–60 秒)、set_display、
clipboard_read○、clipboard_write。
坐标是客户机当前模式下的客户机像素;每一张截图都会说明显示尺寸。组合键使用 X11 的写法(ctrl+s、alt+Tab、Return)。
clipboard_read 接收一个 selection:clipboard(ctrl+C 填充的内容,默认值)、primary(X11 的选中即复制)或 xdnd(进行中的拖放)。空结果对这三者来说都是正常答案。clipboard_write 只接收 text——图像和文件走文件通道,因为动作的参数会被持久化到审计日志,而图像字节不该出现在那里。剪贴板内容以围栏包裹的不可信数据形式返回。
browser_use
标题为“browser_use”的部分navigate(仅 http/https)、reload(hard 可选)、stop_loading、
click(选择器或坐标)、fill、type、key(DOM 键名)、
scroll、mouse_move、drag、left_mouse_down、left_mouse_up、
extract○(a11y | dom | console)、screenshot○(full_page
可选)、set_viewport(最小 320×240)、history、wait_for○(选择器,
0.1–30 秒)、clipboard_read○、clipboard_write。
提取出的页面内容以围栏包裹的不可信数据形式返回。
浏览器只有一个剪贴板,因此 clipboard_read 不接收 selection。两个剪贴板动词都需要 安全上下文:通过纯 http:// 提供的页面根本没有剪贴板 API,该动词会如实说明这一点,而不是报告一个空剪贴板——在那里请改用 type,它会在光标处插入,不经过剪贴板。
mobile_use
标题为“mobile_use”的部分tap、swipe(50–5000 毫秒)、type、key(别名 back、home、
recents、enter、delete、backspace、tab、escape、search、
menu、volume_up、volume_down、power,或任意 KEYCODE_*)、
screenshot○、ui_dump○、install_apk
(限于机台的工作树和服务器数据目录)、start_app、
stop_app、clear_app_data、uninstall_app、open_url、shell。
shell 接收一个 argv 列表并在 Android 内部运行,绝不经过宿主机 shell;
它覆盖 logcat、dumpsys 和 pm grant 之类的开发者诊断。
mobile_use 没有任何剪贴板动词。自 Android 10 起,只有获得焦点的应用才能读取剪贴板。
ios_use
标题为“ios_use”的部分tap、swipe(50–5000 毫秒)、type、key(enter、backspace、tab、
escape、方向键或一个可打印字符;可选的 command、control、option
和 shift 修饰键)、home、lock、unlock、screenshot○、ui_dump○、
install_app(一个模拟器 .app 目录,限于机台工作树或服务器数据目录)、
start_app、stop_app、uninstall_app、open_url、spawn。spawn 接收
argv,并通过 simctl 在模拟器内部执行;它从不调用宿主机 shell。
坐标是 iOS 屏幕的点,而不是截图像素。实时流保持模拟器的固定尺寸,并在其周围加黑边,而不是调整设备大小。宿主机到模拟器、以及模拟器到宿主机的文本剪贴板传输,使用与其他机台相同的按用户、按方向的许可策略;iOS 的粘贴板通道不接受文件和图像。
每一台机台都会创建一个归它所有的 CoreSimulator 设备和 WebDriverAgent 会话。关闭它会停止 WebDriverAgent,关闭并删除该设备,并移除其注册表条目。iOS 机台只能在装有 Xcode、已安装 iOS 运行时并具备锁定自动化桥接的 macOS 服务器上运行。它们不提供音频、麦克风、文件投放、宿主机 shell 或封闭网络的保证。
管理工具
标题为“管理工具”的部分| 工具 | 说明 |
|---|---|
rig_list |
列出某个工作区的机台,含机台类型、状态、显示与控制方 |
rig_close |
销毁一台机台并丢弃其磁盘 |
生命周期
标题为“生命周期”的部分| 阶段 | 含义 |
|---|---|
provisioning |
正在启动;状态详情会逐字携带当前步骤 |
ready |
可达并接受动作 |
parked |
空闲停驻:vCPU 已停止,RAM 仍常驻;下一次动作会唤醒它 |
closing |
正在关闭 |
closed |
已消失;该行会保留下来供审计留痕 |
failed |
已死亡或从未成功启动;可区分出来,以便 UI 能够提供重试 |
| 关闭原因 | 触发时机 |
|---|---|
requested |
人或智能体主动请求 |
idleTimeout |
空闲超过空闲窗口的 2 倍,或因内存被淘汰 |
ttlExpired |
硬性生命周期耗尽 |
conversationEnded |
所属对话已结束 |
workspaceGone |
工作区被删除或成员资格丢失 |
serverShutdown |
服务器正在停止 |
backendFailure |
其底下的虚拟机管理程序已死亡 |
默认值与上限
标题为“默认值与上限”的部分| 项目 | 终端 | 浏览器 | 电脑 | 移动端 |
|---|---|---|---|---|
| 内存 | 512 MB | 2048 MB(Chromium);2560 MB(Firefox、WebKit) | 4096 MB | 4096 MB |
| vCPU | 2 | 2 | 4 | 4 |
| 硬性 TTL | 8 小时 | 2 小时 | 2 小时 | 2 小时 |
| 空闲窗口 | 45 分钟 | 15 分钟 | 15 分钟 | 15 分钟 |
| 启动显示 | — | 1280×800 | 1280×800 | 1080×1920 |
- 智能体通道的静态画面上限为 1280×800;人类通道最高协商到 2560×1600。
- 宿主机为所有机台设定的常驻内存预算默认为 12 GB,以常驻兆字节计算(已停驻的 VM 仍占用它的 RAM)。淘汰采用最久未使用策略,绝不会夺走有人正在操控、正在观看或在其上打开着终端的机器。
- 打开着的终端或观看通道会让机台免于停驻和空闲回收;硬性 TTL 仍然适用。
端口与开发域名
标题为“端口与开发域名”的部分| 端口 | 位置 | 用途 |
|---|---|---|
7911 |
终端客户机 | 承载所有转发的那台端口多路复用器 |
9222, 9223 |
浏览器客户机 | 自动化中继与端点(WebKit 直接绑定 9222,不使用中继) |
80, 443 |
浏览器客户机回环 | 开发域名的 HTTP/HTTPS 通道 |
- 服务端大约每 4 秒轮询一次终端客户机正在监听的 TCP 端口。
- 每台机台最多自动转发 16 个端口;更多的端口只记录在日志里,不转发。
- 宿主机侧的转发绑定回环,优先使用客户机自身的端口号;在局域网共享会额外增加一个由操作系统分配的 LAN 端口。
- 开发域名必须匹配
name.test或name.localhost(小写,标签最长 63 个字符)。一个域名映射到一个端口。 - 开发域名的 HTTPS 使用服务器本地的 CA(EC P-256,位于
<dataDir>/rigs/tls/下,密钥权限 0600,绝不进入任何客户机),并为*.test/*.localhost签发一张通配符叶子证书。宿主机上没有openssl就没有 HTTPS 通道;纯 HTTP 仍可路由。
出站访问默认值
标题为“出站访问默认值”的部分| 机台 | 默认允许列表 |
|---|---|
| 终端 | 沙盒开发者基线(代码托管、软件包注册表、模型提供商)+ Ubuntu apt 镜像源 + 工作树自身的代码托管主机 |
| 浏览器 | usectrl.dev(产品站点)——外加每种引擎首次启动、安装其软件包时的 Debian 归档源。主页是一份本地 file:// 文档,不需要出站访问 |
| 电脑 | 空——拒绝一切——直到调用方授予主机 |
| 移动端 | 不强制执行(由模拟器管理网络) |
每一台 microVM 还会额外获得其镜像的镜像仓库主机,仅用于拉取。凭据代理的允许主机集合源自同一份允许列表:对于客户机够不到的主机,不会签发任何令牌。
剪贴板与文件传输
标题为“剪贴板与文件传输”的部分复制/粘贴的两个方向,以及文件的拖入拖出。可供操控机台的人使用;智能体则通过上面的动词获得剪贴板,完全没有文件传输(它在隔离舱内已经有一个 shell)。
剪贴板跨越边界按用户、按方向各自独立把关。宿主到机台的粘贴默认允许;机台到宿主机的复制会先弹出询问(可授予十分钟权限,或选择始终允许,该选项位于设置 → 服务器 → 机台 → 剪贴板访问下)。这两个选择彼此独立,而且从来不是工作区设置。
| 通道 | 路由 | 承载内容 |
|---|---|---|
| 剪贴板 | GET/POST /rig/clipboard/<rigId> |
文本、一张 PNG/JPEG(≤16 MB),以及剪贴板上各文件在客户机中的路径 |
| 文件 | POST /rig/files/<rigId> |
最多 64 个文件,每个 ≤256 MB 且每次投放 ≤512 MB,可带一个可选的客户机像素投放点 |
| 文件 | GET /rig/files/<rigId>?p=<base64url path> |
一个文件的字节,取回方向 |
- 是 HTTP,不是 RPC。 RPC 套接字在收到超过 256 KB 的入站帧时会关闭连接——这道 DoS 防护对 JSON 命令是对的,对粘贴的截图却是致命的。字节走的是机台帧和会议音频已经在用的同类路由。
- 认证用的是签名目标
rig-files:<workspaceId>/<rigId>加上工作区成员资格——一个独立的目标,因此为观看某台机器而签发的 URL 无法同时向其中写入文件。 - 接管同样适用。 写入剪贴板和投放文件属于变更类操作,在他人持有控制权时会被拒绝;读取剪贴板和读出文件属于观察类操作,不受此限。
- 投放文件的落点:
~/Drops(桌面)、~/drops(终端——位于工作树旁边,绝不位于其中,因此投放的文件绝不会表现为未跟踪的改动)、/tmp/cc-drops(浏览器)。目录由服务器挑选;调用方无法指定。已有文件绝不会被覆盖——重名者会以2-name.ext落盘。 - 客户机会被告知什么: 浏览器页面会在请求的位置收到一个真实的拖放事件(
Input.dispatchDragEvent);桌面则会在其剪贴板上拿到这些文件及其 URI,因为没有任何宿主机能在不于客户机内运行提权守护进程的情况下,向任意工具包合成一次 XDND 拖放。回复会说明实际发生的是哪一种。 - 拖出只在桌面上可用,会在拖放进行中读取客户机的
XdndSelection。如果载荷与按下开始时客户机已经持有的内容完全相同,它会被当作上一次已结束拖放留下的陈旧选择,而不是一次新的拖放——X 没有“拖放已结束”这一步。 - 镜像要求: 桌面镜像必须为协议 2 或更高版本(在剪贴板支持落地之后构建)。较旧的镜像会返回 404,应用会说需要重新构建它,而不是报告机器损坏。
- 审计日志记录的是形态,绝不是内容:
clipboard_read、clipboard_write、drop_files和read_file各行携带的是数量、大小和路径。剪贴板正是凭据一次停留几秒的地方,而其中任何内容都不会被记下来。
护栏、事件与存储
标题为“护栏、事件与存储”的部分- 机台工具声明
enclosureControl动作类别(各 use 工具还声明networkEgress和processSpawn;mobile_use和ios_use还声明packageInstall)。rig_close声明enclosureControl;rig_list什么都不声明。默认允许;只读模式一概拒绝;按空间生效的自主程度旋钮可以把它降级为先询问。 - 领域事件:
RigClosedEvent(含其关闭原因)、RigControlChanged(接管 / 交还)、RigReaped(空闲、TTL 或内存淘汰,含驱动它的智能体)。打开一台机台不算事件。参见领域事件。 - 存储:每个工作区数据库中的
rig_sessions和rig_action_log。动作日志记录每一个输入事件及其主体,以及一个按机台单调递增的序号;键入的文本在写入之前会被抹去,只留下长度和哈希。 - 数据目录:
rigs/images/(导入的 qcow2 镜像)、rigs/smolvm/(每台机台的运行时状态)、rigs/smolvm-packs/(预先解压的机器包)、rigs/tls/(开发域名的 CA)、rigs/run/(QEMU 覆盖层和种子)。 - 路由:实时画面是空间或 PR 页面里的一个标签页;
/settings/server/rigs是机台设置界面。