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

@@ -6,10 +6,11 @@ import {
import {
buildMentionRegexes,
formatInboundEnvelope,
implicitMentionKindWhen,
logInboundDrop,
matchesMentionWithExplicit,
resolveEnvelopeFormatOptions,
resolveMentionGatingWithBypass,
resolveInboundMentionDecision,
} from "openclaw/plugin-sdk/channel-inbound";
import { resolveControlCommandGate } from "openclaw/plugin-sdk/command-auth";
import { hasControlCommand } from "openclaw/plugin-sdk/command-auth";
@@ -383,14 +384,16 @@ export async function prepareSlackMessage(params: {
canResolveExplicit: Boolean(ctx.botUserId),
},
}));
const implicitMention = Boolean(
!ctx.threadRequireExplicitMention &&
!isDirectMessage &&
ctx.botUserId &&
message.thread_ts &&
(message.parent_user_id === ctx.botUserId ||
hasSlackThreadParticipation(account.accountId, message.channel, message.thread_ts)),
);
const implicitMentionKinds =
isDirectMessage || !ctx.botUserId || !message.thread_ts
? []
: [
...implicitMentionKindWhen("reply_to_bot", message.parent_user_id === ctx.botUserId),
...implicitMentionKindWhen(
"bot_thread_participant",
hasSlackThreadParticipation(account.accountId, message.channel, message.thread_ts),
),
];
let resolvedSenderName = message.username?.trim() || undefined;
const resolveSenderName = async (): Promise<string> => {
@@ -492,19 +495,24 @@ export async function prepareSlackMessage(params: {
// Allow "control commands" to bypass mention gating if sender is authorized.
const canDetectMention = Boolean(ctx.botUserId) || mentionRegexes.length > 0;
const mentionGate = resolveMentionGatingWithBypass({
isGroup: isRoom,
requireMention: Boolean(shouldRequireMention),
canDetectMention,
wasMentioned,
implicitMention,
hasAnyMention,
allowTextCommands,
hasControlCommand: hasControlCommandInMessage,
commandAuthorized,
const mentionDecision = resolveInboundMentionDecision({
facts: {
canDetectMention,
wasMentioned,
hasAnyMention,
implicitMentionKinds,
},
policy: {
isGroup: isRoom,
requireMention: Boolean(shouldRequireMention),
allowedImplicitMentionKinds: ctx.threadRequireExplicitMention ? [] : undefined,
allowTextCommands,
hasControlCommand: hasControlCommandInMessage,
commandAuthorized,
},
});
const effectiveWasMentioned = mentionGate.effectiveWasMentioned;
if (isRoom && shouldRequireMention && mentionGate.shouldSkip) {
const effectiveWasMentioned = mentionDecision.effectiveWasMentioned;
if (isRoom && shouldRequireMention && mentionDecision.shouldSkip) {
ctx.logger.info({ channel: message.channel, reason: "no-mention" }, "skipping channel message");
const pendingText = (message.text ?? "").trim();
const fallbackFile = message.files?.[0]?.name
@@ -567,7 +575,7 @@ export async function prepareSlackMessage(params: {
requireMention: Boolean(shouldRequireMention),
canDetectMention,
effectiveWasMentioned,
shouldBypassMention: mentionGate.shouldBypassMention,
shouldBypassMention: mentionDecision.shouldBypassMention,
}),
);