WebSocket 接口
基于 main@306d253 的文档在 GitHub 上查看编辑此页
WebSocket /ws/sessions
Section titled “WebSocket /ws/sessions”会话列表,文本帧 JSON。连接后先收到 snapshot,之后为增量事件:
{"type": "snapshot", "sessions": [SessionInfo, ...]}{"type": "created", "session": SessionInfo}{"type": "updated", "session": SessionInfo}{"type": "removed", "id": "abc123"}updated 每会话最多 500 ms 一次;仅预览变化最多 2 秒一次。客户端无需发送消息。
WebSocket /ws/s/{id}
Section titled “WebSocket /ws/s/{id}”终端流,二进制帧,首字节为类型(internal/contract/terminal.go 中的 Frame* 常量)。JSON 控制帧由 internal/hub 编码,会话服务只产出类型化消息。
可选查询参数:
| 参数 | 说明 |
|---|---|
client | 客户端 ID,[A-Za-z0-9_-]{8,64};前端每个标签页一个,刷新与重连保持 |
label | 设备名,去除控制字符后最多 40 个字符,用于显示控制端 |
generation | 续接点:客户端终端所属的会话代际 |
offset | 续接点:客户端已渲染到的输出 offset(十进制) |
格式不符的参数被忽略:缺少 client 时服务端为连接生成随机 ID;续接点无效时全量回放。
服务端 → 客户端:
| 首字节 | 含义 | 载荷 |
|---|---|---|
0x00 | 输出 | PTY 原始字节 |
0x01 | 快照 | 可安全回放的缓冲;客户端应先重置终端再写入,截断恢复时仅初始化终端模式,不包含旧文本 |
0x02 | 状态 | JSON SessionInfo |
0x03 | 退出 | JSON {"exitCode": n},随后关闭连接 |
0x04 | 位置 | JSON {"generation", "mode": "reset"|"append", "start", "end", "truncated"},先于回放 |
0x05 | 提交状态 | JSON {"id", "state", "error"?} |
0x06 | 控制 | JSON {"controller": {"clientId", "label", "since"} | null, "cols", "rows", "colorSchemeReports"} |
客户端 → 服务端:
| 首字节 | 含义 | 载荷 |
|---|---|---|
0x00 | 输入 | 写入 PTY 的 UTF-8 字节 |
0x01 | resize | JSON {"cols": n, "rows": n};仅控制端生效 |
0x02 | 重新同步 | 空;服务端回复 reset 位置帧与 0x01 快照 |
0x03 | 提交 | JSON 头 {"id", "generation", "bodyBytes"}、\n、正文与提交键字节 |
0x04 | 查询提交 | JSON {"ids": [...]},最多 64 个 |
0x05 | 接管控制 | 空 |
0x06 | 自动回复 | xterm 对设备查询的应答字节;仅负责回复的连接写入,不取得控制 |
colorSchemeReports 表示程序已开启 DEC 私有模式 2031(CSI ? 2031 h)。服务端持续跟踪该模式,不受回放缓冲淘汰影响,变化时在对应输出之后广播 0x06。开启期间,两类客户端的行为如下:
- 浏览器:在主题切换、附着或取得控制时以自动回复发送
CSI ? 997 ; 1 n(暗色)或CSI ? 997 ; 2 n(亮色),并应答CSI ? 996 n查询;程序据此重新查询 OSC 10/11 颜色。 - 原生终端附着(
webmux attach/run):在取得控制或程序开启该模式时,向本地终端发送 OSC 11 背景色查询,按应答亮度以自动回复发送同样的报告。- 查询应答本身不写入 PTY。
- 本地终端不应答 OSC 11 时不发送报告。
- 分离时关闭本地终端的 2031 模式。
使用二进制帧而非 JSON,是为了避免 base64 开销以及在多字节字符中间截断时的 UTF-8 问题;提交帧同样只把元数据放进 JSON 头。
每次回放(附着或 0x02)依次发送 0x06 控制帧和 0x04 位置帧:观察端先按控制端网格调整终端,再解析按光标定位的历史。offset 为会话自启动以来累计输出的字节数。0x02 清空队列时保留尚未送出的 0x05 提交状态,排在状态帧之后、0x03 退出帧之前;退出帧始终是最后一帧。
mode: "reset":随后一帧0x01快照。缓冲未截断时覆盖[0, end);缓冲已截断时start = end,快照仅包含模式初始化序列,不计入原始流 offset。客户端清空不可恢复的历史,不解析原始缓冲的任意字节后缀。mode: "append":续接点的代际一致且仍在保留范围内时使用,随后(如有)一帧0x00,内容恰为[offset, end);即使更早历史被覆盖,已有终端状态仍可安全续接,不清空画面、不请求重绘。truncated:最早输出已被环形缓冲覆盖;不能由重置后的start推断保留范围。
截断后的重置会请求仍运行的程序重新绘制:
- 做法:短暂改变 PTY 的实际列数,再恢复控制端尺寸;不发送按键、不转移控制权。逻辑控制网格保持不变,并发控制端 resize 优先于待恢复的尺寸。
- 原因:仅发送同尺寸
SIGWINCH不足以使部分 TUI(包括 opencode v2.1.4)重绘。 - 限制:程序不响应时不保证恢复成功,应使用程序自身的重绘操作。已退出程序不请求重绘,不能恢复被覆盖的历史。
- 浏览器不显示历史丢弃或重绘请求提示,查找范围与复制缓冲中的历史截断标记仍保留。
GET /api/sessions/{id}/buffer 仍返回诊断用原始字节,不是可恢复的屏幕快照;复制全部缓冲沿用此接口。
- 退出后的浏览器恢复也使用 WebSocket 的安全回放路径,不将该接口的截断字节送入 xterm。
- 这里不保存完整 VT 状态:若恢复点恰在一个控制序列中间,后续实时字节仍可能包含该序列的尾部,程序完整重绘才是画面恢复依据。
回放与实时订阅在同一把锁内登记,此后的 0x00 帧按字节累加即为客户端的 offset。
浏览器在断线重连期间显示不遮挡终端内容的“正在恢复…”提示,连接恢复后隐藏;全量回放期间使用“正在恢复…”遮罩。
提交 ID 为 [A-Za-z0-9_-]{8,64}。服务端在一次输入锁持有期间写入正文与提交键,状态依次为:
state | 含义 |
|---|---|
received | 已登记,正在写入 |
written | 全部字节已写入 PTY(不代表 agent 已处理) |
partial | 部分字节已写入后出错 |
failed | 未写入;error 为 generation、invalid 或 write |
missing | 仅用于查询:本次运行无此 ID 的记录(未送达或已过期) |
重复 ID 不会再次写入,服务端直接回报已记录状态。状态发往提交客户端(同一 client)的全部连接;在 received 期间查询过该 ID 的其他客户端(例如在新标签页恢复的草稿)也会收到最终状态。记录保留最近 256 条、30 分钟。
- 会话无控制端时,首个附着的客户端成为控制端;同一
client重连保持控制。- 控制端客户端最近附着的连接是唯一有效连接(复制的标签页可能带着相同 ID),只有它的 resize 与自动回复生效。
- 它断开时由该客户端的其他连接接替。
- 同一客户端的另一条连接成为有效连接时(接替或用户输入),服务端向它单独发送
0x06,使它重新发送尺寸。
0x00输入、0x03提交与0x05把控制权交给发送方;控制端的最后一个连接断开后控制权空缺,不自动转交。- 只有控制端连接的
0x01生效,所有权检查与 resize 在同一临界区内完成;尺寸或控制变化时广播0x06。 - 输入来源由客户端标明:xterm 的用户输入走
0x00,对设备查询的自动应答走0x06(同样的字节可能来自按键,如 Shift+F3 与光标位置报告都是ESC [ 1 ; 2 R)。0x06只接受控制端连接,无控制端时只接受最早附着的连接,且不取得控制、不清除关注提醒。 - 前端复制标签页时,若在
sessionStorage中看到原标签页的存活标记,会换用新的客户端 ID。
| 关闭码 | 含义 |
|---|---|
1011 | 服务端内部消息非法或编码失败;仅关闭当前连接,会话继续运行 |
4404 | 会话不存在 |
4408 | 客户端过慢或写超时;客户端应立即重连并回放 |
4409 | 混合会话已移到另一端,原因为 session moved: <所在端>;终止状态,客户端不得自动重连。附着一个没有终端运行的混合会话时也立即以它关闭;webmux attach 视为被动分离,以退出码 0 结束 |
4410 | 会话已退出(先发送快照与退出帧) |
认证失败在握手阶段直接返回 HTTP 401,不升级连接。
所有 WebSocket 路径(/ws/sessions、/ws/s/{id}、/ws/s/{id}/chat、/ws/settings)接受查询参数 compress=1。
带此参数且客户端提议 permessage-deflate 时,服务端以上下文复用模式协商该扩展;不带参数时不协商。
浏览器总是提议该扩展且脚本无法撤回,因此由参数表达意图;前端按设备开关追加它(设置页“网络”,US-H10)。
小于 128 字节的消息不压缩,帧格式与语义不变。