OpenClaw 源码导读(二):Gateway 控制平面 — 一条 WebSocket 连上所有人
在系列第一篇里我们看到,OpenClaw 的核心隐喻是 “Gateway 是控制平面”。它不存转储消息、不排队、不持久化事件流,而是一个长在 127.0.0.1:18789 上的状态化 RPC 服务器,所有客户端(CLI、macOS.app、iOS/Android Node、WebChat 浏览器、Canvas iframe)都靠一条 WebSocket 接入。 这一篇拆开控制平面的五个层面: 进程入口:CLI 怎么从 openclaw gateway 走到 Node 进程常驻 WebSocket 协议:握手帧、RPC 帧、Event 帧、状态快照与版本号 鉴权矩阵:Token / Password / Device Token / Bootstrap Token / Tailscale Whois / Trusted Proxy Pairing:陌生客户端怎么合法加入(setup code、pairing store、allowFrom) Session 模型:Session Key 组成规则、多 Agent 路由、Session ID 模糊匹配 注:下文所有 src/ 路径都指 openclaw/openclaw 仓库根下。 一、CLI 到 Gateway 的六段路径 第一篇概览过 src/entry.ts 和 src/cli/run-main.ts,这里按调用序重新画一遍,把每个关键函数在 graph 上标出来: flowchart TD A0["openclaw gateway --port 18789"] --> A1 A1["entry.tsmain guard + process.title"] --> A2 A2["normalizeEnv + enableCompileCache()"] --> A3 A3["ensureCliRespawnReady()按需 spawn 子 Node"] --> A4 A4["parseCliContainerArgsparseCliProfileArgs"] --> A5 A5["tryHandleRootVersionFastPathtryHandleRootHelpFastPath"] -->|miss| A6 A6["runMainOrRootHelp → runCli(argv)"] --> B0 B0["run-main.tscontainer? container in progress"] --> B1 B1["loadCliDotEnv (~/.openclaw/.env)"] --> B2 B2["assertSupportedRuntime (Node≥22.16)"] --> B3 B3["tryRouteCli(fast-path 子命令路由)"] -->|miss| B4 B4["buildProgram() + commander 注册"] --> B5 B5["installUnhandledRejectionHandler"] --> B6 B6["registerPluginCliCommandsFromValidatedConfig (lazy)"] --> B7 B7["program.parseAsync"] --> C0 C0["gateway 命令匹配 → gatewayCommandAction"] --> C1 C1["lock 文件 + 端口 acquire"] --> C2 C2["HTTP/WS server 启动"] --> C3 C3["channels/plugins/cron/canvas-host 启动"] --> C4["Ready"] 1. 为什么要自己 spawn 自己 ensureCliRespawnReady() 是 src/entry.ts 里最容易被忽视但非常重要的段落: ...