构建和开发
贡献者笔记:构建 Control UI 并将其运行到你选择的 Gateway。
本页面上的每条命令都从 openclaw/openclaw 仓库的检出目录运行,并且已经执行过 pnpm install。打包的 CLI 安装不包含 pnpm 脚本。
构建与开发 UI¶
Gateway 从 dist/control-ui 提供静态文件:
构建的性能报告将初始入口 JavaScript 预算与完整聊天和新会话启动总量分开。这些路由总量包括入口资源和该路由实测的即时动态导入,即使两个预加载列表都引用它,每个资源也只计算一次。因此,将现有启动导入移动到第一个请求波次中,在字节核算中仍然可见。初始入口上限和基线保持不变;路由总量按报告呈现,不引入更高限制。scripts/check-control-ui-performance.mts 上的 --base-dist 在两个构建都包含路由预加载模板时比较路由字节数和请求数。较旧的构建会将该比较报告为不可用,因此请使用冷加载网络捕获来比较它们的完整启动成本。
对于打包构建,Gateway 会保留经过清单验证的资源,以便已打开的标签页在更新后仍能获取旧的资源 URL。缓存最多提供三代资源,总计 96 MiB,并优先使用当前代;为了满足字节预算,较早的代可以更早被清理。后台启动准备通过发布和清理来复用已验证的清单,而不是在每一步重新读取未更改的保留资源。新发布的资源在复用前会经过验证,包括并发发布者中获胜的副本。每个清理器在删除旧目录之前会先声明该目录,因此并发发布者不会删除同一棵树。清理失败会记录警告,并可能暂时在磁盘上留下多余文件,但不会丢弃已成功发布的代。后续准备可以在一小时后回收被遗弃的暂存目录。配置了 gateway.controlUi.root 的构建不使用此缓存。
打包的公共资产(主题、字体、图标和美术资源)使用 ?v=<build-id> URL,并带有一年不可变的 HTTP 缓存。该 ID 包含公共文件的摘要,因此在同一提交上重新构建已更改的文件也会改变它们的 URL。Gateway 在启动时对该身份进行快照;在就地安装重新构建后重启它。无版本请求、过期 ID、文档、sw.js 以及自定义 gateway.controlUi.root 安装保持 Cache-Control: no-cache。Service Worker 对公共资产保持网络优先策略,使浏览器的 HTTP 缓存能够满足匹配的带版本请求。
Gateway 在浏览器之间共享已准备好的打包资产字节,包括 Brotli 和 gzip 变体。冷文件准入和读取在 worker 中运行,因此同时的页面加载不会阻塞聊天投递。自定义根目录继续在每个请求时读取当前文件。
非索引静态资产使用 Last-Modified 进行条件 GET 和 HEAD 请求。If-None-Match 优先于 If-Modified-Since:* 匹配现有资产,而其他值会收到正常的 200 响应,因为静态资产不会发出 ETags。仅基于日期的重新验证仍会为未更改的资产返回 304。如果没有可用的内容编码可接受,Gateway 会在评估任一条件之前返回 406。
所有三种 HTTP-date 格式都按 UTC 解释。无效或重复的 If-Modified-Since 字段会被忽略,因此它们无法抑制当前资产字节。闰秒验证器仍早于下一秒。
静态资产 URL 支持百分号编码的文件名。包含的符号链接保留所请求资产的 MIME 类型,符号链接的 index.html 会获得与其他入口路由相同的 base-path 和文档准备。
可选的绝对基础路径(固定资产 URL):
本地开发(独立开发服务器):
然后将 UI 指向你的 Gateway WS URL(例如 ws://127.0.0.1:18789)。
要在开发时使用真实 Gateway 的功能、媒体和原生插件 UI,请在启动 Vite 时选择其 URL:
配置的开发默认绑定到 loopback。打开 http://127.0.0.1:5173,并使用 Gateway 的正常身份验证和设备配对。目标接受 http://、https://、ws:// 或 wss://,包括已配置的 Gateway 基础路径。不要在此设置中保留凭据。需要时,将确切的浏览器 Origin 添加到 gateway.controlUi.allowedOrigins;开发代理会保留浏览器的 Origin,并且不提供身份验证凭据。
Vite 提供 UI 及其热重载连接,并代理 Gateway 请求。UI 将凭据和已保存的设置限定在实际 Gateway 范围内。所有者配对交接可以在开发页面上交付,同时保留其原始 Gateway 目标和片段。要使用另一个 Gateway,请使用新目标重启 Vite;已打开的页面无法通过替换目标转发请求。重启 Vite 后重新加载页面。
UI 编辑使用 Vite 的正常热重载或页面重载。在开发 Gateway 实现时,请单独启动 Gateway 的源代码监视。停止 Vite 会停止其代理,而不会停止 Gateway 或删除任一应用的状态。如果没有 OPENCLAW_UI_DEV_GATEWAY_URL,pnpm ui:dev 会保留其现有的独立连接行为。此设置不影响 pnpm ui:build。
要使用合成数据进行独立预览,请使用:
在新的 Chromium 配置文件或隔离的浏览器上下文中打开打印出的 URL,不要使用现有的 Service Worker 或操作员凭据。聊天、在线状态和个人资料数据都是合成的。添加 --fixture attachments 以获取媒体示例;打印出的 board fixture URL 也可用。
模拟预览在应用启动前为 Gateway 资源(包括头像)选择自己的源。它提供合成的 WebSocket 响应,并将原生资源请求限制在提供源以及本地 data/blob 测试数据中(包括帧),同时保留同源 Vite HMR 和终端 WebAssembly。未实现的 HTTP API 路由返回本地 JSON 404;外部 fetch 请求会被拒绝,并给出独立模拟诊断。在模拟应用中,新的 workers、Talk WebRTC、弹出窗口以及外部链接/导航操作均被禁用。外部 iframe URL 赋值会在 Chromium 能够推测性连接之前被拒绝。当演示需要另一个响应时,添加一个本地测试数据。每次调用拥有独立的 Vite 缓存,并在优雅关闭时删除它,因此并发预览和附件测试数据不会使彼此失效。
这是一个受信任测试数据的开发边界,而不是用于恶意 HTML、浏览器扩展或已经控制页面的 service worker 的沙箱。应用之外的浏览器级导航不在其控制范围内。生产连接设置和 pnpm ui:dev 行为保持不变;当你确实需要真实 Gateway 或外部集成时,请使用该命令。
聊天输入所有权¶
ChatOutboxGatewayOwner 负责排队输入的准入、更新、移除以及对应窗格投影。单行变更和重新排序共享一个持久化 compare-and-set 操作;队列调用方不会发布单独的存储和显示更新。命令完成时使用 composer 恢复所有者来保留或释放草稿附件。投递会等待完整的设置更新链,然后同步继续准入,使得另一个选择器更新无法在结算和传输之间进入。
聊天渲染调度¶
流式增量和会话名册通知不应触发无关渲染。外壳直接处理会话删除和文档标题更新,而不为名册发布进行渲染。聊天流拥有自己的帧队列;聊天页面的会话订阅通过 SubscriptionsController 合并显示更新。状态同步保持即时,而 Lit 提交在计划帧内运行,因此子属性绑定不会逃逸到更晚的微任务中。断开或替换订阅会使其排队的帧退役。隐藏文档保留即时失效,因为动画帧可能被挂起。
chat-stream-runtime-budgets.e2e.test.ts 测试套件使用结构化更新计数保护流式处理;聊天页面单元测试覆盖其间发生的名册发布。
外壳回调在多次渲染之间保留其身份,因此后台会话更新不会两次重绘导航。发件箱订阅仍会在其底层事实变化时使草稿和关注徽章失效。会话链接装饰保留未变化的属性,而不是在每次名册更新时重写它们。
转录增强会检查插入或变更的 Markdown 块;已稳定的代码块和表格在相邻文本流式处理时不需要再次扫描。Resize 观察者负责几何变化。命令面板同样在导航结果时保留其已测量的输入布局,并重新测量编辑、宽度变化和重新连接的字段。状态时钟在隐藏标签页中暂停,并且仅在其显示值或属性变化时渲染。
首个已连接、已呈现的转录提交会在每个视口边缘之外渲染两行相邻行。下一个动画帧恢复六行滚动缓冲区;待处理的导航、焦点或锚点协调会立即使用该完整缓冲区,包括折叠工作标题等相邻控件。聚焦行和锚定行会独立保留。链接预览发现会等待浏览器空闲时间,然后跟随转录变更,而不是在每次窗格更新时重新扫描未变化的内容。隐藏或退役窗格会取消待处理的发现。
流式 Markdown 在一个有界缓存中保留规范化输入、拆分进度和已渲染前缀。已完成的独立块只渲染一次;替换、区域设置或显示选项变化,以及整个文档范围的 Markdown 依赖会使该复用失效。列表、引用定义、容器、原始 HTML 和冲突的文件标签保留其整块或整个前缀语义以及现有解析限制。
Composer 编辑仅在视口高度或校正后的滚动偏移变化时发布转录大小调整通知。草稿增长、缩小和末尾锚定仍然立即同步。位置栏观察列宽和会话区域高度,而不是在每次流式渲染时测量槽位;虚拟化器和侧边栏几何变化保留其显式同步路径。栏标签在已挂载标记之间共享,因此屏幕外历史不会在每次流更新时增加转换工作。
侧边栏旁白在隐藏时释放其会话兴趣。获取和释放共享有界指数重试退避,具有完全抖动和服务器延迟提示。失败的释放保留其原始句柄,包括迟到的隐藏获取;返回的兴趣会取消排队的释放,并在退役这些句柄前重新获取。共享连接协调器在不确定取消订阅超时后更新观察者,并保留其他查看者的租约和审批投递。DOM 分离会暂停计时器,而不会丢弃已保留的句柄;连接关闭会退役它们并取消重试。
Talk 实时冒烟测试¶
维护者可以从仓库根目录端到端地验证浏览器 Talk 路径。将每个占位符替换为真实密钥:
OPENAI_API_KEY=<openai-key> GEMINI_API_KEY=<gemini-key> \
node --import tsx scripts/dev/realtime-talk-live-smoke.ts
该运行会验证 OpenAI 后端 WebSocket 桥接、合成的 PCM24 语音到响应音频往返、OpenAI 浏览器 WebRTC SDP 交换、Google Live 受限令牌浏览器设置(包含一个 JPEG 帧和 describe_view 函数往返),以及带有假麦克风媒体的 Gateway 中继浏览器适配器。传入 --openai-audio-cycles 3 可执行一次简短的重复 OpenAI 连接、回话和关闭浸泡测试。该命令仅打印提供商状态,并且不会记录密钥。
调试/测试:开发服务器 + 远程 Gateway¶
Control UI 是静态文件;WebSocket 目标可配置,并且可以与 HTTP 源不同。当你希望本地使用 Vite 开发服务器,而 Gateway 运行在其他位置时,这非常方便。
1. 启动 UI 开发服务器
2. 连接远程 Gateway
按照 远程 Gateway URL 交接 参考文档,获取编码后的 Gateway URL 和可选的一次性凭据。
源安全性说明
- 公共非回环 Control UI 部署必须显式设置
gateway.controlUi.allowedOrigins(完整源)。来自回环、RFC1918/链路本地、.local、.ts.net或 Tailscale CGNAT 主机的私有同源 LAN/Tailnet 加载,无需启用 Host 头回退即可被接受。 - Gateway 启动时可能会根据有效运行时绑定和端口,预置本地源,例如
http://localhost:<port>和http://127.0.0.1:<port>,但远程浏览器源仍需要显式条目。 - 除非在严格受控的本地测试中,否则不要使用
gateway.controlUi.allowedOrigins: ["*"];它表示允许任意浏览器源,而不是“匹配我正在使用的任意主机”。 gateway.controlUi.dangerouslyAllowHostHeaderOriginFallback=true会启用 Host 头源回退模式,但这是一个危险的安全模式。
远程访问设置详情:远程访问。
本页原文 Markdown:在 AtomGit 查看·内容源自开源项目 cl/openclaw