fix: honor whatsapp per-group mention overrides

This commit is contained in:
Peter Steinberger
2026-01-03 17:51:10 +01:00
parent dd6b9b510b
commit 591773715e
3 changed files with 69 additions and 4 deletions

View File

@@ -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.

View File

@@ -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<void>)
| undefined;
const listenerFactory = async (opts: {
onMessage: (
msg: import("./inbound.js").WebInboundMessage,
) => Promise<void>;
}) => {
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);

View File

@@ -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);