fix(telegram): retry failed plugin binding callbacks

This commit is contained in:
Vincent Koc
2026-04-13 17:34:51 +01:00
parent 96a6f55da8
commit f1ec7a75f6
2 changed files with 73 additions and 5 deletions

View File

@@ -1283,11 +1283,16 @@ export const registerTelegramHandlers = ({
callbackThreadId != null ? `${chatId}:topic:${callbackThreadId}` : String(chatId);
const pluginBindingApproval = parsePluginBindingApprovalCustomId(data);
if (pluginBindingApproval) {
const resolved = await resolvePluginConversationBindingApproval({
approvalId: pluginBindingApproval.approvalId,
decision: pluginBindingApproval.decision,
senderId: senderId || undefined,
});
let resolved: Awaited<ReturnType<typeof resolvePluginConversationBindingApproval>>;
try {
resolved = await resolvePluginConversationBindingApproval({
approvalId: pluginBindingApproval.approvalId,
decision: pluginBindingApproval.decision,
senderId: senderId || undefined,
});
} catch (err) {
throw new TelegramRetryableCallbackError(err);
}
await clearCallbackButtons();
await replyToCallbackChat(buildPluginBindingResolvedText(resolved));
return;

View File

@@ -3,12 +3,14 @@ import { withEnvAsync } from "openclaw/plugin-sdk/testing";
import { afterAll, beforeAll, beforeEach, describe, expect, it, vi } from "vitest";
import { escapeRegExp, formatEnvelopeTimestamp } from "../../../test/helpers/envelope-timestamp.js";
const harness = await import("./bot.create-telegram-bot.test-harness.js");
const conversationRuntime = await import("openclaw/plugin-sdk/conversation-runtime");
const EYES_EMOJI = "\u{1F440}";
const {
answerCallbackQuerySpy,
botCtorSpy,
commandSpy,
dispatchReplyWithBufferedBlockDispatcher,
editMessageReplyMarkupSpy,
editMessageTextSpy,
enqueueSystemEventSpy,
getLoadWebMediaMock,
@@ -2984,4 +2986,65 @@ describe("createTelegramBot", () => {
expect(editMessageTextSpy).toHaveBeenCalledTimes(2);
expect(editMessageTextSpy.mock.calls.at(-1)?.[2]).toContain("Commands (2/");
});
it("retries plugin binding approval callbacks after a bubbled resolution 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 resolvePluginBindingApprovalSpy = vi.spyOn(
conversationRuntime,
"resolvePluginConversationBindingApproval",
);
resolvePluginBindingApprovalSpy.mockRejectedValueOnce(new Error("binding boom"));
const ctx = {
update: { update_id: 888 },
callbackQuery: {
id: "cbq-plugin-binding-retry-1",
data: conversationRuntime.buildPluginBindingApprovalCustomId("binding-1", "allow-once"),
from: { id: 9, first_name: "Ada", username: "ada_bot" },
message: {
chat: { id: 1234, type: "private" },
date: 1736380800,
message_id: 20,
text: "Plugin approval required.",
},
},
me: { username: "openclaw_bot" },
getFile: async () => ({ download: async () => new Uint8Array() }),
};
try {
await expect(runMiddlewareChain(ctx)).rejects.toThrow("binding boom");
await runMiddlewareChain(ctx);
} finally {
resolvePluginBindingApprovalSpy.mockRestore();
}
expect(editMessageReplyMarkupSpy).toHaveBeenCalledTimes(1);
expect(sendMessageSpy).toHaveBeenCalledTimes(1);
expect(sendMessageSpy.mock.calls[0]?.[1]).toContain("plugin bind approval");
});
});