From b75a55da5de010c2395dad6ef1db57eb31736ac0 Mon Sep 17 00:00:00 2001 From: gaohongxiang <33713367+gaohongxiang@users.noreply.github.com> Date: Wed, 18 Mar 2026 07:05:01 +0000 Subject: [PATCH] fix(feishu): preserve per-account chat disable semantics --- extensions/feishu/src/accounts.test.ts | 27 +++++++++++++++++++ extensions/feishu/src/accounts.ts | 15 ++++++++--- extensions/feishu/src/chat.ts | 12 ++++++++- .../feishu/src/tool-account-routing.test.ts | 22 +++++++++++++++ 4 files changed, 72 insertions(+), 4 deletions(-) diff --git a/extensions/feishu/src/accounts.test.ts b/extensions/feishu/src/accounts.test.ts index f16f2ad4ccf..ad8ea3426d1 100644 --- a/extensions/feishu/src/accounts.test.ts +++ b/extensions/feishu/src/accounts.test.ts @@ -401,4 +401,31 @@ describe("listEnabledFeishuAccountConfigs", () => { }), ); }); + + it("preserves inherited tools flags when account tools only override a subset", () => { + const accounts = listEnabledFeishuAccountConfigs({ + channels: { + feishu: { + enabled: true, + appId: "app", + appSecret: "secret", // pragma: allowlist secret + tools: { + chat: false, + }, + accounts: { + main: { + enabled: true, + tools: { doc: true }, + }, + }, + }, + }, + } as never); + + expect(accounts).toHaveLength(1); + expect(accounts[0]?.config.tools).toEqual({ + chat: false, + doc: true, + }); + }); }); diff --git a/extensions/feishu/src/accounts.ts b/extensions/feishu/src/accounts.ts index b9d9a0137f6..72db0a8687f 100644 --- a/extensions/feishu/src/accounts.ts +++ b/extensions/feishu/src/accounts.ts @@ -100,11 +100,20 @@ function mergeFeishuAccountConfig(cfg: ClawdbotConfig, accountId: string): Feish // Get account-specific overrides const account = resolveAccountConfig(cfg, accountId) ?? {}; - // Merge: account config overrides base config - return { ...base, ...account } as FeishuConfig; + // Merge account overrides over base config while preserving nested tools defaults. + const merged = { ...base, ...account } as FeishuConfig; + const baseTools = base.tools; + const accountTools = account.tools; + if (baseTools || accountTools) { + merged.tools = { + ...(baseTools ?? {}), + ...(accountTools ?? {}), + }; + } + return merged; } -function resolveFeishuAccountConfigState(params: { +export function resolveFeishuAccountConfigState(params: { cfg: ClawdbotConfig; accountId?: string | null; }): { diff --git a/extensions/feishu/src/chat.ts b/extensions/feishu/src/chat.ts index dc6aeffe9b3..7e2f9e87dcc 100644 --- a/extensions/feishu/src/chat.ts +++ b/extensions/feishu/src/chat.ts @@ -1,8 +1,9 @@ import type * as Lark from "@larksuiteoapi/node-sdk"; import type { OpenClawPluginApi } from "../runtime-api.js"; -import { listEnabledFeishuAccountConfigs } from "./accounts.js"; +import { listEnabledFeishuAccountConfigs, resolveFeishuAccountConfigState } from "./accounts.js"; import { FeishuChatSchema, type FeishuChatParams } from "./chat-schema.js"; import { createFeishuToolClient, resolveAnyEnabledFeishuToolsConfig } from "./tool-account.js"; +import { resolveToolsConfig } from "./tools-config.js"; function json(data: unknown) { return { @@ -150,6 +151,15 @@ export function registerFeishuChatTools(api: OpenClawPluginApi) { async execute(_toolCallId, params) { const p = params as FeishuChatExecuteParams; try { + const account = resolveFeishuAccountConfigState({ + cfg: api.config, + accountId: p.accountId ?? defaultAccountId ?? undefined, + }); + if (!resolveToolsConfig(account.config.tools).chat) { + return json({ + error: `Feishu chat is disabled for account "${account.accountId}".`, + }); + } const client = createFeishuToolClient({ api, executeParams: p, diff --git a/extensions/feishu/src/tool-account-routing.test.ts b/extensions/feishu/src/tool-account-routing.test.ts index 10623ab8f51..1c49393c88b 100644 --- a/extensions/feishu/src/tool-account-routing.test.ts +++ b/extensions/feishu/src/tool-account-routing.test.ts @@ -140,6 +140,28 @@ describe("feishu tool account routing", () => { expect(createFeishuClientMock.mock.calls.at(-1)?.[0]?.appId).toBe("app-b"); }); + test("chat tool blocks execution when the routed account disables chat", async () => { + const { api, resolveTool } = createToolFactoryHarness( + createConfig({ + toolsA: { chat: true }, + toolsB: { chat: false }, + }), + ); + registerFeishuChatTools(api); + + const tool = resolveTool("feishu_chat", { agentAccountId: "b" }); + const result = await tool.execute("call", { action: "info", chat_id: "oc_b" }); + + expect(result).toEqual( + expect.objectContaining({ + details: expect.objectContaining({ + error: 'Feishu chat is disabled for account "b".', + }), + }), + ); + expect(createFeishuClientMock).not.toHaveBeenCalled(); + }); + test("perm tool registers when only second account enables it and routes to agentAccountId", async () => { const { api, resolveTool } = createToolFactoryHarness( createConfig({