2026-02-26

OpenClaw 实现教学(面向 iOS 开发)
一、整体架构(用你熟悉的概念类比)
可以把它想成:
Gateway = 一个常驻的「后端服务进程」(类似你平时连的 API 服务器,但这里还管消息通道和 AI 对话)。
Channel = 各种 IM(WhatsApp、Telegram、Slack、iMessage 等)的「适配器」,负责收消息、发消息。
Agent = 一次「对话轮次」的执行单元:收一条用户消息 → 调 LLM → 可能用 Tools → 产出回复。
iOS/macOS/Android 客户端 = 通过 WebSocket 连到同一个 Gateway,以「Node」身份注册,可发 agent 请求、收事件、提供设备能力(画布、相机等)。
文档里有一张架构说明,建议直接看:docs/concepts/architecture.md。核心结论:
一个主机上只跑 一个 Gateway,所有通道、所有客户端都连它。
控制面(Mac 应用、CLI、Web 管理端)和 Node(iOS/Android/头戴设备)都走 同一套 WebSocket 协议,只是 connect 时 role、caps、commands 不同。
和你做 iOS 的对应关系:
入口:openclaw.mjs → dist/entry.js → 根据 argv 决定是跑 CLI 还是起 Gateway。
Gateway:src/gateway/server.impl.ts 里建 HTTP + WebSocket 服务器,挂路由、鉴权、消息处理。
iOS:apps/ios 里用 Swift,通过 GatewayConnectionController 发现/连接 Gateway,用 WebSocket 发 connect 再发 agent 等请求。
二、目录与模块(src/ 里和「实现」最相关的部分)
目录/文件 作用(用 iOS 视角理解)
src/entry.ts 进程入口,区分「是否主模块」后调 runCli(argv) 或其它子进程逻辑。
src/cli/ CLI 的解析与分发:Commander 建 program,command-registry 里注册各子命令(gateway、agent、message、onboard…),你执行 openclaw agent ... 会走到对应 register。
src/gateway/ 核心:Gateway 的 HTTP/WS 服务实现、鉴权、协议、所有「方法」的实现(agent、send、sessions.、config 等)。 | | src/channels/ | 通道抽象:ChannelPlugin 类型、插件注册表;具体实现多在 extensions/(如 telegram、whatsapp、discord)。 | | src/routing/ | 路由:一条入站消息(来自哪个 channel、哪个 account、哪个 peer/群)决定用哪个 agentId、哪个 sessionKey(会话隔离)。 | | src/agents/ | Agent 执行:系统提示词、模型选择、真正跑一轮对话(runEmbeddedPiAgent / Pi 嵌入式运行器)、Tools(bash、send、sessions 等)。 | | src/auto-reply/ | 从「通道收到一条消息」到「决定要不要回复、组历史、调 LLM、再发回通道」的整条流水线(dispatcher、provider-dispatcher、process-message)。 | | src/commands/ | 对 CLI/Gateway 暴露的「命令」实现,例如 agentCommand(src/commands/agent.ts)被 Gateway 的 agent 方法调用。 | | src/config/ | 配置加载、迁移、会话存储路径等。 |
iOS 端在 apps/ios/Sources/: Gateway 连接、发现、TLS、配对等在 Gateway/;发 agent 请求、Canvas A2UI 等在 Model/NodeAppModel.swift、Chat/IOSGatewayChatTransport.swift 等。


三、一条消息从「收到」到「回复」的完整链路(核心流程)

这是理解「怎么实现的」最关键的一条链,分两种入口:通道入站 和 客户端直接请求 agent。

3.1 通道入站(例如 WhatsApp 收到一条消息)

  1. Channel 插件(如 extensions/whatsapp)用 Baileys 等库连上 WhatsApp,收到消息后调用「对接 Gateway」的接口(ChannelGatewayAdapter、lifecycle 等)。 2. Gateway 启动时在 server.impl.ts 里通过 createChannelManager 启动各 channel(server-channels.ts):对每个启用的 channel 调插件的 gateway.start,把「收到消息时调用的回调」传进去。 3. 通道收到消息后,会走到 auto-reply 流水线(WhatsApp 的话在 src/web/auto-reply/monitor/process-message.ts 等): - 路由:resolveAgentRoute(cfg, channel, accountId, peer, …)(src/routing/resolve-route.ts)得到 agentId、sessionKey、accountId。 - 会话/历史:根据 sessionKey 读会话存储、拼历史、组 combinedBody(可能带群聊上下文)。 - 派发回复:dispatchReplyWithBufferedBlockDispatcher(src/auto-reply/reply/provider-dispatcher.ts)等,最终会通过 Gateway 的 agent 能力跑一轮 Agent(见下)。 4. Agent 跑完后,回复通过同一 channel 的 Outbound 发回(例如 WhatsApp 发回该会话)。
    要点:同一条链路里既做了「谁来处理」(routing),又做了「用哪段历史、发到哪」(sessionKey + channel),再交给「跑 Agent」的同一套逻辑。

3.2 客户端发起的 Agent 请求(包括 iOS)

  1. iOS 通过已建立的 WebSocket 发一条请求,例如: { type: "req", id, method: "agent", params: { message, sessionKey, ... } }。 2. Gateway 在 src/gateway/server/ws-connection/message-handler.ts 里收 WebSocket 帧,解析 JSON,按 method 分发给对应 handler。 3. agent 方法在 src/gateway/server-methods/agent.ts: - 校验参数(sessionKey、agentId、channel、idempotencyKey 等); - 可选做 /new、/reset 会话重置; - 写 session store(updateSessionStore); - 调 agentCommand(...)(src/commands/agent.ts),并把自己和 createAgentEventHandler 注册成「谁在跑这次 agent、往哪推事件」。 4. agentCommand(src/commands/agent.ts)里: - 解析 session、解析 model/provider、准备 workspace、skills 等; - 实际执行是 runEmbeddedPiAgent(src/agents/pi-embedded.js → pi-embedded-runner/run.ts): 构建 system prompt、加载 transcript、调 LLM、执行 tools(bash、send、sessions_send 等),流式或非流式。 5. 事件回推:Agent 运行过程中会通过 onAgentEvent(src/infra/agent-events.js)发事件,Gateway 的 createAgentEventHandler(src/gateway/server-chat.ts)把这些事件转成 WebSocket 的 event:agent 推给订阅的客户端(包括 iOS),所以 iOS 能收到 streaming 进度、最终 summary 等。
    所以:无论是通道来的消息,还是 iOS 发的 agent 请求,最终「跑一轮 AI」都收敛到 agentCommand → runEmbeddedPiAgent;差异只在谁触发、sessionKey 从哪来、回复往哪发(通道 vs WebSocket)。

四、几个关键「细节」实现(方便你翻源码)

4.1 路由:这条消息归哪个 Agent / 哪条会话?

  • 入口:resolveAgentRoute()(src/routing/resolve-route.ts)。 - 输入:config、channel、accountId、peer(如 direct/group + id)、可选 guildId/teamId/roles(Discord/Slack)。 - 逻辑: - 从 cfg.bindings 里按 channel + account 筛 binding,再按 peer/guild/team/roles 做匹配; - 算出 agentId、sessionKey(以及 mainSessionKey)。 - sessionKey 格式:在 src/routing/session-key.ts 里,例如 agent:main:whatsapp:direct:+15551234567,表示「main agent + whatsapp + 直聊 + 对方 ID」,用于隔离会话、限流、存储。
    对 iOS:如果你在 app 里发 agent,一般会带一个固定的 sessionKey(例如和「当前设备/用户」对应),不会走 channel 的 binding,但服务端用同一套 session 存储和并发控制。

4.2 Gateway 的「方法」从哪来、怎么挂上去?

  • 方法列表:src/gateway/server-methods-list.ts 里列出所有 method 名字(health、agent、send、sessions.、config.get 等)。 - 实现:在 server-methods.ts、server-methods/agent.ts、send.ts、sessions.ts 等里实现,聚合成 coreGatewayHandlers,在 WS 的 message handler 里按 method 查表调用。 - 协议:先发 connect,再发 { type: "req", id, method, params },收 { type: "res", id, ok, payload|error };服务端可主动推 event:。 详见 docs/concepts/architecture.md 和 Gateway 协议文档。

4.3 Channel 插件长什么样?

  • 类型:ChannelPlugin(src/channels/plugins/types.plugin.js / types 从 types.core.js、types.adapters.js 导出)。 - 能力:通过一堆 adapter 接口描述: ChannelGatewayAdapter(和 Gateway 生命周期、收消息回调)、ChannelOutboundAdapter(发消息)、ChannelStatusAdapter、ChannelPairingAdapter 等。 - 注册:插件在 extensions/* 里实现,由 src/plugins/loader.js 等加载,放进 registry.channels;Gateway 启动时 createChannelManager 会遍历并调各 channel 的 gateway.start。
    这样设计的好处:加一个新 IM 只要加一个 extension,实现同一套 adapter,不改 Gateway 核心。

4.4 Agent 真正「跑一轮」的入口

  • Gateway 侧:server-methods/agent.ts 里在参数和 session 都准备好后,void agentCommand(...)(约 579 行附近),不 await,因为会通过事件和 job 状态通知完成。 - 执行侧:agentCommand 内部调 runEmbeddedPiAgent(src/agents/pi-embedded-runner/run.ts 的 runEmbeddedAttempt): 加载 transcript、拼 system prompt、调 LLM API、处理 tool calls(bash、send、sessions_send 等)、写回 transcript、发事件。 - 等待/取消:客户端可通过 agent-job.ts 的 waitForAgentJob 等按 runId 等结果;Gateway 用 createAgentEventHandler 把 agent 事件推给对应 WS 连接。
    iOS 端发起的 agent 请求走的就是这条:WS → agent handler → agentCommand → runEmbeddedPiAgent,然后通过 event:agent 把结果/流式内容推回 app。

4.5 iOS 如何连上 Gateway、发 Agent?

  • 连接:apps/ios/Sources/Gateway/GatewayConnectionController.swift: 发现(Discovery)、手动输入 host:port、或上次连接;建 WebSocket URL(可带 TLS),带 token 或 password,调 startAutoConnect 等。 - 协议:先发 connect(带 role、client、auth、设备身份等),拿到 hello-ok 后再发其他请求。 - 发 Agent:在 NodeAppModel、IOSGatewayChatTransport 等里组 method: "agent" 的请求(message、sessionKey、idempotencyKey 等),通过已连的 WebSocket 发出去;订阅 event:agent 收流式或最终结果。
    Swift 侧有的类型会从 TypeBox/JSON Schema 生成,和 TS 协议保持一致(见 docs/concepts/architecture.md 里 Protocol typing 一节)。

五、对照你关心的「实现点」可以这样看源码

  • 「一条消息从进到出」:通道 → resolveAgentRoute → auto-reply 的 processMessage → dispatchReplyWithBufferedBlockDispatcher → 内部调 Gateway agent 能力 → agentCommand → runEmbeddedPiAgent → 出站回通道。 或:iOS 发 agent → WS handler → server-methods/agent.ts → agentCommand → runEmbeddedPiAgent → event:agent 回 iOS。 - 「会话/多端一致性」:看 sessionKey 的生成(routing/session-key.ts)和 config/sessions.js 的 store 读写;iOS 用同一个 sessionKey 就会进同一会话。 - 「Gateway 到底在干什么」:看 server.impl.ts 的启动顺序(HTTP/WS、鉴权、channel 启动、注册 handlers)、以及 server-methods-list.ts + server-methods/*.ts。 - 「iOS 怎么和 TS 对上」:看 GatewayConnectionController、WebSocket 发 connect/req、收 res/event;协议形状在 TypeBox schema 和生成的 Swift 里。
    如果你愿意,我可以下一步按「只看 iOS 相关」或「只看某一条链路(例如只跟 agent 执行)」再拆一版更短的「追踪清单」(只列文件名和函数名),方便你断点/搜索。
©著作权归作者所有,转载或内容合作请联系作者
【社区内容提示】社区部分内容疑似由AI辅助生成,浏览时请结合常识与多方信息审慎甄别。
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。

相关阅读更多精彩内容

友情链接更多精彩内容