mirror of
https://github.com/openclaw/openclaw.git
synced 2026-03-13 19:10:39 +00:00
BlueBubbles: require confirmed outbound for self-chat cache
This commit is contained in:
@@ -487,6 +487,15 @@ export async function processMessage(
|
||||
};
|
||||
|
||||
const cacheMessageId = message.messageId?.trim();
|
||||
const confirmedOutboundCacheEntry = cacheMessageId
|
||||
? resolveReplyContextFromCache({
|
||||
accountId: account.accountId,
|
||||
replyToId: cacheMessageId,
|
||||
chatGuid: message.chatGuid,
|
||||
chatIdentifier: message.chatIdentifier,
|
||||
chatId: message.chatId,
|
||||
})
|
||||
: null;
|
||||
let messageShortId: string | undefined;
|
||||
const cacheInboundMessage = () => {
|
||||
if (!cacheMessageId) {
|
||||
@@ -508,6 +517,12 @@ export async function processMessage(
|
||||
if (message.fromMe) {
|
||||
// Cache from-me messages so reply context can resolve sender/body.
|
||||
cacheInboundMessage();
|
||||
const confirmedAssistantOutbound =
|
||||
confirmedOutboundCacheEntry?.senderLabel === "me" &&
|
||||
normalizeSnippet(confirmedOutboundCacheEntry.body ?? "") === normalizeSnippet(rawBody);
|
||||
if (isSelfChatMessage && confirmedAssistantOutbound) {
|
||||
rememberBlueBubblesSelfChatCopy(selfChatLookup);
|
||||
}
|
||||
if (cacheMessageId) {
|
||||
const pending = consumePendingOutboundMessageId({
|
||||
accountId: account.accountId,
|
||||
@@ -517,9 +532,6 @@ export async function processMessage(
|
||||
body: rawBody,
|
||||
});
|
||||
if (pending) {
|
||||
if (isSelfChatMessage) {
|
||||
rememberBlueBubblesSelfChatCopy(selfChatLookup);
|
||||
}
|
||||
const displayId = getShortIdForUuid(cacheMessageId) || cacheMessageId;
|
||||
const previewSource = pending.snippetRaw || rawBody;
|
||||
const preview = previewSource
|
||||
|
||||
@@ -2687,7 +2687,7 @@ describe("BlueBubbles webhook monitor", () => {
|
||||
setBlueBubblesRuntime(core);
|
||||
|
||||
const { sendMessageBlueBubbles } = await import("./send.js");
|
||||
vi.mocked(sendMessageBlueBubbles).mockResolvedValueOnce({ messageId: "ok" });
|
||||
vi.mocked(sendMessageBlueBubbles).mockResolvedValueOnce({ messageId: "msg-self-1" });
|
||||
|
||||
mockDispatchReplyWithBufferedBlockDispatcher.mockImplementationOnce(async (params) => {
|
||||
await params.dispatcherOptions.deliver({ text: "replying now" }, { kind: "final" });
|
||||
@@ -2980,6 +2980,92 @@ describe("BlueBubbles webhook monitor", () => {
|
||||
expect(mockDispatchReplyWithBufferedBlockDispatcher).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it("does not treat a pending text-only match as confirmed assistant outbound", async () => {
|
||||
const account = createMockAccount({ dmPolicy: "open" });
|
||||
const config: OpenClawConfig = {};
|
||||
const core = createMockRuntime();
|
||||
setBlueBubblesRuntime(core);
|
||||
|
||||
const { sendMessageBlueBubbles } = await import("./send.js");
|
||||
vi.mocked(sendMessageBlueBubbles).mockResolvedValueOnce({ messageId: "ok" });
|
||||
|
||||
mockDispatchReplyWithBufferedBlockDispatcher.mockImplementationOnce(async (params) => {
|
||||
await params.dispatcherOptions.deliver({ text: "same text" }, { kind: "final" });
|
||||
return EMPTY_DISPATCH_RESULT;
|
||||
});
|
||||
|
||||
unregister = registerBlueBubblesWebhookTarget({
|
||||
account,
|
||||
config,
|
||||
runtime: { log: vi.fn(), error: vi.fn() },
|
||||
core,
|
||||
path: "/bluebubbles-webhook",
|
||||
});
|
||||
|
||||
const timestamp = Date.now();
|
||||
const inboundPayload = {
|
||||
type: "new-message",
|
||||
data: {
|
||||
text: "hello",
|
||||
handle: { address: "+15551234567" },
|
||||
isGroup: false,
|
||||
isFromMe: false,
|
||||
guid: "msg-self-race-0",
|
||||
chatGuid: "iMessage;-;+15551234567",
|
||||
date: timestamp,
|
||||
},
|
||||
};
|
||||
|
||||
await handleBlueBubblesWebhookRequest(
|
||||
createMockRequest("POST", "/bluebubbles-webhook", inboundPayload),
|
||||
createMockResponse(),
|
||||
);
|
||||
await flushAsync();
|
||||
|
||||
expect(mockDispatchReplyWithBufferedBlockDispatcher).toHaveBeenCalledTimes(1);
|
||||
mockDispatchReplyWithBufferedBlockDispatcher.mockClear();
|
||||
|
||||
const fromMePayload = {
|
||||
type: "new-message",
|
||||
data: {
|
||||
text: "same text",
|
||||
handle: { address: "+15551234567" },
|
||||
isGroup: false,
|
||||
isFromMe: true,
|
||||
guid: "msg-self-race-1",
|
||||
chatGuid: "iMessage;-;+15551234567",
|
||||
date: timestamp,
|
||||
},
|
||||
};
|
||||
|
||||
await handleBlueBubblesWebhookRequest(
|
||||
createMockRequest("POST", "/bluebubbles-webhook", fromMePayload),
|
||||
createMockResponse(),
|
||||
);
|
||||
await flushAsync();
|
||||
|
||||
const reflectedPayload = {
|
||||
type: "new-message",
|
||||
data: {
|
||||
text: "same text",
|
||||
handle: { address: "+15551234567" },
|
||||
isGroup: false,
|
||||
isFromMe: false,
|
||||
guid: "msg-self-race-2",
|
||||
chatGuid: "iMessage;-;+15551234567",
|
||||
date: timestamp,
|
||||
},
|
||||
};
|
||||
|
||||
await handleBlueBubblesWebhookRequest(
|
||||
createMockRequest("POST", "/bluebubbles-webhook", reflectedPayload),
|
||||
createMockResponse(),
|
||||
);
|
||||
await flushAsync();
|
||||
|
||||
expect(mockDispatchReplyWithBufferedBlockDispatcher).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it("does not treat chatGuid-inferred sender ids as self-chat evidence", async () => {
|
||||
const account = createMockAccount({ dmPolicy: "open" });
|
||||
const config: OpenClawConfig = {};
|
||||
|
||||
Reference in New Issue
Block a user