From af414e2e731e140a0ac5391e7c35795a1c89de59 Mon Sep 17 00:00:00 2001 From: Vincent Koc Date: Thu, 12 Mar 2026 23:42:16 -0400 Subject: [PATCH] Config: validate Slack interactive reply capability --- src/config/channel-capabilities.ts | 3 ++- src/config/zod-schema.providers-core.ts | 10 ++++++- src/plugin-sdk/slack.ts | 1 + src/slack/interactive-replies.ts | 35 +++++++++++++++++++++++++ 4 files changed, 47 insertions(+), 2 deletions(-) create mode 100644 src/slack/interactive-replies.ts diff --git a/src/config/channel-capabilities.ts b/src/config/channel-capabilities.ts index 0e66f755e3b..b7edc354596 100644 --- a/src/config/channel-capabilities.ts +++ b/src/config/channel-capabilities.ts @@ -2,9 +2,10 @@ import { normalizeChannelId } from "../channels/plugins/index.js"; import { resolveAccountEntry } from "../routing/account-lookup.js"; import { normalizeAccountId } from "../routing/session-key.js"; import type { OpenClawConfig } from "./config.js"; +import type { SlackCapabilitiesConfig } from "./types.slack.js"; import type { TelegramCapabilitiesConfig } from "./types.telegram.js"; -type CapabilitiesConfig = TelegramCapabilitiesConfig; +type CapabilitiesConfig = TelegramCapabilitiesConfig | SlackCapabilitiesConfig; const isStringArray = (value: unknown): value is string[] => Array.isArray(value) && value.every((entry) => typeof entry === "string"); diff --git a/src/config/zod-schema.providers-core.ts b/src/config/zod-schema.providers-core.ts index 2b2fccee310..125cdb432ef 100644 --- a/src/config/zod-schema.providers-core.ts +++ b/src/config/zod-schema.providers-core.ts @@ -59,6 +59,14 @@ const TelegramCapabilitiesSchema = z.union([ }) .strict(), ]); +const SlackCapabilitiesSchema = z.union([ + z.array(z.string()), + z + .object({ + interactiveReplies: z.boolean().optional(), + }) + .strict(), +]); export const TelegramTopicSchema = z .object({ @@ -831,7 +839,7 @@ export const SlackAccountSchema = z mode: z.enum(["socket", "http"]).optional(), signingSecret: SecretInputSchema.optional().register(sensitive), webhookPath: z.string().optional(), - capabilities: z.array(z.string()).optional(), + capabilities: SlackCapabilitiesSchema.optional(), markdown: MarkdownConfigSchema, enabled: z.boolean().optional(), commands: ProviderCommandsSchema, diff --git a/src/plugin-sdk/slack.ts b/src/plugin-sdk/slack.ts index 18cf529ca45..c3aabde6fe2 100644 --- a/src/plugin-sdk/slack.ts +++ b/src/plugin-sdk/slack.ts @@ -8,6 +8,7 @@ export { resolveSlackAccount, resolveSlackReplyToMode, } from "../slack/accounts.js"; +export { isSlackInteractiveRepliesEnabled } from "../slack/interactive-replies.js"; export { inspectSlackAccount } from "../slack/account-inspect.js"; export { projectCredentialSnapshotFields, diff --git a/src/slack/interactive-replies.ts b/src/slack/interactive-replies.ts new file mode 100644 index 00000000000..94ddaf921ab --- /dev/null +++ b/src/slack/interactive-replies.ts @@ -0,0 +1,35 @@ +import type { OpenClawConfig } from "../config/config.js"; +import { listSlackAccountIds, resolveSlackAccount } from "./accounts.js"; + +function resolveInteractiveRepliesFromCapabilities(capabilities: unknown): boolean { + if (!capabilities) { + return false; + } + if (Array.isArray(capabilities)) { + return capabilities.some( + (entry) => String(entry).trim().toLowerCase() === "interactivereplies", + ); + } + if (typeof capabilities === "object") { + return (capabilities as { interactiveReplies?: unknown }).interactiveReplies === true; + } + return false; +} + +export function isSlackInteractiveRepliesEnabled(params: { + cfg: OpenClawConfig; + accountId?: string | null; +}): boolean { + if (params.accountId) { + const account = resolveSlackAccount({ cfg: params.cfg, accountId: params.accountId }); + return resolveInteractiveRepliesFromCapabilities(account.config.capabilities); + } + const accountIds = listSlackAccountIds(params.cfg); + if (accountIds.length === 0) { + return resolveInteractiveRepliesFromCapabilities(params.cfg.channels?.slack?.capabilities); + } + return accountIds.some((accountId) => { + const account = resolveSlackAccount({ cfg: params.cfg, accountId }); + return resolveInteractiveRepliesFromCapabilities(account.config.capabilities); + }); +}