- 人工智能
- AI Agent
- 即时通讯
- 后端
- 本地部署
- 语音
【免费下载链接】openclaw-cn
中文社区版OpenClaw,同原版保持定期更新,已内置钉钉、企业微信、飞书、QQ、微信以及国内网络环境优化。你的专属个人AI助手。支持所有操作系统和平台。🦞
本文是 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 号码来运行机器人,而不是个人号码(原因见下文“号码模型”一节):
- 安装
signal-cli(需要 Java 运行环境); - 链接机器人设备:执行
signal-cli link -n "Clawdbot",然后用手机 Signal 扫描终端输出的二维码; - 配置 OpenClaw;
- 启动网关。
最简配置如下(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)
进阶路径与新手路径类似,但强调两点:
- 安装
signal-cli(Java 必需); - 链接机器人账户:
signal-cli link -n "Clawdbot",在 Signal 中扫描二维码完成设备注册; - 配置 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 signalopenclaw-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,入站处理链大致为:
monitorSignalProvider解析账户与全部运行参数(历史上限、文本分块上限、访问策略、媒体上限、回执开关等);- 若
autoStart为真则拉起 daemon 并等待就绪,否则直接使用外部baseUrl; runSignalSseLoop建立 SSE 长连接,把每个事件交给createSignalEventHandler;- 事件处理器按发送者、群组、会话键构建上下文,进入 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.cliPath | signal-cli | signal-cli可执行文件路径 |
channels.signal.httpUrl | — | daemon 完整 URL(覆盖 host/port) |
channels.signal.httpHost | 127.0.0.1 | daemon 绑定地址 |
channels.signal.httpPort | 8080 | daemon 绑定端口 |
channels.signal.autoStart | httpUrl未设置时为true | 是否自动拉起 daemon |
channels.signal.startupTimeoutMs | 30000(上限120000) | 等待 daemon 就绪的超时(毫秒) |
channels.signal.receiveMode | — | on-start/manual,对应 daemon 的--receive-mode |
channels.signal.ignoreAttachments | false | 跳过附件下载 |
channels.signal.ignoreStories | false | 忽略 daemon 推送的 stories |
channels.signal.sendReadReceipts | false | 转发已读回执 |
访问控制
| 配置项 | 默认值 | 说明 |
|---|---|---|
channels.signal.dmPolicy | pairing | pairing | allowlist | open | disabled |
channels.signal.allowFrom | — | DM 白名单(E.164 或uuid:<id>);open策略要求写"*"。Signal 没有用户名,请用号码/UUID |
channels.signal.groupPolicy | allowlist | open | 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.textChunkLimit | 4000 | 出站文本分块大小(字符数) |
channels.signal.chunkMode | length | length(按长度分块)或newline(先按空行/段落边界断行,再按长度分块) |
channels.signal.mediaMaxMb | 8 | 入站/出站媒体上限(MB) |
channels.signal.blockStreaming | — | 是否阻塞流式输出 |
channels.signal.responsePrefix | — | 该通道/账户的出站回复前缀覆盖 |
表情与通知
| 配置项 | 默认值 | 说明 |
|---|---|---|
channels.signal.actions.reactions | true | 启用/禁用 message 工具的表情回应 |
channels.signal.reactionLevel | minimal | off | ack | minimal | extensive |
channels.signal.reactionNotifications | own | off | own | all | allowlist,控制“别人回应了你的消息”时是否生成系统事件通知 |
channels.signal.reactionAllowlist | — | reactionNotifications为allowlist时的白名单 |
相关全局选项
agents.list[].groupChat.mentionPatterns:群内提及模式(Signal 不原生支持提及);messages.groupChat.mentionPatterns:全局回退的提及模式;messages.responsePrefix:全局回复前缀。
调试与排障要点
结合源码,遇到问题时可按以下顺序排查:
- daemon 是否就绪:网关启动时会轮询
/api/v1/check,超过startupTimeoutMs(上限 120s)即报错;可先手动执行signal-cli daemon --http 127.0.0.1:8080 --no-receive-stdout验证端口可访问; - 日志分类:
signal-cli的日志统一经过classifySignalCliLogLine分级,包含ERROR/WARN/FAILED/SEVERE/EXCEPTION的行会进入网关错误日志,排查时可关注这些关键词; - 会话被忽略:若“机器人不回复自己”,先确认是否把机器人跑在了个人号码上(见“号码模型”);
- UUID 发送者:仅携带
sourceUuid的消息会以uuid:<id>身份出现,白名单必须使用同一形式; - 表情失败:群内
react必须先提供targetAuthor/targetAuthorUuid,messageId必须是目标消息的 Signal 时间戳; - 媒体超限:附件超限时日志会明确提示超出多少 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助手。支持所有操作系统和平台。🦞
相关推荐
NanoClaw Signal 频道接入完全指南:基于 signal-cli 原生适配器的设备链接与接线实战
NanoClaw Signal 频道接入完全指南:基于 signal cli 原生适配器的设备链接与接线实战 导读 本文是 NanoClaw 仓库中 add s
人工智能AI 应用AI AgentAgent 沙箱交互助手OpenClaw Signal 通道实战:基于 signal-cli 的账号模型、三种传输模式与消息行为全解析
OpenClaw Signal 通道实战:基于 signal cli 的账号模型、三种传输模式与消息行为全解析 本文基于 OpenClaw 仓库中的 Signa
AI 应用AI Agent交互助手后端即时通讯网关MoviePilot AnySearch 技能实战指南:基于 JSON-RPC 的统一实时搜索 CLI 接入与运维
MoviePilot AnySearch 技能实战指南:基于 JSON RPC 的统一实时搜索 CLI 接入与运维 导读 本文以仓库 skills/anysea
后端AI AgentMCP 服务AI 技能
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考