refactor!: rename chat providers to channels

This commit is contained in:
Peter Steinberger
2026-01-13 06:16:43 +00:00
parent 0cd632ba84
commit 90342a4f3a
393 changed files with 8004 additions and 6737 deletions

View File

@@ -16,7 +16,7 @@ export type ResolvedDiscordAccount = {
};
function listConfiguredAccountIds(cfg: ClawdbotConfig): string[] {
const accounts = cfg.discord?.accounts;
const accounts = cfg.channels?.discord?.accounts;
if (!accounts || typeof accounts !== "object") return [];
return Object.keys(accounts).filter(Boolean);
}
@@ -37,7 +37,7 @@ function resolveAccountConfig(
cfg: ClawdbotConfig,
accountId: string,
): DiscordAccountConfig | undefined {
const accounts = cfg.discord?.accounts;
const accounts = cfg.channels?.discord?.accounts;
if (!accounts || typeof accounts !== "object") return undefined;
return accounts[accountId] as DiscordAccountConfig | undefined;
}
@@ -46,7 +46,7 @@ function mergeDiscordAccountConfig(
cfg: ClawdbotConfig,
accountId: string,
): DiscordAccountConfig {
const { accounts: _ignored, ...base } = (cfg.discord ??
const { accounts: _ignored, ...base } = (cfg.channels?.discord ??
{}) as DiscordAccountConfig & { accounts?: unknown };
const account = resolveAccountConfig(cfg, accountId) ?? {};
return { ...base, ...account };
@@ -57,7 +57,7 @@ export function resolveDiscordAccount(params: {
accountId?: string | null;
}): ResolvedDiscordAccount {
const accountId = normalizeAccountId(params.accountId);
const baseEnabled = params.cfg.discord?.enabled !== false;
const baseEnabled = params.cfg.channels?.discord?.enabled !== false;
const merged = mergeDiscordAccountConfig(params.cfg, accountId);
const accountEnabled = merged.enabled !== false;
const enabled = baseEnabled && accountEnabled;

View File

@@ -11,16 +11,18 @@ describe("discord audit", () => {
const { fetchChannelPermissionsDiscord } = await import("./send.js");
const cfg = {
discord: {
enabled: true,
token: "t",
groupPolicy: "allowlist",
guilds: {
"123": {
channels: {
"111": { allow: true },
general: { allow: true },
"222": { allow: false },
channels: {
discord: {
enabled: true,
token: "t",
groupPolicy: "allowlist",
guilds: {
"123": {
channels: {
"111": { allow: true },
general: { allow: true },
"222": { allow: false },
},
},
},
},

View File

@@ -20,9 +20,9 @@ vi.mock("../auto-reply/reply/dispatch-from-config.js", () => ({
dispatchReplyFromConfig: (...args: unknown[]) => dispatchMock(...args),
}));
vi.mock("../pairing/pairing-store.js", () => ({
readProviderAllowFromStore: (...args: unknown[]) =>
readChannelAllowFromStore: (...args: unknown[]) =>
readAllowFromStoreMock(...args),
upsertProviderPairingRequest: (...args: unknown[]) =>
upsertChannelPairingRequest: (...args: unknown[]) =>
upsertPairingRequestMock(...args),
}));
vi.mock("../config/sessions.js", async (importOriginal) => {
@@ -61,13 +61,13 @@ describe("discord tool result dispatch", () => {
},
session: { store: "/tmp/clawdbot-sessions.json" },
messages: { responsePrefix: "PFX" },
discord: { dm: { enabled: true, policy: "open" } },
channels: { discord: { dm: { enabled: true, policy: "open" } } },
} as ReturnType<typeof import("../config/config.js").loadConfig>;
const runtimeError = vi.fn();
const handler = createDiscordMessageHandler({
cfg,
discordConfig: cfg.discord,
discordConfig: cfg.channels.discord,
accountId: "default",
token: "token",
runtime: {
@@ -130,12 +130,12 @@ describe("discord tool result dispatch", () => {
},
},
session: { store: "/tmp/clawdbot-sessions.json" },
discord: { dm: { enabled: true, policy: "open" } },
channels: { discord: { dm: { enabled: true, policy: "open" } } },
} as ReturnType<typeof import("../config/config.js").loadConfig>;
const handler = createDiscordMessageHandler({
cfg,
discordConfig: cfg.discord,
discordConfig: cfg.channels.discord,
accountId: "default",
token: "token",
runtime: {
@@ -210,12 +210,12 @@ describe("discord tool result dispatch", () => {
},
},
session: { store: "/tmp/clawdbot-sessions.json" },
discord: { dm: { enabled: true, policy: "open" } },
channels: { discord: { dm: { enabled: true, policy: "open" } } },
} as ReturnType<typeof import("../config/config.js").loadConfig>;
const handler = createDiscordMessageHandler({
cfg,
discordConfig: cfg.discord,
discordConfig: cfg.channels.discord,
accountId: "default",
token: "token",
runtime: {
@@ -300,12 +300,14 @@ describe("discord tool result dispatch", () => {
},
},
session: { store: "/tmp/clawdbot-sessions.json" },
discord: {
dm: { enabled: true, policy: "open" },
guilds: {
"*": {
requireMention: false,
channels: { c1: { allow: true } },
channels: {
discord: {
dm: { enabled: true, policy: "open" },
guilds: {
"*": {
requireMention: false,
channels: { c1: { allow: true } },
},
},
},
},
@@ -314,7 +316,7 @@ describe("discord tool result dispatch", () => {
const handler = createDiscordMessageHandler({
cfg,
discordConfig: cfg.discord,
discordConfig: cfg.channels.discord,
accountId: "default",
token: "token",
runtime: {
@@ -382,12 +384,14 @@ describe("discord tool result dispatch", () => {
},
},
session: { store: "/tmp/clawdbot-sessions.json" },
discord: { dm: { enabled: true, policy: "pairing", allowFrom: [] } },
channels: {
discord: { dm: { enabled: true, policy: "pairing", allowFrom: [] } },
},
} as ReturnType<typeof import("../config/config.js").loadConfig>;
const handler = createDiscordMessageHandler({
cfg,
discordConfig: cfg.discord,
discordConfig: cfg.channels.discord,
accountId: "default",
token: "token",
runtime: {
@@ -456,10 +460,12 @@ describe("discord tool result dispatch", () => {
},
},
session: { store: "/tmp/clawdbot-sessions.json" },
discord: {
dm: { enabled: true, policy: "open" },
groupPolicy: "open",
guilds: { "*": { requireMention: true } },
channels: {
discord: {
dm: { enabled: true, policy: "open" },
groupPolicy: "open",
guilds: { "*": { requireMention: true } },
},
},
messages: {
responsePrefix: "PFX",
@@ -469,7 +475,7 @@ describe("discord tool result dispatch", () => {
const handler = createDiscordMessageHandler({
cfg,
discordConfig: cfg.discord,
discordConfig: cfg.channels.discord,
accountId: "default",
token: "token",
runtime: {
@@ -549,16 +555,18 @@ describe("discord tool result dispatch", () => {
},
session: { store: "/tmp/clawdbot-sessions.json" },
messages: { responsePrefix: "PFX" },
discord: {
dm: { enabled: true, policy: "open" },
groupPolicy: "open",
guilds: { "*": { requireMention: false } },
channels: {
discord: {
dm: { enabled: true, policy: "open" },
groupPolicy: "open",
guilds: { "*": { requireMention: false } },
},
},
} as ReturnType<typeof import("../config/config.js").loadConfig>;
const handler = createDiscordMessageHandler({
cfg,
discordConfig: cfg.discord,
discordConfig: cfg.channels.discord,
accountId: "default",
token: "token",
runtime: {
@@ -655,17 +663,19 @@ describe("discord tool result dispatch", () => {
const cfg = {
agent: { model: "anthropic/claude-opus-4-5", workspace: "/tmp/clawd" },
session: { store: "/tmp/clawdbot-sessions.json" },
discord: {
dm: { enabled: true, policy: "open" },
groupPolicy: "open",
guilds: { "*": { requireMention: false } },
channels: {
discord: {
dm: { enabled: true, policy: "open" },
groupPolicy: "open",
guilds: { "*": { requireMention: false } },
},
},
routing: { allowFrom: [] },
} as ReturnType<typeof import("../config/config.js").loadConfig>;
const handler = createDiscordMessageHandler({
cfg,
discordConfig: cfg.discord,
discordConfig: cfg.channels.discord,
accountId: "default",
token: "token",
runtime: {
@@ -765,19 +775,21 @@ describe("discord tool result dispatch", () => {
},
session: { store: "/tmp/clawdbot-sessions.json" },
messages: { responsePrefix: "PFX" },
discord: {
dm: { enabled: true, policy: "open" },
groupPolicy: "open",
guilds: { "*": { requireMention: false } },
channels: {
discord: {
dm: { enabled: true, policy: "open" },
groupPolicy: "open",
guilds: { "*": { requireMention: false } },
},
},
bindings: [
{ agentId: "support", match: { provider: "discord", guildId: "g1" } },
{ agentId: "support", match: { channel: "discord", guildId: "g1" } },
],
} as ReturnType<typeof import("../config/config.js").loadConfig>;
const handler = createDiscordMessageHandler({
cfg,
discordConfig: cfg.discord,
discordConfig: cfg.channels.discord,
accountId: "default",
token: "token",
runtime: {

View File

@@ -58,16 +58,16 @@ import type { ClawdbotConfig, ReplyToMode } from "../config/config.js";
import { loadConfig } from "../config/config.js";
import { resolveStorePath, updateLastRoute } from "../config/sessions.js";
import { danger, logVerbose, shouldLogVerbose } from "../globals.js";
import { recordChannelActivity } from "../infra/channel-activity.js";
import { formatDurationSeconds } from "../infra/format-duration.js";
import { recordProviderActivity } from "../infra/provider-activity.js";
import { enqueueSystemEvent } from "../infra/system-events.js";
import { getChildLogger } from "../logging.js";
import { fetchRemoteMedia } from "../media/fetch.js";
import { saveMediaBuffer } from "../media/store.js";
import { buildPairingReply } from "../pairing/pairing-messages.js";
import {
readProviderAllowFromStore,
upsertProviderPairingRequest,
readChannelAllowFromStore,
upsertChannelPairingRequest,
} from "../pairing/pairing-store.js";
import {
buildAgentSessionKey,
@@ -103,6 +103,8 @@ export type MonitorDiscordOpts = {
replyToMode?: ReplyToMode;
};
type DiscordConfig = NonNullable<ClawdbotConfig["channels"]>["discord"];
type DiscordMediaInfo = {
path: string;
contentType?: string;
@@ -721,7 +723,7 @@ async function clearDiscordNativeCommands(params: {
export function createDiscordMessageHandler(params: {
cfg: ReturnType<typeof loadConfig>;
discordConfig: ClawdbotConfig["discord"];
discordConfig: DiscordConfig;
accountId: string;
token: string;
runtime: RuntimeEnv;
@@ -800,7 +802,7 @@ export function createDiscordMessageHandler(params: {
return;
}
if (dmPolicy !== "open") {
const storeAllowFrom = await readProviderAllowFromStore(
const storeAllowFrom = await readChannelAllowFromStore(
"discord",
).catch(() => []);
const effectiveAllowFrom = [...(allowFrom ?? []), ...storeAllowFrom];
@@ -818,8 +820,8 @@ export function createDiscordMessageHandler(params: {
if (!permitted) {
commandAuthorized = false;
if (dmPolicy === "pairing") {
const { code, created } = await upsertProviderPairingRequest({
provider: "discord",
const { code, created } = await upsertChannelPairingRequest({
channel: "discord",
id: author.id,
meta: {
tag: formatDiscordUserTag(author),
@@ -834,7 +836,7 @@ export function createDiscordMessageHandler(params: {
await sendMessageDiscord(
`user:${author.id}`,
buildPairingReply({
provider: "discord",
channel: "discord",
idLine: `Your Discord user id: ${author.id}`,
code,
}),
@@ -863,14 +865,14 @@ export function createDiscordMessageHandler(params: {
const messageText = resolveDiscordMessageText(message, {
includeForwarded: true,
});
recordProviderActivity({
provider: "discord",
recordChannelActivity({
channel: "discord",
accountId,
direction: "inbound",
});
const route = resolveAgentRoute({
cfg,
provider: "discord",
channel: "discord",
accountId,
guildId: data.guild_id ?? undefined,
peer: {
@@ -1174,7 +1176,7 @@ export function createDiscordMessageHandler(params: {
? systemPromptParts.join("\n\n")
: undefined;
let combinedBody = formatAgentEnvelope({
provider: "Discord",
channel: "Discord",
from: fromLabel,
timestamp: resolveTimestampMs(message.timestamp),
body: text,
@@ -1189,7 +1191,7 @@ export function createDiscordMessageHandler(params: {
currentMessage: combinedBody,
formatEntry: (entry) =>
formatAgentEnvelope({
provider: "Discord",
channel: "Discord",
from: fromLabel,
timestamp: entry.timestamp,
body: `${entry.sender}: ${entry.body} [id:${entry.messageId ?? "unknown"} channel:${message.channelId}]`,
@@ -1217,7 +1219,7 @@ export function createDiscordMessageHandler(params: {
});
if (starter?.text) {
const starterEnvelope = formatThreadStarterEnvelope({
provider: "Discord",
channel: "Discord",
author: starter.author,
timestamp: starter.timestamp,
body: starter.text,
@@ -1231,7 +1233,7 @@ export function createDiscordMessageHandler(params: {
if (threadParentId) {
parentSessionKey = buildAgentSessionKey({
agentId: route.agentId,
provider: route.provider,
channel: route.channel,
peer: { kind: "channel", id: threadParentId },
});
}
@@ -1314,7 +1316,7 @@ export function createDiscordMessageHandler(params: {
await updateLastRoute({
storePath,
sessionKey: route.mainSessionKey,
provider: "discord",
channel: "discord",
to: `user:${author.id}`,
accountId: route.accountId,
});
@@ -1602,7 +1604,7 @@ async function handleDiscordReactionEvent(params: {
const text = authorLabel ? `${baseText} from ${authorLabel}` : baseText;
const route = resolveAgentRoute({
cfg: params.cfg,
provider: "discord",
channel: "discord",
accountId: params.accountId,
guildId: data.guild_id ?? undefined,
peer: { kind: "channel", id: data.channel_id },
@@ -1625,7 +1627,7 @@ export function createDiscordNativeCommand(params: {
acceptsArgs: boolean;
};
cfg: ReturnType<typeof loadConfig>;
discordConfig: ClawdbotConfig["discord"];
discordConfig: DiscordConfig;
accountId: string;
sessionPrefix: string;
ephemeralDefault: boolean;
@@ -1721,7 +1723,7 @@ export function createDiscordNativeCommand(params: {
return;
}
if (dmPolicy !== "open") {
const storeAllowFrom = await readProviderAllowFromStore(
const storeAllowFrom = await readChannelAllowFromStore(
"discord",
).catch(() => []);
const effectiveAllowFrom = [
@@ -1742,8 +1744,8 @@ export function createDiscordNativeCommand(params: {
if (!permitted) {
commandAuthorized = false;
if (dmPolicy === "pairing") {
const { code, created } = await upsertProviderPairingRequest({
provider: "discord",
const { code, created } = await upsertChannelPairingRequest({
channel: "discord",
id: user.id,
meta: {
tag: formatDiscordUserTag(user),
@@ -1753,7 +1755,7 @@ export function createDiscordNativeCommand(params: {
if (created) {
await interaction.reply({
content: buildPairingReply({
provider: "discord",
channel: "discord",
idLine: `Your Discord user id: ${user.id}`,
code,
}),
@@ -1798,7 +1800,7 @@ export function createDiscordNativeCommand(params: {
const interactionId = interaction.rawData.id;
const route = resolveAgentRoute({
cfg,
provider: "discord",
channel: "discord",
accountId,
guildId: interaction.guild?.id ?? undefined,
peer: {
@@ -2244,7 +2246,7 @@ function resolveReplyContext(message: Message): string | null {
: "Unknown";
const body = `${referencedText}\n[discord message id: ${referenced.id} channel: ${referenced.channelId} from: ${formatDiscordUserTag(referenced.author)} user id:${referenced.author?.id ?? "unknown"}]`;
return formatAgentEnvelope({
provider: "Discord",
channel: "Discord",
from: fromLabel,
timestamp: resolveTimestampMs(referenced.timestamp),
body,

View File

@@ -18,7 +18,7 @@ import {
} from "discord-api-types/v10";
import { loadConfig } from "../config/config.js";
import { recordProviderActivity } from "../infra/provider-activity.js";
import { recordChannelActivity } from "../infra/channel-activity.js";
import type { RetryConfig } from "../infra/retry.js";
import {
createDiscordRetryRunner,
@@ -630,8 +630,8 @@ export async function sendMessageDiscord(
});
}
recordProviderActivity({
provider: "discord",
recordChannelActivity({
channel: "discord",
accountId: accountInfo.accountId,
direction: "outbound",
});

View File

@@ -23,10 +23,11 @@ export function resolveDiscordToken(
opts: { accountId?: string | null; envToken?: string | null } = {},
): DiscordTokenResolution {
const accountId = normalizeAccountId(opts.accountId);
const discordCfg = cfg?.channels?.discord;
const accountCfg =
accountId !== DEFAULT_ACCOUNT_ID
? cfg?.discord?.accounts?.[accountId]
: cfg?.discord?.accounts?.[DEFAULT_ACCOUNT_ID];
? discordCfg?.accounts?.[accountId]
: discordCfg?.accounts?.[DEFAULT_ACCOUNT_ID];
const accountToken = normalizeDiscordToken(accountCfg?.token ?? undefined);
if (accountToken) return { token: accountToken, source: "config" };
@@ -37,7 +38,7 @@ export function resolveDiscordToken(
if (envToken) return { token: envToken, source: "env" };
const configToken = allowEnv
? normalizeDiscordToken(cfg?.discord?.token ?? undefined)
? normalizeDiscordToken(discordCfg?.token ?? undefined)
: undefined;
if (configToken) return { token: configToken, source: "config" };