refactor: unify group allowlist policy

This commit is contained in:
Peter Steinberger
2026-01-06 04:27:21 +01:00
parent b1bb3ff6a6
commit ca8f66f844
10 changed files with 298 additions and 71 deletions

View File

@@ -169,6 +169,36 @@ describe("monitorIMessageProvider", () => {
expect(replyMock).toHaveBeenCalled();
});
it("blocks group messages when imessage.groups is set without a wildcard", async () => {
config = {
...config,
imessage: { groups: { "99": { requireMention: false } } },
};
const run = monitorIMessageProvider();
await waitForSubscribe();
notificationHandler?.({
method: "message",
params: {
message: {
id: 13,
chat_id: 123,
sender: "+15550001111",
is_from_me: false,
text: "@clawd hello",
is_group: true,
},
},
});
await flush();
closeResolve?.();
await run;
expect(replyMock).not.toHaveBeenCalled();
expect(sendMock).not.toHaveBeenCalled();
});
it("prefixes tool and final replies with responsePrefix", async () => {
config = {
...config,

View File

@@ -9,6 +9,10 @@ import { createReplyDispatcher } from "../auto-reply/reply/reply-dispatcher.js";
import { getReplyFromConfig } from "../auto-reply/reply.js";
import type { ReplyPayload } from "../auto-reply/types.js";
import { loadConfig } from "../config/config.js";
import {
resolveProviderGroupPolicy,
resolveProviderGroupRequireMention,
} from "../config/group-policy.js";
import { resolveStorePath, updateLastRoute } from "../config/sessions.js";
import { danger, logVerbose, shouldLogVerbose } from "../globals.js";
import { mediaKindFromMime } from "../media/constants.js";
@@ -71,24 +75,6 @@ function resolveAllowFrom(opts: MonitorIMessageOpts): string[] {
return raw.map((entry) => String(entry).trim()).filter(Boolean);
}
function resolveGroupRequireMention(
cfg: ReturnType<typeof loadConfig>,
opts: MonitorIMessageOpts,
chatId?: number | null,
): boolean {
if (typeof opts.requireMention === "boolean") return opts.requireMention;
const groupId = chatId != null ? String(chatId) : undefined;
if (groupId) {
const groupConfig = cfg.imessage?.groups?.[groupId];
if (typeof groupConfig?.requireMention === "boolean") {
return groupConfig.requireMention;
}
}
const groupDefault = cfg.imessage?.groups?.["*"]?.requireMention;
if (typeof groupDefault === "boolean") return groupDefault;
return true;
}
async function deliverReplies(params: {
replies: ReplyPayload[];
target: string;
@@ -152,6 +138,21 @@ export async function monitorIMessageProvider(
const isGroup = Boolean(message.is_group);
if (isGroup && !chatId) return;
const groupId = isGroup ? String(chatId) : undefined;
if (isGroup) {
const groupPolicy = resolveProviderGroupPolicy({
cfg,
surface: "imessage",
groupId,
});
if (groupPolicy.allowlistEnabled && !groupPolicy.allowed) {
logVerbose(
`imessage: skipping group message (${groupId ?? "unknown"}) not in allowlist`,
);
return;
}
}
const commandAuthorized = isAllowedIMessageSender({
allowFrom,
sender,
@@ -168,7 +169,13 @@ export async function monitorIMessageProvider(
const mentioned = isGroup
? matchesMentionPatterns(messageText, mentionRegexes)
: true;
const requireMention = resolveGroupRequireMention(cfg, opts, chatId);
const requireMention = resolveProviderGroupRequireMention({
cfg,
surface: "imessage",
groupId,
requireMentionOverride: opts.requireMention,
overrideOrder: "before-config",
});
const canDetectMention = mentionRegexes.length > 0;
const shouldBypassMention =
isGroup &&