refactor: centralize inbound mention policy

This commit is contained in:
Peter Steinberger
2026-04-07 07:50:09 +01:00
parent c8b7058058
commit 625fd5b3e3
31 changed files with 857 additions and 225 deletions

View File

@@ -594,6 +594,45 @@ describe("preflightDiscordMessage", () => {
expect(result).not.toBeNull();
});
it("still drops bot control commands without a real mention when allowBots=mentions", async () => {
const channelId = "channel-bot-command-no-mention";
const guildId = "guild-bot-command-no-mention";
const message = createDiscordMessage({
id: "m-bot-command-no-mention",
channelId,
content: "/new incident room",
author: {
id: "relay-bot-1",
bot: true,
username: "Relay",
},
});
const result = await runMentionOnlyBotPreflight({ channelId, guildId, message });
expect(result).toBeNull();
});
it("still allows bot control commands with an explicit mention when allowBots=mentions", async () => {
const channelId = "channel-bot-command-with-mention";
const guildId = "guild-bot-command-with-mention";
const message = createDiscordMessage({
id: "m-bot-command-with-mention",
channelId,
content: "<@openclaw-bot> /new incident room",
mentionedUsers: [{ id: "openclaw-bot" }],
author: {
id: "relay-bot-1",
bot: true,
username: "Relay",
},
});
const result = await runMentionOnlyBotPreflight({ channelId, guildId, message });
expect(result).not.toBeNull();
});
it("treats @everyone as a mention when requireMention is true", async () => {
const channelId = "channel-everyone-mention";
const guildId = "guild-everyone-mention";

View File

@@ -3,9 +3,10 @@ import { Routes, type APIMessage } from "discord-api-types/v10";
import { formatAllowlistMatchMeta } from "openclaw/plugin-sdk/allow-from";
import {
buildMentionRegexes,
implicitMentionKindWhen,
logInboundDrop,
matchesMentionWithExplicit,
resolveMentionGatingWithBypass,
resolveInboundMentionDecision,
} from "openclaw/plugin-sdk/channel-inbound";
import { resolveControlCommandGate } from "openclaw/plugin-sdk/command-auth-native";
import { hasControlCommand } from "openclaw/plugin-sdk/command-detection";
@@ -181,10 +182,10 @@ function resolveDiscordMentionState(params: {
referencedAuthorId?: string;
senderIsPluralKit: boolean;
transcript?: string;
}): { implicitMention: boolean; wasMentioned: boolean } {
}) {
if (params.isDirectMessage) {
return {
implicitMention: false,
implicitMentionKinds: [],
wasMentioned: false,
};
}
@@ -203,12 +204,15 @@ function resolveDiscordMentionState(params: {
},
transcript: params.transcript,
});
const implicitMention = Boolean(
params.botId && params.referencedAuthorId && params.referencedAuthorId === params.botId,
const implicitMentionKinds = implicitMentionKindWhen(
"reply_to_bot",
Boolean(params.botId) &&
Boolean(params.referencedAuthorId) &&
params.referencedAuthorId === params.botId,
);
return {
implicitMention,
implicitMentionKinds,
wasMentioned,
};
}
@@ -887,7 +891,7 @@ export async function preflightDiscordMessage(
}
const mentionText = hasTypedText ? baseText : "";
const { implicitMention, wasMentioned } = resolveDiscordMentionState({
const { implicitMentionKinds, wasMentioned } = resolveDiscordMentionState({
authorIsBot: Boolean(author.bot),
botId,
hasAnyMention,
@@ -946,23 +950,27 @@ export async function preflightDiscordMessage(
}
const canDetectMention = Boolean(botId) || mentionRegexes.length > 0;
const mentionGate = resolveMentionGatingWithBypass({
isGroup: isGuildMessage,
requireMention: Boolean(shouldRequireMention),
canDetectMention,
wasMentioned,
implicitMention,
hasAnyMention,
allowTextCommands,
hasControlCommand: hasControlCommandInMessage,
commandAuthorized,
const mentionDecision = resolveInboundMentionDecision({
facts: {
canDetectMention,
wasMentioned,
hasAnyMention,
implicitMentionKinds,
},
policy: {
isGroup: isGuildMessage,
requireMention: Boolean(shouldRequireMention),
allowTextCommands,
hasControlCommand: hasControlCommandInMessage,
commandAuthorized,
},
});
const effectiveWasMentioned = mentionGate.effectiveWasMentioned;
const effectiveWasMentioned = mentionDecision.effectiveWasMentioned;
logDebug(
`[discord-preflight] shouldRequireMention=${shouldRequireMention} baseRequireMention=${shouldRequireMentionByConfig} boundThreadSession=${isBoundThreadSession} mentionGate.shouldSkip=${mentionGate.shouldSkip} wasMentioned=${wasMentioned}`,
`[discord-preflight] shouldRequireMention=${shouldRequireMention} baseRequireMention=${shouldRequireMentionByConfig} boundThreadSession=${isBoundThreadSession} mentionDecision.shouldSkip=${mentionDecision.shouldSkip} wasMentioned=${wasMentioned}`,
);
if (isGuildMessage && shouldRequireMention) {
if (botId && mentionGate.shouldSkip) {
if (botId && mentionDecision.shouldSkip) {
logDebug(`[discord-preflight] drop: no-mention`);
logVerbose(`discord: drop guild message (mention required, botId=${botId})`);
logger.info(
@@ -983,7 +991,7 @@ export async function preflightDiscordMessage(
}
if (author.bot && !sender.isPluralKit && allowBotsMode === "mentions") {
const botMentioned = isDirectMessage || wasMentioned || implicitMention;
const botMentioned = isDirectMessage || wasMentioned || mentionDecision.implicitMention;
if (!botMentioned) {
logDebug(`[discord-preflight] drop: bot message missing mention (allowBots=mentions)`);
logVerbose("discord: drop bot message (allowBots=mentions, missing mention)");
@@ -998,7 +1006,7 @@ export async function preflightDiscordMessage(
ignoreOtherMentions &&
hasUserOrRoleMention &&
!wasMentioned &&
!implicitMention
!mentionDecision.implicitMention
) {
logDebug(`[discord-preflight] drop: other-mention`);
logVerbose(
@@ -1103,7 +1111,7 @@ export async function preflightDiscordMessage(
shouldRequireMention,
hasAnyMention,
allowTextCommands,
shouldBypassMention: mentionGate.shouldBypassMention,
shouldBypassMention: mentionDecision.shouldBypassMention,
effectiveWasMentioned,
canDetectMention,
historyEntry,