From 998128abd85b76ee2ac57211a87df1ae3ff592f6 Mon Sep 17 00:00:00 2001 From: Peter Steinberger Date: Mon, 24 Aug 2026 13:28:09 -0700 Subject: [PATCH] fix(reply): preserve distinct streamed location replies (#128846) --- .../reply/block-reply-pipeline.test.ts | 14 +++++++++++ src/auto-reply/reply/block-reply-pipeline.ts | 25 +++++++++---------- 2 files changed, 26 insertions(+), 13 deletions(-) diff --git a/src/auto-reply/reply/block-reply-pipeline.test.ts b/src/auto-reply/reply/block-reply-pipeline.test.ts index 6ecdca99b026..3a143ec4d4db 100644 --- a/src/auto-reply/reply/block-reply-pipeline.test.ts +++ b/src/auto-reply/reply/block-reply-pipeline.test.ts @@ -56,6 +56,9 @@ describe("createBlockReplyContentKey", () => { }); expect(a).toBe(b); expect(a).not.toBe(c); + expect(createBlockReplyContentKey({ location: { latitude: 1, longitude: 2 } })).not.toBe( + createBlockReplyContentKey({ location: { latitude: 3, longitude: 4 } }), + ); }); }); @@ -192,6 +195,17 @@ describe("createBlockReplyPipeline dedup with threading", () => { { text: "After", audioAsVoice: true }, ], }, + { + name: "distinct portable location replies", + payloads: [ + { location: { latitude: 1, longitude: 2 } }, + { location: { latitude: 3, longitude: 4 } }, + ], + expected: [ + { location: { latitude: 1, longitude: 2 } }, + { location: { latitude: 3, longitude: 4 } }, + ], + }, ])("preserves streamed delivery order for $name", async ({ payloads, expected }) => { const sent: ReplyPayload[] = []; const pipeline = createBlockReplyPipeline({ diff --git a/src/auto-reply/reply/block-reply-pipeline.ts b/src/auto-reply/reply/block-reply-pipeline.ts index 266fdc7ec879..8b6a5474a211 100644 --- a/src/auto-reply/reply/block-reply-pipeline.ts +++ b/src/auto-reply/reply/block-reply-pipeline.ts @@ -53,35 +53,34 @@ export function createAudioAsVoiceBuffer(params: { }; } -/** Creates a stable duplicate key for a complete outbound payload. */ -function createBlockReplyPayloadKey(payload: ReplyPayload): string { +function createBlockReplyContentIdentity(payload: ReplyPayload) { const reply = resolveSendableOutboundReplyParts(payload); - return JSON.stringify({ - statusNotice: isReplyPayloadStatusNotice(payload), + return { text: reply.trimmedText, mediaList: reply.mediaUrls, presentation: payload.presentation ?? null, presentationTextMode: payload.presentationTextMode ?? null, interactive: payload.interactive ?? null, channelData: payload.channelData ?? null, + location: payload.location ?? null, + }; +} + +/** Creates a stable duplicate key for a complete outbound payload. */ +function createBlockReplyPayloadKey(payload: ReplyPayload): string { + return JSON.stringify({ + ...createBlockReplyContentIdentity(payload), + statusNotice: isReplyPayloadStatusNotice(payload), replyToId: payload.replyToId ?? null, }); } /** Creates a duplicate key that ignores reply target for final suppression. */ export function createBlockReplyContentKey(payload: ReplyPayload): string { - const reply = resolveSendableOutboundReplyParts(payload); // Content-only key used for final-payload suppression after block streaming. // This intentionally ignores replyToId so a streamed threaded payload and the // later final payload still collapse when they carry the same content. - return JSON.stringify({ - text: reply.trimmedText, - mediaList: reply.mediaUrls, - presentation: payload.presentation ?? null, - presentationTextMode: payload.presentationTextMode ?? null, - interactive: payload.interactive ?? null, - channelData: payload.channelData ?? null, - }); + return JSON.stringify(createBlockReplyContentIdentity(payload)); } function resolveBlockReplyTimeoutMs(timeoutMs: number): number {