refactor(slack): share message action helpers

This commit is contained in:
Peter Steinberger
2026-02-15 00:49:27 +00:00
parent eccd4d8c39
commit f835eb32f3
4 changed files with 71 additions and 125 deletions

View File

@@ -1,12 +1,12 @@
import {
applyAccountNameToChannelSection,
buildChannelConfigSchema,
createActionGate,
DEFAULT_ACCOUNT_ID,
deleteAccountFromConfigSection,
extractSlackToolSend,
formatPairingApproveHint,
getChatChannelMeta,
listEnabledSlackAccounts,
listSlackMessageActions,
listSlackAccountIds,
listSlackDirectoryGroupsFromConfig,
listSlackDirectoryPeersFromConfig,
@@ -26,7 +26,6 @@ import {
setAccountEnabledInConfigSection,
slackOnboardingAdapter,
SlackConfigSchema,
type ChannelMessageActionName,
type ChannelPlugin,
type ResolvedSlackAccount,
} from "openclaw/plugin-sdk";
@@ -233,63 +232,8 @@ export const slackPlugin: ChannelPlugin<ResolvedSlackAccount> = {
},
},
actions: {
listActions: ({ cfg }) => {
const accounts = listEnabledSlackAccounts(cfg).filter(
(account) => account.botTokenSource !== "none",
);
if (accounts.length === 0) {
return [];
}
const isActionEnabled = (key: string, defaultValue = true) => {
for (const account of accounts) {
const gate = createActionGate(
(account.actions ?? cfg.channels?.slack?.actions) as Record<
string,
boolean | undefined
>,
);
if (gate(key, defaultValue)) {
return true;
}
}
return false;
};
const actions = new Set<ChannelMessageActionName>(["send"]);
if (isActionEnabled("reactions")) {
actions.add("react");
actions.add("reactions");
}
if (isActionEnabled("messages")) {
actions.add("read");
actions.add("edit");
actions.add("delete");
}
if (isActionEnabled("pins")) {
actions.add("pin");
actions.add("unpin");
actions.add("list-pins");
}
if (isActionEnabled("memberInfo")) {
actions.add("member-info");
}
if (isActionEnabled("emojiList")) {
actions.add("emoji-list");
}
return Array.from(actions);
},
extractToolSend: ({ args }) => {
const action = typeof args.action === "string" ? args.action.trim() : "";
if (action !== "sendMessage") {
return null;
}
const to = typeof args.to === "string" ? args.to : undefined;
if (!to) {
return null;
}
const accountId = typeof args.accountId === "string" ? args.accountId.trim() : undefined;
return { to, accountId };
},
listActions: ({ cfg }) => listSlackMessageActions(cfg),
extractToolSend: ({ args }) => extractSlackToolSend(args),
handleAction: async ({ action, params, cfg, accountId, toolContext }) => {
const resolveChannelId = () =>
readStringParam(params, "channelId") ?? readStringParam(params, "to", { required: true });

View File

@@ -1,73 +1,13 @@
import type {
ChannelMessageActionAdapter,
ChannelMessageActionContext,
ChannelMessageActionName,
ChannelToolSend,
} from "./types.js";
import { createActionGate, readNumberParam, readStringParam } from "../../agents/tools/common.js";
import type { ChannelMessageActionAdapter, ChannelMessageActionContext } from "./types.js";
import { readNumberParam, readStringParam } from "../../agents/tools/common.js";
import { handleSlackAction, type SlackActionContext } from "../../agents/tools/slack-actions.js";
import { listEnabledSlackAccounts } from "../../slack/accounts.js";
import { extractSlackToolSend, listSlackMessageActions } from "../../slack/message-actions.js";
import { resolveSlackChannelId } from "../../slack/targets.js";
export function createSlackActions(providerId: string): ChannelMessageActionAdapter {
return {
listActions: ({ cfg }) => {
const accounts = listEnabledSlackAccounts(cfg).filter(
(account) => account.botTokenSource !== "none",
);
if (accounts.length === 0) {
return [];
}
const isActionEnabled = (key: string, defaultValue = true) => {
for (const account of accounts) {
const gate = createActionGate(
(account.actions ?? cfg.channels?.slack?.actions) as Record<
string,
boolean | undefined
>,
);
if (gate(key, defaultValue)) {
return true;
}
}
return false;
};
const actions = new Set<ChannelMessageActionName>(["send"]);
if (isActionEnabled("reactions")) {
actions.add("react");
actions.add("reactions");
}
if (isActionEnabled("messages")) {
actions.add("read");
actions.add("edit");
actions.add("delete");
}
if (isActionEnabled("pins")) {
actions.add("pin");
actions.add("unpin");
actions.add("list-pins");
}
if (isActionEnabled("memberInfo")) {
actions.add("member-info");
}
if (isActionEnabled("emojiList")) {
actions.add("emoji-list");
}
return Array.from(actions);
},
extractToolSend: ({ args }): ChannelToolSend | null => {
const action = typeof args.action === "string" ? args.action.trim() : "";
if (action !== "sendMessage") {
return null;
}
const to = typeof args.to === "string" ? args.to : undefined;
if (!to) {
return null;
}
const accountId = typeof args.accountId === "string" ? args.accountId.trim() : undefined;
return { to, accountId };
},
listActions: ({ cfg }) => listSlackMessageActions(cfg),
extractToolSend: ({ args }) => extractSlackToolSend(args),
handleAction: async (ctx: ChannelMessageActionContext) => {
const { action, params, cfg } = ctx;
const accountId = ctx.accountId ?? undefined;

View File

@@ -323,6 +323,7 @@ export {
resolveSlackReplyToMode,
type ResolvedSlackAccount,
} from "../slack/accounts.js";
export { extractSlackToolSend, listSlackMessageActions } from "../slack/message-actions.js";
export { slackOnboardingAdapter } from "../channels/plugins/onboarding/slack.js";
export {
looksLikeSlackTargetId,

View File

@@ -0,0 +1,61 @@
import type { ChannelMessageActionName, ChannelToolSend } from "../channels/plugins/types.js";
import type { OpenClawConfig } from "../config/config.js";
import { createActionGate } from "../agents/tools/common.js";
import { listEnabledSlackAccounts } from "./accounts.js";
export function listSlackMessageActions(cfg: OpenClawConfig): ChannelMessageActionName[] {
const accounts = listEnabledSlackAccounts(cfg).filter(
(account) => account.botTokenSource !== "none",
);
if (accounts.length === 0) {
return [];
}
const isActionEnabled = (key: string, defaultValue = true) => {
for (const account of accounts) {
const gate = createActionGate(
(account.actions ?? cfg.channels?.slack?.actions) as Record<string, boolean | undefined>,
);
if (gate(key, defaultValue)) {
return true;
}
}
return false;
};
const actions = new Set<ChannelMessageActionName>(["send"]);
if (isActionEnabled("reactions")) {
actions.add("react");
actions.add("reactions");
}
if (isActionEnabled("messages")) {
actions.add("read");
actions.add("edit");
actions.add("delete");
}
if (isActionEnabled("pins")) {
actions.add("pin");
actions.add("unpin");
actions.add("list-pins");
}
if (isActionEnabled("memberInfo")) {
actions.add("member-info");
}
if (isActionEnabled("emojiList")) {
actions.add("emoji-list");
}
return Array.from(actions);
}
export function extractSlackToolSend(args: Record<string, unknown>): ChannelToolSend | null {
const action = typeof args.action === "string" ? args.action.trim() : "";
if (action !== "sendMessage") {
return null;
}
const to = typeof args.to === "string" ? args.to : undefined;
if (!to) {
return null;
}
const accountId = typeof args.accountId === "string" ? args.accountId.trim() : undefined;
return { to, accountId };
}