diff --git a/.openclaw-sync/source.json b/.openclaw-sync/source.json index 18201890e..90c5cba0f 100644 --- a/.openclaw-sync/source.json +++ b/.openclaw-sync/source.json @@ -1,5 +1,5 @@ { "repository": "openclaw/openclaw", - "sha": "f1805ab54d63126e8f83b0837d4f20df2e41f3a0", - "syncedAt": "2026-04-21T08:23:24.609Z" + "sha": "14506aeca4be0b4cc77448615d76797e679587f7", + "syncedAt": "2026-04-21T08:44:03.410Z" } diff --git a/docs/.generated/config-baseline.sha256 b/docs/.generated/config-baseline.sha256 index b04602786..a001cee28 100644 --- a/docs/.generated/config-baseline.sha256 +++ b/docs/.generated/config-baseline.sha256 @@ -1,4 +1,4 @@ -e3a16ceb9e933c5b707b717c18a1d9d50f98e687a98e6c35f4f3a290f7036a62 config-baseline.json -ae1ab87635e7bf613c84fee04425af901ceeb67fb5dbcf1c74095aa00a59ee88 config-baseline.core.json -e239cc20f20f8d0172812bc0ad3ee6df52da88e2e2702e3d03a47e01561132ae config-baseline.channel.json -8fb3a1cf5fe56ab8fc2cb46341c3403aed32b0d1f0aaeac0e96cd3599db4f06e config-baseline.plugin.json +cc473bcd00e63c3d3f351e4de1ceb390aae88dddce8616929e98a9d94412b1b9 config-baseline.json +7956c319e82d288d496a51cb2ff4485ab72ef4900cb089f99e1df8b9ef3bfb73 config-baseline.core.json +cd467228990cdbdebde2fa87d8b1384b94c149e791f2e67250bf17b13162d4a1 config-baseline.channel.json +17a73724e5082b3aa846c220d38115916fb6003887439e6794510a99fc73f7de config-baseline.plugin.json diff --git a/docs/channels/bluebubbles.md b/docs/channels/bluebubbles.md index fc69c542c..e347b9b00 100644 --- a/docs/channels/bluebubbles.md +++ b/docs/channels/bluebubbles.md @@ -393,6 +393,103 @@ Use full IDs for durable automations and storage: See [Configuration](/gateway/configuration) for template variables. +## Coalescing split-send DMs (command + URL in one composition) + +When a user types a command and a URL together in iMessage — e.g. `Dump https://example.com/article` — Apple splits the send into **two separate webhook deliveries**: + +1. A text message (`"Dump"`). +2. A URL-preview balloon (`"https://..."`) with OG-preview images as attachments. + +The two webhooks arrive at OpenClaw ~0.8-2.0 s apart on most setups. Without coalescing, the agent receives the command alone on turn 1, replies (often "send me the URL"), and only sees the URL on turn 2 — at which point the command context is already lost. + +`channels.bluebubbles.coalesceSameSenderDms` opts a DM into merging consecutive same-sender webhooks into a single agent turn. Group chats continue to key per-message so multi-user turn structure is preserved. + +### When to enable + +Enable when: + +- You ship skills that expect `command + payload` in one message (dump, paste, save, queue, etc.). +- Your users paste URLs, images, or long content alongside commands. +- You can accept the added DM turn latency (see below). + +Leave disabled when: + +- You need minimum command latency for single-word DM triggers. +- All your flows are one-shot commands without payload follow-ups. + +### Enabling + +```json5 +{ + channels: { + bluebubbles: { + coalesceSameSenderDms: true, // opt in (default: false) + }, + }, +} +``` + +With the flag on and no explicit `messages.inbound.byChannel.bluebubbles`, the debounce window widens to **2500 ms** (the default for non-coalescing is 500 ms). The wider window is required — Apple's split-send cadence of 0.8-2.0 s does not fit in the tighter default. + +To tune the window yourself: + +```json5 +{ + messages: { + inbound: { + byChannel: { + // 2500 ms works for most setups; raise to 4000 ms if your Mac is slow + // or under memory pressure (observed gap can stretch past 2 s then). + bluebubbles: 2500, + }, + }, + }, +} +``` + +### Trade-offs + +- **Added latency for DM control commands.** With the flag on, DM control-command messages (like `Dump`, `Save`, etc.) now wait up to the debounce window before dispatching, in case a payload webhook is coming. Group-chat commands keep instant dispatch. +- **Merged output is bounded** — merged text caps at 4000 chars with an explicit `…[truncated]` marker; attachments cap at 20; source entries cap at 10 (first-plus-latest retained beyond that). Every source `messageId` still reaches inbound-dedupe so a later MessagePoller replay of any individual event is recognized as a duplicate. +- **Opt-in, per-channel.** Other channels (Telegram, WhatsApp, Slack, …) are unaffected. + +### Scenarios and what the agent sees + +| User composes | Apple delivers | Flag off (default) | Flag on + 2500 ms window | +| ------------------------------------------------------------------ | ------------------------- | --------------------------------------- | ----------------------------------------------------------------------- | +| `Dump https://example.com` (one send) | 2 webhooks ~1 s apart | Two agent turns: "Dump" alone, then URL | One turn: merged text `Dump https://example.com` | +| `Save this 📎image.jpg caption` (attachment + text) | 2 webhooks | Two turns | One turn: text + image | +| `/status` (standalone command) | 1 webhook | Instant dispatch | **Wait up to window, then dispatch** | +| URL pasted alone | 1 webhook | Instant dispatch | Instant dispatch (only one entry in bucket) | +| Text + URL sent as two deliberate separate messages, minutes apart | 2 webhooks outside window | Two turns | Two turns (window expires between them) | +| Rapid flood (>10 small DMs inside window) | N webhooks | N turns | One turn, bounded output (first + latest, text/attachment caps applied) | + +### Split-send coalescing troubleshooting + +If the flag is on and split-sends still arrive as two turns, check each layer: + +1. **Config actually loaded.** + + ``` + grep coalesceSameSenderDms ~/.openclaw/openclaw.json + ``` + + Then `openclaw gateway restart` — the flag is read at debouncer-registry creation. + +2. **Debounce window wide enough for your setup.** Look at the BlueBubbles server log under `~/Library/Logs/bluebubbles-server/main.log`: + + ``` + grep -E "Dispatching event to webhook" main.log | tail -20 + ``` + + Measure the gap between the `"Dump"`-style text dispatch and the `"https://..."; Attachments:` dispatch that follows. Raise `messages.inbound.byChannel.bluebubbles` to comfortably cover that gap. + +3. **Session JSONL timestamps ≠ webhook arrival.** Session event timestamps (`~/.openclaw/agents//sessions/*.jsonl`) reflect when the gateway hands a message to the agent, **not** when the webhook arrived. A queued-second message tagged `[Queued messages while agent was busy]` means the first turn was still running when the second webhook arrived — the coalesce bucket had already flushed. Tune the window against the BB server log, not the session log. + +4. **Memory pressure slowing reply dispatch.** On smaller machines (8 GB), agent turns can take long enough that the coalesce bucket flushes before the reply completes, and the URL lands as a queued second turn. Check `memory_pressure` and `ps -o rss -p $(pgrep openclaw-gateway)`; if the gateway is over ~500 MB RSS and the compressor is active, close other heavy processes or bump to a larger host. + +5. **Reply-quote sends are a different path.** If the user tapped `Dump` as a **reply** to an existing URL-balloon (iMessage shows a "1 Reply" badge on the Dump bubble), the URL lives in `replyToBody`, not in a second webhook. Coalescing does not apply — that's a skill/prompt concern, not a debouncer concern. + ## Block streaming Control whether responses are sent as a single message or streamed in blocks: @@ -436,6 +533,7 @@ Provider options: - `channels.bluebubbles.chunkMode`: `length` (default) splits only when exceeding `textChunkLimit`; `newline` splits on blank lines (paragraph boundaries) before length chunking. - `channels.bluebubbles.mediaMaxMb`: Inbound/outbound media cap in MB (default: 8). - `channels.bluebubbles.mediaLocalRoots`: Explicit allowlist of absolute local directories permitted for outbound local media paths. Local path sends are denied by default unless this is configured. Per-account override: `channels.bluebubbles.accounts..mediaLocalRoots`. +- `channels.bluebubbles.coalesceSameSenderDms`: Merge consecutive same-sender DM webhooks into one agent turn so Apple's text+URL split-send arrives as a single message (default: `false`). See [Coalescing split-send DMs](#coalescing-split-send-dms-command--url-in-one-composition) for scenarios, window tuning, and trade-offs. Widens the default inbound debounce window from 500 ms to 2500 ms when enabled without an explicit `messages.inbound.byChannel.bluebubbles`. - `channels.bluebubbles.historyLimit`: Max group messages for context (0 disables). - `channels.bluebubbles.dmHistoryLimit`: DM history limit. - `channels.bluebubbles.actions`: Enable/disable specific actions. @@ -471,6 +569,7 @@ Prefer `chat_guid` for stable routing: - Edit/unsend require macOS 13+ and a compatible BlueBubbles server version. On macOS 26 (Tahoe), edit is currently broken due to private API changes. - Group icon updates can be flaky on macOS 26 (Tahoe): the API may return success but the new icon does not sync. - OpenClaw auto-hides known-broken actions based on the BlueBubbles server's macOS version. If edit still appears on macOS 26 (Tahoe), disable it manually with `channels.bluebubbles.actions.edit=false`. +- `coalesceSameSenderDms` enabled but split-sends (e.g. `Dump` + URL) still arrive as two turns: see the [split-send coalescing troubleshooting](#split-send-coalescing-troubleshooting) checklist — common causes are too-tight debounce window, session-log timestamps misread as webhook arrival, or a reply-quote send (which uses `replyToBody`, not a second webhook). - For status/health info: `openclaw status --all` or `openclaw status --deep`. For general channel workflow reference, see [Channels](/channels) and the [Plugins](/tools/plugin) guide. diff --git a/docs/concepts/messages.md b/docs/concepts/messages.md index 4e9240c0c..6490faf51 100644 --- a/docs/concepts/messages.md +++ b/docs/concepts/messages.md @@ -62,7 +62,7 @@ Config (global default + per-channel overrides): Notes: - Debounce applies to **text-only** messages; media/attachments flush immediately. -- Control commands bypass debouncing so they remain standalone. +- Control commands bypass debouncing so they remain standalone — **except** when a channel explicitly opts in to same-sender DM coalescing (e.g. [BlueBubbles `coalesceSameSenderDms`](/channels/bluebubbles#coalescing-split-send-dms-command--url-in-one-composition)), where DM commands wait inside the debounce window so a split-send payload can join the same agent turn. ## Sessions and devices