refactor: route channel runtime via plugin api

This commit is contained in:
Peter Steinberger
2026-01-18 11:00:19 +00:00
parent 676d41d415
commit ee6e534ccb
82 changed files with 1253 additions and 3167 deletions

View File

@@ -1,12 +1,14 @@
import type { ClawdbotPluginApi } from "clawdbot/plugin-sdk";
import { slackPlugin } from "./src/channel.js";
import { setSlackRuntime } from "./src/runtime.js";
const plugin = {
id: "slack",
name: "Slack",
description: "Slack channel plugin",
register(api: ClawdbotPluginApi) {
setSlackRuntime(api.runtime);
api.registerChannel({ plugin: slackPlugin });
},
};

View File

@@ -6,28 +6,20 @@ import {
deleteAccountFromConfigSection,
formatPairingApproveHint,
getChatChannelMeta,
handleSlackAction,
loadConfig,
listEnabledSlackAccounts,
listSlackAccountIds,
listSlackDirectoryGroupsFromConfig,
listSlackDirectoryGroupsLive,
listSlackDirectoryPeersFromConfig,
listSlackDirectoryPeersLive,
looksLikeSlackTargetId,
migrateBaseNameToDefaultAccount,
normalizeAccountId,
normalizeSlackMessagingTarget,
PAIRING_APPROVED_MESSAGE,
probeSlack,
readNumberParam,
readStringParam,
resolveDefaultSlackAccountId,
resolveSlackAccount,
resolveSlackChannelAllowlist,
resolveSlackGroupRequireMention,
resolveSlackUserAllowlist,
sendMessageSlack,
setAccountEnabledInConfigSection,
slackOnboardingAdapter,
SlackConfigSchema,
@@ -36,6 +28,8 @@ import {
type ResolvedSlackAccount,
} from "clawdbot/plugin-sdk";
import { getSlackRuntime } from "./runtime.js";
const meta = getChatChannelMeta("slack");
// Select the appropriate Slack token for read/write operations.
@@ -61,7 +55,7 @@ export const slackPlugin: ChannelPlugin<ResolvedSlackAccount> = {
idLabel: "slackUserId",
normalizeAllowEntry: (entry) => entry.replace(/^(slack|user):/i, ""),
notifyApproval: async ({ id }) => {
const cfg = loadConfig();
const cfg = getSlackRuntime().config.loadConfig();
const account = resolveSlackAccount({
cfg,
accountId: DEFAULT_ACCOUNT_ID,
@@ -70,11 +64,11 @@ export const slackPlugin: ChannelPlugin<ResolvedSlackAccount> = {
const botToken = account.botToken?.trim();
const tokenOverride = token && token !== botToken ? token : undefined;
if (tokenOverride) {
await sendMessageSlack(`user:${id}`, PAIRING_APPROVED_MESSAGE, {
await getSlackRuntime().channel.slack.sendMessageSlack(`user:${id}`, PAIRING_APPROVED_MESSAGE, {
token: tokenOverride,
});
} else {
await sendMessageSlack(`user:${id}`, PAIRING_APPROVED_MESSAGE);
await getSlackRuntime().channel.slack.sendMessageSlack(`user:${id}`, PAIRING_APPROVED_MESSAGE);
}
},
},
@@ -194,8 +188,9 @@ export const slackPlugin: ChannelPlugin<ResolvedSlackAccount> = {
self: async () => null,
listPeers: async (params) => listSlackDirectoryPeersFromConfig(params),
listGroups: async (params) => listSlackDirectoryGroupsFromConfig(params),
listPeersLive: async (params) => listSlackDirectoryPeersLive(params),
listGroupsLive: async (params) => listSlackDirectoryGroupsLive(params),
listPeersLive: async (params) => getSlackRuntime().channel.slack.listDirectoryPeersLive(params),
listGroupsLive: async (params) =>
getSlackRuntime().channel.slack.listDirectoryGroupsLive(params),
},
resolver: {
resolveTargets: async ({ cfg, accountId, inputs, kind }) => {
@@ -209,7 +204,10 @@ export const slackPlugin: ChannelPlugin<ResolvedSlackAccount> = {
}));
}
if (kind === "group") {
const resolved = await resolveSlackChannelAllowlist({ token, entries: inputs });
const resolved = await getSlackRuntime().channel.slack.resolveChannelAllowlist({
token,
entries: inputs,
});
return resolved.map((entry) => ({
input: entry.input,
resolved: entry.resolved,
@@ -218,7 +216,10 @@ export const slackPlugin: ChannelPlugin<ResolvedSlackAccount> = {
note: entry.archived ? "archived" : undefined,
}));
}
const resolved = await resolveSlackUserAllowlist({ token, entries: inputs });
const resolved = await getSlackRuntime().channel.slack.resolveUserAllowlist({
token,
entries: inputs,
});
return resolved.map((entry) => ({
input: entry.input,
resolved: entry.resolved,
@@ -284,7 +285,7 @@ export const slackPlugin: ChannelPlugin<ResolvedSlackAccount> = {
const mediaUrl = readStringParam(params, "media", { trim: false });
const threadId = readStringParam(params, "threadId");
const replyTo = readStringParam(params, "replyTo");
return await handleSlackAction(
return await getSlackRuntime().channel.slack.handleSlackAction(
{
action: "sendMessage",
to,
@@ -304,7 +305,7 @@ export const slackPlugin: ChannelPlugin<ResolvedSlackAccount> = {
});
const emoji = readStringParam(params, "emoji", { allowEmpty: true });
const remove = typeof params.remove === "boolean" ? params.remove : undefined;
return await handleSlackAction(
return await getSlackRuntime().channel.slack.handleSlackAction(
{
action: "react",
channelId: resolveChannelId(),
@@ -322,7 +323,7 @@ export const slackPlugin: ChannelPlugin<ResolvedSlackAccount> = {
required: true,
});
const limit = readNumberParam(params, "limit", { integer: true });
return await handleSlackAction(
return await getSlackRuntime().channel.slack.handleSlackAction(
{
action: "reactions",
channelId: resolveChannelId(),
@@ -336,7 +337,7 @@ export const slackPlugin: ChannelPlugin<ResolvedSlackAccount> = {
if (action === "read") {
const limit = readNumberParam(params, "limit", { integer: true });
return await handleSlackAction(
return await getSlackRuntime().channel.slack.handleSlackAction(
{
action: "readMessages",
channelId: resolveChannelId(),
@@ -354,7 +355,7 @@ export const slackPlugin: ChannelPlugin<ResolvedSlackAccount> = {
required: true,
});
const content = readStringParam(params, "message", { required: true });
return await handleSlackAction(
return await getSlackRuntime().channel.slack.handleSlackAction(
{
action: "editMessage",
channelId: resolveChannelId(),
@@ -370,7 +371,7 @@ export const slackPlugin: ChannelPlugin<ResolvedSlackAccount> = {
const messageId = readStringParam(params, "messageId", {
required: true,
});
return await handleSlackAction(
return await getSlackRuntime().channel.slack.handleSlackAction(
{
action: "deleteMessage",
channelId: resolveChannelId(),
@@ -386,7 +387,7 @@ export const slackPlugin: ChannelPlugin<ResolvedSlackAccount> = {
action === "list-pins"
? undefined
: readStringParam(params, "messageId", { required: true });
return await handleSlackAction(
return await getSlackRuntime().channel.slack.handleSlackAction(
{
action:
action === "pin" ? "pinMessage" : action === "unpin" ? "unpinMessage" : "listPins",
@@ -400,14 +401,14 @@ export const slackPlugin: ChannelPlugin<ResolvedSlackAccount> = {
if (action === "member-info") {
const userId = readStringParam(params, "userId", { required: true });
return await handleSlackAction(
return await getSlackRuntime().channel.slack.handleSlackAction(
{ action: "memberInfo", userId, accountId: accountId ?? undefined },
cfg,
);
}
if (action === "emoji-list") {
return await handleSlackAction(
return await getSlackRuntime().channel.slack.handleSlackAction(
{ action: "emojiList", accountId: accountId ?? undefined },
cfg,
);
@@ -492,7 +493,7 @@ export const slackPlugin: ChannelPlugin<ResolvedSlackAccount> = {
chunker: null,
textChunkLimit: 4000,
sendText: async ({ to, text, accountId, deps, replyToId, cfg }) => {
const send = deps?.sendSlack ?? sendMessageSlack;
const send = deps?.sendSlack ?? getSlackRuntime().channel.slack.sendMessageSlack;
const account = resolveSlackAccount({ cfg, accountId });
const token = getTokenForOperation(account, "write");
const botToken = account.botToken?.trim();
@@ -505,7 +506,7 @@ export const slackPlugin: ChannelPlugin<ResolvedSlackAccount> = {
return { channel: "slack", ...result };
},
sendMedia: async ({ to, text, mediaUrl, accountId, deps, replyToId, cfg }) => {
const send = deps?.sendSlack ?? sendMessageSlack;
const send = deps?.sendSlack ?? getSlackRuntime().channel.slack.sendMessageSlack;
const account = resolveSlackAccount({ cfg, accountId });
const token = getTokenForOperation(account, "write");
const botToken = account.botToken?.trim();
@@ -541,7 +542,7 @@ export const slackPlugin: ChannelPlugin<ResolvedSlackAccount> = {
probeAccount: async ({ account, timeoutMs }) => {
const token = account.botToken?.trim();
if (!token) return { ok: false, error: "missing token" };
return await probeSlack(token, timeoutMs);
return await getSlackRuntime().channel.slack.probeSlack(token, timeoutMs);
},
buildAccountSnapshot: ({ account, runtime, probe }) => {
const configured = Boolean(account.botToken && account.appToken);
@@ -568,9 +569,7 @@ export const slackPlugin: ChannelPlugin<ResolvedSlackAccount> = {
const botToken = account.botToken?.trim();
const appToken = account.appToken?.trim();
ctx.log?.info(`[${account.accountId}] starting provider`);
// Lazy import: the monitor pulls the reply pipeline; avoid ESM init cycles.
const { monitorSlackProvider } = await import("clawdbot/plugin-sdk");
return monitorSlackProvider({
return getSlackRuntime().channel.slack.monitorSlackProvider({
botToken: botToken ?? "",
appToken: appToken ?? "",
accountId: account.accountId,

View File

@@ -0,0 +1,14 @@
import type { PluginRuntime } from "clawdbot/plugin-sdk";
let runtime: PluginRuntime | null = null;
export function setSlackRuntime(next: PluginRuntime) {
runtime = next;
}
export function getSlackRuntime(): PluginRuntime {
if (!runtime) {
throw new Error("Slack runtime not initialized");
}
return runtime;
}