fix(whatsapp): preserve group broadcast ack checks

This commit is contained in:
Roger Deng
2026-04-23 22:24:59 +08:00
committed by Marcus Castro
parent 176b70d57b
commit 21f8a0ee9e
2 changed files with 64 additions and 2 deletions

View File

@@ -4,6 +4,7 @@ const events: string[] = [];
const transcribeFirstAudioMock = vi.fn();
const maybeSendAckReactionMock = vi.fn();
const processMessageMock = vi.fn();
const maybeBroadcastMessageMock = vi.fn();
vi.mock("./audio-preflight.runtime.js", () => ({
transcribeFirstAudio: (...args: unknown[]) => transcribeFirstAudioMock(...args),
@@ -18,7 +19,7 @@ vi.mock("./process-message.js", () => ({
}));
vi.mock("./broadcast.js", () => ({
maybeBroadcastMessage: vi.fn(async () => false),
maybeBroadcastMessage: (...args: unknown[]) => maybeBroadcastMessageMock(...args),
}));
vi.mock("./group-gating.js", () => ({
@@ -84,6 +85,17 @@ function makeAudioMsg(): WebInboundMsg {
} as WebInboundMsg;
}
function makeGroupAudioMsg(): WebInboundMsg {
return {
...makeAudioMsg(),
from: "1203630@g.us",
chatId: "1203630@g.us",
chatType: "group",
conversationId: "1203630@g.us",
wasMentioned: false,
} as WebInboundMsg;
}
function makeEchoTracker() {
return {
has: () => false,
@@ -96,6 +108,8 @@ function makeEchoTracker() {
describe("createWebOnMessageHandler audio preflight", () => {
beforeEach(() => {
events.length = 0;
maybeBroadcastMessageMock.mockReset();
maybeBroadcastMessageMock.mockImplementation(async () => false);
maybeSendAckReactionMock.mockReset();
maybeSendAckReactionMock.mockImplementation(async () => {
events.push("ack");
@@ -149,6 +163,7 @@ describe("createWebOnMessageHandler audio preflight", () => {
});
it("skips early DM ack/preflight when access-control was not explicitly passed through", async () => {
const handler = createWebOnMessageHandler({
cfg: {
channels: {
@@ -188,4 +203,48 @@ describe("createWebOnMessageHandler audio preflight", () => {
}),
);
});
it("preserves per-agent ack checks for group broadcast voice notes", async () => {
maybeBroadcastMessageMock.mockImplementation(
async (params: { ackAlreadySent?: boolean; preflightAudioTranscript?: string | null }) => {
expect(params.preflightAudioTranscript).toBe("transcribed voice note");
expect(params.ackAlreadySent).toBeUndefined();
return true;
},
);
const handler = createWebOnMessageHandler({
cfg: {
channels: {
whatsapp: {
ackReaction: { enabled: true },
},
},
broadcast: {
"1203630@g.us": ["main", "backup"],
},
} as never,
verbose: false,
connectionId: "conn-1",
maxMediaBytes: 1024 * 1024,
groupHistoryLimit: 20,
groupHistories: new Map(),
groupMemberNames: new Map(),
echoTracker: makeEchoTracker() as never,
backgroundTasks: new Set(),
replyResolver: vi.fn() as never,
replyLogger: {
info: () => {},
warn: () => {},
debug: () => {},
error: () => {},
} as never,
baseMentionConfig: {} as never,
account: { authDir: "/tmp/auth", accountId: "default" },
});
await handler(makeGroupAudioMsg());
expect(events).toEqual(["ack", "stt"]);
expect(processMessageMock).not.toHaveBeenCalled();
});
});

View File

@@ -228,7 +228,10 @@ export function createWebOnMessageHandler(params: {
groupHistoryKey,
groupHistories: params.groupHistories,
...(preflightAudioTranscript !== undefined ? { preflightAudioTranscript } : {}),
...(ackAlreadySent ? { ackAlreadySent: true } : {}),
// Group ack eligibility depends on the target agent/session, so a
// preflight ack attempt on the base route must not suppress downstream
// per-agent checks during broadcast fan-out.
...(ackAlreadySent && msg.chatType !== "group" ? { ackAlreadySent: true } : {}),
processMessage: (m, r, k, opts) => processForRoute(m, r, k, opts),
})
) {