You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Copy file name to clipboardExpand all lines: CHANGELOG.md
+1Lines changed: 1 addition & 0 deletions
Display the source diff
Display the rich diff
Original file line number
Diff line number
Diff line change
@@ -75,6 +75,7 @@ Docs: https://docs.openclaw.ai
75
75
- Codex/app-server: release the session lane when a downstream consumer throws while draining the `turn/completed` notification, so follow-up messages after a Codex plugin reply stop queueing behind a stale lane lock. Fixes #67996. (#69072) Thanks @ayeshakhalid192007-dev.
76
76
- Codex/app-server: default approval handling to `on-request` so Codex harness sessions do not start with overly permissive tool approvals. (#68721) Thanks @Lucenx9.
77
77
- Cron/delivery: keep isolated cron chat delivery tools available, resolve `channel: "last"` targets from the gateway, show delivery previews in `cron list/show`, and avoid duplicate fallback sends after direct message-tool delivery. (#69587) Thanks @obviyus.
78
+
- BlueBubbles: add opt-in `channels.bluebubbles.coalesceSameSenderDms` so a single composed message with text + pasted URL (which Apple splits into two webhooks ~0.8-2.0 s apart) arrives as one agent turn instead of two. When enabled, DM messages that are not linked via `associatedMessageGuid` hash to `dm:<chat>:<sender>` so the inbound debounce window merges them into a single merged turn — including URL-preview balloon events, DM control-command sends (which normally bypass debouncing), and rapid same-sender follow-ups. The default inbound debounce window widens from 500 ms to 2500 ms when the flag is set without an explicit `messages.inbound.byChannel.bluebubbles`, covering the observed Apple split-send cadence. Every source `messageId` folded into the merged view is committed to the inbound dedupe store after processing, so a later MessagePoller replay of any individual source event is recognized as a duplicate. Merged output is bounded (≤4000 chars text with an explicit `…[truncated]` marker, ≤20 attachments, first-plus-latest sampling beyond 10 source entries) so a rapid-fire flood inside the window cannot amplify the downstream prompt. Group chats and existing text+balloon follow-ups continue to key per-message. See [Coalescing split-send DMs](https://docs.openclaw.ai/channels/bluebubbles#coalescing-split-send-dms-command--url-in-one-composition) for scenarios, tuning, and troubleshooting. (#69258) Thanks @omarshahine.
78
79
- Cron/Telegram: key isolated direct-delivery dedupe to each cron execution instead of the reused session id, so recurring Telegram announce runs no longer report delivered while silently skipping later sends. (#69000) Thanks @obviyus.
79
80
- Models/Kimi: default bundled Kimi thinking to off and normalize Anthropic-compatible `thinking` payloads so stale session `/think` state no longer silently re-enables reasoning on Kimi runs. (#68907) Thanks @frankekn.
80
81
- Control UI/cron: keep the runtime-only `last` delivery sentinel from being materialized into persisted cron delivery and failure-alert channel configs when jobs are created or edited. (#68829) Thanks @tianhaocui.
Copy file name to clipboardExpand all lines: docs/channels/bluebubbles.md
+99Lines changed: 99 additions & 0 deletions
Display the source diff
Display the rich diff
Original file line number
Diff line number
Diff line change
@@ -393,6 +393,103 @@ Use full IDs for durable automations and storage:
393
393
394
394
See [Configuration](/gateway/configuration) for template variables.
395
395
396
+
## Coalescing split-send DMs (command + URL in one composition)
397
+
398
+
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**:
399
+
400
+
1. A text message (`"Dump"`).
401
+
2. A URL-preview balloon (`"https://..."`) with OG-preview images as attachments.
402
+
403
+
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.
404
+
405
+
`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.
406
+
407
+
### When to enable
408
+
409
+
Enable when:
410
+
411
+
- You ship skills that expect `command + payload` in one message (dump, paste, save, queue, etc.).
412
+
- Your users paste URLs, images, or long content alongside commands.
413
+
- You can accept the added DM turn latency (see below).
414
+
415
+
Leave disabled when:
416
+
417
+
- You need minimum command latency for single-word DM triggers.
418
+
- All your flows are one-shot commands without payload follow-ups.
419
+
420
+
### Enabling
421
+
422
+
```json5
423
+
{
424
+
channels: {
425
+
bluebubbles: {
426
+
coalesceSameSenderDms:true, // opt in (default: false)
427
+
},
428
+
},
429
+
}
430
+
```
431
+
432
+
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.
433
+
434
+
To tune the window yourself:
435
+
436
+
```json5
437
+
{
438
+
messages: {
439
+
inbound: {
440
+
byChannel: {
441
+
// 2500 ms works for most setups; raise to 4000 ms if your Mac is slow
442
+
// or under memory pressure (observed gap can stretch past 2 s then).
443
+
bluebubbles:2500,
444
+
},
445
+
},
446
+
},
447
+
}
448
+
```
449
+
450
+
### Trade-offs
451
+
452
+
-**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.
453
+
-**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.
454
+
-**Opt-in, per-channel.** Other channels (Telegram, WhatsApp, Slack, …) are unaffected.
455
+
456
+
### Scenarios and what the agent sees
457
+
458
+
| 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`|
461
+
|`Save this 📎image.jpg caption` (attachment + text) | 2 webhooks | Two turns | One turn: text + image |
462
+
|`/status` (standalone command) | 1 webhook | Instant dispatch |**Wait up to window, then dispatch**|
463
+
| URL pasted alone | 1 webhook | Instant dispatch | Instant dispatch (only one entry in bucket) |
464
+
| Text + URL sent as two deliberate separate messages, minutes apart | 2 webhooks outside window | Two turns | Two turns (window expires between them) |
465
+
| Rapid flood (>10 small DMs inside window) | N webhooks | N turns | One turn, bounded output (first + latest, text/attachment caps applied) |
466
+
467
+
### Split-send coalescing troubleshooting
468
+
469
+
If the flag is on and split-sends still arrive as two turns, check each layer:
Then `openclaw gateway restart` — the flag is read at debouncer-registry creation.
478
+
479
+
2.**Debounce window wide enough for your setup.** Look at the BlueBubbles server log under `~/Library/Logs/bluebubbles-server/main.log`:
480
+
481
+
```
482
+
grep -E "Dispatching event to webhook" main.log | tail -20
483
+
```
484
+
485
+
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.
486
+
487
+
3.**Session JSONL timestamps ≠ webhook arrival.** Session event timestamps (`~/.openclaw/agents/<id>/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.
488
+
489
+
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.
490
+
491
+
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.
492
+
396
493
## Block streaming
397
494
398
495
Control whether responses are sent as a single message or streamed in blocks:
@@ -436,6 +533,7 @@ Provider options:
436
533
-`channels.bluebubbles.chunkMode`: `length` (default) splits only when exceeding `textChunkLimit`; `newline` splits on blank lines (paragraph boundaries) before length chunking.
437
534
-`channels.bluebubbles.mediaMaxMb`: Inbound/outbound media cap in MB (default: 8).
438
535
-`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.<accountId>.mediaLocalRoots`.
536
+
-`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`.
439
537
-`channels.bluebubbles.historyLimit`: Max group messages for context (0 disables).
440
538
-`channels.bluebubbles.dmHistoryLimit`: DM history limit.
441
539
-`channels.bluebubbles.actions`: Enable/disable specific actions.
@@ -471,6 +569,7 @@ Prefer `chat_guid` for stable routing:
471
569
- Edit/unsend require macOS 13+ and a compatible BlueBubbles server version. On macOS 26 (Tahoe), edit is currently broken due to private API changes.
472
570
- Group icon updates can be flaky on macOS 26 (Tahoe): the API may return success but the new icon does not sync.
473
571
- 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`.
572
+
-`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).
474
573
- For status/health info: `openclaw status --all` or `openclaw status --deep`.
475
574
476
575
For general channel workflow reference, see [Channels](/channels) and the [Plugins](/tools/plugin) guide.
- Debounce applies to **text-only** messages; media/attachments flush immediately.
65
-
- Control commands bypass debouncing so they remain standalone.
65
+
- 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.
0 commit comments