From 4935ab1ff0ad3cf939ef5faaa4a181b7780b775c Mon Sep 17 00:00:00 2001 From: Ruben Cuevas Date: Sat, 25 Apr 2026 14:43:06 -0400 Subject: [PATCH] fix(telegram): log inbound gateway watch messages --- extensions/telegram/src/bot-message.test.ts | 82 +++++++++++++++++---- extensions/telegram/src/bot-message.ts | 39 +++++++++- 2 files changed, 105 insertions(+), 16 deletions(-) diff --git a/extensions/telegram/src/bot-message.test.ts b/extensions/telegram/src/bot-message.test.ts index 56bf99da122..9743cbbf15d 100644 --- a/extensions/telegram/src/bot-message.test.ts +++ b/extensions/telegram/src/bot-message.test.ts @@ -3,10 +3,22 @@ import type { TelegramBotDeps } from "./bot-deps.js"; const buildTelegramMessageContext = vi.hoisted(() => vi.fn()); const dispatchTelegramMessage = vi.hoisted(() => vi.fn()); +const telegramInboundInfo = vi.hoisted(() => vi.fn()); const upsertChannelPairingRequest = vi.hoisted(() => vi.fn(async () => ({ code: "PAIRCODE", created: true })), ); +vi.mock("openclaw/plugin-sdk/runtime-env", () => ({ + createSubsystemLogger: () => ({ + child: () => ({ + info: telegramInboundInfo, + }), + }), + danger: (message: string) => message, + logVerbose: vi.fn(), + shouldLogVerbose: () => false, +})); + vi.mock("./bot-message-context.js", () => ({ buildTelegramMessageContext, })); @@ -16,15 +28,18 @@ vi.mock("./bot-message-dispatch.js", () => ({ })); let createTelegramMessageProcessor: typeof import("./bot-message.js").createTelegramMessageProcessor; +let formatTelegramInboundLogLine: typeof import("./bot-message.js").formatTelegramInboundLogLine; describe("telegram bot message processor", () => { beforeAll(async () => { - ({ createTelegramMessageProcessor } = await import("./bot-message.js")); + ({ createTelegramMessageProcessor, formatTelegramInboundLogLine } = + await import("./bot-message.js")); }); beforeEach(() => { buildTelegramMessageContext.mockClear(); dispatchTelegramMessage.mockClear(); + telegramInboundInfo.mockClear(); upsertChannelPairingRequest.mockClear(); }); @@ -76,10 +91,7 @@ describe("telegram bot message processor", () => { sendMessage: ReturnType, ) { const runtimeError = vi.fn(); - buildTelegramMessageContext.mockResolvedValue({ - sendTyping: vi.fn().mockResolvedValue(undefined), - ...context, - }); + buildTelegramMessageContext.mockResolvedValue(createMessageContext(context)); dispatchTelegramMessage.mockRejectedValue(new Error("dispatch exploded")); const processMessage = createTelegramMessageProcessor({ ...baseDeps, @@ -89,13 +101,29 @@ describe("telegram bot message processor", () => { return { processMessage, runtimeError }; } + function createMessageContext(context: Record = {}) { + return { + chatId: 123, + ctxPayload: { + From: "telegram:123", + To: "telegram:123", + ChatType: "direct", + RawBody: "hello there", + }, + primaryCtx: { me: { username: "openclaw_bot" } }, + route: { sessionKey: "agent:main:main" }, + sendTyping: vi.fn().mockResolvedValue(undefined), + ...context, + }; + } + it("dispatches when context is available", async () => { const sendTyping = vi.fn().mockResolvedValue(undefined); - buildTelegramMessageContext.mockResolvedValue({ - chatId: 123, - route: { sessionKey: "agent:main:main" }, - sendTyping, - }); + buildTelegramMessageContext.mockResolvedValue( + createMessageContext({ + sendTyping, + }), + ); const processMessage = createTelegramMessageProcessor(baseDeps); await processSampleMessage(processMessage); @@ -105,6 +133,9 @@ describe("telegram bot message processor", () => { expect(sendTyping.mock.invocationCallOrder[0]).toBeLessThan( dispatchTelegramMessage.mock.invocationCallOrder[0], ); + expect(telegramInboundInfo).toHaveBeenCalledWith( + "Inbound message telegram:123 -> @openclaw_bot (direct, 11 chars)", + ); }); it("skips dispatch when no context is produced", async () => { @@ -112,15 +143,36 @@ describe("telegram bot message processor", () => { const processMessage = createTelegramMessageProcessor(baseDeps); await processSampleMessage(processMessage); expect(dispatchTelegramMessage).not.toHaveBeenCalled(); + expect(telegramInboundInfo).not.toHaveBeenCalled(); + }); + + it("formats Telegram inbound summaries without message content", () => { + expect( + formatTelegramInboundLogLine({ + from: "telegram:123", + to: "@openclaw_bot", + chatType: "direct", + body: "secret message", + }), + ).toBe("Inbound message telegram:123 -> @openclaw_bot (direct, 14 chars)"); + expect( + formatTelegramInboundLogLine({ + from: "telegram:group:-100", + to: "@openclaw_bot", + chatType: "group", + body: "", + mediaType: "image/jpeg", + }), + ).toBe("Inbound message telegram:group:-100 -> @openclaw_bot (group, image/jpeg, 13 chars)"); }); it("keeps dispatch running when the early typing cue fails", async () => { const sendTyping = vi.fn().mockRejectedValue(new Error("typing failed")); - buildTelegramMessageContext.mockResolvedValue({ - chatId: 123, - route: { sessionKey: "agent:main:main" }, - sendTyping, - }); + buildTelegramMessageContext.mockResolvedValue( + createMessageContext({ + sendTyping, + }), + ); const processMessage = createTelegramMessageProcessor(baseDeps); await processSampleMessage(processMessage); diff --git a/extensions/telegram/src/bot-message.ts b/extensions/telegram/src/bot-message.ts index f69d2c561b2..1cbf929f2f0 100644 --- a/extensions/telegram/src/bot-message.ts +++ b/extensions/telegram/src/bot-message.ts @@ -1,6 +1,11 @@ import type { ReplyToMode } from "openclaw/plugin-sdk/config-types"; import type { TelegramAccountConfig } from "openclaw/plugin-sdk/config-types"; -import { danger, logVerbose, shouldLogVerbose } from "openclaw/plugin-sdk/runtime-env"; +import { + createSubsystemLogger, + danger, + logVerbose, + shouldLogVerbose, +} from "openclaw/plugin-sdk/runtime-env"; import type { RuntimeEnv } from "openclaw/plugin-sdk/runtime-env"; import type { TelegramBotDeps } from "./bot-deps.js"; import { @@ -15,6 +20,23 @@ import { buildTelegramThreadParams } from "./bot/helpers.js"; import type { TelegramContext, TelegramStreamMode } from "./bot/types.js"; import type { TelegramReplyChainEntry } from "./message-cache.js"; +const telegramInboundLog = createSubsystemLogger("gateway/channels/telegram").child("inbound"); + +export function formatTelegramInboundLogLine(params: { + from?: string; + to?: string; + chatType?: string; + body?: string; + mediaType?: string; +}): string { + const from = params.from || "unknown"; + const to = params.to || "telegram"; + const chatType = params.chatType || "direct"; + const kindLabel = params.mediaType ? `, ${params.mediaType}` : ""; + const length = (params.body ?? "").length; + return `Inbound message ${from} -> ${to} (${chatType}${kindLabel}, ${length} chars)`; +} + /** Dependencies injected once when creating the message processor. */ type TelegramMessageProcessorDeps = Omit< BuildTelegramMessageContextParams, @@ -113,6 +135,21 @@ export const createTelegramMessageProcessor = (deps: TelegramMessageProcessorDep void context.sendTyping().catch((err) => { logVerbose(`telegram early typing cue failed for chat ${context.chatId}: ${String(err)}`); }); + telegramInboundLog.info( + formatTelegramInboundLogLine({ + from: context.ctxPayload.From, + to: context.primaryCtx.me?.username + ? `@${context.primaryCtx.me.username}` + : context.ctxPayload.To, + chatType: context.ctxPayload.ChatType, + body: + context.ctxPayload.RawBody ?? + context.ctxPayload.BodyForCommands ?? + context.ctxPayload.BodyForAgent ?? + context.ctxPayload.Body, + mediaType: allMedia[0]?.contentType, + }), + ); try { await dispatchTelegramMessage({ context,