摘要
在 OpenClaw 的二次开发中,官方推荐的 Channel 扩展模式往往伴随着较高的开发和部署成本。本文介绍了一种更直接的“降维打击”方案:通过逆向工程解析 Gateway 与 WebChat 之间的 WebSocket 通信协议,构建一个通用适配器(Universal Adapter)。该适配器能将任何外部程序(CLI、脚本、第三方 UI)伪装成官方 WebChat 客户端,从而实现零后端修改接入,并天然支持会话历史同步。
正文内容
1. 缘起:为什么我们需要这层“胶水”?
在 OpenClaw 的生态中,如果你想让一个外部系统(比如一个 Python 脚本、一个 IoT 设备或者一个自定义网页)和 Agent 对话,官方的标准答案通常是:“去开发一个 Custom Channel 吧。”
但在实际工程中,开发 Channel 存在明显的痛点:
工程师的思维是懒惰的,也是敏锐的。 既然官方自带的 WebChat 可以完美地和 Gateway 通信,且每个 OpenClaw 实例都默认支持,那为什么不直接复用这条通道呢?
只要我们能通过代码完美模拟 WebChat 的握手和通信协议,我们就拥有了一个**“万能胶水”**——无需修改服务端一行代码,就能把任何项目“粘”到 OpenClaw 上。
2. 核心原理:协议逆向与伪装
本方案的核心不在于“对接接口”,而在于**“行为模拟”**。
2.1 架构对比
- 传统 Channel 模式:需在 Gateway 侧开发插件,通过特定的 API 进行转换。
- 本方案(胶水模式):适配器(Adapter)运行在客户端侧,它在网络层面上完全伪装成了浏览器。Gateway 根本不知道对面是一个 Python 脚本还是 Chrome 浏览器,因此所有的鉴权、流式输出、历史记录保存机制都天然生效。
2.2 协议交互时序
通过抓包分析(Wireshark/DevTools),我们还原了 OpenClaw Gateway 的 WebSocket 握手协议,并将其封装在 SDK 中:
OpenClaw Gateway
胶水适配器 (Python SDK)
任意客户端 (CLI/App)
OpenClaw Gateway
胶水适配器 (Python SDK)
任意客户端 (CLI/App)
#mermaid-svg-nIJ48GFniZ7SkEif{font-family:\”trebuchet ms\”,verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-nIJ48GFniZ7SkEif .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-nIJ48GFniZ7SkEif .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-nIJ48GFniZ7SkEif .error-icon{fill:#552222;}#mermaid-svg-nIJ48GFniZ7SkEif .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-nIJ48GFniZ7SkEif .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-nIJ48GFniZ7SkEif .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-nIJ48GFniZ7SkEif .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-nIJ48GFniZ7SkEif .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-nIJ48GFniZ7SkEif .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-nIJ48GFniZ7SkEif .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-nIJ48GFniZ7SkEif .marker{fill:#333333;stroke:#333333;}#mermaid-svg-nIJ48GFniZ7SkEif .marker.cross{stroke:#333333;}#mermaid-svg-nIJ48GFniZ7SkEif svg{font-family:\”trebuchet ms\”,verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-nIJ48GFniZ7SkEif p{margin:0;}#mermaid-svg-nIJ48GFniZ7SkEif .actor{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:#ECECFF;}#mermaid-svg-nIJ48GFniZ7SkEif text.actor>tspan{fill:black;stroke:none;}#mermaid-svg-nIJ48GFniZ7SkEif .actor-line{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);}#mermaid-svg-nIJ48GFniZ7SkEif .innerArc{stroke-width:1.5;stroke-dasharray:none;}#mermaid-svg-nIJ48GFniZ7SkEif .messageLine0{stroke-width:1.5;stroke-dasharray:none;stroke:#333;}#mermaid-svg-nIJ48GFniZ7SkEif .messageLine1{stroke-width:1.5;stroke-dasharray:2,2;stroke:#333;}#mermaid-svg-nIJ48GFniZ7SkEif #arrowhead path{fill:#333;stroke:#333;}#mermaid-svg-nIJ48GFniZ7SkEif .sequenceNumber{fill:white;}#mermaid-svg-nIJ48GFniZ7SkEif #sequencenumber{fill:#333;}#mermaid-svg-nIJ48GFniZ7SkEif #crosshead path{fill:#333;stroke:#333;}#mermaid-svg-nIJ48GFniZ7SkEif .messageText{fill:#333;stroke:none;}#mermaid-svg-nIJ48GFniZ7SkEif .labelBox{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:#ECECFF;}#mermaid-svg-nIJ48GFniZ7SkEif .labelText,#mermaid-svg-nIJ48GFniZ7SkEif .labelText>tspan{fill:black;stroke:none;}#mermaid-svg-nIJ48GFniZ7SkEif .loopText,#mermaid-svg-nIJ48GFniZ7SkEif .loopText>tspan{fill:black;stroke:none;}#mermaid-svg-nIJ48GFniZ7SkEif .loopLine{stroke-width:2px;stroke-dasharray:2,2;stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);}#mermaid-svg-nIJ48GFniZ7SkEif .note{stroke:#aaaa33;fill:#fff5ad;}#mermaid-svg-nIJ48GFniZ7SkEif .noteText,#mermaid-svg-nIJ48GFniZ7SkEif .noteText>tspan{fill:black;stroke:none;}#mermaid-svg-nIJ48GFniZ7SkEif .activation0{fill:#f4f4f4;stroke:#666;}#mermaid-svg-nIJ48GFniZ7SkEif .activation1{fill:#f4f4f4;stroke:#666;}#mermaid-svg-nIJ48GFniZ7SkEif .activation2{fill:#f4f4f4;stroke:#666;}#mermaid-svg-nIJ48GFniZ7SkEif .actorPopupMenu{position:absolute;}#mermaid-svg-nIJ48GFniZ7SkEif .actorPopupMenuPanel{position:absolute;fill:#ECECFF;box-shadow:0px 8px 16px 0px rgba(0,0,0,0.2);filter:drop-shadow(3px 5px 2px rgb(0 0 0 / 0.4));}#mermaid-svg-nIJ48GFniZ7SkEif .actor-man line{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:#ECECFF;}#mermaid-svg-nIJ48GFniZ7SkEif .actor-man circle,#mermaid-svg-nIJ48GFniZ7SkEif line{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:#ECECFF;stroke-width:2px;}#mermaid-svg-nIJ48GFniZ7SkEif :root{–mermaid-font-family:\”trebuchet ms\”,verdana,arial,sans-serif;}
1. 身份伪装 (Handshake)
此时 Gateway 认为有一个"Web用户"上线了
2. 消息透传 (Streaming)
loop
[流式响应]
历史记录自动存入数据库
create_connected_from_env()
读取 Token/AgentID
WebSocket Connect (Headers: Origin, Auth…)
101 Switching Protocols
stream_chat("你好,Agent")
发送 JSON 协议帧 (Type: Chat)
WebSocket Frame (Chunk)
解析协议包,提取 Content
yield 纯文本片段
3. 实战:三步实现“零侵入”接入
为了让这层“胶水”真正通用,我将其封装为 openclaw-webchat-adapter,屏蔽了底层复杂的协议帧处理。
3.1 安装与配置
这一步体现了“胶水”的特性:即插即用。
pip install openclaw-webchat-adapter
创建 .env 文件,填入你的 OpenClaw 服务地址。因为我们是模拟 WebChat,所以需要的配置和浏览器里看到的一模一样:
# .env 配置示例
OPENCLAW_GATEWAY_URL=ws://127.0.0.1:8080/socket # Gateway 的 WebSocket 地址
OPENCLAW_GATEWAY_TOKEN=eyJhbGciOiJIUz… # 你的用户 Token
OPENCLAW_SESSION_KEY=12345-abcde… # 当前连接的会话id
3.2 极简代码示例
以下是一个最小化的实现。你可以把它看作是一个 Headless WebChat。
"""
OpenClaw 通用适配器示例
目标:将终端 (Console) 变成 OpenClaw 的聊天窗口
"""
import sys
# 引入我们的"胶水"适配器
from openclaw_webchat_adapter.ws_adapter import OpenClawChatWsAdapter as adapter
def main() –> int:
# 1. 一键连接:自动读取环境变量,完成复杂的握手协议
print("正在连接 OpenClaw Gateway…")
try:
connect = adapter.create_connected_from_env()
print("✅ 连接成功!(伪装 WebChat 模式)")
except Exception as e:
print(f"❌ 连接失败: {e}")
return 1
# 2. 交互循环
try:
while True:
# 获取用户输入
query = input("\\n[我] > ").strip()
if not query: continue
if query.lower() in ("/exit", "/quit"): break
print("[Agent] > ", end="")
# 3. 流式透传:SDK 帮你处理了所有分包逻辑
# 这里返回的 chunk 已经是清洗过的纯文本
for chunk in connect.stream_chat(query):
print(chunk, end="", flush=True)
print("") # 换行
except KeyboardInterrupt:
print("\\n再见!")
finally:
# 释放连接资源
connect.stop()
return 0
if __name__ == "__main__":
sys.exit(main())
这里我成功的接入了自己的ai聊天平台项目中,并且还加入再了虾聊ai聊天社区中
去
4. 总结与思考
4.1 方案价值:通用性与一致性
- 无缝嵌入:因为接口极其简单(stream_chat),你可以把它嵌入到 Django/FastAPI 后端、Qt 桌面应用、甚至是树莓派的自动化脚本中。
- 历史漫游:这是一个巨大的隐形优势。因为 Gateway 认为你是 WebChat 用户,所以你在 CLI 里的所有聊天记录,打开浏览器登录 OpenClaw 官网时,全部都在。这对于调试 Agent Prompt 或回溯对话非常有用。
4.2 局限性
- 依赖稳定性:这本质上是一种 Protocol Reverse Engineering。如果 OpenClaw 官方大幅修改了 WebSocket 的通信 Payload 结构,适配器代码需要随之更新(虽然 WebChat 协议通常为了兼容性会保持稳定)。
- 并发模型:Python 的 WebSocket 依赖 threading 或 asyncio,在高并发场景下作为中间件转发时,需要注意连接池的管理。
5. 项目资源
- Gitee: https://gitee.com/ha-big-brother/openclaw-webchat-adapter
- GitHub: tangdeyx2333-beep/openclaw-webchat-adapter
- 协议细节: 详见仓库 README2 README3 文档
6. 维护
我会一直维护这个包,预计12号之前还还能实现支持获取会话聊天消息的接口





