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

@@ -30,7 +30,7 @@ export type ResolvedSlackAccount = {
};
function listConfiguredAccountIds(cfg: ClawdbotConfig): string[] {
const accounts = cfg.slack?.accounts;
const accounts = cfg.channels?.slack?.accounts;
if (!accounts || typeof accounts !== "object") return [];
return Object.keys(accounts).filter(Boolean);
}
@@ -51,7 +51,7 @@ function resolveAccountConfig(
cfg: ClawdbotConfig,
accountId: string,
): SlackAccountConfig | undefined {
const accounts = cfg.slack?.accounts;
const accounts = cfg.channels?.slack?.accounts;
if (!accounts || typeof accounts !== "object") return undefined;
return accounts[accountId] as SlackAccountConfig | undefined;
}
@@ -60,7 +60,7 @@ function mergeSlackAccountConfig(
cfg: ClawdbotConfig,
accountId: string,
): SlackAccountConfig {
const { accounts: _ignored, ...base } = (cfg.slack ??
const { accounts: _ignored, ...base } = (cfg.channels?.slack ??
{}) as SlackAccountConfig & { accounts?: unknown };
const account = resolveAccountConfig(cfg, accountId) ?? {};
return { ...base, ...account };
@@ -71,7 +71,7 @@ export function resolveSlackAccount(params: {
accountId?: string | null;
}): ResolvedSlackAccount {
const accountId = normalizeAccountId(params.accountId);
const baseEnabled = params.cfg.slack?.enabled !== false;
const baseEnabled = params.cfg.channels?.slack?.enabled !== false;
const merged = mergeSlackAccountConfig(params.cfg, accountId);
const accountEnabled = merged.enabled !== false;
const enabled = baseEnabled && accountEnabled;

View File

@@ -42,7 +42,7 @@ function resolveToken(explicit?: string, accountId?: string) {
)} source=${account.botTokenSource ?? "unknown"}`,
);
throw new Error(
"SLACK_BOT_TOKEN or slack.botToken is required for Slack actions",
"SLACK_BOT_TOKEN or channels.slack.botToken is required for Slack actions",
);
}
return token;

View File

@@ -38,9 +38,9 @@ vi.mock("./send.js", () => ({
}));
vi.mock("../pairing/pairing-store.js", () => ({
readProviderAllowFromStore: (...args: unknown[]) =>
readChannelAllowFromStore: (...args: unknown[]) =>
readAllowFromStoreMock(...args),
upsertProviderPairingRequest: (...args: unknown[]) =>
upsertChannelPairingRequest: (...args: unknown[]) =>
upsertPairingRequestMock(...args),
}));
@@ -108,9 +108,11 @@ beforeEach(() => {
ackReaction: "👀",
ackReactionScope: "group-mentions",
},
slack: {
dm: { enabled: true, policy: "open", allowFrom: ["*"] },
groupPolicy: "open",
channels: {
slack: {
dm: { enabled: true, policy: "open", allowFrom: ["*"] },
groupPolicy: "open",
},
},
};
sendMock.mockReset().mockResolvedValue(undefined);
@@ -179,14 +181,16 @@ describe("monitorSlackProvider tool results", () => {
bindings: [
{
agentId: "rich",
match: { provider: "slack", peer: { kind: "dm", id: "U1" } },
match: { channel: "slack", peer: { kind: "dm", id: "U1" } },
},
],
messages: {
ackReaction: "👀",
ackReactionScope: "group-mentions",
},
slack: { dm: { enabled: true, policy: "open", allowFrom: ["*"] } },
channels: {
slack: { dm: { enabled: true, policy: "open", allowFrom: ["*"] } },
},
};
replyMock.mockImplementation(async (_ctx, opts) => {
@@ -228,10 +232,12 @@ describe("monitorSlackProvider tool results", () => {
it("wraps room history in Body and preserves RawBody", async () => {
config = {
messages: { ackReactionScope: "group-mentions" },
slack: {
historyLimit: 5,
dm: { enabled: true, policy: "open", allowFrom: ["*"] },
channels: { "*": { requireMention: false } },
channels: {
slack: {
historyLimit: 5,
dm: { enabled: true, policy: "open", allowFrom: ["*"] },
channels: { "*": { requireMention: false } },
},
},
};
@@ -344,9 +350,11 @@ describe("monitorSlackProvider tool results", () => {
responsePrefix: "PFX",
groupChat: { mentionPatterns: ["\\bclawd\\b"] },
},
slack: {
dm: { enabled: true, policy: "open", allowFrom: ["*"] },
channels: { C1: { allow: true, requireMention: true } },
channels: {
slack: {
dm: { enabled: true, policy: "open", allowFrom: ["*"] },
channels: { C1: { allow: true, requireMention: true } },
},
},
};
replyMock.mockResolvedValue({ text: "hi" });
@@ -422,9 +430,11 @@ describe("monitorSlackProvider tool results", () => {
ackReaction: "👀",
ackReactionScope: "group-mentions",
},
slack: {
dm: { enabled: true, policy: "open", allowFrom: ["*"] },
replyToMode: "off",
channels: {
slack: {
dm: { enabled: true, policy: "open", allowFrom: ["*"] },
replyToMode: "off",
},
},
};
@@ -467,9 +477,11 @@ describe("monitorSlackProvider tool results", () => {
ackReaction: "👀",
ackReactionScope: "group-mentions",
},
slack: {
dm: { enabled: true, policy: "open", allowFrom: ["*"] },
replyToMode: "all",
channels: {
slack: {
dm: { enabled: true, policy: "open", allowFrom: ["*"] },
replyToMode: "all",
},
},
};
@@ -560,9 +572,11 @@ describe("monitorSlackProvider tool results", () => {
config = {
messages: { responsePrefix: "PFX" },
slack: {
dm: { enabled: true, policy: "open", allowFrom: ["*"] },
channels: { C1: { allow: true, requireMention: false } },
channels: {
slack: {
dm: { enabled: true, policy: "open", allowFrom: ["*"] },
channels: { C1: { allow: true, requireMention: false } },
},
},
};
@@ -610,12 +624,14 @@ describe("monitorSlackProvider tool results", () => {
replyMock.mockResolvedValue({ text: "ok" });
config = {
messages: { responsePrefix: "PFX" },
slack: {
dm: { enabled: true, policy: "open", allowFrom: ["*"] },
channels: { C1: { allow: true, requireMention: false } },
channels: {
slack: {
dm: { enabled: true, policy: "open", allowFrom: ["*"] },
channels: { C1: { allow: true, requireMention: false } },
},
},
bindings: [
{ agentId: "support", match: { provider: "slack", teamId: "T1" } },
{ agentId: "support", match: { channel: "slack", teamId: "T1" } },
],
};
@@ -678,9 +694,11 @@ describe("monitorSlackProvider tool results", () => {
ackReaction: "👀",
ackReactionScope: "group-mentions",
},
slack: {
dm: { enabled: true, policy: "open", allowFrom: ["*"] },
replyToMode: "off",
channels: {
slack: {
dm: { enabled: true, policy: "open", allowFrom: ["*"] },
replyToMode: "off",
},
},
};
@@ -722,9 +740,11 @@ describe("monitorSlackProvider tool results", () => {
ackReaction: "👀",
ackReactionScope: "group-mentions",
},
slack: {
dm: { enabled: true, policy: "open", allowFrom: ["*"] },
replyToMode: "first",
channels: {
slack: {
dm: { enabled: true, policy: "open", allowFrom: ["*"] },
replyToMode: "first",
},
},
};
@@ -767,9 +787,11 @@ describe("monitorSlackProvider tool results", () => {
ackReaction: "👀",
ackReactionScope: "group-mentions",
},
slack: {
dm: { enabled: true, policy: "open", allowFrom: ["*"] },
replyToMode: "off",
channels: {
slack: {
dm: { enabled: true, policy: "open", allowFrom: ["*"] },
replyToMode: "off",
},
},
};
@@ -850,7 +872,13 @@ describe("monitorSlackProvider tool results", () => {
it("replies with pairing code when dmPolicy is pairing and no allowFrom is set", async () => {
config = {
...config,
slack: { dm: { enabled: true, policy: "pairing", allowFrom: [] } },
channels: {
...config.channels,
slack: {
...config.channels?.slack,
dm: { enabled: true, policy: "pairing", allowFrom: [] },
},
},
};
const controller = new AbortController();
@@ -893,7 +921,13 @@ describe("monitorSlackProvider tool results", () => {
it("does not resend pairing code when a request is already pending", async () => {
config = {
...config,
slack: { dm: { enabled: true, policy: "pairing", allowFrom: [] } },
channels: {
...config.channels,
slack: {
...config.channels?.slack,
dm: { enabled: true, policy: "pairing", allowFrom: [] },
},
},
};
upsertPairingRequestMock
.mockResolvedValueOnce({ code: "PAIRCODE", created: true })

View File

@@ -59,8 +59,8 @@ import { type FetchLike, 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 { resolveAgentRoute } from "../routing/resolve-route.js";
import {
@@ -488,7 +488,7 @@ export async function monitorSlackProvider(opts: MonitorSlackOpts = {}) {
const appToken = resolveSlackAppToken(opts.appToken ?? account.appToken);
if (!botToken || !appToken) {
throw new Error(
`Slack bot + app tokens missing for account "${account.accountId}" (set slack.accounts.${account.accountId}.botToken/appToken or SLACK_BOT_TOKEN/SLACK_APP_TOKEN for default).`,
`Slack bot + app tokens missing for account "${account.accountId}" (set channels.slack.accounts.${account.accountId}.botToken/appToken or SLACK_BOT_TOKEN/SLACK_APP_TOKEN for default).`,
);
}
@@ -745,7 +745,7 @@ export async function monitorSlackProvider(opts: MonitorSlackOpts = {}) {
const allowBots =
channelConfig?.allowBots ??
account.config?.allowBots ??
cfg.slack?.allowBots ??
cfg.channels?.slack?.allowBots ??
false;
const isBotMessage = Boolean(message.bot_id);
if (isBotMessage) {
@@ -779,7 +779,7 @@ export async function monitorSlackProvider(opts: MonitorSlackOpts = {}) {
return;
}
const storeAllowFrom = await readProviderAllowFromStore("slack").catch(
const storeAllowFrom = await readChannelAllowFromStore("slack").catch(
() => [],
);
const effectiveAllowFrom = normalizeAllowList([
@@ -807,8 +807,8 @@ export async function monitorSlackProvider(opts: MonitorSlackOpts = {}) {
if (dmPolicy === "pairing") {
const sender = await resolveUserName(directUserId);
const senderName = sender?.name ?? undefined;
const { code, created } = await upsertProviderPairingRequest({
provider: "slack",
const { code, created } = await upsertChannelPairingRequest({
channel: "slack",
id: directUserId,
meta: { name: senderName },
});
@@ -820,7 +820,7 @@ export async function monitorSlackProvider(opts: MonitorSlackOpts = {}) {
await sendMessageSlack(
message.channel,
buildPairingReply({
provider: "slack",
channel: "slack",
idLine: `Your Slack user id: ${directUserId}`,
code,
}),
@@ -848,7 +848,7 @@ export async function monitorSlackProvider(opts: MonitorSlackOpts = {}) {
const route = resolveAgentRoute({
cfg,
provider: "slack",
channel: "slack",
accountId: account.accountId,
teamId: teamId || undefined,
peer: {
@@ -1012,7 +1012,7 @@ export async function monitorSlackProvider(opts: MonitorSlackOpts = {}) {
const textWithId = `${rawBody}\n[slack message id: ${message.ts} channel: ${message.channel}]`;
const body = formatAgentEnvelope({
provider: "Slack",
channel: "Slack",
from: senderName,
timestamp: message.ts ? Math.round(Number(message.ts) * 1000) : undefined,
body: textWithId,
@@ -1028,7 +1028,7 @@ export async function monitorSlackProvider(opts: MonitorSlackOpts = {}) {
currentMessage: combinedBody,
formatEntry: (entry) =>
formatAgentEnvelope({
provider: "Slack",
channel: "Slack",
from: roomLabel,
timestamp: entry.timestamp,
body: `${entry.sender}: ${entry.body}${
@@ -1068,7 +1068,7 @@ export async function monitorSlackProvider(opts: MonitorSlackOpts = {}) {
const starterName = starterUser?.name ?? starter.userId ?? "Unknown";
const starterWithId = `${starter.text}\n[slack message id: ${starter.ts ?? threadTs} channel: ${message.channel}]`;
threadStarterBody = formatThreadStarterEnvelope({
provider: "Slack",
channel: "Slack",
author: starterName,
timestamp: starter.ts
? Math.round(Number(starter.ts) * 1000)
@@ -1126,7 +1126,7 @@ export async function monitorSlackProvider(opts: MonitorSlackOpts = {}) {
await updateLastRoute({
storePath,
sessionKey: route.mainSessionKey,
provider: "slack",
channel: "slack",
to: `user:${message.user}`,
accountId: route.accountId,
});
@@ -1771,7 +1771,7 @@ export async function monitorSlackProvider(opts: MonitorSlackOpts = {}) {
}
}
const storeAllowFrom = await readProviderAllowFromStore("slack").catch(
const storeAllowFrom = await readChannelAllowFromStore("slack").catch(
() => [],
);
const effectiveAllowFrom = normalizeAllowList([
@@ -1801,15 +1801,15 @@ export async function monitorSlackProvider(opts: MonitorSlackOpts = {}) {
});
if (!permitted) {
if (dmPolicy === "pairing") {
const { code, created } = await upsertProviderPairingRequest({
provider: "slack",
const { code, created } = await upsertChannelPairingRequest({
channel: "slack",
id: command.user_id,
meta: { name: senderName },
});
if (created) {
await respond({
text: buildPairingReply({
provider: "slack",
channel: "slack",
idLine: `Your Slack user id: ${command.user_id}`,
code,
}),
@@ -1882,7 +1882,7 @@ export async function monitorSlackProvider(opts: MonitorSlackOpts = {}) {
const isRoomish = isRoom || isGroupDm;
const route = resolveAgentRoute({
cfg,
provider: "slack",
channel: "slack",
accountId: account.accountId,
teamId: teamId || undefined,
peer: {

View File

@@ -53,7 +53,7 @@ function resolveToken(params: {
)} source=${params.fallbackSource ?? "unknown"}`,
);
throw new Error(
`Slack bot token missing for account "${params.accountId}" (set slack.accounts.${params.accountId}.botToken or SLACK_BOT_TOKEN for default).`,
`Slack bot token missing for account "${params.accountId}" (set channels.slack.accounts.${params.accountId}.botToken or SLACK_BOT_TOKEN for default).`,
);
}
return fallback;