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 <noreply@anthropic.com>

* test(telegram): simplify draft warning logger proof

Co-authored-by: Arseniy Palagin <valeradzigurda3@gmail.com>

* 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 <valeradzigurda3@gmail.com>
Co-authored-by: Claude <noreply@anthropic.com>
Co-authored-by: Peter Steinberger <steipete@gmail.com>
Co-authored-by: Gio Della-Libera <giodl73@gmail.com>
Copilot-Session: d1bd0f71-7638-463a-958d-d5b3e2da1047
This commit is contained in:
Arseniy Palagin
2026-07-29 06:36:19 +03:00
committed by GitHub
parent 5cc9693507
commit ede8fe33df
2 changed files with 50 additions and 3 deletions

View File

@@ -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 {

View File

@@ -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<typeof import("openclaw/plugin-sdk/runtime-env")>();
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" });