diff --git a/extensions/telegram/src/bot-handlers.runtime.ts b/extensions/telegram/src/bot-handlers.runtime.ts index f6378820957..8e70609d713 100644 --- a/extensions/telegram/src/bot-handlers.runtime.ts +++ b/extensions/telegram/src/bot-handlers.runtime.ts @@ -1498,7 +1498,11 @@ export const registerTelegramHandlers = ({ if (modelCallback.type === "providers" || modelCallback.type === "back") { if (providers.length === 0) { - await editMessageWithButtons("No providers available.", []); + try { + await editMessageWithButtons("No providers available.", []); + } catch (err) { + throw new TelegramRetryableCallbackError(err); + } return; } const providerInfos: ProviderInfo[] = providers.map((p) => ({ @@ -1506,7 +1510,11 @@ export const registerTelegramHandlers = ({ count: byProvider.get(p)?.size ?? 0, })); const buttons = buildProviderKeyboard(providerInfos); - await editMessageWithButtons("Select a provider:", buttons); + try { + await editMessageWithButtons("Select a provider:", buttons); + } catch (err) { + throw new TelegramRetryableCallbackError(err); + } return; } @@ -1520,10 +1528,14 @@ export const registerTelegramHandlers = ({ count: byProvider.get(p)?.size ?? 0, })); const buttons = buildProviderKeyboard(providerInfos); - await editMessageWithButtons( - `Unknown provider: ${provider}\n\nSelect a provider:`, - buttons, - ); + try { + await editMessageWithButtons( + `Unknown provider: ${provider}\n\nSelect a provider:`, + buttons, + ); + } catch (err) { + throw new TelegramRetryableCallbackError(err); + } return; } const models = [...modelSet].toSorted(); @@ -1549,7 +1561,11 @@ export const registerTelegramHandlers = ({ agentDir: resolveAgentDir(cfg, sessionState.agentId), sessionEntry: sessionState.sessionEntry, }); - await editMessageWithButtons(text, buttons); + try { + await editMessageWithButtons(text, buttons); + } catch (err) { + throw new TelegramRetryableCallbackError(err); + } return; } @@ -1565,19 +1581,27 @@ export const registerTelegramHandlers = ({ count: byProvider.get(p)?.size ?? 0, })); const buttons = buildProviderKeyboard(providerInfos); - await editMessageWithButtons( - `Could not resolve model "${selection.model}".\n\nSelect a provider:`, - buttons, - ); + try { + await editMessageWithButtons( + `Could not resolve model "${selection.model}".\n\nSelect a provider:`, + buttons, + ); + } catch (err) { + throw new TelegramRetryableCallbackError(err); + } return; } const modelSet = byProvider.get(selection.provider); if (!modelSet?.has(selection.model)) { - await editMessageWithButtons( - `❌ Model "${selection.provider}/${selection.model}" is not allowed.`, - [], - ); + try { + await editMessageWithButtons( + `❌ Model "${selection.provider}/${selection.model}" is not allowed.`, + [], + ); + } catch (err) { + throw new TelegramRetryableCallbackError(err); + } return; } diff --git a/extensions/telegram/src/bot.create-telegram-bot.test.ts b/extensions/telegram/src/bot.create-telegram-bot.test.ts index b6a68a7332b..6a825ce09a0 100644 --- a/extensions/telegram/src/bot.create-telegram-bot.test.ts +++ b/extensions/telegram/src/bot.create-telegram-bot.test.ts @@ -3104,4 +3104,56 @@ describe("createTelegramBot", () => { expect(sendMessageSpy).toHaveBeenCalledTimes(1); expect(sendMessageSpy.mock.calls[0]?.[1]).toContain("plugin bind approval"); }); + + it("retries model provider callbacks after a bubbled edit failure", async () => { + createTelegramBot({ token: "tok" }); + const callbackHandler = getOnHandler("callback_query"); + const middlewares = middlewareUseSpy.mock.calls + .map((call) => call[0]) + .filter( + (fn): fn is (ctx: Record, next: () => Promise) => Promise => + typeof fn === "function", + ); + const runMiddlewareChain = async (ctx: Record) => { + let idx = -1; + const dispatch = async (i: number): Promise => { + if (i <= idx) { + throw new Error("middleware dispatch called multiple times"); + } + idx = i; + const fn = middlewares[i]; + if (!fn) { + await callbackHandler(ctx); + return; + } + await fn(ctx, async () => dispatch(i + 1)); + }; + await dispatch(0); + }; + + const ctx = { + update: { update_id: 889 }, + callbackQuery: { + id: "cbq-model-provider-retry-1", + data: "mdl_prov", + from: { id: 9, first_name: "Ada", username: "ada_bot" }, + message: { + chat: { id: 1234, type: "private" }, + date: 1736380800, + message_id: 23, + }, + }, + me: { username: "openclaw_bot" }, + getFile: async () => ({ download: async () => new Uint8Array() }), + }; + + editMessageTextSpy.mockImplementationOnce(async () => { + throw new Error("edit boom"); + }); + await expect(runMiddlewareChain(ctx)).rejects.toThrow("edit boom"); + await runMiddlewareChain(ctx); + + expect(editMessageTextSpy).toHaveBeenCalledTimes(2); + expect(editMessageTextSpy.mock.calls.at(-1)?.[2]).toContain("Select a provider:"); + }); });