From b860487aef2f7970a853ba22cc9d250d1fffa8a0 Mon Sep 17 00:00:00 2001 From: clawsweeper <274271284+clawsweeper[bot]@users.noreply.github.com> Date: Sun, 17 May 2026 19:35:00 +0000 Subject: [PATCH] Log Telegram outbound delivery success --- extensions/telegram/src/send.test.ts | 42 ++++++++++++++++++++++++++++ extensions/telegram/src/send.ts | 26 +++++++++++------ 2 files changed, 59 insertions(+), 9 deletions(-) diff --git a/extensions/telegram/src/send.test.ts b/extensions/telegram/src/send.test.ts index 62466209c91..8692ba24783 100644 --- a/extensions/telegram/src/send.test.ts +++ b/extensions/telegram/src/send.test.ts @@ -2052,6 +2052,43 @@ describe("sendMessageTelegram", () => { expect(logs).not.toContain(body); }); + it("logs threadless outbound text delivery after missing-thread fallback", async () => { + const logFile = captureInfoLogs(); + const chatId = "-1001234567890"; + const body = "fallback reply body should stay private"; + const threadErr = new Error("400: Bad Request: message thread not found"); + const sendMessage = vi + .fn() + .mockRejectedValueOnce(threadErr) + .mockResolvedValueOnce({ + message_id: 322, + chat: { id: chatId }, + }); + const api = { sendMessage } as unknown as { + sendMessage: typeof sendMessage; + }; + + await sendMessageTelegram(`telegram:group:${chatId}:topic:271`, body, { + cfg: TELEGRAM_TEST_CFG, + token: "tok", + accountId: "ops", + api, + }); + + expect(sendMessage).toHaveBeenNthCalledWith(1, chatId, body, { + parse_mode: "HTML", + message_thread_id: 271, + }); + expect(sendMessage).toHaveBeenNthCalledWith(2, chatId, body, { + parse_mode: "HTML", + }); + const logs = capturedLogText(logFile); + expect(logs).toContain("outbound send ok"); + expect(logs).toContain("messageId=322"); + expect(logs).not.toContain("threadId=271"); + expect(logs).not.toContain(body); + }); + it("logs successful outbound media delivery without caption or media location", async () => { const logFile = captureInfoLogs(); const chatId = "123"; @@ -2125,6 +2162,7 @@ describe("sendMessageTelegram", () => { }); it("retries media sends without message_thread_id when thread is missing", async () => { + const logFile = captureInfoLogs(); const chatId = "-100123"; const threadErr = new Error("400: Bad Request: message thread not found"); const sendPhoto = vi @@ -2172,6 +2210,10 @@ describe("sendMessageTelegram", () => { }, ); expect(res.messageId).toBe("59"); + const logs = capturedLogText(logFile); + expect(logs).toContain("outbound send ok"); + expect(logs).toContain("messageId=59"); + expect(logs).not.toContain("threadId=271"); }); it("defaults outbound media uploads to 100MB", async () => { diff --git a/extensions/telegram/src/send.ts b/extensions/telegram/src/send.ts index 499d44d4031..b715e291368 100644 --- a/extensions/telegram/src/send.ts +++ b/extensions/telegram/src/send.ts @@ -584,9 +584,9 @@ async function withTelegramThreadFallback< verbose: boolean | undefined, allowThreadlessRetry: boolean, attempt: (effectiveParams: TParams, effectiveLabel: string) => Promise, -): Promise { +): Promise<{ result: T; acceptedParams: TParams }> { try { - return await attempt(params, label); + return { result: await attempt(params, label), acceptedParams: params }; } catch (err) { // Do not widen this fallback to cover "chat not found". // chat-not-found is routing/auth/membership/token; stripping thread IDs hides root cause. @@ -603,7 +603,10 @@ async function withTelegramThreadFallback< ); } const retriedParams = removeMessageThreadIdParam(params); - return await attempt(retriedParams, `${label}-threadless`); + return { + result: await attempt(retriedParams, `${label}-threadless`), + acceptedParams: retriedParams, + }; } } @@ -763,17 +766,22 @@ export async function sendMessageTelegram( ): Promise<{ messageId: string; chatId: string }> => { let lastMessageId = ""; let lastChatId = chatId; + let lastAcceptedParams: TelegramThreadScopedParams | undefined; let sentChunkCount = 0; for (let index = 0; index < chunks.length; index += 1) { const chunk = chunks[index]; if (!chunk) { continue; } - const res = await sendTelegramTextChunk(chunk, buildTextParams(index === chunks.length - 1)); + const { result: res, acceptedParams } = await sendTelegramTextChunk( + chunk, + buildTextParams(index === chunks.length - 1), + ); const messageId = resolveTelegramMessageIdOrThrow(res, context); recordSentMessage(chatId, messageId, cfg); lastMessageId = String(messageId); lastChatId = String(res?.chat?.id ?? chatId); + lastAcceptedParams = acceptedParams; sentChunkCount += 1; } if (lastMessageId) { @@ -783,7 +791,7 @@ export async function sendMessageTelegram( messageId: lastMessageId, operation: "sendMessage", deliveryKind: "text", - messageThreadId: threadParams.message_thread_id, + messageThreadId: lastAcceptedParams?.message_thread_id, replyToMessageId: opts.replyToMessageId, silent: opts.silent, chunkCount: sentChunkCount, @@ -1015,7 +1023,7 @@ export async function sendMessageTelegram( }; })(); - const result = await sendMedia(mediaSender.label, mediaSender.sender); + const { result, acceptedParams } = await sendMedia(mediaSender.label, mediaSender.sender); const mediaMessageId = resolveTelegramMessageIdOrThrow(result, "media send"); const resolvedChatId = String(result?.chat?.id ?? chatId); recordSentMessage(chatId, mediaMessageId, cfg); @@ -1028,7 +1036,7 @@ export async function sendMessageTelegram( .map((part) => part.charAt(0).toUpperCase() + part.slice(1)) .join("")}`, deliveryKind: mediaSender.label, - messageThreadId: threadParams.message_thread_id, + messageThreadId: acceptedParams?.message_thread_id, replyToMessageId: opts.replyToMessageId, silent: opts.silent, }); @@ -1598,7 +1606,7 @@ export async function sendStickerTelegram( const stickerParams = hasThreadParams ? threadParams : undefined; - const result = await withTelegramThreadFallback( + const { result } = await withTelegramThreadFallback( stickerParams, "sticker", opts.verbose, @@ -1706,7 +1714,7 @@ export async function sendPollTelegram( ...(opts.silent === true ? { disable_notification: true } : {}), }; - const result = await withTelegramThreadFallback( + const { result } = await withTelegramThreadFallback( pollParams, "poll", opts.verbose,