mirror of
https://github.com/openclaw/openclaw.git
synced 2026-08-09 03:22:40 +00:00
fix(feishu): preserve debounce dedupe identity
This commit is contained in:
@@ -8,6 +8,7 @@ import type { ClawdbotConfig, PluginRuntime } from "../runtime-api.js";
|
||||
import { parseMergeForwardContent } from "./bot-content.js";
|
||||
import type { FeishuMessageEvent } from "./bot.js";
|
||||
import { handleFeishuMessage } from "./bot.js";
|
||||
import { resolveFeishuMessageDedupeKey } from "./dedupe-key.js";
|
||||
import { createFeishuMessageReceiveHandler } from "./monitor.message-handler.js";
|
||||
import { setFeishuRuntime } from "./runtime.js";
|
||||
|
||||
@@ -4166,6 +4167,70 @@ describe("handleFeishuMessage command authorization", () => {
|
||||
});
|
||||
|
||||
describe("createFeishuMessageReceiveHandler media dedupe", () => {
|
||||
it("preserves the original dispatch dedupe key when debounce merges text content", async () => {
|
||||
const handleMessage = vi.fn(async () => undefined);
|
||||
const core = {
|
||||
channel: {
|
||||
debounce: {
|
||||
resolveInboundDebounceMs: vi.fn(() => 10),
|
||||
createInboundDebouncer: vi.fn(
|
||||
(options: { onFlush: (entries: FeishuMessageEvent[]) => Promise<void> | void }) => {
|
||||
const entries: FeishuMessageEvent[] = [];
|
||||
return {
|
||||
enqueue: async (event: FeishuMessageEvent) => {
|
||||
entries.push(event);
|
||||
if (entries.length === 2) {
|
||||
await options.onFlush(entries);
|
||||
}
|
||||
},
|
||||
};
|
||||
},
|
||||
),
|
||||
},
|
||||
commands: {
|
||||
isControlCommandMessage: vi.fn(() => false),
|
||||
},
|
||||
},
|
||||
} as unknown as PluginRuntime;
|
||||
const createTextEvent = (messageId: string, createTime: string, text: string) =>
|
||||
({
|
||||
sender: { sender_id: { open_id: "ou-text-debounce" } },
|
||||
message: {
|
||||
message_id: messageId,
|
||||
chat_id: "oc-dm",
|
||||
chat_type: "p2p",
|
||||
message_type: "text",
|
||||
content: JSON.stringify({ text }),
|
||||
create_time: createTime,
|
||||
},
|
||||
}) satisfies FeishuMessageEvent;
|
||||
const last = createTextEvent("msg-text-last", "1710000001000", "second");
|
||||
const handler = createFeishuMessageReceiveHandler({
|
||||
cfg: { channels: { feishu: { dmPolicy: "open" } } } as ClawdbotConfig,
|
||||
channelRuntime: core.channel,
|
||||
accountId: "receive-text-debounce",
|
||||
chatHistories: new Map(),
|
||||
handleMessage,
|
||||
resolveDebounceText: ({ event }) =>
|
||||
(JSON.parse(event.message.content) as { text: string }).text,
|
||||
hasProcessedMessage: vi.fn(async () => false),
|
||||
recordProcessedMessage: vi.fn(async () => true),
|
||||
});
|
||||
|
||||
await handler(createTextEvent("msg-text-first", "1710000000000", "first"));
|
||||
await handler(last);
|
||||
|
||||
const call = mockCallArg<{
|
||||
event?: FeishuMessageEvent;
|
||||
messageDedupeKey?: string;
|
||||
}>(handleMessage, 0, 0);
|
||||
expect(call.event?.message.content).toBe(JSON.stringify({ text: "first\nsecond" }));
|
||||
expect(call.messageDedupeKey).toBe(resolveFeishuMessageDedupeKey(last));
|
||||
expect(resolveFeishuMessageDedupeKey(call.event as FeishuMessageEvent)).not.toBe(
|
||||
call.messageDedupeKey,
|
||||
);
|
||||
});
|
||||
|
||||
it("keeps same-id media variants distinct at receive time", async () => {
|
||||
const handleMessage = vi.fn(async () => undefined);
|
||||
const core = {
|
||||
|
||||
@@ -466,6 +466,7 @@ export async function handleFeishuMessage(params: {
|
||||
chatHistories?: Map<string, HistoryEntry[]>;
|
||||
accountId?: string;
|
||||
processingClaimHeld?: boolean;
|
||||
messageDedupeKey?: string;
|
||||
}): Promise<void> {
|
||||
const {
|
||||
cfg,
|
||||
@@ -477,6 +478,7 @@ export async function handleFeishuMessage(params: {
|
||||
chatHistories,
|
||||
accountId,
|
||||
processingClaimHeld = false,
|
||||
messageDedupeKey: messageDedupeKeyOverride,
|
||||
} = params;
|
||||
|
||||
// Resolve account with merged config
|
||||
@@ -487,7 +489,7 @@ export async function handleFeishuMessage(params: {
|
||||
const error = runtime?.error ?? console.error;
|
||||
|
||||
const messageId = event.message.message_id;
|
||||
const messageDedupeKey = resolveFeishuMessageDedupeKey(event);
|
||||
const messageDedupeKey = messageDedupeKeyOverride ?? resolveFeishuMessageDedupeKey(event);
|
||||
if (
|
||||
!(await finalizeFeishuMessageProcessing({
|
||||
messageId: messageDedupeKey,
|
||||
|
||||
@@ -28,6 +28,7 @@ type FeishuMessageReceiveHandlerContext = {
|
||||
chatHistories?: Map<string, HistoryEntry[]>;
|
||||
accountId?: string;
|
||||
processingClaimHeld?: boolean;
|
||||
messageDedupeKey?: string;
|
||||
}) => Promise<void>;
|
||||
resolveDebounceText: (params: {
|
||||
event: FeishuMessageEvent;
|
||||
@@ -184,7 +185,7 @@ export function createFeishuMessageReceiveHandler({
|
||||
},
|
||||
});
|
||||
|
||||
const dispatchFeishuMessage = async (event: FeishuMessageEvent) => {
|
||||
const dispatchFeishuMessage = async (event: FeishuMessageEvent, messageDedupeKey?: string) => {
|
||||
const sequentialKey = resolveSequentialKey({
|
||||
accountId,
|
||||
event,
|
||||
@@ -202,6 +203,7 @@ export function createFeishuMessageReceiveHandler({
|
||||
chatHistories,
|
||||
accountId,
|
||||
processingClaimHeld: true,
|
||||
messageDedupeKey,
|
||||
});
|
||||
await enqueue(sequentialKey, task);
|
||||
};
|
||||
@@ -266,7 +268,7 @@ export function createFeishuMessageReceiveHandler({
|
||||
return;
|
||||
}
|
||||
if (entries.length === 1) {
|
||||
await dispatchFeishuMessage(last);
|
||||
await dispatchFeishuMessage(last, resolveFeishuMessageDedupeKey(last));
|
||||
return;
|
||||
}
|
||||
const dedupedEntries = dedupeFeishuDebounceEntriesByDedupeKey(entries);
|
||||
@@ -280,10 +282,8 @@ export function createFeishuMessageReceiveHandler({
|
||||
if (!dispatchEntry) {
|
||||
return;
|
||||
}
|
||||
await recordSuppressedMessageIds(
|
||||
dedupedEntries,
|
||||
resolveFeishuMessageDedupeKey(dispatchEntry),
|
||||
);
|
||||
const dispatchDedupeKey = resolveFeishuMessageDedupeKey(dispatchEntry);
|
||||
await recordSuppressedMessageIds(dedupedEntries, dispatchDedupeKey);
|
||||
const combinedText = freshEntries
|
||||
.map((entry) => resolveDebounceText(entry))
|
||||
.filter(Boolean)
|
||||
@@ -292,19 +292,22 @@ export function createFeishuMessageReceiveHandler({
|
||||
entries: freshEntries,
|
||||
botOpenId: getBotOpenId(accountId),
|
||||
});
|
||||
await dispatchFeishuMessage({
|
||||
...dispatchEntry,
|
||||
message: {
|
||||
...dispatchEntry.message,
|
||||
...(combinedText.trim()
|
||||
? {
|
||||
message_type: "text",
|
||||
content: JSON.stringify({ text: combinedText }),
|
||||
}
|
||||
: {}),
|
||||
mentions: mergedMentions ?? dispatchEntry.message.mentions,
|
||||
await dispatchFeishuMessage(
|
||||
{
|
||||
...dispatchEntry,
|
||||
message: {
|
||||
...dispatchEntry.message,
|
||||
...(combinedText.trim()
|
||||
? {
|
||||
message_type: "text",
|
||||
content: JSON.stringify({ text: combinedText }),
|
||||
}
|
||||
: {}),
|
||||
mentions: mergedMentions ?? dispatchEntry.message.mentions,
|
||||
},
|
||||
},
|
||||
});
|
||||
dispatchDedupeKey,
|
||||
);
|
||||
},
|
||||
onError: (err, entries) => {
|
||||
for (const entry of entries) {
|
||||
|
||||
Reference in New Issue
Block a user