fix: tighten group elevated targeting

This commit is contained in:
Peter Steinberger
2026-01-08 22:57:08 +01:00
parent cda2025c49
commit 014667e00b
32 changed files with 338 additions and 57 deletions

View File

@@ -1132,6 +1132,81 @@ describe("web auto-reply", () => {
expect(payload.Body).toContain("[from: Bob (+222)]");
});
it("uses per-agent mention patterns for group gating", 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 } },
},
routing: {
groupChat: { mentionPatterns: ["@global"] },
agents: {
work: { mentionPatterns: ["@workbot"] },
},
bindings: [
{
agentId: "work",
match: { provider: "whatsapp", peer: { kind: "group", id: "123@g.us" } },
},
],
},
}));
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: "@global ping",
from: "123@g.us",
conversationId: "123@g.us",
chatId: "123@g.us",
chatType: "group",
to: "+2",
id: "g1",
senderE164: "+111",
senderName: "Alice",
selfE164: "+999",
sendComposing,
reply,
sendMedia,
});
expect(resolver).not.toHaveBeenCalled();
await capturedOnMessage?.({
body: "@workbot ping",
from: "123@g.us",
conversationId: "123@g.us",
chatId: "123@g.us",
chatType: "group",
to: "+2",
id: "g2",
senderE164: "+222",
senderName: "Bob",
selfE164: "+999",
sendComposing,
reply,
sendMedia,
});
expect(resolver).toHaveBeenCalledTimes(1);
});
it("allows group messages when whatsapp groups default disables mention gating", async () => {
const sendMedia = vi.fn();
const reply = vi.fn().mockResolvedValue(undefined);

View File

@@ -162,8 +162,11 @@ type MentionConfig = {
allowFrom?: Array<string | number>;
};
function buildMentionConfig(cfg: ReturnType<typeof loadConfig>): MentionConfig {
const mentionRegexes = buildMentionRegexes(cfg);
function buildMentionConfig(
cfg: ReturnType<typeof loadConfig>,
agentId?: string,
): MentionConfig {
const mentionRegexes = buildMentionRegexes(cfg, agentId);
return { mentionRegexes, allowFrom: cfg.whatsapp?.allowFrom };
}
@@ -793,7 +796,9 @@ export async function monitorWebProvider(
tuning.heartbeatSeconds,
);
const reconnectPolicy = resolveReconnectPolicy(cfg, tuning.reconnect);
const mentionConfig = buildMentionConfig(cfg);
const resolveMentionConfig = (agentId?: string) =>
buildMentionConfig(cfg, agentId);
const baseMentionConfig = resolveMentionConfig();
const groupHistoryLimit =
cfg.routing?.groupChat?.historyLimit ?? DEFAULT_GROUP_HISTORY_LIMIT;
const groupHistories = new Map<
@@ -913,7 +918,7 @@ export async function monitorWebProvider(
};
const resolveOwnerList = (selfE164?: string | null) => {
const allowFrom = mentionConfig.allowFrom;
const allowFrom = baseMentionConfig.allowFrom;
const raw =
Array.isArray(allowFrom) && allowFrom.length > 0
? allowFrom
@@ -943,9 +948,13 @@ export async function monitorWebProvider(
);
};
const stripMentionsForCommand = (text: string, selfE164?: string | null) => {
const stripMentionsForCommand = (
text: string,
mentionRegexes: RegExp[],
selfE164?: string | null,
) => {
let result = text;
for (const re of mentionConfig.mentionRegexes) {
for (const re of mentionRegexes) {
result = result.replace(re, " ");
}
if (selfE164) {
@@ -1362,7 +1371,12 @@ export async function monitorWebProvider(
});
}
noteGroupMember(groupHistoryKey, msg.senderE164, msg.senderName);
const commandBody = stripMentionsForCommand(msg.body, msg.selfE164);
const mentionConfig = resolveMentionConfig(route.agentId);
const commandBody = stripMentionsForCommand(
msg.body,
mentionConfig.mentionRegexes,
msg.selfE164,
);
const activationCommand = parseActivationCommand(commandBody);
const isOwner = isOwnerSender(msg);
const statusCommand = isStatusCommand(commandBody);