diff --git a/CHANGELOG.md b/CHANGELOG.md index fb0cd44687d..06933054fe6 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -38,6 +38,7 @@ Docs: https://docs.openclaw.ai - Memory/QMD: honor `memory.qmd.update.embedInterval` even when regular QMD update cadence is disabled or slower by arming a dedicated embed-cadence maintenance timer, while avoiding redundant timers when regular updates are already frequent enough. (#37326) Thanks @barronlroth. - Agents/memory flush: keep daily memory flush files append-only during embedded attempts so compaction writes do not overwrite earlier notes. (#53725) Thanks @HPluseven. - Web UI/markdown: stop bare auto-links from swallowing adjacent CJK text while preserving valid mixed-script path and query characters in rendered links. (#48410) Thanks @jnuyao. +- BlueBubbles/iMessage: coalesce URL-only inbound messages with their link-preview balloon again so sharing a bare link no longer drops the URL from agent context. Thanks @vincentkoc. - Sandbox/browser: install `fonts-noto-cjk` in the sandbox browser image so screenshots render Chinese, Japanese, and Korean text correctly instead of tofu boxes. Fixes #35597. Thanks @carrotRakko and @vincentkoc. - Memory/FTS: add configurable trigram tokenization plus short-CJK substring fallback so memory search can find Chinese, Japanese, and Korean text without breaking mixed long-and-short queries. Thanks @carrotRakko. - Hooks/config: accept runtime channel plugin ids in `hooks.mappings[].channel` (for example `feishu`) instead of rejecting non-core channels during config validation. (#56226) Thanks @AiKrai001. diff --git a/extensions/bluebubbles/src/monitor-debounce.ts b/extensions/bluebubbles/src/monitor-debounce.ts index e7f642d49ea..44940bb6d1c 100644 --- a/extensions/bluebubbles/src/monitor-debounce.ts +++ b/extensions/bluebubbles/src/monitor-debounce.ts @@ -150,7 +150,7 @@ export function createBlueBubblesDebounceRegistry(params: { const balloonBundleId = msg.balloonBundleId?.trim(); const associatedMessageGuid = msg.associatedMessageGuid?.trim(); if (balloonBundleId && associatedMessageGuid) { - return `bluebubbles:${account.accountId}:balloon:${associatedMessageGuid}`; + return `bluebubbles:${account.accountId}:msg:${associatedMessageGuid}`; } const messageId = msg.messageId?.trim(); diff --git a/extensions/bluebubbles/src/monitor.test.ts b/extensions/bluebubbles/src/monitor.test.ts index 199a14315e2..c2caba10d4e 100644 --- a/extensions/bluebubbles/src/monitor.test.ts +++ b/extensions/bluebubbles/src/monitor.test.ts @@ -850,6 +850,68 @@ describe("BlueBubbles webhook monitor", () => { } }); + it("coalesces URL text with URL balloon webhook events by associatedMessageGuid", async () => { + vi.useFakeTimers(); + try { + const core = createMockRuntime(); + installTimingAwareInboundDebouncer(core); + const processMessage = vi.fn().mockResolvedValue(undefined); + const registry = createBlueBubblesDebounceRegistry({ processMessage }); + const account = createMockAccount(); + const target = { + account, + config: {}, + runtime: { log: vi.fn(), error: vi.fn() }, + core, + path: "/bluebubbles-webhook", + }; + const debouncer = registry.getOrCreateDebouncer(target); + + const messageId = "url-msg-1"; + const chatGuid = "iMessage;-;+15551234567"; + const url = "https://github.com/bitfocus/companion/issues/4047"; + + await debouncer.enqueue({ + message: createDebounceTestMessage({ + chatGuid, + text: url, + messageId, + }), + target, + }); + + await vi.advanceTimersByTimeAsync(300); + + await debouncer.enqueue({ + message: createDebounceTestMessage({ + chatGuid, + text: url, + messageId: "url-balloon-1", + balloonBundleId: "com.apple.messages.URLBalloonProvider", + associatedMessageGuid: messageId, + }), + target, + }); + + expect(processMessage).not.toHaveBeenCalled(); + + await vi.advanceTimersByTimeAsync(600); + + expect(processMessage).toHaveBeenCalledTimes(1); + expect(processMessage).toHaveBeenCalledWith( + expect.objectContaining({ + text: url, + messageId, + balloonBundleId: undefined, + }), + target, + ); + expect(target.runtime.error).not.toHaveBeenCalled(); + } finally { + vi.useRealTimers(); + } + }); + it("skips null-text entries during flush and still delivers the valid message", async () => { vi.useFakeTimers(); try {