Appearance
实现或调试 OpenClaw Gateway WebSocket 客户端时,本文提供完整的协议细节,包括握手挑战、帧格式(请求/响应/事件)、角色(operator/node)与作用域、设备身份签名与配对、版本协商(当前协议 v4)、客户端常量(如超时和重试间隔)以及节点后台保活事件。还列出了常见 RPC 方法家族和广播事件的范围控制规则。对于设备认证迁移,提供了 error.details.code 对照表和修复建议。
OpenClaw Gateway WebSocket 协议:握手、帧格式与认证
Gateway WS 协议是 OpenClaw 的唯一控制面 + 节点传输通道。所有客户端(CLI、Web UI、macOS 应用、iOS/Android 节点、无头节点)均通过 WebSocket 连接,并在握手时声明自己的角色(role)和作用域(scope)。
传输层
- WebSocket,文本帧,JSON 载荷。
- 第一帧必须是
connect请求。 - 预连接帧最大 64 KiB。握手成功后,客户端应遵循
hello-ok.policy.maxPayload和hello-ok.policy.maxBufferedBytes限制。启用诊断时,超大的入站帧和慢的出站缓冲区会在 Gateway 关闭或丢弃之前发出payload.large事件。这些事件保留大小、限制、表面和安全的理由码,不保留消息体、附件内容、原始帧体、令牌、cookie 或密钥值。
握手(connect)
Gateway → 客户端(预连接挑战):
json
{
"type": "event",
"event": "connect.challenge",
"payload": { "nonce": "…", "ts": 1737264000000 }
}客户端 → Gateway:
json
{
"type": "req",
"id": "…",
"method": "connect",
"params": {
"minProtocol": 3,
"maxProtocol": 4,
"client": {
"id": "cli",
"version": "1.2.3",
"platform": "macos",
"mode": "operator"
},
"role": "operator",
"scopes": ["operator.read", "operator.write"],
"caps": [],
"commands": [],
"permissions": {},
"auth": { "token": "…" },
"locale": "en-US",
"userAgent": "openclaw-cli/1.2.3",
"device": {
"id": "device_fingerprint",
"publicKey": "…",
"signature": "…",
"signedAt": 1737264000000,
"nonce": "…"
}
}
}Gateway → 客户端:
json
{
"type": "res",
"id": "…",
"ok": true,
"payload": {
"type": "hello-ok",
"protocol": 4,
"server": { "version": "…", "connId": "…" },
"features": { "methods": ["…"], "events": ["…"] },
"snapshot": { "…": "…" },
"auth": {
"role": "operator",
"scopes": ["operator.read", "operator.write"]
},
"policy": {
"maxPayload": 26214400,
"maxBufferedBytes": 52428800,
"tickIntervalMs": 15000
}
}
}如果 Gateway 仍在完成启动 sidecars,connect 请求可能会返回可重试的 UNAVAILABLE 错误,details.reason 为 "startup-sidecars",并附带 retryAfterMs。客户端应在整体连接预算内重试,而不是将其视为最终握手失败。
server、features、snapshot 和 policy 都是 schema 要求的(src/gateway/protocol/schema/frames.ts)。auth 也是必需的,报告协商后的角色/作用域。pluginSurfaceUrls 是可选的,将插件表面名(如 canvas)映射到限域的托管 URL。
限域的插件表面 URL 可能会过期。节点可以调用 node.pluginSurface.refresh 并传入 { "surface": "canvas" },以便在 pluginSurfaceUrls 中获得新的条目。实验性的 Canvas 插件重构不支持已弃用的 canvasHostUrl、canvasCapability 或 node.canvas.capability.refresh 兼容路径;当前的原生客户端和 Gateway 必须使用插件表面。
未颁发设备令牌时,hello-ok.auth 报告协商的权限,不包含令牌字段:
json
{
"auth": {
"role": "operator",
"scopes": ["operator.read", "operator.write"]
}
}受信的同一进程后端客户端(client.id: "gateway-client",client.mode: "backend")可以在直接环回连接上省略 device,前提是它们使用共享的网关令牌/密码进行身份验证。此路径保留用于内部控制面 RPC,并防止过时的 CLI/设备配对基线阻塞本地后端工作(如子智能体会话更新)。远程客户端、浏览器来源客户端、节点客户端以及显式的设备令牌/设备身份客户端仍然使用正常的配对和作用域升级检查。
当颁发设备令牌时,hello-ok 还会包含:
json
{
"auth": {
"deviceToken": "…",
"role": "operator",
"scopes": ["operator.read", "operator.write"]
}
}内置的 QR/设置码引导是一种新的移动交接路径。成功的基线设置码连接会返回一个主节点令牌加一个受限的 operator 令牌:
json
{
"auth": {
"deviceToken": "…",
"role": "node",
"scopes": [],
"deviceTokens": [
{
"deviceToken": "…",
"role": "operator",
"scopes": ["operator.approvals", "operator.read", "operator.write"]
}
]
}
}operator 交接是有意限制的,以便 QR 初次安装可以启动移动 operator 循环,而不授予 operator.admin、operator.pairing 或 operator.talk.secrets。这些作用域需要单独的已批准 operator 配对或令牌流程。客户端应仅在 connect 使用了受信传输(如 wss:// 或环回/本地配对)上的引导身份验证时,才持久化 hello-ok.auth.deviceTokens。
节点示例
json
{
"type": "req",
"id": "…",
"method": "connect",
"params": {
"minProtocol": 3,
"maxProtocol": 4,
"client": {
"id": "ios-node",
"version": "1.2.3",
"platform": "ios",
"mode": "node"
},
"role": "node",
"scopes": [],
"caps": ["camera", "canvas", "screen", "location", "voice"],
"commands": ["camera.snap", "canvas.navigate", "screen.record", "location.get"],
"permissions": { "camera.capture": true, "screen.record": false },
"auth": { "token": "…" },
"locale": "en-US",
"userAgent": "openclaw-ios/1.2.3",
"device": {
"id": "device_fingerprint",
"publicKey": "…",
"signature": "…",
"signedAt": 1737264000000,
"nonce": "…"
}
}
}帧格式
- 请求(Request):
{type:"req", id, method, params} - 响应(Response):
{type:"res", id, ok, payload|error} - 事件(Event):
{type:"event", event, payload, seq?, stateVersion?}
有副作用的方法需要携带幂等性 key(参见 schema)。
角色与作用域
完整的 operator 作用域模型、审批时检查和共享密钥语义请参见 Operator 作用域。
角色
operator= 控制面客户端(CLI/UI/自动化)node= 能力宿主(摄像头/屏幕/画布/system.run)
作用域(operator)
常见作用域:
operator.readoperator.writeoperator.adminoperator.approvalsoperator.pairingoperator.talk.secrets
使用 includeSecrets: true 的 talk.config 需要 operator.talk.secrets(或 operator.admin)。
插件注册的 Gateway RPC 方法可以请求自己的 operator 作用域,但保留的核心 admin 前缀(config.*、exec.approvals.*、wizard.*、update.*)始终解析为 operator.admin。
方法作用域只是第一道门禁。通过 chat.send 触发的部分 slash 命令还会在此之上执行更严格的命令级别检查。例如,持久写入操作 /config set 和 /config unset 需要 operator.admin 作用域。
node.pair.approve 在基础方法作用域之上还有额外的审批时作用域检查:
- 无命令的请求:
operator.pairing - 包含非 exec 节点命令的请求:
operator.pairing+operator.write - 包含
system.run、system.run.prepare或system.which的请求:operator.pairing+operator.admin
能力/命令/权限(node)
节点在连接时声明能力:
caps:高级能力类别,如camera、canvas、screen、location、voice、talkcommands:用于调用的命令白名单permissions:细粒度开关(例如screen.record、camera.capture)
Gateway 将这些视为声明并在服务端执行白名单验证。
在线状态
system-presence返回以设备身份为 key 的条目。- 在线条目包含
deviceId、roles和scopes,方便 UI 为同时以 operator 和 node 两种角色连接的设备显示单行记录。 node.list包含可选的lastSeenAtMs和lastSeenReason字段。已连接的节点将其当前连接时间报告为lastSeenAtMs,原因为connect;已配对节点也可以在受信节点事件更新其配对元数据时报告持久的后台存在。
节点后台保活事件
节点可以调用 node.event,传入 event: "node.presence.alive",记录已配对节点在后台唤醒时的存活状态,而不标记其为已连接。
json
{
"event": "node.presence.alive",
"payloadJSON": "{\"trigger\":\"silent_push\",\"sentAtMs\":1737264000000,\"displayName\":\"Peter's iPhone\",\"version\":\"2026.4.28\",\"platform\":\"iOS 18.4.0\",\"deviceFamily\":\"iPhone\",\"modelIdentifier\":\"iPhone17,1\",\"pushTransport\":\"relay\"}"
}trigger 是一个封闭的枚举:background、silent_push、bg_app_refresh、significant_location、manual 或 connect。未知的触发器字符串在持久化前会被 Gateway 规范化为 background。该事件仅对经过身份验证的节点设备会话持久化;无设备或未配对的会话返回 handled: false。
成功的 Gateway 会返回结构化结果:
json
{
"ok": true,
"event": "node.presence.alive",
"handled": true,
"reason": "persisted"
}较旧的 Gateway 可能仍返回 { "ok": true } 作为对 node.event 的响应;客户端应将其视为已确认的 RPC,而非持久的在线状态。
广播事件的作用域控制
服务端推送的 WebSocket 广播事件受作用域门控,使配对作用域或仅节点会话不会被动接收会话内容。
- Chat、agent 和工具结果帧(包括流式
agent事件和工具调用结果)至少需要operator.read。没有operator.read的会话会跳过这些帧。 - 插件定义的
plugin.*广播根据插件注册方式,门控到operator.write或operator.admin。 - 状态和传输事件(
heartbeat、presence、tick、连接/断开生命周期等)保持不受限制,以便每个经过身份验证的会话都能观察传输健康。 - 未知的广播事件家族默认受作用域门控(故障关闭),除非已注册的处理程序明确放宽。
每个客户端连接维护自己的客户端序列号,因此即使不同客户端看到作用域过滤后的不同事件子集,广播也能在该 socket 上保持单调排序。
常见 RPC 方法家族
公有 WS 表面比上述握手/认证示例更广。这不是自动生成的转储——hello-ok.features.methods 是从 src/gateway/server-methods-list.ts 加上已加载插件/渠道方法导出构建的保守发现列表。请将其视为功能发现,而非 src/gateway/server-methods/*.ts 的完整枚举。
系统与身份
health:返回缓存或最新探测的 Gateway 健康快照diagnostics.stability:返回最近有界的诊断稳定性记录器。它保留操作元数据(事件名称、计数、字节大小、内存读数、队列/会话状态、渠道/插件名称和会话 ID)。不保留聊天文本、webhook 请求体、工具输出、原始请求或响应体、令牌、cookie 或密钥值。需要operator.read作用域status:返回/status风格的 Gateway 摘要;敏感字段仅向 admin 作用域的 operator 客户端开放gateway.identity.get:返回 relay 和配对流程使用的 Gateway 设备身份system-presence:返回已连接 operator/节点设备的当前在线快照system-event:追加系统事件,可更新/广播在线上下文last-heartbeat:返回最新持久化的心跳事件set-heartbeats:切换 Gateway 上的心跳处理
模型与用量
models.list:返回运行时允许的模型目录。传入{ "view": "configured" }获取选择器大小的已配置模型(优先使用agents.defaults.models,然后是models.providers.*.models),传入{ "view": "all" }获取完整目录usage.status:返回提供商用量窗口/剩余配额摘要usage.cost:返回日期范围内的聚合成本摘要doctor.memory.status:返回默认 agent 工作区的向量记忆/embedding 就绪状态。仅在调用方明确需要实时 embedding 提供商 ping 时才传入{ "probe": true }或{ "deep": true }doctor.memory.remHarness:返回远程控制面客户端的有界只读 REM harness 预览。可能包含工作区路径、记忆片段、渲染的 grounded markdown 和深度提升候选,因此调用方需要operator.readsessions.usage:返回每会话用量摘要sessions.usage.timeseries:返回某会话的时序用量sessions.usage.logs:返回某会话的用量日志条目
渠道与登录助手
channels.status:返回内置 + 捆绑渠道/插件的状态摘要channels.logout:注销支持注销的特定渠道/账号web.login.start:启动当前支持 QR/网页登录的渠道提供商的登录流程web.login.wait:等待 QR/网页登录流程完成,成功后启动渠道push.test:向已注册的 iOS 节点发送测试 APNs 推送voicewake.get:返回存储的唤醒词触发器voicewake.set:更新唤醒词触发器并广播变更
消息与日志
send:在聊天执行器外,直接向渠道/账号/线程发送消息的出站投递 RPClogs.tail:返回带游标/限制和最大字节控制的 Gateway 文件日志尾
Talk 与 TTS
talk.catalog:返回只读的 Talk 提供商目录,包含提供商标识、标签、已配置状态、暴露的模型/语音 ID、规范模式、传输方式、大脑策略以及实时音频/能力标志。不返回提供商密钥或变更全局配置talk.config:返回有效的 Talk 配置载荷;includeSecrets需要operator.talk.secrets(或operator.admin)talk.session.create:创建 Gateway 拥有的 Talk 会话,用于realtime/gateway-relay、transcription/gateway-relay或stt-tts/managed-room。对于stt-tts/managed-room,传入sessionKey的operator.write调用方还必须传入spawnedBy以实现作用域会话 key 可见性;无作用域的sessionKey创建和brain: "direct-tools"需要operator.admintalk.session.join:验证托管房间会话令牌,根据需要发出session.ready或session.replaced事件,并返回房间/会话元数据及最近的 Talk 事件(不包含明文令牌或存储的令牌哈希)talk.session.appendAudio:将 base64 PCM 输入音频追加到 Gateway 拥有的实时中继和转录会话talk.session.startTurn、talk.session.endTurn、talk.session.cancelTurn:驱动托管房间的回合生命周期,在清除状态前拒绝过期的回合talk.session.cancelOutput:停止助理音频输出,主要用于 VAD 门控的抢话(barge-in)talk.session.submitToolResult:完成由 Gateway 拥有的实时中继会话发出的提供商工具调用。传入options: { willContinue: true }表示中间工具输出(稍后有最终结果),传入options: { suppressResponse: true }表示工具结果应满足提供商调用而不启动另一轮助理响应talk.session.close:关闭 Gateway 拥有的中继、转录或托管房间会话,并发出终端 Talk 事件talk.mode:设置/广播当前 Talk 模式状态(用于 WebChat/控制 UI 客户端)talk.client.create:创建客户端拥有的实时提供商会话,使用webrtc或provider-websocket,同时 Gateway 拥有配置、凭据、指令和工具策略talk.client.toolCall:让客户端拥有的实时传输将提供商工具调用转发给 Gateway 策略。第一个支持的工具是openclaw_agent_consult;客户端收到运行 ID,然后等待正常的聊天生命周期事件,再提交提供商特定的工具结果talk.event:Talk 事件通道,用于实时、转录、STT/TTS、托管房间、电话和会议适配器talk.speak:通过当前激活的 Talk 语音提供商合成语音tts.status:返回 TTS 启用状态、当前提供商、回退提供商和提供商配置状态tts.providers:返回可见的 TTS 提供商清单tts.enable/tts.disable:切换 TTS 偏好状态tts.setProvider:更新首选 TTS 提供商tts.convert:运行一次性文本转语音转换
Secrets、配置、更新与向导
secrets.reload:重新解析活跃的 SecretRef,仅在完全成功时替换运行时密钥状态secrets.resolve:解析特定命令/目标集的命令目标密钥分配config.get:返回当前配置快照和哈希config.set:写入已验证的配置载荷config.patch:合并部分配置更新config.apply:验证并替换完整配置载荷config.schema:返回控制 UI 和 CLI 工具使用的实时配置 schema 载荷:schema、uiHints、版本和生成元数据,包括运行时能加载的插件 + 渠道 schema 元数据。schema 包含从 UI 使用的相同标签和帮助文本派生的字段title/description元数据,包括嵌套对象、通配符、数组项以及anyOf/oneOf/allOf组合分支(当匹配的字段文档存在时)config.schema.lookup:返回某配置路径的路径作用域查找载荷:归一化路径、浅层 schema 节点、匹配的 hint +hintPath、可选的reloadKind,以及用于 UI/CLI 下钻的直接子摘要。reloadKind是restart、hot或none之一,反映 Gateway 对该路径的配置重载计划。查找 schema 节点保留用户面向的文档和常见验证字段(title、description、type、enum、const、format、pattern、数字/字符串/数组/对象边界,以及additionalProperties、deprecated、readOnly、writeOnly等标志)。子摘要暴露key、归一化path、type、required、hasChildren、可选的reloadKind,以及匹配的hint/hintPathupdate.run:运行 Gateway 更新流程,仅在更新本身成功时才安排重启;带有会话的调用方可包含continuationMessage,以便启动时通过重启延续队列恢复一次代理回合。包管理器的控制面更新使用分离的托管服务交接,而不是替换实时 Gateway 内的包树。已启动的交接返回ok: true并带result.reason: "managed-service-handoff-started"和handoff.status: "started";不可用或失败的交接返回ok: false并带managed-service-handoff-unavailable或managed-service-handoff-failed,同时handoff.command在需要手动 shell 更新时出现。在已启动的交接期间,重启哨兵可能短暂报告stats.reason: "restart-health-pending";延续会延迟,直到 CLI 验证重启的 Gateway 并写入最终ok哨兵update.status:返回最新的缓存更新重启哨兵,包括可用时的重启后运行版本wizard.start、wizard.next、wizard.status、wizard.cancel:通过 WS RPC 暴露引导向导
Agent 与工作区助手
agents.list:返回已配置的 agent 条目,包括有效模型和运行时元数据agents.create、agents.update、agents.delete:管理 agent 记录和工作区接线agents.files.list、agents.files.get、agents.files.set:管理某 agent 暴露的 bootstrap 工作区文件tasks.list、tasks.get、tasks.cancel:向 SDK 和 operator 客户端暴露 Gateway 任务账本artifacts.list、artifacts.get、artifacts.download:暴露基于 transcript 的产物摘要和下载,限定于显式sessionKey、runId或taskId作用域。运行和任务查询在服务端解析所属会话,仅返回具有匹配来源的 transcript 媒体;不安全或本地 URL 来源返回不支持的下载,而不是服务端抓取environments.list和environments.status:为 SDK 客户端暴露只读的 Gateway 本地和节点环境发现agent.identity.get:返回某个 agent 或会话的有效助理身份agent.wait:等待运行完成并在可用时返回终端快照
会话控制
sessions.list:返回当前会话索引,包括配置了 agent 运行时后端时的每行agentRuntime元数据sessions.subscribe/sessions.unsubscribe:切换当前 WS 客户端的会话变更事件订阅sessions.messages.subscribe/sessions.messages.unsubscribe:切换某会话的转录/消息事件订阅sessions.preview:返回特定会话 key 的有界转录预览sessions.describe:返回精确会话 key 的一个 Gateway 会话行sessions.resolve:解析或规范化会话目标sessions.create:创建新会话条目sessions.send:向已有会话发送消息sessions.steer:活跃会话的中断并转向变体sessions.abort:中止会话的活跃工作。调用方可传入key加可选的runId,或单独传入runId(用于 Gateway 能解析到会话的活跃运行)sessions.patch:更新会话元数据/覆盖,并报告解析后的规范模型及有效agentRuntimesessions.reset、sessions.delete、sessions.compact:执行会话维护sessions.get:返回完整的存储会话行- 聊天执行仍然使用
chat.history、chat.send、chat.abort和chat.inject。chat.history是面向 UI 客户端显示规范化的:内联指令标签从可见文本中移除,纯文本工具调用 XML 载荷(包括<tool_call>...</tool_call>、<function_call>...</function_call>、<tool_calls>...</tool_calls>、<function_calls>...</function_calls>以及截断的工具调用块)和泄漏的 ASCII/全角模型控制令牌会被移除,纯静默令牌 assistant 行(如精确的NO_REPLY/no_reply)被省略,过大的行可以用占位符替换
设备配对与设备 Token
device.pair.list:返回待审批和已审批的配对设备device.pair.approve、device.pair.reject、device.pair.remove:管理设备配对记录device.token.rotate:在已审批的角色和作用域边界内轮换配对设备令牌device.token.revoke:撤销配对设备令牌
节点配对、调用与待处理工作
node.pair.request、node.pair.list、node.pair.approve、node.pair.reject、node.pair.remove、node.pair.verify:节点配对与 bootstrap 验证node.list/node.describe:返回已知/已连接的节点状态node.rename:更新配对节点标签node.invoke:将命令转发给已连接的节点node.invoke.result:返回调用请求的结果node.event:将节点发起的事件回传到 Gatewaynode.pending.pull/node.pending.ack:已连接节点的队列 APInode.pending.enqueue/node.pending.drain:管理离线/已断开节点的持久待处理工作
审批系列
exec.approval.request、exec.approval.get、exec.approval.list、exec.approval.resolve:一次性 exec 审批请求及待审批查找/重放exec.approval.waitDecision:等待一个待审批的 exec 审批并返回最终决定(超时返回null)exec.approvals.get/exec.approvals.set:管理 Gateway exec 审批策略快照exec.approvals.node.get/exec.approvals.node.set:通过节点中继命令管理节点本地 exec 审批策略plugin.approval.request、plugin.approval.list、plugin.approval.waitDecision、plugin.approval.resolve:插件定义的审批流程
自动化、技能与工具
- 自动化:
wake(调度立即或下次心跳的唤醒文本注入);cron.get、cron.list、cron.status、cron.add、cron.update、cron.remove、cron.run、cron.runs管理定时任务 cron.run仍是一个入队风格的 RPC。需要完成语义的客户端应读取返回的runId并轮询cron.runscron.runs接受可选的非空runId过滤器,以便客户端可以跟踪一个排队的单次运行,而不会与同一作业的其他历史条目产生竞态- 技能与工具:
commands.list、skills.*、tools.catalog、tools.effective、tools.invoke
常见事件系列
chat:UI 聊天更新,如chat.inject和其他仅 transcript 的聊天事件。在协议 v4 中,delta 载荷携带deltaText;message保持累积的 assistant 快照。非前缀替换设置replace=true并使用deltaText作为替换文本session.message、session.operation、session.tool:已订阅会话的 transcript、进行中会话操作和事件流更新sessions.changed:会话索引或元数据变更presence:系统在线快照更新tick:周期性 keepalive / 存活事件health:Gateway 健康快照更新heartbeat:心跳事件流更新cron:cron 运行/任务变更事件shutdown:Gateway 关闭通知node.pair.requested/node.pair.resolved:节点配对生命周期node.invoke.request:节点调用请求广播device.pair.requested/device.pair.resolved:配对设备生命周期voicewake.changed:唤醒词触发配置变更exec.approval.requested/exec.approval.resolved:exec 审批生命周期plugin.approval.requested/plugin.approval.resolved:插件审批生命周期
节点辅助方法
- 节点可调用
skills.bins获取当前技能可执行文件列表,用于自动允许检查
任务账本 RPC
Operator 客户端可以通过任务账本 RPC 检查和取消 Gateway 后台任务记录。这些方法返回清理后的任务摘要,而非原始运行时状态。
tasks.list需要operator.read。- 参数:可选的
status("queued"、"running"、"completed"、"failed"、"cancelled"、"timed_out")或这些状态的数组,可选的agentId、可选的sessionKey、可选的limit(1-500)、可选的字符串cursor。 - 结果:
{ "tasks": TaskSummary[], "nextCursor"?: string }。
- 参数:可选的
tasks.get需要operator.read。- 参数:
{ "taskId": string }。 - 结果:
{ "task": TaskSummary }。 - 缺失的任务 ID 返回 Gateway 的未找到错误形状。
- 参数:
tasks.cancel需要operator.write。- 参数:
{ "taskId": string, "reason"?: string }。 - 结果:
{ "found": boolean, "cancelled": boolean, "reason"?: string, "task"?: TaskSummary }。 found报告账本是否包含匹配的任务。cancelled报告运行时是否接受或记录了取消。
- 参数:
TaskSummary 包含 id、status 和可选的元数据,如 kind、runtime、title、agentId、sessionKey、childSessionKey、ownerKey、runId、taskId、flowId、parentTaskId、sourceId、时间戳、进度、终端摘要和清理后的错误文本。
Operator 辅助方法
- Operator 可以调用
commands.list(需要operator.read)来获取某个 agent 的运行时命令清单。agentId是可选的;省略则读取默认 agent 工作区。scope控制主要name的目标表面:text返回不带前导/的主要文本命令令牌native和默认的both路径在可用时返回提供商感知的原生名称
textAliases携带精确的斜杠别名,如/model和/m。nativeName在存在时携带提供商感知的原生命令名称。provider是可选的,仅影响原生命名和原生插件命令可用性。includeArgs=false从响应中省略序列化的参数元数据。
- Operator 可以调用
tools.catalog(需要operator.read)来获取某个 agent 的运行时工具目录。响应包含分组工具和来源元数据:source:core或pluginpluginId:当source="plugin"时的插件所有者optional:插件工具是否可选
- Operator 可以调用
tools.effective(需要operator.read)来获取某个会话的运行时有效工具清单。sessionKey是必需的。- Gateway 从服务端会话中派生受信运行时上下文,而不是接受调用方提供的 auth 或投递上下文。
- 响应是会话作用域的,反映当前对话可以使用的所有工具,包括 core、plugin 和 channel 工具。
- Operator 可以调用
tools.invoke(需要operator.write)通过与/tools/invoke相同的 Gateway 策略路径调用一个可用工具。name是必需的。args、sessionKey、agentId、confirm和idempotencyKey是可选的。- 如果同时提供
sessionKey和agentId,则解析的会话 agent 必须与agentId匹配。 - 响应是一个 SDK 面向的包装,包含
ok、toolName、可选的output和类型化的error字段。审批或策略拒绝会在 payload 中返回ok:false,而不是绕过 Gateway 工具策略管道。
- Operator 可以调用
skills.status(需要operator.read)来获取某个 agent 的可见技能清单。agentId是可选的;省略则读取默认 agent 工作区。- 响应包括资格、缺失要求、配置检查和清理后的安装选项,不暴露原始密钥值。
- Operator 可以调用
skills.search和skills.detail(需要operator.read)进行 ClawHub 发现元数据。 - Operator 可以调用
skills.upload.begin、skills.upload.chunk和skills.upload.commit(需要operator.admin)来暂存私有技能归档,然后再安装。这是一个用于受信客户端的独立 admin 上传路径,不是正常的 ClawHub 技能安装流程,默认禁用,除非skills.install.allowUploadedArchives已启用。skills.upload.begin({ kind: "skill-archive", slug, sizeBytes, sha256?, force?, idempotencyKey? })创建一个绑定到该 slug 和 force 值的上传。skills.upload.chunk({ uploadId, offset, dataBase64 })在精确的解码偏移处追加字节。skills.upload.commit({ uploadId, sha256? })验证最终大小和 SHA-256。commit 仅完成上传,不安装技能。- 上传的技能归档是包含根
SKILL.md的 zip 归档。归档的内部目录名从不选择安装目标。
- Operator 可以调用
skills.install(需要operator.admin),有三种模式:- ClawHub 模式:
{ source: "clawhub", slug, version?, force? }将技能文件夹安装到默认 agent 工作区的skills/目录。 - 上传模式:
{ source: "upload", uploadId, slug, force?, sha256?, timeoutMs? }将已提交的上传安装到默认 agent 工作区的skills/<slug>目录。slug 和 force 值必须与原始skills.upload.begin请求匹配。此模式被拒绝,除非skills.install.allowUploadedArchives已启用。该设置不影响 ClawHub 安装。 - Gateway 安装程序模式:
{ name, installId, dangerouslyForceUnsafeInstall?, timeoutMs? }在 Gateway 主机上执行声明的metadata.openclaw.install操作。
- ClawHub 模式:
- Operator 可以调用
skills.update(需要operator.admin),有两种模式:- ClawHub 模式更新默认 agent 工作区中的一个 tracked slug 或所有 tracked ClawHub 安装。
- 配置模式修补
skills.entries.<skillKey>值,如enabled、apiKey和env。
models.list 视图
models.list 接受可选的 view 参数:
- 省略或
"default":当前运行时行为。如果配置了agents.defaults.models,则响应是允许的目录,包括动态发现的provider/*条目。否则响应是完整的 Gateway 目录。 "configured":选择器大小的行为。如果配置了agents.defaults.models,它仍然优先,包括provider/*条目的提供商作用域发现。如果没有 allowlist,则响应使用显式的models.providers.*.models条目,仅当没有已配置的模型行时回退到完整目录。"all":完整的 Gateway 目录,绕过agents.defaults.models。用于诊断和发现 UI,而不是正常的模型选择器。
Exec 审批
- 当 exec 请求需要审批时,Gateway 会广播
exec.approval.requested。 - Operator 客户端通过调用
exec.approval.resolve(需要operator.approvals作用域)来解决审批。 - 对于
host=node,exec.approval.request必须包含systemRunPlan(规范argv/cwd/rawCommand/会话元数据)。缺少systemRunPlan的请求会被拒绝。 - 审批后,转发的
node.invoke system.run调用会重用该规范systemRunPlan作为权威命令/cwd/会话上下文。 - 如果调用方在准备和最终批准的
system.run转发之间改变了command、rawCommand、cwd、agentId或sessionKey,Gateway 会拒绝运行,而不是信任改变后的载荷。
Agent 投递回退
agent请求可以包含deliver=true以请求出站投递。bestEffortDeliver=false保持严格行为:无法解析或仅限内部的投递目标返回INVALID_REQUEST。bestEffortDeliver=true允许回退到仅会话执行,当无法解析外部可投递路由时(例如内部/WebChat 会话或模糊的多渠道配置)。- 最终的
agent结果可能包含result.deliveryStatus,当请求了投递时,使用与openclaw agent --json --deliver相同的sent、suppressed、partial_failed和failed状态。
版本管理
PROTOCOL_VERSION定义在src/gateway/protocol/version.ts。- 客户端发送
minProtocol+maxProtocol;服务端拒绝不包含其当前协议的版本范围。当前客户端和服务端需要协议 v4。 - Schema 和模型由 TypeBox 定义生成:
pnpm protocol:genpnpm protocol:gen:swiftpnpm protocol:check
客户端常量
参考客户端在 src/gateway/client.ts 中使用这些默认值。它们在协议 v4 中稳定,是第三方客户端的预期基线。
| 常量 | 默认值 | 来源 |
|---|---|---|
PROTOCOL_VERSION | 4 | src/gateway/protocol/version.ts |
MIN_CLIENT_PROTOCOL_VERSION | 4 | src/gateway/protocol/version.ts |
| 请求超时(每次 RPC) | 30_000 ms | src/gateway/client.ts (requestTimeoutMs) |
| 预认证 / connect-challenge 超时 | 15_000 ms | src/gateway/handshake-timeouts.ts (配置/env 可以提高配对的服务器/客户端预算) |
| 初始重连回退 | 1_000 ms | src/gateway/client.ts (backoffMs) |
| 最大重连回退 | 30_000 ms | src/gateway/client.ts (scheduleReconnect) |
| 设备令牌关闭后的快速重试钳制 | 250 ms | src/gateway/client.ts |
强制停止优雅期(terminate() 前) | 250 ms | FORCE_STOP_TERMINATE_GRACE_MS |
stopAndWait() 默认超时 | 1_000 ms | STOP_AND_WAIT_TIMEOUT_MS |
默认 tick 间隔(hello-ok 前) | 30_000 ms | src/gateway/client.ts |
| Tick 超时关闭码 | 静默超过 tickIntervalMs * 2 时关闭码 4000 | src/gateway/client.ts |
MAX_PAYLOAD_BYTES | 25 * 1024 * 1024 (25 MB) | src/gateway/server-constants.ts |
服务端在 hello-ok 中广播有效的 policy.tickIntervalMs、policy.maxPayload 和 policy.maxBufferedBytes;客户端应遵循这些值,而不是握手前的默认值。
认证
- 共享密钥 Gateway 认证使用
connect.params.auth.token或connect.params.auth.password,取决于配置的认证模式。 - 身份承载模式(如 Tailscale Serve (
gateway.auth.allowTailscale: true) 或非环回gateway.auth.mode: "trusted-proxy")从请求头满足 connect 认证检查,而不是通过connect.params.auth.*。 - 私有入口
gateway.auth.mode: "none"完全跳过共享密钥 connect 认证;不要将该模式暴露在公共/非受信入口上。 - 配对后,Gateway 会颁发一个设备令牌,作用域绑定到连接角色和作用域。它在
hello-ok.auth.deviceToken中返回,客户端应持久化以便将来连接。 - 任何成功连接后,客户端都应持久化主
hello-ok.auth.deviceToken。 - 使用该存储的设备令牌重新连接时,还应重用该令牌的已批准作用域集合。这保留了已授予的读/探测/状态访问权限,并避免静默地将重新连接折叠到更窄的隐式仅 admin 作用域。
- 客户端侧 connect 认证组装(
selectConnectAuth在src/gateway/client.ts中):auth.password是正交的,设置后始终转发。auth.token按优先级顺序填充:首先是显式共享令牌,然后是显式deviceToken,然后是存储的按设备令牌(以deviceId+role为 key)。auth.bootstrapToken仅当以上都没有解析出auth.token时才发送。共享令牌或任何已解析的设备令牌会抑制它。- 在一次性
AUTH_TOKEN_MISMATCH重试中自动提升存储的设备令牌仅限于受信端点——环回或带有固定tlsFingerprint的wss://。没有固定的公共wss://不符合条件。
- 内置设置码引导返回主节点
hello-ok.auth.deviceToken加上hello-ok.auth.deviceTokens中的受限 operator 令牌,用于受信移动交接。operator 令牌排除operator.admin、operator.pairing和operator.talk.secrets。 - 当非基线设置码引导正在等待批准时,
PAIRING_REQUIRED的详细信息包括recommendedNextStep: "wait_then_retry"、retryable: true和pauseReconnect: false。客户端应继续使用相同的引导令牌重新连接,直到请求被批准或令牌变得无效。 - 仅当 connect 使用了受信传输(如
wss://或环回/本地配对)上的引导认证时,才持久化hello-ok.auth.deviceTokens。 - 如果客户端提供显式
deviceToken或显式scopes,则调用方请求的作用域集保持权威;仅当客户端重用存储的按设备令牌时,才会重用缓存的作用域。 - 设备令牌可以通过
device.token.rotate和device.token.revoke轮换/吊销(需要operator.pairing作用域)。 device.token.rotate返回轮换元数据。仅对已使用该设备令牌认证的同一设备调用回显替换承载令牌,以便仅令牌客户端可以在重新连接前持久化其替换。共享/admin 轮换不回显承载令牌。- 令牌签发、轮换和吊销保持在该设备配对记录中批准的委托角色集范围内;令牌突变不能扩展或针对配对批准从未授予的设备角色。
- 对于配对设备令牌会话,设备管理是自作用域的,除非调用方也具有
operator.admin:非 admin 调用方只能移除/吊销/轮换它们自己的设备条目。 device.token.rotate和device.token.revoke还会检查目标 operator 令牌作用域集与调用方当前会话作用域。非 admin 调用方不能轮换或吊销比它们已有的更宽的 operator 令牌。- 认证失败包括
error.details.code加上恢复提示:error.details.canRetryWithDeviceToken(boolean)error.details.recommendedNextStep(retry_with_device_token、update_auth_configuration、update_auth_credentials、wait_then_retry、review_auth_configuration)
- 客户端处理
AUTH_TOKEN_MISMATCH的行为:- 受信客户端可以尝试使用缓存的按设备令牌进行一次有限重试。
- 如果该重试失败,客户端应停止自动重连循环并显示 operator 操作指导。
AUTH_SCOPE_MISMATCH表示设备令牌被识别但不覆盖请求的角色/作用域。客户端不应将其视为坏令牌;提示 operator 重新配对或批准更窄/更宽的作用域合约。
设备身份与配对
- 节点应携带从密钥对指纹派生的稳定设备身份(
device.id)。 - Gateway 按设备 + 角色颁发令牌。
- 新设备 ID 需要配对审批,除非启用本地自动审批。
- 配对自动审批以直接本地环回连接为中心。
- OpenClaw 还有一个狭窄的后端/容器本地自连接路径,用于受信的共享密钥辅助流程。
- 同主机 tailnet 或 LAN 连接在配对方面仍视为远程,需要审批。
- WS 客户端通常应在
connect时包含device身份(operator + 节点)。唯一的无设备 operator 异常是显式受信路径:gateway.controlUi.allowInsecureAuth=true用于仅 localhost 的不安全 HTTP 兼容模式- 成功的
gateway.auth.mode: "trusted-proxy"operator Control UI 认证 gateway.controlUi.dangerouslyDisableDeviceAuth=true(紧急方案,严重安全降级)- 直接环回
gateway-client后端 RPC,使用共享网关令牌/密码认证
- 所有连接必须对服务端提供的
connect.challengenonce 进行签名。
设备认证迁移诊断
对于仍使用挑战签名之前旧版行为的遗留客户端,connect 现在会在 error.details.code 中返回 DEVICE_AUTH_* 详细码,并在 error.details.reason 中提供稳定原因。
常见迁移失败:
| 消息 | details.code | details.reason | 含义 |
|---|---|---|---|
device nonce required | DEVICE_AUTH_NONCE_REQUIRED | device-nonce-missing | 客户端未提供 device.nonce(或为空) |
device nonce mismatch | DEVICE_AUTH_NONCE_MISMATCH | device-nonce-mismatch | 客户端使用了过期/错误的 nonce 进行签名 |
device signature invalid | DEVICE_AUTH_SIGNATURE_INVALID | device-signature | 签名载荷与 v2 载荷格式不匹配 |
device signature expired | DEVICE_AUTH_SIGNATURE_EXPIRED | device-signature-stale | 签名时间戳超出允许的时钟偏差范围 |
device identity mismatch | DEVICE_AUTH_DEVICE_ID_MISMATCH | device-id-mismatch | device.id 与公钥指纹不匹配 |
device public key invalid | DEVICE_AUTH_PUBLIC_KEY_INVALID | device-public-key | 公钥格式或规范化失败 |
迁移目标:
- 始终等待
connect.challenge。 - 使用包含服务端 nonce 的 v2 载荷进行签名。
- 在
connect.params.device.nonce中发送相同的 nonce。 - 推荐使用
v3签名载荷,它在 device/client/role/scopes/token/nonce 字段之外还绑定了platform和deviceFamily。 - 向后兼容:
v2签名仍被接受,但配对后设备元数据固定仍会在重连时控制命令策略。
TLS 与证书固定
- WS 连接支持 TLS。
- 客户端可选择性地固定 Gateway 证书指纹(参见
gateway.tls配置以及gateway.remote.tlsFingerprint或 CLI--tls-fingerprint)。
接口范围
本协议暴露了完整的 Gateway API(状态、渠道、模型、聊天、agent、会话、节点、审批等)。确切接口范围由 src/gateway/protocol/schema.ts 中的 TypeBox schema 定义。
相关文档
常见问题
connect 请求返回 UNAVAILABLE 错误怎么办?
表示 Gateway 仍在启动 sidecars。检查 error.details.reason 是否为 "startup-sidecars",如果是,根据 retryAfterMs 重试。应在整体连接预算内重试,不要视为最终失败。
设备配对时权限不足,提示 AUTH_SCOPE_MISMATCH 怎么处理?
设备令牌被识别但请求的角色/作用域不在已批准的范围内。需要重新配对或由 operator 审批更宽/更窄的作用域合约。不要将设备视为坏令牌,应提示操作者。
协议版本协商失败如何排查?
客户端发送 minProtocol 和 maxProtocol,服务端仅接受包含其 PROTOCOL_VERSION 的版本范围。当前双方都要求协议 v4。如果客户端发送范围不包括 v4,服务端会拒绝连接。运行 pnpm protocol:check 可以验证本地 schema 一致性。