From ecb91bbb1a1f9b31ac9dc6afbd8bfffdee722889 Mon Sep 17 00:00:00 2001 From: Gustavo Madeira Santana Date: Mon, 12 Jan 2026 21:59:28 -0500 Subject: [PATCH] Add accountId and config support to Telegram webhook The Telegram webhook and monitor now accept and pass through accountId and config parameters, enabling routing and configuration per Telegram account. Tests have been updated to verify correct bot instantiation and DM routing based on accountId bindings. --- src/telegram/bot.test.ts | 47 ++++++++++++++++++++++++++++++++++++ src/telegram/monitor.ts | 2 ++ src/telegram/webhook.test.ts | 19 ++++++++++++--- src/telegram/webhook.ts | 5 ++++ 4 files changed, 69 insertions(+), 4 deletions(-) diff --git a/src/telegram/bot.test.ts b/src/telegram/bot.test.ts index 65caf1ebf..15d095459 100644 --- a/src/telegram/bot.test.ts +++ b/src/telegram/bot.test.ts @@ -887,6 +887,53 @@ describe("createTelegramBot", () => { expect(replySpy).toHaveBeenCalledTimes(1); }); + it("routes DMs by telegram accountId binding", async () => { + onSpy.mockReset(); + const replySpy = replyModule.__replySpy as unknown as ReturnType< + typeof vi.fn + >; + replySpy.mockReset(); + + loadConfig.mockReturnValue({ + telegram: { + accounts: { + opie: { + botToken: "tok-opie", + dmPolicy: "open", + }, + }, + }, + bindings: [ + { + agentId: "opie", + match: { provider: "telegram", accountId: "opie" }, + }, + ], + }); + + createTelegramBot({ token: "tok", accountId: "opie" }); + const handler = getOnHandler("message") as ( + ctx: Record, + ) => Promise; + + await handler({ + message: { + chat: { id: 123, type: "private" }, + from: { id: 999, username: "testuser" }, + text: "hello", + date: 1736380800, + message_id: 42, + }, + me: { username: "clawdbot_bot" }, + getFile: async () => ({ download: async () => new Uint8Array() }), + }); + + expect(replySpy).toHaveBeenCalledTimes(1); + const payload = replySpy.mock.calls[0][0]; + expect(payload.AccountId).toBe("opie"); + expect(payload.SessionKey).toBe("agent:opie:main"); + }); + it("allows per-group requireMention override", async () => { onSpy.mockReset(); const replySpy = replyModule.__replySpy as unknown as ReturnType< diff --git a/src/telegram/monitor.ts b/src/telegram/monitor.ts index 6ae8e45bb..7ed3f2faf 100644 --- a/src/telegram/monitor.ts +++ b/src/telegram/monitor.ts @@ -96,6 +96,8 @@ export async function monitorTelegramProvider(opts: MonitorTelegramOpts = {}) { if (opts.useWebhook) { await startTelegramWebhook({ token, + accountId: account.accountId, + config: cfg, path: opts.webhookPath, port: opts.webhookPort, secret: opts.webhookSecret, diff --git a/src/telegram/webhook.test.ts b/src/telegram/webhook.test.ts index 657cab274..ba5aa83cc 100644 --- a/src/telegram/webhook.test.ts +++ b/src/telegram/webhook.test.ts @@ -14,25 +14,33 @@ const handlerSpy = vi.fn( const setWebhookSpy = vi.fn(); const stopSpy = vi.fn(); +const createTelegramBotSpy = vi.fn(() => ({ + api: { setWebhook: setWebhookSpy }, + stop: stopSpy, +})); + vi.mock("grammy", () => ({ webhookCallback: () => handlerSpy, })); vi.mock("./bot.js", () => ({ - createTelegramBot: () => ({ - api: { setWebhook: setWebhookSpy }, - stop: stopSpy, - }), + createTelegramBot: (...args: unknown[]) => createTelegramBotSpy(...args), })); describe("startTelegramWebhook", () => { it("starts server, registers webhook, and serves health", async () => { + createTelegramBotSpy.mockClear(); const abort = new AbortController(); const { server } = await startTelegramWebhook({ token: "tok", + accountId: "opie", + config: { bindings: [] }, port: 0, // random free port abortSignal: abort.signal, }); + expect(createTelegramBotSpy).toHaveBeenCalledWith( + expect.objectContaining({ accountId: "opie" }), + ); const address = server.address(); if (!address || typeof address === "string") throw new Error("no address"); const url = `http://127.0.0.1:${address.port}`; @@ -46,9 +54,12 @@ describe("startTelegramWebhook", () => { it("invokes webhook handler on matching path", async () => { handlerSpy.mockClear(); + createTelegramBotSpy.mockClear(); const abort = new AbortController(); const { server } = await startTelegramWebhook({ token: "tok", + accountId: "opie", + config: { bindings: [] }, port: 0, abortSignal: abort.signal, path: "/hook", diff --git a/src/telegram/webhook.ts b/src/telegram/webhook.ts index b0b26b6c7..479f98466 100644 --- a/src/telegram/webhook.ts +++ b/src/telegram/webhook.ts @@ -1,6 +1,7 @@ import { createServer } from "node:http"; import { webhookCallback } from "grammy"; +import type { ClawdbotConfig } from "../config/config.js"; import { formatErrorMessage } from "../infra/errors.js"; import type { RuntimeEnv } from "../runtime.js"; import { defaultRuntime } from "../runtime.js"; @@ -8,6 +9,8 @@ import { createTelegramBot } from "./bot.js"; export async function startTelegramWebhook(opts: { token: string; + accountId?: string; + config?: ClawdbotConfig; path?: string; port?: number; host?: string; @@ -27,6 +30,8 @@ export async function startTelegramWebhook(opts: { token: opts.token, runtime, proxyFetch: opts.fetch, + config: opts.config, + accountId: opts.accountId, }); const handler = webhookCallback(bot, "http", { secretToken: opts.secret,