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

@@ -9,7 +9,9 @@ describe("resolveTelegramAccount", () => {
process.env.TELEGRAM_BOT_TOKEN = "";
try {
const cfg: ClawdbotConfig = {
telegram: { accounts: { work: { botToken: "tok-work" } } },
channels: {
telegram: { accounts: { work: { botToken: "tok-work" } } },
},
};
const account = resolveTelegramAccount({ cfg });
@@ -30,7 +32,9 @@ describe("resolveTelegramAccount", () => {
process.env.TELEGRAM_BOT_TOKEN = "tok-env";
try {
const cfg: ClawdbotConfig = {
telegram: { accounts: { work: { botToken: "tok-work" } } },
channels: {
telegram: { accounts: { work: { botToken: "tok-work" } } },
},
};
const account = resolveTelegramAccount({ cfg });
@@ -51,7 +55,9 @@ describe("resolveTelegramAccount", () => {
process.env.TELEGRAM_BOT_TOKEN = "";
try {
const cfg: ClawdbotConfig = {
telegram: { accounts: { work: { botToken: "tok-work" } } },
channels: {
telegram: { accounts: { work: { botToken: "tok-work" } } },
},
};
const account = resolveTelegramAccount({ cfg, accountId: "default" });

View File

@@ -16,7 +16,7 @@ export type ResolvedTelegramAccount = {
};
function listConfiguredAccountIds(cfg: ClawdbotConfig): string[] {
const accounts = cfg.telegram?.accounts;
const accounts = cfg.channels?.telegram?.accounts;
if (!accounts || typeof accounts !== "object") return [];
return Object.keys(accounts).filter(Boolean);
}
@@ -37,7 +37,7 @@ function resolveAccountConfig(
cfg: ClawdbotConfig,
accountId: string,
): TelegramAccountConfig | undefined {
const accounts = cfg.telegram?.accounts;
const accounts = cfg.channels?.telegram?.accounts;
if (!accounts || typeof accounts !== "object") return undefined;
return accounts[accountId] as TelegramAccountConfig | undefined;
}
@@ -46,7 +46,7 @@ function mergeTelegramAccountConfig(
cfg: ClawdbotConfig,
accountId: string,
): TelegramAccountConfig {
const { accounts: _ignored, ...base } = (cfg.telegram ??
const { accounts: _ignored, ...base } = (cfg.channels?.telegram ??
{}) as TelegramAccountConfig & { accounts?: unknown };
const account = resolveAccountConfig(cfg, accountId) ?? {};
return { ...base, ...account };
@@ -57,7 +57,7 @@ export function resolveTelegramAccount(params: {
accountId?: string | null;
}): ResolvedTelegramAccount {
const hasExplicitAccountId = Boolean(params.accountId?.trim());
const baseEnabled = params.cfg.telegram?.enabled !== false;
const baseEnabled = params.cfg.channels?.telegram?.enabled !== false;
const resolve = (accountId: string) => {
const merged = mergeTelegramAccountConfig(params.cfg, accountId);

View File

@@ -64,7 +64,9 @@ vi.mock("../config/config.js", async (importOriginal) => {
const actual = await importOriginal<typeof import("../config/config.js")>();
return {
...actual,
loadConfig: () => ({ telegram: { dmPolicy: "open", allowFrom: ["*"] } }),
loadConfig: () => ({
channels: { telegram: { dmPolicy: "open", allowFrom: ["*"] } },
}),
};
});

View File

@@ -127,7 +127,9 @@ describe("createTelegramBot", () => {
beforeEach(() => {
resetInboundDedupe();
loadConfig.mockReturnValue({
telegram: { dmPolicy: "open", allowFrom: ["*"] },
channels: {
telegram: { dmPolicy: "open", allowFrom: ["*"] },
},
});
loadWebMedia.mockReset();
sendAnimationSpy.mockReset();
@@ -309,7 +311,9 @@ describe("createTelegramBot", () => {
>;
replySpy.mockReset();
loadConfig.mockReturnValue({ telegram: { dmPolicy: "pairing" } });
loadConfig.mockReturnValue({
channels: { telegram: { dmPolicy: "pairing" } },
});
readTelegramAllowFromStore.mockResolvedValue([]);
upsertTelegramPairingRequest.mockResolvedValue({
code: "PAIRME12",
@@ -352,7 +356,9 @@ describe("createTelegramBot", () => {
>;
replySpy.mockReset();
loadConfig.mockReturnValue({ telegram: { dmPolicy: "pairing" } });
loadConfig.mockReturnValue({
channels: { telegram: { dmPolicy: "pairing" } },
});
readTelegramAllowFromStore.mockResolvedValue([]);
upsertTelegramPairingRequest
.mockResolvedValueOnce({ code: "PAIRME12", created: true })
@@ -412,9 +418,11 @@ describe("createTelegramBot", () => {
loadConfig.mockReturnValue({
identity: { name: "Bert" },
messages: { groupChat: { mentionPatterns: ["\\bbert\\b"] } },
telegram: {
groupPolicy: "open",
groups: { "*": { requireMention: true } },
channels: {
telegram: {
groupPolicy: "open",
groups: { "*": { requireMention: true } },
},
},
});
@@ -451,9 +459,11 @@ describe("createTelegramBot", () => {
replySpy.mockReset();
loadConfig.mockReturnValue({
telegram: {
groupPolicy: "open",
groups: { "*": { requireMention: false } },
channels: {
telegram: {
groupPolicy: "open",
groups: { "*": { requireMention: false } },
},
},
});
@@ -500,9 +510,11 @@ describe("createTelegramBot", () => {
ackReactionScope: "group-mentions",
groupChat: { mentionPatterns: ["\\bbert\\b"] },
},
telegram: {
groupPolicy: "open",
groups: { "*": { requireMention: true } },
channels: {
telegram: {
groupPolicy: "open",
groups: { "*": { requireMention: true } },
},
},
});
@@ -547,9 +559,11 @@ describe("createTelegramBot", () => {
loadConfig.mockReturnValue({
messages: { groupChat: { mentionPatterns: ["\\bbert\\b"] } },
telegram: {
groupPolicy: "open",
groups: { "*": { requireMention: true } },
channels: {
telegram: {
groupPolicy: "open",
groups: { "*": { requireMention: true } },
},
},
});
@@ -582,9 +596,11 @@ describe("createTelegramBot", () => {
loadConfig.mockReturnValue({
messages: { groupChat: { mentionPatterns: [] } },
telegram: {
groupPolicy: "open",
groups: { "*": { requireMention: true } },
channels: {
telegram: {
groupPolicy: "open",
groups: { "*": { requireMention: true } },
},
},
});
@@ -724,7 +740,9 @@ describe("createTelegramBot", () => {
return { text: "final reply" };
});
loadConfig.mockReturnValue({
telegram: { dmPolicy: "open", allowFrom: ["*"] },
channels: {
telegram: { dmPolicy: "open", allowFrom: ["*"] },
},
messages: { responsePrefix: "PFX" },
});
@@ -787,9 +805,11 @@ describe("createTelegramBot", () => {
>;
replySpy.mockReset();
loadConfig.mockReturnValue({
telegram: {
groups: {
"123": { requireMention: false },
channels: {
telegram: {
groups: {
"123": { requireMention: false },
},
},
},
});
@@ -819,7 +839,9 @@ describe("createTelegramBot", () => {
>;
replySpy.mockReset();
loadConfig.mockReturnValue({
telegram: { groups: { "*": { requireMention: true } } },
channels: {
telegram: { groups: { "*": { requireMention: true } } },
},
});
createTelegramBot({ token: "tok" });
@@ -858,15 +880,17 @@ describe("createTelegramBot", () => {
"utf-8",
);
loadConfig.mockReturnValue({
telegram: {
groupPolicy: "open",
groups: { "*": { requireMention: true } },
channels: {
telegram: {
groupPolicy: "open",
groups: { "*": { requireMention: true } },
},
},
bindings: [
{
agentId: "ops",
match: {
provider: "telegram",
channel: "telegram",
peer: { kind: "group", id: "123" },
},
},
@@ -946,11 +970,13 @@ describe("createTelegramBot", () => {
>;
replySpy.mockReset();
loadConfig.mockReturnValue({
telegram: {
groupPolicy: "open",
groups: {
"*": { requireMention: true },
"123": { requireMention: false },
channels: {
telegram: {
groupPolicy: "open",
groups: {
"*": { requireMention: true },
"123": { requireMention: false },
},
},
},
});
@@ -980,14 +1006,16 @@ describe("createTelegramBot", () => {
>;
replySpy.mockReset();
loadConfig.mockReturnValue({
telegram: {
groupPolicy: "open",
groups: {
"*": { requireMention: true },
"-1001234567890": {
requireMention: true,
topics: {
"99": { requireMention: false },
channels: {
telegram: {
groupPolicy: "open",
groups: {
"*": { requireMention: true },
"-1001234567890": {
requireMention: true,
topics: {
"99": { requireMention: false },
},
},
},
},
@@ -1025,9 +1053,11 @@ describe("createTelegramBot", () => {
>;
replySpy.mockReset();
loadConfig.mockReturnValue({
telegram: {
groupPolicy: "open",
groups: { "*": { requireMention: false } },
channels: {
telegram: {
groupPolicy: "open",
groups: { "*": { requireMention: false } },
},
},
});
@@ -1056,9 +1086,11 @@ describe("createTelegramBot", () => {
>;
replySpy.mockReset();
loadConfig.mockReturnValue({
telegram: {
groupPolicy: "open",
groups: { "*": { requireMention: true } },
channels: {
telegram: {
groupPolicy: "open",
groups: { "*": { requireMention: true } },
},
},
});
@@ -1130,9 +1162,11 @@ describe("createTelegramBot", () => {
>;
replySpy.mockReset();
loadConfig.mockReturnValue({
telegram: {
groupPolicy: "disabled",
allowFrom: ["123456789"],
channels: {
telegram: {
groupPolicy: "disabled",
allowFrom: ["123456789"],
},
},
});
@@ -1163,9 +1197,11 @@ describe("createTelegramBot", () => {
>;
replySpy.mockReset();
loadConfig.mockReturnValue({
telegram: {
groupPolicy: "allowlist",
allowFrom: ["123456789"], // Does not include sender 999999
channels: {
telegram: {
groupPolicy: "allowlist",
allowFrom: ["123456789"], // Does not include sender 999999
},
},
});
@@ -1195,10 +1231,12 @@ describe("createTelegramBot", () => {
>;
replySpy.mockReset();
loadConfig.mockReturnValue({
telegram: {
groupPolicy: "allowlist",
allowFrom: ["123456789"],
groups: { "*": { requireMention: false } }, // Skip mention check
channels: {
telegram: {
groupPolicy: "allowlist",
allowFrom: ["123456789"],
groups: { "*": { requireMention: false } }, // Skip mention check
},
},
});
@@ -1228,10 +1266,12 @@ describe("createTelegramBot", () => {
>;
replySpy.mockReset();
loadConfig.mockReturnValue({
telegram: {
groupPolicy: "allowlist",
allowFrom: ["@testuser"], // By username
groups: { "*": { requireMention: false } },
channels: {
telegram: {
groupPolicy: "allowlist",
allowFrom: ["@testuser"], // By username
groups: { "*": { requireMention: false } },
},
},
});
@@ -1261,10 +1301,12 @@ describe("createTelegramBot", () => {
>;
replySpy.mockReset();
loadConfig.mockReturnValue({
telegram: {
groupPolicy: "allowlist",
allowFrom: ["telegram:77112533"],
groups: { "*": { requireMention: false } },
channels: {
telegram: {
groupPolicy: "allowlist",
allowFrom: ["telegram:77112533"],
groups: { "*": { requireMention: false } },
},
},
});
@@ -1294,10 +1336,12 @@ describe("createTelegramBot", () => {
>;
replySpy.mockReset();
loadConfig.mockReturnValue({
telegram: {
groupPolicy: "allowlist",
allowFrom: ["TG:77112533"],
groups: { "*": { requireMention: false } },
channels: {
telegram: {
groupPolicy: "allowlist",
allowFrom: ["TG:77112533"],
groups: { "*": { requireMention: false } },
},
},
});
@@ -1327,9 +1371,11 @@ describe("createTelegramBot", () => {
>;
replySpy.mockReset();
loadConfig.mockReturnValue({
telegram: {
groupPolicy: "open",
groups: { "*": { requireMention: false } },
channels: {
telegram: {
groupPolicy: "open",
groups: { "*": { requireMention: false } },
},
},
});
@@ -1359,10 +1405,12 @@ describe("createTelegramBot", () => {
>;
replySpy.mockReset();
loadConfig.mockReturnValue({
telegram: {
groupPolicy: "allowlist",
allowFrom: ["@TestUser"], // Uppercase in config
groups: { "*": { requireMention: false } },
channels: {
telegram: {
groupPolicy: "allowlist",
allowFrom: ["@TestUser"], // Uppercase in config
groups: { "*": { requireMention: false } },
},
},
});
@@ -1392,9 +1440,11 @@ describe("createTelegramBot", () => {
>;
replySpy.mockReset();
loadConfig.mockReturnValue({
telegram: {
groupPolicy: "disabled", // Even with disabled, DMs should work
allowFrom: ["123456789"],
channels: {
telegram: {
groupPolicy: "disabled", // Even with disabled, DMs should work
allowFrom: ["123456789"],
},
},
});
@@ -1424,8 +1474,10 @@ describe("createTelegramBot", () => {
>;
replySpy.mockReset();
loadConfig.mockReturnValue({
telegram: {
allowFrom: [" TG:123456789 "],
channels: {
telegram: {
allowFrom: [" TG:123456789 "],
},
},
});
@@ -1455,8 +1507,10 @@ describe("createTelegramBot", () => {
>;
replySpy.mockReset();
loadConfig.mockReturnValue({
telegram: {
allowFrom: ["telegram:123456789"],
channels: {
telegram: {
allowFrom: ["telegram:123456789"],
},
},
});
@@ -1486,10 +1540,12 @@ describe("createTelegramBot", () => {
>;
replySpy.mockReset();
loadConfig.mockReturnValue({
telegram: {
groupPolicy: "allowlist",
allowFrom: ["*"], // Wildcard allows everyone
groups: { "*": { requireMention: false } },
channels: {
telegram: {
groupPolicy: "allowlist",
allowFrom: ["*"], // Wildcard allows everyone
groups: { "*": { requireMention: false } },
},
},
});
@@ -1519,9 +1575,11 @@ describe("createTelegramBot", () => {
>;
replySpy.mockReset();
loadConfig.mockReturnValue({
telegram: {
groupPolicy: "allowlist",
allowFrom: ["123456789"],
channels: {
telegram: {
groupPolicy: "allowlist",
allowFrom: ["123456789"],
},
},
});
@@ -1551,10 +1609,12 @@ describe("createTelegramBot", () => {
>;
replySpy.mockReset();
loadConfig.mockReturnValue({
telegram: {
groupPolicy: "allowlist",
allowFrom: ["telegram:123456789"], // Prefixed format
groups: { "*": { requireMention: false } },
channels: {
telegram: {
groupPolicy: "allowlist",
allowFrom: ["telegram:123456789"], // Prefixed format
groups: { "*": { requireMention: false } },
},
},
});
@@ -1585,10 +1645,12 @@ describe("createTelegramBot", () => {
>;
replySpy.mockReset();
loadConfig.mockReturnValue({
telegram: {
groupPolicy: "allowlist",
allowFrom: ["TG:123456789"], // Prefixed format (case-insensitive)
groups: { "*": { requireMention: false } },
channels: {
telegram: {
groupPolicy: "allowlist",
allowFrom: ["TG:123456789"], // Prefixed format (case-insensitive)
groups: { "*": { requireMention: false } },
},
},
});
@@ -1619,9 +1681,11 @@ describe("createTelegramBot", () => {
>;
replySpy.mockReset();
loadConfig.mockReturnValue({
telegram: {
groupPolicy: "allowlist",
groups: { "*": { requireMention: false } },
channels: {
telegram: {
groupPolicy: "allowlist",
groups: { "*": { requireMention: false } },
},
},
});
@@ -1651,10 +1715,12 @@ describe("createTelegramBot", () => {
>;
replySpy.mockReset();
loadConfig.mockReturnValue({
telegram: {
groupPolicy: "allowlist",
groupAllowFrom: [" TG:123456789 "],
groups: { "*": { requireMention: true } },
channels: {
telegram: {
groupPolicy: "allowlist",
groupAllowFrom: [" TG:123456789 "],
groups: { "*": { requireMention: true } },
},
},
});
@@ -1686,9 +1752,11 @@ describe("createTelegramBot", () => {
replySpy.mockReset();
loadConfig.mockReturnValue({
telegram: {
groupPolicy: "open",
groups: { "*": { requireMention: false } },
channels: {
telegram: {
groupPolicy: "open",
groups: { "*": { requireMention: false } },
},
},
});
@@ -1737,9 +1805,11 @@ describe("createTelegramBot", () => {
replySpy.mockReset();
loadConfig.mockReturnValue({
telegram: {
groupPolicy: "open",
groups: { "*": { requireMention: false } },
channels: {
telegram: {
groupPolicy: "open",
groups: { "*": { requireMention: false } },
},
},
});
@@ -1824,17 +1894,19 @@ describe("createTelegramBot", () => {
replySpy.mockReset();
loadConfig.mockReturnValue({
telegram: {
groupPolicy: "open",
groups: {
"-1001234567890": {
requireMention: false,
systemPrompt: "Group prompt",
skills: ["group-skill"],
topics: {
"99": {
skills: [],
systemPrompt: "Topic prompt",
channels: {
telegram: {
groupPolicy: "open",
groups: {
"-1001234567890": {
requireMention: false,
systemPrompt: "Group prompt",
skills: ["group-skill"],
topics: {
"99": {
skills: [],
systemPrompt: "Topic prompt",
},
},
},
},
@@ -1883,9 +1955,11 @@ describe("createTelegramBot", () => {
replySpy.mockResolvedValue({ text: "response" });
loadConfig.mockReturnValue({
telegram: {
groupPolicy: "open",
groups: { "*": { requireMention: false } },
channels: {
telegram: {
groupPolicy: "open",
groups: { "*": { requireMention: false } },
},
},
});
@@ -1931,10 +2005,12 @@ describe("createTelegramBot", () => {
loadConfig.mockReturnValue({
commands: { native: true },
telegram: {
dmPolicy: "open",
allowFrom: ["*"],
groups: { "*": { requireMention: false } },
channels: {
telegram: {
dmPolicy: "open",
allowFrom: ["*"],
groups: { "*": { requireMention: false } },
},
},
});
@@ -2019,7 +2095,9 @@ describe("createTelegramBot", () => {
replySpy.mockReset();
loadConfig.mockReturnValue({
telegram: { dmPolicy: "open", allowFrom: ["*"] },
channels: {
telegram: { dmPolicy: "open", allowFrom: ["*"] },
},
});
createTelegramBot({ token: "tok" });
@@ -2054,7 +2132,9 @@ describe("createTelegramBot", () => {
replySpy.mockReset();
loadConfig.mockReturnValue({
telegram: { dmPolicy: "open", allowFrom: ["*"] },
channels: {
telegram: { dmPolicy: "open", allowFrom: ["*"] },
},
});
createTelegramBot({ token: "tok" });
@@ -2092,7 +2172,9 @@ describe("createTelegramBot", () => {
replySpy.mockReset();
loadConfig.mockReturnValue({
telegram: { dmPolicy: "open", allowFrom: ["*"] },
channels: {
telegram: { dmPolicy: "open", allowFrom: ["*"] },
},
});
createTelegramBot({ token: "tok" });

View File

@@ -32,6 +32,11 @@ import {
} from "../auto-reply/reply/mentions.js";
import { dispatchReplyWithBufferedBlockDispatcher } from "../auto-reply/reply/provider-dispatcher.js";
import type { ReplyPayload } from "../auto-reply/types.js";
import {
formatLocationText,
type NormalizedLocation,
toLocationContext,
} from "../channels/location.js";
import {
isNativeCommandsExplicitlyDisabled,
resolveNativeCommandsEnabled,
@@ -39,8 +44,8 @@ import {
import type { ClawdbotConfig, ReplyToMode } from "../config/config.js";
import { loadConfig } from "../config/config.js";
import {
resolveProviderGroupPolicy,
resolveProviderGroupRequireMention,
resolveChannelGroupPolicy,
resolveChannelGroupRequireMention,
} from "../config/group-policy.js";
import {
loadSessionStore,
@@ -48,19 +53,14 @@ import {
updateLastRoute,
} from "../config/sessions.js";
import { danger, logVerbose, shouldLogVerbose } from "../globals.js";
import { recordChannelActivity } from "../infra/channel-activity.js";
import { createDedupeCache } from "../infra/dedupe.js";
import { formatErrorMessage } from "../infra/errors.js";
import { recordProviderActivity } from "../infra/provider-activity.js";
import { getChildLogger } from "../logging.js";
import { mediaKindFromMime } from "../media/constants.js";
import { fetchRemoteMedia } from "../media/fetch.js";
import { isGifMedia } from "../media/mime.js";
import { saveMediaBuffer } from "../media/store.js";
import {
formatLocationText,
type NormalizedLocation,
toLocationContext,
} from "../providers/location.js";
import { resolveAgentRoute } from "../routing/resolve-route.js";
import type { RuntimeEnv } from "../runtime.js";
import { loadWebMedia } from "../web/media.js";
@@ -369,9 +369,9 @@ export function createTelegramBot(opts: TelegramBotOptions) {
return botHasTopicsEnabled;
};
const resolveGroupPolicy = (chatId: string | number) =>
resolveProviderGroupPolicy({
resolveChannelGroupPolicy({
cfg,
provider: "telegram",
channel: "telegram",
accountId: account.accountId,
groupId: String(chatId),
});
@@ -397,9 +397,9 @@ export function createTelegramBot(opts: TelegramBotOptions) {
return undefined;
};
const resolveGroupRequireMention = (chatId: string | number) =>
resolveProviderGroupRequireMention({
resolveChannelGroupRequireMention({
cfg,
provider: "telegram",
channel: "telegram",
accountId: account.accountId,
groupId: String(chatId),
requireMentionOverride: opts.requireMention,
@@ -427,8 +427,8 @@ export function createTelegramBot(opts: TelegramBotOptions) {
options?: { forceWasMentioned?: boolean; messageIdOverride?: string },
) => {
const msg = primaryCtx.message;
recordProviderActivity({
provider: "telegram",
recordChannelActivity({
channel: "telegram",
accountId: account.accountId,
direction: "inbound",
});
@@ -450,7 +450,7 @@ export function createTelegramBot(opts: TelegramBotOptions) {
: String(chatId);
const route = resolveAgentRoute({
cfg,
provider: "telegram",
channel: "telegram",
accountId: account.accountId,
peer: {
kind: isGroup ? "group" : "dm",
@@ -699,7 +699,7 @@ export function createTelegramBot(opts: TelegramBotOptions) {
? buildGroupLabel(msg, chatId, resolvedThreadId)
: undefined;
const body = formatAgentEnvelope({
provider: "Telegram",
channel: "Telegram",
from: isGroup
? buildGroupFromLabel(msg, chatId, senderId, resolvedThreadId)
: buildSenderLabel(msg, senderId || chatId),
@@ -727,7 +727,7 @@ export function createTelegramBot(opts: TelegramBotOptions) {
currentMessage: combinedBody,
formatEntry: (entry) =>
formatAgentEnvelope({
provider: "Telegram",
channel: "Telegram",
from: groupLabel ?? `group:${chatId}`,
timestamp: entry.timestamp,
body: `${entry.sender}: ${entry.body} [id:${entry.messageId ?? "unknown"} chat:${chatId}]`,
@@ -800,7 +800,7 @@ export function createTelegramBot(opts: TelegramBotOptions) {
await updateLastRoute({
storePath,
sessionKey: route.mainSessionKey,
provider: "telegram",
channel: "telegram",
to: String(chatId),
accountId: route.accountId,
});
@@ -1127,7 +1127,7 @@ export function createTelegramBot(opts: TelegramBotOptions) {
const prompt = buildCommandText(command.name, ctx.match ?? "");
const route = resolveAgentRoute({
cfg,
provider: "telegram",
channel: "telegram",
accountId: account.accountId,
peer: {
kind: isGroup ? "group" : "dm",
@@ -1718,7 +1718,9 @@ async function resolveMedia(
}
const fetchImpl = proxyFetch ?? globalThis.fetch;
if (!fetchImpl) {
throw new Error("fetch is not available; set telegram.proxy in config");
throw new Error(
"fetch is not available; set channels.telegram.proxy in config",
);
}
const url = `https://api.telegram.org/file/bot${token}/${file.file_path}`;
const fetched = await fetchRemoteMedia({

View File

@@ -18,7 +18,7 @@ describe("resolveTelegramDraftStreamingChunking", () => {
it("clamps to telegram.textChunkLimit", () => {
const cfg: ClawdbotConfig = {
telegram: { allowFrom: ["*"], textChunkLimit: 150 },
channels: { telegram: { allowFrom: ["*"], textChunkLimit: 150 } },
};
const chunking = resolveTelegramDraftStreamingChunking(cfg, "default");
expect(chunking).toEqual({
@@ -30,15 +30,17 @@ describe("resolveTelegramDraftStreamingChunking", () => {
it("supports per-account overrides", () => {
const cfg: ClawdbotConfig = {
telegram: {
allowFrom: ["*"],
accounts: {
default: {
allowFrom: ["*"],
draftChunk: {
minChars: 10,
maxChars: 20,
breakPreference: "sentence",
channels: {
telegram: {
allowFrom: ["*"],
accounts: {
default: {
allowFrom: ["*"],
draftChunk: {
minChars: 10,
maxChars: 20,
breakPreference: "sentence",
},
},
},
},

View File

@@ -1,6 +1,6 @@
import { resolveTextChunkLimit } from "../auto-reply/chunk.js";
import { getChannelDock } from "../channels/dock.js";
import type { ClawdbotConfig } from "../config/config.js";
import { getProviderDock } from "../providers/dock.js";
import { normalizeAccountId } from "../routing/session-key.js";
const DEFAULT_TELEGRAM_DRAFT_STREAM_MIN = 200;
@@ -15,14 +15,14 @@ export function resolveTelegramDraftStreamingChunking(
breakPreference: "paragraph" | "newline" | "sentence";
} {
const providerChunkLimit =
getProviderDock("telegram")?.outbound?.textChunkLimit;
getChannelDock("telegram")?.outbound?.textChunkLimit;
const textLimit = resolveTextChunkLimit(cfg, "telegram", accountId, {
fallbackLimit: providerChunkLimit,
});
const normalizedAccountId = normalizeAccountId(accountId);
const draftCfg =
cfg?.telegram?.accounts?.[normalizedAccountId]?.draftChunk ??
cfg?.telegram?.draftChunk;
cfg?.channels?.telegram?.accounts?.[normalizedAccountId]?.draftChunk ??
cfg?.channels?.telegram?.draftChunk;
const maxRequested = Math.max(
1,

View File

@@ -7,7 +7,9 @@ export function resolveTelegramFetch(
const isBun = "Bun" in globalThis || Boolean(process?.versions?.bun);
if (!isBun) return undefined;
if (!fetchImpl) {
throw new Error("fetch is not available; set telegram.proxy in config");
throw new Error(
"fetch is not available; set channels.telegram.proxy in config",
);
}
return fetchImpl;
}

View File

@@ -31,7 +31,7 @@ const { initSpy, runSpy, loadConfig } = vi.hoisted(() => ({
})),
loadConfig: vi.fn(() => ({
agents: { defaults: { maxConcurrent: 2 } },
telegram: {},
channels: { telegram: {} },
})),
}));
@@ -80,7 +80,7 @@ describe("monitorTelegramProvider (grammY)", () => {
beforeEach(() => {
loadConfig.mockReturnValue({
agents: { defaults: { maxConcurrent: 2 } },
telegram: {},
channels: { telegram: {} },
});
initSpy.mockClear();
runSpy.mockClear();
@@ -110,7 +110,7 @@ describe("monitorTelegramProvider (grammY)", () => {
runSpy.mockClear();
loadConfig.mockReturnValue({
agents: { defaults: { maxConcurrent: 3 } },
telegram: {},
channels: { telegram: {} },
});
await monitorTelegramProvider({ token: "tok" });

View File

@@ -79,7 +79,7 @@ export async function monitorTelegramProvider(opts: MonitorTelegramOpts = {}) {
const token = opts.token?.trim() || account.token;
if (!token) {
throw new Error(
`Telegram bot token missing for account "${account.accountId}" (set telegram.accounts.${account.accountId}.botToken/tokenFile or TELEGRAM_BOT_TOKEN for default).`,
`Telegram bot token missing for account "${account.accountId}" (set channels.telegram.accounts.${account.accountId}.botToken/tokenFile or TELEGRAM_BOT_TOKEN for default).`,
);
}

View File

@@ -1,10 +1,10 @@
import type { ClawdbotConfig } from "../config/config.js";
import {
addProviderAllowFromStoreEntry,
approveProviderPairingCode,
listProviderPairingRequests,
readProviderAllowFromStore,
upsertProviderPairingRequest,
addChannelAllowFromStoreEntry,
approveChannelPairingCode,
listChannelPairingRequests,
readChannelAllowFromStore,
upsertChannelPairingRequest,
} from "../pairing/pairing-store.js";
export type TelegramPairingListEntry = {
@@ -22,15 +22,15 @@ const PROVIDER = "telegram" as const;
export async function readTelegramAllowFromStore(
env: NodeJS.ProcessEnv = process.env,
): Promise<string[]> {
return readProviderAllowFromStore(PROVIDER, env);
return readChannelAllowFromStore(PROVIDER, env);
}
export async function addTelegramAllowFromStoreEntry(params: {
entry: string | number;
env?: NodeJS.ProcessEnv;
}): Promise<{ changed: boolean; allowFrom: string[] }> {
return addProviderAllowFromStoreEntry({
provider: PROVIDER,
return addChannelAllowFromStoreEntry({
channel: PROVIDER,
entry: params.entry,
env: params.env,
});
@@ -39,7 +39,7 @@ export async function addTelegramAllowFromStoreEntry(params: {
export async function listTelegramPairingRequests(
env: NodeJS.ProcessEnv = process.env,
): Promise<TelegramPairingListEntry[]> {
const list = await listProviderPairingRequests(PROVIDER, env);
const list = await listChannelPairingRequests(PROVIDER, env);
return list.map((r) => ({
chatId: r.id,
code: r.code,
@@ -58,8 +58,8 @@ export async function upsertTelegramPairingRequest(params: {
lastName?: string;
env?: NodeJS.ProcessEnv;
}): Promise<{ code: string; created: boolean }> {
return upsertProviderPairingRequest({
provider: PROVIDER,
return upsertChannelPairingRequest({
channel: PROVIDER,
id: String(params.chatId),
env: params.env,
meta: {
@@ -74,8 +74,8 @@ export async function approveTelegramPairingCode(params: {
code: string;
env?: NodeJS.ProcessEnv;
}): Promise<{ chatId: string; entry?: TelegramPairingListEntry } | null> {
const res = await approveProviderPairingCode({
provider: PROVIDER,
const res = await approveChannelPairingCode({
channel: PROVIDER,
code: params.code,
env: params.env,
});
@@ -99,12 +99,14 @@ export async function resolveTelegramEffectiveAllowFrom(params: {
env?: NodeJS.ProcessEnv;
}): Promise<{ dm: string[]; group: string[] }> {
const env = params.env ?? process.env;
const cfgAllowFrom = (params.cfg.telegram?.allowFrom ?? [])
const cfgAllowFrom = (params.cfg.channels?.telegram?.allowFrom ?? [])
.map((v) => String(v).trim())
.filter(Boolean)
.map((v) => v.replace(/^(telegram|tg):/i, ""))
.filter((v) => v !== "*");
const cfgGroupAllowFrom = (params.cfg.telegram?.groupAllowFrom ?? [])
const cfgGroupAllowFrom = (
params.cfg.channels?.telegram?.groupAllowFrom ?? []
)
.map((v) => String(v).trim())
.filter(Boolean)
.map((v) => v.replace(/^(telegram|tg):/i, ""))

View File

@@ -7,8 +7,8 @@ import type {
import { type ApiClientOptions, Bot, InputFile } from "grammy";
import { loadConfig } from "../config/config.js";
import { logVerbose } from "../globals.js";
import { recordChannelActivity } from "../infra/channel-activity.js";
import { formatErrorMessage } from "../infra/errors.js";
import { recordProviderActivity } from "../infra/provider-activity.js";
import type { RetryConfig } from "../infra/retry.js";
import { createTelegramRetryRunner } from "../infra/retry-policy.js";
import { mediaKindFromMime } from "../media/constants.js";
@@ -65,7 +65,7 @@ function resolveToken(
if (explicit?.trim()) return explicit.trim();
if (!params.token) {
throw new Error(
`Telegram bot token missing for account "${params.accountId}" (set telegram.accounts.${params.accountId}.botToken/tokenFile or TELEGRAM_BOT_TOKEN for default).`,
`Telegram bot token missing for account "${params.accountId}" (set channels.telegram.accounts.${params.accountId}.botToken/tokenFile or TELEGRAM_BOT_TOKEN for default).`,
);
}
return params.token.trim();
@@ -269,8 +269,8 @@ export async function sendMessageTelegram(
});
}
const messageId = String(result?.message_id ?? "unknown");
recordProviderActivity({
provider: "telegram",
recordChannelActivity({
channel: "telegram",
accountId: account.accountId,
direction: "outbound",
});
@@ -324,8 +324,8 @@ export async function sendMessageTelegram(
throw wrapChatNotFound(err);
});
const messageId = String(res?.message_id ?? "unknown");
recordProviderActivity({
provider: "telegram",
recordChannelActivity({
channel: "telegram",
accountId: account.accountId,
direction: "outbound",
});

View File

@@ -18,7 +18,9 @@ describe("resolveTelegramToken", () => {
it("prefers env token over config", () => {
vi.stubEnv("TELEGRAM_BOT_TOKEN", "env-token");
const cfg = { telegram: { botToken: "cfg-token" } } as ClawdbotConfig;
const cfg = {
channels: { telegram: { botToken: "cfg-token" } },
} as ClawdbotConfig;
const res = resolveTelegramToken(cfg);
expect(res.token).toBe("env-token");
expect(res.source).toBe("env");
@@ -29,7 +31,7 @@ describe("resolveTelegramToken", () => {
const dir = withTempDir();
const tokenFile = path.join(dir, "token.txt");
fs.writeFileSync(tokenFile, "file-token\n", "utf-8");
const cfg = { telegram: { tokenFile } } as ClawdbotConfig;
const cfg = { channels: { telegram: { tokenFile } } } as ClawdbotConfig;
const res = resolveTelegramToken(cfg);
expect(res.token).toBe("file-token");
expect(res.source).toBe("tokenFile");
@@ -38,7 +40,9 @@ describe("resolveTelegramToken", () => {
it("falls back to config token when no env or tokenFile", () => {
vi.stubEnv("TELEGRAM_BOT_TOKEN", "");
const cfg = { telegram: { botToken: "cfg-token" } } as ClawdbotConfig;
const cfg = {
channels: { telegram: { botToken: "cfg-token" } },
} as ClawdbotConfig;
const res = resolveTelegramToken(cfg);
expect(res.token).toBe("cfg-token");
expect(res.source).toBe("config");
@@ -49,7 +53,7 @@ describe("resolveTelegramToken", () => {
const dir = withTempDir();
const tokenFile = path.join(dir, "missing-token.txt");
const cfg = {
telegram: { tokenFile, botToken: "cfg-token" },
channels: { telegram: { tokenFile, botToken: "cfg-token" } },
} as ClawdbotConfig;
const res = resolveTelegramToken(cfg);
expect(res.token).toBe("");

View File

@@ -24,15 +24,16 @@ export function resolveTelegramToken(
opts: ResolveTelegramTokenOpts = {},
): TelegramTokenResolution {
const accountId = normalizeAccountId(opts.accountId);
const telegramCfg = cfg?.channels?.telegram;
const accountCfg =
accountId !== DEFAULT_ACCOUNT_ID
? cfg?.telegram?.accounts?.[accountId]
: cfg?.telegram?.accounts?.[DEFAULT_ACCOUNT_ID];
? telegramCfg?.accounts?.[accountId]
: telegramCfg?.accounts?.[DEFAULT_ACCOUNT_ID];
const accountTokenFile = accountCfg?.tokenFile?.trim();
if (accountTokenFile) {
if (!fs.existsSync(accountTokenFile)) {
opts.logMissingFile?.(
`telegram.accounts.${accountId}.tokenFile not found: ${accountTokenFile}`,
`channels.telegram.accounts.${accountId}.tokenFile not found: ${accountTokenFile}`,
);
return { token: "", source: "none" };
}
@@ -43,7 +44,9 @@ export function resolveTelegramToken(
}
} catch (err) {
opts.logMissingFile?.(
`telegram.accounts.${accountId}.tokenFile read failed: ${String(err)}`,
`channels.telegram.accounts.${accountId}.tokenFile read failed: ${String(
err,
)}`,
);
return { token: "", source: "none" };
}
@@ -63,10 +66,12 @@ export function resolveTelegramToken(
return { token: envToken, source: "env" };
}
const tokenFile = cfg?.telegram?.tokenFile?.trim();
const tokenFile = telegramCfg?.tokenFile?.trim();
if (tokenFile && allowEnv) {
if (!fs.existsSync(tokenFile)) {
opts.logMissingFile?.(`telegram.tokenFile not found: ${tokenFile}`);
opts.logMissingFile?.(
`channels.telegram.tokenFile not found: ${tokenFile}`,
);
return { token: "", source: "none" };
}
try {
@@ -75,12 +80,14 @@ export function resolveTelegramToken(
return { token, source: "tokenFile" };
}
} catch (err) {
opts.logMissingFile?.(`telegram.tokenFile read failed: ${String(err)}`);
opts.logMissingFile?.(
`channels.telegram.tokenFile read failed: ${String(err)}`,
);
return { token: "", source: "none" };
}
}
const configToken = cfg?.telegram?.botToken?.trim();
const configToken = telegramCfg?.botToken?.trim();
if (configToken && allowEnv) {
return { token: configToken, source: "config" };
}