diff --git a/CHANGELOG.md b/CHANGELOG.md index fec4691a379..42864fbfc48 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -28,6 +28,7 @@ Docs: https://docs.openclaw.ai ### Fixes +- 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. - Sandbox/workspace mount permissions: make primary `/workspace` bind mounts read-only whenever `workspaceAccess` is not `rw` (including `none`) across both core sandbox container and sandbox browser create flows. (#32227) Thanks @guanyu-zhang. - Signal/message actions: allow `react` to fall back to `toolContext.currentMessageId` when `messageId` is omitted, matching Telegram behavior and unblocking agent-initiated reactions on inbound turns. (#32217) Thanks @dunamismax. diff --git a/extensions/feishu/src/bot.test.ts b/extensions/feishu/src/bot.test.ts index 2e54dfe9898..116af803bd7 100644 --- a/extensions/feishu/src/bot.test.ts +++ b/extensions/feishu/src/bot.test.ts @@ -366,6 +366,41 @@ describe("handleFeishuMessage command authorization", () => { ); }); + it("replies pairing challenge to DM chat_id instead of user:sender id", async () => { + const cfg: ClawdbotConfig = { + channels: { + feishu: { + dmPolicy: "pairing", + }, + }, + } as ClawdbotConfig; + + const event: FeishuMessageEvent = { + sender: { + sender_id: { + user_id: "u_mobile_only", + }, + }, + message: { + message_id: "msg-pairing-chat-reply", + chat_id: "oc_dm_chat_1", + chat_type: "p2p", + message_type: "text", + content: JSON.stringify({ text: "hello" }), + }, + }; + + mockReadAllowFromStore.mockResolvedValue([]); + mockUpsertPairingRequest.mockResolvedValue({ code: "ABCDEFGH", created: true }); + + await dispatchMessage({ cfg, event }); + + expect(mockSendMessageFeishu).toHaveBeenCalledWith( + expect.objectContaining({ + to: "chat:oc_dm_chat_1", + }), + ); + }); it("creates pairing request and drops unauthorized DMs in pairing mode", async () => { mockShouldComputeCommandAuthorized.mockReturnValue(false); mockReadAllowFromStore.mockResolvedValue([]); @@ -410,7 +445,7 @@ describe("handleFeishuMessage command authorization", () => { }); expect(mockSendMessageFeishu).toHaveBeenCalledWith( expect.objectContaining({ - to: "user:ou-unapproved", + to: "chat:oc-dm", accountId: "default", }), ); diff --git a/extensions/feishu/src/bot.ts b/extensions/feishu/src/bot.ts index d50eeb7d80e..c35786d30b3 100644 --- a/extensions/feishu/src/bot.ts +++ b/extensions/feishu/src/bot.ts @@ -907,7 +907,7 @@ export async function handleFeishuMessage(params: { try { await sendMessageFeishu({ cfg, - to: `user:${ctx.senderOpenId}`, + to: `chat:${ctx.chatId}`, text: core.channel.pairing.buildPairingReply({ channel: "feishu", idLine: `Your Feishu user id: ${ctx.senderOpenId}`,