diff --git a/CHANGELOG.md b/CHANGELOG.md index 6602d899b57..27cb256570f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -20,6 +20,7 @@ Docs: https://docs.openclaw.ai - Channels/Multi-account config: when adding a non-default channel account to a single-account top-level channel setup, move existing account-scoped top-level single-account values into `channels..accounts.default` before writing the new account so the original account keeps working without duplicated account values at channel root; `openclaw doctor --fix` now repairs previously mixed channel account shapes the same way. (#27334) thanks @gumadeiras. - Feishu/Doc tools: route `feishu_doc` and `feishu_app_scopes` through the active agent account context (with explicit `accountId` override support) so multi-account agents no longer default to the first configured app, with regression coverage for context routing and explicit override behavior. (#27338) thanks @AaronL725. - Feishu/Inbound message metadata: include inbound `message_id` in `BodyForAgent` on a dedicated metadata line so agents can reliably correlate and act on media/message operations that require message IDs, with regression coverage. (#27253) thanks @xss925175263. +- Feishu/Permission error dispatch: merge sender-name permission notices into the main inbound dispatch so one user message produces one agent turn/reply (instead of a duplicate permission-notice turn), with regression coverage. (#27381) thanks @byungsker. - Telegram/Inline buttons: allow callback-query button handling in groups (including `/models` follow-up buttons) when group policy authorizes the sender, by removing the redundant callback allowlist gate that blocked open-policy groups. (#27343) Thanks @GodsBoy. - Telegram/Streaming preview: when finalizing without an existing preview message, prime pending preview text with final answer before stop-flush so users do not briefly see stale 1-2 word fragments (for example `no` before `no problem`). (#27449) Thanks @emanuelst for the original fix direction in #19673. - Telegram/sendChatAction 401 handling: add bounded exponential backoff + temporary local typing suppression after repeated unauthorized failures to stop unbounded `sendChatAction` retry loops that can trigger Telegram abuse enforcement and bot deletion. (#27415) Thanks @widingmarcus-cyber. diff --git a/extensions/feishu/src/bot.test.ts b/extensions/feishu/src/bot.test.ts index 1d3ad44948f..4f3490389f2 100644 --- a/extensions/feishu/src/bot.test.ts +++ b/extensions/feishu/src/bot.test.ts @@ -9,6 +9,7 @@ const { mockSendMessageFeishu, mockGetMessageFeishu, mockDownloadMessageResourceFeishu, + mockCreateFeishuClient, } = vi.hoisted(() => ({ mockCreateFeishuReplyDispatcher: vi.fn(() => ({ dispatcher: vi.fn(), @@ -22,6 +23,7 @@ const { contentType: "video/mp4", fileName: "clip.mp4", }), + mockCreateFeishuClient: vi.fn(), })); vi.mock("./reply-dispatcher.js", () => ({ @@ -37,6 +39,10 @@ vi.mock("./media.js", () => ({ downloadMessageResourceFeishu: mockDownloadMessageResourceFeishu, })); +vi.mock("./client.js", () => ({ + createFeishuClient: mockCreateFeishuClient, +})); + function createRuntimeEnv(): RuntimeEnv { return { log: vi.fn(), @@ -72,6 +78,13 @@ describe("handleFeishuMessage command authorization", () => { beforeEach(() => { vi.clearAllMocks(); + mockCreateFeishuClient.mockReturnValue({ + contact: { + user: { + get: vi.fn().mockResolvedValue({ data: { user: { name: "Sender" } } }), + }, + }, + }); setFeishuRuntime({ system: { enqueueSystemEvent: vi.fn(), @@ -417,4 +430,67 @@ describe("handleFeishuMessage command authorization", () => { }), ); }); + + it("dispatches once and appends permission notice to the main agent body", async () => { + mockShouldComputeCommandAuthorized.mockReturnValue(false); + mockCreateFeishuClient.mockReturnValue({ + contact: { + user: { + get: vi.fn().mockRejectedValue({ + response: { + data: { + code: 99991672, + msg: "permission denied https://open.feishu.cn/app/cli_test", + }, + }, + }), + }, + }, + }); + + const cfg: ClawdbotConfig = { + channels: { + feishu: { + appId: "cli_test", + appSecret: "sec_test", + groups: { + "oc-group": { + requireMention: false, + }, + }, + }, + }, + } as ClawdbotConfig; + + const event: FeishuMessageEvent = { + sender: { + sender_id: { + open_id: "ou-perm", + }, + }, + message: { + message_id: "msg-perm-1", + chat_id: "oc-group", + chat_type: "group", + message_type: "text", + content: JSON.stringify({ text: "hello group" }), + }, + }; + + await dispatchMessage({ cfg, event }); + + expect(mockDispatchReplyFromConfig).toHaveBeenCalledTimes(1); + expect(mockFinalizeInboundContext).toHaveBeenCalledWith( + expect.objectContaining({ + BodyForAgent: expect.stringContaining( + "Permission grant URL: https://open.feishu.cn/app/cli_test", + ), + }), + ); + expect(mockFinalizeInboundContext).toHaveBeenCalledWith( + expect.objectContaining({ + BodyForAgent: expect.stringContaining("ou-perm: hello group"), + }), + ); + }); });