diff --git a/extensions/telegram/src/bot-message-dispatch.test.ts b/extensions/telegram/src/bot-message-dispatch.test.ts index 302e5fbb21fd..0e72f5f6a7ad 100644 --- a/extensions/telegram/src/bot-message-dispatch.test.ts +++ b/extensions/telegram/src/bot-message-dispatch.test.ts @@ -913,6 +913,7 @@ describe("dispatchTelegramMessage draft streaming", () => { ); const pipelineArgs = expectRecordFields(mockCallArg(createChannelMessageReplyPipeline), {}); const typing = expectRecordFields(pipelineArgs.typing, {}); + expect(typing.maxConsecutiveFailures).toBe(5); await (typing.start as () => Promise)(); expect(sendChatAction).toHaveBeenCalledWith(-1003774691294, "typing", { message_thread_id: 3731, diff --git a/extensions/telegram/src/bot-message-dispatch.ts b/extensions/telegram/src/bot-message-dispatch.ts index 96501d0b2a78..6bdacea1de44 100644 --- a/extensions/telegram/src/bot-message-dispatch.ts +++ b/extensions/telegram/src/bot-message-dispatch.ts @@ -156,6 +156,8 @@ import { clipTelegramProgressText } from "./truncate.js"; export { resetTelegramReplyFenceForTests }; +// Telegram sendChatAction can fail transiently; keep the tolerance scoped to this transport. +const TELEGRAM_MAX_CONSECUTIVE_TYPING_FAILURES = 5; const EMPTY_RESPONSE_FALLBACK = "No response generated. Please try again."; const silentReplyDispatchLogger = createSubsystemLogger("telegram/silent-reply-dispatch"); @@ -2154,6 +2156,7 @@ export const dispatchTelegramMessage = async ({ accountId: route.accountId, typing: { start: sendTyping, + maxConsecutiveFailures: TELEGRAM_MAX_CONSECUTIVE_TYPING_FAILURES, onStartError: (err) => { logTypingFailure({ log: logVerbose, diff --git a/extensions/telegram/src/bot/delivery.test.ts b/extensions/telegram/src/bot/delivery.test.ts index ddf784978da3..6dafd4facb3d 100644 --- a/extensions/telegram/src/bot/delivery.test.ts +++ b/extensions/telegram/src/bot/delivery.test.ts @@ -212,6 +212,12 @@ function createRichEntityInvalidError(entity = "EMAIL", operation = "sendRichMes ); } +function createRichContentRequiredError(operation = "sendRichMessage") { + return new Error( + `GrammyError: Call to '${operation}' failed! (400: Bad Request: RICH_MESSAGE_CONTENT_REQUIRED)`, + ); +} + function createHtmlParseError(operation = "sendMessage") { return new Error( `GrammyError: Call to '${operation}' failed! (400: Bad Request: can't parse entities: Can't find end of the entity)`, diff --git a/extensions/telegram/src/send.test.ts b/extensions/telegram/src/send.test.ts index d40f32406d0c..420497f57704 100644 --- a/extensions/telegram/src/send.test.ts +++ b/extensions/telegram/src/send.test.ts @@ -292,6 +292,12 @@ function createRichEntityInvalidError(entity = "EMAIL", operation = "sendRichMes ); } +function createRichContentRequiredError(operation = "sendRichMessage"): Error { + return new Error( + `GrammyError: Call to '${operation}' failed! (400: Bad Request: RICH_MESSAGE_CONTENT_REQUIRED)`, + ); +} + function createHtmlParseError(operation = "sendMessage"): Error { return new Error( `GrammyError: Call to '${operation}' failed! (400: Bad Request: can't parse entities: Can't find end of the entity)`, diff --git a/src/channels/typing.test.ts b/src/channels/typing.test.ts index f228108504bd..7a0bd9424e93 100644 --- a/src/channels/typing.test.ts +++ b/src/channels/typing.test.ts @@ -183,6 +183,26 @@ describe("createTypingCallbacks", () => { expect(start).toHaveBeenCalledTimes(2); expect(onStartError).toHaveBeenCalledTimes(2); + await vi.advanceTimersByTimeAsync(9_000); + expect(start).toHaveBeenCalledTimes(2); + }); + }); + + it("honors an explicit higher consecutive failure breaker", async () => { + await withFakeTimers(async () => { + const { start, onStartError, callbacks } = createTypingHarness({ + start: vi.fn().mockRejectedValue(new Error("gone")), + maxConsecutiveFailures: 5, + }); + await callbacks.onReplyStart(); + await flushMicrotasks(); + expect(start).toHaveBeenCalledTimes(1); + expect(onStartError).toHaveBeenCalledTimes(1); + + await vi.advanceTimersByTimeAsync(3_000); + expect(start).toHaveBeenCalledTimes(2); + expect(onStartError).toHaveBeenCalledTimes(2); + await vi.advanceTimersByTimeAsync(3_000); expect(start).toHaveBeenCalledTimes(3); expect(onStartError).toHaveBeenCalledTimes(3); @@ -220,19 +240,11 @@ describe("createTypingCallbacks", () => { expect(onStartError).toHaveBeenCalledTimes(2); await vi.advanceTimersByTimeAsync(3_000); - expect(start).toHaveBeenCalledTimes(3); - expect(onStartError).toHaveBeenCalledTimes(3); - - await vi.advanceTimersByTimeAsync(3_000); - expect(start).toHaveBeenCalledTimes(4); - expect(onStartError).toHaveBeenCalledTimes(4); - - await vi.advanceTimersByTimeAsync(3_000); - expect(start).toHaveBeenCalledTimes(5); - expect(onStartError).toHaveBeenCalledTimes(5); + expect(start).toHaveBeenCalledTimes(2); + expect(onStartError).toHaveBeenCalledTimes(2); await vi.advanceTimersByTimeAsync(9_000); - expect(start).toHaveBeenCalledTimes(5); + expect(start).toHaveBeenCalledTimes(2); }); }); diff --git a/src/channels/typing.ts b/src/channels/typing.ts index 42c5b903b020..784a98d9e533 100644 --- a/src/channels/typing.ts +++ b/src/channels/typing.ts @@ -25,7 +25,7 @@ export type CreateTypingCallbacksParams = { maxDurationMs?: number; }; -const DEFAULT_MAX_CONSECUTIVE_TYPING_FAILURES = 5; +const DEFAULT_MAX_CONSECUTIVE_TYPING_FAILURES = 2; function resolvePositiveIntegerOption(value: number | undefined, fallback: number): number { const parsed = parseFiniteNumber(value);