WebSocket 与 WebUI
对应源码
| 主题 | 源码 |
|---|---|
| WebSocket Channel 与协议 | nanobot/channels/websocket.py |
| WebUI 会话协调 | nanobot/session/webui_turns.py |
| WebUI 后端路由与运行数据 | nanobot/webui/ |
| 前端客户端与消息渲染 | webui/src/lib/nanobot-client.ts、webui/src/hooks/useNanobotStream.ts、webui/src/components/ |
| WebSocket/WebUI 测试 | tests/channels/test_websocket_*.py、tests/webui/ |
学习目标
读完后,你应当能够:
- 解释 WebUI 为什么依赖 WebSocket Channel 和 Gateway;
- 区分 WebSocket 消息帧与 Runtime Event;
- 理解多 chat multiplex、stream segment 和认证边界;
- 找到 UI 状态协调的 Python 侧入口。
前置知识
- 理解 ChannelManager;
- 理解 RuntimeEventBus;
- 知道 WebSocket 是双向长连接。
1. WebUI 不是 Gateway health 页面
默认端口:
| 表面 | 默认端口 |
|---|---|
| WebSocket Channel / WebUI | 8765 |
| Gateway health | 18790 |
| OpenAI-compatible API | 8900 |
WebUI 静态资源由 WebSocket Channel 相关 HTTP 服务提供。18790 主要是 Gateway health,不是浏览器工作台。
2. 结构
flowchart TD UI["React/TypeScript WebUI"] --> HTTP["静态资源与 WebUI HTTP API"] UI <--> WS["WebSocket Channel"] WS <--> BUS["MessageBus"] BUS <--> AL["AgentLoop"] AL --> RE["RuntimeEventBus"] RE --> WTC["WebuiTurnCoordinator"] WTC --> BUS HTTP --> GS["Gateway Services"] GS --> SM["Session / Cron / Skills / Settings"]
WebUI 同时使用:
- WebSocket 对话和实时事件;
- HTTP routes 管理设置、媒体、Skills、Apps、Cron 等;
- Session 和 workspace metadata。
3. WebSocket 连接
连接 URL:
ws://<host>:<port><path>?client_id=<id>&token=<token>认证可能使用:
- static token;
- 短期 issued token;
tokenIssueSecret;websocketRequiresToken。
allowFrom 仍控制 client ID。
对外绑定 0.0.0.0 时,Gateway 会要求 token 或 token issue secret,避免无认证暴露。
4. 多 Chat Multiplex
一个 WebSocket connection 可以订阅多个 chat ID。客户端 envelope 包括:
new_chatattachmessage
服务端确认:
attached
这意味着:
- connection ID 不等于 chat ID;
- 一个浏览器连接可以承载多个 Session;
- page reload 后可以重新 attach;
- stream buffer 必须按 chat ID 和 stream ID 区分。
5. 主要服务端事件
| 事件 | 作用 |
|---|---|
ready | 连接已建立 |
message | 完整回复 |
delta | 文本增量 |
stream_end | 当前文本 segment 结束 |
reasoning_delta | 推理增量 |
reasoning_end | 推理 segment 结束 |
runtime_model_updated | 运行模型发生变化 |
goal_state 等协调事件 | UI 状态同步 |
error | 软协议错误,连接可保持 |
具体协议字段以 官方 WebSocket 文档 和 nanobot/channels/websocket.py 为准。
6. Stream ID
同一个 chat 内可以出现多个 stream segment,例如:
模型先输出
→ 调用 Tool
→ 继续输出因此 UI 不能只按 chat ID 把所有 delta 永久拼接。_stream_id 用于区分 segment,stream_end 关闭当前 segment。
7. Runtime Event 与 WebSocket Frame
RuntimeEventBus 发布通用 Python 事件:
- Turn started;
- run status;
- completed;
- goal changed;
- runtime model changed。
WebuiTurnCoordinator 或 WebSocket adapter 将其转换为 UI 所需 frame。
Runtime Event:核心内部语义
WebSocket Frame:客户端 wire protocol
React state:最终展示状态三层应保持分离。
8. WebUI Python 侧服务
nanobot/webui/ 包含:
- settings routes/API;
- Skills API;
- MCP preset;
- CLI Apps;
- media;
- file preview;
- session list/index;
- automations;
- gateway token;
- workspace;
- token usage;
- thread disk/display state。
这些不是全都经过 AgentLoop。例如修改设置或列出 Skills 是管理操作,不是用户 Agent Turn。
9. React 前端
源码位于 webui/。只有在修改浏览器展示时才需要深入:
- Vite;
- React;
- TypeScript;
- WebSocket client;
- 状态和组件。
排查顺序:
- 核心 Turn 是否完成;
- Runtime Event 是否发布;
- WebSocket frame 是否送达;
- 浏览器是否接收;
- React 是否正确更新。
不要从 CSS 或组件开始猜后端状态。
10. WebUI 能力不等于安全授权
WebUI 可以选择 workspace 和本地 access mode,但不能绕过:
- Gateway 配置;
- Tool workspace restriction;
- shell sandbox;
- 操作系统权限;
- Provider 权限;
- SSRF;
- token/allowFrom。
UI 控件是能力选择表面,不是新的安全根。
源码与测试
nanobot/channels/websocket.pynanobot/session/webui_turns.pynanobot/webui/webui/tests/channels/test_websocket_integration.pytests/channels/test_websocket_http_routes.pytests/channels/test_websocket_envelope_media.pytests/webui/
自检
- 为什么 WebUI 在 8765,而 health 在 18790?
- connection ID、client ID 和 chat ID 有什么区别?
- Runtime Event 为什么不能直接当成 WebSocket JSON?
- 一个 chat 为什么需要多个 stream ID?
- WebUI access mode 为什么不能绕过 Tool 安全边界?