构建远程 PTY 终端流式传输与实时多人协同观察架构
在云端机器上运行 Claude Code 只需要一个容器。从浏览器观察它则需要沙箱发起的主动连接、在浏览器端而非沙箱端测量的背压控制,以及显式的关闭帧。
把代理搬到云端机器上是简单的一半,也是所有人都在讨论的一半:一个容器、一个镜像、里面运行着 CLI。决定产品是否可用的另一半,是这台机器与观察它的浏览器之间的连接,它会以任何测试都无法察觉、用户也无法准确报告的两种方式悄然失效。
笔记本不再是运行环境
把代理移出笔记本的原因并不是笔记本速度慢,而是让执行过程不再依附于某台物理设备。代理的进程和代码检出运行在我们运营的机器上;你的浏览器负责传输按键并渲染回传的字符。合上笔记本只会中断旁观,不会停止工作。
这一句话在产品的核心位置引入了一条网络连接。每一次按键和每一个字节的输出现在都要穿越一条可能变慢、停滞或毫无预兆中断的网络链路。
沙箱主动发起外联
运行代理的机器没有公开的入口点。它向控制平面打开一条连接并长期保持,连接到该机器的所有浏览器都通过这一条链路进行多路复用。这是自托管 CI runner 采用的模式,原因也完全相同:不需要暴露任何端口,不需要配置转发,处于任何 NAT 之后的沙箱都能直接工作。
这也意味着真正有价值的故障与连通性无关,而在于一条已建立但状态恶化的链路会发生什么。
第一类隐形故障:测量了错误的套接字
编译大量日志的代理产生输出的速度可能远快于浏览器的渲染速度。必须有人察觉并让生产者减速,最直观的实现是让沙箱监视自己的套接字:如果缓冲区增大,就暂停写入。
这测量了错误的对象。沙箱持有的套接字是通往控制平面的链路,控制平面是一台拥有良好网络的高性能机器,会欣然接受发给它的一切。真正发生积压的套接字是浏览器持有的那一个,位于网络最远端,由完全不同的进程处理。
因此,拥塞检测必须发生在慢速读取端,压力必须反向传回。控制平面监控它所维护的浏览器套接字,当该浏览器跟不上节奏时,停止读取与沙箱之间的链路。TCP 会处理剩余部分:接收窗口关闭,拥塞最终在沙箱端体现为其自身缓冲区的增长,这也是唯一能够做出暂停写入决策的地方。
在共享链路上暂停读取会导致其上的所有其他浏览器发生队头阻塞。拥塞的链路只会表明整条链路发生拥塞,绝不会标识出某一个慢速观众。因此暂停只能持续数毫秒,针对单个观众的限速仍由控制平面负责。
第二类隐形故障:永远会自动回复的 Ping
询问对端是否在线的常规方式是 WebSocket ping。但这在这里毫无意义,因为 ping 是由对端的操作系统网络栈自动回复的,而不是由对端应用程序回复。一个已关闭的标签页、一台休眠的笔记本、一个崩溃的网页,在一段时间内都会照常回复 ping。
负责维护浏览器套接字的一端是唯一能确认浏览器已离开的角色。因此它会在内部链路中显式发送离开消息,沙箱仅凭该消息做出判断。当链路本身断开时,其上保持的所有连接都会在本地关闭一次,确保终端绝不会向无效地址持续推送输出。
这两条规则的存在是为了避免内存泄露以及在控制台上显示虚假活跃会话。
为什么这不是次要细节
任何人都能把 CLI 放进容器。决定人们是否愿意长久运行代理的关键,在于机器与人之间的连接能否真实反映自身状态:它清楚何时发生拥塞,知晓观察者何时离开,并据实做出反应。
防止多个代理相互干扰的预约看板是另一个独立问题,有专门的文章阐述。防止运行模型代码的机器接触机密信息的架构隔离则是第三篇文章的主题。本篇探讨的是贯穿它们之间的通信纽带。
直接回答
合上笔记本后 Claude Code 还能继续运行吗?
可以,只要代理进程和代码检出位于云端机器上。浏览器仅负责发送按键并绘制回显,合上电脑只会结束观看,不会终止运行。
云端代理需要在我的本地网络上开放端口吗?
不需要。沙箱主动向控制平面发起单向长连接,采用类似自托管 CI runner 的模式,无需公网入口即可穿透任何 NAT。
当观察的浏览器崩溃或断开时,正在运行的任务会怎样?
任务会继续运行。终止浏览器连接的一端会显式通知沙箱,避免因 WebSocket 协议层自动响应 ping 而误判空标签页依然活跃。