Skip to content

网关核心

源码版本v2026.6.11

职责

startGatewayServer 是 OpenClaw 的常驻进程入口:绑定端口、加载插件、注册 RPC 方法、广播 agent 事件。所有渠道消息、ACP 请求、Web 客户端都连到这个网关,它再把请求转给 agent 主循环,把 agent 产生的事件广播回各客户端。

网关本身不做"思考"——它只负责接、转、播:接 JSON-RPC 请求,转给对应 handler(很多最终落到 agent),把 agent 事件播给订阅了该会话的 WebSocket 连接。

设计动机

为什么把网关单拎成一个常驻进程?因为 OpenClaw 要同时挂在 22 个渠道上,这些渠道大多需要长连接(WebSocket/polling)。如果每次消息都起一个短命进程,连接握手的开销和状态恢复的复杂度会压垮设计。常驻 gateway 让所有渠道连接、所有 agent session、所有插件状态都在一个进程里共享,跨层协调(比如渠道 A 的消息触发的 agent 调用工具,工具结果要投递到渠道 B)只是一个进程内的函数调用。

关键文件

数据流

startGatewayServer 的核心启动序列(server.impl.ts:551):

typescript
export async function startGatewayServer(
  port = 18789,
  opts: GatewayServerOptions = {},
): Promise<GatewayServer> {
  normalizeStateDirEnv(process.env);
  // 清理上一轮重启遗留的旧插件 generation,保证两轮重启间状态干净
  const installRecords = loadInstalledPluginIndexInstallRecordsSync();
  const removedGenerations = await cleanupRetiredManagedNpmInstallGenerations({
    activeInstallPaths: Object.values(installRecords).flatMap((record) =>
      record.installPath ? [record.installPath] : [],
    ),
    onError: (error, projectRoot) =>
      log.warn(`failed to clean retained npm generation ${projectRoot}: ${String(error)}`),
  });
  const { bootstrapGatewayNetworkRuntime } = await import("./server-network-runtime.js");
  bootstrapGatewayNetworkRuntime();

注意默认端口 18789,以及启动第一件事是清理旧插件 npm generation——这是为 in-process restart 设计的:gateway 重启时要先回收上一轮留下的插件资源,避免泄漏。

请求分发(handleGatewayRequest:638)走 JSON-RPC:

typescript
/** Authorizes and dispatches one gateway JSON-RPC-style request. */
export async function handleGatewayRequest(
  opts: GatewayRequestOptions & { extraHandlers?: GatewayRequestHandlers },
): Promise<void> {
  const { req, respond, client, isWebchatConnect, context } = opts;
  // 优先用调用方附带的 registry(它持有该方法时),否则从 live plugin registry 重建,
  // 让启动快照之后注册的插件 RPC 方法仍可达 (#94127)

handler registry 是每请求重建的(createRequestGatewayMethodRegistry:598):合并 core handler、当前激活插件 handler、调用方额外 handler。这种设计让插件可以热注册 RPC 方法,不必重启网关。

agent 事件广播(createAgentEventHandler:299)注入了三个原语:

typescript
export function createAgentEventHandler({
  broadcast,              // 广播到所有订阅该会话的连接
  broadcastToConnIds,     // 定向广播到指定连接
  nodeSendToSession,      // 跨节点投递到某 session
  agentRunSeq,
  chatRunState,
  resolveSessionKeyForRun,
  ...

broadcastbroadcastToConnIdscreateGatewayNodeSessionRuntimeserver.impl.ts:971 注入,贯穿整个网关生命周期。

边界与失败

  • in-process restart:网关重启时先清理旧插件 npm generation(src/gateway/server.impl.ts:560),若清理失败只打 warn 不阻断启动——保证服务能拉起比清理干净更重要。
  • 插件热注册的 handler 可达性:handleGatewayRequest 在调用方快照不持方法时回退到 live registry 重建,修了 #94127——否则启动快照之后注册的插件 RPC 会"看不见"。
  • 状态目录:启动先 normalizeStateDirEnv(process.env),确保 $OPENCLAW_STATE_DIR 解析正确,否则配置和 workspace 路径会错位。
  • 网络运行时:bootstrapGatewayNetworkRuntime() 在加载网络层之前调用,顺序错了会导致 WebSocket 通道未就绪。

小结

网关只做接、转、播三件事,但每件都为"常驻 + 多渠道 + 可热插拔"做了设计:启动先清理、handler 每请求重建、广播原语注入。真正"想"的是 Agent 主循环,请求怎么授权分发看 RPC 方法表,事件怎么到客户端看 聊天广播

对照官方资料:Gateway 文档 · README