From ede8fe33dfe78b3fc7615773c0fb398596ec5b62 Mon Sep 17 00:00:00 2001 From: Arseniy Palagin Date: Wed, 29 Jul 2026 06:36:19 +0300 Subject: [PATCH] fix(telegram): surface draft stream delivery failures at warn level (#111065) * fix(telegram): surface draft stream delivery failures at warn level createTelegramDraftController wired the draft stream's warn callback to logVerbose, so preview send/edit/cleanup failures ("telegram stream preview failed: ...", "telegram stream preview cleanup failed: ...") were only emitted when verbose logging was enabled. In the default configuration a dying preview/draft stream left no operator-visible trace: the bot just went quiet, especially in progress stream mode where the activity window is the only delivery surface. Route the warn callback through the telegram subsystem logger (telegram/draft-stream) at warn level with lane, chatId, and threadId context so draft delivery failures show up in default logs. The verbose log callback is unchanged. Co-authored-by: Claude * test(telegram): simplify draft warning logger proof Co-authored-by: Arseniy Palagin * fix(telegram): emit draft terminal delivery diagnostics Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Copilot-Session: d1bd0f71-7638-463a-958d-d5b3e2da1047 * fix(telegram): keep draft visibility change scoped Remove the diagnostic expansion so this contributor PR remains focused on its original warn-level logging repair. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Copilot-Session: d1bd0f71-7638-463a-958d-d5b3e2da1047 --------- Co-authored-by: Arseniy Palagin Co-authored-by: Claude Co-authored-by: Peter Steinberger Co-authored-by: Gio Della-Libera Copilot-Session: d1bd0f71-7638-463a-958d-d5b3e2da1047 --- .../src/bot-message-dispatch-draft.ts | 14 ++++++- ...e-dispatch.draft-failures-progress.test.ts | 39 ++++++++++++++++++- 2 files changed, 50 insertions(+), 3 deletions(-) diff --git a/extensions/telegram/src/bot-message-dispatch-draft.ts b/extensions/telegram/src/bot-message-dispatch-draft.ts index 349c1c7b5fbe..689eecc304d6 100644 --- a/extensions/telegram/src/bot-message-dispatch-draft.ts +++ b/extensions/telegram/src/bot-message-dispatch-draft.ts @@ -8,7 +8,7 @@ import type { } from "openclaw/plugin-sdk/config-contracts"; import type { ReplyPayload } from "openclaw/plugin-sdk/reply-payload"; import type { BlockReplyContext } from "openclaw/plugin-sdk/reply-runtime"; -import { logVerbose } from "openclaw/plugin-sdk/runtime-env"; +import { createSubsystemLogger, logVerbose } from "openclaw/plugin-sdk/runtime-env"; import type { TelegramBotDeps } from "./bot-deps.js"; import { resolveMarkdownTableMode } from "./bot-message-dispatch.runtime.js"; import type { @@ -26,6 +26,8 @@ import { recordOutboundMessageForPromptContext } from "./outbound-message-contex import { splitTelegramReasoningText } from "./reasoning-lane-coordinator.js"; import { buildTelegramRichMarkdown, TELEGRAM_RICH_TEXT_LIMIT } from "./rich-message.js"; +const draftLogger = createSubsystemLogger("telegram/draft-stream"); + const DRAFT_MIN_INITIAL_CHARS = 30; type DraftPartialTextUpdate = { @@ -161,7 +163,15 @@ export function createTelegramDraftController(params: { }); }, log: logVerbose, - warn: logVerbose, + // Draft delivery failures must stay operator-visible: verbose-only + // logging hid preview send/edit/cleanup errors, so a dead progress + // stream looked like the bot silently ignoring the user. + warn: (message) => + draftLogger.warn(message, { + lane: laneName, + chatId: params.chatId, + threadId: params.threadSpec.id, + }), }) : undefined; return { diff --git a/extensions/telegram/src/bot-message-dispatch.draft-failures-progress.test.ts b/extensions/telegram/src/bot-message-dispatch.draft-failures-progress.test.ts index 7b67af1d5dbc..cc5e25399720 100644 --- a/extensions/telegram/src/bot-message-dispatch.draft-failures-progress.test.ts +++ b/extensions/telegram/src/bot-message-dispatch.draft-failures-progress.test.ts @@ -1,9 +1,10 @@ -import { expect, it } from "vitest"; +import { expect, it, vi } from "vitest"; import { describeTelegramDispatch, createContext, createDirectSessionPayload, createReasoningStreamContext, + createTelegramDraftStream, deliverReplies, dispatchReplyWithBufferedBlockDispatcher, dispatchWithContext, @@ -11,12 +12,48 @@ import { expectDeliveredReply, expectDeliverRepliesParams, expectWindowCollapsedTo, + mockCallArg, requireInvocationOrder, setupDraftStreams, telegramProgressPreview, } from "./bot-message-dispatch.test-harness.js"; +const draftWarn = vi.hoisted(() => vi.fn()); + +vi.mock("openclaw/plugin-sdk/runtime-env", async (importOriginal) => { + const actual = await importOriginal(); + return { + ...actual, + createSubsystemLogger: (subsystem: string) => { + const logger = actual.createSubsystemLogger(subsystem); + return subsystem === "telegram/draft-stream" ? { ...logger, warn: draftWarn } : logger; + }, + }; +}); + describeTelegramDispatch("dispatchTelegramMessage draft-failures-progress", () => { + it("routes draft stream failures to the warn-level telegram logger with lane context", async () => { + setupDraftStreams({ answerMessageId: 2001 }); + dispatchReplyWithBufferedBlockDispatcher.mockImplementation(async ({ dispatcherOptions }) => { + await dispatcherOptions.deliver({ text: "Final answer" }, { kind: "final" }); + return { queuedFinal: true }; + }); + + await dispatchWithContext({ context: createContext() }); + + const draftParams = mockCallArg(createTelegramDraftStream) as { + warn?: (message: string) => void; + }; + expect(typeof draftParams.warn).toBe("function"); + draftWarn.mockClear(); + draftParams.warn?.("telegram stream preview failed: 400: Bad Request: chat not found"); + + expect(draftWarn).toHaveBeenCalledWith( + "telegram stream preview failed: 400: Bad Request: chat not found", + { lane: "answer", chatId: 123, threadId: 777 }, + ); + }); + it("sends an error fallback when dispatch fails after only partial output", async () => { dispatchReplyWithBufferedBlockDispatcher.mockImplementation(async ({ dispatcherOptions }) => { await dispatcherOptions.deliver({ text: "partial answer" }, { kind: "block" });