Control UI(Canvas)

一句话概括

Control UI 是一个基于 Lit 的单页 Web 应用,通过单个 WebSocket 连接提供实时的操作面板,用于管理 agents、会话、通道、skills、cron 任务和聊天。

职责

  • 提供基于浏览器的控制面板,涵盖所有 Gateway 操作(聊天、配置、通道、agents、会话、用量、cron、skills、调试、日志)
  • 维护持久的 WebSocket 连接,使用 Ed25519 设备认证和自动重连
  • 渲染实时流式聊天,支持 Markdown、工具使用指示器和文件附件
  • 提供由服务端 Zod schema 驱动的动态配置编辑器
  • 支持国际化(en、pt-BR、zh-CN、zh-TW)和移动端响应式布局

架构图

关键源码文件

文件行数角色
ui/src/ui/app.ts616主组件<openclaw-app> Lit 元素,100+ 响应式状态属性,生命周期委托
ui/src/ui/app-render.ts1,141渲染逻辑renderApp() 根据标签页分发到对应视图函数
ui/src/ui/gateway.ts360Gateway 客户端GatewayBrowserClient — WebSocket、RPC、事件、Ed25519 设备认证、自动重连
ui/src/ui/app-gateway.ts350Gateway 集成:将 Gateway 事件桥接到应用状态更新
ui/src/ui/navigation.ts166客户端路由:History API,4 组 13 个标签页
ui/src/ui/types.ts641类型定义:UI 层所有接口
ui/src/ui/app-view-state.ts325状态接口AppViewState — UI 状态的完整形态
ui/src/ui/storage.ts92持久化:LocalStorage 设置存取包装器
ui/src/ui/views/chat.ts616聊天视图:流式消息、markdown 渲染、附件
ui/src/ui/views/config.ts820配置编辑器:从服务端 schema 动态生成的表单
ui/package.json27依赖:Lit 3.3、Vite 7.3、marked 17、DOMPurify 3.3
ui/vite.config.ts42构建配置:输出到 ../dist/control-ui/,开发服务器端口 5173

数据流

WebSocket 协议

所有通信使用单个 WebSocket 连接,包含三种消息类型:

客户端 → 服务端(请求):
  { type: "req", id: "uuid", method: "chat.send", params: {...} }

服务端 → 客户端(响应):
  { type: "res", id: "uuid", ok: true, payload: {...} }

服务端 → 客户端(事件):
  { type: "event", event: "chat", payload: { state: "delta", ... }, seq: 42 }

认证握手

1. 客户端打开 WebSocket 到 ws://host:18789
2. 服务端发送 "connect.challenge" 事件(含 nonce)
3. 客户端使用 Ed25519 设备密钥签名 nonce(Web Crypto API)
4. 客户端发送 "connect" RPC,包含:
   - role: "operator"
   - scopes: ["operator.admin", "operator.approvals", "operator.pairing"]
   - 设备身份(publicKey + 签名)
   - token(从上次连接保存)
5. 服务端响应 hello-ok + canvas 宿主 URL

关键 RPC 方法

方法用途
connect初始握手(设备认证)
chat.send发送用户消息
chat.history加载聊天记录
chat.abort取消进行中的 agent 轮次
config.get / config.save读取/写入配置
channels.status通道连接状态
agents.list列出已配置的 agents
sessions.list列出会话历史
usage.queryToken/成本分析
cron.jobs.list列出定时任务
skills.reportSkills 目录状态

事件类型(服务端 → 客户端)

事件用途
chat流式聊天增量、完成
agent工具使用、思考指示器
health系统健康更新
presence已连接的客户端
cron任务状态变更
tick周期性状态同步

页面结构

4 个标签组,共 13 个标签页:

分组标签页用途
聊天chat主对话界面
控制overviewchannelsinstancessessionsusagecron系统管理
AgentagentsskillsnodesAgent 配置
设置configdebuglogs配置和诊断

技术栈

组件技术版本
UI 框架Lit(Web Components)3.3.2
构建工具Vite7.3.1
Markdownmarked17.0.3
XSS 防护DOMPurify3.3.1
加密@noble/ed255193.0.0
测试Vitest + Playwright4.0.18
国际化自定义(4 种语言)-

状态管理

无外部状态库。使用 Lit 内置的响应式系统:

@state() 属性 → 修改 → 自动重渲染 → 视图更新
  • 100+ 响应式属性OpenClawApp 上 — 单一数据源
  • 视图是纯函数,接收 AppViewState 返回 TemplateResult
  • 控制器处理用户操作,调用 Gateway RPC,更新 @state() 属性
  • LocalStorage 持久化主题、选中标签页和设备设置
  • IndexedDB 存储 Ed25519 设备身份(跨会话持久)

重连策略

断开连接时:
  1. 等待 800ms(初始延迟)
  2. 尝试重连
  3. 失败时:delay *= 1.5(指数退避)
  4. 最大延迟:15,000ms(15 秒)
  5. 成功时:重置延迟为 800ms

事件序列号追踪:
  - 每个事件有单调递增的 seq 序号
  - 客户端检测间隙(断连期间遗漏的事件)
  - 检测到间隙 → 从服务端全量重新同步状态

与其他模块的关系

  • 依赖

    • gateway/ — WebSocket 服务器托管 Control UI 并处理所有 RPC 方法
    • Gateway 在端口 18789+1 上提供构建好的 dist/control-ui/ 静态文件
  • 被依赖

    • 无 — Control UI 是叶节点,纯粹作为客户端

构建与部署

开发环境:
  cd ui/ && npm run dev  →  Vite 开发服务器,端口 5173

生产环境:
  cd ui/ && npm run build  →  ../dist/control-ui/
  Gateway 在端口 18790 上提供 dist/control-ui/ 的静态文件

Gateway 自动发现并提供构建好的 Control UI — 无需单独部署。

我的认知盲区

  • 所有 13 个标签页的精确 WebSocket 消息类型 — 已记录主要的,但每个标签页可能有额外的 RPC 方法
  • config.ts(820 行)中的动态配置表单如何将服务端 Zod schema 映射为表单字段 — 需要追踪完整的 schema → 表单管线
  • 事件序列号间隙检测触发的是全量重载还是选择性重同步
  • exec-approval.ts 的工作方式 — 操作员审批工具执行的流程
  • Canvas A2UI 集成细节 — 基于 WebView 的 canvas 协议在 UI 侧如何工作

变更频率

  • app.ts:中 — 新功能上线时添加新状态属性
  • gateway.ts:低 — WebSocket 协议稳定
  • app-render.ts:中 — 布局变更和新标签页添加
  • views/:高 — 各视图随功能演进频繁变更
  • controllers/:高 — 业务逻辑与功能开发紧密相关