From 8a11d0ea32a8ee6514febc88c83c92ee3561c2ca Mon Sep 17 00:00:00 2001 From: Dale Date: Mon, 2 Mar 2026 07:38:54 -0500 Subject: [PATCH] fix(signal): fall back to dataMessage.timestamp for ACK reaction Mirror the same timestamp fallback already used for read receipts: envelope.timestamp ?? dataMessage.timestamp Messages that carry only dataMessage.timestamp (no envelope.timestamp) previously skipped the ACK entirely; they now receive one correctly. Updates the 'timestamp missing' test to require both timestamps absent, and adds a new test confirming the dataMessage fallback path. Addresses: https://github.com/openclaw/openclaw/pull/31078#discussion_r2871998637 --- .../event-handler.ack-reaction.test.ts | 23 +++++++++++++++++-- .../signal/src/monitor/event-handler.ts | 12 ++++++++-- 2 files changed, 31 insertions(+), 4 deletions(-) diff --git a/extensions/signal/src/monitor/event-handler.ack-reaction.test.ts b/extensions/signal/src/monitor/event-handler.ack-reaction.test.ts index af6ef328601..ef6f20d9e21 100644 --- a/extensions/signal/src/monitor/event-handler.ack-reaction.test.ts +++ b/extensions/signal/src/monitor/event-handler.ack-reaction.test.ts @@ -173,13 +173,32 @@ describe("Signal ACK reactions", () => { expect(sendReactionSignal).not.toHaveBeenCalled(); }); - it("does NOT send ack when timestamp is missing", async () => { + it("sends ack using dataMessage.timestamp when envelope.timestamp is missing", async () => { const deps = makeDeps(); const handler = createSignalEventHandler(deps); await handler( makeEvent({ timestamp: undefined, - dataMessage: { message: "hello", timestamp: 1700000000000 }, + dataMessage: { message: "hello", timestamp: 1700000001234 }, + }), + ); + + expect(sendReactionSignal).toHaveBeenCalledTimes(1); + expect(sendReactionSignal).toHaveBeenCalledWith( + expect.any(String), + 1700000001234, + expect.any(String), + expect.any(Object), + ); + }); + + it("does NOT send ack when both envelope and dataMessage timestamps are missing", async () => { + const deps = makeDeps(); + const handler = createSignalEventHandler(deps); + await handler( + makeEvent({ + timestamp: undefined, + dataMessage: { message: "hello", timestamp: undefined }, }), ); diff --git a/extensions/signal/src/monitor/event-handler.ts b/extensions/signal/src/monitor/event-handler.ts index b571e5f50f1..499d0d845ff 100644 --- a/extensions/signal/src/monitor/event-handler.ts +++ b/extensions/signal/src/monitor/event-handler.ts @@ -708,14 +708,22 @@ export function createSignalEventHandler(deps: SignalEventHandlerDeps) { messageText || dataMessage.attachments?.length || dataMessage.quote?.text?.trim(), ); + // Resolve ACK timestamp — mirror the same fallback used for read receipts. + const ackTimestamp = + typeof envelope.timestamp === "number" + ? envelope.timestamp + : typeof dataMessage.timestamp === "number" + ? dataMessage.timestamp + : undefined; + // Send ACK reaction immediately — before any awaited I/O (attachment fetch, read receipt) // so the user gets instant visual feedback that their message was received. - if (hasEarlyBody && typeof envelope.timestamp === "number") { + if (hasEarlyBody && ackTimestamp !== undefined) { maybeSendSignalAckReaction({ cfg: deps.cfg, agentId: route.agentId, senderRecipient, - targetTimestamp: envelope.timestamp, + targetTimestamp: ackTimestamp, isGroup, groupId, wasMentioned: effectiveWasMentioned,