Skip to content

Commit 77d04a3

Browse files
committed
fix(feishu): separate synthetic ids from reply targets
1 parent e918e5f commit 77d04a3

9 files changed

Lines changed: 67 additions & 5 deletions

CHANGELOG.md

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -65,6 +65,9 @@ Docs: https://docs.openclaw.ai
6565
- Feishu: accept Schema 2.0 card action callbacks that report
6666
`context.open_chat_id` instead of legacy `context.chat_id`, so button
6767
callbacks no longer drop as malformed. Fixes #71670. Thanks @eddy1068.
68+
- Feishu: keep synthetic card-action and bot-menu ids out of platform reply
69+
targets, using the real card callback message id when Feishu provides one and
70+
plain-sending otherwise. Fixes #71673. Thanks @eddy1068.
6871
- QQ Bot: make `qqbot_remind` schedule, list, and remove Gateway cron jobs
6972
directly for owner-authorized senders instead of returning `cronParams` and
7073
relying on a follow-up generic `cron` tool call. Fixes #70865. (#70937)

extensions/feishu/src/bot.ts

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -122,6 +122,8 @@ export function parseFeishuMessageEvent(
122122
const ctx: FeishuMessageContext = {
123123
chatId: event.message.chat_id,
124124
messageId: event.message.message_id,
125+
replyTargetMessageId: event.message.reply_target_message_id?.trim() || undefined,
126+
suppressReplyTarget: event.message.suppress_reply_target === true,
125127
senderId: senderUserId || senderOpenId || "",
126128
// Keep the historical field name, but fall back to user_id when open_id is unavailable
127129
// (common in some mobile app deliveries).
@@ -1037,7 +1039,11 @@ export async function handleFeishuMessage(params: {
10371039
isGroup &&
10381040
(groupConfig?.replyInThread ?? feishuCfg?.replyInThread ?? "disabled") === "enabled";
10391041
const replyTargetMessageId =
1040-
isTopicSession || configReplyInThread ? (ctx.rootId ?? ctx.messageId) : ctx.messageId;
1042+
isTopicSession || configReplyInThread
1043+
? (ctx.rootId ??
1044+
ctx.replyTargetMessageId ??
1045+
(ctx.suppressReplyTarget ? undefined : ctx.messageId))
1046+
: (ctx.replyTargetMessageId ?? (ctx.suppressReplyTarget ? undefined : ctx.messageId));
10411047
const threadReply = isGroup ? (groupSession?.threadReply ?? false) : false;
10421048

10431049
if (broadcastAgents) {

extensions/feishu/src/card-action.ts

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,9 @@ export type FeishuCardActionEvent = {
2222
value: Record<string, unknown>;
2323
tag: string;
2424
};
25+
open_message_id?: string;
2526
context: {
27+
open_message_id?: string;
2628
open_id?: string;
2729
user_id?: string;
2830
chat_id?: string;
@@ -107,6 +109,7 @@ function buildSyntheticMessageEvent(
107109
content: string,
108110
chatType: "p2p" | "group",
109111
): FeishuMessageEvent {
112+
const replyTargetMessageId = event.context.open_message_id ?? event.open_message_id;
110113
return {
111114
sender: {
112115
sender_id: {
@@ -117,6 +120,8 @@ function buildSyntheticMessageEvent(
117120
},
118121
message: {
119122
message_id: `card-action-${event.token}`,
123+
...(replyTargetMessageId ? { reply_target_message_id: replyTargetMessageId } : {}),
124+
...(!replyTargetMessageId ? { suppress_reply_target: true } : {}),
120125
chat_id: event.context.chat_id || event.operator.open_id,
121126
chat_type: chatType,
122127
message_type: "text",

extensions/feishu/src/event-types.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,8 @@ export type FeishuMessageEvent = {
1010
};
1111
message: {
1212
message_id: string;
13+
reply_target_message_id?: string;
14+
suppress_reply_target?: boolean;
1315
root_id?: string;
1416
parent_id?: string;
1517
thread_id?: string;

extensions/feishu/src/monitor.account.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -210,6 +210,7 @@ function parseFeishuCardActionEventPayload(value: unknown): FeishuCardActionEven
210210
const unionId = firstString(operator.union_id);
211211
const tag = readString(action.tag);
212212
const actionValue = action.value;
213+
const openMessageId = firstString(value.open_message_id, context.open_message_id);
213214
const contextOpenId = firstString(context.open_id, openId);
214215
const contextUserId = firstString(context.user_id, userId);
215216
const chatId = firstString(context.chat_id, context.open_chat_id);
@@ -227,7 +228,9 @@ function parseFeishuCardActionEventPayload(value: unknown): FeishuCardActionEven
227228
value: actionValue,
228229
tag,
229230
},
231+
...(openMessageId ? { open_message_id: openMessageId } : {}),
230232
context: {
233+
...(openMessageId ? { open_message_id: openMessageId } : {}),
231234
...(contextOpenId ? { open_id: contextOpenId } : {}),
232235
...(contextUserId ? { user_id: contextUserId } : {}),
233236
...(chatId ? { chat_id: chatId } : {}),

extensions/feishu/src/monitor.bot-menu-handler.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -93,6 +93,7 @@ export function createFeishuBotMenuHandler(params: {
9393
},
9494
message: {
9595
message_id: `bot-menu:${eventKey}:${event.timestamp ?? Date.now()}`,
96+
suppress_reply_target: true,
9697
chat_id: `p2p:${operatorOpenId}`,
9798
chat_type: "p2p",
9899
message_type: "text",

extensions/feishu/src/monitor.bot-menu.lifecycle.test-support.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -179,7 +179,7 @@ describe("Feishu bot-menu lifecycle", () => {
179179
expect.objectContaining({
180180
accountId: "acct-menu",
181181
chatId: "p2p:ou_user1",
182-
replyToMessageId: "bot-menu:quick-actions:1700000000001",
182+
replyToMessageId: undefined,
183183
}),
184184
);
185185
expect(finalizeInboundContextMock).toHaveBeenCalledWith(

extensions/feishu/src/monitor.card-action.lifecycle.test-support.ts

Lines changed: 43 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -181,7 +181,7 @@ describe("Feishu card-action lifecycle", () => {
181181
expect.objectContaining({
182182
accountId: "acct-card",
183183
chatId: "p2p:ou_user1",
184-
replyToMessageId: "card-action-tok-card-once",
184+
replyToMessageId: undefined,
185185
}),
186186
);
187187
expect(finalizeInboundContextMock).toHaveBeenCalledWith(
@@ -233,7 +233,12 @@ describe("Feishu card-action lifecycle", () => {
233233
expect.objectContaining({
234234
accountId: "acct-card",
235235
chatId,
236-
replyToMessageId: "card-action-tok-card-v2-context",
236+
replyToMessageId: "om_card_v2",
237+
}),
238+
);
239+
expect(finalizeInboundContextMock).toHaveBeenCalledWith(
240+
expect.objectContaining({
241+
MessageSid: "card-action-tok-card-v2-context",
237242
}),
238243
);
239244
});
@@ -261,7 +266,42 @@ describe("Feishu card-action lifecycle", () => {
261266
expect.objectContaining({
262267
accountId: "acct-card",
263268
chatId: "ou_user1",
264-
replyToMessageId: "card-action-tok-card-sdk-flat",
269+
replyToMessageId: "om_sdk_card",
270+
}),
271+
);
272+
expect(finalizeInboundContextMock).toHaveBeenCalledWith(
273+
expect.objectContaining({
274+
MessageSid: "card-action-tok-card-sdk-flat",
275+
}),
276+
);
277+
});
278+
279+
it("plain-sends card action replies when Feishu provides no real message id", async () => {
280+
const onCardAction = await setupLifecycleMonitor();
281+
282+
await onCardAction({
283+
open_id: "ou_user1",
284+
token: "tok-card-no-reply-target",
285+
action: {
286+
tag: "button",
287+
value: {
288+
command: "/help",
289+
},
290+
},
291+
});
292+
293+
expect(lastRuntime?.error).not.toHaveBeenCalled();
294+
expect(dispatchReplyFromConfigMock).toHaveBeenCalledTimes(1);
295+
expect(createFeishuReplyDispatcherMock).toHaveBeenCalledWith(
296+
expect.objectContaining({
297+
accountId: "acct-card",
298+
chatId: "ou_user1",
299+
replyToMessageId: undefined,
300+
}),
301+
);
302+
expect(finalizeInboundContextMock).toHaveBeenCalledWith(
303+
expect.objectContaining({
304+
MessageSid: "card-action-tok-card-no-reply-target",
265305
}),
266306
);
267307
});

extensions/feishu/src/types.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,8 @@ export type FeishuIdType = "open_id" | "user_id" | "union_id" | "chat_id";
4040
export type FeishuMessageContext = {
4141
chatId: string;
4242
messageId: string;
43+
replyTargetMessageId?: string;
44+
suppressReplyTarget?: boolean;
4345
senderId: string;
4446
senderOpenId: string;
4547
senderName?: string;

0 commit comments

Comments
 (0)