From 75b8117f8352f9c36fdf83fef472a5cdf138001d Mon Sep 17 00:00:00 2001 From: Peter Steinberger Date: Tue, 17 Mar 2026 01:46:21 +0000 Subject: [PATCH] refactor(slack): share plugin base config --- extensions/slack/src/channel.setup.ts | 72 +++++------------------ extensions/slack/src/channel.ts | 84 ++++++++------------------- extensions/slack/src/shared.ts | 84 ++++++++++++++++++++++++--- 3 files changed, 115 insertions(+), 125 deletions(-) diff --git a/extensions/slack/src/channel.setup.ts b/extensions/slack/src/channel.setup.ts index f523e2a4d71..003c33e04b4 100644 --- a/extensions/slack/src/channel.setup.ts +++ b/extensions/slack/src/channel.setup.ts @@ -1,61 +1,19 @@ -import { - buildChannelConfigSchema, - getChatChannelMeta, - SlackConfigSchema, - type ChannelPlugin, -} from "../../../src/plugin-sdk-internal/slack.js"; +import { type ChannelPlugin } from "openclaw/plugin-sdk/slack"; import { type ResolvedSlackAccount } from "./accounts.js"; -import { isSlackInteractiveRepliesEnabled } from "./interactive-replies.js"; -import { - isSlackPluginAccountConfigured, - slackConfigAccessors, - slackConfigBase, - slackSetupWizard, -} from "./plugin-shared.js"; -import { slackSetupAdapter } from "./setup-core.js"; +import { createSlackSetupWizardProxy, slackSetupAdapter } from "./setup-core.js"; +import { createSlackPluginBase } from "./shared.js"; + +async function loadSlackChannelRuntime() { + return await import("./channel.runtime.js"); +} + +const slackSetupWizard = createSlackSetupWizardProxy(async () => ({ + slackSetupWizard: (await loadSlackChannelRuntime()).slackSetupWizard, +})); export const slackSetupPlugin: ChannelPlugin = { - 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..capabilities`).", - ], - }, - streaming: { - blockStreamingCoalesceDefaults: { minChars: 1500, idleMs: 1000 }, - }, - reload: { configPrefixes: ["channels.slack"] }, - configSchema: buildChannelConfigSchema(SlackConfigSchema), - config: { - ...slackConfigBase, - isConfigured: (account) => isSlackPluginAccountConfigured(account), - describeAccount: (account) => ({ - accountId: account.accountId, - name: account.name, - enabled: account.enabled, - configured: isSlackPluginAccountConfigured(account), - botTokenSource: account.botTokenSource, - appTokenSource: account.appTokenSource, - }), - ...slackConfigAccessors, - }, - setup: slackSetupAdapter, + ...createSlackPluginBase({ + setupWizard: slackSetupWizard, + setup: slackSetupAdapter, + }), }; diff --git a/extensions/slack/src/channel.ts b/extensions/slack/src/channel.ts index 8005a29f76f..e1c515576d9 100644 --- a/extensions/slack/src/channel.ts +++ b/extensions/slack/src/channel.ts @@ -1,20 +1,17 @@ -import { resolveOutboundSendDep } from "../../../src/infra/outbound/send-deps.js"; import { buildAccountScopedAllowlistConfigEditor, buildAccountScopedDmSecurityPolicy, - collectOpenGroupPolicyConfiguredRouteWarnings, collectOpenProviderGroupPolicyWarnings, -} from "../../../src/plugin-sdk-internal/channel-config.js"; + collectOpenGroupPolicyConfiguredRouteWarnings, +} from "openclaw/plugin-sdk/compat"; import { buildAgentSessionKey, resolveThreadSessionKeys, type RoutePeer, -} from "../../../src/plugin-sdk-internal/core.js"; +} from "openclaw/plugin-sdk/core"; import { buildComputedAccountStatusSnapshot, - buildChannelConfigSchema, DEFAULT_ACCOUNT_ID, - getChatChannelMeta, listSlackDirectoryGroupsFromConfig, listSlackDirectoryPeersFromConfig, looksLikeSlackTargetId, @@ -24,10 +21,10 @@ import { resolveConfiguredFromRequiredCredentialStatuses, resolveSlackGroupRequireMention, resolveSlackGroupToolPolicy, - SlackConfigSchema, type ChannelPlugin, type OpenClawConfig, -} from "../../../src/plugin-sdk-internal/slack.js"; +} from "openclaw/plugin-sdk/slack"; +import { resolveOutboundSendDep } from "../../../src/infra/outbound/send-deps.js"; import { buildPassiveProbedChannelStatusSummary } from "../../shared/channel-status-summary.js"; import { listEnabledSlackAccounts, @@ -41,23 +38,25 @@ import { isSlackInteractiveRepliesEnabled } from "./interactive-replies.js"; import { handleSlackMessageAction } from "./message-action-dispatch.js"; import { extractSlackToolSend, listSlackMessageActions } from "./message-actions.js"; import { normalizeAllowListLower } from "./monitor/allow-list.js"; -import { - isSlackPluginAccountConfigured, - slackConfigAccessors, - slackConfigBase, - slackSetupWizard, -} from "./plugin-shared.js"; import type { SlackProbe } from "./probe.js"; import { resolveSlackUserAllowlist } from "./resolve-users.js"; import { getSlackRuntime } from "./runtime.js"; import { fetchSlackScopes } from "./scopes.js"; -import { slackSetupAdapter } from "./setup-core.js"; +import { createSlackSetupWizardProxy, slackSetupAdapter } from "./setup-core.js"; +import { + createSlackPluginBase, + isSlackPluginAccountConfigured, + slackConfigAccessors, +} from "./shared.js"; import { parseSlackTarget } from "./targets.js"; import { buildSlackThreadingToolContext } from "./threading-tool-context.js"; -const meta = getChatChannelMeta("slack"); const SLACK_CHANNEL_TYPE_CACHE = new Map(); +async function loadSlackChannelRuntime() { + return await import("./channel.runtime.js"); +} + // Select the appropriate Slack token for read/write operations. function getTokenForOperation( account: ResolvedSlackAccount, @@ -329,13 +328,15 @@ async function resolveSlackAllowlistNames(params: { return await resolveSlackUserAllowlist({ token, entries: params.entries }); } +const slackSetupWizard = createSlackSetupWizardProxy(async () => ({ + slackSetupWizard: (await loadSlackChannelRuntime()).slackSetupWizard, +})); + export const slackPlugin: ChannelPlugin = { - id: "slack", - meta: { - ...meta, - preferSessionLookupForAnnounceTarget: true, - }, - setupWizard: slackSetupWizard, + ...createSlackPluginBase({ + setupWizard: slackSetupWizard, + setup: slackSetupAdapter, + }), pairing: { idLabel: "slackUserId", normalizeAllowEntry: (entry) => entry.replace(/^(slack|user):/i, ""), @@ -364,42 +365,6 @@ export const slackPlugin: ChannelPlugin = { } }, }, - 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..capabilities`).", - ], - }, - streaming: { - blockStreamingCoalesceDefaults: { minChars: 1500, idleMs: 1000 }, - }, - reload: { configPrefixes: ["channels.slack"] }, - configSchema: buildChannelConfigSchema(SlackConfigSchema), - config: { - ...slackConfigBase, - isConfigured: (account) => isSlackPluginAccountConfigured(account), - describeAccount: (account) => ({ - accountId: account.accountId, - name: account.name, - enabled: account.enabled, - configured: isSlackPluginAccountConfigured(account), - botTokenSource: account.botTokenSource, - appTokenSource: account.appTokenSource, - }), - ...slackConfigAccessors, - }, allowlist: { supportsScope: ({ scope }) => scope === "dm", readConfig: ({ cfg, accountId }) => @@ -569,14 +534,13 @@ export const slackPlugin: ChannelPlugin = { extractToolSend: ({ args }) => extractSlackToolSend(args), handleAction: async (ctx) => await handleSlackMessageAction({ - providerId: meta.id, + providerId: "slack", ctx, includeReadThreadId: true, invoke: async (action, cfg, toolContext) => await getSlackRuntime().channel.slack.handleSlackAction(action, cfg, toolContext), }), }, - setup: slackSetupAdapter, outbound: { deliveryMode: "direct", chunker: null, diff --git a/extensions/slack/src/shared.ts b/extensions/slack/src/shared.ts index de7238a7a78..e7276da9ae1 100644 --- a/extensions/slack/src/shared.ts +++ b/extensions/slack/src/shared.ts @@ -1,14 +1,18 @@ -import { formatAllowFromLowercase } from "openclaw/plugin-sdk/allow-from"; +import { + buildChannelConfigSchema, + getChatChannelMeta, + SlackConfigSchema, + type ChannelPlugin, +} from "openclaw/plugin-sdk/slack"; +import { patchChannelConfigForAccount } from "../../../src/channels/plugins/setup-wizard-helpers.js"; +import type { OpenClawConfig } from "../../../src/config/config.js"; +import { hasConfiguredSecretInput } from "../../../src/config/types.secrets.js"; +import { formatAllowFromLowercase } from "../../../src/plugin-sdk/allow-from.js"; import { createScopedAccountConfigAccessors, createScopedChannelConfigBase, -} from "openclaw/plugin-sdk/channel-config-helpers"; -import { - formatDocsLink, - hasConfiguredSecretInput, - patchChannelConfigForAccount, - type OpenClawConfig, -} from "openclaw/plugin-sdk/setup"; +} from "../../../src/plugin-sdk/channel-config-helpers.js"; +import { formatDocsLink } from "../../../src/terminal/links.js"; import { inspectSlackAccount } from "./account-inspect.js"; import { listSlackAccountIds, @@ -16,6 +20,7 @@ import { resolveSlackAccount, type ResolvedSlackAccount, } from "./accounts.js"; +import { isSlackInteractiveRepliesEnabled } from "./interactive-replies.js"; export const SLACK_CHANNEL = "slack" as const; @@ -152,3 +157,66 @@ export const slackConfigBase = createScopedChannelConfigBase({ defaultAccountId: resolveDefaultSlackAccountId, clearBaseFields: ["botToken", "appToken", "name"], }); + +export function createSlackPluginBase(params: { + setupWizard: NonNullable["setupWizard"]>; + setup: NonNullable["setup"]>; +}): Pick< + ChannelPlugin, + | "id" + | "meta" + | "setupWizard" + | "capabilities" + | "agentPrompt" + | "streaming" + | "reload" + | "configSchema" + | "config" + | "setup" +> { + return { + id: SLACK_CHANNEL, + meta: { + ...getChatChannelMeta(SLACK_CHANNEL), + preferSessionLookupForAnnounceTarget: true, + }, + setupWizard: params.setupWizard, + 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..capabilities`).", + ], + }, + streaming: { + blockStreamingCoalesceDefaults: { minChars: 1500, idleMs: 1000 }, + }, + reload: { configPrefixes: ["channels.slack"] }, + configSchema: buildChannelConfigSchema(SlackConfigSchema), + config: { + ...slackConfigBase, + isConfigured: (account) => isSlackPluginAccountConfigured(account), + describeAccount: (account) => ({ + accountId: account.accountId, + name: account.name, + enabled: account.enabled, + configured: isSlackPluginAccountConfigured(account), + botTokenSource: account.botTokenSource, + appTokenSource: account.appTokenSource, + }), + ...slackConfigAccessors, + }, + setup: params.setup, + }; +}