Skip to content

Commit f2db66a

Browse files
committed
feat: scope group mention patterns by channel
1 parent 7606e1d commit f2db66a

32 files changed

Lines changed: 307 additions & 42 deletions
Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
370da2e3a4253f00c3963a3ad8b57707ea3f67a8d0d394b7d2b96db4f3413d32 config-baseline.json
2-
6a66c70d36dacf5fd1a8b7e157d1ff4812e97f518c13ebc3190509df4c269f29 config-baseline.core.json
3-
a9102c0611b8170fac37853cc31771810f31757a9e3b2c6796bbd9625f9b9206 config-baseline.channel.json
4-
923a8cac695c752e51751cc2dea185a3fbe19d0015722f7ea1909f897dfbb898 config-baseline.plugin.json
1+
8162a661edc183008a336db265a092acc90762c6c547b1383ef14fd0d381dea5 config-baseline.json
2+
5ee177382cf32c2816dca0a4e67cd6c01df1045d600b21a6e9c11639ddb10ce8 config-baseline.core.json
3+
7a7aba829deb8b54047b5c9e7dd3f3c9eade721e2f728db339c4ea99b77a162e config-baseline.channel.json
4+
e6a1d6f51f0d9c04bd92d51deebfaca8c7917dd28d7998d225c0074e0a095348 config-baseline.plugin.json
Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,2 @@
1-
3cc84cf3d30697d541ba98a5c1835784a4254a9193e51b009372e1620948e430 plugin-sdk-api-baseline.json
2-
515c9e2972f0d79dbed27ffae815a96d432a341005046d603a82a235c4108340 plugin-sdk-api-baseline.jsonl
1+
34d396bf8f1b2963884256e87c8879a378e2ce7c8064ae0c30d734085a305dd6 plugin-sdk-api-baseline.json
2+
bf3e94dcccaf169811990dfd058a16ace8ce2ab44ea9eac6042b525b6d8baf5f plugin-sdk-api-baseline.jsonl

docs/channels/groups.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -362,6 +362,8 @@ Replying to a bot message counts as an implicit mention when the channel support
362362
<Accordion title="Mention gating notes">
363363
- `mentionPatterns` are case-insensitive safe regex patterns; invalid patterns and unsafe nested-repetition forms are ignored.
364364
- Surfaces that provide explicit mentions still pass; patterns are a fallback.
365+
- `channels.<channel>.mentionPatterns.mode: "deny"` disables configured mention patterns by default for that channel; opt selected conversations back in with `allowIn`.
366+
- `channels.<channel>.mentionPatterns.denyIn` disables configured mention patterns for specific conversation IDs while native platform @mentions still pass.
365367
- Per-agent override: `agents.list[].groupChat.mentionPatterns` (useful when multiple agents share a group).
366368
- Mention gating is only enforced when mention detection is possible (native mentions or `mentionPatterns` are configured).
367369
- Allowlisting a group or sender does not disable mention gating; set that group's `requireMention` to `false` when all messages should trigger.

extensions/discord/src/monitor/message-handler.preflight.ts

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -425,7 +425,11 @@ export async function preflightDiscordMessage(
425425
logVerbose(`discord: drop bound-thread bot system message ${message.id}`);
426426
return null;
427427
}
428-
const mentionRegexes = buildMentionRegexes(params.cfg, effectiveRoute.agentId);
428+
const mentionRegexes = buildMentionRegexes(params.cfg, effectiveRoute.agentId, {
429+
provider: "discord",
430+
conversationId: messageChannelId,
431+
providerPolicy: params.discordConfig?.mentionPatterns,
432+
});
429433
const explicitlyMentioned = Boolean(
430434
botId && message.mentionedUsers?.some((user: User) => user.id === botId),
431435
);

extensions/matrix/src/config-schema.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ import {
55
ContextVisibilityModeSchema,
66
GroupPolicySchema,
77
MarkdownConfigSchema,
8+
MentionPatternsPolicySchema,
89
ToolPolicySchema,
910
} from "openclaw/plugin-sdk/channel-config-schema";
1011
import { buildSecretInputSchema } from "openclaw/plugin-sdk/secret-input";
@@ -121,6 +122,7 @@ export const MatrixConfigSchema = z.object({
121122
allowBots: z.union([z.boolean(), z.literal("mentions")]).optional(),
122123
botLoopProtection: botLoopProtectionSchema,
123124
groupPolicy: GroupPolicySchema.optional(),
125+
mentionPatterns: MentionPatternsPolicySchema.optional(),
124126
contextVisibility: ContextVisibilityModeSchema.optional(),
125127
blockStreaming: z.boolean().optional(),
126128
streaming: z

extensions/matrix/src/matrix/monitor/handler.ts

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -965,7 +965,11 @@ export function createMatrixRoomMessageHandler(params: MatrixMonitorHandlerParam
965965
resolveAgentRoute: core.channel.routing.resolveAgentRoute,
966966
});
967967
const hasExplicitSessionBinding = _configuredBinding !== null || _runtimeBindingId !== null;
968-
const agentMentionRegexes = core.channel.mentions.buildMentionRegexes(cfg, _route.agentId);
968+
const agentMentionRegexes = core.channel.mentions.buildMentionRegexes(cfg, _route.agentId, {
969+
provider: "matrix",
970+
conversationId: roomId,
971+
providerPolicy: accountConfig?.mentionPatterns,
972+
});
969973
const selfDisplayName = content.formatted_body
970974
? await getMemberDisplayName(roomId, selfUserId).catch(() => undefined)
971975
: undefined;

extensions/matrix/src/types.ts

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,7 @@
1-
import type { ChannelBotLoopProtectionConfig } from "openclaw/plugin-sdk/config-contracts";
1+
import type {
2+
ChannelBotLoopProtectionConfig,
3+
MentionPatternsPolicyConfig,
4+
} from "openclaw/plugin-sdk/config-contracts";
25
import type {
36
ContextVisibilityMode,
47
DmPolicy,
@@ -153,6 +156,8 @@ export type MatrixConfig = {
153156
botLoopProtection?: ChannelBotLoopProtectionConfig;
154157
/** Group message policy (default: allowlist). */
155158
groupPolicy?: GroupPolicy;
159+
/** Scope configured groupChat mentionPatterns to selected Matrix room IDs. */
160+
mentionPatterns?: MentionPatternsPolicyConfig;
156161
/** Supplemental context visibility policy (all|allowlist|allowlist_quote). */
157162
contextVisibility?: ContextVisibilityMode;
158163
/**

extensions/slack/src/monitor/message-handler/prepare.ts

Lines changed: 16 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -205,8 +205,14 @@ async function restoreSlackAssistantThreadContextFromMetadata(params: {
205205
function resolveCachedMentionRegexes(
206206
ctx: SlackMonitorContext,
207207
agentId: string | undefined,
208+
options?: Parameters<typeof buildMentionRegexes>[2],
208209
): RegExp[] {
209-
const key = normalizeOptionalString(agentId) ?? "__default__";
210+
const key = [
211+
normalizeOptionalString(agentId) ?? "__default__",
212+
normalizeOptionalString(options?.provider),
213+
normalizeOptionalString(options?.conversationId ?? undefined),
214+
options?.providerPolicy ? JSON.stringify(options.providerPolicy) : "",
215+
].join("\u001f");
210216
let byAgent = mentionRegexCache.get(ctx);
211217
if (!byAgent) {
212218
byAgent = new Map<string, RegExp[]>();
@@ -216,7 +222,7 @@ function resolveCachedMentionRegexes(
216222
if (cached) {
217223
return cached;
218224
}
219-
const built = buildMentionRegexes(ctx.cfg, agentId);
225+
const built = buildMentionRegexes(ctx.cfg, agentId, options);
220226
byAgent.set(key, built);
221227
return built;
222228
}
@@ -721,7 +727,13 @@ export async function prepareSlackMessage(params: {
721727
canResolveExplicit: Boolean(ctx.botUserId),
722728
},
723729
}));
724-
let mentionRegexes = resolveCachedMentionRegexes(ctx, routing.route.agentId);
730+
const buildPolicyMentionRegexes = (agentId: string | undefined) =>
731+
resolveCachedMentionRegexes(ctx, agentId, {
732+
provider: "slack",
733+
conversationId: message.channel,
734+
providerPolicy: account.config.mentionPatterns,
735+
});
736+
let mentionRegexes = buildPolicyMentionRegexes(routing.route.agentId);
725737
let wasMentioned = resolveWasMentioned(mentionRegexes);
726738
const hasBoundSession = Boolean(
727739
routing.runtimeBoundSessionKey || routing.configuredBindingSessionKey,
@@ -746,7 +758,7 @@ export async function prepareSlackMessage(params: {
746758
seedTopLevelRoomThread: true,
747759
assistantThreadTs: assistantThreadContext?.threadTs,
748760
});
749-
mentionRegexes = resolveCachedMentionRegexes(ctx, routing.route.agentId);
761+
mentionRegexes = buildPolicyMentionRegexes(routing.route.agentId);
750762
wasMentioned = resolveWasMentioned(mentionRegexes);
751763
}
752764
const {

extensions/telegram/src/bot-message-context.body.ts

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ import {
55
logInboundDrop,
66
matchesMentionWithExplicit,
77
resolveInboundMentionDecision,
8+
type BuildMentionRegexesOptions,
89
type NormalizedLocation,
910
} from "openclaw/plugin-sdk/channel-inbound";
1011
import { resolveChannelGroupPolicy } from "openclaw/plugin-sdk/channel-policy";
@@ -148,6 +149,7 @@ export async function resolveTelegramInboundBody(params: {
148149
effectiveDmAllow: NormalizedAllowFrom;
149150
groupConfig?: TelegramGroupConfig | TelegramDirectConfig;
150151
topicConfig?: TelegramTopicConfig;
152+
providerMentionPatterns?: BuildMentionRegexesOptions["providerPolicy"];
151153
requireMention?: boolean;
152154
options?: TelegramMessageContextOptions;
153155
groupHistories: Map<string, HistoryEntry[]>;
@@ -173,14 +175,19 @@ export async function resolveTelegramInboundBody(params: {
173175
effectiveDmAllow,
174176
groupConfig,
175177
topicConfig,
178+
providerMentionPatterns,
176179
requireMention,
177180
options,
178181
groupHistories,
179182
historyLimit,
180183
logger,
181184
} = params;
182185
const botUsername = normalizeOptionalLowercaseString(primaryCtx.me?.username);
183-
const mentionRegexes = buildMentionRegexes(cfg, routeAgentId);
186+
const mentionRegexes = buildMentionRegexes(cfg, routeAgentId, {
187+
provider: "telegram",
188+
conversationId: isGroup ? buildTelegramGroupPeerId(chatId, resolvedThreadId) : String(chatId),
189+
providerPolicy: providerMentionPatterns,
190+
});
184191
const messageTextParts = getTelegramTextParts(msg);
185192
const allowForCommands = isGroup ? effectiveGroupAllow : effectiveDmAllow;
186193
const useAccessGroups = cfg.commands?.useAccessGroups !== false;

extensions/telegram/src/bot-message-context.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -471,6 +471,7 @@ export const buildTelegramMessageContext = async ({
471471
effectiveDmAllow: dmAllow.effectiveAllow,
472472
groupConfig,
473473
topicConfig,
474+
providerMentionPatterns: cfg.channels?.telegram?.accounts?.[account.accountId]?.mentionPatterns,
474475
requireMention,
475476
options,
476477
groupHistories,

0 commit comments

Comments
 (0)