握手与角色
第一帧以及返回的内容:连接请求、hello-ok 载荷、worker 角色、客户端能力,以及角色和范围模型。
握手¶
Gateway 发送预连接质询:
设备认证客户端将质询中的 ts 用作 connect.params.device.signedAt。
对于 WebSocket 质询,ts 必须是非负整数。明确支持 connect.challenge 出现之前的 Gateway 的客户端,仅在未收到质询时可以使用本地时间;如果收到的质询缺少 ts 或 ts 格式无效,则该质询无效。
客户端使用 connect 回复:
{
"type": "req",
"id": "…",
"method": "connect",
"params": {
"minProtocol": 4,
"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 使用 hello-ok 响应:
{
"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,
"attachments": { "maxBytes": 20971520, "maxImageBytes": 6291456 }
}
}
}
server、features、snapshot、policy 和 auth 均由 HelloOkSchema(packages/gateway-protocol/src/schema/frames.ts)要求必填。auth 报告协商后的角色以及当前 socket 的有效授权范围,即使未颁发设备令牌也是如此(形状如上)。deviceToken 如果存在,则是同一设备和角色的主要可复用凭证。controlUiUrl 可选地通告 Gateway 配置的公共 Control UI 源和基础路径,用于可分享链接,独立于客户端的隧道或开发服务器地址。当 gateway.publicOrigin 未设置或 Control UI 被禁用时,它会省略。它不包含任何凭证,也不授予任何访问权限。policy.attachments 是可选的(旧版 Gateway 会省略它),并通告聊天附件在 chat.send、sessions.send 以及会话创建初始轮次中面临的解码大小上限:
| 字段 | 含义 |
|---|---|
maxBytes |
单个附件接受的最大解码大小(agents.defaults.mediaMaxMb,默认 20 MB) |
maxImageBytes |
单个图像接受的最大解码大小:min(maxBytes, 6 MB agent-hydration cap) |
发送前验证:
- 对每个文件的解码大小进行检查:图像对照
maxImageBytes,其他所有文件对照maxBytes。 - 序列化整个请求,并将其编码大小与
policy.maxPayload进行比较。policy.attachments是单个附件的上限,绝不保证帧能容纳:附件以 base64 传输,因此 20 MB 文件在链路上约为 26.7 MB,仅凭自身就会超过默认的 25 MiB 帧限制。 - 对于其他所有内容,以服务器为权威。接受的 MIME 类型和逐消息处理被刻意不通告,因为它们取决于入口点、解析后的模型以及载荷嗅探。Gateway 可以返回类型化拒绝,而仅文本模型运行可以在其卸载上限之后省略额外图像,并仍然完成请求。
- 每次重连时重新读取这些值。它们是连接时的快照,因此对
mediaMaxMb的实时编辑只有在现有连接重连后才会到达这些连接。
pluginSurfaceUrls 是可选的,将插件 surface 名称(例如 canvas)映射到作用域内的托管 URL;它可能会过期,因此节点使用 { "surface": "canvas" } 调用 node.pluginSurface.refresh 以获取新条目。Control UI 仅在 hello 授予的范围满足 operator.read(包括 operator.write 和 operator.admin)时使用 plugin.surface.refresh。FORBIDDEN 响应会停止当前租约的自动续期重试;重连会评估新 hello 的授权。已弃用的 canvasHostUrl / canvasCapability / node.canvas.capability.refresh 路径不受支持;请使用插件 surface。sessions.observer.ask 方法已移除;请使用 sessions.companion.ask。快照中可选的 appliedConfigHash 是活动 Gateway 运行时接受的已解析源配置修订版本。客户端可以将其与 config.get.configRevisionHash 进行比较,以确定较新的已保存配置是否仍需要重启。config.get.hash 是配置写入冲突保护使用的不透明的已编写修订版本。它涵盖根文件字节以及所捕获的包含文件的标识和内容。
快照中可选的 controlUiIdentityUrl 在 Gateway 使用 trusted-proxy 或 Tailscale Serve 身份时,通告活动 Gateway 的 HTTPS 仪表板 URL。operator 客户端可以打开此 URL 进行个人浏览器登录,而不是转发共享设备凭证。该 URL 包含 Control UI 基础路径;客户端必须使用常规 HTTPS 信任,而不是原生 TLS 固定,并且不得向其发送原生连接令牌或密码。从每个已认证的 hello 快照中重新读取它,并在该连接关闭时丢弃它。如果受管理的 Serve 路由退出或被替换,Gateway 会以代码 1012 关闭收到其身份 URL 的连接;重连以发现当前路由。
openclaw.setup.verify 还会在其实时推理探测前后检查 Gateway 当前的应用和重启状态。当已保存的设置未生效、仍有重启工作未完成,或已验证的运行时在探测期间发生变化时,它会返回 { ok: false, status: "unavailable", error }。客户端应保留所选模型,并在应用或重启完成后重试。独立 CLI 验证仍然可以测试已保存的配置,而不需要正在运行的 Gateway。
当 gateway 仍在完成启动 sidecars 时,connect 可能返回可重试的 UNAVAILABLE 错误,并带有 details.reason: "startup-sidecars" 和 retryAfterMs。请在连接预算内重试,而不是将其视为终端握手失败。
当颁发设备 token 时,hello-ok.auth 会添加它:
```json validate=false { "auth": { "deviceToken": "…", "role": "operator", "scopes": ["operator.read"] } }
内置 QR/setup-code 引导是移动端交接路径。一次成功的基线 setup-code 连接会返回一个主 node token,外加一个受限的 operator token:
```json validate=false
{
"auth": {
"deviceToken": "…",
"role": "node",
"scopes": [],
"deviceTokens": [
{
"deviceToken": "…",
"role": "operator",
"scopes": ["operator.approvals", "operator.read", "operator.talk.secrets", "operator.write"]
}
]
}
}
此 operator 交接是有意受限的:足以启动移动端 operator 循环和原生 setup,其中 operator.write 满足 Talk 会话,operator.talk.secrets 覆盖 Talk 配置读取,但没有配对变更 scopes,也没有 operator.admin。更广泛的配对/管理员访问需要单独的已批准配对或 token 流程。仅当引导认证通过可信传输(wss:// 或 loopback/本地配对)运行时,才持久化 hello-ok.auth.deviceTokens。
受信任的本地 backend 客户端(client.id: "gateway-client"、client.mode: "backend")在使用共享 gateway token/密码进行认证时,可以在直接 loopback 连接上省略 device。此路径保留给内部控制平面 RPC(例如 subagent 会话更新),可避免过期的 CLI/device 配对基线阻塞本地 backend 工作。当该 backend 提供已签名的设备身份时,此例外也适用:它不会创建配对记录,因此未配对的身份不会获得设备 token。远程、浏览器来源、node 和非 backend 客户端遵循其正常的配对和 scope 升级策略。设备 token 认证在任何本地 backend 配对例外之前,仍会验证现有 token 的 role 和 scopes。
Worker 角色与封闭协议¶
由 Gateway 拥有的启动器通过 openclaw worker 启动 worker 进程;它不是手动注册命令。Workers 通过主 TLS 端点上的公共 /__openclaw__/worker WebSocket 路径,或通过 gateway 拥有、主机密钥固定的 SSH 隧道到达的专用 loopback 入口,使用封闭协议。该路由在读取帧之前选择 worker 模式,因此它从不分发通用 auth、node 事件、operator RPC 或插件方法。公共准入共享主每客户端预认证预算和认证速率限制器;其线路错误会将凭据和环境细节折叠为 admission-rejected,而受信任的 gateway 诊断保留内部原因。严格的 connect 会验证一个静态哈希的短期凭据,该凭据绑定到环境、bundle 哈希、owner epoch、RPC 集版本、过期时间以及一个可空 session;它会单独检查当前版本和功能集。成功时返回最小的 worker-hello-ok;功能协商独立于通用协议版本。帧保持在 64 KiB 以下,但协商后的 worker.inference.start 帧最多可达 25 MiB。封闭允许列表包含 worker.heartbeat、worker.transcript.commit、worker.live-event、worker.inference.start 和 worker.inference.cancel。
对于经过身份审计的附加运行,live turn 能力可以将凭据、build、owner-epoch 和 placement 检查记录为一个强制的准入回执。该回执不包含任何凭据、build 哈希、token、环境 id 或 session id。Worker 操作行和 placement 状态仍然是其权威所有者;成功连接不是操作成功回执。
Transcript 提交使用 owner-epoch 围栏、gateway 拥有的 session 绑定、base-leaf 比较并交换以及持久化序列重放;gateway 通过常规 session 写入器生成 transcript 条目和父 ID。每次 RPC 都会重新检查所有权和过期时间。
客户端能力¶
Operator 客户端可以在 connect.params.caps 中声明可选能力:
tool-events:接受结构化工具生命周期事件。inline-widgets:可以渲染托管的内联 widget 工具结果。ultrafast:接受会话元数据中显式的"ultrafast"快速模式值。 如果没有它,对于仅接受布尔值和"auto"的已发布原生解码器,会话响应和事件会将该值呈现为 Fast(true)。这是按连接呈现的:存储的选择和执行仍保持为"ultrafast"。 升级后的客户端会声明此能力以保留显式选择;仅当这些已发布客户端不再受支持时,才移除旧版投影。chat-only-assistant-text:从chat渲染助手文本,并省略冗余的助手文本agent流。参见 事件族。
客户端能力描述的是已连接的客户端,而不是授权。Agent 工具可以声明所需能力;除非所有要求都出现在源客户端的 caps 中,否则 Gateway 会省略这些工具。通道来源的运行没有 Gateway 客户端能力,因此即使工具策略明确允许,能力门控的工具也不可用。
节点连接示例¶
{
"type": "req",
"id": "…",
"method": "connect",
"params": {
"minProtocol": 4,
"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": "…"
}
}
}
节点在连接时声明其能力:
caps:高级类别,例如camera、canvas、screen、location、voice、talk。commands:用于 invoke 的命令允许列表。permissions:细粒度开关(例如screen.record、camera.capture)。
Gateway 将这些视为声明,并强制执行服务端允许列表。
角色与作用域¶
完整的 Operator 作用域模型、审批时检查以及共享密钥语义,参见 Operator 作用域。
角色:
operator:控制平面客户端(CLI/UI/自动化)。node:能力主机(camera/screen/canvas/system.run)。worker:专用封闭 worker 协议上的云执行主机。
Operator 作用域(src/gateway/operator-scopes.ts),完整的封闭集合:
operator.readoperator.writeoperator.adminoperator.approvalsoperator.questionsoperator.pairingoperator.talkoperator.talk.secrets
operator.write 仍然满足 operator.talk,以兼容现有客户端。语音设备设置可以颁发更窄的 Talk 授权,而不需要通用的 Gateway 写访问权限。
talk.config 带有 includeSecrets: true 时,需要 operator.talk.secrets(或
operator.admin)。包含密钥时,从 talk.resolved.config.apiKey 读取当前生效的 Talk 提供商凭据;talk.providers.<id>.apiKey
保持源结构,并且可以是 SecretRef 对象或脱敏字符串。
插件注册的 Gateway RPC 方法可以请求其自身的 operator 作用域,
但这些保留的核心前缀始终解析为 operator.admin
(src/shared/gateway-method-policy.ts):config.*、exec.approvals.*、
wizard.*、update.*。
方法作用域只是第一道关卡。通过
chat.send 访问的一些斜杠命令会应用更严格的命令级检查:持久化的 /config set 和
/config unset 写入需要 operator.admin,即使对于已经持有较低 operator 作用域的 Gateway 客户端也是如此。
node.pair.approve 在基础方法作用域(operator.pairing)之上还有一个额外的审批时作用域检查,基于待处理请求声明的
commands(src/infra/node-pairing-authz.ts):
| 声明的命令 | 所需作用域 |
|---|---|
| 无 | operator.pairing |
| 普通命令 | operator.pairing + operator.write |
包含 system.run、system.run.prepare、system.which、browser.proxy、browser.proxy.upload.v1、fs.listDir 或 system.execApprovals.get/set |
operator.pairing + operator.admin |
在本表中,fs.listDir 是通过 node.invoke 转发的节点命令。
顶层 Gateway fs.listDir RPC 在用于工作区内的主机浏览时需要 operator.write,当存在 nodeId 时需要 operator.admin。
必须按 fs.listDir 返回的目录路径原样传递:目录名称中的空白字符(包括末尾空格)具有意义。
能力/命令/权限(节点)¶
节点在连接时声明其能力:
caps:高级能力类别,例如camera、canvas、screen、location、voice和talk。commands:用于 invoke 的命令允许列表。permissions:细粒度开关(例如screen.record、camera.capture)。
Gateway 将这些视为声明,并强制执行服务端允许列表。
已连接节点可以在成功连接或重新连接后,使用 node.pluginTools.update 发布可选的、对 agent 可见的插件或 MCP 工具
描述符。无头节点主机需要重启以应用声明式 MCP 清单
变更。该更新方法是唯一的发布路径;connect 参数中不接受插件工具描述符。每个描述符必须使用对 provider 安全的工具 name,并指定
节点当前命令允许列表中的一个 command。Gateway 信任来自已配对节点的描述符
元数据,过滤超出已批准命令范围的描述符,在节点断开连接时移除它们,并拒绝 operator 尝试
修改其他节点目录的操作。设置 gateway.nodes.pluginTools.enabled: false
以忽略节点发布的描述符。
已连接节点主机使用 node.skills.update 发布其完整的技能替换目录。
该节点角色方法是唯一的节点技能发布路径;connect 参数中不接受技能。每个描述符包含一个
安全名称、描述以及有界的 SKILL.md 内容。Gateway 使用常规技能加载器解析该内容,在节点连接期间将其包含在 agent 技能快照中,并在断开连接时移除。设置
gateway.nodes.allowSkills: false 以忽略节点发布的技能。
本页原文 Markdown:在 AtomGit 查看·内容源自开源项目 cl/openclaw