mirror of
https://github.com/openclaw/openclaw.git
synced 2026-05-06 07:30:43 +00:00
fix(telegram): retry failed model callbacks
This commit is contained in:
@@ -671,6 +671,13 @@ export const registerTelegramHandlers = ({
|
||||
},
|
||||
};
|
||||
|
||||
class TelegramRetryableCallbackError extends Error {
|
||||
constructor(public readonly cause: unknown) {
|
||||
super(String(cause));
|
||||
this.name = "TelegramRetryableCallbackError";
|
||||
}
|
||||
}
|
||||
|
||||
const resolveTelegramEventAuthorizationContext = async (params: {
|
||||
chatId: number;
|
||||
isGroup: boolean;
|
||||
@@ -1436,18 +1443,22 @@ export const registerTelegramHandlers = ({
|
||||
// Model selection callback handler (mdl_prov, mdl_list_*, mdl_sel_*, mdl_back)
|
||||
const modelCallback = parseModelCallbackData(data);
|
||||
if (modelCallback) {
|
||||
const sessionState = resolveTelegramSessionState({
|
||||
chatId,
|
||||
isGroup,
|
||||
isForum,
|
||||
messageThreadId,
|
||||
resolvedThreadId,
|
||||
senderId,
|
||||
});
|
||||
const modelData = await telegramDeps.buildModelsProviderData(
|
||||
runtimeCfg,
|
||||
sessionState.agentId,
|
||||
);
|
||||
let sessionState: ReturnType<typeof resolveTelegramSessionState>;
|
||||
let modelData: Awaited<ReturnType<typeof telegramDeps.buildModelsProviderData>>;
|
||||
try {
|
||||
// Retry only the callback preflight that happens before any visible chat mutation.
|
||||
sessionState = resolveTelegramSessionState({
|
||||
chatId,
|
||||
isGroup,
|
||||
isForum,
|
||||
messageThreadId,
|
||||
resolvedThreadId,
|
||||
senderId,
|
||||
});
|
||||
modelData = await telegramDeps.buildModelsProviderData(runtimeCfg, sessionState.agentId);
|
||||
} catch (err) {
|
||||
throw new TelegramRetryableCallbackError(err);
|
||||
}
|
||||
const { byProvider, providers } = modelData;
|
||||
|
||||
const editMessageWithButtons = async (
|
||||
@@ -1511,15 +1522,7 @@ export const registerTelegramHandlers = ({
|
||||
const safePage = Math.max(1, Math.min(page, totalPages));
|
||||
|
||||
// Resolve current model from session (prefer overrides)
|
||||
const currentSessionState = resolveTelegramSessionState({
|
||||
chatId,
|
||||
isGroup,
|
||||
isForum,
|
||||
messageThreadId,
|
||||
resolvedThreadId,
|
||||
senderId,
|
||||
});
|
||||
const currentModel = currentSessionState.model;
|
||||
const currentModel = sessionState.model;
|
||||
|
||||
const buttons = buildModelsKeyboard({
|
||||
provider,
|
||||
@@ -1533,8 +1536,8 @@ export const registerTelegramHandlers = ({
|
||||
provider,
|
||||
total: models.length,
|
||||
cfg,
|
||||
agentDir: resolveAgentDir(cfg, currentSessionState.agentId),
|
||||
sessionEntry: currentSessionState.sessionEntry,
|
||||
agentDir: resolveAgentDir(cfg, sessionState.agentId),
|
||||
sessionEntry: sessionState.sessionEntry,
|
||||
});
|
||||
await editMessageWithButtons(text, buttons);
|
||||
return;
|
||||
@@ -1635,6 +1638,9 @@ export const registerTelegramHandlers = ({
|
||||
});
|
||||
} catch (err) {
|
||||
runtime.error?.(danger(`callback handler failed: ${String(err)}`));
|
||||
if (err instanceof TelegramRetryableCallbackError) {
|
||||
throw err.cause;
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
@@ -9,6 +9,7 @@ const {
|
||||
botCtorSpy,
|
||||
commandSpy,
|
||||
dispatchReplyWithBufferedBlockDispatcher,
|
||||
editMessageTextSpy,
|
||||
enqueueSystemEventSpy,
|
||||
getLoadWebMediaMock,
|
||||
getChatSpy,
|
||||
@@ -2859,4 +2860,76 @@ describe("createTelegramBot", () => {
|
||||
expect(enqueueSystemEventSpy).toHaveBeenCalledTimes(2);
|
||||
expect(enqueueSystemEventSpy.mock.calls.at(-1)?.[0]).toContain("Telegram reaction added:");
|
||||
});
|
||||
|
||||
it("retries model callback updates after a bubbled preflight failure", async () => {
|
||||
loadConfig.mockReturnValue({
|
||||
agents: {
|
||||
defaults: {
|
||||
model: "openai/gpt-5.4",
|
||||
},
|
||||
},
|
||||
channels: {
|
||||
telegram: {
|
||||
dmPolicy: "open",
|
||||
allowFrom: ["*"],
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
const buildModelsProviderDataMock =
|
||||
telegramBotDepsForTest.buildModelsProviderData as unknown as ReturnType<typeof vi.fn>;
|
||||
buildModelsProviderDataMock.mockClear();
|
||||
editMessageTextSpy.mockClear();
|
||||
|
||||
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 ctx = {
|
||||
update: { update_id: 666 },
|
||||
callbackQuery: {
|
||||
id: "cbq-model-retry-1",
|
||||
data: "mdl_prov",
|
||||
from: { id: 9, first_name: "Ada", username: "ada_bot" },
|
||||
message: {
|
||||
chat: { id: 1234, type: "private" },
|
||||
date: 1736380800,
|
||||
message_id: 18,
|
||||
},
|
||||
},
|
||||
me: { username: "openclaw_bot" },
|
||||
getFile: async () => ({ download: async () => new Uint8Array() }),
|
||||
};
|
||||
|
||||
buildModelsProviderDataMock.mockImplementationOnce(async () => {
|
||||
throw new Error("providers boom");
|
||||
});
|
||||
await expect(runMiddlewareChain(ctx)).rejects.toThrow("providers boom");
|
||||
await runMiddlewareChain(ctx);
|
||||
|
||||
expect(buildModelsProviderDataMock).toHaveBeenCalledTimes(2);
|
||||
expect(editMessageTextSpy).toHaveBeenCalledTimes(1);
|
||||
expect(editMessageTextSpy.mock.calls[0]?.[2]).toContain("Select a provider:");
|
||||
});
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user