fix(whatsapp): normalize reaction peer IDs to match message routing
Reactions now use the same peer ID normalization as messages (E.164 format for DMs) to ensure they land in the correct session key. Without this fix, per-peer DM scopes would route reactions to a JID-based session while messages go to an E.164-based session, making reactions invisible to the agent. - Add resolveReactionPeerId() matching resolvePeerId() logic - Apply E.164 normalization for DM reactions when senderE164 available - Add test verifying reactions land in the same session as messages - Fixes session routing mismatch caught by Codex review Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
parent
5571967ebd
commit
e629254b26
@ -184,4 +184,63 @@ describe("web auto-reply – inbound reaction system events", () => {
|
||||
expect(events).toHaveLength(1);
|
||||
expect(events[0]).toBe("WhatsApp reaction added: 🔥 by someone msg msg-noid");
|
||||
});
|
||||
|
||||
it("normalizes DM reaction peer ID to E.164 matching message routing", async () => {
|
||||
setLoadConfigMock(() => ({
|
||||
channels: { whatsapp: { allowFrom: ["*"] } },
|
||||
messages: {},
|
||||
}));
|
||||
|
||||
let capturedOnReaction: ((reaction: WebInboundReaction) => void) | undefined;
|
||||
const listenerFactory = async (opts: {
|
||||
onMessage: (...args: unknown[]) => Promise<void>;
|
||||
onReaction?: (reaction: WebInboundReaction) => void;
|
||||
}) => {
|
||||
capturedOnReaction = opts.onReaction;
|
||||
return { close: vi.fn() };
|
||||
};
|
||||
|
||||
await monitorWebChannel(false, listenerFactory, false);
|
||||
|
||||
const cfg = { channels: { whatsapp: { allowFrom: ["*"] } }, messages: {} };
|
||||
|
||||
// For DM reactions with senderE164, the peer ID should be normalized to E.164
|
||||
// to match how messages are routed (via resolvePeerId).
|
||||
const normalizedRoute = resolveAgentRoute({
|
||||
cfg: cfg as Parameters<typeof resolveAgentRoute>[0]["cfg"],
|
||||
channel: "whatsapp",
|
||||
accountId: "default",
|
||||
peer: { kind: "dm", id: "+19995551234" },
|
||||
});
|
||||
|
||||
// Drain both potential session keys to clear "gateway connected" events
|
||||
drainSystemEvents(normalizedRoute.sessionKey);
|
||||
const jidRoute = resolveAgentRoute({
|
||||
cfg: cfg as Parameters<typeof resolveAgentRoute>[0]["cfg"],
|
||||
channel: "whatsapp",
|
||||
accountId: "default",
|
||||
peer: { kind: "dm", id: "19995551234@s.whatsapp.net" },
|
||||
});
|
||||
drainSystemEvents(jidRoute.sessionKey);
|
||||
|
||||
capturedOnReaction!({
|
||||
messageId: "msg-normalized",
|
||||
emoji: "✅",
|
||||
chatJid: "19995551234@s.whatsapp.net",
|
||||
chatType: "direct",
|
||||
accountId: "default",
|
||||
senderJid: "19995551234@s.whatsapp.net",
|
||||
senderE164: "+19995551234",
|
||||
timestamp: Date.now(),
|
||||
});
|
||||
|
||||
// The reaction should land in the E.164-normalized session, not the JID-based one.
|
||||
const events = peekSystemEvents(normalizedRoute.sessionKey);
|
||||
expect(events).toHaveLength(1);
|
||||
expect(events[0]).toBe("WhatsApp reaction added: ✅ by +19995551234 msg msg-normalized");
|
||||
|
||||
// Verify it did NOT land in a JID-based session key.
|
||||
const jidEvents = peekSystemEvents(jidRoute.sessionKey);
|
||||
expect(jidEvents).toHaveLength(0);
|
||||
});
|
||||
});
|
||||
|
||||
@ -28,6 +28,7 @@ import { whatsappHeartbeatLog, whatsappLog } from "./loggers.js";
|
||||
import { buildMentionConfig } from "./mentions.js";
|
||||
import { createEchoTracker } from "./monitor/echo.js";
|
||||
import { createWebOnMessageHandler } from "./monitor/on-message.js";
|
||||
import { resolveReactionPeerId } from "./monitor/peer.js";
|
||||
import type { WebInboundReaction } from "../inbound.js";
|
||||
import type { WebChannelStatus, WebInboundMsg, WebMonitorTuning } from "./types.js";
|
||||
import { isLikelyWhatsAppCryptoError } from "./util.js";
|
||||
@ -205,13 +206,14 @@ export async function monitorWebChannel(
|
||||
onReaction: (reaction: WebInboundReaction) => {
|
||||
status.lastEventAt = Date.now();
|
||||
emitStatus();
|
||||
const peerId = resolveReactionPeerId(reaction);
|
||||
const route = resolveAgentRoute({
|
||||
cfg,
|
||||
channel: "whatsapp",
|
||||
accountId: reaction.accountId,
|
||||
peer: {
|
||||
kind: reaction.chatType === "group" ? "group" : "dm",
|
||||
id: reaction.chatJid,
|
||||
id: peerId,
|
||||
},
|
||||
});
|
||||
const senderLabel = reaction.senderE164 ?? reaction.senderJid ?? "someone";
|
||||
|
||||
@ -1,5 +1,6 @@
|
||||
import { jidToE164, normalizeE164 } from "../../../utils.js";
|
||||
import type { WebInboundMsg } from "../types.js";
|
||||
import type { WebInboundReaction } from "../../inbound/types.js";
|
||||
|
||||
export function resolvePeerId(msg: WebInboundMsg) {
|
||||
if (msg.chatType === "group") return msg.conversationId ?? msg.from;
|
||||
@ -7,3 +8,10 @@ export function resolvePeerId(msg: WebInboundMsg) {
|
||||
if (msg.from.includes("@")) return jidToE164(msg.from) ?? msg.from;
|
||||
return normalizeE164(msg.from) ?? msg.from;
|
||||
}
|
||||
|
||||
export function resolveReactionPeerId(reaction: WebInboundReaction) {
|
||||
if (reaction.chatType === "group") return reaction.chatJid;
|
||||
if (reaction.senderE164) return normalizeE164(reaction.senderE164) ?? reaction.senderE164;
|
||||
if (reaction.chatJid.includes("@")) return jidToE164(reaction.chatJid) ?? reaction.chatJid;
|
||||
return normalizeE164(reaction.chatJid) ?? reaction.chatJid;
|
||||
}
|
||||
|
||||
Loading…
Reference in New Issue
Block a user