mirror of
https://github.com/openclaw/openclaw.git
synced 2026-08-07 10:34:44 +00:00
fix(feishu): reply inside P2P direct-message threads (#92136)
* fix(feishu): keep P2P replies inside direct-message threads * fix(clownfish): address review for ghcrawl-165996-agentic-merge (1) * fix(clownfish): address review for ghcrawl-165996-agentic-merge (1) Co-authored-by: LiaoyuanNing@TTC <259494737+LiaoyuanNing@users.noreply.github.com> --------- Co-authored-by: vincentkoc <25068+vincentkoc@users.noreply.github.com> Co-authored-by: openclaw-clownfish[bot] <280122609+openclaw-clownfish[bot]@users.noreply.github.com> Co-authored-by: LiaoyuanNing@TTC <259494737+LiaoyuanNing@users.noreply.github.com>
This commit is contained in:
committed by
GitHub
parent
1e878dde7c
commit
c692fabeba
@@ -525,6 +525,40 @@ describe("handleFeishuMessage ACP routing", () => {
|
||||
expect(message.text).toContain("runtime unavailable");
|
||||
});
|
||||
|
||||
it("surfaces configured ACP initialization failures inside P2P direct-message threads", async () => {
|
||||
mockResolveConfiguredBindingRoute.mockReturnValue(createConfiguredFeishuRoute());
|
||||
mockEnsureConfiguredBindingRouteReady.mockResolvedValue(
|
||||
createConfiguredBindingReadiness(false, "runtime unavailable"),
|
||||
);
|
||||
|
||||
await dispatchMessage({
|
||||
cfg: {
|
||||
session: { mainKey: "main", scope: "per-sender" },
|
||||
channels: { feishu: { enabled: true, allowFrom: ["ou_sender_1"], dmPolicy: "open" } },
|
||||
},
|
||||
event: {
|
||||
sender: { sender_id: { open_id: "ou_sender_1" } },
|
||||
message: {
|
||||
message_id: "msg-thread-child",
|
||||
root_id: "msg-thread-root",
|
||||
thread_id: "omt-acp-dm-thread",
|
||||
chat_id: "oc_dm",
|
||||
chat_type: "p2p",
|
||||
message_type: "text",
|
||||
content: JSON.stringify({ text: "hello" }),
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
expect(mockSendMessageFeishu).toHaveBeenCalledWith(
|
||||
expect.objectContaining({
|
||||
to: "chat:oc_dm",
|
||||
replyToMessageId: "msg-thread-root",
|
||||
replyInThread: true,
|
||||
}),
|
||||
);
|
||||
});
|
||||
|
||||
it("routes Feishu topic messages through active bound conversations", async () => {
|
||||
mockResolveBoundConversation.mockReturnValue(createBoundConversation());
|
||||
|
||||
@@ -3359,6 +3393,79 @@ describe("handleFeishuMessage command authorization", () => {
|
||||
expect(dispatcherOptions.rootId).toBe("om_topic_sender_root");
|
||||
});
|
||||
|
||||
it("keeps P2P replies inside a direct-message thread when Feishu supplies thread_id", async () => {
|
||||
mockShouldComputeCommandAuthorized.mockReturnValue(false);
|
||||
|
||||
const cfg: ClawdbotConfig = {
|
||||
channels: {
|
||||
feishu: {
|
||||
dmPolicy: "open",
|
||||
},
|
||||
},
|
||||
} as ClawdbotConfig;
|
||||
|
||||
const event: FeishuMessageEvent = {
|
||||
sender: { sender_id: { open_id: "ou-thread-dm" } },
|
||||
message: {
|
||||
message_id: "om_dm_thread_child",
|
||||
root_id: "om_dm_thread_root",
|
||||
thread_id: "omt_dm_thread",
|
||||
chat_id: "oc-dm-thread",
|
||||
chat_type: "p2p",
|
||||
message_type: "text",
|
||||
content: JSON.stringify({ text: "hello inside a DM thread" }),
|
||||
},
|
||||
};
|
||||
|
||||
await dispatchMessage({ cfg, event });
|
||||
|
||||
expect(mockCreateFeishuReplyDispatcher).toHaveBeenCalledWith(
|
||||
expect.objectContaining({
|
||||
replyToMessageId: "om_dm_thread_root",
|
||||
rootId: "om_dm_thread_root",
|
||||
skipReplyToInMessages: false,
|
||||
replyInThread: true,
|
||||
threadReply: true,
|
||||
}),
|
||||
);
|
||||
});
|
||||
|
||||
it("keeps root_id-only P2P replies as quote replies outside thread mode", async () => {
|
||||
mockShouldComputeCommandAuthorized.mockReturnValue(false);
|
||||
|
||||
const cfg: ClawdbotConfig = {
|
||||
channels: {
|
||||
feishu: {
|
||||
dmPolicy: "open",
|
||||
},
|
||||
},
|
||||
} as ClawdbotConfig;
|
||||
|
||||
const event: FeishuMessageEvent = {
|
||||
sender: { sender_id: { open_id: "ou-quote-dm" } },
|
||||
message: {
|
||||
message_id: "om_dm_quote_reply",
|
||||
root_id: "om_dm_quote_root",
|
||||
chat_id: "oc-dm-quote",
|
||||
chat_type: "p2p",
|
||||
message_type: "text",
|
||||
content: JSON.stringify({ text: "quoted DM reply" }),
|
||||
},
|
||||
};
|
||||
|
||||
await dispatchMessage({ cfg, event });
|
||||
|
||||
expect(mockCreateFeishuReplyDispatcher).toHaveBeenCalledWith(
|
||||
expect.objectContaining({
|
||||
replyToMessageId: "om_dm_quote_reply",
|
||||
rootId: "om_dm_quote_root",
|
||||
skipReplyToInMessages: true,
|
||||
replyInThread: false,
|
||||
threadReply: false,
|
||||
}),
|
||||
);
|
||||
});
|
||||
|
||||
it("forces thread replies when inbound message contains thread_id", async () => {
|
||||
mockShouldComputeCommandAuthorized.mockReturnValue(false);
|
||||
|
||||
|
||||
@@ -802,7 +802,14 @@ export async function handleFeishuMessage(params: {
|
||||
const feishuTo = isGroup ? `chat:${ctx.chatId}` : `user:${ctx.senderOpenId}`;
|
||||
const peerId = isGroup ? (groupSession?.peerId ?? ctx.chatId) : ctx.senderOpenId;
|
||||
const parentPeer = isGroup ? (groupSession?.parentPeer ?? null) : null;
|
||||
const replyInThread = isGroup ? (groupSession?.replyInThread ?? false) : false;
|
||||
const directThreadReply = !isGroup && Boolean(ctx.threadId?.trim());
|
||||
const defaultReplyTargetMessageId =
|
||||
ctx.replyTargetMessageId ?? (ctx.suppressReplyTarget ? undefined : ctx.messageId);
|
||||
const directThreadRootId = directThreadReply ? ctx.rootId?.trim() || undefined : undefined;
|
||||
const directThreadReplyTargetMessageId = directThreadReply
|
||||
? (directThreadRootId ?? defaultReplyTargetMessageId)
|
||||
: undefined;
|
||||
const replyInThread = isGroup ? (groupSession?.replyInThread ?? false) : directThreadReply;
|
||||
const feishuAcpConversationSupported =
|
||||
!isGroup ||
|
||||
groupSession?.groupSessionScope === "group_topic" ||
|
||||
@@ -906,10 +913,13 @@ export async function handleFeishuMessage(params: {
|
||||
bindingResolution: configuredBinding,
|
||||
});
|
||||
if (!ensured.ok) {
|
||||
const replyTargetMessageId =
|
||||
const acpTopicReply =
|
||||
isGroup &&
|
||||
(groupSession?.groupSessionScope === "group_topic" ||
|
||||
groupSession?.groupSessionScope === "group_topic_sender")
|
||||
groupSession?.groupSessionScope === "group_topic_sender");
|
||||
const replyTargetMessageId = directThreadReply
|
||||
? directThreadReplyTargetMessageId
|
||||
: acpTopicReply
|
||||
? (ctx.rootId ?? ctx.messageId)
|
||||
: ctx.messageId;
|
||||
await sendMessageFeishu({
|
||||
@@ -917,7 +927,7 @@ export async function handleFeishuMessage(params: {
|
||||
to: `chat:${ctx.chatId}`,
|
||||
text: `⚠️ Failed to initialize the configured ACP session for this Feishu conversation: ${ensured.error}`,
|
||||
replyToMessageId: replyTargetMessageId,
|
||||
replyInThread: isGroup ? (groupSession?.replyInThread ?? false) : false,
|
||||
replyInThread,
|
||||
accountId: account.accountId,
|
||||
}).catch((err: unknown) => {
|
||||
log(`feishu[${account.accountId}]: failed to send ACP init error reply: ${String(err)}`);
|
||||
@@ -1387,13 +1397,13 @@ export async function handleFeishuMessage(params: {
|
||||
const configReplyInThread =
|
||||
isGroup &&
|
||||
(groupConfig?.replyInThread ?? feishuCfg?.replyInThread ?? "disabled") === "enabled";
|
||||
const replyTargetMessageId =
|
||||
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;
|
||||
const topicReplyTargetMessageId = ctx.rootId ?? defaultReplyTargetMessageId;
|
||||
const replyTargetMessageId = directThreadReply
|
||||
? directThreadReplyTargetMessageId
|
||||
: isTopicSession || configReplyInThread
|
||||
? topicReplyTargetMessageId
|
||||
: defaultReplyTargetMessageId;
|
||||
const threadReply = isGroup ? (groupSession?.threadReply ?? false) : directThreadReply;
|
||||
const lastRouteThreadId =
|
||||
isGroup && (isTopicSession || configReplyInThread || threadReply)
|
||||
? replyTargetMessageId
|
||||
@@ -1518,7 +1528,7 @@ export async function handleFeishuMessage(params: {
|
||||
chatId: ctx.chatId,
|
||||
allowReasoningPreview,
|
||||
replyToMessageId: replyTargetMessageId,
|
||||
skipReplyToInMessages: !isGroup,
|
||||
skipReplyToInMessages: !isGroup && !directThreadReply,
|
||||
replyInThread,
|
||||
rootId: ctx.rootId,
|
||||
threadReply,
|
||||
@@ -1694,7 +1704,7 @@ export async function handleFeishuMessage(params: {
|
||||
chatId: ctx.chatId,
|
||||
allowReasoningPreview,
|
||||
replyToMessageId: replyTargetMessageId,
|
||||
skipReplyToInMessages: !isGroup,
|
||||
skipReplyToInMessages: !isGroup && !directThreadReply,
|
||||
replyInThread,
|
||||
rootId: ctx.rootId,
|
||||
threadReply,
|
||||
|
||||
Reference in New Issue
Block a user