refactor(slack): share plugin base config

This commit is contained in:
Peter Steinberger 2026-03-17 01:46:21 +00:00
parent f90d432de3
commit 75b8117f83
3 changed files with 115 additions and 125 deletions

View File

@ -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<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) => 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,
}),
};

View File

@ -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<string, "channel" | "group" | "dm" | "unknown">();
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<ResolvedSlackAccount> = {
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<ResolvedSlackAccount> = {
}
},
},
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) => 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<ResolvedSlackAccount> = {
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,

View File

@ -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<ChannelPlugin<ResolvedSlackAccount>["setupWizard"]>;
setup: NonNullable<ChannelPlugin<ResolvedSlackAccount>["setup"]>;
}): Pick<
ChannelPlugin<ResolvedSlackAccount>,
| "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.<account>.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,
};
}