Clawdbot 源码解读 4:启动、WebSocket 协议、方法处理与认证

6 阅读9分钟

Gateway 核心:启动、WebSocket 协议、方法处理与认证

前言

在上一篇中,我们梳理了 Clawdbot 的配置系统:路径、JSON5、$include、环境变量、Zod 校验与运行时覆盖。配置是 Gateway 运行的基础——端口、绑定、认证、Control UI、HTTP 端点等均来自配置。本文是《Clawdbot 源码解读》系列的第四篇,我们将深入 Gateway 核心:从 CLI gateway runstartGatewayServer 的启动流程、运行时配置与状态、WebSocket 协议(连接、req/res、event)、方法注册与请求路由、以及认证与安全(Token/Password/Tailscale/设备配对)。

学习目标

  • 理解 Gateway 从 CLI 到 HTTP/WebSocket 服务器的完整启动顺序
  • 掌握运行时配置(bind、auth、Control UI、TLS)与运行时状态(HTTP 服务器、WS、broadcast)的构建
  • 熟悉 WebSocket 协议:connect.challenge → connect → hello-ok、请求/响应帧、事件广播
  • 了解方法注册、权限(role/scopes)与认证(token/password/tailscale/device)的协作方式

前置知识

  • 已阅读系列前三篇(架构、CLI、配置)
  • 对 HTTP/WebSocket、Node.js 服务器有基本概念
  • 对 TypeBox/AJV 或类似 schema 校验有印象即可

一、核心概念

1.1 Gateway 的职责

Gateway 是 Clawdbot 的 控制平面:同一主机上只跑一个 Gateway 进程,负责:

  • WebSocket 服务:客户端(CLI、macOS 应用、Web UI、iOS/Android 节点)通过 WebSocket 连接,发送 请求帧(req)并接收 响应帧(res)和 事件(event)。
  • HTTP 服务:在同一端口上提供 Control UI(可选)、Canvas Host、Hooks、插件 HTTP、以及可选的 OpenAI 兼容 /v1/chat/completions 和 OpenResponses /v1/responses
  • 渠道与 Agent:管理消息渠道(WhatsApp、Telegram 等)的启停、将消息路由到 Agent、广播心跳与状态变更。
  • 认证与安全:连接时校验 Token/Password 或 Tailscale/设备配对,方法级权限由 role(operator/node)与 scopes(operator.read/write/admin 等)控制。

1.2 启动顺序概览

  1. CLIclawdbot gateway run 解析端口、bind、auth、tailscale 等,可选 --force 释放端口,然后调用 runGatewayLoop(start),其中 start 返回 startGatewayServer(port, opts)
  2. 配置与迁移readConfigFileSnapshot() 读配置快照;若有 legacy 问题则 migrateLegacyConfig 并写回;若配置无效则抛错退出。
  3. 运行时配置resolveGatewayRuntimeConfig 得到 bindHost、resolvedAuth、controlUi、openAi/openResponses、tailscale、hooks、canvasHost 等;认证未配置且非 loopback 绑定会拒绝启动
  4. 运行时状态createGatewayRuntimeState 创建 HTTP 服务器(含 Control UI、Canvas、Hooks、插件路由)、WebSocket 服务器、clients 集合、broadcast、chat/agent 状态等,并执行 listen
  5. 渠道与侧车:创建 ChannelManager、Discovery(mDNS)、Cron、Maintenance 定时器、Agent/Heartbeat 事件订阅;attachGatewayWsHandlers 注册 WebSocket 连接与消息处理;最后 startGatewaySidecars(Browser 控制、Gmail watcher、内部 Hooks、插件服务等)。
  6. 关闭:返回 { close };调用 close() 时依次停止 Discovery、Tailscale、渠道、插件、Cron、定时器、取消订阅、关闭 HTTP/WS。

1.3 WebSocket 协议要点

  • 连接建立:服务端先发 event: connect.challenge(带 nonce);客户端发 connect 请求(params 含 client 信息、auth.token/auth.password 或 device 配对信息);服务端校验认证后回复 hello-ok,之后才接受其他请求。
  • 请求/响应:客户端发 reqidmethodparams),服务端回复 residokresulterror);方法由 coreGatewayHandlers 与插件/execApproval 的 extraHandlers 提供,先做 authorizeGatewayMethod(role/scopes),再调用对应 handler。
  • 事件:服务端通过 broadcast(event, payload) 向所有已连接客户端推送事件(如 heartbeatvoicewake.changedshutdown);部分事件带 stateVersion(presence/health)用于客户端增量更新。

1.4 认证模式

  • Tokengateway.auth.mode === "token"(或未设且仅有 token),客户端在 connect.params.auth.token 中携带,与配置/环境变量中的 token 做 timingSafeEqual 比较。
  • Passwordgateway.auth.mode === "password",客户端在 connect.params.auth.password 中携带;Tailscale Funnel 要求必须使用 password
  • Tailscale:当 gateway.tailscale.mode === "serve"allowTailscale === true 时,来自 Tailscale 代理的请求可凭 Tailscale 请求头 + whois 校验 通过,无需 token/password。
  • 设备配对:客户端在 connect.params.device 中携带设备 id、公钥、签名等,服务端校验签名与 nonce 后完成设备配对或复用已有 token。

二、代码解析

2.1 CLI 到 startGatewayServer

src/cli/gateway-cli/run.tsrunGatewayCommand:解析 --port--bind--token--password--auth--tailscale--force--dev 等;若 --force 则调用 forceFreePortAndWait 释放目标端口;将 token/password 写入 process.env(CLAWDBOT_GATEWAY_TOKEN / CLAWDBOT_GATEWAY_PASSWORD);然后 runGatewayLoop({ start: async () => startGatewayServer(port, { bind, auth, tailscale }) })。循环内会调用 start() 得到 server,并监听 SIGINT/SIGTERM 后调用 server.close()

// src/cli/gateway-cli/run.ts(节选)
await runGatewayLoop({
  runtime: defaultRuntime,
  start: async () =>
    await startGatewayServer(port, {
      bind,
      auth: authMode || passwordRaw || tokenRaw ? { mode: authMode ?? undefined, token: tokenRaw, password: passwordRaw } : undefined,
      tailscale: tailscaleMode ? { mode: tailscaleMode, resetOnExit: Boolean(opts.tailscaleResetOnExit) } : undefined,
    }),
});

2.2 startGatewayServer:配置、运行时配置与状态

src/gateway/server.impl.tsstartGatewayServer(port, opts) 的主干:

  1. 配置快照与迁移readConfigFileSnapshot();若存在 legacyIssuesmigrateLegacyConfigwriteConfigFile,再重新读快照;若 exists && !valid 则抛错(提示运行 clawdbot doctor)。
  2. 插件自动启用applyPluginAutoEnable,若有变更则写回配置。
  3. 加载配置loadConfig() 得到 cfgAtStart;初始化 subagent、默认 agent、workspace;loadGatewayPlugins 得到 pluginRegistrygatewayMethods(核心 + 渠道插件);resolveGatewayRuntimeConfig 得到 runtimeConfig(bindHost、resolvedAuth、controlUi、openAi/openResponses、tailscale、hooks、canvasHost)。
  4. TLSloadGatewayTlsRuntime(cfg.gateway?.tls);若配置启用 TLS 但加载失败则抛错。
  5. 运行时状态createGatewayRuntimeState 创建 Canvas Host(若启用)、HTTP 处理器(Control UI、Hooks、插件)、createGatewayHttpServerattachGatewayUpgradeHandler(WebSocket 升级)、listenGatewayHttpServerbindHosts 上监听;返回 httpServerwssclientsbroadcastchatRunStateagentRunSeqdedupe 等。
  6. 渠道与发现createChannelManagerstartGatewayDiscovery(mDNS/Bonjour)、NodeRegistryserver-node-subscriptionsstartGatewayMaintenanceTimers(tick、health、dedupe 清理);onAgentEvent / onHeartbeatEvent 订阅并转发到 broadcast
  7. WebSocket 处理attachGatewayWsHandlersattachGatewayWsConnectionHandler 挂到 wss 上,传入 gatewayMethodseventsextraHandlers(插件 + execApproval)、broadcastcontext
  8. 侧车startGatewaySidecars 启动 Browser 控制服务、Gmail watcher、内部 Hooks、startPluginServices(插件服务);startGatewayTailscaleExposure 若配置了 tailscale serve/funnel。
  9. 热重载与关闭createGatewayReloadHandlersstartGatewayConfigReloader(监听配置文件变更);返回 close,由 createGatewayCloseHandler 统一清理。

2.3 运行时配置:bind、auth、Control UI

src/gateway/server-runtime-config.tsresolveGatewayRuntimeConfig

  • bindHost:由 resolveGatewayBindHost(bindMode, customBindHost) 得到;bind 来自 opts 或 cfg.gateway?.bind,默认 "loopback"(127.0.0.1);可选 lan(0.0.0.0)、tailnetautocustom
  • controlUiEnabled:opts 或 cfg.gateway?.controlUi?.enabled,默认 true。
  • openAiChatCompletionsEnabled / openResponsesEnabled:来自配置的 gateway.http.endpoints
  • resolvedAuthresolveGatewayAuth(config + env),得到 mode(token/password)、tokenpasswordallowTailscaleassertGatewayAuthConfigured 在未配置且非仅 Tailscale 时抛错。
  • tailscaleModeoff | serve | funnel;funnel 时强制要求 password;非 loopback 绑定时不能开 tailscale。
  • 非 loopback 且无共享密钥时拒绝启动:避免裸奔监听公网。

2.4 运行时状态:HTTP、WS、broadcast

src/gateway/server-runtime-state.tscreateGatewayRuntimeState

  • canvasHostEnabled,则 createCanvasHostHandler 并挂到 HTTP。
  • createGatewayHooksRequestHandlercreateGatewayPluginRequestHandler 供 HTTP 路由使用。
  • resolveGatewayListenHosts(bindHost) 得到要监听的地址列表(如 IPv4/IPv6);对每个地址 createGatewayHttpServer(Control UI、Canvas、openAi/openResponses、Hooks、插件、认证)、attachGatewayUpgradeHandler(将 WebSocket 升级交给 wss)。
  • createGatewayBroadcaster:向 clients 中所有 WebSocket 发送 { type: "event", event, payload },支持 dropIfSlowstateVersion
  • 返回 httpServer(第一个)、httpServerswssclientsbroadcastchatRunStateagentRunSeqdedupe 等,供 attachGatewayWsHandlers 和后续逻辑使用。

2.5 WebSocket 连接与协议

src/gateway/server/ws-connection.tswss.on("connection", (socket, upgradeReq)) 时:

  • 生成 connId,发送 connect.challenge(nonce、ts)。
  • 设置 attachGatewayWsMessageHandler 处理后续消息;握手超时内未完成 connect 则关闭连接。
  • 消息处理(message-handler.ts):解析 JSON;若为 connect 请求则校验 validateConnectParamsauthorizeGatewayConnect(token/password/tailscale/device),通过则构建 client(id、role、scopes 等)加入 clients,回复 hello-ok,并发送快照(health、presence 等);若为 reqvalidateRequestFramehandleGatewayRequest,回复 res(ok + result 或 error)。
  • RequestFrameidmethodparamsResponseidokresulterror(含 code、message);协议 schema 在 protocol/schema/ 下用 TypeBox 定义,校验通过后才交给 handler。

2.6 方法注册与路由

src/gateway/server-methods.ts

  • coreGatewayHandlers:由 connectHandlershealthHandlerschannelsHandlerschatHandlersagentHandlerssessionsHandlersnodeHandlersconfigHandlerswizardHandlersupdateHandlers 等数十个模块合并而成,每个导出形如 { "method.name": async (opts) => { ... } }
  • handleGatewayRequest:先 authorizeGatewayMethod(req.method, client)(根据 client 的 role、scopes 判断是否允许调用该方法);再取 handler = extraHandlers[method] ?? coreGatewayHandlers[method];无 handler 则返回 unknown method;有则 await handler({ req, params, client, respond, context })
  • authorizeGatewayMethodnode 角色只能调用 NODE_ROLE_METHODS(node.invoke.result、node.event、skills.bins);operator 需具备相应 scopes(operator.read、operator.write、operator.admin、operator.approvals、operator.pairing),否则返回 INVALID_REQUEST

2.7 认证:resolveGatewayAuth 与 authorizeGatewayConnect

src/gateway/auth.ts

  • resolveGatewayAuth:从 authConfigenv 读取 token(gateway.auth.token 或 CLAWDBOT_GATEWAY_TOKEN)、password(gateway.auth.password 或 CLAWDBOT_GATEWAY_PASSWORD);mode 为 config 或推断(有 password 则 password);allowTailscale 由 config 或(tailscale serve 且 mode !== password)决定。
  • authorizeGatewayConnect:先判断是否 isLocalDirectRequest(本机直连);若非本地且 allowTailscale,则用 resolveVerifiedTailscaleUser(请求头 + whois 校验);否则若 mode === "token" 则比较 connect.params.auth.tokenauth.token(timingSafeEqual);若 mode === "password" 则比较 password;若客户端带 device 则走设备配对校验。返回 GatewayAuthResult(ok、method、user、reason)。

三、架构图解

3.1 Gateway 启动流程

graph TB
  A[clawdbot gateway run] --> B[runGatewayCommand]
  B --> C[forceFreePort?]
  C --> D[runGatewayLoop]
  D --> E[startGatewayServer]
  E --> F[readConfigFileSnapshot + migrateLegacy]
  F --> G[resolveGatewayRuntimeConfig]
  G --> H[createGatewayRuntimeState]
  H --> I[HTTP + WS listen]
  I --> J[ChannelManager / Discovery / Cron / Maintenance]
  J --> K[attachGatewayWsHandlers]
  K --> L[startGatewaySidecars]
  L --> M[return close]
  M --> N[loop: wait for signal → server.close]

3.2 WebSocket 协议流程

sequenceDiagram
  participant C as Client
  participant G as Gateway

  C->>G: WS connect
  G->>C: event: connect.challenge (nonce)
  C->>G: req: connect (params: client, auth/device)
  G->>G: authorizeGatewayConnect
  alt 认证通过
    G->>C: res: hello-ok + snapshot
    C->>G: req: health / agent / ...
    G->>G: authorizeGatewayMethod → handler
    G->>C: res: result / error
    G->>C: event: heartbeat / ...
  else 认证失败
    G->>C: res: error → close
  end

3.3 方法路由与权限

graph LR
  A[req] --> B[validateRequestFrame]
  B --> C[authorizeGatewayMethod]
  C --> D{role/scopes}
  D -->|通过| E[extraHandlers / coreGatewayHandlers]
  D -->|拒绝| F[res error]
  E --> G[handler]
  G --> H[respond res]

四、实践建议

4.1 如何调试 Gateway 启动

  • 端口被占用:使用 clawdbot gateway run --force 释放端口,或修改 --port;日志会提示 GatewayLockError 或端口诊断(inspectPortUsage)。
  • 认证未配置:非 loopback 绑定时必须配置 gateway.auth.tokengateway.auth.password(或环境变量);否则 assertGatewayAuthConfiguredresolveGatewayRuntimeConfig 会抛错。
  • 配置无效:若 readConfigFileSnapshot().valid === false,启动会抛错并列出 issues;可先运行 clawdbot doctor 修复。
  • 详细日志clawdbot gateway run --verbose;WebSocket 日志可通过 --ws-log full--compact 控制。

4.2 如何扩展 Gateway 方法

  1. server-methods/ 下新增模块(如 server-methods/foo.ts),导出 fooHandlers{ "foo.bar": async (opts) => { ... } })。
  2. server-methods.tsimport 并合并到 coreGatewayHandlers(或通过插件 extraHandlers 注册)。
  3. server-methods-list.tslistGatewayMethods 中加入新 method 字符串,以便客户端与 hello-ok 中的方法列表一致。
  4. 若需权限控制,在 authorizeGatewayMethod 中为该方法挂到 READ_METHODS / WRITE_METHODS / ADMIN 等,或单独判断 scopes
  5. 协议参数/结果若需严格校验,在 protocol/schema/ 下用 TypeBox 定义 Schema,并在 protocol/index.ts 中导出 validateXxx,在 handler 内调用。

4.3 认证与安全建议

  • 生产环境:使用 passwordtoken,并避免绑定到 0.0.0.0 却不配置认证。
  • Tailscale:内网暴露用 serve,公网用 funnel(必须 password);allowTailscale 时 Tailscale 用户可免 token/password,需确保 Tailscale 网络可信。
  • 设备配对:移动端/节点通过 connect.params.device 完成配对后,可获长期有效 token,便于无交互重连。
  • 调试:本机直连(127.0.0.1)时部分路径会放宽;远程调试时务必设好 token/password 或 Tailscale。

4.4 常见问题

Q: 连接后立即被关闭?
A: 检查是否在握手超时内完成 connect 并携带正确 auth.tokenauth.password(或 device);查看服务端 --ws-log full 下的关闭原因(如 token_mismatch、handshake_timeout)。

Q: 调用某方法返回 unknown method?
A: 确认方法名已加入 listGatewayMethodscoreGatewayHandlersextraHandlers 中有对应 key;客户端与服务端协议版本一致。

Q: 返回 missing scope: operator.xxx?
A: 连接时 connect.params 中的 rolescopes 需满足 authorizeGatewayMethod 的要求;例如写操作需 operator.write,管理类需 operator.admin


五、总结与下一篇预告

5.1 本文要点

  • 启动链:CLI gateway runrunGatewayLoopstartGatewayServer;配置快照与迁移 → resolveGatewayRuntimeConfig(bind、auth、Control UI、tailscale)→ createGatewayRuntimeState(HTTP + WS + broadcast)→ attachGatewayWsHandlersstartGatewaySidecars → 返回 close
  • WebSocket 协议connect.challenge → 客户端 connect(client、auth/device)→ 服务端 authorizeGatewayConnecthello-ok;之后 req/resevent 广播;帧结构由 TypeBox schema 校验。
  • 方法处理handleGatewayRequestauthorizeGatewayMethod(role/scopes),再 coreGatewayHandlers / extraHandlers 执行;node 仅限 NODE_ROLE_METHODS,operator 需对应 scopes。
  • 认证resolveGatewayAuth 得到 token/password/allowTailscale;authorizeGatewayConnect 支持 token、password、Tailscale whois、设备配对;非 loopback 绑定必须配置共享密钥。

参考资源

  • 项目仓库:github.com/clawdbot/cl…
  • 官方文档:docs.clawd.bot
  • Gateway 启动与关闭:src/gateway/server.impl.tssrc/gateway/server-close.ts
  • CLI 启动命令:src/cli/gateway-cli/run.tssrc/cli/gateway-cli/run-loop.ts
  • 运行时配置与状态:src/gateway/server-runtime-config.tssrc/gateway/server-runtime-state.ts
  • WebSocket 连接与消息:src/gateway/server/ws-connection.tssrc/gateway/server/ws-connection/message-handler.tssrc/gateway/server-ws-runtime.ts
  • 方法注册与路由:src/gateway/server-methods.tssrc/gateway/server-methods-list.tssrc/gateway/server-methods/
  • 认证:src/gateway/auth.tssrc/gateway/device-auth.ts
  • 协议 Schema:src/gateway/protocol/src/gateway/protocol/schema/
  • 侧车与发现:src/gateway/server-startup.tssrc/gateway/server-discovery-runtime.ts