From cbd492d68019f77bd7605016b8aa54c8508c23ed Mon Sep 17 00:00:00 2001 From: Peter Steinberger Date: Fri, 29 May 2026 22:52:10 +0100 Subject: [PATCH] fix(feishu): reopen retryable bot menu replay --- extensions/feishu/src/dedup.ts | 20 +++++++++++++ .../feishu/src/monitor.bot-menu-handler.ts | 7 +++-- .../feishu/src/monitor.bot-menu.test.ts | 29 +++++++++++++------ .../lib/bundled-runtime-sidecar-paths.json | 1 - 4 files changed, 45 insertions(+), 12 deletions(-) diff --git a/extensions/feishu/src/dedup.ts b/extensions/feishu/src/dedup.ts index 3e0c606b1f1b..71c82f94a537 100644 --- a/extensions/feishu/src/dedup.ts +++ b/extensions/feishu/src/dedup.ts @@ -216,6 +216,26 @@ export async function recordProcessedFeishuMessage( return await tryRecordMessagePersistent(normalizedMessageId, namespace, log); } +export async function forgetProcessedFeishuMessage( + messageId: string | undefined | null, + namespace = "global", + log?: (...args: unknown[]) => void, +): Promise { + const normalizedNamespace = normalizeNamespace(namespace); + const normalizedMessageId = normalizeMessageId(messageId); + if (!normalizedMessageId) { + return false; + } + memory.delete(memoryKey(normalizedNamespace, normalizedMessageId)); + const key = dedupeStoreKey(normalizedNamespace, normalizedMessageId); + try { + return openDedupStore(normalizedNamespace).delete(key); + } catch (error) { + log?.(`feishu-dedup: persistent delete failed: ${String(error)}`); + return false; + } +} + export async function hasProcessedFeishuMessage( messageId: string | undefined | null, namespace = "global", diff --git a/extensions/feishu/src/monitor.bot-menu-handler.ts b/extensions/feishu/src/monitor.bot-menu-handler.ts index 69a6611bd85a..0d6b4144d179 100644 --- a/extensions/feishu/src/monitor.bot-menu-handler.ts +++ b/extensions/feishu/src/monitor.bot-menu-handler.ts @@ -4,6 +4,7 @@ import { handleFeishuMessage, type FeishuMessageEvent } from "./bot.js"; import { maybeHandleFeishuQuickActionMenu } from "./card-ux-launcher.js"; import { claimUnprocessedFeishuMessage, + forgetProcessedFeishuMessage, recordProcessedFeishuMessage, releaseFeishuMessageProcessing, } from "./dedup.js"; @@ -131,18 +132,20 @@ export function createFeishuBotMenuHandler(params: { .then(async (handledMenu) => { if (handledMenu) { await recordProcessedFeishuMessage(syntheticMessageId, accountId, log); - releaseFeishuMessageProcessing(syntheticMessageId, accountId); return; } return await handleLegacyMenu(); }) .catch(async (err) => { if (isFeishuRetryableSyntheticEventError(err)) { - releaseFeishuMessageProcessing(syntheticMessageId, accountId); + await forgetProcessedFeishuMessage(syntheticMessageId, accountId, log); } else { await recordProcessedFeishuMessage(syntheticMessageId, accountId, log); } throw err; + }) + .finally(() => { + releaseFeishuMessageProcessing(syntheticMessageId, accountId); }); if (fireAndForget) { promise.catch((err) => { diff --git a/extensions/feishu/src/monitor.bot-menu.test.ts b/extensions/feishu/src/monitor.bot-menu.test.ts index f9c8df2f332a..8fca3e3d6a8a 100644 --- a/extensions/feishu/src/monitor.bot-menu.test.ts +++ b/extensions/feishu/src/monitor.bot-menu.test.ts @@ -1,5 +1,5 @@ import { afterAll, afterEach, beforeEach, describe, expect, it, vi } from "vitest"; -import type { ClawdbotConfig } from "../runtime-api.js"; +import type { ClawdbotConfig, RuntimeEnv } from "../runtime-api.js"; import { expectFirstSentCardUsesFillWidthOnly } from "./card-test-helpers.js"; import { createFeishuBotMenuHandler } from "./monitor.bot-menu-handler.js"; @@ -40,15 +40,18 @@ function createBotMenuEvent(params: { eventKey: string; timestamp: string }) { }; } -async function registerHandlers() { - return createFeishuBotMenuHandler({ - cfg: {} as ClawdbotConfig, - accountId: "default", - runtime: { +async function registerHandlers(params: { runtime?: RuntimeEnv } = {}) { + const runtime = + params.runtime ?? + ({ log: vi.fn(), error: vi.fn(), exit: vi.fn(), - }, + } as RuntimeEnv); + return createFeishuBotMenuHandler({ + cfg: {} as ClawdbotConfig, + accountId: "default", + runtime, chatHistories: new Map(), fireAndForget: true, getBotOpenId: () => "ou_bot", @@ -163,7 +166,8 @@ describe("Feishu bot menu handler", () => { }); it("reopens replay for explicit retryable fallback failures", async () => { - const onBotMenu = await registerHandlers(); + const runtime = { log: vi.fn(), error: vi.fn(), exit: vi.fn() } as RuntimeEnv; + const onBotMenu = await registerHandlers({ runtime }); sendCardFeishuMock .mockImplementationOnce(async () => { throw new Error("boom"); @@ -180,9 +184,16 @@ describe("Feishu bot menu handler", () => { .mockResolvedValueOnce(undefined); await onBotMenu(createBotMenuEvent({ eventKey: "quick-actions", timestamp: "1700000000004" })); + await vi.waitFor(() => { + expect(runtime.error).toHaveBeenCalledWith( + "feishu[default]: error handling bot menu event: FeishuRetryableSyntheticEventError: retry me", + ); + }); await onBotMenu(createBotMenuEvent({ eventKey: "quick-actions", timestamp: "1700000000004" })); expect(sendCardFeishuMock).toHaveBeenCalledTimes(2); - expect(handleFeishuMessageMock).toHaveBeenCalledTimes(1); + await vi.waitFor(() => { + expect(handleFeishuMessageMock).toHaveBeenCalledTimes(2); + }); }); }); diff --git a/scripts/lib/bundled-runtime-sidecar-paths.json b/scripts/lib/bundled-runtime-sidecar-paths.json index b8b9777c2ad4..893b7400b818 100644 --- a/scripts/lib/bundled-runtime-sidecar-paths.json +++ b/scripts/lib/bundled-runtime-sidecar-paths.json @@ -14,7 +14,6 @@ "dist/extensions/signal/runtime-api.js", "dist/extensions/telegram/runtime-api.js", "dist/extensions/telegram/runtime-setter-api.js", - "dist/extensions/tokenjuice/runtime-api.js", "dist/extensions/webhooks/runtime-api.js", "dist/extensions/workboard/runtime-api.js", "dist/extensions/zai/runtime-api.js"