mirror of
https://github.com/openclaw/openclaw.git
synced 2026-06-03 17:54:04 +00:00
feat: scope group mention patterns by channel
Provider-scoped configured regex mention patterns for Discord, Matrix, Slack, Telegram, and WhatsApp. Native platform mentions keep their existing behavior, and unsupported channels do not opt into the new regex policy path. The new policy supports per-channel allow/deny routing through mentionPatterns.mode with allowIn and denyIn so group auto-reply regexes can be limited without broad global blast radius. Refs #70864. Supersedes #87200. Thanks @patrick-slimelab.
This commit is contained in:
@@ -1,4 +1,4 @@
|
||||
370da2e3a4253f00c3963a3ad8b57707ea3f67a8d0d394b7d2b96db4f3413d32 config-baseline.json
|
||||
6a66c70d36dacf5fd1a8b7e157d1ff4812e97f518c13ebc3190509df4c269f29 config-baseline.core.json
|
||||
a9102c0611b8170fac37853cc31771810f31757a9e3b2c6796bbd9625f9b9206 config-baseline.channel.json
|
||||
923a8cac695c752e51751cc2dea185a3fbe19d0015722f7ea1909f897dfbb898 config-baseline.plugin.json
|
||||
8162a661edc183008a336db265a092acc90762c6c547b1383ef14fd0d381dea5 config-baseline.json
|
||||
5ee177382cf32c2816dca0a4e67cd6c01df1045d600b21a6e9c11639ddb10ce8 config-baseline.core.json
|
||||
7a7aba829deb8b54047b5c9e7dd3f3c9eade721e2f728db339c4ea99b77a162e config-baseline.channel.json
|
||||
e6a1d6f51f0d9c04bd92d51deebfaca8c7917dd28d7998d225c0074e0a095348 config-baseline.plugin.json
|
||||
|
||||
@@ -1,2 +1,2 @@
|
||||
3cc84cf3d30697d541ba98a5c1835784a4254a9193e51b009372e1620948e430 plugin-sdk-api-baseline.json
|
||||
515c9e2972f0d79dbed27ffae815a96d432a341005046d603a82a235c4108340 plugin-sdk-api-baseline.jsonl
|
||||
34d396bf8f1b2963884256e87c8879a378e2ce7c8064ae0c30d734085a305dd6 plugin-sdk-api-baseline.json
|
||||
bf3e94dcccaf169811990dfd058a16ace8ce2ab44ea9eac6042b525b6d8baf5f plugin-sdk-api-baseline.jsonl
|
||||
|
||||
@@ -362,6 +362,8 @@ Replying to a bot message counts as an implicit mention when the channel support
|
||||
<Accordion title="Mention gating notes">
|
||||
- `mentionPatterns` are case-insensitive safe regex patterns; invalid patterns and unsafe nested-repetition forms are ignored.
|
||||
- Surfaces that provide explicit mentions still pass; patterns are a fallback.
|
||||
- `channels.<channel>.mentionPatterns.mode: "deny"` disables configured mention patterns by default for that channel; opt selected conversations back in with `allowIn`.
|
||||
- `channels.<channel>.mentionPatterns.denyIn` disables configured mention patterns for specific conversation IDs while native platform @mentions still pass.
|
||||
- Per-agent override: `agents.list[].groupChat.mentionPatterns` (useful when multiple agents share a group).
|
||||
- Mention gating is only enforced when mention detection is possible (native mentions or `mentionPatterns` are configured).
|
||||
- Allowlisting a group or sender does not disable mention gating; set that group's `requireMention` to `false` when all messages should trigger.
|
||||
|
||||
@@ -425,7 +425,11 @@ export async function preflightDiscordMessage(
|
||||
logVerbose(`discord: drop bound-thread bot system message ${message.id}`);
|
||||
return null;
|
||||
}
|
||||
const mentionRegexes = buildMentionRegexes(params.cfg, effectiveRoute.agentId);
|
||||
const mentionRegexes = buildMentionRegexes(params.cfg, effectiveRoute.agentId, {
|
||||
provider: "discord",
|
||||
conversationId: messageChannelId,
|
||||
providerPolicy: params.discordConfig?.mentionPatterns,
|
||||
});
|
||||
const explicitlyMentioned = Boolean(
|
||||
botId && message.mentionedUsers?.some((user: User) => user.id === botId),
|
||||
);
|
||||
|
||||
@@ -5,6 +5,7 @@ import {
|
||||
ContextVisibilityModeSchema,
|
||||
GroupPolicySchema,
|
||||
MarkdownConfigSchema,
|
||||
MentionPatternsPolicySchema,
|
||||
ToolPolicySchema,
|
||||
} from "openclaw/plugin-sdk/channel-config-schema";
|
||||
import { buildSecretInputSchema } from "openclaw/plugin-sdk/secret-input";
|
||||
@@ -121,6 +122,7 @@ export const MatrixConfigSchema = z.object({
|
||||
allowBots: z.union([z.boolean(), z.literal("mentions")]).optional(),
|
||||
botLoopProtection: botLoopProtectionSchema,
|
||||
groupPolicy: GroupPolicySchema.optional(),
|
||||
mentionPatterns: MentionPatternsPolicySchema.optional(),
|
||||
contextVisibility: ContextVisibilityModeSchema.optional(),
|
||||
blockStreaming: z.boolean().optional(),
|
||||
streaming: z
|
||||
|
||||
@@ -965,7 +965,11 @@ export function createMatrixRoomMessageHandler(params: MatrixMonitorHandlerParam
|
||||
resolveAgentRoute: core.channel.routing.resolveAgentRoute,
|
||||
});
|
||||
const hasExplicitSessionBinding = _configuredBinding !== null || _runtimeBindingId !== null;
|
||||
const agentMentionRegexes = core.channel.mentions.buildMentionRegexes(cfg, _route.agentId);
|
||||
const agentMentionRegexes = core.channel.mentions.buildMentionRegexes(cfg, _route.agentId, {
|
||||
provider: "matrix",
|
||||
conversationId: roomId,
|
||||
providerPolicy: accountConfig?.mentionPatterns,
|
||||
});
|
||||
const selfDisplayName = content.formatted_body
|
||||
? await getMemberDisplayName(roomId, selfUserId).catch(() => undefined)
|
||||
: undefined;
|
||||
|
||||
@@ -1,4 +1,7 @@
|
||||
import type { ChannelBotLoopProtectionConfig } from "openclaw/plugin-sdk/config-contracts";
|
||||
import type {
|
||||
ChannelBotLoopProtectionConfig,
|
||||
MentionPatternsPolicyConfig,
|
||||
} from "openclaw/plugin-sdk/config-contracts";
|
||||
import type {
|
||||
ContextVisibilityMode,
|
||||
DmPolicy,
|
||||
@@ -153,6 +156,8 @@ export type MatrixConfig = {
|
||||
botLoopProtection?: ChannelBotLoopProtectionConfig;
|
||||
/** Group message policy (default: allowlist). */
|
||||
groupPolicy?: GroupPolicy;
|
||||
/** Scope configured groupChat mentionPatterns to selected Matrix room IDs. */
|
||||
mentionPatterns?: MentionPatternsPolicyConfig;
|
||||
/** Supplemental context visibility policy (all|allowlist|allowlist_quote). */
|
||||
contextVisibility?: ContextVisibilityMode;
|
||||
/**
|
||||
|
||||
@@ -205,8 +205,14 @@ async function restoreSlackAssistantThreadContextFromMetadata(params: {
|
||||
function resolveCachedMentionRegexes(
|
||||
ctx: SlackMonitorContext,
|
||||
agentId: string | undefined,
|
||||
options?: Parameters<typeof buildMentionRegexes>[2],
|
||||
): RegExp[] {
|
||||
const key = normalizeOptionalString(agentId) ?? "__default__";
|
||||
const key = [
|
||||
normalizeOptionalString(agentId) ?? "__default__",
|
||||
normalizeOptionalString(options?.provider),
|
||||
normalizeOptionalString(options?.conversationId ?? undefined),
|
||||
options?.providerPolicy ? JSON.stringify(options.providerPolicy) : "",
|
||||
].join("\u001f");
|
||||
let byAgent = mentionRegexCache.get(ctx);
|
||||
if (!byAgent) {
|
||||
byAgent = new Map<string, RegExp[]>();
|
||||
@@ -216,7 +222,7 @@ function resolveCachedMentionRegexes(
|
||||
if (cached) {
|
||||
return cached;
|
||||
}
|
||||
const built = buildMentionRegexes(ctx.cfg, agentId);
|
||||
const built = buildMentionRegexes(ctx.cfg, agentId, options);
|
||||
byAgent.set(key, built);
|
||||
return built;
|
||||
}
|
||||
@@ -721,7 +727,13 @@ export async function prepareSlackMessage(params: {
|
||||
canResolveExplicit: Boolean(ctx.botUserId),
|
||||
},
|
||||
}));
|
||||
let mentionRegexes = resolveCachedMentionRegexes(ctx, routing.route.agentId);
|
||||
const buildPolicyMentionRegexes = (agentId: string | undefined) =>
|
||||
resolveCachedMentionRegexes(ctx, agentId, {
|
||||
provider: "slack",
|
||||
conversationId: message.channel,
|
||||
providerPolicy: account.config.mentionPatterns,
|
||||
});
|
||||
let mentionRegexes = buildPolicyMentionRegexes(routing.route.agentId);
|
||||
let wasMentioned = resolveWasMentioned(mentionRegexes);
|
||||
const hasBoundSession = Boolean(
|
||||
routing.runtimeBoundSessionKey || routing.configuredBindingSessionKey,
|
||||
@@ -746,7 +758,7 @@ export async function prepareSlackMessage(params: {
|
||||
seedTopLevelRoomThread: true,
|
||||
assistantThreadTs: assistantThreadContext?.threadTs,
|
||||
});
|
||||
mentionRegexes = resolveCachedMentionRegexes(ctx, routing.route.agentId);
|
||||
mentionRegexes = buildPolicyMentionRegexes(routing.route.agentId);
|
||||
wasMentioned = resolveWasMentioned(mentionRegexes);
|
||||
}
|
||||
const {
|
||||
|
||||
@@ -5,6 +5,7 @@ import {
|
||||
logInboundDrop,
|
||||
matchesMentionWithExplicit,
|
||||
resolveInboundMentionDecision,
|
||||
type BuildMentionRegexesOptions,
|
||||
type NormalizedLocation,
|
||||
} from "openclaw/plugin-sdk/channel-inbound";
|
||||
import { resolveChannelGroupPolicy } from "openclaw/plugin-sdk/channel-policy";
|
||||
@@ -148,6 +149,7 @@ export async function resolveTelegramInboundBody(params: {
|
||||
effectiveDmAllow: NormalizedAllowFrom;
|
||||
groupConfig?: TelegramGroupConfig | TelegramDirectConfig;
|
||||
topicConfig?: TelegramTopicConfig;
|
||||
providerMentionPatterns?: BuildMentionRegexesOptions["providerPolicy"];
|
||||
requireMention?: boolean;
|
||||
options?: TelegramMessageContextOptions;
|
||||
groupHistories: Map<string, HistoryEntry[]>;
|
||||
@@ -173,6 +175,7 @@ export async function resolveTelegramInboundBody(params: {
|
||||
effectiveDmAllow,
|
||||
groupConfig,
|
||||
topicConfig,
|
||||
providerMentionPatterns,
|
||||
requireMention,
|
||||
options,
|
||||
groupHistories,
|
||||
@@ -180,7 +183,11 @@ export async function resolveTelegramInboundBody(params: {
|
||||
logger,
|
||||
} = params;
|
||||
const botUsername = normalizeOptionalLowercaseString(primaryCtx.me?.username);
|
||||
const mentionRegexes = buildMentionRegexes(cfg, routeAgentId);
|
||||
const mentionRegexes = buildMentionRegexes(cfg, routeAgentId, {
|
||||
provider: "telegram",
|
||||
conversationId: isGroup ? buildTelegramGroupPeerId(chatId, resolvedThreadId) : String(chatId),
|
||||
providerPolicy: providerMentionPatterns,
|
||||
});
|
||||
const messageTextParts = getTelegramTextParts(msg);
|
||||
const allowForCommands = isGroup ? effectiveGroupAllow : effectiveDmAllow;
|
||||
const useAccessGroups = cfg.commands?.useAccessGroups !== false;
|
||||
|
||||
@@ -471,6 +471,7 @@ export const buildTelegramMessageContext = async ({
|
||||
effectiveDmAllow: dmAllow.effectiveAllow,
|
||||
groupConfig,
|
||||
topicConfig,
|
||||
providerMentionPatterns: cfg.channels?.telegram?.accounts?.[account.accountId]?.mentionPatterns,
|
||||
requireMention,
|
||||
options,
|
||||
groupHistories,
|
||||
|
||||
@@ -33,6 +33,7 @@ export type ResolvedWhatsAppAccount = {
|
||||
allowFrom?: string[];
|
||||
groupAllowFrom?: string[];
|
||||
groupPolicy?: GroupPolicy;
|
||||
mentionPatterns?: WhatsAppAccountConfig["mentionPatterns"];
|
||||
dmPolicy?: DmPolicy;
|
||||
historyLimit?: number;
|
||||
textChunkLimit?: number;
|
||||
@@ -141,6 +142,7 @@ export function resolveWhatsAppAccount(params: {
|
||||
allowFrom: merged.allowFrom,
|
||||
groupAllowFrom: merged.groupAllowFrom,
|
||||
groupPolicy: merged.groupPolicy,
|
||||
mentionPatterns: merged.mentionPatterns,
|
||||
historyLimit: merged.historyLimit,
|
||||
textChunkLimit: merged.textChunkLimit,
|
||||
chunkMode: merged.chunkMode,
|
||||
|
||||
@@ -25,8 +25,12 @@ export type MentionTargets = {
|
||||
self: WhatsAppIdentity;
|
||||
};
|
||||
|
||||
export function buildMentionConfig(cfg: OpenClawConfig, agentId?: string): MentionConfig {
|
||||
const mentionRegexes = buildMentionRegexes(cfg, agentId);
|
||||
export function buildMentionConfig(
|
||||
cfg: OpenClawConfig,
|
||||
agentId?: string,
|
||||
options?: Parameters<typeof buildMentionRegexes>[2],
|
||||
): MentionConfig {
|
||||
const mentionRegexes = buildMentionRegexes(cfg, agentId, options);
|
||||
return { mentionRegexes, allowFrom: cfg.channels?.whatsapp?.allowFrom };
|
||||
}
|
||||
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import type { BuildMentionRegexesOptions } from "openclaw/plugin-sdk/channel-mention-gating";
|
||||
import type { OpenClawConfig } from "openclaw/plugin-sdk/config-contracts";
|
||||
import { resolveWhatsAppGroupsConfigPath } from "../../group-config-path.js";
|
||||
import {
|
||||
@@ -41,6 +42,7 @@ type ApplyGroupGatingParams = {
|
||||
agentId: string;
|
||||
sessionKey: string;
|
||||
baseMentionConfig: MentionConfig;
|
||||
providerMentionPatterns?: BuildMentionRegexesOptions["providerPolicy"];
|
||||
authDir?: string;
|
||||
groupHistories: Map<string, GroupHistoryEntry[]>;
|
||||
groupHistoryLimit: number;
|
||||
@@ -167,7 +169,11 @@ export async function applyGroupGating(params: ApplyGroupGatingParams) {
|
||||
allowFrom: inboundPolicy.configuredAllowFrom,
|
||||
};
|
||||
const mentionConfig = {
|
||||
...buildMentionConfig(params.cfg, params.agentId),
|
||||
...buildMentionConfig(params.cfg, params.agentId, {
|
||||
provider: "whatsapp",
|
||||
conversationId: params.conversationId,
|
||||
providerPolicy: params.providerMentionPatterns,
|
||||
}),
|
||||
allowFrom: inboundPolicy.configuredAllowFrom,
|
||||
};
|
||||
const mentionMsg =
|
||||
|
||||
@@ -250,6 +250,7 @@ export function createWebOnMessageHandler(params: {
|
||||
agentId: route.agentId,
|
||||
sessionKey: route.sessionKey,
|
||||
baseMentionConfig,
|
||||
providerMentionPatterns: account.mentionPatterns,
|
||||
authDir: account.authDir,
|
||||
selfChatMode: account.selfChatMode,
|
||||
groupHistories: params.groupHistories,
|
||||
@@ -275,6 +276,7 @@ export function createWebOnMessageHandler(params: {
|
||||
agentId: route.agentId,
|
||||
sessionKey: route.sessionKey,
|
||||
baseMentionConfig,
|
||||
providerMentionPatterns: account.mentionPatterns,
|
||||
authDir: account.authDir,
|
||||
selfChatMode: account.selfChatMode,
|
||||
groupHistories: params.groupHistories,
|
||||
|
||||
@@ -973,6 +973,81 @@ describe("mention helpers", () => {
|
||||
expect(matchesMentionPatterns("global: hi", regexes)).toBe(false);
|
||||
});
|
||||
|
||||
it("scopes configured mention patterns by provider conversation policy", () => {
|
||||
const cfg = {
|
||||
messages: {
|
||||
groupChat: {
|
||||
mentionPatterns: ["\\bopenclaw\\b"],
|
||||
},
|
||||
},
|
||||
channels: {
|
||||
slack: {
|
||||
mentionPatterns: {
|
||||
mode: "deny",
|
||||
allowIn: ["C123"],
|
||||
},
|
||||
},
|
||||
},
|
||||
} satisfies OpenClawConfig;
|
||||
|
||||
const allowed = buildMentionRegexes(cfg, undefined, {
|
||||
provider: "slack",
|
||||
conversationId: "C123",
|
||||
});
|
||||
const denied = buildMentionRegexes(cfg, undefined, {
|
||||
provider: "slack",
|
||||
conversationId: "C999",
|
||||
});
|
||||
|
||||
expect(matchesMentionPatterns("openclaw: hi", allowed)).toBe(true);
|
||||
expect(matchesMentionPatterns("openclaw: hi", denied)).toBe(false);
|
||||
});
|
||||
|
||||
it("preserves mention patterns for callers without scoped policy facts", () => {
|
||||
const regexes = buildMentionRegexes({
|
||||
messages: {
|
||||
groupChat: {
|
||||
mentionPatterns: ["\\bopenclaw\\b"],
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
expect(matchesMentionPatterns("openclaw", regexes)).toBe(true);
|
||||
});
|
||||
|
||||
it("lets provider deny lists override globally allowed mention patterns", () => {
|
||||
const cfg = {
|
||||
messages: {
|
||||
groupChat: {
|
||||
mentionPatterns: ["\\bopenclaw\\b"],
|
||||
},
|
||||
},
|
||||
channels: {
|
||||
telegram: {
|
||||
mentionPatterns: {
|
||||
denyIn: ["-100:topic:7"],
|
||||
},
|
||||
},
|
||||
},
|
||||
} satisfies OpenClawConfig;
|
||||
|
||||
expect(
|
||||
buildMentionRegexes(cfg, undefined, {
|
||||
provider: "telegram",
|
||||
conversationId: "-100:topic:7",
|
||||
}),
|
||||
).toEqual([]);
|
||||
expect(
|
||||
matchesMentionPatterns(
|
||||
"openclaw",
|
||||
buildMentionRegexes(cfg, undefined, {
|
||||
provider: "telegram",
|
||||
conversationId: "-100:topic:8",
|
||||
}),
|
||||
),
|
||||
).toBe(true);
|
||||
});
|
||||
|
||||
it("strips safe mention patterns and ignores unsafe ones", () => {
|
||||
const stripped = stripMentions("openclaw " + "a".repeat(28) + "!", {} as MsgContext, {
|
||||
messages: {
|
||||
|
||||
@@ -4,6 +4,7 @@ import {
|
||||
normalizeOptionalString,
|
||||
} from "@openclaw/normalization-core/string-coerce";
|
||||
import { resolveAgentConfig } from "../../agents/agent-scope.js";
|
||||
import { resolveMentionPatternPolicy } from "../../channels/mention-pattern-policy.js";
|
||||
import type { ChannelId } from "../../channels/plugins/channel-id.types.js";
|
||||
import { getLoadedChannelPluginById } from "../../channels/plugins/registry-loaded.js";
|
||||
import type { ChannelPlugin } from "../../channels/plugins/types.plugin.js";
|
||||
@@ -13,8 +14,8 @@ import { createSubsystemLogger } from "../../logging/subsystem.js";
|
||||
import { compileConfigRegexes, type ConfigRegexRejectReason } from "../../security/config-regex.js";
|
||||
import { escapeRegExp } from "../../utils.js";
|
||||
import type { MsgContext } from "../templating.js";
|
||||
import type { ExplicitMentionSignal } from "./mentions.types.js";
|
||||
export type { ExplicitMentionSignal } from "./mentions.types.js";
|
||||
import type { BuildMentionRegexesOptions, ExplicitMentionSignal } from "./mentions.types.js";
|
||||
export type { BuildMentionRegexesOptions, ExplicitMentionSignal } from "./mentions.types.js";
|
||||
|
||||
function deriveMentionPatterns(identity?: { name?: string; emoji?: string }) {
|
||||
const patterns: string[] = [];
|
||||
@@ -127,7 +128,14 @@ function resolveMentionPatterns(cfg: OpenClawConfig | undefined, agentId?: strin
|
||||
return derived.length > 0 ? derived : [];
|
||||
}
|
||||
|
||||
export function buildMentionRegexes(cfg: OpenClawConfig | undefined, agentId?: string): RegExp[] {
|
||||
export function buildMentionRegexes(
|
||||
cfg: OpenClawConfig | undefined,
|
||||
agentId?: string,
|
||||
options?: BuildMentionRegexesOptions,
|
||||
): RegExp[] {
|
||||
if (!resolveMentionPatternPolicy({ ...options, cfg, agentId }).enabled) {
|
||||
return [];
|
||||
}
|
||||
const patterns = normalizeMentionPatterns(resolveMentionPatterns(cfg, agentId));
|
||||
return compileMentionPatternsCached({
|
||||
patterns,
|
||||
|
||||
@@ -1,6 +1,13 @@
|
||||
import type { ResolveMentionPatternPolicyParams } from "../../channels/mention-pattern-policy.js";
|
||||
import type { OpenClawConfig } from "../../config/types.openclaw.js";
|
||||
|
||||
export type BuildMentionRegexes = (cfg: OpenClawConfig | undefined, agentId?: string) => RegExp[];
|
||||
export type BuildMentionRegexesOptions = Omit<ResolveMentionPatternPolicyParams, "cfg" | "agentId">;
|
||||
|
||||
export type BuildMentionRegexes = (
|
||||
cfg: OpenClawConfig | undefined,
|
||||
agentId?: string,
|
||||
options?: BuildMentionRegexesOptions,
|
||||
) => RegExp[];
|
||||
|
||||
export type MatchesMentionPatterns = (text: string, mentionRegexes: RegExp[]) => boolean;
|
||||
|
||||
|
||||
63
src/channels/mention-pattern-policy.ts
Normal file
63
src/channels/mention-pattern-policy.ts
Normal file
@@ -0,0 +1,63 @@
|
||||
import { normalizeOptionalString } from "@openclaw/normalization-core/string-coerce";
|
||||
import type { MentionPatternsMode, MentionPatternsPolicyConfig } from "../config/types.messages.js";
|
||||
import type { OpenClawConfig } from "../config/types.openclaw.js";
|
||||
|
||||
export type ResolveMentionPatternPolicyParams = {
|
||||
cfg?: OpenClawConfig;
|
||||
provider?: string;
|
||||
conversationId?: string | null;
|
||||
providerPolicy?: MentionPatternsPolicyConfig;
|
||||
agentId?: string;
|
||||
};
|
||||
|
||||
export type ResolvedMentionPatternPolicy = {
|
||||
effectiveMode: MentionPatternsMode;
|
||||
allowMatched: boolean;
|
||||
denyMatched: boolean;
|
||||
enabled: boolean;
|
||||
};
|
||||
|
||||
function normalizeIdList(values?: string[]): Set<string> {
|
||||
const normalized = new Set<string>();
|
||||
for (const value of values ?? []) {
|
||||
const next = normalizeOptionalString(value);
|
||||
if (next) {
|
||||
normalized.add(next);
|
||||
}
|
||||
}
|
||||
return normalized;
|
||||
}
|
||||
|
||||
function isMentionPatternsPolicyConfig(value: unknown): value is MentionPatternsPolicyConfig {
|
||||
return value != null && typeof value === "object" && !Array.isArray(value);
|
||||
}
|
||||
|
||||
function resolveProviderMentionPatternsPolicy(
|
||||
cfg: OpenClawConfig | undefined,
|
||||
provider: string | undefined,
|
||||
): MentionPatternsPolicyConfig | undefined {
|
||||
if (!cfg || !provider) {
|
||||
return undefined;
|
||||
}
|
||||
const policy = cfg.channels?.[provider]?.mentionPatterns;
|
||||
return isMentionPatternsPolicyConfig(policy) ? policy : undefined;
|
||||
}
|
||||
|
||||
export function resolveMentionPatternPolicy(
|
||||
params: ResolveMentionPatternPolicyParams,
|
||||
): ResolvedMentionPatternPolicy {
|
||||
const conversationId = normalizeOptionalString(params.conversationId ?? undefined) ?? undefined;
|
||||
const providerPolicy =
|
||||
params.providerPolicy ?? resolveProviderMentionPatternsPolicy(params.cfg, params.provider);
|
||||
const effectiveMode =
|
||||
providerPolicy?.mode === "allow" || providerPolicy?.mode === "deny"
|
||||
? providerPolicy.mode
|
||||
: "allow";
|
||||
const allowMatched =
|
||||
conversationId != null && normalizeIdList(providerPolicy?.allowIn).has(conversationId);
|
||||
const denyMatched =
|
||||
conversationId != null && normalizeIdList(providerPolicy?.denyIn).has(conversationId);
|
||||
const enabled = effectiveMode === "allow" ? !denyMatched : allowMatched && !denyMatched;
|
||||
|
||||
return { effectiveMode, allowMatched, denyMatched, enabled };
|
||||
}
|
||||
File diff suppressed because one or more lines are too long
@@ -8,6 +8,7 @@ import type { DiscordConfig } from "./types.discord.js";
|
||||
import type { GoogleChatConfig } from "./types.googlechat.js";
|
||||
import type { IMessageConfig } from "./types.imessage.js";
|
||||
import type { IrcConfig } from "./types.irc.js";
|
||||
import type { MentionPatternsPolicyConfig } from "./types.messages.js";
|
||||
import type { MSTeamsConfig } from "./types.msteams.js";
|
||||
import type { SignalConfig } from "./types.signal.js";
|
||||
import type { SlackConfig } from "./types.slack.js";
|
||||
@@ -50,6 +51,7 @@ export type ExtensionChannelConfig = {
|
||||
defaultAccount?: string;
|
||||
dmPolicy?: string;
|
||||
groupPolicy?: GroupPolicy;
|
||||
mentionPatterns?: MentionPatternsPolicyConfig;
|
||||
contextVisibility?: ContextVisibilityMode;
|
||||
healthMonitor?: ChannelHealthMonitorConfig;
|
||||
dm?: ExtensionNestedPolicyConfig;
|
||||
|
||||
@@ -12,7 +12,11 @@ import type {
|
||||
ChannelHealthMonitorConfig,
|
||||
ChannelHeartbeatVisibilityConfig,
|
||||
} from "./types.channel-health.js";
|
||||
import type { DmConfig, ProviderCommandsConfig } from "./types.messages.js";
|
||||
import type {
|
||||
DmConfig,
|
||||
MentionPatternsPolicyConfig,
|
||||
ProviderCommandsConfig,
|
||||
} from "./types.messages.js";
|
||||
import type { SecretInput } from "./types.secrets.js";
|
||||
import type { GroupToolPolicyBySenderConfig, GroupToolPolicyConfig } from "./types.tools.js";
|
||||
import type { TtsConfig } from "./types.tts.js";
|
||||
@@ -377,6 +381,8 @@ export type DiscordAccountConfig = {
|
||||
* - "allowlist": only allow channels present in discord.guilds.*.channels
|
||||
*/
|
||||
groupPolicy?: GroupPolicy;
|
||||
/** Scope configured groupChat mentionPatterns to selected Discord channel IDs. */
|
||||
mentionPatterns?: MentionPatternsPolicyConfig;
|
||||
/** Supplemental context visibility policy (all|allowlist|allowlist_quote). */
|
||||
contextVisibility?: ContextVisibilityMode;
|
||||
/** Outbound text chunk size (chars). Default: 2000. */
|
||||
|
||||
@@ -1,6 +1,14 @@
|
||||
import type { QueueDropPolicy, QueueMode, QueueModeByProvider } from "./types.queue.js";
|
||||
import type { TtsConfig } from "./types.tts.js";
|
||||
|
||||
export type MentionPatternsMode = "allow" | "deny";
|
||||
|
||||
export type MentionPatternsPolicyConfig = {
|
||||
mode?: MentionPatternsMode;
|
||||
allowIn?: string[];
|
||||
denyIn?: string[];
|
||||
};
|
||||
|
||||
export type GroupChatConfig = {
|
||||
mentionPatterns?: string[];
|
||||
historyLimit?: number;
|
||||
|
||||
@@ -15,7 +15,11 @@ import type {
|
||||
ChannelHealthMonitorConfig,
|
||||
ChannelHeartbeatVisibilityConfig,
|
||||
} from "./types.channel-health.js";
|
||||
import type { DmConfig, ProviderCommandsConfig } from "./types.messages.js";
|
||||
import type {
|
||||
DmConfig,
|
||||
MentionPatternsPolicyConfig,
|
||||
ProviderCommandsConfig,
|
||||
} from "./types.messages.js";
|
||||
import type { GroupToolPolicyBySenderConfig, GroupToolPolicyConfig } from "./types.tools.js";
|
||||
|
||||
export type SlackDmConfig = {
|
||||
@@ -180,6 +184,8 @@ export type SlackAccountConfig = {
|
||||
* - "allowlist": only allow channels present in channels.slack.channels
|
||||
*/
|
||||
groupPolicy?: GroupPolicy;
|
||||
/** Scope configured groupChat mentionPatterns to selected Slack channel IDs. */
|
||||
mentionPatterns?: MentionPatternsPolicyConfig;
|
||||
/** Supplemental context visibility policy (all|allowlist|allowlist_quote). */
|
||||
contextVisibility?: ContextVisibilityMode;
|
||||
/** Max channel messages to keep as history context (0 disables). */
|
||||
|
||||
@@ -13,7 +13,11 @@ import type {
|
||||
ChannelHealthMonitorConfig,
|
||||
ChannelHeartbeatVisibilityConfig,
|
||||
} from "./types.channel-health.js";
|
||||
import type { DmConfig, ProviderCommandsConfig } from "./types.messages.js";
|
||||
import type {
|
||||
DmConfig,
|
||||
MentionPatternsPolicyConfig,
|
||||
ProviderCommandsConfig,
|
||||
} from "./types.messages.js";
|
||||
import type { GroupToolPolicyBySenderConfig, GroupToolPolicyConfig } from "./types.tools.js";
|
||||
|
||||
export type TelegramActionConfig = {
|
||||
@@ -152,6 +156,8 @@ export type TelegramAccountConfig = {
|
||||
* - "allowlist": only allow group messages from senders in groupAllowFrom/allowFrom
|
||||
*/
|
||||
groupPolicy?: GroupPolicy;
|
||||
/** Scope configured groupChat mentionPatterns to selected Telegram chat/thread IDs. */
|
||||
mentionPatterns?: MentionPatternsPolicyConfig;
|
||||
/** Supplemental context visibility policy (all|allowlist|allowlist_quote). */
|
||||
contextVisibility?: ContextVisibilityMode;
|
||||
/** Max group messages to keep as history context (0 disables). */
|
||||
|
||||
@@ -11,7 +11,7 @@ import type {
|
||||
ChannelHealthMonitorConfig,
|
||||
ChannelHeartbeatVisibilityConfig,
|
||||
} from "./types.channel-health.js";
|
||||
import type { DmConfig } from "./types.messages.js";
|
||||
import type { DmConfig, MentionPatternsPolicyConfig } from "./types.messages.js";
|
||||
import type { GroupToolPolicyBySenderConfig, GroupToolPolicyConfig } from "./types.tools.js";
|
||||
|
||||
export type WhatsAppActionConfig = {
|
||||
@@ -70,6 +70,8 @@ type WhatsAppSharedConfig = {
|
||||
* - "allowlist": only allow group messages from senders in groupAllowFrom/allowFrom
|
||||
*/
|
||||
groupPolicy?: GroupPolicy;
|
||||
/** Scope configured groupChat mentionPatterns to selected WhatsApp conversation IDs. */
|
||||
mentionPatterns?: MentionPatternsPolicyConfig;
|
||||
/** Supplemental context visibility policy (all|allowlist|allowlist_quote). */
|
||||
contextVisibility?: ContextVisibilityMode;
|
||||
/** Max group messages to keep as history context (0 disables). */
|
||||
|
||||
@@ -549,6 +549,16 @@ export const VisibleRepliesSchema = z
|
||||
return value;
|
||||
});
|
||||
|
||||
export const MentionPatternsModeSchema = z.union([z.literal("allow"), z.literal("deny")]);
|
||||
|
||||
export const MentionPatternsPolicySchema = z
|
||||
.object({
|
||||
mode: MentionPatternsModeSchema.optional(),
|
||||
allowIn: z.array(z.string()).optional(),
|
||||
denyIn: z.array(z.string()).optional(),
|
||||
})
|
||||
.strict();
|
||||
|
||||
export const GroupChatSchema = z
|
||||
.object({
|
||||
mentionPatterns: z.array(z.string()).optional(),
|
||||
|
||||
@@ -23,6 +23,7 @@ import {
|
||||
GroupPolicySchema,
|
||||
HexColorSchema,
|
||||
MarkdownConfigSchema,
|
||||
MentionPatternsPolicySchema,
|
||||
MSTeamsReplyStyleSchema,
|
||||
ProviderCommandsSchema,
|
||||
SecretInputSchema,
|
||||
@@ -271,6 +272,7 @@ export const TelegramAccountSchemaBase = z
|
||||
defaultTo: z.union([z.string(), z.number()]).optional(),
|
||||
groupAllowFrom: z.array(z.union([z.string(), z.number()])).optional(),
|
||||
groupPolicy: GroupPolicySchema.optional().default("allowlist"),
|
||||
mentionPatterns: MentionPatternsPolicySchema.optional(),
|
||||
contextVisibility: ContextVisibilityModeSchema.optional(),
|
||||
historyLimit: z.number().int().min(0).optional(),
|
||||
dmHistoryLimit: z.number().int().min(0).optional(),
|
||||
@@ -652,6 +654,7 @@ export const DiscordAccountSchema = z
|
||||
dangerouslyAllowNameMatching: z.boolean().optional(),
|
||||
mentionAliases: z.record(z.string(), DiscordSnowflakeStringSchema).optional(),
|
||||
groupPolicy: GroupPolicySchema.optional().default("allowlist"),
|
||||
mentionPatterns: MentionPatternsPolicySchema.optional(),
|
||||
contextVisibility: ContextVisibilityModeSchema.optional(),
|
||||
historyLimit: z.number().int().min(0).optional(),
|
||||
dmHistoryLimit: z.number().int().min(0).optional(),
|
||||
@@ -979,6 +982,7 @@ export const SlackAccountSchema = z
|
||||
dangerouslyAllowNameMatching: z.boolean().optional(),
|
||||
requireMention: z.boolean().optional(),
|
||||
groupPolicy: GroupPolicySchema.optional(),
|
||||
mentionPatterns: MentionPatternsPolicySchema.optional(),
|
||||
contextVisibility: ContextVisibilityModeSchema.optional(),
|
||||
historyLimit: z.number().int().min(0).optional(),
|
||||
dmHistoryLimit: z.number().int().min(0).optional(),
|
||||
@@ -1039,6 +1043,7 @@ export const SlackConfigSchema = SlackAccountSchema.safeExtend({
|
||||
signingSecret: SecretInputSchema.optional().register(sensitive),
|
||||
webhookPath: z.string().optional().default("/slack/events"),
|
||||
groupPolicy: GroupPolicySchema.optional().default("allowlist"),
|
||||
mentionPatterns: MentionPatternsPolicySchema.optional(),
|
||||
contextVisibility: ContextVisibilityModeSchema.optional(),
|
||||
accounts: z.record(z.string(), SlackAccountSchema.optional()).optional(),
|
||||
defaultAccount: z.string().optional(),
|
||||
|
||||
@@ -13,6 +13,7 @@ import {
|
||||
DmPolicySchema,
|
||||
GroupPolicySchema,
|
||||
MarkdownConfigSchema,
|
||||
MentionPatternsPolicySchema,
|
||||
ReplyToModeSchema,
|
||||
} from "./zod-schema.core.js";
|
||||
|
||||
@@ -86,6 +87,7 @@ function buildWhatsAppCommonShape(params: { useDefaults: boolean }) {
|
||||
groupPolicy: params.useDefaults
|
||||
? GroupPolicySchema.optional().default("allowlist")
|
||||
: GroupPolicySchema.optional(),
|
||||
mentionPatterns: MentionPatternsPolicySchema.optional(),
|
||||
contextVisibility: ContextVisibilityModeSchema.optional(),
|
||||
historyLimit: z.number().int().min(0).optional(),
|
||||
dmHistoryLimit: z.number().int().min(0).optional(),
|
||||
|
||||
@@ -13,6 +13,7 @@ export {
|
||||
DmPolicySchema,
|
||||
GroupPolicySchema,
|
||||
MarkdownConfigSchema,
|
||||
MentionPatternsPolicySchema,
|
||||
ReplyRuntimeConfigSchemaShape,
|
||||
requireAllowlistAllowFrom,
|
||||
requireOpenAllowFrom,
|
||||
|
||||
@@ -41,7 +41,13 @@ export {
|
||||
matchesMentionPatterns,
|
||||
matchesMentionWithExplicit,
|
||||
normalizeMentionText,
|
||||
type BuildMentionRegexesOptions,
|
||||
} from "../auto-reply/reply/mentions.js";
|
||||
export {
|
||||
resolveMentionPatternPolicy,
|
||||
type ResolveMentionPatternPolicyParams,
|
||||
type ResolvedMentionPatternPolicy,
|
||||
} from "../channels/mention-pattern-policy.js";
|
||||
export {
|
||||
createChannelInboundDebouncer,
|
||||
shouldDebounceTextInbound,
|
||||
|
||||
@@ -23,4 +23,10 @@ export {
|
||||
CURRENT_MESSAGE_MARKER,
|
||||
buildMentionRegexes,
|
||||
normalizeMentionText,
|
||||
type BuildMentionRegexesOptions,
|
||||
} from "../auto-reply/reply/mentions.js";
|
||||
export {
|
||||
resolveMentionPatternPolicy,
|
||||
type ResolveMentionPatternPolicyParams,
|
||||
type ResolvedMentionPatternPolicy,
|
||||
} from "../channels/mention-pattern-policy.js";
|
||||
|
||||
@@ -28,6 +28,7 @@ export type {
|
||||
GroupToolPolicyConfig,
|
||||
MarkdownConfig,
|
||||
MarkdownTableMode,
|
||||
MentionPatternsPolicyConfig,
|
||||
MSTeamsChannelConfig,
|
||||
MSTeamsCloudName,
|
||||
MSTeamsConfig,
|
||||
|
||||
Reference in New Issue
Block a user