fix(whatsapp): restore append recency filter lost in extensions refactor, handle Long timestamps (#42588)

Merged via squash.

Prepared head SHA: 8ce59bb715
Co-authored-by: MonkeyLeeT <6754057+MonkeyLeeT@users.noreply.github.com>
Co-authored-by: scoootscooob <167050519+scoootscooob@users.noreply.github.com>
Reviewed-by: @scoootscooob
This commit is contained in:
Ted Li 2026-03-15 03:03:31 -07:00 committed by GitHub
parent d7ac16788e
commit 843e3c1efb
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
3 changed files with 157 additions and 1 deletions

View File

@ -30,6 +30,7 @@ Docs: https://docs.openclaw.ai
- macOS/canvas actions: keep unattended local agent actions on trusted in-app canvas surfaces only, and stop exposing the deep-link fallback key to arbitrary page scripts. Thanks @vincentkoc.
- Agents/compaction: extend the enclosing run deadline once while compaction is actively in flight, and abort the underlying SDK compaction on timeout/cancel so large-session compactions stop freezing mid-run. (#46889) Thanks @asyncjason.
- Models/openai-completions: default non-native OpenAI-compatible providers to omit tool-definition `strict` fields unless users explicitly opt back in, so tool calling keeps working on providers that reject that option. (#45497) Thanks @sahancava.
- WhatsApp/reconnect: restore the append recency filter in the extension inbox monitor and handle protobuf `Long` timestamps correctly, so fresh post-reconnect append messages are processed while stale history sync stays suppressed. (#42588) thanks @MonkeyLeeT.
### Fixes

View File

@ -413,7 +413,13 @@ export async function monitorWebInbox(options: {
// If this is history/offline catch-up, mark read above but skip auto-reply.
if (upsert.type === "append") {
continue;
const APPEND_RECENT_GRACE_MS = 60_000;
const msgTsRaw = msg.messageTimestamp;
const msgTsNum = msgTsRaw != null ? Number(msgTsRaw) : NaN;
const msgTsMs = Number.isFinite(msgTsNum) ? msgTsNum * 1000 : 0;
if (msgTsMs < connectedAtMs - APPEND_RECENT_GRACE_MS) {
continue;
}
}
const enriched = await enrichInboundMessage(msg);

View File

@ -0,0 +1,149 @@
import "./monitor-inbox.test-harness.js";
import { describe, expect, it, vi } from "vitest";
import { monitorWebInbox } from "./inbound.js";
import {
DEFAULT_ACCOUNT_ID,
getAuthDir,
getSock,
installWebMonitorInboxUnitTestHooks,
} from "./monitor-inbox.test-harness.js";
describe("append upsert handling (#20952)", () => {
installWebMonitorInboxUnitTestHooks();
type InboxOnMessage = NonNullable<Parameters<typeof monitorWebInbox>[0]["onMessage"]>;
async function tick() {
await new Promise((resolve) => setImmediate(resolve));
}
async function startInboxMonitor(onMessage: InboxOnMessage) {
const listener = await monitorWebInbox({
verbose: false,
onMessage,
accountId: DEFAULT_ACCOUNT_ID,
authDir: getAuthDir(),
});
return { listener, sock: getSock() };
}
it("processes recent append messages (within 60s of connect)", async () => {
const onMessage = vi.fn(async () => {});
const { listener, sock } = await startInboxMonitor(onMessage);
// Timestamp ~5 seconds ago — recent, should be processed.
const recentTs = Math.floor(Date.now() / 1000) - 5;
sock.ev.emit("messages.upsert", {
type: "append",
messages: [
{
key: { id: "recent-1", fromMe: false, remoteJid: "120363@g.us" },
message: { conversation: "hello from group" },
messageTimestamp: recentTs,
pushName: "Tester",
},
],
});
await tick();
expect(onMessage).toHaveBeenCalledTimes(1);
await listener.close();
});
it("skips stale append messages (older than 60s before connect)", async () => {
const onMessage = vi.fn(async () => {});
const { listener, sock } = await startInboxMonitor(onMessage);
// Timestamp 5 minutes ago — stale history sync, should be skipped.
const staleTs = Math.floor(Date.now() / 1000) - 300;
sock.ev.emit("messages.upsert", {
type: "append",
messages: [
{
key: { id: "stale-1", fromMe: false, remoteJid: "120363@g.us" },
message: { conversation: "old history sync" },
messageTimestamp: staleTs,
pushName: "OldTester",
},
],
});
await tick();
expect(onMessage).not.toHaveBeenCalled();
await listener.close();
});
it("skips append messages with NaN/non-finite timestamps", async () => {
const onMessage = vi.fn(async () => {});
const { listener, sock } = await startInboxMonitor(onMessage);
// NaN timestamp should be treated as 0 (stale) and skipped.
sock.ev.emit("messages.upsert", {
type: "append",
messages: [
{
key: { id: "nan-1", fromMe: false, remoteJid: "120363@g.us" },
message: { conversation: "bad timestamp" },
messageTimestamp: NaN,
pushName: "BadTs",
},
],
});
await tick();
expect(onMessage).not.toHaveBeenCalled();
await listener.close();
});
it("handles Long-like protobuf timestamps correctly", async () => {
const onMessage = vi.fn(async () => {});
const { listener, sock } = await startInboxMonitor(onMessage);
// Baileys can deliver messageTimestamp as a Long object (from protobufjs).
// Number(longObj) calls valueOf() and returns the numeric value.
const recentTs = Math.floor(Date.now() / 1000) - 5;
const longLike = { low: recentTs, high: 0, unsigned: true, valueOf: () => recentTs };
sock.ev.emit("messages.upsert", {
type: "append",
messages: [
{
key: { id: "long-1", fromMe: false, remoteJid: "120363@g.us" },
message: { conversation: "long timestamp" },
messageTimestamp: longLike,
pushName: "LongTs",
},
],
});
await tick();
expect(onMessage).toHaveBeenCalledTimes(1);
await listener.close();
});
it("always processes notify messages regardless of timestamp", async () => {
const onMessage = vi.fn(async () => {});
const { listener, sock } = await startInboxMonitor(onMessage);
// Very old timestamp but type=notify — should always be processed.
const oldTs = Math.floor(Date.now() / 1000) - 86400;
sock.ev.emit("messages.upsert", {
type: "notify",
messages: [
{
key: { id: "notify-1", fromMe: false, remoteJid: "999@s.whatsapp.net" },
message: { conversation: "normal message" },
messageTimestamp: oldTs,
pushName: "User",
},
],
});
await tick();
expect(onMessage).toHaveBeenCalledTimes(1);
await listener.close();
});
});