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,42 +1,43 @@
import {
applyAccountNameToChannelSection,
auditDiscordChannelPermissions,
buildChannelConfigSchema,
collectDiscordAuditChannelIds,
collectDiscordStatusIssues,
DEFAULT_ACCOUNT_ID,
deleteAccountFromConfigSection,
discordMessageActions,
discordOnboardingAdapter,
DiscordConfigSchema,
formatPairingApproveHint,
getChatChannelMeta,
listDiscordAccountIds,
listDiscordDirectoryGroupsFromConfig,
listDiscordDirectoryGroupsLive,
listDiscordDirectoryPeersFromConfig,
listDiscordDirectoryPeersLive,
looksLikeDiscordTargetId,
migrateBaseNameToDefaultAccount,
normalizeAccountId,
normalizeDiscordMessagingTarget,
PAIRING_APPROVED_MESSAGE,
probeDiscord,
resolveDiscordAccount,
resolveDefaultDiscordAccountId,
resolveDiscordChannelAllowlist,
resolveDiscordGroupRequireMention,
resolveDiscordUserAllowlist,
sendMessageDiscord,
sendPollDiscord,
setAccountEnabledInConfigSection,
shouldLogVerbose,
type ChannelMessageActionAdapter,
type ChannelPlugin,
type ResolvedDiscordAccount,
} from "clawdbot/plugin-sdk";
import { getDiscordRuntime } from "./runtime.js";
const meta = getChatChannelMeta("discord");
const discordMessageActions: ChannelMessageActionAdapter = {
listActions: (ctx) => getDiscordRuntime().channel.discord.messageActions.listActions(ctx),
extractToolSend: (ctx) =>
getDiscordRuntime().channel.discord.messageActions.extractToolSend(ctx),
handleAction: async (ctx) =>
await getDiscordRuntime().channel.discord.messageActions.handleAction(ctx),
};
export const discordPlugin: ChannelPlugin<ResolvedDiscordAccount> = {
id: "discord",
meta: {
@@ -47,7 +48,10 @@ export const discordPlugin: ChannelPlugin<ResolvedDiscordAccount> = {
idLabel: "discordUserId",
normalizeAllowEntry: (entry) => entry.replace(/^(discord|user):/i, ""),
notifyApproval: async ({ id }) => {
await sendMessageDiscord(`user:${id}`, PAIRING_APPROVED_MESSAGE);
await getDiscordRuntime().channel.discord.sendMessageDiscord(
`user:${id}`,
PAIRING_APPROVED_MESSAGE,
);
},
},
capabilities: {
@@ -158,8 +162,10 @@ export const discordPlugin: ChannelPlugin<ResolvedDiscordAccount> = {
self: async () => null,
listPeers: async (params) => listDiscordDirectoryPeersFromConfig(params),
listGroups: async (params) => listDiscordDirectoryGroupsFromConfig(params),
listPeersLive: async (params) => listDiscordDirectoryPeersLive(params),
listGroupsLive: async (params) => listDiscordDirectoryGroupsLive(params),
listPeersLive: async (params) =>
getDiscordRuntime().channel.discord.listDirectoryPeersLive(params),
listGroupsLive: async (params) =>
getDiscordRuntime().channel.discord.listDirectoryGroupsLive(params),
},
resolver: {
resolveTargets: async ({ cfg, accountId, inputs, kind }) => {
@@ -173,7 +179,10 @@ export const discordPlugin: ChannelPlugin<ResolvedDiscordAccount> = {
}));
}
if (kind === "group") {
const resolved = await resolveDiscordChannelAllowlist({ token, entries: inputs });
const resolved = await getDiscordRuntime().channel.discord.resolveChannelAllowlist({
token,
entries: inputs,
});
return resolved.map((entry) => ({
input: entry.input,
resolved: entry.resolved,
@@ -185,7 +194,10 @@ export const discordPlugin: ChannelPlugin<ResolvedDiscordAccount> = {
note: entry.note,
}));
}
const resolved = await resolveDiscordUserAllowlist({ token, entries: inputs });
const resolved = await getDiscordRuntime().channel.discord.resolveUserAllowlist({
token,
entries: inputs,
});
return resolved.map((entry) => ({
input: entry.input,
resolved: entry.resolved,
@@ -267,7 +279,8 @@ export const discordPlugin: ChannelPlugin<ResolvedDiscordAccount> = {
textChunkLimit: 2000,
pollMaxOptions: 10,
sendText: async ({ to, text, accountId, deps, replyToId }) => {
const send = deps?.sendDiscord ?? sendMessageDiscord;
const send =
deps?.sendDiscord ?? getDiscordRuntime().channel.discord.sendMessageDiscord;
const result = await send(to, text, {
verbose: false,
replyTo: replyToId ?? undefined,
@@ -276,7 +289,8 @@ export const discordPlugin: ChannelPlugin<ResolvedDiscordAccount> = {
return { channel: "discord", ...result };
},
sendMedia: async ({ to, text, mediaUrl, accountId, deps, replyToId }) => {
const send = deps?.sendDiscord ?? sendMessageDiscord;
const send =
deps?.sendDiscord ?? getDiscordRuntime().channel.discord.sendMessageDiscord;
const result = await send(to, text, {
verbose: false,
mediaUrl,
@@ -286,7 +300,7 @@ export const discordPlugin: ChannelPlugin<ResolvedDiscordAccount> = {
return { channel: "discord", ...result };
},
sendPoll: async ({ to, poll, accountId }) =>
await sendPollDiscord(to, poll, {
await getDiscordRuntime().channel.discord.sendPollDiscord(to, poll, {
accountId: accountId ?? undefined,
}),
},
@@ -310,7 +324,9 @@ export const discordPlugin: ChannelPlugin<ResolvedDiscordAccount> = {
lastProbeAt: snapshot.lastProbeAt ?? null,
}),
probeAccount: async ({ account, timeoutMs }) =>
probeDiscord(account.token, timeoutMs, { includeApplication: true }),
getDiscordRuntime().channel.discord.probeDiscord(account.token, timeoutMs, {
includeApplication: true,
}),
auditAccount: async ({ account, timeoutMs, cfg }) => {
const { channelIds, unresolvedChannels } = collectDiscordAuditChannelIds({
cfg,
@@ -327,7 +343,7 @@ export const discordPlugin: ChannelPlugin<ResolvedDiscordAccount> = {
elapsedMs: 0,
};
}
const audit = await auditDiscordChannelPermissions({
const audit = await getDiscordRuntime().channel.discord.auditChannelPermissions({
token: botToken,
accountId: account.accountId,
channelIds,
@@ -364,7 +380,7 @@ export const discordPlugin: ChannelPlugin<ResolvedDiscordAccount> = {
const token = account.token.trim();
let discordBotLabel = "";
try {
const probe = await probeDiscord(token, 2500, {
const probe = await getDiscordRuntime().channel.discord.probeDiscord(token, 2500, {
includeApplication: true,
});
const username = probe.ok ? probe.bot?.username?.trim() : null;
@@ -385,14 +401,12 @@ export const discordPlugin: ChannelPlugin<ResolvedDiscordAccount> = {
);
}
} catch (err) {
if (shouldLogVerbose()) {
if (getDiscordRuntime().logging.shouldLogVerbose()) {
ctx.log?.debug?.(`[${account.accountId}] bot probe failed: ${String(err)}`);
}
}
ctx.log?.info(`[${account.accountId}] starting provider${discordBotLabel}`);
// Lazy import: the monitor pulls the reply pipeline; avoid ESM init cycles.
const { monitorDiscordProvider } = await import("clawdbot/plugin-sdk");
return monitorDiscordProvider({
return getDiscordRuntime().channel.discord.monitorDiscordProvider({
token,
accountId: account.accountId,
config: ctx.cfg,

View File

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