refactor: share dual text command gating

This commit is contained in:
Peter Steinberger 2026-03-14 01:23:36 +00:00
parent 91d9573b55
commit b61bc4948e
5 changed files with 55 additions and 23 deletions

View File

@ -9,7 +9,7 @@ import {
evaluateSenderGroupAccessForPolicy,
resolveSenderScopedGroupPolicy,
recordPendingHistoryEntryIfEnabled,
resolveControlCommandGate,
resolveDualTextControlCommandGate,
resolveDefaultGroupPolicy,
isDangerousNameMatchingEnabled,
readStoreAllowFromForDmPolicy,
@ -297,18 +297,15 @@ export function createMSTeamsMessageHandler(deps: MSTeamsMessageHandlerDeps) {
senderName,
allowNameMatching: isDangerousNameMatchingEnabled(msteamsCfg),
});
const hasControlCommandInMessage = core.channel.text.hasControlCommand(text, cfg);
const commandGate = resolveControlCommandGate({
const { commandAuthorized, shouldBlock } = resolveDualTextControlCommandGate({
useAccessGroups,
authorizers: [
{ configured: commandDmAllowFrom.length > 0, allowed: ownerAllowedForCommands },
{ configured: effectiveGroupAllowFrom.length > 0, allowed: groupAllowedForCommands },
],
allowTextCommands: true,
hasControlCommand: hasControlCommandInMessage,
primaryConfigured: commandDmAllowFrom.length > 0,
primaryAllowed: ownerAllowedForCommands,
secondaryConfigured: effectiveGroupAllowFrom.length > 0,
secondaryAllowed: groupAllowedForCommands,
hasControlCommand: core.channel.text.hasControlCommand(text, cfg),
});
const commandAuthorized = commandGate.commandAuthorized;
if (commandGate.shouldBlock) {
if (shouldBlock) {
logInboundDrop({
log: logVerboseMessage,
channel: "msteams",

View File

@ -2,6 +2,7 @@ import { describe, expect, it } from "vitest";
import {
resolveCommandAuthorizedFromAuthorizers,
resolveControlCommandGate,
resolveDualTextControlCommandGate,
} from "./command-gating.js";
describe("resolveCommandAuthorizedFromAuthorizers", () => {
@ -94,4 +95,17 @@ describe("resolveControlCommandGate", () => {
});
expect(result.shouldBlock).toBe(false);
});
it("supports the dual-authorizer text gate helper", () => {
const result = resolveDualTextControlCommandGate({
useAccessGroups: true,
primaryConfigured: true,
primaryAllowed: false,
secondaryConfigured: true,
secondaryAllowed: true,
hasControlCommand: true,
});
expect(result.commandAuthorized).toBe(true);
expect(result.shouldBlock).toBe(false);
});
});

View File

@ -43,3 +43,24 @@ export function resolveControlCommandGate(params: {
const shouldBlock = params.allowTextCommands && params.hasControlCommand && !commandAuthorized;
return { commandAuthorized, shouldBlock };
}
export function resolveDualTextControlCommandGate(params: {
useAccessGroups: boolean;
primaryConfigured: boolean;
primaryAllowed: boolean;
secondaryConfigured: boolean;
secondaryAllowed: boolean;
hasControlCommand: boolean;
modeWhenAccessGroupsOff?: CommandGatingModeWhenAccessGroupsOff;
}): { commandAuthorized: boolean; shouldBlock: boolean } {
return resolveControlCommandGate({
useAccessGroups: params.useAccessGroups,
authorizers: [
{ configured: params.primaryConfigured, allowed: params.primaryAllowed },
{ configured: params.secondaryConfigured, allowed: params.secondaryAllowed },
],
allowTextCommands: true,
hasControlCommand: params.hasControlCommand,
modeWhenAccessGroupsOff: params.modeWhenAccessGroupsOff,
});
}

View File

@ -12,7 +12,7 @@ import {
} from "../../auto-reply/reply/history.js";
import { finalizeInboundContext } from "../../auto-reply/reply/inbound-context.js";
import { buildMentionRegexes, matchesMentionPatterns } from "../../auto-reply/reply/mentions.js";
import { resolveControlCommandGate } from "../../channels/command-gating.js";
import { resolveDualTextControlCommandGate } from "../../channels/command-gating.js";
import { logInboundDrop } from "../../channels/logging.js";
import type { OpenClawConfig } from "../../config/config.js";
import {
@ -310,18 +310,15 @@ export function resolveIMessageInboundDecision(params: {
chatIdentifier,
})
: false;
const hasControlCommandInMessage = hasControlCommand(messageText, params.cfg);
const commandGate = resolveControlCommandGate({
const { commandAuthorized, shouldBlock } = resolveDualTextControlCommandGate({
useAccessGroups,
authorizers: [
{ configured: commandDmAllowFrom.length > 0, allowed: ownerAllowedForCommands },
{ configured: effectiveGroupAllowFrom.length > 0, allowed: groupAllowedForCommands },
],
allowTextCommands: true,
hasControlCommand: hasControlCommandInMessage,
primaryConfigured: commandDmAllowFrom.length > 0,
primaryAllowed: ownerAllowedForCommands,
secondaryConfigured: effectiveGroupAllowFrom.length > 0,
secondaryAllowed: groupAllowedForCommands,
hasControlCommand: hasControlCommand(messageText, params.cfg),
});
const commandAuthorized = commandGate.commandAuthorized;
if (isGroup && commandGate.shouldBlock) {
if (isGroup && shouldBlock) {
if (params.logVerbose) {
logInboundDrop({
log: params.logVerbose,

View File

@ -12,7 +12,10 @@ export {
export { isSilentReplyText, SILENT_REPLY_TOKEN } from "../auto-reply/tokens.js";
export type { ReplyPayload } from "../auto-reply/types.js";
export { mergeAllowlist, summarizeMapping } from "../channels/allowlists/resolve-utils.js";
export { resolveControlCommandGate } from "../channels/command-gating.js";
export {
resolveControlCommandGate,
resolveDualTextControlCommandGate,
} from "../channels/command-gating.js";
export { logInboundDrop, logTypingFailure } from "../channels/logging.js";
export { resolveMentionGating } from "../channels/mention-gating.js";
export type { AllowlistMatch } from "../channels/plugins/allowlist-match.js";