Commit Graph

380 Commits

Author SHA1 Message Date
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
Omar Shahine
4af7641350 BlueBubbles/catchup: per-message retry cap for wedged messages (#66870) (#67426)
Merged via squash.

Prepared head SHA: 39e3cf1df5
Co-authored-by: omarshahine <10343873+omarshahine@users.noreply.github.com>
Co-authored-by: omarshahine <10343873+omarshahine@users.noreply.github.com>
Reviewed-by: @omarshahine
2026-04-15 22:23:27 -07:00
Gustavo Madeira Santana
78ac118427 fix(plugins): stabilize bundled setup runtimes (#67200)
Merged via squash.

Prepared head SHA: e8d6738fd0
Co-authored-by: gumadeiras <5599352+gumadeiras@users.noreply.github.com>
Co-authored-by: gumadeiras <5599352+gumadeiras@users.noreply.github.com>
Reviewed-by: @gumadeiras
2026-04-15 12:35:18 -04:00
Omar Shahine
6f1d321aab feat(bluebubbles): replay missed webhook messages after gateway restart (#66857)
Adds an in-process startup catchup pass to the BlueBubbles channel that
queries BB Server for messages delivered since a persisted per-account
cursor and re-feeds each through the existing processMessage pipeline.

Fixes the missed-message hole documented in #66721: BB's WebhookService
is fire-and-forget on POST failure, and MessagePoller only re-fires
webhooks on BB-side reconnection events, not on webhook-receiver
recovery.

- New extensions/bluebubbles/src/catchup.ts with singleflight per
  accountId, cursor persistence via the canonical state-paths
  resolver, bounded query (perRunLimit + maxAgeMinutes), failure-held
  cursor, truncation-aware page-boundary advancement, future-cursor
  recovery, isFromMe filter (pre- and post-normalization).
- monitor.ts fires catchup as a background task after the webhook
  target registers.
- config-schema.ts adds optional catchup block; accounts.ts adds
  catchup to nestedObjectKeys for deep-merge per-account overrides.
- Dedupes against #66816's persistent inbound GUID cache.
- 22 scoped tests; full BB suite 411/411; pnpm check green; live E2E
  on macOS 26.3 / BB Server 1.9.x recovered 3/3 missed messages.

Closes #66721.

Co-authored-by: Omar Shahine <omar@shahine.com>
2026-04-14 19:20:42 -07:00
Omar Shahine
58742acaab fix(bluebubbles): dedupe inbound webhooks across restarts (#19176, #12053) (#66816)
BlueBubbles MessagePoller replays its ~1-week lookback window as new-message
webhooks after BB Server restart or reconnect. Add a persistent file-backed
GUID dedupe (TTL=7d) at the top of processMessage using createClaimableDedupe
from the Plugin SDK. Claim/finalize/release semantics ensure transient delivery
failures release the GUID so a later replay can retry.

Fixes #19176, #12053.

Co-authored-by: Omar Shahine <omar@shahine.com>
2026-04-14 15:45:05 -07:00
Omar Shahine
85cfba675a fix(bluebubbles): lazy-refresh Private API status on send (#43764) (#65447)
* fix(bluebubbles): lazy refresh Private API cache on send to prevent silent reply threading degradation (#43764)

When the 10-minute server info cache expires, sends requesting reply
threading or effects silently degrade to plain messages. Add a lazy
async refresh of the cache in the send path when Private API features
are needed but status is unknown, preserving graceful degradation if
the refresh fails.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* fix(bluebubbles): apply lazy Private API refresh to attachment sends and add missing test coverage (#43764)

Attachment sends had the same cache-expiry bug as text sends: when the
10-minute Private API status cache TTL expired, reply threading metadata
was silently dropped. Apply the same lazy-refresh pattern from send.ts.

Also add the missing "refresh succeeds with private_api: false" test case
for both send.ts and attachments.ts — proves effects throw and reply
threading degrades without the "unknown" warning when the API is explicitly
disabled.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* chore: update no-raw-channel-fetch allowlist for test-harness line shift

Adding fetchBlueBubblesServerInfo to the probe mock module shifted
globalThis.fetch in test-harness.ts from line 128 to 130.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

---------

Co-authored-by: Lobster <lobster@shahine.com>
Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-13 11:03:47 -07:00
Peter Steinberger
39d1a817fa lint: enable small oxlint rules 2026-04-11 02:15:21 +01:00
Peter Steinberger
60b61288c4 test: fix cron and binding stability 2026-04-11 02:10:47 +01:00
Peter Steinberger
37b91be894 fix(ci): reset BlueBubbles binding adapter fixtures 2026-04-11 01:21:59 +01:00
Tak Hoffman
6afff0642e fix: preserve account binding metadata on rebind 2026-04-10 19:12:02 -05:00
Peter Steinberger
ebfd468ee0 refactor: simplify typed conversions 2026-04-11 01:01:30 +01:00
Peter Steinberger
29ff425727 refactor: simplify bluebubbles setup strings 2026-04-11 00:08:15 +01:00
Tak Hoffman
8e45398e1d fix: preserve outbound sender policy context 2026-04-10 17:48:58 -05:00
Vincent Koc
60a3733f12 fix(bluebubbles): remove status type barrel cycle 2026-04-09 09:22:11 +01:00
Vignesh Natarajan
2484064c48 chore(lint): clear extension lint regressions and add #63416 changelog 2026-04-08 17:17:29 -07:00
Peter Steinberger
95e397a266 refactor: dedupe repeated test helpers 2026-04-08 09:58:22 +01:00
Vincent Koc
4260ac4cf6 perf(plugins): narrow boundary compile sdk imports 2026-04-08 08:52:51 +01:00
Peter Steinberger
b6970865b6 refactor: dedupe path lowercase helpers 2026-04-07 22:57:52 +01:00
Peter Steinberger
a4bb2698dd refactor: dedupe ui provider lowercase helpers 2026-04-07 20:58:01 +01:00
James Reagan
dac72889e5 fix(bluebubbles): localhost probe respects private-network opt-out (#59373)
* honor localhost private-network policy

* drop flaky monitor private-network test

* align mocks and imports

* preserve account private-network overrides

* keep default account config

* strip stale private-network aliases

* fix(bluebubbles): remove unused channel imports

* fix: add changelog for bluebubbles private-network opt-out landing (#59373) (thanks @jpreagan)

---------

Co-authored-by: Shadow <hi@shadowing.dev>
2026-04-07 11:29:21 -05:00
Peter Steinberger
bbcc95948e refactor: dedupe provider lowercase helpers 2026-04-07 15:53:50 +01:00
Peter Steinberger
4bcbb22678 refactor: dedupe messaging lowercase helpers 2026-04-07 15:53:49 +01:00
Peter Steinberger
ad605052bf refactor: dedupe provider lowercase helpers 2026-04-07 15:12:31 +01:00
Peter Steinberger
1409d5a160 fix(boundary): restore bluebubbles and matrix type seams 2026-04-07 14:17:03 +01:00
Peter Steinberger
60199fbee3 test: speed up bluebubbles pairing tests 2026-04-07 13:59:09 +01:00
Peter Steinberger
88b394ba1b refactor: dedupe feishu and bluebubbles lowercase helpers 2026-04-07 13:44:41 +01:00
Peter Steinberger
4ede1e4e3a fix(boundary): restore compile and dm policy type paths 2026-04-07 13:28:55 +01:00
Vincent Koc
dfb6c9c920 perf(plugin-sdk): split channel secret runtime helpers 2026-04-07 13:09:12 +01:00
Peter Steinberger
b39c7eece6 refactor: dedupe extension lowercase readers 2026-04-07 12:18:01 +01:00
Peter Steinberger
ac478e2024 test: speed up setup surface tests 2026-04-07 11:57:25 +01:00
Peter Steinberger
6236db5192 refactor: dedupe runtime helper aliases 2026-04-07 09:44:53 +01:00
Peter Steinberger
9d8d1dd4c5 refactor: dedupe shared string aliases 2026-04-07 09:44:53 +01:00
Peter Steinberger
dbc67a5626 refactor: dedupe helper alias readers 2026-04-07 08:40:34 +01:00
Peter Steinberger
424b65b697 refactor: dedupe bluebubbles and zalouser readers 2026-04-07 08:40:34 +01:00
Peter Steinberger
9fcef82f2d refactor: dedupe bluebubbles readers 2026-04-07 06:55:45 +01:00
Peter Steinberger
9e2a1e12fd refactor: dedupe channel runtime error formatting 2026-04-07 02:03:34 +01:00
Peter Steinberger
61f7d53731 refactor: dedupe shared string readers 2026-04-07 02:03:33 +01:00
Peter Steinberger
a88f240311 refactor: dedupe shared record coercers 2026-04-07 02:03:33 +01:00
Peter Steinberger
29163a8caa refactor: dedupe bluebubbles status record helper 2026-04-06 22:54:48 +01:00
Peter Steinberger
92e3299793 refactor: dedupe bluebubbles send record helper 2026-04-06 22:54:48 +01:00
Peter Steinberger
ca73e598e0 refactor: dedupe bluebubbles monitor record helper 2026-04-06 22:54:47 +01:00
Vincent Koc
78639eff76 perf(secrets): narrow channel secret sdk seam 2026-04-06 20:40:11 +01:00
Peter Steinberger
6acb43f294 fix: resolve channel typing regressions 2026-04-06 17:43:57 +01:00
Peter Steinberger
e7e3f11b20 refactor: dedupe legacy private-network doctor contracts 2026-04-06 17:28:11 +01:00
Peter Steinberger
943d7de240 refactor: dedupe doctor compatibility adapters 2026-04-06 17:25:36 +01:00
Peter Steinberger
1c5cbad0a6 refactor: dedupe account conversation bindings 2026-04-06 17:18:36 +01:00
Vincent Koc
c3b19d204a perf(test): lazy-load bundled channel secrets 2026-04-06 16:40:41 +01:00
Vincent Koc
6067f2d9ad chore(plugins): drop dead channel test any suppressions 2026-04-06 15:45:18 +01:00
Peter Steinberger
af62a2c2e4 style: fix extension lint violations 2026-04-06 14:53:55 +01:00
Vincent Koc
0fdf9e874b fix(config): normalize channel streaming config shape (#61381)
* feat(config): add canonical streaming config helpers

* refactor(runtime): prefer canonical streaming accessors

* feat(config): normalize preview channel streaming shape

* test(config): lock streaming normalization followups

* fix(config): polish streaming migration edges

* chore(config): refresh streaming baseline hash
2026-04-06 05:08:20 +01:00