
OpenClaw 外部 CLI 的 JSON-RPC 适配signal-cli HTTP 守护进程与 imsg stdio 双模式深度解析【免费下载链接】openclawThe AI that really does things. Any OS. Any Platform. The lobster way. 项目地址: https://gitcode.com/GitHub_Trending/cl/openclawOpenClaw 通过JSON-RPC将外部 CLIsignal-cli、imsg接入统一的消息通道体系。本文以 docs/reference/rpc.md 为骨架系统讲解两种已成型的 RPC 适配模式——HTTP 守护进程signal-cli与 stdio 子进程imsg——并结合仓库源码daemon.ts、client.ts 等剖析其进程生命周期管理、事件流接入、超时与恢复机制最后归纳可复用的适配器设计准则。读完本文你将能理解 OpenClaw 如何拥有外部 CLI 进程、两种模式各自的配置与适用场景以及如何规避最常见的集成陷阱。一、为什么要用 JSON-RPC外部 CLI 适配的整体思路OpenClaw 并不内嵌 Signal 协议栈不引入 libsignal也不直接操作 macOS Messages 私有数据库而是将消息收发委托给成熟的社区 CLIsignal-cli与imsg。两者之间需要一个统一、稳定、可调试的进程间协议OpenClaw 选择的标准就是JSON-RPC 2.0请求/响应携带jsonrpc、id、method、params、result/error字段。从源码结构看这一思路体现在两个独立插件中插件外部 CLIRPC 传输事件推送主要源码openclaw/signalsignal-cliHTTP POST JSON-RPCSSEServer-Sent Eventsclient.ts、daemon.tsopenclaw/imessageimsg行分隔 JSON-RPC over stdin/stdout服务端主动通知notificationclient.ts、monitor-provider.ts两种模式的核心区别在于传输载体一个走常驻守护进程 HTTP 端口另一个走随启随用的子进程 标准输入输出管道。下面分别展开。二、Pattern AHTTP 守护进程模式signal-cli2.1 运行形态与端点约定signal-cli以守护进程daemon方式运行对外暴露三类 HTTP 端点JSON-RPC 调用端点POST /api/v1/rpc承载send、listAccounts等 RPC 方法调用事件流端点GET /api/v1/eventsSSE 长连接推送收到的消息事件健康探针端点GET /api/v1/check用于启动就绪检测与诊断。这三者的实现均可在 client.ts 中对应找到signalRpcRequest()组装{jsonrpc:2.0,method:...,params:...,id:...}请求体并以POST /api/v1/rpc发出HTTP 201 视为无返回值的成功client.tssignalCheck()以GET /api/v1/check探测健康状态2xx 视为就绪client.tsstreamSignalEvents()以Accept: text/event-stream打开GET /api/v1/events并在内部完成 SSE 帧解析事件、数据、ID 字段拆解含 1 MiB 的缓冲区与单事件数据上限保护client.ts。2.2 进程生命周期managed-native 与 external-native当channels.signal.transport.kind managed-native默认值时OpenClaw 拥有 signal-cli 守护进程的完整生命周期启动spawn、就绪等待、退出监听、停止stop。这是文档强调的OpenClaw owns lifecycle的具体含义。启动参数构造daemon.ts 中的buildDaemonArgs()展示了实际传给signal-cli的参数signal-cli [--config path] [-a account] daemon --http host:port --no-receive-stdout [--receive-mode on-start|manual] [--ignore-attachments] [--ignore-stories] [--send-read-receipts]其中--no-receive-stdout强制所有消息事件走 HTTP/SSE 通道而非标准输出这是 OpenClaw 采用 Pattern A 的关键前提。端口与地址管理默认绑定地址与端口为127.0.0.1:8080由 transport-policy.ts 中的DEFAULT_SIGNAL_MANAGED_NATIVE_PORT与DEFAULT_SIGNAL_MANAGED_NATIVE_HOST定义。多账户场景下allocateSignalManagedNativePort()会从 8080 起自动递增分配未被占用的端口transport-policy.ts。启动前还会执行assertSignalDaemonEndpointAvailable()预检端口若发现EADDRINUSE端口被占会给出明确报错提示停止冲突服务、换transport.httpPort、或改用external-native交给运维自行管理——可见端口冲突是 managed-native 模式最常见的失败点daemon.ts。就绪等待与优雅退出waitForSignalDaemonReady()以 150ms 间隔轮询/api/v1/check直至启动截止时间startupTimeoutMs范围 1000120000ms默认 30000msdaemon.ts该超时可通过channels.signal.transport.startupTimeoutMs配置针对 JVM 冷启动慢的场景。停止流程是典型的先礼后兵先发SIGTERM若 1500ms 内未退出再发SIGKILL并且 stop 承诺会等待守护进程真正释放端口与配置锁后才 resolve避免新守护进程启动时与旧进程冲突daemon.ts。日志分类守护进程的 stdout/stderr 输出经过 classifySignalCliLogLine() 分类ERROR、FAILED、SEVERE、EXCEPTION视为错误进入失败状态而receive exception: invalid PreKey message: decryption failed这类可恢复的接收解密失败被降级为普通日志避免噪音干扰故障判定。2.3 配置示例managed-native{ channels: { signal: { enabled: true, account: 15551234567, transport: { kind: managed-native, cliPath: signal-cli, // configPath: ~/.local/share/signal-cli, // 可选signal-cli --config 目录 // httpHost: 127.0.0.1, // 默认 // httpPort: 8080, // 默认多账户时自动递增 // startupTimeoutMs: 30000, // min 1000, cap 120000 // receiveMode: on-start, // on-start | manual // ignoreStories: false, }, dmPolicy: pairing, allowFrom: [15557654321], }, }, }transport.kind的三个取值完整表格见 docs/channels/signal.md值行为适用场景managed-nativeOpenClaw spawn signal-cliJSON-RPC 于/api/v1/rpc SSE 于/api/v1/eventsurl可指定与守护进程绑定不同的连接端点默认适合大多数单机部署external-native连接一个已由运维启动的 signal-cli 守护进程JVM 冷启动慢、容器 init、共享 CPU 等场景container连接 bbernhard/signal-cli-rest-api 容器REST/v2/send WebSocket/v1/receive/{account}已有容器化部署注意容器模式与原生模式走完全不同的协议栈openclaw doctor --fix可以探测端点识别其具体种类但运行时不会自动探测或切换协议——配置必须与后端一致。2.4 运行时主循环monitor.ts 展示了 managed-native 的完整启动序列预检端口 → 重新检查 abort 与截止时间 →spawnSignalDaemon()→waitForSignalDaemonReady()→ 注册通道运行时上下文 → 启动 ingress 监控 → 进入runSignalSseLoop()常驻事件循环事件到达后经createSignalEventHandler()归一化为统一通道信封envelope。整个流程与abortSignal严格联动任何阶段中止都会触发守护进程清理确保进程生命周期与 provider 生命周期绑定。三、Pattern Bstdio 子进程模式imsg3.1 运行形态无需端口、无需守护进程iMessage 通道openclaw/imessage采用另一种形态OpenClaw 以子进程方式 spawnimsg rpcJSON-RPC 以换行分隔line-delimited在 stdin/stdout 上传输每行一个 JSON 对象。没有 TCP 端口、没有独立 daemon进程随通道启停。这大幅简化了部署尤其适合 macOS 上的权限上下文代价是调用方必须自行负责行分帧与请求-响应匹配。3.2 核心 RPC 方法文档列出的四个核心方法在源码与测试中均可验证方法用途佐证watch.subscribe订阅消息变更服务端通过method: message的 notification 推送新消息支持since_rowid参数用于断线重放monitor-provider.tswatch.unsubscribe取消订阅与 subscribe 成对出现send发送文本/媒体消息actions.runtime.tschats.list列出会话探针/诊断用--limit分页actions-chat-guid.ts特别地watch.subscribe的since_rowid是 iMessage 断线自动恢复机制的基础网关重启后把上次持久化的chat.dbrowid 传给 imsg让 imsg 重放停机期间错过的行再进入实时尾随详见 docs/channels/imessage.md 的 Inbound recovery 一节。3.3 客户端实现剖析IMessageRpcClientclient.ts 中的IMessageRpcClient是 Pattern B 的参考实现几个关键设计行分帧LF framercreateLfLineFramer()使用StringDecoder缓冲二进制分块按\n切出完整行即使 UTF-8 码点被 TCP 分块拆散也能正确重组并支持flush()处理文件末尾未换行的残片client.ts。请求-响应匹配每次request()生成自增id将{jsonrpc,id,method,params}序列化并追加\n写入 stdin响应行按id在pendingMap 中匹配并 resolve/reject无id的行则视为 notification转交onNotification回调client.ts、client.ts。超时与韧性探针类操作默认超时 10sDEFAULT_IMESSAGE_PROBE_TIMEOUT_MS见 constants.ts发送操作因 imsg 私有桥最长等待 150s 且可能回退 AppleScript外层超时设为180s以覆盖完整链路DEFAULT_IMESSAGE_SEND_TIMEOUT_MS见 constants.ts。桥接停滞自动恢复当错误消息包含Timed out waiting for responseimsg 私有 API 桥停止响应时客户端会失效缓存的能力状态、尝试自动恢复桥接recoverIMessageBridge并在错误信息后追加可操作指引Run imsg launch to re-inject the dylib, then openclaw channels status --probeclient.ts、client.ts。进程停止stopChild()采用三级降级——先stdin.end()优雅关闭500ms 宽限后SIGTERM再 500ms 后SIGKILL确保不残留孤儿进程client.ts。3.4 配置示例与 SSH 封装本机部署的最小配置{ channels: { imessage: { enabled: true, cliPath: /usr/local/bin/imsg, dbPath: /Users/user/Library/Messages/chat.db, }, }, }当网关不在 Messages Mac 上运行时cliPath可指向一个透明的 SSH 封装脚本网关主机上其必须保持长生命周期、字节级透传的 stdio 管道语义——逐字节转发、保留换行、避免固定大小阻塞读、保持 stderr 与 JSON-RPC stdout 分离#!/usr/bin/env bash exec ssh -T messages-mac imsg ${ channels: { imessage: { enabled: true, cliPath: /home/openclaw/.openclaw/scripts/imsg-ssh, remoteHost: usermessages-mac, // 在 Messages Mac 上解释 dbPath dbPath: /Users/user/Library/Messages/chat.db, includeAttachments: true, }, }, }注意像ssh host imsg | grep -v ^DEBUG这类带管道的写法不安全——行缓冲工具可能滞留小帧症状表现为imsg rpc timeout (chats.list)或通道反复重启而imsg rpc本身是健康的。详见 docs/channels/imessage.md。四、适配器设计准则三条通用原则文档将两种模式的共性沉淀为三条准则源码实现提供了完整印证4.1 Gateway 拥有进程生命周期无论哪种模式进程的 start/stop 都必须与 provider通道生命周期严格绑定managed-native 下 OpenClaw spawn/stop signal-cli 守护进程daemon.tsiMessage 通道下 OpenClaw spawn/stopimsg rpc子进程client.ts。这与外部 CLI 集成的边界设计一致外部进程只做协议翻译生命周期与故障责任都在网关侧。4.2 保持 RPC 客户端韧性超时 退出处理超时兜底所有请求都有超时上限signal HTTP 默认 10simsg 探针 10s、发送 180s避免对无响应外部进程的无限等待。退出监听SignalDaemonHandle暴露exitedPromise 与isExited()进程异常退出会作为通道失败状态上抛daemon.tsIMessageRpcClient将进程/stdio 任一错误视为传输终结事件统一 fail 所有 pending 请求client.ts。自动恢复imsg 桥停滞时自动触发recoverIMessageBridgeSSE 事件循环支持重连策略reconnectPolicy。4.3 优先使用稳定 ID 而非显示字符串iMessage 的寻址推荐chat_id:123、chat_guid:...、chat_identifier:...等显式稳定标识见 docs/channels/imessage.md 的 Addressing formats文档明确说明chat_id优于显示字符串——显示名会变、可重复而chat_id是 SQLite 主键。Signal 侧同样如此发送者使用 E.164 号码或uuid:id来自sourceUuid群组使用group:groupId别名aliases机制即为为稳定 ID 起稳定名字而设计。五、两种模式的选型对比与实战建议维度Pattern Asignal-cliPattern Bimsg传输载体HTTP SSEstdin/stdout 行分隔 JSON-RPC端口占用 TCP 端口默认 127.0.0.1:8080无端口常驻进程是daemon可 managed 或 external否随通道 spawn事件推送SSE 长连接服务端 notification生命周期归属managed-native 时归 OpenClaw归 OpenClaw关键配置transport.kind/httpPort/startupTimeoutMscliPath/dbPath/remoteHost典型坑端口冲突EADDRINUSE、JVM 冷启动超时封装 wrapper 破坏行分帧、权限Full Disk Access / Automation选型建议单机部署且能接受常驻进程用 managed-native默认即可守护进程启动慢或想自行运维改external-native已有 bbernhard 容器则用container。iMessage 通道只有 stdio 一条路部署核心是确保imsg与网关位于同一权限上下文或通过透明的 SSH 封装并优先启用 Private API 模式以解锁全部原生动作。六、延伸阅读RPC 适配参考本文骨架文档Signal 通道完整文档number model、安装、QR 链接/SMS 注册、访问控制、媒体与限额iMessage 通道完整文档imsg 安装、Private API 启用、SSH 拓扑、断线自动恢复Gateway 协议文档OpenClaw 自身的 JSON-RPC 方法族与事件体系源码入口signal 客户端、signal 守护进程、imsg RPC 客户端【免费下载链接】openclawThe AI that really does things. Any OS. Any Platform. The lobster way. 项目地址: https://gitcode.com/GitHub_Trending/cl/openclaw创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考