openclaw/extensions/bluebubbles/src
Omar Shahine 77d9fd693f
fix(bluebubbles): restore inbound image attachments and accept updated-message events (#67510)
* fix(bluebubbles): restore inbound image attachments and accept updated-message events

Four interconnected fixes for BlueBubbles inbound media:

1. Strip bundled-undici dispatcher from non-SSRF fetch path so attachment
   downloads no longer silently fail on Node 22+ (#64105, #61861)

2. Accept updated-message webhook events that carry attachments instead of
   filtering them as non-reaction events (#65430)

3. Include eventType in the persistent GUID dedup key so updated-message
   follow-ups are not rejected as duplicates of the original new-message (#52277)

4. Retry attachment fetch from BB API (2s delay) when the initial webhook
   arrives with an empty attachments array — image-only messages and
   updated-message events only (#67437)

Closes #64105, closes #61861, closes #65430.

* fix(bluebubbles): resolve review findings — SSRF policy, reuse extractAttachments, add tests

- F1 (BLOCKER): pass undefined instead of {} for SSRF policy when
  allowPrivateNetwork is false, so localhost BB servers are not blocked.
- F2 (IMPORTANT): reuse exported extractAttachments() from monitor-normalize
  instead of duplicating field extraction logic.
- F3 (IMPORTANT): simplify asRecord(asRecord(payload)?.data) to
  asRecord(payload.data) since payload is already Record<string, unknown>.
- F4 (NIT): bind retryMessageId before the guard to eliminate non-null assertion.
- F5 (IMPORTANT): add 4 tests for fetchBlueBubblesMessageAttachments covering
  success, non-ok HTTP, empty data, and guid-less entries.
- Add CHANGELOG entry for the user-facing fix.

* fix(ci): update raw-fetch allowlist line number after dispatcher strip

* fix(bluebubbles): resolve PR review findings (#67510)

- monitor-processing: move attachment retry into the !rawBody guard so
  image-only new-message events that arrive with empty attachments and
  empty text are recovered via a BB API refetch before being dropped.
  The existing retry block at the end of processMessageAfterDedupe was
  unreachable for this case because the !rawBody early-return fired
  first. (Greptile)
- monitor: derive isAttachmentUpdate from the normalized message shape
  instead of raw payload.data.attachments so updated-message webhooks
  with attachments under wrapper formats (payload.message, JSON-string
  payloads) are correctly routed through for processing instead of
  silently filtered. (Codex)
- types: use bundled-undici fetch when init.dispatcher is present so
  the SSRF guard's DNS-pinning dispatcher is preserved when this
  function is called as fetchImpl from guarded callers (e.g. the
  attachment download path via fetchRemoteMedia). Falls back to
  globalThis.fetch when no dispatcher is present so tests that stub
  globalThis.fetch keep working. (Codex)
- attachments: blueBubblesPolicy returns undefined for the non-private
  case (matching monitor-processing's helper) so sendBlueBubblesAttachment
  stops routing localhost BB through the SSRF guard. (Greptile)
- scripts/check-no-raw-channel-fetch: bump the types.ts allowlist line
  to match the restructured non-SSRF branch.

* fix(bluebubbles): move attachment retry before rawBody guard, fix stale log

Move the attachment retry block (2s BB API refetch for empty attachments)
before the !rawBody early-return guard. Previously, image-only messages
with text='' and attachments=[] would be dropped by the !rawBody check
before the retry could fire, making fix #4 dead code for its primary
use-case. Now the retry runs first and recomputes the placeholder from
resolved attachments so rawBody becomes non-empty when media is found.

Also fix stale log message that still said 'without reaction' after the
filter was expanded to pass through attachment updates.

* fix(bluebubbles): revert undici import, restore dispatcher-strip approach

Revert the @claude bot's undici import in types.ts — it introduced a
direct 'undici' dependency that is not declared in the BB extension's
package.json and would break isolated plugin installs. Restore the
original dispatcher-strip approach which is correct: the SSRF guard
already completed validation upstream before calling this function as
fetchImpl, so stripping the dispatcher does not weaken security.

* fix(bluebubbles): remove dead empty-body recovery block in !rawBody guard

The empty-body attachment-recovery block added in the earlier PR revision
is now redundant because the main retry block was moved above the rawBody
computation in 0d7d1c4208. Worse, that leftover block reassigned the
(now-const) placeholder variable, throwing `TypeError: Assignment to
constant variable` at runtime for image-only messages — breaking the very
recovery path it was meant to protect (flagged by Codex on 4bfc2777).

Remove the dead block; the up-front retry already handles the image-only
case by recovering attachments before the rawBody computation, so once we
reach the !rawBody guard with an empty body it is genuinely empty and
should drop as before.

* fix(ci): update raw-fetch allowlist line after dispatcher-strip revert

279dba17d2 reverted types.ts back to the dispatcher-strip approach,
which put the `fetch(url, ...)` call at line 189 instead of line 198.
Bump the allowlist entry to match so `lint:tmp:no-raw-channel-fetch`
stops failing check-additional.

* test(pdf-tool): update stale opus-4-6 constant to opus-4-7

`628b454eff feat: default Anthropic to Opus 4.7` bumped the bundled
anthropic image default to `claude-opus-4-7` but missed updating the
`ANTHROPIC_PDF_MODEL` constant in pdf-tool.model-config.test.ts. The
tests now fail on any PR that runs the `checks-node-agentic-agents-plugins`
shard because the resolver returns 4-7 while the test asserts 4-6.

Bump the constant to 4-7 to match the bundled default.

---------

Co-authored-by: Lobster <10343873+omarshahine@users.noreply.github.com>
2026-04-16 10:04:20 -07:00
..
test-support fix(bluebubbles): dedupe inbound webhooks across restarts (#19176, #12053) (#66816) 2026-04-14 15:45:05 -07:00
account-resolve.test.ts fix(bluebubbles): localhost probe respects private-network opt-out (#59373) 2026-04-07 11:29:21 -05:00
account-resolve.ts fix(bluebubbles): localhost probe respects private-network opt-out (#59373) 2026-04-07 11:29:21 -05:00
accounts-normalization.ts refactor: dedupe repeated test helpers 2026-04-08 09:58:22 +01:00
accounts.ts feat(bluebubbles): replay missed webhook messages after gateway restart (#66857) 2026-04-14 19:20:42 -07:00
actions-api.ts refactor(plugins): narrow bundled channel core seams 2026-04-04 07:39:53 +01:00
actions-contract.ts refactor(plugins): narrow bundled channel core seams 2026-04-04 07:39:53 +01:00
actions.runtime.ts test: narrow bluebubbles reply-cache imports 2026-04-03 14:16:29 +01:00
actions.test.ts fix: honor bluebubbles action discovery account config 2026-04-03 11:11:58 -05:00
actions.ts refactor: dedupe extension lowercase readers 2026-04-07 12:18:01 +01:00
attachments.test.ts fix(bluebubbles): restore inbound image attachments and accept updated-message events (#67510) 2026-04-16 10:04:20 -07:00
attachments.ts fix(bluebubbles): restore inbound image attachments and accept updated-message events (#67510) 2026-04-16 10:04:20 -07:00
catchup.test.ts BlueBubbles/catchup: per-message retry cap for wedged messages (#66870) (#67426) 2026-04-15 22:23:27 -07:00
catchup.ts BlueBubbles/catchup: per-message retry cap for wedged messages (#66870) (#67426) 2026-04-15 22:23:27 -07:00
channel-shared.ts refactor: share webhook channel status helpers 2026-03-29 02:11:22 +01:00
channel.pairing.test.ts test: speed up bluebubbles pairing tests 2026-04-07 13:59:09 +01:00
channel.runtime.ts test: narrow bluebubbles reply-cache imports 2026-04-03 14:16:29 +01:00
channel.setup.ts perf(plugins): narrow boundary compile sdk imports 2026-04-08 08:52:51 +01:00
channel.status.test.ts fix(bluebubbles): localhost probe respects private-network opt-out (#59373) 2026-04-07 11:29:21 -05:00
channel.ts fix(bluebubbles): localhost probe respects private-network opt-out (#59373) 2026-04-07 11:29:21 -05:00
chat.test.ts
chat.ts refactor: trim bluebubbles runtime seams 2026-03-28 02:21:34 +00:00
config-apply.ts perf(plugins): narrow boundary compile sdk imports 2026-04-08 08:52:51 +01:00
config-schema.ts BlueBubbles/catchup: per-message retry cap for wedged messages (#66870) (#67426) 2026-04-15 22:23:27 -07:00
config-ui-hints.ts perf(plugins): narrow boundary compile sdk imports 2026-04-08 08:52:51 +01:00
conversation-bindings.test.ts test: fix cron and binding stability 2026-04-11 02:10:47 +01:00
conversation-bindings.ts refactor: dedupe account conversation bindings 2026-04-06 17:18:36 +01:00
conversation-id.ts feat(acp): add conversation binds for message channels 2026-03-28 01:54:25 +00:00
conversation-route.test.ts fix(ci): reset BlueBubbles binding adapter fixtures 2026-04-11 01:21:59 +01:00
conversation-route.ts feat(acp): add conversation binds for message channels 2026-03-28 01:54:25 +00:00
doctor-contract.ts refactor: dedupe legacy private-network doctor contracts 2026-04-06 17:28:11 +01:00
doctor.test.ts fix: resolve channel typing regressions 2026-04-06 17:43:57 +01:00
doctor.ts refactor: dedupe doctor compatibility adapters 2026-04-06 17:25:36 +01:00
group-policy.ts
history.ts style: fix extension lint violations 2026-04-06 14:53:55 +01:00
inbound-dedupe.test.ts fix(bluebubbles): restore inbound image attachments and accept updated-message events (#67510) 2026-04-16 10:04:20 -07:00
inbound-dedupe.ts fix(bluebubbles): restore inbound image attachments and accept updated-message events (#67510) 2026-04-16 10:04:20 -07:00
local-file-access.ts refactor: dedupe provider lowercase helpers 2026-04-07 15:53:50 +01:00
media-send.test.ts test: narrow bluebubbles reply-cache imports 2026-04-03 14:16:29 +01:00
media-send.ts refactor: dedupe path lowercase helpers 2026-04-07 22:57:52 +01:00
monitor-debounce.ts lint: enable small oxlint rules 2026-04-11 02:15:21 +01:00
monitor-normalize.test.ts fix(bluebubbles): enable group participant enrichment by default, add fallback fetch and handle field aliases 2026-03-26 05:45:41 -07:00
monitor-normalize.ts fix(bluebubbles): restore inbound image attachments and accept updated-message events (#67510) 2026-04-16 10:04:20 -07:00
monitor-processing-api.ts refactor(extensions): split channel runtime helper seams 2026-04-04 07:39:53 +01:00
monitor-processing.ts fix(bluebubbles): restore inbound image attachments and accept updated-message events (#67510) 2026-04-16 10:04:20 -07:00
monitor-reply-cache.ts refactor: dedupe helper alias readers 2026-04-07 08:40:34 +01:00
monitor-self-chat-cache.test.ts
monitor-self-chat-cache.ts refactor: dedupe helper alias readers 2026-04-07 08:40:34 +01:00
monitor-shared.ts perf(plugins): narrow boundary compile sdk imports 2026-04-08 08:52:51 +01:00
monitor.test.ts fix: preserve outbound sender policy context 2026-04-10 17:48:58 -05:00
monitor.ts fix(bluebubbles): restore inbound image attachments and accept updated-message events (#67510) 2026-04-16 10:04:20 -07:00
monitor.webhook-auth.test.ts style: fix extension lint violations 2026-04-06 14:53:55 +01:00
monitor.webhook.test-helpers.ts fix(types): annotate portable exported helper types 2026-04-04 03:57:47 +09:00
multipart.ts refactor: trim bluebubbles runtime seams 2026-03-28 02:21:34 +00:00
pairing.ts test: speed up bluebubbles pairing tests 2026-04-07 13:59:09 +01:00
participant-contact-names.test.ts BlueBubbles: enrich group participants with local Contacts names (#54984) 2026-03-26 18:38:37 +09:00
participant-contact-names.ts refactor: dedupe bluebubbles and zalouser readers 2026-04-07 08:40:34 +01:00
probe.ts refactor: dedupe runtime helper aliases 2026-04-07 09:44:53 +01:00
reactions.test.ts fix(extensions): route fetch calls through fetchWithSsrFGuard (#53929) 2026-03-26 02:04:54 -07:00
reactions.ts refactor: dedupe feishu and bluebubbles lowercase helpers 2026-04-07 13:44:41 +01:00
request-url.ts test(bluebubbles): narrow request-url export 2026-04-03 20:36:24 +09:00
runtime-api.ts refactor(plugins): narrow bundled channel core seams 2026-04-04 07:39:53 +01:00
runtime.ts fix(plugins): stabilize bundled setup runtimes (#67200) 2026-04-15 12:35:18 -04:00
secret-contract.ts chore(lint): clear extension lint regressions and add #63416 changelog 2026-04-08 17:17:29 -07:00
secret-input.ts refactor: unify plugin sdk primitives 2026-03-18 23:58:56 +00:00
send-helpers.ts refactor: dedupe bluebubbles send record helper 2026-04-06 22:54:48 +01:00
send.test.ts fix(bluebubbles): lazy-refresh Private API status on send (#43764) (#65447) 2026-04-13 11:03:47 -07:00
send.ts fix(bluebubbles): lazy-refresh Private API status on send (#43764) (#65447) 2026-04-13 11:03:47 -07:00
session-route.ts refactor(extensions): split channel runtime helper seams 2026-04-04 07:39:53 +01:00
setup-core.ts fix: resolve channel typing regressions 2026-04-06 17:43:57 +01:00
setup-surface.test.ts fix(bluebubbles): localhost probe respects private-network opt-out (#59373) 2026-04-07 11:29:21 -05:00
setup-surface.ts lint: enable small oxlint rules 2026-04-11 02:15:21 +01:00
status-issues.test.ts refactor(plugins): narrow bundled channel core seams 2026-04-04 07:39:53 +01:00
status-issues.ts fix(bluebubbles): remove status type barrel cycle 2026-04-09 09:22:11 +01:00
targets.ts refactor: dedupe messaging lowercase helpers 2026-04-07 15:53:49 +01:00
test-harness.ts fix(bluebubbles): lazy-refresh Private API status on send (#43764) (#65447) 2026-04-13 11:03:47 -07:00
test-mocks.ts
types.ts fix(bluebubbles): restore inbound image attachments and accept updated-message events (#67510) 2026-04-16 10:04:20 -07:00
webhook-ingress.ts test(bluebubbles): split webhook ingress seam 2026-04-03 20:58:03 +09:00
webhook-shared.ts refactor: dedupe bluebubbles readers 2026-04-07 06:55:45 +01:00