fix(feishu): preserve per-account chat disable semantics

This commit is contained in:
gaohongxiang 2026-03-18 07:05:01 +00:00
parent 466b8ad0e6
commit b75a55da5d
4 changed files with 72 additions and 4 deletions

View File

@ -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,
});
});
});

View File

@ -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;
}): {

View File

@ -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,

View File

@ -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({