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") {
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 content = readStringParam(params, "message", {
required: true,

View File

@ -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,

View File

@ -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(),

View File

@ -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", {

View File

@ -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");

View File

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

View File

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

View File

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