From 591773715ebc2d2cdc9edc32eb0c973e705a1486 Mon Sep 17 00:00:00 2001 From: Peter Steinberger Date: Sat, 3 Jan 2026 17:51:10 +0100 Subject: [PATCH] fix: honor whatsapp per-group mention overrides --- CHANGELOG.md | 2 ++ src/web/auto-reply.test.ts | 52 ++++++++++++++++++++++++++++++++++++++ src/web/auto-reply.ts | 19 +++++++++++--- 3 files changed, 69 insertions(+), 4 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 9fb2244e7..efe9c022a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -9,6 +9,7 @@ ### Features - Gateway: support `gateway.port` + `CLAWDIS_GATEWAY_PORT` across CLI, TUI, and macOS app. - UI: centralize tool display metadata and show action/detail summaries across Web Chat, SwiftUI, Android, and the TUI. +- Control UI: support configurable base paths (`gateway.controlUi.basePath`) for hosting under URL prefixes. - Onboarding: shared wizard engine powering CLI + macOS via gateway wizard RPC. - Config: expose schema + UI hints for generic config forms (Web UI + future clients). @@ -29,6 +30,7 @@ - Build: fix regex literal in tool-meta path detection (watch build error). - Build: require AVX2 Bun for x86_64 relay packaging (reject baseline builds). - Auto-reply: add run-level telemetry + typing TTL guardrails to diagnose stuck replies. +- WhatsApp: honor per-group mention gating overrides when group ids are stored as session keys. ### Docs - Skills: add Sheets/Docs examples to gog skill (#128) — thanks @mbelinky. diff --git a/src/web/auto-reply.test.ts b/src/web/auto-reply.test.ts index 8d1a1d1c0..e6668ac87 100644 --- a/src/web/auto-reply.test.ts +++ b/src/web/auto-reply.test.ts @@ -1056,6 +1056,58 @@ describe("web auto-reply", () => { resetLoadConfigMock(); }); + it("honors per-group mention overrides when conversationId uses session key", async () => { + const sendMedia = vi.fn(); + const reply = vi.fn().mockResolvedValue(undefined); + const sendComposing = vi.fn(); + const resolver = vi.fn().mockResolvedValue({ text: "ok" }); + + setLoadConfigMock(() => ({ + whatsapp: { + allowFrom: ["*"], + groups: { + "*": { requireMention: true }, + "123@g.us": { requireMention: false }, + }, + }, + routing: { groupChat: { mentionPatterns: ["@clawd"] } }, + })); + + let capturedOnMessage: + | ((msg: import("./inbound.js").WebInboundMessage) => Promise) + | undefined; + const listenerFactory = async (opts: { + onMessage: ( + msg: import("./inbound.js").WebInboundMessage, + ) => Promise; + }) => { + capturedOnMessage = opts.onMessage; + return { close: vi.fn() }; + }; + + await monitorWebProvider(false, listenerFactory, false, resolver); + expect(capturedOnMessage).toBeDefined(); + + await capturedOnMessage?.({ + body: "hello group", + from: "whatsapp:group:123@g.us", + conversationId: "whatsapp:group:123@g.us", + chatId: "123@g.us", + chatType: "group", + to: "+2", + id: "g-per-group-session-key", + senderE164: "+111", + senderName: "Alice", + selfE164: "+999", + sendComposing, + reply, + sendMedia, + }); + + expect(resolver).toHaveBeenCalledTimes(1); + resetLoadConfigMock(); + }); + it("supports always-on group activation with silent token and preserves history", async () => { const sendMedia = vi.fn(); const reply = vi.fn().mockResolvedValue(undefined); diff --git a/src/web/auto-reply.ts b/src/web/auto-reply.ts index ce587e089..814ee72fd 100644 --- a/src/web/auto-reply.ts +++ b/src/web/auto-reply.ts @@ -16,6 +16,7 @@ import { loadConfig } from "../config/config.js"; import { DEFAULT_IDLE_MINUTES, loadSessionStore, + resolveGroupSessionKey, resolveSessionKey, resolveStorePath, saveSessionStore, @@ -813,8 +814,16 @@ export async function monitorWebProvider( .join(", "); }; + const resolveGroupResolution = (conversationId: string) => + resolveGroupSessionKey({ + From: conversationId, + ChatType: "group", + Surface: "whatsapp", + }); + const resolveGroupRequireMentionFor = (conversationId: string) => { - const groupConfig = cfg.whatsapp?.groups?.[conversationId]; + const groupId = resolveGroupResolution(conversationId)?.id ?? conversationId; + const groupConfig = cfg.whatsapp?.groups?.[groupId]; if (typeof groupConfig?.requireMention === "boolean") { return groupConfig.requireMention; } @@ -824,9 +833,11 @@ export async function monitorWebProvider( }; const resolveGroupActivationFor = (conversationId: string) => { - const key = conversationId.startsWith("group:") - ? conversationId - : `whatsapp:group:${conversationId}`; + const key = + resolveGroupResolution(conversationId)?.key ?? + (conversationId.startsWith("group:") + ? conversationId + : `whatsapp:group:${conversationId}`); const store = loadSessionStore(sessionStorePath); const entry = store[key]; const requireMention = resolveGroupRequireMentionFor(conversationId);