fix(telegram): retry failed model selections

This commit is contained in:
Vincent Koc
2026-04-13 17:56:57 +01:00
parent df4c086c52
commit aa017bf9dd
2 changed files with 83 additions and 12 deletions

View File

@@ -1625,19 +1625,23 @@ export const registerTelegramHandlers = ({
selection.provider === resolvedDefault.provider && selection.provider === resolvedDefault.provider &&
selection.model === resolvedDefault.model; selection.model === resolvedDefault.model;
await updateSessionStore(storePath, (store) => { try {
const sessionKey = sessionState.sessionKey; await updateSessionStore(storePath, (store) => {
const entry = store[sessionKey] ?? {}; const sessionKey = sessionState.sessionKey;
store[sessionKey] = entry; const entry = store[sessionKey] ?? {};
applyModelOverrideToSessionEntry({ store[sessionKey] = entry;
entry, applyModelOverrideToSessionEntry({
selection: { entry,
provider: selection.provider, selection: {
model: selection.model, provider: selection.provider,
isDefault: isDefaultSelection, model: selection.model,
}, isDefault: isDefaultSelection,
},
});
}); });
}); } catch (err) {
throw new TelegramRetryableCallbackError(err);
}
// Update message to show success with visual feedback // Update message to show success with visual feedback
const escapeHtml = (text: string) => const escapeHtml = (text: string) =>
@@ -1651,6 +1655,9 @@ export const registerTelegramHandlers = ({
{ parse_mode: "HTML" }, { parse_mode: "HTML" },
); );
} catch (err) { } catch (err) {
if (err instanceof TelegramRetryableCallbackError) {
throw err;
}
await editMessageWithButtons(`❌ Failed to change model: ${String(err)}`, []); await editMessageWithButtons(`❌ Failed to change model: ${String(err)}`, []);
} }
return; return;

View File

@@ -4,6 +4,7 @@ import { afterAll, beforeAll, beforeEach, describe, expect, it, vi } from "vites
import { escapeRegExp, formatEnvelopeTimestamp } from "../../../test/helpers/envelope-timestamp.js"; import { escapeRegExp, formatEnvelopeTimestamp } from "../../../test/helpers/envelope-timestamp.js";
const harness = await import("./bot.create-telegram-bot.test-harness.js"); const harness = await import("./bot.create-telegram-bot.test-harness.js");
const conversationRuntime = await import("openclaw/plugin-sdk/conversation-runtime"); const conversationRuntime = await import("openclaw/plugin-sdk/conversation-runtime");
const configRuntime = await import("openclaw/plugin-sdk/config-runtime");
const EYES_EMOJI = "\u{1F440}"; const EYES_EMOJI = "\u{1F440}";
const { const {
answerCallbackQuerySpy, answerCallbackQuerySpy,
@@ -3156,4 +3157,67 @@ describe("createTelegramBot", () => {
expect(editMessageTextSpy).toHaveBeenCalledTimes(2); expect(editMessageTextSpy).toHaveBeenCalledTimes(2);
expect(editMessageTextSpy.mock.calls.at(-1)?.[2]).toContain("Select a provider:"); expect(editMessageTextSpy.mock.calls.at(-1)?.[2]).toContain("Select a provider:");
}); });
it("retries model selection callbacks after a bubbled session-store 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<string, unknown>, next: () => Promise<void>) => Promise<void> =>
typeof fn === "function",
);
const runMiddlewareChain = async (ctx: Record<string, unknown>) => {
let idx = -1;
const dispatch = async (i: number): Promise<void> => {
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 updateSessionStoreSpy = vi.spyOn(configRuntime, "updateSessionStore");
updateSessionStoreSpy.mockRejectedValueOnce(new Error("session store boom"));
const ctx = {
update: { update_id: 890 },
callbackQuery: {
id: "cbq-model-select-retry-1",
data: "mdl_sel_openai/gpt-5.4",
from: { id: 9, first_name: "Ada", username: "ada_bot" },
message: {
chat: { id: 1234, type: "private" },
date: 1736380800,
message_id: 24,
},
},
me: { username: "openclaw_bot" },
getFile: async () => ({ download: async () => new Uint8Array() }),
};
try {
await expect(runMiddlewareChain(ctx)).rejects.toThrow("session store boom");
await runMiddlewareChain(ctx);
} finally {
updateSessionStoreSpy.mockRestore();
}
expect(editMessageTextSpy).toHaveBeenCalledTimes(1);
expect(String(editMessageTextSpy.mock.calls.at(-1)?.[2] ?? "")).toContain(
"This model will be used for your next message.",
);
expect(
editMessageTextSpy.mock.calls.some((call) =>
String(call[2] ?? "").includes("Failed to change model"),
),
).toBe(false);
});
}); });