乐于分享
好东西不私藏

OpenClaw远程管理终极篇:WebSocket协议+150个RPC一次讲透,全栈实战!

OpenClaw远程管理终极篇:WebSocket协议+150个RPC一次讲透,全栈实战!

WEBSOCKET    JSON-RPC DOCUMENTATION

王炸技术篇来了!

OpenClaw的CLI是一次性的刀——建连、调用、断开,干完就走。Admin HTTP RPC更简单,一个POST请求搞定。但有些场景只靠这两个不够:实时事件推送、双向通信、前端Web页面管理——这些只有WebSocket能做到。

WebSocket JSON-RPC是OpenClaw的唯一控制面传输协议。所有客户端(CLI、Web UI、macOS/iOS/Android App、Node Host)的底层全部走的WebSocket。CLI只是把它封装成了一次性命令,Admin HTTP RPC只是把其中约50个方法暴露成了HTTP接口。你直接对接WebSocket,就是在用OpenClaw最底层、最完整的能力。

这篇把WebSocket JSON-RPC的完整协议拆解出来:握手流程、帧格式、认证体系、150+个RPC方法清单、25+个实时事件、四种语言SDK封装、以及9个实机验证的踩坑记录。读完这篇加上前两篇,你可以用任何语言写一个完整的OpenClaw管理前端。依旧可以丢给AI,直接让基于本文开发一个基于Openclaw的完整的前后端服务,这也是最近笔者在做企业级的claw,总结整理出来的技术沉淀!

01协议概述

OpenClaw Gateway采用WebSocket JSON-RPC作为唯一的控制面传输协议。

协议特点:

特性
说明
传输层
WebSocket (wss://)
编码格式
JSON 文本帧
通信模式
全双工(请求-响应 + 事件推送)
帧类型
req(请求)、res(响应)、event(事件推送)
认证方式
Token / Password / Device Identity / Bootstrap Token
方法数量
150+(全量,CLI和WebSocket完全对齐)
事件推送
25+(health/chat/presence/shutdown等)

跟前两篇的对比:

维度
WebSocket JSON-RPC
CLI 远程调用
Admin HTTP RPC
连接模式
长连接
一次性 WS
HTTP 短连接
方法数量
150+ 全量
150+ 全量
~50 白名单
事件推送
支持
不支持
不支持
协议版本
可手动指定
硬编码必须匹配
无协议要求
适用场景
Web UI / 实时管理
运维终端
自动化脚本
02连接与握手

整个握手流程就四步:建连 -> challenge -> connect -> hello-ok。

连接流程
客户端                                    OpenClaw 网关  |                                           |  |  1. WebSocket 连接建立 (wss://)            |  | —————————————-> |  |                                           |  |  2. 网关推送 connect.challenge             |  | <— event: connect.challenge ———– |  |      { nonce, ts }                        |  |                                           |  |  3. 客户端发送 connect 握手请求             |  | —- req: connect ———————-> |  |      { client, role, scopes, auth }       |  |                                           |  |  4. 网关返回 hello-ok                      |  | <— res: hello-ok ———————- |  |   { methods[], events[], snapshot, auth } |  |                                           |  |  握手完成,自由调用 RPC                     |  |                                           |
建立WebSocket连接
const ws = new WebSocket(‘wss://你的网关地址’, {  headers: {    // 关键:必须携带与网关地址匹配的 Origin 头    Origin: ‘https://你的网关地址’  }});
握手请求(Connect)

收到 connect.challenge 事件后,客户端必须发送的第一个请求帧

{  “type”: “req”,  “id”: “1”,  “method”: “connect”,  “params”: {    “minProtocol”: 3,    “maxProtocol”: 4,    “client”: {      “id”: “openclaw-control-ui”,      “version”: “1.0.0”,      “platform”: “web”,      “mode”: “ui”    },    “role”: “operator”,    “scopes”: [“operator.read”, “operator.write”, “operator.admin”],    “auth”: {      “token”: “你的网关Token”    }  }}
握手成功响应(Hello-OK)
{  “type”: “res”,  “id”: “1”,  “ok”: true,  “payload”: {    “type”: “hello-ok”,    “protocol”: 3,    “server”: { “version”: “2026.5.19”, “connId”: “uuid” },    “features”: {      “methods”: [“status”, “models.list”, “…”],      “events”: [“health”, “chat”, “…”]    },    “snapshot”: { “presence”: [“…”], “health”: {“…”}, “stateVersion”: {“…”} },    “auth”: {      “role”: “operator”,      “scopes”: [“operator.read”, “operator.write”, “operator.admin”]    },    “policy”: {      “maxPayload”: 26214400,      “maxBufferedBytes”: 52428800,      “tickIntervalMs”: 30000    }  }}

关键检查: 握手成功后务必检查 payload.auth.scopes 是否包含所需权限。如果为空 [],说明权限被网关安全策略剥离。后面踩坑实录会详细讲这个最阴险的坑。

Connect Params 字段说明
字段
类型
必填
说明
minProtocol
number
客户端支持的最低协议版本(填 3)
maxProtocol
number
客户端支持的最高协议版本(填 4)
client.id
string
客户端标识(见下方枚举)
client.version
string
客户端版本号
client.platform
string
运行平台(web/macos/linux/windows/ios/android)
client.mode
string
运行模式(见下方枚举)
client.displayName
string
客户端显示名称
client.deviceFamily
string
设备族(Mac/iPhone/Android等)
client.instanceId
string
实例唯一标识
role
string
角色:operator 或 node
scopes
string[]
请求的权限范围
auth.token
string
共享密钥 Token
auth.password
string
密码认证
auth.deviceToken
string
设备 Token(配对后获得)
auth.bootstrapToken
string
引导 Token(首次配对用)
device
object
设备身份(Ed25519 签名)
caps
string[]
客户端能力声明
locale
string
区域设置(如 zh-CN)
Client ID 枚举
ID
说明
openclaw-control-ui
Web 管理界面
openclaw-tui
TUI 终端界面
cli
命令行客户端
gateway-client
程序化网关客户端
webchat
网页聊天
webchat-ui
网页聊天 UI
openclaw-macos
macOS 应用
openclaw-ios
iOS 应用
openclaw-android
Android 应用
node-host
节点宿主
openclaw-probe
探针/健康检查
Client Mode 枚举
Mode
说明
ui
UI 管理模式
cli
命令行模式
backend
后端程序化模式
webchat
网页聊天模式
node
节点模式
probe
探针模式
test
测试模式
03帧格式定义

WebSocket上传输的每一帧都是JSON文本。三种类型:请求、响应、事件。经常做这方面的同学可以跳过看后边~~

请求帧 (Request Frame)
{  “type”: “req”,  “id”: “唯一请求ID(字符串)”,  “method”: “方法名”,  “params”: {}}
响应帧 (Response Frame)

成功:

{  “type”: “res”,  “id”: “对应请求ID”,  “ok”: true,  “payload”: {}}

失败:

{  “type”: “res”,  “id”: “对应请求ID”,  “ok”: false,  “error”: {    “code”: “INVALID_REQUEST”,    “message”: “错误描述”,    “details”: {},    “retryable”: false,    “retryAfterMs”: 5000  }}
事件帧 (Event Frame)
{  “type”: “event”,  “event”: “事件名”,  “payload”: {},  “seq”: 1,  “stateVersion”: { “presence”: 27, “health”: 1251 }}

请求和响应通过 id 字段匹配。事件帧没有 id,是网关主动推送的。一个连接上会同时收到响应帧和事件帧,必须通过 type 区分。

04认证与权限
权限体系 (Scopes)
Scope
说明
包含
operator.admin
管理员(最高权限)
包含 read + write
operator.write
写入权限
包含 read
operator.read
只读权限
operator.pairing
设备配对权限
operator.approvals
审批权限
认证方式优先级

1. Device Token(已配对设备的持久凭证)

2. Shared Token(配置文件中的共享密钥)

3. Password(密码认证)

4. Bootstrap Token(首次引导配对用)

5. Trusted Proxy(反向代理信任)

获取完整Scopes的条件

网关默认采用Default-Deny策略。无设备身份的客户端会被清除scopes。要获得完整权限,有三种方式:

方式一:携带匹配的Origin头(Control UI模式)

headers: { Origin: ‘https://网关地址’ }// + client.id = ‘openclaw-control-ui’, mode = ‘ui’

方式二:设备签名认证(最安全)

◆ 生成 Ed25519 密钥对
◆ 对握手数据签名
◆ 首次需完成设备配对流程

方式三:网关配置 dangerouslyDisableDeviceAuth: true

{ “gateway”: { “controlUi”: { “dangerouslyDisableDeviceAuth”: true } } }

这个配置项名字就已经告诉你了——危险。只有在完全私有网络里才用。

05完整RPC方法清单

150+个方法,按功能分19类。每个方法标注了需要的权限Scope和参数。

系统信息 & 健康
方法
Scope
参数
说明
health
无需
{ probe?: boolean }
网关健康状态
status
read
{ includeChannelSummary?: boolean }
系统综合摘要
diagnostics.stability
read
{}
稳定性诊断
gateway.identity.get
read
{}
网关设备ID和公钥
gateway.restart.preflight
read
{}
重启前检查
gateway.restart.request
admin
{}
请求网关重启
system-presence
read
{}
在线节点/客户端列表
system-event
admin
{ text }
注入系统事件
last-heartbeat
read
{}
最近心跳事件
set-heartbeats
admin
{ enabled: boolean }
开关心跳
wake
write
{}
唤醒网关
logs.tail
read
{ lines?, filter? }
实时日志
usage.status
read
{}
用量状态
usage.cost
read
{}
费用统计
commands.list
read
{}
可用斜杠命令
Agent管理
方法
Scope
参数
说明
agents.list
read
{}
列出所有Agent
agents.create
admin
{ name, workspace, model?, emoji?, avatar? }
创建Agent
agents.update
admin
{ agentId, name?, workspace?, model?, emoji?, avatar? }
更新Agent
agents.delete
admin
{ agentId, deleteFiles?: boolean }
删除Agent
agents.files.list
read
{ agentId }
列出workspace文件
agents.files.get
read
{ agentId, name }
读取文件内容
agents.files.set
admin
{ agentId, name, content }
写入/编辑文件
agent.identity.get
read
{}
Agent身份信息
agent
write
{ … }
执行Agent任务
agent.wait
write
{ … }
等待Agent完成

agents.files.set 允许写入的文件白名单:AGENTS.md、SOUL.md、TOOLS.md、IDENTITY.md、USER.md、HEARTBEAT.md、BOOTSTRAP.md、MEMORY.md。

会话 (Sessions)
方法
Scope
参数
说明
sessions.list
read
{}
列出所有会话
sessions.create
write
{ agentId?, … }
创建新会话
sessions.send
write
{ sessionKey, message, … }
向会话发送消息
sessions.abort
write
{ sessionKey }
中止当前运行
sessions.preview
read
{ sessionKey }
预览会话内容
sessions.describe
read
{ sessionKey }
会话详情
sessions.subscribe
read
{}
订阅会话变更
sessions.unsubscribe
read
{}
取消订阅
sessions.messages.subscribe
read
{ sessionKey }
订阅消息流
sessions.messages.unsubscribe
read
{ sessionKey }
取消消息订阅
sessions.patch
admin
{ sessionKey, … }
修改会话元数据
sessions.pluginPatch
admin
{ sessionKey, … }
插件修改会话
sessions.reset
admin
{ sessionKey }
重置会话
sessions.delete
admin
{ sessionKey }
删除会话
sessions.cleanup
admin
{}
清理过期会话
sessions.compact
admin
{ sessionKey }
压缩会话
sessions.compaction.list
read
{ sessionKey }
压缩版本列表
sessions.compaction.get
read
{ sessionKey, id }
压缩详情
sessions.compaction.branch
write
{ sessionKey, id }
从压缩点分支
sessions.compaction.restore
admin
{ sessionKey, id }
恢复到压缩点
聊天 (Chat)
方法
Scope
参数
说明
chat.history
read
{ sessionKey?, limit? }
获取聊天历史
chat.send
write
{ message, sessionKey?, … }
发送消息(触发回复)
chat.abort
write
{ sessionKey? }
中止生成
send
write
{ … }
底层消息发送
message.action
write
{ … }
消息操作
渠道 (Channels)
方法
Scope
参数
说明
channels.status
read
{ probe?: boolean, channel?, timeoutMs? }
渠道状态
channels.start
admin
{ channel, accountId? }
启动渠道
channels.stop
admin
{ channel, accountId? }
停止渠道
channels.logout
admin
{ channel, accountId? }
注销登录
模型 (Models)
方法
Scope
参数
说明
models.list
read
`{ view?: “default” \
“all” }`
可用模型列表
models.authStatus
read
{}
模型鉴权状态
models.authLogout
admin
{}
登出模型鉴权
工具 (Tools)
方法
Scope
参数
说明
tools.catalog
read
{ agentId?, includePlugins?: boolean }
工具目录
tools.effective
read
{ sessionKey, agentId? }
会话有效工具
tools.invoke
write
{ tool, args, sessionKey? }
远程调用工具
技能 (Skills)
方法
Scope
参数
说明
skills.status
read
{ agentId? }
技能激活状态
skills.search
read
{ query?, limit? }
市场搜索
skills.detail
read
{ slug }
技能详情
skills.install
admin
`{ name, installId } \
{ source: “clawhub”, slug }`
安装技能
skills.update
admin
{ skillKey, enabled?, apiKey?, env? }
更新技能配置
skills.bins
node
{}
技能依赖列表
skills.upload.begin
admin
{ … }
上传技能(开始)
skills.upload.chunk
admin
{ … }
上传技能(分块)
skills.upload.commit
admin
{ … }
上传技能(提交)
配置 (Config)
方法
Scope
参数
说明
config.get
read
{}
读取完整配置
config.set
admin
{ raw, baseHash }
覆盖整个配置
config.patch
admin
{ raw, baseHash }
JSON Merge Patch局部更新
config.apply
admin
{ raw, baseHash }
应用配置并重启
config.schema
admin
{}
获取配置Schema
config.schema.lookup
read
{ path }
查询路径的Schema

Config修改流程:

1. config.get -> 获取当前配置和 baseHash2. 在客户端修改 JSON3. config.patch -> 提交修改(携带 baseHash 做乐观锁)4. 网关自动热重载或返回 restart: true
定时任务 (Cron)
方法
Scope
参数
说明
cron.list
read
{}
列出所有任务
cron.get
read
{ id }
单个任务详情
cron.status
read
{}
任务状态
cron.add
admin
{ schedule, command, … }
添加任务
cron.update
admin
{ id, … }
更新任务
cron.remove
admin
{ id }
删除任务
cron.run
admin
{ id }
手动触发执行
cron.runs
read
{ id?, limit? }
运行历史
任务 (Tasks)
方法
Scope
参数
说明
tasks.list
read
{ status?, limit? }
后台任务列表
tasks.get
read
{ taskId }
任务详情
tasks.cancel
write
{ taskId }
取消任务
节点 & 设备配对
方法
Scope
参数
说明
node.list
read
{}
已连接节点列表
node.describe
read
{ nodeId }
节点详情
node.rename
pairing
{ nodeId, name }
重命名节点
node.invoke
write
{ nodeId, command, … }
远程调用节点
node.pending.enqueue
write
{ … }
入队待处理
node.pending.drain
node
{}
消费队列
node.pending.pull
node
{}
拉取待处理
node.pending.ack
node
{ … }
确认完成
node.invoke.result
node
{ … }
返回结果
node.event
node
{ … }
事件上报
node.pair.request
pairing
{ … }
请求配对
node.pair.list
pairing
{}
待配对列表
node.pair.approve
pairing
{ requestId }
批准配对
node.pair.reject
pairing
{ requestId }
拒绝配对
node.pair.remove
pairing
{ nodeId }
移除已配对
node.pair.verify
pairing
{ … }
验证配对
device.pair.list
pairing
{}
设备配对列表
device.pair.approve
pairing
{ requestId }
批准设备
device.pair.reject
pairing
{ requestId }
拒绝设备
device.pair.remove
pairing
{ deviceId }
移除设备
device.token.rotate
pairing
{ deviceId }
轮换Token
device.token.revoke
pairing
{ deviceId }
吊销Token
语音 & TTS
方法
Scope
参数
说明
tts.status
read
{}
TTS状态
tts.providers
read
{}
可用提供商
tts.personas
read
{}
可用音色
tts.enable
write
{}
启用TTS
tts.disable
write
{}
禁用TTS
tts.convert
write
{ text, … }
文本转语音
tts.setProvider
write
{ provider }
设置提供商
tts.setPersona
write
{ persona }
设置音色
talk.config
read
{}
实时对话配置
talk.speak
write
{ … }
实时语音合成
talk.mode
write
{ … }
切换对话模式
talk.realtime.session
write
{ … }
实时语音会话
talk.realtime.relayAudio
write
{ … }
中继音频
talk.realtime.relayMark
write
{ … }
中继标记
talk.realtime.relayStop
write
{ … }
停止中继
talk.realtime.relayToolResult
write
{ … }
工具结果回传
语音唤醒 (Voice Wake)
方法
Scope
参数
说明
voicewake.get
read
{}
获取唤醒词配置
voicewake.set
write
{ … }
设置唤醒词
voicewake.routing.get
read
{}
唤醒路由配置
voicewake.routing.set
write
{ … }
设置路由
环境 & 产物
方法
Scope
参数
说明
environments.list
read
{}
环境列表
environments.status
read
{ id? }
环境状态
artifacts.list
read
{ sessionKey? }
产物列表
artifacts.get
read
{ id }
获取产物
artifacts.download
read
{ id }
下载产物
插件 & 向导
方法
Scope
参数
说明
plugins.uiDescriptors
read
{}
插件UI描述
plugins.sessionAction
dynamic
{ … }
插件会话操作
wizard.start
admin
{ … }
启动向导
wizard.next
admin
{ … }
向导下一步
wizard.cancel
admin
{}
取消向导
wizard.status
admin
{}
向导状态
审批 (Approvals)
方法
Scope
参数
说明
exec.approvals.get
admin
{}
获取审批策略
exec.approvals.set
admin
{ … }
设置审批策略
exec.approvals.node.get
admin
{ … }
节点审批策略
exec.approvals.node.set
admin
{ … }
设置节点审批
exec.approval.get
approvals
{ id }
获取单个审批
exec.approval.request
approvals
{ … }
请求审批
exec.approval.resolve
approvals
{ id, decision }
审批决策
plugin.approval.request
approvals
{ … }
插件审批请求
plugin.approval.resolve
approvals
{ id, decision }
插件审批决策
密钥 & 更新
方法
Scope
参数
说明
secrets.reload
admin
{}
重载密钥
secrets.resolve
admin
{ refs }
解析密钥引用
update.status
admin
{}
版本更新状态
update.run
admin
{}
执行版本更新
记忆 & 诊断 (Doctor)
方法
Scope
参数
说明
doctor.memory.status
read
{}
记忆系统状态
doctor.memory.dreamDiary
read
{}
梦境日记
doctor.memory.backfillDreamDiary
write
{}
回填梦境
doctor.memory.resetDreamDiary
write
{}
重置梦境
doctor.memory.resetGroundedShortTerm
write
{}
重置短期记忆
doctor.memory.repairDreamingArtifacts
write
{}
修复梦境产物
doctor.memory.dedupeDreamDiary
write
{}
梦境去重
doctor.memory.remHarness
read
{}
REM状态
06实时事件推送

这是WebSocket相对CLI和HTTP RPC最大的优势——握手完成后,网关会主动推送事件,你不需要轮询。

事件名
触发时机
Payload示例
health
健康状态变更
{ ok, ts, channels, eventLoop }
tick
定期心跳(30s)
{ ts }
presence
在线状态变更
[{ host, ip, mode, … }]
chat
聊天消息流
{ sessionKey, message, … }
agent
Agent执行事件
{ … }
session.message
会话消息
{ sessionKey, … }
session.tool
工具调用事件
{ sessionKey, tool, … }
sessions.changed
会话列表变更
{ … }
shutdown
网关关闭/重启
{ reason, restartExpectedMs? }
heartbeat
心跳结果
{ agentId, … }
cron
定时任务事件
{ jobId, … }
node.pair.requested
节点配对请求
{ requestId, deviceId }
node.pair.resolved
配对结果
{ requestId, approved }
node.invoke.request
节点调用请求
{ … }
device.pair.requested
设备配对请求
{ requestId, deviceId }
device.pair.resolved
设备配对结果
{ requestId, approved }
voicewake.changed
唤醒词变更
{ … }
voicewake.routing.changed
唤醒路由变更
{ … }
exec.approval.requested
执行审批请求
{ id, command, … }
exec.approval.resolved
审批决策通知
{ id, decision }
plugin.approval.requested
插件审批请求
{ … }
plugin.approval.resolved
插件审批决策
{ … }
talk.mode
语音模式变更
{ … }
update.available
版本更新可用
{ version }
07二次开发指南

四种语言的SDK封装示例,覆盖前端Web页面、后端Node.js、Python集成、配置修改完整流程。

前端Web管理页面开发(TypeScript)
class OpenClawGatewayClient {  private ws: WebSocket;  private requestId = 0;  private pending = new Map<string, { resolve, reject }>();  private eventHandlers = new Map<string, Function[]>();  constructor(private url: string, private token: string) {}  async connect(): Promise<void> {    return new Promise((resolve, reject) => {      this.ws = new WebSocket(this.url, {        headers: { Origin: this.url.replace(‘wss://’, ‘https://’) }      });      this.ws.onmessage = (event) => {        const msg = JSON.parse(event.data);        this.handleFrame(msg);      };      // 等待 challenge -> 发 connect -> 等 hello-ok      this.once(‘internal:connected’, resolve);      this.once(‘internal:error’, reject);    });  }  async call<T = unknown>(method: string, params: object = {}): Promise<T> {    const id = String(++this.requestId);    return new Promise((resolve, reject) => {      this.pending.set(id, { resolve, reject });      this.ws.send(JSON.stringify({ type: ‘req’, id, method, params }));      // 超时保护      setTimeout(() => {        if (this.pending.has(id)) {          this.pending.delete(id);          reject(new Error(`RPC timeout: ${method}`));        }      }, 30000);    });  }  on(event: string, handler: Function) {    const handlers = this.eventHandlers.get(event) || [];    handlers.push(handler);    this.eventHandlers.set(event, handlers);  }  private handleFrame(msg: any) {    if (msg.type === ‘event’) {      if (msg.event === ‘connect.challenge’) {        this.sendConnect();      }      const handlers = this.eventHandlers.get(msg.event) || [];      handlers.forEach(h => h(msg.payload));    }    if (msg.type === ‘res’) {      const pending = this.pending.get(msg.id);      if (pending) {        this.pending.delete(msg.id);        if (msg.ok) {          if (msg.payload?.type === ‘hello-ok’) {            this.emit(‘internal:connected’);          }          pending.resolve(msg.payload);        } else {          pending.reject(msg.error);        }      }    }  }  private sendConnect() {    this.call(‘connect’, {      minProtocol: 3,      maxProtocol: 4,      client: { id: ‘openclaw-control-ui’, version: ‘1.0.0’, platform: ‘web’, mode: ‘ui’ },      role: ‘operator’,      scopes: [‘operator.read’, ‘operator.write’, ‘operator.admin’],      auth: { token: this.token }    });  }}// 使用示例const client = new OpenClawGatewayClient(  ‘wss://your-gateway.fc.zyunapp.cn’,  ‘zqi-xxxxx’);await client.connect();const status = await client.call(‘status’);const models = await client.call(‘models.list’, { view: ‘default’ });const skills = await client.call(‘skills.status’);const channels = await client.call(‘channels.status’, { probe: true });const tools = await client.call(‘tools.catalog’, { includePlugins: true });const files = await client.call(‘agents.files.list’, { agentId: ‘main’ });
后端服务集成(Node.js)
const WebSocket = require(‘ws’);class OpenClawRPC {  constructor(gatewayUrl, token) {    this.url = gatewayUrl;    this.token = token;    this.ws = null;    this.requestId = 0;    this.pending = new Map();    this.connected = false;  }  connect() {    return new Promise((resolve, reject) => {      this.ws = new WebSocket(this.url, {        rejectUnauthorized: false,        headers: { Origin: this.url.replace(‘wss://’, ‘https://’).replace(‘ws://’, ‘http://’) }      });      this.ws.on(‘message’, (data) => {        const msg = JSON.parse(data.toString());        if (msg.type === ‘event’ && msg.event === ‘connect.challenge’) {          this._sendConnect();          return;        }        if (msg.type === ‘res’) {          if (msg.payload?.type === ‘hello-ok’) {            this.connected = true;            resolve(msg.payload);            return;          }          const p = this.pending.get(msg.id);          if (p) {            this.pending.delete(msg.id);            msg.ok ? p.resolve(msg.payload) : p.reject(msg.error);          }        }      });      this.ws.on(‘error’, reject);    });  }  call(method, params = {}) {    const id = String(++this.requestId);    return new Promise((resolve, reject) => {      this.pending.set(id, { resolve, reject });      this.ws.send(JSON.stringify({ type: ‘req’, id, method, params }));    });  }  close() { this.ws?.close(); }  _sendConnect() {    const id = String(++this.requestId);    this.ws.send(JSON.stringify({      type: ‘req’, id, method: ‘connect’,      params: {        minProtocol: 3, maxProtocol: 4,        client: { id: ‘gateway-client’, version: ‘1.0.0’, platform: process.platform, mode: ‘backend’ },        role: ‘operator’,        scopes: [‘operator.read’, ‘operator.write’, ‘operator.admin’],        auth: { token: this.token }      }    }));  }}// 使用async function main() {  const rpc = new OpenClawRPC(‘wss://your-gateway-url’, ‘your-token’);  await rpc.connect();  // 获取系统状态  const status = await rpc.call(‘status’);  console.log(‘系统状态:’, status);  // 切换模型  await rpc.call(‘agents.update’, { agentId: ‘main’, model: ‘-deepseek-v4-pro’ });  // 编辑文件  await rpc.call(‘agents.files.set’, {    agentId: ‘main’,    name: ‘IDENTITY.md’,    content: ‘# 新的身份文件内容\n…’  });  rpc.close();}
Python集成
import asyncioimport jsonimport websocketsclass OpenClawGateway:    def __init__(self, url: str, token: str):        self.url = url        self.token = token        self.ws = None        self.request_id = 0        self.pending = {}    async def connect(self):        origin = self.url.replace(‘wss://’, ‘https://’).replace(‘ws://’, ‘http://’)        self.ws = await websockets.connect(            self.url,            additional_headers={‘Origin’: origin},            ssl=True        )        # 等待 challenge        msg = json.loads(await self.ws.recv())        assert msg[‘type’] == ‘event’ and msg[‘event’] == ‘connect.challenge’        # 发送 connect        self.request_id += 1        await self.ws.send(json.dumps({            ‘type’: ‘req’, ‘id’: str(self.request_id), ‘method’: ‘connect’,            ‘params’: {                ‘minProtocol’: 3, ‘maxProtocol’: 4,                ‘client’: {‘id’: ‘gateway-client’, ‘version’: ‘1.0.0’, ‘platform’: ‘python’, ‘mode’: ‘backend’},                ‘role’: ‘operator’,                ‘scopes’: [‘operator.read’, ‘operator.write’, ‘operator.admin’],                ‘auth’: {‘token’: self.token}            }        }))        # 等待 hello-ok        while True:            msg = json.loads(await self.ws.recv())            if msg.get(‘type’) == ‘res’ and msg.get(‘ok’) and msg.get(‘payload’, {}).get(‘type’) == ‘hello-ok’:                return msg[‘payload’]    async def call(self, method: str, params: dict = None) -> dict:        self.request_id += 1        req_id = str(self.request_id)        await self.ws.send(json.dumps({            ‘type’: ‘req’, ‘id’: req_id, ‘method’: method, ‘params’: params or {}        }))        while True:            msg = json.loads(await self.ws.recv())            if msg.get(‘type’) == ‘res’ and msg.get(‘id’) == req_id:                if msg[‘ok’]:                    return msg.get(‘payload’)                else:                    raise Exception(f”RPC Error: {msg[‘error’]}”)    async def close(self):        await self.ws.close()# 使用async def main():    gw = OpenClawGateway(‘wss://your-gateway-url’, ‘your-token’)    await gw.connect()    status = await gw.call(‘status’)    models = await gw.call(‘models.list’, {‘view’: ‘default’})    skills = await gw.call(‘skills.status’)    await gw.close()asyncio.run(main())
配置修改(切换模型)完整流程
// 1. 获取当前配置和 baseHashconst configRes = await rpc.call(‘config.get’);const baseHash = configRes.hash;const currentConfig = configRes.config;// 2. 构造 patch(JSON5 格式字符串)const patch = JSON.stringify({  agents: {    defaults: {      model: { primary: ‘deepseek-v4-pro’ }    }  }});// 3. 提交 patchconst patchRes = await rpc.call(‘config.patch’, { raw: patch, baseHash });console.log(‘配置更新结果:’, patchRes);// 如果 patchRes.restart === true,网关会自动重启应用配置
08注意事项与最佳实践
连接管理
事项
说明
Origin头
必须携带与网关地址匹配的Origin,否则Control UI模式会被拒绝
心跳保活
网关每 tickIntervalMs(30s)发送tick事件;客户端无需回复,但需保持连接活跃
重连策略
收到 shutdown 事件后,等待 restartExpectedMs 再重连
单连接
一个WebSocket连接即可满足所有RPC调用和事件订阅
并发请求
支持并发发送多个请求,通过 id 字段匹配响应
安全注意事项
事项
说明
Token保密
绝不将Token暴露在前端JS源码或URL中
wss加密
生产环境必须使用 wss://,禁止明文 ws://
Scope最小化
只请求实际需要的Scopes
baseHash校验
配置修改必须携带baseHash,防止并发冲突
文件白名单
agents.files.set

 只允许写入指定的8个文件
性能优化
事项
说明
连接复用
避免频繁断连重连,单连接长期复用
事件驱动
优先监听事件推送,减少轮询
按需probe
channels.status

 的 probe: true 会触发网络探活,非必要不开启
分页查询
大列表使用 limit 参数分页
payload大小
单帧不超过 maxPayload(26MB),否则会被断开
错误处理
try {  const result = await rpc.call(‘agents.files.set’, { agentId: ‘main’, name: ‘SOUL.md’, content: ‘…’ });} catch (error) {  switch (error.code) {    case ‘INVALID_REQUEST’:      // 参数错误或权限不足      break;    case ‘UNAVAILABLE’:      // 服务暂时不可用,可重试      break;    case ‘NOT_PAIRED’:      // 需要设备配对      break;  }}
09错误码参考
错误码
含义
处理建议
INVALID_REQUEST
请求参数无效或权限不足
检查参数和scopes
UNAVAILABLE
服务暂时不可用
稍后重试
NOT_PAIRED
设备未配对
完成设备配对流程
STARTUP_UNAVAILABLE
网关启动中
等待 restartExpectedMs 后重试
10完整代码示例:一次获取所有管理信息

下面这段代码演示了如何在一个WebSocket连接上并发拉取系统状态、模型、渠道、工具、技能、Agent文件、定时任务、会话列表等所有管理信息:

const WebSocket = require(‘ws’);async function fetchAllInfo(gatewayUrl, token) {  return new Promise((resolve, reject) => {    const ws = new WebSocket(gatewayUrl, {      rejectUnauthorized: false,      headers: { Origin: gatewayUrl.replace(‘wss://’, ‘https://’) }    });    let reqId = 0;    const results = {};    const queue = [      [‘status’, { includeChannelSummary: true }],      [‘models.list’, { view: ‘default’ }],      [‘channels.status’, { probe: false }],      [‘tools.catalog’, { includePlugins: true }],      [‘skills.status’, {}],      [‘agents.files.list’, { agentId: ‘main’ }],      [‘cron.list’, {}],      [‘sessions.list’, {}],    ];    let queueIndex = 0;    function send(method, params) {      const id = String(++reqId);      ws.send(JSON.stringify({ type: ‘req’, id, method, params }));      return id;    }    function nextQuery() {      if (queueIndex >= queue.length) {        ws.close();        resolve(results);        return;      }      const [method, params] = queue[queueIndex];      send(method, params);    }    ws.on(‘message’, (data) => {      const msg = JSON.parse(data.toString());      if (msg.type === ‘event’ && msg.event === ‘connect.challenge’) {        send(‘connect’, {          minProtocol: 3, maxProtocol: 4,          client: { id: ‘openclaw-control-ui’, version: ‘1.0.0’, platform: ‘web’, mode: ‘ui’ },          role: ‘operator’,          scopes: [‘operator.read’, ‘operator.write’, ‘operator.admin’],          auth: { token }        });        return;      }      if (msg.type !== ‘res’) return;      if (msg.payload?.type === ‘hello-ok’) {        results._gateway = {          version: msg.payload.server.version,          connId: msg.payload.server.connId,          scopes: msg.payload.auth.scopes,          methods: msg.payload.features.methods,          events: msg.payload.features.events,        };        nextQuery();        return;      }      if (msg.ok) {        const [method] = queue[queueIndex];        results[method] = msg.payload;        queueIndex++;        nextQuery();      } else {        const [method] = queue[queueIndex];        results[method] = { error: msg.error };        queueIndex++;        nextQuery();      }    });    ws.on(‘error’, reject);    setTimeout(() => reject(new Error(‘Timeout’)), 30000);  });}// 调用fetchAllInfo(‘wss://your-gateway.fc.zyunapp.cn’, ‘your-token’)  .then(info => console.log(JSON.stringify(info, null, 2)));
11踩坑实录

9个实机验证踩坑记录,全部来自真实环境复现。WebSocket协议比CLI和HTTP复杂得多,坑也最多。

坑1:Origin头必须匹配(Control UI模式)

实测现象:

client.id
Origin 头
结果
openclaw-control-ui
不传
origin not allowed

 (1008 断开)
openclaw-control-ui https://其他域名 origin not allowed
openclaw-control-ui https://网关地址

 (匹配)
正常,scopes 保留
cli
不传
连接成功,但 scopes 被清空

使用 openclaw-control-ui 客户端ID时,必须在WebSocket连接建立时携带与网关地址匹配的Origin头。否则直接被拒绝,错误信息是 “origin not allowed”:

new WebSocket(‘wss://网关地址’, {  headers: { Origin: ‘https://网关地址’ }  // 关键!});
坑2:Scopes静默清空(最阴险的坑)

实测现象:

client.id
有 Origin
有 device 签名
hello-ok auth.scopes
cli

 + token
[]

 被清空
cli

 + token
[“operator.read”,”write”,”admin”]
openclaw-control-ui

 + token
是 匹配
[“operator.read”,”write”,”admin”]

后果:握手 hello-ok 返回 ok: true,看起来连接成功了,但 auth.scopes = []。后续调用任何需要权限的方法都报:

{“ok”:false,”error”:{“code”:”INVALID_REQUEST”,”message”:”missing scope: operator.read”}}

连接”成功”不代表有权限。务必检查hello-ok返回的 payload.auth.scopes 是否非空。如果为空,要么换Control UI模式 + Origin头,要么完成设备签名配对。

坑3:client.mode必须是合法枚举值

实测:

{“client”:{“id”:”cli”,”mode”:”operator”}}

返回:

{“ok”:false,”error”:{“code”:”INVALID_REQUEST”,”message”:”invalid connect params: at /client/mode: must be equal to one of the allowed values”}}

合法的mode值:uiclibackendwebchatnodeprobetestmode 不是填角色名(operator/admin),而是客户端运行模式。

坑4:协议版本必须匹配

实测:

◆ 远端网关协议v3,客户端发 minProtocol:4, maxProtocol:4 -> 连接被拒 (close 1002)
◆ 客户端发 minProtocol:3, maxProtocol:4 -> 连接成功,协商为v3

minProtocol 不要写得太高。建议固定用 minProtocol: 3, maxProtocol: 4 以兼容老版本网关。这是WebSocket脚本方式相对CLI的一个优势——CLI的协议版本是源码硬编码的,你只能升级或降级整个CLI;WebSocket脚本你可以自己控制minProtocol。

坑5:第一帧必须是connect

握手阶段,收到 connect.challenge 后发送的第一个请求必须是 method: “connect”。发送任何其他方法会被直接断开:

invalid handshake: first request must be connect

不能在握手完成前发送 health 或其他请求。必须等 hello-ok 返回后才能自由调用。

坑6:Event帧和Response帧混杂

握手成功后,你会同时收到两种帧:

◆ type: “res” —— 你的请求的响应
◆ type: “event” —— 网关主动推送的事件(health/tick/presence/…)

常见事件干扰:

// 你发了 status 请求 (id: “2”)// 但先收到了这个 event(不是你的响应!){“type”:”event”,”event”:”health”,”payload”:{},”seq”:1}// 再收到了这个 event{“type”:”event”,”event”:”tick”,”payload”:{“ts”:”…”},”seq”:2}// 最后才收到你的响应{“type”:”res”,”id”:”2″,”ok”:true,”payload”:{}}

必须通过 msg.type === ‘res’ && msg.id === yourId 来匹配响应,不能假设收到的下一帧就是你请求的响应。

坑7:tick超时与连接保活

网关每隔 tickIntervalMs(默认30秒)发送一个 tick 事件。如果长时间没有收到tick,说明连接可能已经断开。

但反过来:如果你的客户端长时间不发送任何数据,某些代理/负载均衡器可能会切断空闲连接。

生产环境建议实现WebSocket ping/pong或在空闲时定期发一个轻量请求(如 health)保活。

坑8:设备配对未完成

如果提供了device签名但该设备从未被配对过:

{  “ok”: false,  “error”: {    “code”: “NOT_PAIRED”,    “message”: “pairing required: device is not approved yet”,    “details”: {      “code”: “PAIRING_REQUIRED”,      “requestId”: “6164fa32-…”,      “remediationHint”: “Approve this device from the pending pairing requests.”    }  }}

连接被close 1008断开。此时需要在服务器端执行 openclaw device approve <requestId> 批准该设备。如果不想配对,用Control UI模式 + Origin头代替。

坑9:三种方式的坑点总结表

把前两篇和这篇的所有坑点汇总到一张表里:

坑点
WebSocket
Admin HTTP RPC
CLI
需要Origin头
是 (Control UI)
Scopes被清空
是 可能
否 (全权限)
否 (有设备签名)
协议版本匹配
可手动指定
否 无此问题
是 硬编码不可改
插件需启用
是 必须启用
方法白名单限制
否 全量
是 ~50个
否 全量
事件帧干扰
是 需过滤
否 无事件
否 CLI内部处理
设备配对
取决于模式
否 无需
是 自动签名

WebSocket的坑最多——但它能力也最完整。

12版本兼容性与功能矩阵

不同的RPC方法对网关版本有要求,这是实际开发中容易踩的坑:

功能
最低网关版本
说明
核心RPC(status/health/config/channels/agents/skills/tools)
所有版本
基础功能
sessions.usage

 多维聚合(daily/byModel/byAgent/byChannel)
所有版本
用量统计
sessions.usage.timeseries

 / sessions.usage.logs
所有版本
需传 key 参数
skills.upload.begin

 / chunk / commit
≥ v2026.5.12
分片上传Skill压缩包
skills.install

 source=upload
≥ v2026.5.12
安装已上传的Skill
Protocol v4
≥ v2026.5.22
CLI默认v4,老网关需手动指定 minProtocol:3

如果你对接的是较老的网关版本,避免使用上面标注的高版本方法,或者降级网关版本。

13config.patch配置路径参考

config.patch 是WebSocket最常用的写操作之一。规则很多,新手容易踩坑。

常用操作对应的patch路径
操作
config.patch的raw JSON
切换工具档位
{“tools”: {“profile”: “coding”}}
关闭图片理解
{“tools”: {“media”: {“image”: {“enabled”: false}}}}
关闭视频理解
{“tools”: {“media”: {“video”: {“enabled”: false}}}}
关闭网页搜索
{“tools”: {“web”: {“search”: {“enabled”: false}}}}
技能开关
{“skills”: {“entries”: {“<skillId>”: {“enabled”: true/false}}}}
插件开关
{“plugins”: {“entries”: {“<pluginId>”: {“enabled”: true/false}}}}
渠道配置
{“channels”: {“<channelId>”: {“<configPatch>”}}}
Cron总开关
{“cron”: {“enabled”: true/false}}
切换全局默认模型
{“agents”: {“defaults”: {“model”: {“primary”: “provider/modelId”}}}}
切换Agent模型
{“agents”: {“list”: [{“id”: “main”, “model”: {“primary”: “provider/modelId”}}]}}

patch的根节点是OpenClaw配置文件的顶层key(tools / skills / plugins / channels / agents / models),不是 “config.tools”。

数组覆盖陷阱

config.patch 使用JSON Merge Patch (RFC 7386):

◆ 对象字段:逐字段合并,没传的不动
◆ 数组字段整体替换,不是追加

往 models.providers.<name>.models[] 追加新模型时,必须把原有模型也带上

// 错误:只传新模型,原来的全没了{“models”:{“providers”:{“appmkt”:{“models”:[{“id”:”new-model”}]}}}}// 正确:先 config.get 拿当前 models[],把新的 append 进去再提交{“models”:{“providers”:{“appmkt”:{“models”:[  {“id”:”old-model-1″,”name”:”…”,”contextWindow”:200000,”maxTokens”:64000},  {“id”:”old-model-2″,”name”:”…”,”contextWindow”:128000,”maxTokens”:64000},  {“id”:”new-model”,”name”:”…”,”contextWindow”:128000,”maxTokens”:64000}]}}}}

这个坑非常常见,初次接触Merge Patch的人都会撞一次。

切换模型的完整步骤

一次 config.patch 同时做三件事:

1. models.providers.<name>.models[]     -> 追加新模型声明(带上旧的)2. agents.defaults.model.primary        -> 改默认指针   agents.defaults.models.<fullId>: {}  -> 注册到可选白名单(可选,影响 UI 下拉框)3. agents.list[].model.primary          -> 改 Agent 指针

遗漏任何一步的后果:

◆ 漏1:网关不认识新模型ID,contextWindow/maxTokens用默认值,压缩阈值算错
◆ 漏2:Control UI默认显示还是老模型(Agent实际已切换,但UI不同步)
◆ 漏3:Agent实际跑的还是老模型(默认改了但Agent有单独配置覆盖了)
写操作的baseHash乐观锁

所有config写操作(config.set / config.patch / config.apply)必须携带 baseHash

1. config.get -> 返回 { config, hash }2. 构造 patch3. config.patch -> 传 { baseHash: hash, raw: patchJSON }

如果两个客户端同时修改,后提交的会报 “config changed since last load; re-run config.get and retry”。需要重新 config.get 拿新hash再重试。

性能优化:缓存baseHash(30秒TTL),首次直接patch,hash失效再config.get重试。

14skills.upload 分片上传协议

如果你想做一个支持上传Skill压缩包的UI,需要走分片上传协议。

使用前提
检查项
要求
不满足时的报错
网关版本
≥ 2026.5.12
方法不存在
配置开关
skills.install.allowUploadedArchives: true UNAVAILABLE: Uploaded skill archive installs are disabled
权限
operator.admin

 scope
missing scope: operator.admin
单片大小
≤ 4 MB (Base64编码后)
validation error
活跃上传数
≤ 32 个同时进行
too many active uploads
上传有效期
1 小时内必须 commit
过期自动清理
上传流程
步骤1: skills.upload.begin  params: { kind: “skill-archive”, slug: “my-skill”, sizeBytes: 102400, sha256?: “…”, force?: false }  返回:   { uploadId: “uuid” }步骤2: skills.upload.chunk (循环,每片 ≤ 4MB)  params: { uploadId: “uuid”, offset: 0, dataBase64: “…” }  params: { uploadId: “uuid”, offset: 4194304, dataBase64: “…” }  …步骤3: skills.upload.commit  params: { uploadId: “uuid”, sha256?: “最终校验” }步骤4: skills.install (触发安装)  params: { source: “upload”, uploadId: “uuid”, slug: “my-skill”, force?: false }  返回:   { ok: true, slug, targetDir, sha256 }
15agents.create 子Agent创建

WebSocket可以远程创建子Agent,这是HTTP RPC不支持的能力之一。

参数
{  “method”: “agents.create”,  “params”: {    “name”: “新助手”,    “workspace”: “/data/openclaw/.openclaw/workspace-新助手”,    “model”: “provider/model-id”,    “emoji”: “robot”,    “avatar”: “https://…”  }}
注意事项
◆ agentId 不是参数,由网关从 name 自动normalize生成(小写、去空格)
◆ “main” 是保留ID,不允许创建
◆ workspace 路径如果不存在,网关会自动创建目录和bootstrap文件
◆ 创建后即可通过 agents.files.set 编辑其SOUL.md / AGENTS.md等文件
相关方法
方法
参数
说明
agents.create { name, workspace, model?, emoji?, avatar? }
创建
agents.update { agentId, name?, workspace?, model?, emoji?, avatar? }
更新
agents.delete { agentId, deleteFiles?: true }
删除(默认同时删工作区文件)
16并发与性能优化要点
WebSocket多路复用并发

WebSocket最大的性能优势:同一个连接上同时发N个请求,通过ID匹配响应:

发: req id:1 method:status发: req id:2 method:models.list发: req id:3 method:channels.status发: req id:4 method:tools.catalog发: req id:5 method:skills.status(不等,全部同时发)收: res id:3 …  <- 乱序返回收: res id:1 …收: event: health  <- 事件穿插收: res id:5 …收: res id:2 …收: res id:4 …总耗时 = max(单个 RPC 耗时) ≈ 200-400ms,不是 5 个之和

CLI每次调用都是一次性的WebSocket握手+连接+断开,这个开销在多请求场景下会被放大很多倍。

写操作优化

避免每次写操作都串行 GetConfig → PatchConfig(两次RPC):

◆ 缓存baseHash(30秒TTL)
◆ 首次直接patch,hash过期再GetConfig重试
◆ 写操作从400-800ms降到200-400ms
聚合查询

页面加载时不要逐个HTTP请求拉数据,在后端用一个入口并发调多个RPC,前端一次HTTP拿到所有数据。这是为什么OpenClaw自己的Control UI首屏加载非常快——它就是这么干的。

17附录:OpenClaw RPC方法完整索引

下面是从源码扒出来的全部RPC方法分类索引,共150+个,覆盖19个功能域。这份索引相当于OpenClaw控制面的完整字典——开发管理前端、写自动化脚本、调试问题时,都可以直接当查询手册用。配合前两篇(curl接管和CLI接管)一起看,OpenClaw的远程管理能力你就全掌握了。

按功能域分类
功能域
方法数
代表方法
系统信息 & 健康
15
health / status / diagnostics.stability / logs.tail
Agent管理
10
agents.list / create / update / delete / files.set
会话 (Sessions)
20
sessions.list / create / send / subscribe / compact
聊天 (Chat)
5
chat.send / history / abort
渠道 (Channels)
4
channels.status / start / stop / logout
模型 (Models)
3
models.list / authStatus / authLogout
工具 (Tools)
3
tools.catalog / effective / invoke
技能 (Skills)
9
skills.status / install / search / upload
配置 (Config)
6
config.get / patch / apply / schema
定时任务 (Cron)
8
cron.list / add / remove / run / runs
任务 (Tasks)
3
tasks.list / get / cancel
节点 & 设备配对
22
node.* / device.pair.* / device.token.*
语音 & TTS
16
tts.* / talk.* / talk.realtime.*
语音唤醒
4
voicewake.get / set / routing.*
环境 & 产物
5
environments.* / artifacts.*
插件 & 向导
6
plugins.* / wizard.*
审批 (Approvals)
9
exec.approval.* / plugin.approval.*
密钥 & 更新
4
secrets.* / update.status / update.run
记忆 & 诊断
8
doctor.memory.*
按Scope分类
Scope
方法数
用途
无需
1
health(公开)
read
60+
所有查询类方法
write
30+
sessions.send / chat.send / tts.convert 等
admin
50+
所有配置/创建/删除/更新类方法
pairing
12
节点和设备配对相关
approvals
5
执行和插件审批
node
5
节点专用方法
dynamic
1
plugins.sessionAction(动态判定)
完整事件清单

握手成功后会推送的事件共25个,按触发频率分类:

类型
事件名
高频(>1次/分钟)
tick / health
中频(任务触发)
chat / agent / session.message / session.tool / sessions.changed / heartbeat / cron
低频(状态变更)
presence / shutdown / update.available / voicewake.changed / voicewake.routing.changed / talk.mode
配对相关
node.pair.requested / node.pair.resolved / node.invoke.request / device.pair.requested / device.pair.resolved
审批相关
exec.approval.requested / exec.approval.resolved / plugin.approval.requested / plugin.approval.resolved
18OpenClaw远程管理三板斧总结

到这里,OpenClaw远程管理的三种方式就讲完了:

第1篇
Admin HTTP RPC
一个POST搞定
自动化脚本 / CI/CD / 监控系统
第2篇
CLI 远程调用
一次性的刀
运维终端 / 快速调试
第3篇
WebSocket JSON-RPC
持续在线的神经网络
Web UI / 实时管理 / 自研前端

三种方式不是替代关系,是分工关系:

◆ 想要快速跑个健康检查脚本 -> Admin HTTP RPC
◆ 在终端里日常调用 -> CLI
◆ 写一个完整的管理前端 -> WebSocket

把三篇都看完,OpenClaw的远程管理能力你就吃透了。下一步就是动手写代码——用任何你喜欢的语言,对接最适合的协议,把OpenClaw真正变成你自己的智能体平台。

收藏这三篇,遇到任何远程管理的问题翻一翻基本都能找到答案。

下篇见。

版权声明:本文由AI技术博客原创,转载请注明出处。

#openclaw#龙虾#RPC#管理页面