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).
This commit is contained in:
Matt Keenan 2026-01-30 15:06:56 +00:00
parent da71eaebd2
commit fe778e81f9
8 changed files with 19 additions and 2 deletions

View File

@ -77,6 +77,11 @@ export const googlechatMessageActions: ChannelMessageActionAdapter = {
} }
if (action === "send") { if (action === "send") {
const actionConfig = account.config.actions ?? (cfg as OpenClawConfig).channels?.["googlechat"]?.actions;
const isActionEnabled = createActionGate(actionConfig as Record<string, boolean | undefined>);
if (!isActionEnabled("sendMessage")) {
throw new Error("Google Chat sendMessage is disabled.");
}
const to = readStringParam(params, "to", { required: true }); const to = readStringParam(params, "to", { required: true });
const content = readStringParam(params, "message", { const content = readStringParam(params, "message", {
required: true, required: true,

View File

@ -51,6 +51,11 @@ export const matrixMessageActions: ChannelMessageActionAdapter = {
readStringParam(params, "to", { required: true }); readStringParam(params, "to", { required: true });
if (action === "send") { 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 to = readStringParam(params, "to", { required: true });
const content = readStringParam(params, "message", { const content = readStringParam(params, "message", {
required: true, required: true,

View File

@ -6,6 +6,7 @@ const allowFromEntry = z.union([z.string(), z.number()]);
const matrixActionSchema = z const matrixActionSchema = z
.object({ .object({
reactions: z.boolean().optional(), reactions: z.boolean().optional(),
sendMessage: z.boolean().optional(),
messages: z.boolean().optional(), messages: z.boolean().optional(),
pins: z.boolean().optional(), pins: z.boolean().optional(),
memberInfo: z.boolean().optional(), memberInfo: z.boolean().optional(),

View File

@ -222,8 +222,8 @@ export async function handleDiscordMessagingAction(
}); });
} }
case "sendMessage": { case "sendMessage": {
if (!isActionEnabled("messages")) { if (!isActionEnabled("sendMessage")) {
throw new Error("Discord message sends are disabled."); throw new Error("Discord sendMessage is disabled.");
} }
const to = readStringParam(params, "to", { required: true }); const to = readStringParam(params, "to", { required: true });
const content = readStringParam(params, "content", { const content = readStringParam(params, "content", {

View File

@ -154,6 +154,9 @@ export async function handleSlackAction(
} }
switch (action) { switch (action) {
case "sendMessage": { case "sendMessage": {
if (!isActionEnabled("sendMessage")) {
throw new Error("Slack sendMessage is disabled.");
}
const to = readStringParam(params, "to", { required: true }); const to = readStringParam(params, "to", { required: true });
const content = readStringParam(params, "content", { required: true }); const content = readStringParam(params, "content", { required: true });
const mediaUrl = readStringParam(params, "mediaUrl"); const mediaUrl = readStringParam(params, "mediaUrl");

View File

@ -55,6 +55,7 @@ export type DiscordGuildEntry = {
export type DiscordActionConfig = { export type DiscordActionConfig = {
reactions?: boolean; reactions?: boolean;
sendMessage?: boolean;
stickers?: boolean; stickers?: boolean;
polls?: boolean; polls?: boolean;
permissions?: boolean; permissions?: boolean;

View File

@ -30,6 +30,7 @@ export type GoogleChatGroupConfig = {
export type GoogleChatActionConfig = { export type GoogleChatActionConfig = {
reactions?: boolean; reactions?: boolean;
sendMessage?: boolean;
}; };
export type GoogleChatAccountConfig = { export type GoogleChatAccountConfig = {

View File

@ -48,6 +48,7 @@ export type SlackReactionNotificationMode = "off" | "own" | "all" | "allowlist";
export type SlackActionConfig = { export type SlackActionConfig = {
reactions?: boolean; reactions?: boolean;
sendMessage?: boolean;
messages?: boolean; messages?: boolean;
pins?: boolean; pins?: boolean;
search?: boolean; search?: boolean;