diff --git a/extensions/msteams/src/monitor-handler/message-handler.ts b/extensions/msteams/src/monitor-handler/message-handler.ts index fff243fb70c..60a88c56664 100644 --- a/extensions/msteams/src/monitor-handler/message-handler.ts +++ b/extensions/msteams/src/monitor-handler/message-handler.ts @@ -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", diff --git a/src/channels/command-gating.test.ts b/src/channels/command-gating.test.ts index 5ea0614e287..9b3f645e515 100644 --- a/src/channels/command-gating.test.ts +++ b/src/channels/command-gating.test.ts @@ -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); + }); }); diff --git a/src/channels/command-gating.ts b/src/channels/command-gating.ts index 1492d4760a4..068db8328be 100644 --- a/src/channels/command-gating.ts +++ b/src/channels/command-gating.ts @@ -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, + }); +} diff --git a/src/imessage/monitor/inbound-processing.ts b/src/imessage/monitor/inbound-processing.ts index b3fc10c1e7b..e6c964dff95 100644 --- a/src/imessage/monitor/inbound-processing.ts +++ b/src/imessage/monitor/inbound-processing.ts @@ -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, diff --git a/src/plugin-sdk/msteams.ts b/src/plugin-sdk/msteams.ts index 90d5ee1b1ac..32036f60a35 100644 --- a/src/plugin-sdk/msteams.ts +++ b/src/plugin-sdk/msteams.ts @@ -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";