跳转到内容

BlueBubbles

状态:内置插件,通过 HTTP 与 BlueBubbles macOS 服务器通信。由于其拥有更丰富的 API 且相比旧版 imsg 渠道 设置更简单,推荐用于 iMessage 集成

  • 通过 BlueBubbles 助手应用在 macOS 上运行(bluebubbles.app)。
  • 推荐/测试环境:macOS Sequoia (15)。macOS Tahoe (26) 可用;但在 Tahoe 上编辑功能目前损坏,群组图标更新可能报告成功但不会同步。
  • OpenClaw 通过其 REST API (GET /api/v1/ping, POST /message/text, POST /chat/:id/*) 与其通信。
  • 传入消息通过 webhooks 到达;传出回复、正在输入指示器、已读回执和点回(tapbacks)均为 REST 调用。
  • 附件和贴纸作为入站媒体被摄取(并在可能时呈现给代理)。
  • 合成 MP3 或 CAF 音频的自动 TTS 回复将作为 iMessage 语音备忘录气泡传送,而不是普通文件附件。
  • 配对/允许列表(allowlist)的工作方式与其他通道(/channels/pairing 等)相同,使用 channels.bluebubbles.allowFrom + 配对码。
  • 反应会像 Slack/Telegram 一样作为系统事件呈现,因此座席可以在回复前“提及”它们。
  • 高级功能:编辑、撤销、回复线程、消息效果、群组管理。
  1. BlueBubbles安装 BlueBubbles

    在您的 Mac 上安装 BlueBubbles 服务器(遵循 bluebubbles.app/install 上的说明)。

  2. 启用 web API

    在 BlueBubbles 配置中,启用 web API 并设置密码。

  3. 配置 OpenClaw

    运行 openclaw onboard 并选择 BlueBubbles,或手动配置:

    {
    channels: {
    bluebubbles: {
    enabled: true,
    serverUrl: "http://192.168.1.100:1234",
    password: "example-password",
    webhookPath: "/bluebubbles-webhook",
    },
    },
    }
  4. 将 webhooks 指向网关

    将 BlueBubbles webhooks 指向您的网关(例如:`https://your-gateway-host:3000/bluebubbles-webhook?password=

    `)。

  5. 启动网关

    启动网关;它将注册 webhook 处理程序并开始配对。

保持 Messages.app 活跃(虚拟机 / 无头设置)

Section titled “保持 Messages.app 活跃(虚拟机 / 无头设置)”

某些 macOS 虚拟机 / 始终开启设置可能会导致 Messages.app 进入“空闲”状态(传入事件停止,直到应用被打开/置于前台)。一个简单的解决方法是使用 AppleScript + LaunchAgent 每 5 分钟唤醒一次 Messages

  1. 保存 AppleScript

    将此保存为 ~/Scripts/poke-messages.scpt

    try
    tell application "Messages"
    if not running then
    launch
    end if
    -- Touch the scripting interface to keep the process responsive.
    set _chatCount to (count of chats)
    end tell
    on error
    -- Ignore transient failures (first-run prompts, locked session, etc).
    end try
  2. 安装 LaunchAgent

    将此保存为 ~/Library/LaunchAgents/com.user.poke-messages.plist

    Label

    com.user.poke-messages

    ProgramArguments

    /bin/bash

    -lc

    /usr/bin/osascript “$HOME/Scripts/poke-messages.scpt”

    RunAtLoad

    StartInterval

    300

    StandardOutPath

    /tmp/poke-messages.log

    StandardErrorPath

    /tmp/poke-messages.err

    这将 **每 300 秒运行一次** 并 **在登录时运行**。首次运行可能会触发 macOS **自动化** 提示(`osascript` → Messages)。请在运行 LaunchAgent 的同一用户会话中批准它们。
  3. 加载它

    Terminal window
    launchctl unload ~/Library/LaunchAgents/com.user.poke-messages.plist 2>/dev/null || true
    launchctl load ~/Library/LaunchAgents/com.user.poke-messages.plist

BlueBubbles 可在交互式新手引导中使用:

openclaw onboard

向导将提示输入:

BlueBubbles 服务器地址(例如 `http://192.168.1.100:1234`)。 来自 API 服务器设置的 BlueBubbles 密码。 Webhook 端点路径。 `pairing`、`allowlist`、`open` 或 `disabled`。 电话号码、电子邮件或聊天目标。

您也可以通过 BlueBubbles 添加 CLI:

openclaw channels add bluebubbles --http-url http://192.168.1.100:1234 --password <password>
  • 默认值:channels.bluebubbles.dmPolicy = "pairing"
  • 未知发送者会收到配对码;在批准之前消息将被忽略(代码 1 小时后过期)。
  • 通过以下方式批准:
    • openclaw pairing list bluebubbles
    • `openclaw pairing approve bluebubbles

` - 配对是默认的令牌交换。详情:配对

BlueBubbles 群组 webhook 通常只包含原始参与者地址。如果您希望 GroupMembers 上下文显示本地联系人姓名,可以选择在 macOS 上启用本地联系人充实功能:

  • channels.bluebubbles.enrichGroupParticipantsFromContacts = true 启用查找。默认:false
  • 查找仅在群组访问权限、命令授权和提及限制均允许消息通过后运行。
  • 仅未命名的电话参与者会被充实。
  • 当未找到本地匹配项时,原始电话号码将作为备用选项保留。
{
channels: {
bluebubbles: {
enrichGroupParticipantsFromContacts: true,
},
},
}

BlueBubbles 支持群聊的提及限制,匹配 iMessage/WhatsApp 的行为:

  • 使用 agents.list[].groupChat.mentionPatterns(或 messages.groupChat.mentionPatterns)来检测提及。
  • 当为群组启用 requireMention 时,代理仅在收到提及时才会响应。
  • 来自授权发送者的控制命令会绕过提及限制。

按群组配置:

{
channels: {
bluebubbles: {
groupPolicy: "allowlist",
groupAllowFrom: ["+15555550123"],
groups: {
"*": { requireMention: true }, // default for all groups
"iMessage;-;chat123": { requireMention: false }, // override for specific group
},
},
},
}
  • 控制命令(例如 /config/model)需要授权。
  • 使用 allowFromgroupAllowFrom 来确定命令授权。
  • 授权发送者即使未在群组中提及,也可以运行控制命令。

channels.bluebubbles.groups.* 下的每个条目接受一个可选的 systemPrompt 字符串。该值会在处理该群组消息的每一轮对话中注入到代理的系统提示词中,因此您可以设置每个群组的人设或行为规则,而无需编辑代理提示词:

{
channels: {
bluebubbles: {
groups: {
"iMessage;-;chat123": {
systemPrompt: "Keep responses under 3 sentences. Mirror the group's casual tone.",
},
},
},
},
}

键匹配 BlueBubbles 为该群组报告的 chatGuid / chatIdentifier / 数字 chatId,而 "*" 通配符条目为每个没有精确匹配的群组提供默认值(与 requireMention 和按群组工具策略使用的模式相同)。精确匹配始终优先于通配符。私信会忽略此字段;请改用代理级或账户级的提示词自定义。

实战示例:串联回复和点按反应(私有 API)

Section titled “实战示例:串联回复和点按反应(私有 API)”

启用 BlueBubbles 私有 API 后,入站消息会带有短消息 ID(例如 [[reply_to:5]]),代理可以调用 action=reply 以串联回复特定消息,或调用 action=react 以发送点按反应。按群组 systemPrompt 是确保代理选择正确工具的可靠方法:

{
channels: {
bluebubbles: {
groups: {
"iMessage;+;chat-family": {
systemPrompt: "When replying in this group, always call action=reply with the [[reply_to:N]] messageId from context so your response threads under the triggering message. Never send a new unlinked message. For short acknowledgements ('ok', 'got it', 'on it'), use action=react with an appropriate tapback emoji (❤️, 👍, 😂, ‼️, ❓) instead of sending a text reply.",
},
},
},
},
}

Tapback 反应和线程回复都需要 BlueBubbles 私有 API;有关底层机制,请参阅 高级操作消息 ID

BlueBubbles 聊天可以转换为持久的 ACP 工作区,而无需更改传输层。

快速操作流程:

  • 在私信或允许的群聊中运行 /acp spawn codex --bind here
  • 同一 BlueBubbles 对话中的后续消息将路由到生成的 ACP 会话。
  • /new/reset 会原地重置同一绑定的 ACP 会话。
  • /acp close 会关闭 ACP 会话并移除绑定。

还支持通过带有 type: "acp"match.channel: "bluebubbles" 的顶级 bindings[] 条目来配置持久绑定。

match.peer.id 可以使用任何支持的 BlueBubbles 目标格式:

  • 标准化的私信句柄,例如 +15555550123[email protected]
  • chat_id:<id>
  • chat_guid:<guid>
  • chat_identifier:<identifier>

对于稳定的群组绑定,建议优先使用 chat_id:*chat_identifier:*

示例:

{
agents: {
list: [
{
id: "codex",
runtime: {
type: "acp",
acp: { agent: "codex", backend: "acpx", mode: "persistent" },
},
},
],
},
bindings: [
{
type: "acp",
agentId: "codex",
match: {
channel: "bluebubbles",
accountId: "default",
peer: { kind: "dm", id: "+15555550123" },
},
acp: { label: "codex-imessage" },
},
],
}

有关共享 ACP 绑定行为,请参阅 ACP 代理

  • 正在输入指示器:在生成响应之前和生成期间自动发送。
  • 已读回执:由 channels.bluebubbles.sendReadReceipts 控制(默认值:true)。
  • 正在输入指示器:OpenClaw 发送正在输入开始事件;BlueBubbles 会在发送或超时时自动清除正在输入状态(通过 DELETE 手动停止不可靠)。
{
channels: {
bluebubbles: {
sendReadReceipts: false, // disable read receipts
},
},
}

如果在配置中启用,BlueBubbles 支持高级消息操作:

{
channels: {
bluebubbles: {
actions: {
reactions: true, // tapbacks (default: true)
edit: true, // edit sent messages (macOS 13+, broken on macOS 26 Tahoe)
unsend: true, // unsend messages (macOS 13+)
reply: true, // reply threading by message GUID
sendWithEffect: true, // message effects (slam, loud, etc.)
renameGroup: true, // rename group chats
setGroupIcon: true, // set group chat icon/photo (flaky on macOS 26 Tahoe)
addParticipant: true, // add participants to groups
removeParticipant: true, // remove participants from groups
leaveGroup: true, // leave group chats
sendAttachment: true, // send attachments/media
},
},
},
}
可用操作
  • react:添加/删除点按反应(messageIdemojiremove)。iMessage 的原生点按集合为 lovelikedislikelaughemphasizequestion。当代理选择该集合之外的表情符号(例如 👀)时,反应工具将回退到 love,以便点按仍然能够渲染,而不会导致整个请求失败。配置的确认反应仍会进行严格验证,并在遇到未知值时报错。
  • edit:编辑已发送的消息(messageIdtext)。
  • unsend:撤销发送一条消息(messageId)。
  • reply:回复特定消息(messageIdtextto)。
  • sendWithEffect:带有 iMessage 特效发送(texttoeffectId)。
  • renameGroup:重命名群组聊天(chatGuiddisplayName)。
  • setGroupIcon:设置群组聊天的图标/照片(chatGuidmedia)——在 macOS 26 Tahoe 上不稳定(API 可能返回成功,但图标不会同步)。
  • addParticipant:将某人添加到群组(chatGuidaddress)。
  • removeParticipant:从群组中移除某人(chatGuidaddress)。
  • leaveGroup:退出群组聊天(chatGuid)。
  • upload-file:发送媒体/文件(tobufferfilenameasVoice)。
    • 语音备忘录:将 asVoice: true 设置为 MP3CAF 音频,以作为 iMessage 语音消息发送。发送语音备忘录时,BlueBubbles 会将 MP3 转换为 CAF。
  • 旧版别名:sendAttachment 仍然有效,但 upload-file 是规范的操作名称。

OpenClaw 可能会显示消息 ID(例如,12)以节省 token。

  • MessageSid / ReplyToId 可以是短 ID。
  • MessageSidFull / ReplyToIdFull 包含提供商的完整 ID。
  • 短 ID 存储在内存中;在重启或缓存驱逐后它们可能会过期。
  • 操作接受短 ID 或完整 messageId,但如果短 ID 不再可用则会报错。

对于持久的自动化和存储,请使用完整 ID:

  • 模板:{{MessageSidFull}}{{ReplyToIdFull}}
  • 上下文:入站负载中的 MessageSidFull / ReplyToIdFull

有关模板变量,请参阅配置

合并拆分发送的私信(一条消息中包含命令 + URL)

Section titled “合并拆分发送的私信(一条消息中包含命令 + URL)”

当用户在 iMessage 中同时输入命令和 URL 时——例如 Dump https://example.com/article —— Apple 会将发送拆分为两次单独的 webhook 投递

  1. 一条文本消息("Dump")。
  2. 一个带有 OG 预览图像作为附件的 URL 预览气泡("https://...")。

在大多数设置中,这两个 webhook 到达 OpenClaw 的时间相隔约 0.8-2.0 秒。如果不合并,代理在第 1 轮仅收到命令,并回复(通常是“发送 URL 给我”),而在第 2 轮才看到 URL —— 此时命令上下文已丢失。

channels.bluebubbles.coalesceSameSenderDms 选择将私信中的连续相同发送者 webhook 合并为单个代理轮次。群聊继续按消息键入,以保留多用户轮次结构。

在以下情况下启用:

  • 您构建的技能期望在一个消息中包含 command + payload(转储、粘贴、保存、队列等)。
  • 您的用户将 URL、图像或长内容与命令一起粘贴。
  • 您可以接受增加的私信轮次延迟(见下文)。

在以下情况下保持禁用:

  • 您需要单字私信触发器的最小命令延迟。
  • 您的所有流程都是没有后续负载的一次性命令。
用户组合Apple 传送标志关闭(默认)标志开启 + 2500 ms 窗口
Dump https://example.com(一次发送)2 个 webhook,间隔约 1 秒两次代理轮次:先是“Dump”,然后是 URL一次轮次:合并文本 Dump https://example.com
Save this 📎image.jpg caption(附件 + 文本)2 个 webhook两次轮次一次轮次:文本 + 图片
/status(独立命令)1 个 webhook即时发送等待至窗口结束,然后发送
单独粘贴 URL1 个 webhook即时发送即时发送(桶中仅有一个条目)
文本 + URL 作为两条特意分开的消息发送,相隔数分钟2 个 webhook,位于窗口之外两次轮次两次轮次(窗口在它们之间过期)
快速刷屏(窗口内有 >10 条小私信)N 个 webhookN 次轮次一次轮次,输出受限(首个 + 最新,应用文本/附件上限)

如果标志已开启,但分批发送仍然作为两次轮次到达,请检查每一层:

配置实际已加载
grep coalesceSameSenderDms ~/.openclaw/openclaw.json

然后 openclaw gateway restart —— 该标志在创建防抖注册表时被读取。

防抖窗口足够适应您的设置

查看 BlueBubbles 服务器日志下的 ~/Library/Logs/bluebubbles-server/main.log

grep -E "Dispatching event to webhook" main.log | tail -20

测量 "Dump" 风格文本发送与随后的 "https://..."; Attachments: 发送之间的间隔。提高 messages.inbound.byChannel.bluebubbles 以充分覆盖该间隔。

会话 JSONL 时间戳 ≠ Webhook 到达时间

会话事件时间戳 (`~/.openclaw/agents/

/sessions/*.jsonl) 反映的是网关将消息传递给代理的时间,**而不是** webhook 到达的时间。标记为 [Queued messages while agent was busy]` 的排队第二条消息意味着当第二个 webhook 到达时,第一轮对话仍在运行 —— 合并桶已经刷新了。请根据 BB 服务器日志而非会话日志来调整窗口。

内存压力导致回复发送变慢

在较小的机器(8 GB)上,代理轮次耗时可能较长,导致合并桶在回复完成前就已刷新,URL 作为排队的第二轮对话落地。检查 memory_pressureps -o rss -p $(pgrep openclaw-gateway);如果网关 RSS 超过 ~500 MB 且压缩器处于活动状态,请关闭其他繁重进程或升级到更大的主机。

回复引用发送是不同的路径

如果用户点击 Dump 作为对现有 URL 气泡的 回复(iMessage 会在 Dump 气泡上显示“1 条回复”徽章),URL 位于 replyToBody 中,而不是在第二个 webhook 中。合并不适用 —— 这是技能/提示词的问题,而不是防抖器的问题。

控制响应是作为单条消息发送还是以块的形式流式传输:

{
channels: {
bluebubbles: {
blockStreaming: true, // enable block streaming (off by default)
},
},
}
  • 入站附件会被下载并存储在媒体缓存中。
  • 通过 channels.bluebubbles.mediaMaxMb 对入站和出站媒体进行容量限制(默认:8 MB)。
  • 出站文本会被分块为 channels.bluebubbles.textChunkLimit(默认:4000 个字符)。

完整配置:配置

连接和 Webhook
  • channels.bluebubbles.enabled:启用/禁用该渠道。
  • channels.bluebubbles.serverUrl:BlueBubbles REST API 基础 URL。
  • channels.bluebubbles.password:API 密码。
  • channels.bluebubbles.webhookPath:Webhook 端点路径(默认:/bluebubbles-webhook)。
访问策略
  • channels.bluebubbles.dmPolicypairing | allowlist | open | disabled(默认: pairing)。
  • channels.bluebubbles.allowFrom: 私信白名单(handles、emails、E.164 号码、chat_id:*chat_guid:*)。
  • channels.bluebubbles.groupPolicyopen | allowlist | disabled(默认: allowlist)。
  • channels.bluebubbles.groupAllowFrom: 群组发送者白名单。
  • channels.bluebubbles.enrichGroupParticipantsFromContactsmacOS: 在 macOS 上,可选地在通过访问控制后,从本地联系人中丰富未命名的群组参与者。默认: false
  • channels.bluebubbles.groups: 每个群组的配置(requireMention 等)。
传递和分块
  • channels.bluebubbles.sendReadReceipts: 发送已读回执(默认: true)。
  • channels.bluebubbles.blockStreaming: 启用分块流式传输(默认: false;流式回复所必需)。
  • channels.bluebubbles.textChunkLimit: 出站分块大小(字符)(默认: 4000)。
  • channels.bluebubbles.sendTimeoutMs: 通过 /api/v1/message/textmacOSAPIiMessageiMessage 发送出站文本的每个请求超时时间(毫秒)(默认: 30000)。在 macOS 26 环境中提高此值,因为私有 API iMessage 发送可能会在 iMessage 框架内停顿 60 秒以上;例如 4500060000。探测、聊天查找、反应、编辑和健康检查目前保持较短的 10 秒默认值;计划在后续步骤中将覆盖范围扩大到反应和编辑。按账户覆盖: `channels.bluebubbles.accounts.

.sendTimeoutMs。 - channels.bluebubbles.chunkModelength(默认)仅在超过 textChunkLimit时拆分;newline` 在按长度分块之前按空行(段落边界)拆分。

媒体与历史
  • channels.bluebubbles.mediaMaxMb:入站/出站媒体上限,单位 MB(默认:8)。
  • channels.bluebubbles.mediaLocalRoots:允许用于出站本地媒体路径的绝对本地目录显式允许列表。除非配置此项,否则默认禁止发送本地路径。按账户覆盖:`channels.bluebubbles.accounts.

.mediaLocalRoots。 - channels.bluebubbles.coalesceSameSenderDms:将连续的同一发送者私信 Webhook 合并为一个 agent 轮次,以便 Apple 的文本+URL 分批发送作为单条消息到达(默认:false)。请参阅[合并分批发送的私信](#coalescing-split-send-dms-command--url-in-one-composition)了解场景、窗口调整和权衡。如果在没有显式 messages.inbound.byChannel.bluebubbles的情况下启用此功能,会将默认入站防抖窗口从 500 毫秒扩大到 2500 毫秒。 -channels.bluebubbles.historyLimit:用于上下文的最大群组消息数(0 表示禁用)。 - channels.bluebubbles.dmHistoryLimit:私信历史记录限制。 - channels.bluebubbles.replyContextApiFallback:当入站回复到达时缺少 replyToBody/replyToSender 且内存中的回复上下文缓存未命中时,从 BlueBubbles HTTP API 获取原始消息作为尽力而为的回退(默认:false)。这对于共享一个 BlueBubbles 账户的多实例部署、进程重启后或长期 TTL/LRU 缓存驱逐后非常有用。该获取操作受与其他每个 BlueBubbles 客户端请求相同的策略保护以防止 SSRF,永不抛出异常,并填充缓存以便后续回复摊销成本。按账户覆盖:channels.bluebubbles.accounts.

.replyContextApiFallback`。渠道级别的设置会传播到省略该标志的账户。

操作与账户
  • channels.bluebubbles.actions:启用/禁用特定操作。
  • channels.bluebubbles.accounts:多账户配置。

相关的全局选项:

  • agents.list[].groupChat.mentionPatterns(或 messages.groupChat.mentionPatterns)。
  • messages.responsePrefix

为稳定路由首选 chat_guid

  • chat_guid:iMessage;-;+15555550123 (群组首选)
  • chat_id:123
  • chat_identifier:...
  • 直接句柄:+15555550123[email protected]
    • 如果直接句柄没有现有的私聊聊天,OpenClaw 将通过 POST /api/v1/chat/new 创建一个。这需要启用 BlueBubbles 私有 API。

当同一句柄在 Mac 上同时具有 iMessage 和 SMS 聊天时(例如一个已注册 iMessage 但也收到过绿色气泡回退短信的电话号码),OpenClaw 首选 iMessage 聊天,且绝不会静默降级为 SMS。要强制使用 SMS 聊天,请使用显式的 sms: 目标前缀(例如 sms:+15555550123)。没有匹配 iMessage 聊天的句柄仍将通过 BlueBubbles 报告的任何聊天发送。

  • Webhook 请求通过将 guid/password 查询参数或标头与 channels.bluebubbles.password 进行比较来进行身份验证。
  • 请保持 API 密码和 webhook 端点的机密性(将它们视为凭据)。
  • BlueBubbles Webhook 身份验证没有本地主机绕过机制。如果您代理 Webhook 流量,请确保请求端到端保留 BlueBubbles 密码。gateway.trustedProxies 不会在此处替换 channels.bluebubbles.password。参见 Gateway(网关) 安全
  • 如果在局域网外暴露 BlueBubbles 服务器,请在其上启用 HTTPS 和防火墙规则。
  • 如果输入/已读事件停止工作,请检查 BlueBubbles Webhook 日志并验证网关路径是否匹配 channels.bluebubbles.webhookPath
  • 配对码在一小时后过期;请使用 openclaw pairing list bluebubblesopenclaw pairing approve bluebubbles <code>
  • 反应需要 BlueBubbles 私有 API (POST /api/v1/message/react);请确保服务器版本已将其公开。
  • 编辑/取消发送需要 macOS 13+ 和兼容的 BlueBubbles 服务器版本。在 macOS 26 (Tahoe) 上,由于私有 API 变更,编辑功能目前无法使用。
  • 在 macOS 26 (Tahoe) 上,群组图标更新可能不稳定:API 可能返回成功,但新图标不会同步。
  • OpenClaw 会根据 BlueBubbles 服务器的 macOS 版本自动隐藏已知损坏的操作。如果在 macOS 26 (Tahoe) 上仍然显示编辑功能,请使用 OpenClawBlueBubblesmacOSmacOSchannels.bluebubbles.actions.edit=false 手动禁用它。
  • 已启用 coalesceSameSenderDms 但拆分发送(例如 Dump + URL)仍然作为两个回合到达:请参阅拆分发送合并故障排除检查清单 —— 常见原因是防抖窗口过紧、会话日志时间戳被误读为 Webhook 到达时间,或者是回复引用发送(它使用 replyToBody,而不是第二个 Webhook)。
  • 有关状态/运行状况信息:openclaw status --allopenclaw status --deep

有关一般渠道工作流参考,请参阅渠道插件指南。