Skip to content

Commit c4fe168

Browse files
committed
fix(telegram): cool down transient sendChatAction failures
1 parent 3423649 commit c4fe168

4 files changed

Lines changed: 61 additions & 1 deletion

File tree

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ Docs: https://docs.openclaw.ai
1212

1313
### Fixes
1414

15+
- Channels/Telegram: cool down transient `sendChatAction` network, rate-limit, and server failures without reclassifying broad message text, so typing indicators back off during Telegram outages while the shared typing guard still trips. Carries forward #55886; refs #55811, #55838, #56096, and #56153. Thanks @Boulea7, @sumaiazaman, and @Huangting-xy.
1516
- Control UI/WebChat: keep large attachment payloads out of Lit state and optimistic chat messages, using object URL previews plus send-time payload serialization so PDF/image uploads no longer trigger `RangeError: Maximum call stack size exceeded`. Fixes #73360; refs #54378 and #63432. Thanks @hejunhui-73, @Ansub, and @christianhernandez3-afk.
1617
- Agents/models: keep per-agent primary models strict when `fallbacks` is omitted, so probe-only custom providers are not tried as hidden fallback candidates unless the agent explicitly opts in. Fixes #73332. Thanks @haumanto.
1718
- Gateway/models: add `models.pricing.enabled` so offline or restricted-network installs can skip startup OpenRouter and LiteLLM pricing-catalog fetches while keeping explicit model costs working. Fixes #53639. Thanks @callebtc, @palewire, and @rjdjohnston.

extensions/telegram/src/network-errors.test.ts

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -219,6 +219,16 @@ describe("isTelegramServerError", () => {
219219
});
220220

221221
describe("isTelegramRateLimitError", () => {
222+
class MockHttpError extends Error {
223+
constructor(
224+
message: string,
225+
public readonly error: unknown,
226+
) {
227+
super(message);
228+
this.name = "HttpError";
229+
}
230+
}
231+
222232
it.each([
223233
["Too Many Requests", 429, true],
224234
["Forbidden", 403, false],
@@ -231,6 +241,19 @@ describe("isTelegramRateLimitError", () => {
231241
const outer = Object.assign(new Error("wrapped"), { cause: inner });
232242
expect(isTelegramRateLimitError(outer)).toBe(true);
233243
});
244+
245+
it("detects structured grammY HttpError payloads", () => {
246+
const wrapped = new MockHttpError("Too Many Requests", {
247+
error_code: 429,
248+
description: "Too Many Requests: retry after 5",
249+
parameters: { retry_after: 5 },
250+
});
251+
expect(isTelegramRateLimitError(wrapped)).toBe(true);
252+
});
253+
254+
it("does not infer rate limits from plain text", () => {
255+
expect(isTelegramRateLimitError(new Error("429 Too Many Requests"))).toBe(false);
256+
});
234257
});
235258

236259
describe("isTelegramClientRejection", () => {

extensions/telegram/src/sendchataction-401-backoff.test.ts

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,8 @@ describe("createTelegramSendChatActionHandler", () => {
3434
const make401Error = () => new Error("401 Unauthorized");
3535
const makeTransientNetworkError = () =>
3636
Object.assign(new Error("TypeError: fetch failed"), { code: "ETIMEDOUT" });
37+
const makeBroadNetworkMessageError = () =>
38+
new Error("Network request for 'sendChatAction' failed!");
3739
const makeRateLimitError = () => ({
3840
error_code: 429,
3941
message: "429 Too Many Requests",
@@ -200,6 +202,40 @@ describe("createTelegramSendChatActionHandler", () => {
200202
expect(logger).toHaveBeenCalledWith(expect.stringContaining("transient failure"));
201203
});
202204

205+
it("does not cool down broad network-message errors without structured network details", async () => {
206+
const fn = vi.fn().mockRejectedValue(makeBroadNetworkMessageError());
207+
const logger = vi.fn();
208+
const { handler } = createHandler({
209+
sendChatActionFn: fn,
210+
logger,
211+
});
212+
213+
await expect(handler.sendChatAction(123, "typing")).rejects.toThrow(
214+
"Network request for 'sendChatAction' failed!",
215+
);
216+
await expect(handler.sendChatAction(123, "typing")).rejects.toThrow(
217+
"Network request for 'sendChatAction' failed!",
218+
);
219+
220+
expect(fn).toHaveBeenCalledTimes(2);
221+
expect(logger).not.toHaveBeenCalledWith(expect.stringContaining("transient failure"));
222+
});
223+
224+
it("keeps rejecting during transient cooldown so the typing start guard can trip", async () => {
225+
const fn = vi.fn().mockRejectedValue(makeTransientNetworkError());
226+
const logger = vi.fn();
227+
const { handler } = createHandler({
228+
sendChatActionFn: fn,
229+
logger,
230+
});
231+
232+
await expect(handler.sendChatAction(123, "typing")).rejects.toThrow("fetch failed");
233+
await expect(handler.sendChatAction(123, "typing")).rejects.toThrow("fetch failed");
234+
235+
expect(fn).toHaveBeenCalledTimes(1);
236+
expect(logger).toHaveBeenCalledTimes(1);
237+
});
238+
203239
it("treats Telegram 5xx responses as transient and does not suspend", async () => {
204240
const fn = vi.fn().mockRejectedValue(makeServerError());
205241
const logger = vi.fn();

extensions/telegram/src/sendchataction-401-backoff.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -85,7 +85,7 @@ function is401Error(error: unknown): boolean {
8585
function isTransientSendChatActionError(error: unknown): boolean {
8686
return (
8787
isTelegramRateLimitError(error) ||
88-
isRecoverableTelegramNetworkError(error, { context: "unknown", allowMessageMatch: true }) ||
88+
isRecoverableTelegramNetworkError(error, { context: "send", allowMessageMatch: false }) ||
8989
isTelegramServerError(error)
9090
);
9191
}

0 commit comments

Comments
 (0)