Commit Graph

524 Commits

Author SHA1 Message Date
Shakker
97016fbf02 perf: mark channel plugins startup lazy 2026-04-28 04:33:47 +01:00
Peter Steinberger
e1acb61317 refactor: expose SDK test helper subpaths 2026-04-28 03:28:17 +01:00
Peter Steinberger
f34b41f198 refactor: split plugin sdk test helpers 2026-04-28 01:14:19 +01:00
Peter Steinberger
8057561cee refactor: promote plugin test helpers to sdk 2026-04-28 00:55:11 +01:00
Peter Steinberger
0df6e5a473 refactor: expose plugin test helpers via sdk 2026-04-27 23:45:26 +01:00
Peter Steinberger
8599fdda4a test: keep extension mocks on sdk seams 2026-04-27 22:55:09 +01:00
Omar Shahine
da3d17e1ca fix(tts): pre-transcode synthesized audio to opus-in-CAF for native iMessage voice-memo bubbles via BlueBubbles (#72586)
End-to-end testing on macOS + BlueBubbles + ElevenLabs walked through three CAF flavors before landing on the format Apple's Messages.app actually emits when a user records a native iMessage voice memo:

- PCM int16 @ 44.1 kHz CAF: BlueBubbles' internal `afconvert -f m4af -d aac` conversion fails; the original CAF reaches iMessage but renders with 0 s duration.
- AAC @ 22.05 kHz mono CAF: BlueBubbles' conversion succeeds and the server silently downgrades the delivery, sending the converted MP3 as a generic audio attachment.
- **Opus @ 24 kHz mono CAF**: byte-identical to the descriptor block Apple's Messages.app produces; BlueBubbles passes it through unchanged and iMessage renders a native voice-memo bubble with proper duration and waveform UI.

Adds an opt-in `tts.voice.preferAudioFileFormat` channel capability and a macOS `afconvert`-backed pre-transcode in the speech-core pipeline. BlueBubbles declares `preferAudioFileFormat: "caf"`. Other channels are unaffected. Falls back to the original buffer when the host platform, the source/target pair, or the transcoder process can't produce the preferred container — so non-Darwin hosts and unsupported provider combinations are unchanged.

Also adds a `caff` magic-byte sniff in `src/media/mime.ts` so the auto-reply host-local-media validator (which uses `file-type` and didn't recognize CAF natively) accepts the buffer instead of dropping it as "⚠️ Media failed."

Fixes #72506.

Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-27 14:15:16 -07:00
Peter Steinberger
e9b1fbb8c4 refactor: pin remaining extension api surfaces 2026-04-27 21:02:53 +01:00
Peter Steinberger
f0000ab72d refactor(plugin-sdk): split infra runtime barrel 2026-04-27 20:50:35 +01:00
Peter Steinberger
4336a7f3a9 refactor(plugin-sdk): narrow config runtime imports 2026-04-27 14:58:32 +01:00
Peter Steinberger
0141471dd5 refactor: move shared helpers off reserved sdk seams 2026-04-27 13:07:54 +01:00
Peter Steinberger
eaae63d288 refactor: keep plugin sdk owner seams explicit 2026-04-27 12:50:31 +01:00
Peter Steinberger
7f3f108521 refactor(config): migrate plugin config access 2026-04-27 12:35:58 +01:00
Peter Steinberger
30aa1b5223 fix(release): stabilize beta validation lanes 2026-04-26 16:22:12 +01:00
Peter Steinberger
67b9167b80 test(extensions): restore transformed dynamic imports 2026-04-26 13:16:05 +01:00
Peter Steinberger
bc49fb1cdf test: fix extension dynamic imports 2026-04-26 11:15:45 +01:00
Peter Steinberger
d613c8e29b refactor(tts): resolve voice delivery from channel capabilities 2026-04-26 07:03:25 +01:00
Peter Steinberger
6a67f65568 fix(voice): reuse preflight transcripts across channels 2026-04-26 05:42:04 +01:00
Peter Steinberger
7fcefd56b7 chore: bump version to 2026.4.25 2026-04-25 10:31:52 +01:00
Peter Steinberger
2e2a134489 perf: lazy load bluebubbles catchup 2026-04-24 12:08:34 +01:00
Peter Steinberger
2e0e0654ce test(extensions): trim hot extension imports 2026-04-23 13:06:19 +01:00
Peter Steinberger
0b0662b1c9 chore: apply extension lint cleanups 2026-04-23 05:30:49 +01:00
Peter Steinberger
f88da75ed9 refactor(channels): centralize runtime binding routes 2026-04-22 23:16:57 +01:00
Peter Steinberger
80a16339e1 refactor: declare channel add flags in manifests 2026-04-22 19:13:51 +01:00
Omar Shahine
14506aeca4 fix(bluebubbles): add opt-in coalesceSameSenderDms for split-send DMs (#69258)
Merged via squash.

Prepared head SHA: 8f1bd3cf53
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-21 01:43:17 -07:00
Omar Shahine
b5f25de352 bluebubbles: forward per-group systemPrompt into GroupSystemPrompt (#69198)
Forward per-group systemPrompt config into inbound context GroupSystemPrompt so configured group-specific behavioral instructions (for example threaded-reply and tapback conventions) are injected on every turn. Supports "*" wildcard fallback matching the existing requireMention pattern.

Closes #60665.

Co-authored-by: Omar Shahine <omarshahine@users.noreply.github.com>
2026-04-20 20:01:03 -07:00
Peter Steinberger
f85c0b7dc5 refactor: reuse shared local file access 2026-04-21 01:07:09 +01:00
Peter Steinberger
b248899878 perf(channels): narrow slow bundled channel entry imports 2026-04-20 22:34:11 +01:00
Peter Steinberger
7abf2e0574 test(bluebubbles): dedupe runtime type import 2026-04-20 22:00:02 +01:00
Peter Steinberger
75c8c4c08c test(bluebubbles): import runtime type 2026-04-20 21:58:34 +01:00
Peter Steinberger
22f2de0c4f test(extensions): fix shared test helper contracts 2026-04-20 21:56:17 +01:00
Peter Steinberger
5272a94a19 test: share bluebubbles media fixtures 2026-04-20 21:52:13 +01:00
Peter Steinberger
49b2ec1e2e test(extensions): move config regression coverage 2026-04-20 21:51:34 +01:00
Peter Steinberger
231ed8570c fix(bluebubbles): use runtime fetch wrapper 2026-04-20 18:15:43 +01:00
Peter Steinberger
254417a344 test: stabilize BlueBubbles catchup parallelism 2026-04-20 18:06:45 +01:00
Omar Shahine
e89b41fce7 fix(bluebubbles): configurable sendTimeoutMs, bump send default to 30s (#69193)
Merged via squash.

Prepared head SHA: 358204f963
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-20 10:04:52 -07:00
Peter Steinberger
8116e638f3 chore: release 2026.4.20 2026-04-20 13:16:40 +01:00
Peter Steinberger
9429b0976a fix(bluebubbles): refresh client cache on network policy changes 2026-04-20 13:10:02 +01:00
Peter Steinberger
6c711a64cb style: apply formatter fixes 2026-04-20 12:27:15 +01:00
Roy Martin
9fc0d2a6bf fix(bluebubbles): prefer iMessage over SMS when both chats exist (#61781)
Merged via squash.

Prepared head SHA: 664fecff2f
Co-authored-by: rmartin <119151+rmartin@users.noreply.github.com>
Co-authored-by: omarshahine <10343873+omarshahine@users.noreply.github.com>
Reviewed-by: @omarshahine
2026-04-19 21:12:41 -07:00
zqchris
77b424b15e BlueBubbles/reactions: fall back unsupported reactions to love (#64693)
* bluebubbles: fall back unsupported reactions to love

iMessage tapback only supports love/like/dislike/laugh/emphasize/question.
Previously, `normalizeBlueBubblesReactionInput` threw when the input did
not map to one of those (e.g. a non-standard unicode emoji like 👀 used
to mean "seen, working on it"), which aborted the whole reaction request
and left the user with no feedback.

This splits the normalizer into a strict and lenient variant:

- `normalizeBlueBubblesReactionInputStrict` throws on unsupported input
  and is used by validator-style callers (e.g. `resolveBlueBubblesAckReaction`
  in monitor-processing.ts) that rely on the throw to detect misconfigured
  ack reactions and skip them cleanly. This preserves the previous silent-skip
  + warn-once behavior for ack reactions configured with an unsupported
  emoji.
- `normalizeBlueBubblesReactionInput` stays lenient and falls back to
  `love` (or `-love` when removing) on unsupported input, so agent-driven
  `sendBlueBubblesReaction` still produces a visible tapback instead of
  failing the whole reaction request. Contract errors (empty input)
  continue to bubble up.

`love` is chosen over `like` as the neutral default: `❤️` reads as a
general acknowledgment across chat norms, while `👍` carries an
agreement connotation that does not match the "seen, working on it"
semantic.

* CHANGELOG: note BlueBubbles reaction fallback

---------

Co-authored-by: Omar Shahine <10343873+omarshahine@users.noreply.github.com>
2026-04-19 20:52:36 -07:00
Omar Shahine
8fbf0972e7 bluebubbles: always set method explicitly on text sends, force Private API on macOS 26 (#69070)
Merged via squash.

Prepared head SHA: e3af5c5d83
Co-authored-by: omarshahine <198016546+xqing3@users.noreply.github.com>
Co-authored-by: omarshahine <10343873+omarshahine@users.noreply.github.com>
Reviewed-by: @omarshahine
2026-04-19 13:42:56 -07:00
Omar Shahine
055c17b088 bluebubbles: consolidate HTTP traffic through typed BlueBubblesClient (#68234)
Merged via squash.

Prepared head SHA: ee72657bc8
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-19 08:43:32 -07:00
Peter Steinberger
ac8f0c9c0d chore: prepare 2026.4.19-beta.1 release 2026-04-19 02:09:43 +01:00
Peter Steinberger
3f2e73b723 chore(release): bump version to 2026.4.18 2026-04-18 15:46:33 +01:00
Gustavo Madeira Santana
54e4e16844 Tests: narrow session binding contract setup 2026-04-17 03:36:13 -04:00
Peter Steinberger
0dc4c4076c chore: bump version to 2026.4.16 2026-04-17 00:45:04 +01:00
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