Skip to content

OpenClaw 源码解读(二):Gateway 网关系统

模块:src/gateway/ | 版本:2026.3.2


一、模块概览

Gateway 是 OpenClaw 的中央控制平面——一个运行在本地的 HTTP + WebSocket 混合服务器。所有消息渠道(WhatsApp/Telegram/Discord 等)、AI Agent、定时任务、Control UI 和移动端 App 都通过 Gateway 协调工作。

Gateway 的核心职责:

  1. WebSocket RPC 服务 — 提供 JSON-RPC 风格的双向通信协议,供 CLI、Control UI、移动 App 调用
  2. 多渠道消息路由 — 启动和管理所有消息渠道的连接生命周期
  3. 认证与安全 — Token/密码/Tailscale/可信代理 四种认证模式 + 速率限制
  4. 配置热重载 — 监听配置文件变化,无需重启即可应用大部分配置更新
  5. Agent 运行编排 — 协调 AI Agent 的启动、消息流转、事件广播
  6. 插件宿主 — 加载和运行第三方插件,注入自定义 Gateway 方法
  7. 节点注册表 — 管理连接的客户端节点(CLI/App/浏览器),跟踪能力和权限
  8. 定时任务调度 — 运行 Cron 任务系统
  9. 健康监控 — 周期性检查渠道连接状态,对外暴露健康状态
  10. TLS/Tailscale 暴露 — 支持 HTTPS 和 Tailscale 网络访问

涉及文件清单(按启动顺序):

文件职责行数
server.ts公共导出门面(barrel re-export)3
server.impl.ts核心启动编排startGatewayServer~994
auth.ts认证逻辑(4 种模式)~487
auth-rate-limit.ts滑动窗口速率限制器~232
credentials.ts凭据解析(config + env)~120
net.ts网络工具(IP 检测、绑定地址)~180
boot.ts启动时 BOOT.md 检查~203
client.tsGateway WebSocket 客户端~450
node-registry.ts节点注册表(客户端管理)~200
server-runtime-state.ts运行时状态创建(HTTP/WS/Canvas)~300
server-runtime-config.ts运行时配置解析~200
server-ws-runtime.tsWebSocket 处理器挂载~44
server-channels.ts渠道管理器(启停渠道)~180
server-chat.tsAgent 事件处理器~150
server-cron.tsCron 服务构建~80
server-methods.ts核心 RPC 方法定义~600
server-methods-list.ts方法名注册表~70
server-close.ts优雅关闭编排~120
server-plugins.ts插件加载~100
server-startup.ts边车服务启动(Browser Control + 渠道)~80
config-reload.ts配置文件监听 + 热重载~200
protocol/index.tsWebSocket 协议定义(JSON Schema)~700+
events.tsGateway 事件类型定义7

![Gateway 中央控制平面概览:核心职责与连接关系](https://qn.huat.xyz/blog/article-Illustration/OpenClaw 源码解读(二)Gateway 网关系统/01-infographic-gateway-overview-1775150688334.png)


二、启动流程全景图

当执行 openclaw gateway run --port 18789 时,startGatewayServer() 函数编排了以下完整启动序列:

startGatewayServer(port=18789)

├─ 阶段1: 配置验证与迁移
│  ├─ readConfigFileSnapshot()
│  ├─ 检测旧版配置 → migrateLegacyConfig() 自动迁移
│  ├─ 二次验证配置合法性
│  └─ applyPluginAutoEnable() 自动启用新插件

├─ 阶段2: 密钥与认证
│  ├─ activateRuntimeSecrets() → 解析 ${{ secret.xxx }} 引用
│  ├─ ensureGatewayStartupAuth() → 确保有认证 token
│  │   └─ 如果没有 token → 自动生成并持久化
│  └─ 二次 activateRuntimeSecrets() → 最终密钥快照

├─ 阶段3: 核心基础设施
│  ├─ initSubagentRegistry() → 子 Agent 注册表
│  ├─ loadGatewayPlugins() → 加载插件 + 扩展方法列表
│  ├─ resolveGatewayRuntimeConfig() → 解析运行时配置
│  ├─ createGatewayAuthRateLimiters() → 创建速率限制器
│  └─ createDefaultDeps() → CLI 依赖注入容器

├─ 阶段4: 创建运行时状态
│  ├─ createGatewayRuntimeState()
│  │   ├─ 创建 HTTP 服务器(含 TLS)
│  │   ├─ 挂载 HTTP 路由(/health, /v1/chat/completions, Control UI 等)
│  │   ├─ 创建 WebSocket 服务器(ws.Server)
│  │   └─ 创建广播系统、Agent 运行状态、Canvas Host
│  ├─ new NodeRegistry() → 节点注册表
│  └─ createNodeSubscriptionManager() → 节点订阅管理

├─ 阶段5: 启动服务
│  ├─ startGatewayDiscovery() → mDNS/Bonjour 广播 + 广域发现
│  ├─ setSkillsRemoteRegistry() → 远程技能注册
│  ├─ startGatewayMaintenanceTimers() → 维护定时器
│  │   ├─ tickInterval: 30s Tick 心跳
│  │   ├─ healthInterval: 刷新健康状态
│  │   └─ dedupeCleanup: 去重缓存清理
│  ├─ onAgentEvent() → Agent 事件订阅
│  ├─ startHeartbeatRunner() → 心跳运行器
│  ├─ startChannelHealthMonitor() → 渠道健康监控
│  └─ cron.start() → Cron 调度器启动

├─ 阶段6: 挂载 WebSocket 处理器
│  ├─ attachGatewayWsHandlers()
│  │   └─ 注册所有 RPC 方法 + 事件广播逻辑
│  └─ logGatewayStartup() → 打印启动信息

├─ 阶段7: 后台服务
│  ├─ scheduleGatewayUpdateCheck() → 版本更新检查
│  ├─ startGatewayTailscaleExposure() → Tailscale 暴露
│  ├─ startGatewaySidecars() → 边车启动
│  │   ├─ Browser Control Server
│  │   ├─ startChannels() → 启动所有渠道连接
│  │   └─ 运行 gateway_start 插件钩子
│  └─ startGatewayConfigReloader() → 配置文件监听

└─ 返回 { close: async () => { ... } }
    └─ 优雅关闭: 停止所有服务 → 断开连接 → 释放资源

![Gateway 7阶段启动序列全景图](https://qn.huat.xyz/blog/article-Illustration/OpenClaw 源码解读(二)Gateway 网关系统/02-infographic-startup-sequence-1775150689094.png)


三、核心子系统深度解读

3.1 认证系统 (auth.ts)

Gateway 支持四种认证模式,通过 gateway.auth.mode 配置切换:

┌────────────────────────────────────────────────────────────────┐
│                  认证模式优先级链                                │
│                                                                │
│  请求到达 → ❶ trusted-proxy 模式?                             │
│             │  → 检查代理 IP + 用户头 → 通过/拒绝              │
│             │                                                  │
│           ❷ none 模式?                                        │
│             │  → 直接放行                                      │
│             │                                                  │
│           ❸ 速率限制检查                                       │
│             │  → 被限制?返回 429 + retryAfterMs               │
│             │                                                  │
│           ❹ Tailscale 认证(WS Control UI 专用)               │
│             │  → Whois 验证 → 通过/继续                        │
│             │                                                  │
│           ❺ token 模式?                                       │
│             │  → safeEqualSecret(提供的token, 配置的token)     │
│             │                                                  │
│           ❻ password 模式?                                    │
│             │  → safeEqualSecret(提供的密码, 配置的密码)       │
│             │                                                  │
│           ❼ 全部未匹配 → 返回 "unauthorized"                  │
└────────────────────────────────────────────────────────────────┘

认证模式详解

模式配置值凭据来源典型场景
nonegateway.auth.mode: "none"无需凭据仅本地开发
tokengateway.auth.mode: "token"config token / env OPENCLAW_GATEWAY_TOKEN默认模式
passwordgateway.auth.mode: "password"config password多用户共享
trusted-proxygateway.auth.mode: "trusted-proxy"反向代理注入的用户头Nginx/Caddy 前置

时序恒定比较 (safeEqualSecret)

typescript
// auth.ts 中的关键调用
if (!safeEqualSecret(connectAuth.token, auth.token)) {
  limiter?.recordFailure(ip, rateLimitScope);
  return { ok: false, reason: "token_mismatch" };
}

safeEqualSecret() 使用了 Node.js 的 crypto.timingSafeEqual(),确保无论比较的哪个位置不匹配,函数执行时间都相同。这是防止时序侧信道攻击的标准做法——攻击者无法通过响应时间推断 token 的正确前缀。

本地请求检测 (isLocalDirectRequest)

typescript
export function isLocalDirectRequest(req, trustedProxies, allowRealIpFallback): boolean {
  const clientIp = resolveRequestClientIp(req, trustedProxies, allowRealIpFallback);
  if (!isLoopbackAddress(clientIp)) return false;
  const hasForwarded = Boolean(req.headers["x-forwarded-for"] || ...);
  const remoteIsTrustedProxy = isTrustedProxyAddress(req.socket?.remoteAddress, trustedProxies);
  return isLocalishHost(req.headers?.host) && (!hasForwarded || remoteIsTrustedProxy);
}

判断一个请求是否来自本机:

  1. 客户端 IP 必须是回环地址(127.0.0.1 / ::1)
  2. Host 头必须是本地(localhost / 127.0.0.1)
  3. 如果有代理头(X-Forwarded-For),发送方必须是可信代理

Tailscale 认证

Tailscale 认证是一个三步验证过程:

❶ 从请求头读取 Tailscale-User-Login
❷ 确认请求来自 Tailscale 代理(回环地址 + X-Forwarded-* 头)
❸ 通过 Tailscale Whois API 验证用户身份
    → 比较 Whois 返回的 login 与请求头中的 login 是否一致

这种双重验证防止了攻击者伪造 Tailscale 用户头。

![认证系统四模式优先级决策链](https://qn.huat.xyz/blog/article-Illustration/OpenClaw 源码解读(二)Gateway 网关系统/03-infographic-auth-chain-1775150690240.png)


3.2 速率限制器 (auth-rate-limit.ts)

算法:滑动窗口 + 锁定期

                  窗口大小: 60s
    ┌──────────────────────────────┐
    │ 失败1  失败2  ...  失败10    │
    └──────────────────────────────┘

      超过 maxAttempts=10


    ┌──────────────────────────────────────────┐
    │           锁定期: 300s (5分钟)            │
    │  所有请求被拒绝,返回 retryAfterMs       │
    └──────────────────────────────────────────┘

      锁定期过后


    计数器重置,重新开始

数据结构

typescript
interface RateLimitEntry {
  attempts: number[]; // 失败时间戳数组(滑动窗口)
  lockedUntil?: number; // 锁定到期时间
}

// 存储结构: Map<"scope:ip", RateLimitEntry>
// 示例: "shared-secret:192.168.1.5" → { attempts: [1711234567890, ...] }

设计亮点

  1. 回环地址豁免127.0.0.1::1 默认不受速率限制(exemptLoopback: true),确保本地 CLI 永远不会被锁定
  2. 作用域隔离 — 不同认证方式(shared-secret / device-token / hook-auth)使用独立计数器
  3. 自动清理setInterval 每 60 秒清理过期条目,.unref() 确保定时器不阻止进程退出
  4. 成功重置 — 认证成功后立即调用 reset(ip) 清除该 IP 的所有失败记录

![滑动窗口速率限制算法运作机制](https://qn.huat.xyz/blog/article-Illustration/OpenClaw 源码解读(二)Gateway 网关系统/04-infographic-rate-limiter-1775150691021.png)


3.3 启动编排 (server.impl.ts)

startGatewayServer() 是整个项目中最长最复杂的函数(~760 行),它是一个过程式编排器

配置迁移与验证(第 250-296 行)

读取配置快照 → 检测旧版字段?
  │                    │
  ├─ 有旧版字段        ├─ 无旧版字段
  │  │                 │
  │  Nix 模式?        继续
  │  ├─ 是 → 抛出错误(Nix 配置不可变)
  │  └─ 否 → migrateLegacyConfig()
  │          │
  │          写入迁移后的配置文件
  │          │
  │          二次读取验证

  └─ 配置无效?→ 抛出错误 + 提示运行 openclaw doctor

认证引导(第 381-406 行)

typescript
const authBootstrap = await ensureGatewayStartupAuth({
  cfg: cfgAtStart,
  env: process.env,
  authOverride: opts.auth,
  tailscaleOverride: opts.tailscale,
  persist: true,
});

如果没有配置认证 token,ensureGatewayStartupAuth自动生成一个随机 token 并尝试持久化到配置文件。这确保 Gateway 启动后不会处于完全无认证的状态。

运行时状态创建(第 519-561 行)

createGatewayRuntimeState() 是 Gateway 的"基础设施工厂",它创建:

创建的对象类型用途
httpServerhttp.Server / https.ServerHTTP 服务器(含 TLS 支持)
wssWebSocket.ServerWebSocket 服务器
clientsSet<GatewayWsClient>已连接的 WebSocket 客户端集合
broadcast(event, payload) => void向所有客户端广播事件
canvasHostCanvasHostServerCanvas Host(A2UI 运行环境)
agentRunSeqMap<string, number>Agent 运行序号追踪
dedupeDedupeStore消息去重存储
chatRunStateChatRunState聊天运行状态(中止控制器、缓冲区等)

维护定时器(第 645-662 行)

typescript
const { tickInterval, healthInterval, dedupeCleanup } = startGatewayMaintenanceTimers({
  broadcast,                      // 广播函数
  nodeSendToAllSubscribed,       // 向订阅节点发送
  getPresenceVersion,            // 在线状态版本
  getHealthVersion,              // 健康状态版本
  refreshGatewayHealthSnapshot,  // 刷新健康快照
  dedupe,                        // 去重存储
  chatAbortControllers,          // 中止控制器
  ...
});

三个定时器的职责:

定时器间隔职责
tickInterval~30s向所有客户端发送 tick 心跳,检测静默断连
healthInterval~60s刷新渠道健康快照,广播状态变化
dedupeCleanup~60s清理过期的去重条目,释放内存

WebSocket 处理器挂载(第 751-823 行)

typescript
attachGatewayWsHandlers({
  wss, clients, port,
  resolvedAuth,
  gatewayMethods,          // RPC 方法列表
  extraHandlers: {         // 扩展处理器
    ...pluginRegistry.gatewayHandlers,
    ...execApprovalHandlers,
    ...secretsHandlers,
  },
  context: {               // 请求上下文(所有 RPC 方法共享)
    deps, cron, cronStorePath,
    execApprovalManager,
    broadcast, nodeRegistry,
    chatAbortControllers,
    ...
  },
});

context 对象是依赖注入容器——所有 RPC 方法通过 context 访问 Gateway 内部状态,而不是直接导入全局变量。

优雅关闭(第 945-993 行)

typescript
const close = createGatewayCloseHandler({
  bonjourStop,           // 停止 mDNS 广播
  tailscaleCleanup,      // 清理 Tailscale 暴露
  canvasHost,            // 关闭 Canvas Host
  stopChannel,           // 停止渠道
  pluginServices,        // 停止插件服务
  cron,                  // 停止 Cron
  heartbeatRunner,       // 停止心跳
  clients,               // 断开所有客户端
  configReloader,        // 停止配置监听
  browserControl,        // 停止浏览器控制服务
  wss, httpServer,       // 关闭 HTTP/WS 服务器
  ...
});

关闭时还会触发 gateway_stop 插件钩子,让插件有机会做清理工作。


3.4 配置热重载 (config-reload.ts)

Gateway 监听 openclaw.json 文件变化,能在不重启的情况下应用大部分配置更新。

配置文件变化检测

├─ 计算变更计划 (ConfigReloadPlan)
│  ├─ 哪些字段变化了?
│  ├─ 变化的字段需要热重载还是重启?
│  └─ 输出: { hotReloadable: [...], requiresRestart: [...] }

├─ 全部可热重载?
│  ├─ 是 → applyHotReload(plan, nextConfig)
│  │        ├─ 更新 Hooks 配置
│  │        ├─ 重建心跳运行器
│  │        ├─ 重建 Cron 服务
│  │        ├─ 启停渠道
│  │        └─ 更新渠道健康监控间隔
│  │
│  └─ 有需要重启的字段?
│     └─ requestGatewayRestart(plan, nextConfig)
│         └─ 发送 SIGUSR1 信号触发优雅重启

可热重载的配置项示例:

  • gateway.hooks — Webhook 钩子 URL
  • heartbeat — 心跳间隔
  • cron — 定时任务
  • 渠道开关 — 启用/禁用特定渠道

需要重启的配置项示例:

  • gateway.auth — 认证模式变化
  • gateway.bind — 绑定地址变化
  • gateway.tls — TLS 证书变化

![配置热重载决策与执行路径](https://qn.huat.xyz/blog/article-Illustration/OpenClaw 源码解读(二)Gateway 网关系统/05-infographic-config-reload-1775150691904.png)


3.5 WebSocket 协议 (protocol/index.ts)

Gateway 使用自定义的 JSON-RPC 风格协议进行通信。

帧类型

typescript
// 请求帧: 客户端 → Gateway
type RequestFrame = {
  id: string; // 请求 ID(用于匹配响应)
  method: string; // RPC 方法名
  params?: unknown; // 方法参数
};

// 响应帧: Gateway → 客户端
type ResponseFrame = {
  id: string; // 对应请求 ID
  result?: unknown; // 成功结果
  error?: ErrorShape; // 错误信息
  final?: boolean; // 是否是最终响应(流式场景)
};

// 事件帧: Gateway → 客户端(主动推送)
type EventFrame = {
  event: string; // 事件名
  seq: number; // 序列号(检测丢失)
  payload?: unknown; // 事件数据
};

连接握手流程

客户端                               Gateway
  │                                     │
  ├──── WebSocket 连接 ────────────────→│
  │                                     │
  │←──── challenge { nonce } ──────────┤  ❶ 服务端发送挑战
  │                                     │
  ├──── connect {                ──────→│  ❷ 客户端响应
  │       auth: { token },              │
  │       client: { name, version },    │
  │       device: {                     │     设备认证(可选)
  │         id, publicKey,              │
  │         signature, nonce            │
  │       },                            │
  │       scopes: ["operator.admin"]    │
  │     }                               │
  │                                     │
  │←──── hello.ok {               ─────┤  ❸ 认证成功
  │       connId, sessionKey,           │
  │       version, features,            │
  │       canvasHost                    │
  │     }                               │
  │                                     │
  │←──── events (广播) ────────────────┤  ❹ 开始接收事件
  │                                     │
  ├──── request { method, params } ───→│  ❺ 发送 RPC 请求
  │                                     │
  │←──── response { result }    ───────┤  ❻ 接收 RPC 响应

![WebSocket 协议帧类型与握手流程](https://qn.huat.xyz/blog/article-Illustration/OpenClaw 源码解读(二)Gateway 网关系统/06-infographic-ws-protocol-1775150692595.png)

已注册的 RPC 方法(部分列表)

协议层定义了丰富的 RPC 方法,每个方法都有 JSON Schema 验证:

方法分组方法名功能
Agentagent, agent.wait, agent.identityAgent 对话、等待、身份
Chatchat.send, chat.abort, chat.history, chat.inject聊天发送/中止/历史/注入
Configconfig.get, config.set, config.patch, config.apply配置 CRUD
Channelschannels.status, channels.logout, channels.login渠道状态/登出/登录
Croncron.list, cron.add, cron.remove, cron.run定时任务管理
Memorymemory.search, memory.status记忆搜索/状态
Modelsmodels.list, models.status模型列表/状态
Sessionssessions.list, sessions.patch会话管理
Agentsagents.list, agents.create, agents.update, agents.delete多 Agent 管理
Nodesnodes.list, nodes.invoke, nodes.subscribe节点管理与远程调用
Execexec-approval.*命令执行审批
Secretssecrets.reload, secrets.resolve密钥管理
Devicedevice-pair.*, device-token.*设备配对与令牌

3.6 Gateway 客户端 (client.ts)

GatewayClient 是一个自动重连的 WebSocket 客户端,被 CLI 和其他消费者使用来连接 Gateway。

核心状态机

┌─────────┐     start()      ┌──────────┐
│ INITIAL │────────────────→│ CONNECT  │
└─────────┘                  └────┬─────┘

                          WebSocket open


                           ┌──────────┐    challenge 到达
                           │ WAITING  │──────────────────→ sendConnect()
                           │ CHALLENGE│                        │
                           └──────────┘                        │
                                                         认证成功


                                                        ┌───────────┐
                                                        │ CONNECTED │
                                                        │ (活跃)    │
                                                        └─────┬─────┘

                                                     连接断开 │ stop()

                                                        ┌───────────┐
                                                        │ RECONNECT │
                                                        │ (退避等待)│
                                                        └─────┬─────┘

                                                        backoff 到期


                                                        回到 CONNECT

安全检查

typescript
// 阻止所有明文 ws:// 到非回环地址的连接(CWE-319, CVSS 9.8)
if (!isSecureWebSocketUrl(url, { allowPrivateWs })) {
  const error = new Error(
    `SECURITY ERROR: Cannot connect to "${displayHost}" over plaintext ws://. ` +
      "Both credentials and chat data would be exposed to network interception. ...",
  );
  this.opts.onConnectError?.(error);
  return;
}

只有以下连接被允许:

  • ws://127.0.0.1:* — 本地回环(安全)
  • ws://localhost:* — 本地回环(安全)
  • wss://* — TLS 加密(安全)
  • ws://* + OPENCLAW_ALLOW_INSECURE_PRIVATE_WS=1 — 显式破窗(不推荐)

![Gateway 客户端状态机与指数退避重连](https://qn.huat.xyz/blog/article-Illustration/OpenClaw 源码解读(二)Gateway 网关系统/07-infographic-client-state-machine-1775150693325.png)

设备认证

客户端连接时可以进行设备级认证

typescript
const payload = buildDeviceAuthPayloadV3({
  deviceId: identity.deviceId, // 设备唯一 ID
  clientId: "gateway-client", // 客户端类型
  role: "operator", // 角色
  scopes: ["operator.admin"], // 权限范围
  signedAtMs: Date.now(), // 签名时间
  token: authToken, // 认证 token
  nonce, // 服务端挑战随机数
  platform: "darwin", // 平台
});
const signature = signDevicePayload(identity.privateKeyPem, payload);

这个流程使用 公私钥签名 + 挑战-响应

  1. Gateway 发送随机 nonce
  2. 客户端用设备私钥对 (deviceId + token + nonce + ...) 签名
  3. Gateway 用设备公钥验证签名
  4. 验证通过后存储设备 token,后续连接可以跳过完整签名流程

自动重连与退避

连接断开

  ├─ backoffMs = 1000 (初始 1 秒)

  ├─ 第 1 次重连失败 → backoffMs = 2000
  ├─ 第 2 次重连失败 → backoffMs = 4000
  ├─ ...
  └─ 最大退避 → backoffMs = 30000 (30 秒)

断连时清理设备 token

typescript
ws.on("close", (code, reason) => {
  // 仅在设备 token 认证失败时清理本地 token
  if (
    code === 1008 &&
    reasonText.includes("device token mismatch") &&
    !this.opts.token &&
    !this.opts.password
  ) {
    clearDeviceAuthToken({ deviceId, role });
    clearDevicePairing(deviceId);
  }
  this.scheduleReconnect();
});

3.7 节点注册表 (node-registry.ts)

NodeRegistry 管理所有通过 WebSocket 连接到 Gateway 的客户端节点(CLI 实例、移动 App、Control UI 等)。

typescript
class NodeRegistry {
  private nodesById = new Map<string, NodeSession>();    // nodeId → 节点信息
  private nodesByConn = new Map<string, string>();       // connId → nodeId
  private pendingInvokes = new Map<string, PendingInvoke>(); // 待处理的远程调用

  register(client, opts) { ... }     // 节点连接时注册
  unregister(connId) { ... }         // 节点断开时注销
  sendEvent(nodeId, event, payload)  // 向指定节点发送事件
  invoke(nodeId, command, params)    // 远程调用节点上的命令
}

每个连接的客户端被注册为一个 NodeSession

typescript
type NodeSession = {
  nodeId: string; // 节点 ID(设备 ID 或客户端 ID)
  connId: string; // WebSocket 连接 ID
  client: GatewayWsClient; // WebSocket 客户端引用
  displayName?: string; // 显示名称
  platform?: string; // 平台(darwin/linux/win32/ios/android)
  caps: string[]; // 能力列表(如 "screen-snapshot")
  commands: string[]; // 可执行命令列表
  permissions?: Record<string, boolean>; // 权限
  connectedAtMs: number; // 连接时间
};

远程调用机制: Gateway 可以向连接的节点发送命令(如让移动 App 截屏),通过 invoke() 发起请求,等待节点响应。


3.8 Boot 机制 (boot.ts)

Gateway 启动时会检查工作目录下的 BOOT.md 文件,如果存在则让 Agent 执行其中的指令。

Gateway 启动

  ├─ loadBootFile(workspaceDir) → 读取 BOOT.md
  │   ├─ 文件不存在 → { status: "missing" } → 跳过
  │   ├─ 文件为空 → { status: "empty" } → 跳过
  │   └─ 文件有内容 → { status: "ok", content: "..." }

  ├─ snapshotMainSessionMapping() → 快照当前会话映射

  ├─ buildBootPrompt() → 构建 Boot 提示词
  │   "You are running a boot check. Follow BOOT.md instructions exactly."
  │   "BOOT.md: [文件内容]"
  │   "If nothing needs attention, reply with ONLY: __SILENT__"

  ├─ agentCommand({ message, deliver: false }) → Agent 执行

  └─ restoreMainSessionMapping() → 恢复会话映射(不污染主会话)

设计洞察:

  • Boot 运行使用独立的 session IDboot-2026-03-25_12-00-00-abc12345),不会污染用户的主会话历史
  • 运行前快照会话映射,运行后恢复——即使 Agent 修改了会话数据,也会被回滚
  • deliver: false 确保 Boot 产生的消息不会发送到消息渠道

四、关键设计模式

4.1 过程式编排器(Procedural Orchestrator)

startGatewayServer() 采用了一种"大函数编排"模式,而不是将每个步骤封装到单独的类中。这种设计的优劣:

优势:

  • 启动流程一目了然,从上到下就是执行顺序
  • 所有依赖关系通过局部变量的作用域自然表达
  • 闭包可以直接访问启动阶段创建的所有对象

劣势:

  • 函数体非常长(~760 行)
  • 难以对启动流程的某个阶段做单元测试

缓解措施: 每个逻辑子步骤都被提取到独立模块(server-runtime-state.tsserver-channels.ts 等),编排器只负责调用顺序和依赖传递。

4.2 广播/订阅模式

Gateway 的事件分发使用了一个简单但高效的广播系统:

typescript
const broadcast = (event: string, payload: unknown, opts?) => {
  for (const client of clients) {
    if (opts?.dropIfSlow && client.bufferedAmount > threshold) continue;
    client.send(JSON.stringify({ event, seq: nextSeq++, payload }));
  }
};

dropIfSlow 机制: 当某个客户端的发送缓冲区积压严重时,跳过该客户端的事件推送,避免慢客户端拖慢整个 Gateway。

4.3 请求上下文注入

所有 RPC 方法通过 GatewayRequestContext 共享同一个上下文对象:

typescript
type GatewayRequestContext = {
  deps: CliDeps;
  cron: CronService;
  nodeRegistry: NodeRegistry;
  broadcast: BroadcastFn;
  execApprovalManager: ExecApprovalManager;
  // ... 共 30+ 个字段
};

这种"宽接口"设计让每个 RPC 方法都能访问 Gateway 的全部内部状态。虽然违反了接口最小化原则,但在单进程服务器中是务实的选择——避免了为每个方法定义独立的依赖接口。

4.4 最小测试 Gateway 模式

typescript
const minimalTestGateway =
  process.env.VITEST === "1" &&
  process.env.OPENCLAW_TEST_MINIMAL_GATEWAY === "1";

测试中可以启动一个"最小 Gateway"——跳过 mDNS 发现、渠道连接、插件加载、Cron 启动等重量级操作,只保留 HTTP/WS 核心和认证系统。这大大加快了测试速度。


五、数据流图

5.1 CLI 发送消息到 WhatsApp 的完整链路

用户: openclaw message send --target +155 --message "Hi"

  ├─ CLI 连接 Gateway WebSocket
  │   └─ GatewayClient.start() → 握手 → 认证

  ├─ CLI 发送 RPC 请求
  │   { method: "chat.send", params: { message: "Hi", target: "+155" } }

  ├─ Gateway 处理 chat.send
  │   ├─ resolveSessionKeyForRun() → 确定会话 Key
  │   ├─ agentCommand({ message: "Hi", deliver: true })
  │   │   ├─ Agent 处理消息 → 生成回复
  │   │   └─ 发布 AgentEvent(streaming 事件流)
  │   │
  │   ├─ createAgentEventHandler 捕获事件
  │   │   ├─ broadcast("chat.delta", { ... })  → 推送给所有 WS 客户端
  │   │   └─ nodeSendToSession() → 推送给订阅该会话的节点
  │   │
  │   └─ Agent 完成 → deliver 到 WhatsApp 渠道
  │       └─ sendMessageWhatsApp({ to: "+155", text: "Agent 回复" })

  └─ CLI 收到 chat.complete 事件 → 显示结果

![CLI 到 WhatsApp 消息流转完整链路](https://qn.huat.xyz/blog/article-Illustration/OpenClaw 源码解读(二)Gateway 网关系统/08-infographic-message-flow-1775150694246.png)

5.2 配置热重载流程

用户修改 ~/.openclaw/openclaw.json

  ├─ fs.watch 检测到变化

  ├─ readConfigFileSnapshot() → 读取新配置

  ├─ 计算 ConfigReloadPlan
  │   { changed: ["heartbeat.interval", "cron.jobs[0].schedule"] }

  ├─ 全部可热重载 → onHotReload(plan, nextConfig)
  │   ├─ activateRuntimeSecrets(nextConfig) → 更新密钥快照
  │   ├─ applyHotReload()
  │   │   ├─ heartbeatRunner.updateConfig(nextConfig)
  │   │   ├─ cron.stop() → buildGatewayCronService(nextConfig) → cron.start()
  │   │   └─ 更新 channelHealthMonitor 间隔
  │   └─ 如果热重载失败 → 回滚密钥快照

  └─ 有需要重启的字段 → onRestart(plan, nextConfig)
      └─ SIGUSR1 → Gateway 优雅重启

六、安全架构

6.1 防护层次

层次防护手段位置
传输层强制 WSS / 回环地址限制client.ts:121
认证层Token / 密码 / Tailscale / 可信代理auth.ts
速率限制滑动窗口 + 锁定期auth-rate-limit.ts
时序攻击crypto.timingSafeEqualsecurity/secret-equal.js
设备认证公私钥签名 + 挑战响应client.ts:274-298
代理验证可信代理 IP 白名单 + 必要头检查auth.ts:321-358
TailscaleWhois 双重验证auth.ts:183-214

6.2 SSRF 防护

Gateway 的 HTTP 端点和 WebSocket 连接都考虑了 SSRF 防护:

  • 代理头(X-Forwarded-For)仅在发送方是可信代理时才被采信
  • isLocalDirectRequest() 综合判断请求是否真正来自本机
  • X-Real-IP 头默认不信任,需要 allowRealIpFallback 显式开启

![Gateway 分层安全架构纵深防御](https://qn.huat.xyz/blog/article-Illustration/OpenClaw 源码解读(二)Gateway 网关系统/09-infographic-security-layers-1775150694988.png)


七、调试入口推荐

断点位置观察目标
server.impl.ts:232 (startGatewayServer 入口)整体启动流程
server.impl.ts:250 (readConfigFileSnapshot)配置加载
server.impl.ts:382 (ensureGatewayStartupAuth)认证引导
server.impl.ts:537 (createGatewayRuntimeState)HTTP/WS 创建
auth.ts:364 (authorizeGatewayConnect)认证判断逻辑
auth-rate-limit.ts:141 (check)速率限制判断
client.ts:107 (start)客户端连接流程
client.ts:234 (sendConnect)客户端认证握手
node-registry.ts:43 (register)节点注册
boot.ts:138 (runBootOnce)Boot 机制
config-reload.tsonHotReload配置热重载

调试命令:

bash
# 启动 Gateway(无渠道连接,适合调试核心逻辑)
OPENCLAW_SKIP_CHANNELS=1 pnpm gateway:dev

# 运行 Gateway 认证测试
pnpm vitest run --config vitest.gateway.config.ts src/gateway/auth.test.ts

# 运行 Gateway 启动测试
pnpm vitest run --config vitest.gateway.config.ts src/gateway/boot.test.ts

# 运行速率限制测试
pnpm vitest run --config vitest.gateway.config.ts src/gateway/auth-rate-limit.test.ts

# 运行客户端测试
pnpm vitest run --config vitest.gateway.config.ts src/gateway/client*.test.ts

八、源码文件依赖图

server.ts (公共门面)
└── server.impl.ts (核心编排)
    ├── auth.ts ← 认证逻辑
    │   ├── auth-rate-limit.ts ← 速率限制
    │   ├── credentials.ts ← 凭据解析
    │   └── net.ts ← 网络工具(IP/回环检测)

    ├── server-runtime-state.ts ← HTTP/WS/Canvas 创建
    │   └── server/ws-connection.ts ← WS 连接处理

    ├── server-runtime-config.ts ← 运行时配置解析
    ├── server-channels.ts ← 渠道管理器
    ├── server-chat.ts ← Agent 事件处理
    ├── server-cron.ts ← Cron 服务构建
    ├── server-methods.ts ← 核心 RPC 方法
    ├── server-methods-list.ts ← 方法名注册
    ├── server-plugins.ts ← 插件加载
    ├── server-startup.ts ← 边车服务启动
    ├── server-close.ts ← 优雅关闭
    ├── server-ws-runtime.ts ← WS 处理器挂载
    ├── server-maintenance.ts ← 维护定时器
    ├── server-discovery-runtime.ts ← mDNS/广域发现
    ├── server-tailscale.ts ← Tailscale 暴露
    ├── config-reload.ts ← 配置热重载
    ├── server-reload-handlers.ts ← 热重载执行
    ├── startup-auth.ts ← 启动认证引导
    ├── node-registry.ts ← 节点注册表
    ├── exec-approval-manager.ts ← 命令审批管理

    ├── protocol/index.ts ← WebSocket 协议定义
    │   └── (JSON Schema + 类型定义 + 验证器)

    ├── boot.ts ← BOOT.md 启动检查

    └── events.ts ← 事件类型定义

client.ts (独立模块 — Gateway 客户端)
    ├── device-auth.ts ← 设备认证
    ├── net.ts ← 安全 URL 检查
    └── protocol/index.ts ← 协议复用

九、学到的设计思想

9.1 "分层安全"原则

Gateway 的安全不依赖于单一措施,而是多层叠加:

  • 传输层阻止明文连接
  • 认证层验证身份
  • 速率限制防暴力破解
  • 设备签名防重放攻击
  • 代理验证防 IP 伪造

每一层都假设其他层可能被绕过。

9.2 "过程式编排 + 模块化子步骤"

startGatewayServer() 虽然很长,但每个子步骤都是独立可测试的模块(server-channels.tsserver-cron.ts 等)。编排器本身不包含业务逻辑,只负责调用顺序依赖传递

9.3 "最小 Gateway"测试模式

通过 minimalTestGateway 标志,测试可以启动一个轻量级 Gateway,只包含核心 HTTP/WS 功能。这个设计在测试速度和测试覆盖率之间找到了平衡。

9.4 "快照-执行-恢复"模式

Boot 机制中的 snapshotMainSessionMapping → agentCommand → restoreMainSessionMapping 三步走,确保了 Boot 运行不会产生副作用。这种"快照-执行-恢复"模式在需要隔离副作用的场景中非常有用。

9.5 热重载的"回滚安全"

配置热重载如果失败,会自动回滚密钥快照到上一个已知正确的版本。这种"乐观更新 + 悲观回滚"策略确保了 Gateway 在配置变更失败时不会处于不一致状态。

读文档、看源码、写代码,理解 AI Agent 本质 🤖