跳转至

WebChat

状态:macOS/iOS SwiftUI 聊天 UI 直接连接到 Gateway WebSocket。没有嵌入式浏览器,也没有本地静态服务器。

它是什么

  • 为 Gateway 提供的原生聊天 UI。
  • 使用与其他通道相同的会话和路由规则。
  • 确定性路由:回复始终返回 WebChat。
  • 历史记录始终从 Gateway 获取(不监视本地文件)。如果 Gateway 不可达,WebChat 为只读。

快速开始

  1. 启动 Gateway。
  2. 打开 WebChat UI(macOS/iOS 应用)或 Control UI 的聊天选项卡。
  3. 确保已配置有效的 Gateway 认证路径(默认使用共享密钥,即使在回环地址上也是如此)。参见 认证基础(本地与远程)。

工作原理

  • UI 连接到 Gateway WebSocket,并使用 chat.history、chat.send、chat.inject 和 chat.message.get RPC 方法。
  • Control UI 通过 chat.startup 打开一个带有少量最近历史记录页面的聊天。短聊天链接会在同一请求中解析其会话。短引用启动还会在读取历史记录之前让连接订阅已授权的会话事件,因此在聊天窗格挂载前到达的更新不会丢失。向上滚动以加载更早的消息。恢复的 Home 窗格会等待所选聊天加载完成,而显式打开 Home 会立即加载。
  • chat.history 为保证稳定性设有边界:Gateway 可能会截断较长的文本字段、省略重量级元数据,并用 [chat.history omitted: message too large] 替换超大条目。历史记录页面会跳过隐藏和仅工具相关的转录条目,同时从现有索引转录中填充所请求的可见消息窗口。API 客户端可以发送按请求的 maxChars 来为单次调用覆盖默认限制。
  • 分页的 chat.history 和 chat.startup 请求还接受 maxBytes 页面目标,上限为 Gateway 的响应限制。Control UI 会保持初始尾部较小,然后使用 Gateway 的响应限制请求最多 1,000 条更早的消息,以减少重复读取和回滚等待。一条可读消息可能超过目标值,因此小页面不会隐藏其内容。完整导入的快照保留其现有预算,因为它们不支持回滚分页。
  • 如果在读取页面期间历史记录发生变化,Gateway 会从当前历史记录返回一个一致的页面。windowReset: true 响应包含一个替换尾部;客户端替换其已加载的历史记录,并使用其 deltaCursor 和分页字段继续。过期的 delta 游标会返回 { kind: "reset" },提示发起新的尾部请求。与投影重建竞争的读取会短暂等待,然后返回可重试的重建错误。
  • 当某个可见的助手消息在 chat.history 中被截断时,Control UI 会通过 chat.message.get 自动获取完整的显示规范化条目。该获取不会增加默认历史记录负载。加载期间预览保持可见。恢复的内容会内联替换它。chat.message.get 使用与 chat.history 相同的转录分支和显示规则。与 chat.history 不同,它通过 messageId 定位单个条目。当完整内容无法再返回时,它会返回一个如实的不可用原因。
  • 对于仅追加的会话文件,chat.history 遵循活动转录分支,因此被放弃的重写分支和被取代的提示副本不会在 WebChat 中渲染。
  • 压缩条目会渲染为历史记录分隔线,在 token 测量可用时显示上下文缩减。普通对话历史和 Fork 仍然可用;压缩不会创建单独的检查点浏览或恢复界面。
  • Control UI 会记住 chat.history 返回的底层 Gateway sessionId。它会在后续 chat.send 调用中包含该 id。因此,重连和页面刷新会继续同一已存储的对话,除非用户开始或重置会话。
  • 前台发送还会包含从渲染历史记录中显示的分支叶节点作为 expectedLeafEntryId。如果另一个客户端先切换了分支,Control UI 会暂存该消息以供审查,并刷新转录,而不是将其发布到新分支。重连和恢复的发件箱重放会在协调当前历史记录后有意省略此前提条件。
  • 当你更改聊天设置并立即发送时,Control UI 会显示 正在应用聊天设置,直到该更改及其会话刷新完成。之后的后台会话刷新不会延长此等待。在不更改设置的情况下打开窗格不会创建设置等待。
  • chat.send 接受一个幂等键(Control UI 使用运行 id)。Gateway 会对重用相同键的重复请求进行去重。当会话、消息、附件和提及选择匹配时,重试或重复的在途提交不会创建第二个运行。使用不同的提及选择重用该键会被拒绝。
  • 排队消息即使执行在不同的运行 id 下开始,也会保留其原始发送身份。客户端通过该发送身份将本地待处理消息与其已保存的转录条目进行协调,因此完成和历史记录重新加载只显示一份副本。
  • 在重连期间,已尝试但投递不确定的消息会保留在转录中,并带有 等待重连 状态。尚未尝试的消息保留在编辑器队列中。Gateway 已为后续轮次排队的消息也会显示在编辑器上方,直到被消费或取消,而不会产生重复的转录气泡或另一次发送。其移除操作会取消 Gateway 上确切排队的消息。一旦移除得到确认,该提示及其附件就会从队列和对话中消失,包括在重连或重新加载之后。停止、超时和发送失败取消会保留其恢复消息可见。重新排序仅适用于仍由浏览器拥有的消息。对不确定的本地消息执行 丢弃 只会删除其浏览器副本;它不会取消 Gateway 已接收的消息。
  • 回复特定消息(右键 → 回复)会在 chat.send 上以 replyToId 发送目标的转录 id。对于具有可见文本的来源,Gateway 会从会话历史记录中解析该消息。它会填充 Discord 回复所使用的相同通道无关回复上下文元数据。代理会看到 has_reply_context 以及带有发件人标签和正文的不可信 "Reply target of current user message" 块。根据直接 webchat 会话现有的字节稳定提示策略,Webchat 提示会保持易变的对话 id(例如 reply_to_id)被抑制。没有持久化转录 id 的回复目标(例如待发送消息)会回退为消息正文中的内联引用。
  • 仅附件消息保留其回复操作。预览使用文件名或图像标签,并且 replyToId 仍指向原始条目。回复不会将源文件附加到新消息。
  • 工作区启动文件和待处理的 BOOTSTRAP.md 指令通过代理系统提示的 # Project Context 部分提供,而不是复制到 WebChat 用户消息中。如果 bootstrap 内容被截断,系统提示会改为获得一条简短的 "Bootstrap Context Notice"。详细计数和配置项保留在诊断界面上。
  • chat.history 上的显示规范化会剥离:仅限运行时的 OpenClaw 上下文、入站信封包装器、内联投递指令标签(例如 [[reply_to_current]]、[[reply_to:<id>]] 和 [[audio_as_voice]])、纯文本工具调用 XML 负载(<tool_call>、<function_call>、<tool_calls>、<function_calls>,包括截断块),以及泄漏的 ASCII/全角模型控制 token。移除模型控制 token 会保留标点符号和 Markdown 格式,保持相邻单词分隔,并保留代码示例完整。整个可见文本仅为静默 token NO_REPLY(不区分大小写)的助手条目会被省略。
  • 当回复附件无法读取或准备时,WebChat 会保留任何可交付的附件。它会显示一条简短的失败警告,而不暴露本地文件系统路径。
  • 在 Control UI 聊天和新会话编辑器中,超大的静态 PNG 上传会在发送前在浏览器工作线程中调整大小,使图像解码和编码远离编辑器主线程。这适用于拖放、文件选择器和图像粘贴。已在 Gateway 通告限制内的图像保持不变;调整大小后的图像保留其宽高比和 PNG 透明度。浏览器调整大小限制为 2500 万像素的源数据。动画 PNG 不会被压平,其他文件类型保留其大小限制。如果图像无法解码或调整大小,其附件会显示读取失败,而不是发送超大字节。
  • 在 Control UI 中,助手图像和附件按消息顺序出现,位于其周围段落之间。前/后标签保留在对应图像旁边。
  • 当前 WebChat 回复拥有的附件指令在文件准备期间会在实时转录事件中保持隐藏。用户提示、围栏示例以及该回复附件管道之外的引用保持不变。
  • WebChat 会从助手内容、转录重放文本和音频内容块中排除标记为推理的回复负载(isReasoning: true)。因此,仅思考的负载不会显示为可见的助手消息或可播放音频。
  • chat.inject 直接将助手注释追加到转录中,并将其广播到 UI(不运行代理)。
  • 已中止的运行可以在 UI 中保持部分助手输出可见。当存在缓冲输出时,Gateway 会将该部分文本持久化到转录历史中,并用中止元数据标记该条目。

会话记录与投递模型

准入与会话记录持久化是相互独立的。一个 chat.send、sessions.send 或初始 sessions.create 确认可能在已批准输入等待于持久化待处理输入托管中时到达,包括在工作区准备期间。 可选的 messageSeq 仅来自已提交的会话记录回执。客户端 不得根据历史长度预测它,也不得将 status: "started" 视为持久化。 Control UI 会用已接受的托管替换其临时来源,然后再用 规范行替换。已接受的输入在提交到会话记录之前,会保持在已保存的对话历史之下。 其渲染器在此交接期间会在同一图像元素中保留已加载的本地预览,同时规范媒体元数据和图像字节正在加载。 权威文本、媒体替换和移除仍然优先。不可用或 访问被拒绝的媒体会显示可见原因。 一旦托管、消费记录或已提交的用户消息回执使本地来源失效,重放的终结事件就无法将其恢复,即使其行 在后续历史页面中缺失。提交身份与执行运行保持分离,因此两次有意相同的发送仍然是两个输入。

WebChat 有两条独立的数据路径:

  • SQLite 会话记录行是持久的模型/运行时会话记录。对于正常代理运行,嵌入式 OpenClaw 运行时通过会话访问器持久化模型可见的 user、assistant 和 toolResult 消息。WebChat 不会将任意投递、状态或辅助文本写入该会话记录。
  • Gateway ReplyPayload 事件是实时投递投影:为 WebChat/频道显示、块流式传输、指令标签、媒体嵌入、TTS/音频标志和 UI 回退行为进行规范化。它们本身不是规范会话日志。
  • 需要通过 tools.message 产生可见回复的框架仍使用 WebChat 作为当前运行的内部来源回复接收器。来自该活动 WebChat 运行的无目标 message.send 会被投影到同一聊天中,并镜像到会话记录。WebChat 不会成为可重用的出站频道,也永远不会继承 lastChannel。
  • WebChat 仅在 Gateway 拥有正常嵌入式代理轮次之外的显示消息时注入助手会话记录条目。这些情况包括 chat.inject、非代理命令回复、中止的部分输出以及 WebChat 管理的媒体会话记录补充。
  • 如果实时助手文本在运行期间出现,但在历史重新加载后消失,请按顺序检查三件事。第一,SQLite 会话记录是否包含该助手文本。第二,chat.history 显示投影是否将其剥离。第三,Control UI 乐观尾部合并是否用持久化快照替换了本地投递状态。

正常代理运行的最终答案应当是持久的,因为嵌入式运行时会写入助手的 message_end。任何将已投递的最终负载镜像到会话记录的回退机制,都必须首先避免重复嵌入式运行时已经写入的助手轮次。

人类提及投递

Control UI 将每个选定人员绑定到提交的消息文本。chat.send、sessions.create 上的初始消息以及 sessions.send 接受一个可选的 mentions 数组,包含 { profileId, start, end } 注释。最多有十个注释。start 包含边界,end 不包含边界,以 UTF-16 码元为单位测量。Gateway 在接受输入之前会验证它们的文本范围和接收者。普通 @name 文本和代理输出不会创建人类提及。复制或引用文本不会复制其接收者选择。

只有当原始人类消息被新提交到会话记录后,该提及才有资格获得收件箱条目和可选的浏览器推送。早期的 status: "started" 确认、暂存的初始消息或持久化待处理输入托管都不是该提交。因此,排队或远程放置的首条消息在仍等待记录时不会通知。后续代理失败不会撤销其人类消息已经提交的提及。

传输重试会保留精确提交的文本、提及注释和原始发送身份,即使执行获得不同的运行 id。重放该已提交的输入不会创建另一个提及或恢复已关闭的条目。有意的新发送具有新身份,即使其文本相同,也可能再次通知。会话记录加载、隐藏续接和附件增强不是新的人类发送。

这些辅助 RPC 要求 operator.read,并使用已认证的个人资料,绝不使用调用者选择的收件箱所有者:

方法或事件 契约
users.mentionable 使用 { sessionKey, agentId?, query? } 搜索,或在创建会话前使用 { agentId, visibility?, query? }。返回有界的 { users, truncated } 结果,包含个人资料 id、显示名称、可选头像 URL 和在线状态。
mentions.list 调用 {} 以获取你当前的 { gatewayInstanceId, revision, items } 快照。
mentions.dismiss 传入 { ids },其中最多包含 100 个不同的可见条目 id。关闭后返回相同形状的快照。
方法或事件 契约
mentions.changed 针对 { gatewayInstanceId, revision } 的失效。重新获取 mentions.list。修订号描述该连接被授权查看的内容,而不是全局 Gateway 活动。

投递为尽力而为。收件箱和重放记录在 Gateway 重启后仍会保留,但受其保留期限限制。容量限制仍可能跳过告警。重启或重新连接不会重新发送旧的浏览器通知,也不会恢复已忽略的条目。通知失败不会重试,也不会撤销已发布的聊天消息。浏览器推送不提供恰好一次投递。参见 收件箱保留 和 通知偏好设置。

Control UI 代理工具面板

  • Control UI 的 /agents 工具面板包含一个“工具预览”,由 tools.effective(sessionKey=...) 提供支持。它使用会话的已保存设置,预览核心、插件、频道拥有的以及已发现的 MCP 服务器工具。它不会报告活动运行的确切工具。
  • 一个独立的配置编辑视图(由 tools.catalog 提供支持)涵盖配置档案、按代理覆盖和目录语义。
  • 预览的作用域为会话。在同一代理上切换会话可能会改变它,重置会话会刷新它。未保存的配置编辑不会反映在预览中,已保存或运行时更改可能需要一段时间才会出现。运行专属工具可能在运行开始时变为可用;预览中缺失的工具不一定被禁用。运行仍会应用其当前策略、凭据和执行检查。
  • 当已配置的 MCP 服务器自上次发现以来未连接或发生变化时,面板会显示通知。它不会从读取路径静默启动 MCP 传输。加载中和失败的预览并不意味着会话没有任何工具。
  • 配置编辑器并不意味着运行时可用。有效访问仍遵循策略优先级(allow/deny、按代理以及提供商/频道覆盖)。

远程使用

  • 远程模式通过 SSH/Tailscale 隧道传输 gateway WebSocket。
  • 你无需运行单独的 WebChat 服务器。

配置参考(WebChat)

完整配置:配置

WebChat 没有持久化配置部分。Gateway 使用内置的 chat.history 显示限制。API 客户端可以按请求发送 maxChars,以在单次调用中覆盖它。旧版 channels.webchat 和 gateway.webchat 配置已弃用。运行 openclaw doctor --fix 将其移除。

相关全局选项:

  • gateway.port、gateway.bind:WebSocket 主机/端口。
  • gateway.auth.mode、gateway.auth.token、gateway.auth.password: 共享密钥 WebSocket 认证。
  • gateway.auth.allowTailscale:启用时,浏览器 Control UI 聊天标签页可以使用 Tailscale Serve 身份请求头。
  • gateway.auth.mode: "trusted-proxy":用于位于身份感知 非回环 代理源后面的浏览器客户端的反向代理认证(参见 可信代理认证)。
  • gateway.remote.url、gateway.remote.token、gateway.remote.password:远程 gateway 目标。
  • session.*:会话路由和存储。

本页原文 Markdown:在 AtomGit 查看·内容源自开源项目 cl/openclaw