From fe778e81f9cfa0a8c12dee9700656b16f1b755bc Mon Sep 17 00:00:00 2001 From: Matt Keenan Date: Fri, 30 Jan 2026 15:06:56 +0000 Subject: [PATCH] feat: add actions.sendMessage gating to Discord, Slack, Google Chat, Matrix (#4703) Enables read-only mode for these channels by adding actions.sendMessage configuration option. When set to false, blocks outbound messages while still allowing message reception. Follows the existing Telegram/WhatsApp pattern: - Add sendMessage?: boolean to channel ActionConfig types - Check gate in action handler, throw error if disabled - Default: true (enabled) when not specified This brings read-only mode support to 6 out of 7 core channels (Telegram, WhatsApp, Discord, Slack, Google Chat, Matrix). --- extensions/googlechat/src/actions.ts | 5 +++++ extensions/matrix/src/actions.ts | 5 +++++ extensions/matrix/src/config-schema.ts | 1 + src/agents/tools/discord-actions-messaging.ts | 4 ++-- src/agents/tools/slack-actions.ts | 3 +++ src/config/types.discord.ts | 1 + src/config/types.googlechat.ts | 1 + src/config/types.slack.ts | 1 + 8 files changed, 19 insertions(+), 2 deletions(-) diff --git a/extensions/googlechat/src/actions.ts b/extensions/googlechat/src/actions.ts index cecce7b14..02e7f4ef8 100644 --- a/extensions/googlechat/src/actions.ts +++ b/extensions/googlechat/src/actions.ts @@ -77,6 +77,11 @@ export const googlechatMessageActions: ChannelMessageActionAdapter = { } if (action === "send") { + const actionConfig = account.config.actions ?? (cfg as OpenClawConfig).channels?.["googlechat"]?.actions; + const isActionEnabled = createActionGate(actionConfig as Record); + if (!isActionEnabled("sendMessage")) { + throw new Error("Google Chat sendMessage is disabled."); + } const to = readStringParam(params, "to", { required: true }); const content = readStringParam(params, "message", { required: true, diff --git a/extensions/matrix/src/actions.ts b/extensions/matrix/src/actions.ts index 113680a3b..de65ae2eb 100644 --- a/extensions/matrix/src/actions.ts +++ b/extensions/matrix/src/actions.ts @@ -51,6 +51,11 @@ export const matrixMessageActions: ChannelMessageActionAdapter = { readStringParam(params, "to", { required: true }); if (action === "send") { + const account = resolveMatrixAccount({ cfg: cfg as CoreConfig }); + const isActionEnabled = createActionGate((cfg as CoreConfig).channels?.matrix?.actions); + if (!isActionEnabled("sendMessage")) { + throw new Error("Matrix sendMessage is disabled."); + } const to = readStringParam(params, "to", { required: true }); const content = readStringParam(params, "message", { required: true, diff --git a/extensions/matrix/src/config-schema.ts b/extensions/matrix/src/config-schema.ts index 5d08fc73b..d17e6de2a 100644 --- a/extensions/matrix/src/config-schema.ts +++ b/extensions/matrix/src/config-schema.ts @@ -6,6 +6,7 @@ const allowFromEntry = z.union([z.string(), z.number()]); const matrixActionSchema = z .object({ reactions: z.boolean().optional(), + sendMessage: z.boolean().optional(), messages: z.boolean().optional(), pins: z.boolean().optional(), memberInfo: z.boolean().optional(), diff --git a/src/agents/tools/discord-actions-messaging.ts b/src/agents/tools/discord-actions-messaging.ts index f90fb60de..ae8f48800 100644 --- a/src/agents/tools/discord-actions-messaging.ts +++ b/src/agents/tools/discord-actions-messaging.ts @@ -222,8 +222,8 @@ export async function handleDiscordMessagingAction( }); } case "sendMessage": { - if (!isActionEnabled("messages")) { - throw new Error("Discord message sends are disabled."); + if (!isActionEnabled("sendMessage")) { + throw new Error("Discord sendMessage is disabled."); } const to = readStringParam(params, "to", { required: true }); const content = readStringParam(params, "content", { diff --git a/src/agents/tools/slack-actions.ts b/src/agents/tools/slack-actions.ts index 3bb8ea717..bdb40df1d 100644 --- a/src/agents/tools/slack-actions.ts +++ b/src/agents/tools/slack-actions.ts @@ -154,6 +154,9 @@ export async function handleSlackAction( } switch (action) { case "sendMessage": { + if (!isActionEnabled("sendMessage")) { + throw new Error("Slack sendMessage is disabled."); + } const to = readStringParam(params, "to", { required: true }); const content = readStringParam(params, "content", { required: true }); const mediaUrl = readStringParam(params, "mediaUrl"); diff --git a/src/config/types.discord.ts b/src/config/types.discord.ts index 07d4e658f..742890517 100644 --- a/src/config/types.discord.ts +++ b/src/config/types.discord.ts @@ -55,6 +55,7 @@ export type DiscordGuildEntry = { export type DiscordActionConfig = { reactions?: boolean; + sendMessage?: boolean; stickers?: boolean; polls?: boolean; permissions?: boolean; diff --git a/src/config/types.googlechat.ts b/src/config/types.googlechat.ts index 5fceff49e..5cd46f751 100644 --- a/src/config/types.googlechat.ts +++ b/src/config/types.googlechat.ts @@ -30,6 +30,7 @@ export type GoogleChatGroupConfig = { export type GoogleChatActionConfig = { reactions?: boolean; + sendMessage?: boolean; }; export type GoogleChatAccountConfig = { diff --git a/src/config/types.slack.ts b/src/config/types.slack.ts index cbf912e80..d05e09886 100644 --- a/src/config/types.slack.ts +++ b/src/config/types.slack.ts @@ -48,6 +48,7 @@ export type SlackReactionNotificationMode = "off" | "own" | "all" | "allowlist"; export type SlackActionConfig = { reactions?: boolean; + sendMessage?: boolean; messages?: boolean; pins?: boolean; search?: boolean;