mirror of
https://github.com/openclaw/openclaw.git
synced 2026-05-06 15:50:46 +00:00
fix(feishu): separate synthetic ids from reply targets
This commit is contained in:
@@ -122,6 +122,8 @@ export function parseFeishuMessageEvent(
|
||||
const ctx: FeishuMessageContext = {
|
||||
chatId: event.message.chat_id,
|
||||
messageId: event.message.message_id,
|
||||
replyTargetMessageId: event.message.reply_target_message_id?.trim() || undefined,
|
||||
suppressReplyTarget: event.message.suppress_reply_target === true,
|
||||
senderId: senderUserId || senderOpenId || "",
|
||||
// Keep the historical field name, but fall back to user_id when open_id is unavailable
|
||||
// (common in some mobile app deliveries).
|
||||
@@ -1037,7 +1039,11 @@ export async function handleFeishuMessage(params: {
|
||||
isGroup &&
|
||||
(groupConfig?.replyInThread ?? feishuCfg?.replyInThread ?? "disabled") === "enabled";
|
||||
const replyTargetMessageId =
|
||||
isTopicSession || configReplyInThread ? (ctx.rootId ?? ctx.messageId) : ctx.messageId;
|
||||
isTopicSession || configReplyInThread
|
||||
? (ctx.rootId ??
|
||||
ctx.replyTargetMessageId ??
|
||||
(ctx.suppressReplyTarget ? undefined : ctx.messageId))
|
||||
: (ctx.replyTargetMessageId ?? (ctx.suppressReplyTarget ? undefined : ctx.messageId));
|
||||
const threadReply = isGroup ? (groupSession?.threadReply ?? false) : false;
|
||||
|
||||
if (broadcastAgents) {
|
||||
|
||||
@@ -22,7 +22,9 @@ export type FeishuCardActionEvent = {
|
||||
value: Record<string, unknown>;
|
||||
tag: string;
|
||||
};
|
||||
open_message_id?: string;
|
||||
context: {
|
||||
open_message_id?: string;
|
||||
open_id?: string;
|
||||
user_id?: string;
|
||||
chat_id?: string;
|
||||
@@ -107,6 +109,7 @@ function buildSyntheticMessageEvent(
|
||||
content: string,
|
||||
chatType: "p2p" | "group",
|
||||
): FeishuMessageEvent {
|
||||
const replyTargetMessageId = event.context.open_message_id ?? event.open_message_id;
|
||||
return {
|
||||
sender: {
|
||||
sender_id: {
|
||||
@@ -117,6 +120,8 @@ function buildSyntheticMessageEvent(
|
||||
},
|
||||
message: {
|
||||
message_id: `card-action-${event.token}`,
|
||||
...(replyTargetMessageId ? { reply_target_message_id: replyTargetMessageId } : {}),
|
||||
...(!replyTargetMessageId ? { suppress_reply_target: true } : {}),
|
||||
chat_id: event.context.chat_id || event.operator.open_id,
|
||||
chat_type: chatType,
|
||||
message_type: "text",
|
||||
|
||||
@@ -10,6 +10,8 @@ export type FeishuMessageEvent = {
|
||||
};
|
||||
message: {
|
||||
message_id: string;
|
||||
reply_target_message_id?: string;
|
||||
suppress_reply_target?: boolean;
|
||||
root_id?: string;
|
||||
parent_id?: string;
|
||||
thread_id?: string;
|
||||
|
||||
@@ -210,6 +210,7 @@ function parseFeishuCardActionEventPayload(value: unknown): FeishuCardActionEven
|
||||
const unionId = firstString(operator.union_id);
|
||||
const tag = readString(action.tag);
|
||||
const actionValue = action.value;
|
||||
const openMessageId = firstString(value.open_message_id, context.open_message_id);
|
||||
const contextOpenId = firstString(context.open_id, openId);
|
||||
const contextUserId = firstString(context.user_id, userId);
|
||||
const chatId = firstString(context.chat_id, context.open_chat_id);
|
||||
@@ -227,7 +228,9 @@ function parseFeishuCardActionEventPayload(value: unknown): FeishuCardActionEven
|
||||
value: actionValue,
|
||||
tag,
|
||||
},
|
||||
...(openMessageId ? { open_message_id: openMessageId } : {}),
|
||||
context: {
|
||||
...(openMessageId ? { open_message_id: openMessageId } : {}),
|
||||
...(contextOpenId ? { open_id: contextOpenId } : {}),
|
||||
...(contextUserId ? { user_id: contextUserId } : {}),
|
||||
...(chatId ? { chat_id: chatId } : {}),
|
||||
|
||||
@@ -93,6 +93,7 @@ export function createFeishuBotMenuHandler(params: {
|
||||
},
|
||||
message: {
|
||||
message_id: `bot-menu:${eventKey}:${event.timestamp ?? Date.now()}`,
|
||||
suppress_reply_target: true,
|
||||
chat_id: `p2p:${operatorOpenId}`,
|
||||
chat_type: "p2p",
|
||||
message_type: "text",
|
||||
|
||||
@@ -179,7 +179,7 @@ describe("Feishu bot-menu lifecycle", () => {
|
||||
expect.objectContaining({
|
||||
accountId: "acct-menu",
|
||||
chatId: "p2p:ou_user1",
|
||||
replyToMessageId: "bot-menu:quick-actions:1700000000001",
|
||||
replyToMessageId: undefined,
|
||||
}),
|
||||
);
|
||||
expect(finalizeInboundContextMock).toHaveBeenCalledWith(
|
||||
|
||||
@@ -181,7 +181,7 @@ describe("Feishu card-action lifecycle", () => {
|
||||
expect.objectContaining({
|
||||
accountId: "acct-card",
|
||||
chatId: "p2p:ou_user1",
|
||||
replyToMessageId: "card-action-tok-card-once",
|
||||
replyToMessageId: undefined,
|
||||
}),
|
||||
);
|
||||
expect(finalizeInboundContextMock).toHaveBeenCalledWith(
|
||||
@@ -233,7 +233,12 @@ describe("Feishu card-action lifecycle", () => {
|
||||
expect.objectContaining({
|
||||
accountId: "acct-card",
|
||||
chatId,
|
||||
replyToMessageId: "card-action-tok-card-v2-context",
|
||||
replyToMessageId: "om_card_v2",
|
||||
}),
|
||||
);
|
||||
expect(finalizeInboundContextMock).toHaveBeenCalledWith(
|
||||
expect.objectContaining({
|
||||
MessageSid: "card-action-tok-card-v2-context",
|
||||
}),
|
||||
);
|
||||
});
|
||||
@@ -261,7 +266,42 @@ describe("Feishu card-action lifecycle", () => {
|
||||
expect.objectContaining({
|
||||
accountId: "acct-card",
|
||||
chatId: "ou_user1",
|
||||
replyToMessageId: "card-action-tok-card-sdk-flat",
|
||||
replyToMessageId: "om_sdk_card",
|
||||
}),
|
||||
);
|
||||
expect(finalizeInboundContextMock).toHaveBeenCalledWith(
|
||||
expect.objectContaining({
|
||||
MessageSid: "card-action-tok-card-sdk-flat",
|
||||
}),
|
||||
);
|
||||
});
|
||||
|
||||
it("plain-sends card action replies when Feishu provides no real message id", async () => {
|
||||
const onCardAction = await setupLifecycleMonitor();
|
||||
|
||||
await onCardAction({
|
||||
open_id: "ou_user1",
|
||||
token: "tok-card-no-reply-target",
|
||||
action: {
|
||||
tag: "button",
|
||||
value: {
|
||||
command: "/help",
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
expect(lastRuntime?.error).not.toHaveBeenCalled();
|
||||
expect(dispatchReplyFromConfigMock).toHaveBeenCalledTimes(1);
|
||||
expect(createFeishuReplyDispatcherMock).toHaveBeenCalledWith(
|
||||
expect.objectContaining({
|
||||
accountId: "acct-card",
|
||||
chatId: "ou_user1",
|
||||
replyToMessageId: undefined,
|
||||
}),
|
||||
);
|
||||
expect(finalizeInboundContextMock).toHaveBeenCalledWith(
|
||||
expect.objectContaining({
|
||||
MessageSid: "card-action-tok-card-no-reply-target",
|
||||
}),
|
||||
);
|
||||
});
|
||||
|
||||
@@ -40,6 +40,8 @@ export type FeishuIdType = "open_id" | "user_id" | "union_id" | "chat_id";
|
||||
export type FeishuMessageContext = {
|
||||
chatId: string;
|
||||
messageId: string;
|
||||
replyTargetMessageId?: string;
|
||||
suppressReplyTarget?: boolean;
|
||||
senderId: string;
|
||||
senderOpenId: string;
|
||||
senderName?: string;
|
||||
|
||||
Reference in New Issue
Block a user