news 2026/10/4 13:49:54

OpenClaw 接入 Signal 通道实战指南:基于 signal-cli 的 JSON-RPC + SSE 集成、配置与运维

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
OpenClaw 接入 Signal 通道实战指南:基于 signal-cli 的 JSON-RPC + SSE 集成、配置与运维
  • 人工智能
  • AI Agent
  • 即时通讯
  • 后端
  • 本地部署
  • 语音

【免费下载链接】openclaw-cn

中文社区版OpenClaw,同原版保持定期更新,已内置钉钉、企业微信、飞书、QQ、微信以及国内网络环境优化。你的专属个人AI助手。支持所有操作系统和平台。🦞

项目地址:https://gitcode.com/gh_mirrors/op/openclaw-cn
点击查看免费下载

本文是 OpenClaw 中文社区版(仓库路径gh_mirrors/op/openclaw-cn)接入 Signal 即时通讯通道的完整技术指南。全文围绕 docs/channels/signal.md 展开,从零起步讲解signal-cli的安装、设备链接、最小配置、访问控制、媒体处理、表情回应与投递目标语法,并结合仓库源码(src/signal 与 src/config/types.signal.ts)剖析网关与 daemon 之间 JSON-RPC + SSE 的底层通信机制。读完本文,你将能够独立完成 Signal 机器人通道的搭建、调试与多账户扩展,并理解每个配置项在代码层面的真实作用。

架构总览:OpenClaw 如何与 Signal 通信

Signal 通道在 OpenClaw 中属于外部 CLI 集成(external CLI integration)类型:网关并不内嵌 libsignal 协议栈,而是通过 HTTP 与signal-cli守护进程通信,具体采用两条通道:

  • JSON-RPC(请求/响应):网关向signal-cli发送发消息、收附件、发回执、发表情等指令;
  • SSE(Server-Sent Events,事件流):网关持续订阅来自signal-cli的入站消息事件。

这两条通道的实现可以在 src/signal/client.ts 中看到:RPC 请求封装为 JSON-RPC 2.0 格式 POST 到/api/v1/rpc,健康检查 GET/api/v1/check,事件流则通过Accept: text/event-stream长连接读取/api/v1/events(可携带account查询参数区分多账户)。src/signal/sse-reconnect.ts 负责事件流的断线重连,保证长时间运行的稳定性。

路由行为有两个明确约定:

  • 确定性路由:机器人的回复永远回到原对话(同一个号码或同一个群组);
  • 会话隔离:私聊(DM)复用 Agent 的主会话;群聊则使用独立会话键agent:<agentId>:signal:group:<groupId>,不同群之间上下文互不串扰。

快速上手(新手路径)

官方推荐的起步流程只有四步,强烈建议使用独立的 Signal 号码来运行机器人,而不是个人号码(原因见下文“号码模型”一节):

  1. 安装signal-cli(需要 Java 运行环境);
  2. 链接机器人设备:执行signal-cli link -n "Clawdbot",然后用手机 Signal 扫描终端输出的二维码;
  3. 配置 OpenClaw;
  4. 启动网关。

最简配置如下(JSON5 格式):

{ channels: { signal: { enabled: true, account: "+15551234567", cliPath: "signal-cli", dmPolicy: "pairing", allowFrom: ["+15557654321"] } } }

配置引导器(onboarding wizard)也内置了 Signal 的设置流程,可在 src/channels/plugins/onboarding/signal.ts 中看到它会提示输入allowFrom(E.164 或uuid:形式),并给出signal-cli link -n "OpenClaw"的链接指引。

配置写入权限

默认情况下,Signal 会话中的用户可以通过/config set|unset命令触发配置更新(前提是commands.config: true已开启)。如果希望禁止这种通道发起的配置变更,可显式关闭:

{ channels: { signal: { configWrites: false } } }

该字段在 src/config/types.signal.ts 中定义为configWrites?: boolean,默认true。它同样支持在accounts.<id>级别逐账户覆盖。

号码模型(重要)

理解 Signal 通道的前提是弄清楚“网关连接的是谁的设备”:

  • 网关连接的是一个 Signal 设备,即signal-cli所代表的那个账户(由channels.signal.account指定 E.164 号码);
  • 如果你把机器人跑在个人 Signal 账户上,网关会忽略你自己发出的消息——这是防回环保护(loop protection),避免机器人与自己的消息互相触发;
  • 因此,要实现“我发消息、机器人回复”的效果,请使用独立的机器人号码。

快速安装(fast path)

进阶路径与新手路径类似,但强调两点:

  1. 安装signal-cli(Java 必需);
  2. 链接机器人账户:signal-cli link -n "Clawdbot",在 Signal 中扫描二维码完成设备注册;
  3. 配置 Signal 通道并启动网关。

以多账户为例,可同时配置多个机器人号码:

{ channels: { signal: { accounts: { main: { enabled: true, name: "Main Bot", account: "+15551234567", cliPath: "signal-cli" }, backup: { enabled: true, name: "Backup Bot", account: "+15559876543" } } } } }

多账户配置使用channels.signal.accounts.<id>键值结构,每个账户拥有独立的配置项和可选的name(用于 CLI/UI 列表显示),这是与 Telegram/Discord/Slack/iMessage 等通道共用的模式,详见 docs/gateway/configuration.md。

从源码看,账户解析逻辑位于 src/signal/accounts.ts:listSignalAccountIds在未配置accounts时返回默认账户 ID;resolveSignalAccount会把顶层基础配置(如httpHost、httpPort、autoStart)与账户级配置合并,并用merged.httpUrl || "http://<httpHost>:<httpPort>"构造 daemon 的 base URL。也就是说,多账户模式下未在账户内显式设置的字段会继承channels.signal顶层值。

外部守护进程模式(httpUrl)

signal-cli是 JVM 应用,存在冷启动慢的问题;在容器初始化或共享 CPU 场景下,你可能更希望自己管理 daemon 的生命周期,让 OpenClaw 只做客户端。此时使用httpUrl指向外部 daemon:

{ channels: { signal: { httpUrl: "http://127.0.0.1:8080", autoStart: false } } }

配置httpUrl后,网关会跳过自动 spawn 和启动等待,直接连接该地址。如果仍由网关自动拉起 daemon 但机器启动缓慢,可以调大channels.signal.startupTimeoutMs延长等待时间。

网关自动拉起 daemon 时的真实命令行可以在 src/signal/daemon.ts 中看到:

signal-cli [-a <account>] daemon --http <host>:<port> --no-receive-stdout [--receive-mode <mode>] [--ignore-attachments] [--ignore-stories] [--send-read-receipts]

实现要点:

  • spawnSignalDaemon用node:child_process的spawn启动子进程,--http指定监听地址(默认127.0.0.1:8080);
  • --no-receive-stdout确保入站消息走 SSE 事件流而非标准输出;
  • daemon 的 stdout/stderr 日志会被classifySignalCliLogLine分类:包含ERROR/WARN/WARNING或FAILED/SEVERE/EXCEPTION的行进入错误日志,其余按普通日志处理;
  • 进程收到 abort 信号或网关退出时,会向子进程发送SIGTERM优雅停止。

启动阶段,src/signal/monitor.ts 的waitForSignalDaemonReady会以 150ms 间隔轮询/api/v1/check,直到 daemon 就绪或超过startupTimeoutMs(源码中该值被钳制在 1000ms~120000ms 之间,默认 30000ms)。

访问控制(私聊 + 群聊)

私聊(DM)

  • 默认策略channels.signal.dmPolicy = "pairing";
  • 未知发送者会收到一个pairing code(配对码),其消息被忽略,直到管理员批准;配对码1 小时过期;
  • 批准命令:
    • openclaw-cn pairing list signal
    • openclaw-cn pairing approve signal <CODE>

配对是 Signal 私聊的默认令牌交换机制,细节见 docs/start/pairing.md。

关于发送者身份,需要特别注意:Signal 消息可能只携带sourceUuid而没有号码。此时发送者被标识为uuid:<id>,并以该形式写入channels.signal.allowFrom。发送者解析与匹配实现在 src/signal/identity.ts:

  • resolveSignalSender优先取sourceNumber(规范化为 E.164),否则取sourceUuid;
  • formatSignalSenderId对 UUID 发送者输出uuid:<raw>,对号码发送者输出 E.164;
  • isSignalSenderAllowed支持三类白名单条目:*(任意)、uuid:<id>、裸 E.164;UUID 匹配还兼容连字符格式与 32 位紧凑格式。

群聊(Group)

  • channels.signal.groupPolicy可选open | allowlist | disabled;
  • 当策略为allowlist时,channels.signal.groupAllowFrom决定谁可以在群内触发机器人。

从源码看(src/signal/identity.ts 的isSignalGroupAllowed),判定逻辑为:disabled一律拒绝;open一律放行;allowlist则回落到与私聊一致的发送者白名单匹配。另外,src/signal/monitor.ts 中若未单独配置groupAllowFrom,会回退复用allowFrom。

工作方式(行为细节)

  • signal-cli以 daemon 形式常驻,网关通过SSE读取入站事件;
  • 入站消息被归一化为共享的通道信封(channel envelope),即 OpenClaw 各通道统一的消息中间表示,便于后续 Agent 会话处理;
  • 回复始终路由回同一个号码或群组(确定性路由)。

入站事件到会话的完整链路

结合 src/signal/monitor.ts 与 src/signal/monitor/event-handler.ts,入站处理链大致为:

  1. monitorSignalProvider解析账户与全部运行参数(历史上限、文本分块上限、访问策略、媒体上限、回执开关等);
  2. 若autoStart为真则拉起 daemon 并等待就绪,否则直接使用外部baseUrl;
  3. runSignalSseLoop建立 SSE 长连接,把每个事件交给createSignalEventHandler;
  4. 事件处理器按发送者、群组、会话键构建上下文,进入 Agent 主循环;回复经deliverReplies写回 daemon(sendRPC)。

媒体与文本限制

  • 出站文本按channels.signal.textChunkLimit分块发送,默认 4000 字符;
  • 可选channels.signal.chunkMode="newline":优先在**空行(段落边界)**处断行,再按长度分块;
  • 附件支持:从signal-cli以base64形式拉取后落盘;
  • 默认媒体上限channels.signal.mediaMaxMb(默认 8 MB);
  • 用channels.signal.ignoreAttachments可跳过媒体下载;
  • 群聊历史上下文由channels.signal.historyLimit(或channels.signal.accounts.*.historyLimit)控制,未设置时回退到messages.groupChat.historyLimit;设为0禁用,默认 50条。

这些行为在源码中的对应实现:

  • 分块逻辑位于 src/auto-reply/chunk.ts,resolveTextChunkLimit支持顶层、账户级、DM 级逐级覆盖;chunkMode的newline模式只在段落边界(空行)处断开,避免打断段落内部的单行换行;
  • 附件拉取在 src/signal/monitor.ts 的fetchAttachment中完成:先检查attachment.size是否超过maxBytes(超限直接抛错并说明限额),再调用getAttachmentRPC 获取 base64 数据,经saveMediaBuffer落盘;
  • 群历史上限解析在 src/signal/monitor.ts 中:accountInfo.config.historyLimit ?? cfg.messages?.groupChat?.historyLimit ?? DEFAULT_GROUP_HISTORY_LIMIT。

输入指示与已读回执

  • 输入指示(typing indicators):回复生成期间,网关通过signal-cli sendTyping持续发送输入状态并周期性刷新;
  • 已读回执(read receipts):当channels.signal.sendReadReceipts为true时,网关会为允许的私聊转发已读回执;
  • 群聊不支持已读回执(signal-cli 不暴露该能力)。

对应 RPC 实现在 src/signal/send.ts:

  • sendTypingSignal调用sendTyping方法,stop参数为真时通知对方停止输入;
  • sendReadReceiptSignal调用sendReceipt,支持type: "read" | "viewed",且要求合法的targetTimestamp。

在事件处理器中(src/signal/monitor/event-handler.ts),已读回执只对非群聊(!isGroup)且sendReadReceipts开启时发出;若 daemon 由网关托管(readReceiptsViaDaemon),则直接依赖 daemon 的--send-read-receipts参数,避免重复发送。

表情回应(message 工具)

使用message action=react并指定channel=signal:

  • 目标:发送者的 E.164 号码或 UUID(可用uuid:<id>形式,裸 UUID 也可);
  • messageId:被回应消息的 Signal时间戳;
  • 群内回应必须提供targetAuthor或targetAuthorUuid。

示例:

message action=react channel=signal target=uuid:123e4567-e89b-12d3-a456-426614174000 messageId=1737630212345 emoji=🔥 message action=react channel=signal target=+15551234567 messageId=1737630212345 emoji=🔥 remove=true message action=react channel=signal target=signal:group:<groupId> targetAuthor=uuid:<sender-uuid> messageId=1737630212345 emoji=✅

配置项:

  • channels.signal.actions.reactions:开关表情回应动作(默认true);
  • channels.signal.reactionLevel:off | ack | minimal | extensive;
    • off/ack会禁用 Agent 的表情回应(此时 message 工具的react会报错);
    • minimal/extensive启用 Agent 表情回应并设置引导强度;
  • 逐账户覆盖:channels.signal.accounts.<id>.actions.reactions、channels.signal.accounts.<id>.reactionLevel。

源码层面的实现要点(src/signal/send-reactions.ts):

  • 通过sendReactionRPC 发送/移除表情,移除时设置remove: true;
  • targetAuthor解析接受uuid:前缀、裸 UUID、E.164 三种输入,最终统一为信号侧可用的目标;
  • 群内回应(传入groupId)时若缺少targetAuthor会直接报错,这与文档要求一致;
  • reactionLevel的完整定义见 src/config/types.signal.ts:ack仅允许自动确认回应(处理中发送 👀),minimal(默认)与extensive分别对应“克制”与“宽松”的 Agent 表情使用策略。

投递目标语法(CLI / cron)

向 Signal 投递消息时,目标(target)支持以下形式:

目标类型语法说明
私聊号码signal:+15551234567或裸 E.164最常用
UUID 私聊uuid:<id>或裸 UUID针对仅携带 UUID 的发送者
群组signal:group:<groupId>groupId 为 Signal 群 ID
用户名username:<name>取决于你的 Signal 账户是否支持

目标字符串的归一化逻辑在 src/channels/plugins/normalize/signal.ts:会剥离可选的signal:前缀,识别group:、username:/u:、uuid:前缀并各自归一化;looksLikeSignalTargetId则用于判断一个字符串是否像 Signal 目标 ID(支持 UUID 连字符/紧凑格式与 E.164 号码)。

配置参考(Signal 全量)

完整配置请见 docs/gateway/configuration.md。以下为 Signal 通道的全部 Provider 配置项,字段定义与注释均可对照 src/config/types.signal.ts:

基础连接

配置项默认值说明
channels.signal.enabled—启用/禁用通道启动
channels.signal.account—机器人账户的 E.164 号码
channels.signal.cliPathsignal-clisignal-cli可执行文件路径
channels.signal.httpUrl—daemon 完整 URL(覆盖 host/port)
channels.signal.httpHost127.0.0.1daemon 绑定地址
channels.signal.httpPort8080daemon 绑定端口
channels.signal.autoStarthttpUrl未设置时为true是否自动拉起 daemon
channels.signal.startupTimeoutMs30000(上限120000)等待 daemon 就绪的超时(毫秒)
channels.signal.receiveMode—on-start/manual,对应 daemon 的--receive-mode
channels.signal.ignoreAttachmentsfalse跳过附件下载
channels.signal.ignoreStoriesfalse忽略 daemon 推送的 stories
channels.signal.sendReadReceiptsfalse转发已读回执

访问控制

配置项默认值说明
channels.signal.dmPolicypairingpairing | allowlist | open | disabled
channels.signal.allowFrom—DM 白名单(E.164 或uuid:<id>);open策略要求写"*"。Signal 没有用户名,请用号码/UUID
channels.signal.groupPolicyallowlistopen | allowlist | disabled
channels.signal.groupAllowFrom回退到allowFrom群内发送者白名单

历史与消息

配置项默认值说明
channels.signal.historyLimit回退messages.groupChat.historyLimit,默认50群聊历史上下文条数,0禁用
channels.signal.dmHistoryLimit—DM 历史(按用户轮数计);可按用户覆盖:channels.signal.dms["<phone_or_uuid>"].historyLimit
channels.signal.textChunkLimit4000出站文本分块大小(字符数)
channels.signal.chunkModelengthlength(按长度分块)或newline(先按空行/段落边界断行,再按长度分块)
channels.signal.mediaMaxMb8入站/出站媒体上限(MB)
channels.signal.blockStreaming—是否阻塞流式输出
channels.signal.responsePrefix—该通道/账户的出站回复前缀覆盖

表情与通知

配置项默认值说明
channels.signal.actions.reactionstrue启用/禁用 message 工具的表情回应
channels.signal.reactionLevelminimaloff | ack | minimal | extensive
channels.signal.reactionNotificationsownoff | own | all | allowlist,控制“别人回应了你的消息”时是否生成系统事件通知
channels.signal.reactionAllowlist—reactionNotifications为allowlist时的白名单

相关全局选项

  • agents.list[].groupChat.mentionPatterns:群内提及模式(Signal 不原生支持提及);
  • messages.groupChat.mentionPatterns:全局回退的提及模式;
  • messages.responsePrefix:全局回复前缀。

调试与排障要点

结合源码,遇到问题时可按以下顺序排查:

  1. daemon 是否就绪:网关启动时会轮询/api/v1/check,超过startupTimeoutMs(上限 120s)即报错;可先手动执行signal-cli daemon --http 127.0.0.1:8080 --no-receive-stdout验证端口可访问;
  2. 日志分类:signal-cli的日志统一经过classifySignalCliLogLine分级,包含ERROR/WARN/FAILED/SEVERE/EXCEPTION的行会进入网关错误日志,排查时可关注这些关键词;
  3. 会话被忽略:若“机器人不回复自己”,先确认是否把机器人跑在了个人号码上(见“号码模型”);
  4. UUID 发送者:仅携带sourceUuid的消息会以uuid:<id>身份出现,白名单必须使用同一形式;
  5. 表情失败:群内react必须先提供targetAuthor/targetAuthorUuid,messageId必须是目标消息的 Signal 时间戳;
  6. 媒体超限:附件超限时日志会明确提示超出多少 MB,可调大mediaMaxMb或改用ignoreAttachments跳过下载。

小结

Signal 通道是 OpenClaw 众多外部 CLI 集成中的典型代表:网关不碰 libsignal 协议细节,而是以signal-clidaemon 为中间层,用一套轻量的 HTTP JSON-RPC + SSE 协议完成收发、回执、输入状态与表情回应。理解 docs/channels/signal.md 与 src/signal 源码的对应关系后,你不仅能完成标准部署,还能从容应对多账户、外部 daemon、UUID 发送者与群聊权限等进阶场景。

  • 人工智能
  • AI Agent
  • 即时通讯
  • 后端
  • 本地部署
  • 语音

【免费下载链接】openclaw-cn

中文社区版OpenClaw,同原版保持定期更新,已内置钉钉、企业微信、飞书、QQ、微信以及国内网络环境优化。你的专属个人AI助手。支持所有操作系统和平台。🦞

项目地址:https://gitcode.com/gh_mirrors/op/openclaw-cn
点击查看免费下载

相关推荐

上一篇:魔兽争霸III终极辅助工具:免费开源的游戏体验增强完整指南
下一篇:Sunshine终极指南:5分钟搭建免费游戏串流服务器的完整教程

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/10/4 13:49:02

openEuler太空计算Meetup:星载操作系统技术需求与部署迁移路径

1. 从一场成都Meetup说起&#xff1a;openEuler为什么要谈太空计算2026年openEuler Meetup成都站把主题定在了“操作系统技术”与“太空计算”的交叉点上&#xff0c;这个组合乍看有点跳脱&#xff0c;但如果你这两年一直在跟openEuler的社区动态&#xff0c;会发现这条线其实铺…

作者头像 李华
网站建设 2026/10/4 13:47:34

MRAM工业嵌入式存储实战:MR25H40CDF与PIC18F86J15驱动开发

1. 为什么 MRAM 在工业嵌入式场景里越来越受关注1.1 从 EEPROM 和 Flash 的痛点说起做过工业设备的人大概都有过这样的经历&#xff1a;现场设备跑了三年&#xff0c;突然某天参数丢失&#xff0c;返厂一查是 EEPROM 某个扇区擦写寿命到了。或者更尴尬的是&#xff0c;设备正在…

作者头像 李华
网站建设 2026/10/4 13:45:48

一行代码调用Clef:Jev/SystemOne /v1/systemone兼容API实战指南

一行代码调用Clef&#xff1a;Jev/SystemOne /v1/systemone兼容API实战指南 【免费下载链接】clef 项目地址: https://ai.gitcode.com/hf_mirrors/Cloudflare/clef Clef 是 Cloudflare 开源的 27B 多模态决策模型&#xff0c;它的 API 与 Jev / SystemOne 的 POST /v1/…

作者头像 李华
网站建设 2026/10/4 13:44:32

MRAM与PIC18F96J94工业数据存储方案:SPI驱动与掉电保护实战

1. 项目缘起与方案选型&#xff1a;为什么是 MR25H40CDF 加 PIC18F96J941.1 一个真实的需求场景前阵子接了个工业数据采集终端的活儿&#xff0c;客户的要求很朴素&#xff1a;设备要在产线上连续跑&#xff0c;断电不能丢数据&#xff0c;写入要快&#xff0c;寿命要长&#x…

作者头像 李华
网站建设 2026/10/4 13:44:09

MRAM与PIC18F4455的工业数据存储方案:掉电不丢、无限写入

把 MR25H40CDF 和 PIC18F4455 放在一起做数据存储&#xff0c;是我去年帮客户做工业参数记录仪时定下来的方案。前者是 Everspin 一颗 4Mbit 的串行 MRAM&#xff0c;后者是 Microchip 的老牌 USB 单片机&#xff0c;组合到一起后&#xff0c;掉电不丢、无限次写入、免擦除&…

作者头像 李华
网站建设 2026/10/4 13:42:23

R报错:parallelSlotNames不是S4泛型?彻底排查与修复指南

用 R 的人&#xff0c;尤其是折腾 Bioconductor 生态的&#xff0c;应该都见过这类让人头皮发麻的报错&#xff1a;in processing ‘XVector’ namespace, exportMethods(parallelSlotNames) failed: ‘parallelSlotNames’ is not an S4 generic function。第一次遇到的时候&a…

作者头像 李华