From 1234cc4c3187c732c363321b7f1b24b47128eef9 Mon Sep 17 00:00:00 2001 From: Brian Mendonca Date: Mon, 2 Mar 2026 16:41:36 -0700 Subject: [PATCH] Feishu: reply to topic roots (#29968) * Feishu: reply to topic roots * Changelog: note Feishu topic-root reply targeting --------- Co-authored-by: bmendonca3 <208517100+bmendonca3@users.noreply.github.com> Co-authored-by: Tak Hoffman <781889+Takhoffman@users.noreply.github.com> --- CHANGELOG.md | 1 + extensions/feishu/src/bot.test.ts | 38 +++++++++++++++++++++++++++++++ extensions/feishu/src/bot.ts | 4 ++-- 3 files changed, 41 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index d3734343c..fb93b2f6f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -29,6 +29,7 @@ Docs: https://docs.openclaw.ai ### Fixes - Feishu/topic session routing: use `thread_id` as topic session scope fallback when `root_id` is absent, keep first-turn topic keys stable across thread creation, and force thread replies when inbound events already carry topic/thread context. (#29788) Thanks @songyaolun. +- Feishu/topic root replies: prefer `root_id` as outbound `replyTargetMessageId` when present, and parse millisecond `message_create_time` values correctly so topic replies anchor to the root message in grouped thread flows. (#29968) Thanks @bmendonca3. - Feishu/DM pairing reply target: send pairing challenge replies to `chat:` instead of `user:` so Lark/Feishu private chats with user-id-only sender payloads receive pairing messages reliably. (#31403) Thanks @stakeswky. - Feishu/Lark private DM routing: treat inbound `chat_type: "private"` as direct-message context for pairing/mention-forward/reaction synthetic handling so Lark private chats behave like Feishu p2p DMs. (#31400) Thanks @stakeswky. - Feishu/Sender lookup permissions: suppress user-facing grant prompts for stale non-existent scope errors (`contact:contact.base:readonly`) during best-effort sender-name resolution so inbound messages continue without repeated false permission notices. (#31761) diff --git a/extensions/feishu/src/bot.test.ts b/extensions/feishu/src/bot.test.ts index 3be5dbf8c..6a108777d 100644 --- a/extensions/feishu/src/bot.test.ts +++ b/extensions/feishu/src/bot.test.ts @@ -1458,6 +1458,44 @@ describe("handleFeishuMessage command authorization", () => { ); }); + it("replies to the topic root when handling a message inside an existing topic", async () => { + mockShouldComputeCommandAuthorized.mockReturnValue(false); + + const cfg: ClawdbotConfig = { + channels: { + feishu: { + groups: { + "oc-group": { + requireMention: false, + replyInThread: "enabled", + }, + }, + }, + }, + } as ClawdbotConfig; + + const event: FeishuMessageEvent = { + sender: { sender_id: { open_id: "ou-topic-user" } }, + message: { + message_id: "om_child_message", + root_id: "om_root_topic", + chat_id: "oc-group", + chat_type: "group", + message_type: "text", + content: JSON.stringify({ text: "reply inside topic" }), + }, + }; + + await dispatchMessage({ cfg, event }); + + expect(mockCreateFeishuReplyDispatcher).toHaveBeenCalledWith( + expect.objectContaining({ + replyToMessageId: "om_root_topic", + rootId: "om_root_topic", + }), + ); + }); + it("forces thread replies when inbound message contains thread_id", async () => { mockShouldComputeCommandAuthorized.mockReturnValue(false); diff --git a/extensions/feishu/src/bot.ts b/extensions/feishu/src/bot.ts index 3c8da4bc1..924a94213 100644 --- a/extensions/feishu/src/bot.ts +++ b/extensions/feishu/src/bot.ts @@ -1242,13 +1242,13 @@ export async function handleFeishuMessage(params: { const messageCreateTimeMs = event.message.create_time ? parseInt(event.message.create_time, 10) : undefined; - + const replyTargetMessageId = ctx.rootId ?? ctx.messageId; const { dispatcher, replyOptions, markDispatchIdle } = createFeishuReplyDispatcher({ cfg, agentId: route.agentId, runtime: runtime as RuntimeEnv, chatId: ctx.chatId, - replyToMessageId: ctx.messageId, + replyToMessageId: replyTargetMessageId, skipReplyToInMessages: !isGroup, replyInThread, rootId: ctx.rootId,