openclaw/extensions/slack/src/channel.setup.ts
2026-03-15 19:47:58 -07:00

101 lines
3.6 KiB
TypeScript

import { createScopedChannelConfigBase } from "openclaw/plugin-sdk/compat";
import {
createScopedAccountConfigAccessors,
formatAllowFromLowercase,
} from "openclaw/plugin-sdk/compat";
import {
buildChannelConfigSchema,
getChatChannelMeta,
inspectSlackAccount,
isSlackInteractiveRepliesEnabled,
listSlackAccountIds,
resolveDefaultSlackAccountId,
resolveSlackAccount,
SlackConfigSchema,
type ChannelPlugin,
type ResolvedSlackAccount,
} from "openclaw/plugin-sdk/slack";
import { createSlackSetupWizardProxy, slackSetupAdapter } from "./setup-core.js";
async function loadSlackChannelRuntime() {
return await import("./channel.runtime.js");
}
function isSlackAccountConfigured(account: ResolvedSlackAccount): boolean {
const mode = account.config.mode ?? "socket";
const hasBotToken = Boolean(account.botToken?.trim());
if (!hasBotToken) {
return false;
}
if (mode === "http") {
return Boolean(account.config.signingSecret?.trim());
}
return Boolean(account.appToken?.trim());
}
const slackConfigAccessors = createScopedAccountConfigAccessors({
resolveAccount: ({ cfg, accountId }) => resolveSlackAccount({ cfg, accountId }),
resolveAllowFrom: (account: ResolvedSlackAccount) => account.dm?.allowFrom,
formatAllowFrom: (allowFrom) => formatAllowFromLowercase({ allowFrom }),
resolveDefaultTo: (account: ResolvedSlackAccount) => account.config.defaultTo,
});
const slackConfigBase = createScopedChannelConfigBase({
sectionKey: "slack",
listAccountIds: listSlackAccountIds,
resolveAccount: (cfg, accountId) => resolveSlackAccount({ cfg, accountId }),
inspectAccount: (cfg, accountId) => inspectSlackAccount({ cfg, accountId }),
defaultAccountId: resolveDefaultSlackAccountId,
clearBaseFields: ["botToken", "appToken", "name"],
});
const slackSetupWizard = createSlackSetupWizardProxy(async () => ({
slackSetupWizard: (await loadSlackChannelRuntime()).slackSetupWizard,
}));
export const slackSetupPlugin: ChannelPlugin<ResolvedSlackAccount> = {
id: "slack",
meta: {
...getChatChannelMeta("slack"),
preferSessionLookupForAnnounceTarget: true,
},
setupWizard: slackSetupWizard,
capabilities: {
chatTypes: ["direct", "channel", "thread"],
reactions: true,
threads: true,
media: true,
nativeCommands: true,
},
agentPrompt: {
messageToolHints: ({ cfg, accountId }) =>
isSlackInteractiveRepliesEnabled({ cfg, accountId })
? [
"- Slack interactive replies: use `[[slack_buttons: Label:value, Other:other]]` to add action buttons that route clicks back as Slack interaction system events.",
"- Slack selects: use `[[slack_select: Placeholder | Label:value, Other:other]]` to add a static select menu that routes the chosen value back as a Slack interaction system event.",
]
: [
"- Slack interactive replies are disabled. If needed, ask to set `channels.slack.capabilities.interactiveReplies=true` (or the same under `channels.slack.accounts.<account>.capabilities`).",
],
},
streaming: {
blockStreamingCoalesceDefaults: { minChars: 1500, idleMs: 1000 },
},
reload: { configPrefixes: ["channels.slack"] },
configSchema: buildChannelConfigSchema(SlackConfigSchema),
config: {
...slackConfigBase,
isConfigured: (account) => isSlackAccountConfigured(account),
describeAccount: (account) => ({
accountId: account.accountId,
name: account.name,
enabled: account.enabled,
configured: isSlackAccountConfigured(account),
botTokenSource: account.botTokenSource,
appTokenSource: account.appTokenSource,
}),
...slackConfigAccessors,
},
setup: slackSetupAdapter,
};