fix(feishu): guard routed tool disables

This commit is contained in:
gaohongxiang 2026-03-18 08:15:43 +00:00
parent b75a55da5d
commit 5339a790d5
8 changed files with 206 additions and 11 deletions

View File

@ -1,9 +1,13 @@
import type * as Lark from "@larksuiteoapi/node-sdk";
import type { OpenClawPluginApi } from "../runtime-api.js";
import { listEnabledFeishuAccountConfigs, resolveFeishuAccountConfigState } from "./accounts.js";
import { listEnabledFeishuAccountConfigs } from "./accounts.js";
import { FeishuChatSchema, type FeishuChatParams } from "./chat-schema.js";
import { createFeishuToolClient, resolveAnyEnabledFeishuToolsConfig } from "./tool-account.js";
import { resolveToolsConfig } from "./tools-config.js";
import {
createFeishuToolClient,
isFeishuToolEnabledForRoutedAccount,
resolveAnyEnabledFeishuToolsConfig,
resolveFeishuToolAccountConfigState,
} from "./tool-account.js";
function json(data: unknown) {
return {
@ -151,11 +155,19 @@ 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,
const account = resolveFeishuToolAccountConfigState({
api,
executeParams: p,
defaultAccountId,
});
if (!resolveToolsConfig(account.config.tools).chat) {
if (
!isFeishuToolEnabledForRoutedAccount({
api,
executeParams: p,
defaultAccountId,
tool: "chat",
})
) {
return json({
error: `Feishu chat is disabled for account "${account.accountId}".`,
});

View File

@ -67,4 +67,37 @@ describe("feishu_doc account selection", () => {
expect(createFeishuClientMock.mock.calls.at(-1)?.[0]?.appId).toBe("app-a");
});
test("blocks execution when configured defaultAccount disables doc", async () => {
const cfg = {
channels: {
feishu: {
enabled: true,
defaultAccount: "b",
accounts: {
a: { appId: "app-a", appSecret: "sec-a", tools: { doc: true } }, // pragma: allowlist secret
b: { appId: "app-b", appSecret: "sec-b", tools: { doc: false } }, // pragma: allowlist secret
},
},
},
} as OpenClawPluginApi["config"];
const { api, resolveTool } = createToolFactoryHarness(cfg);
registerFeishuDocTools(api);
const docTool = resolveTool("feishu_doc", { agentAccountId: "a" });
const result = await docTool.execute("call-disabled", {
action: "list_blocks",
doc_token: "d",
});
expect(result).toEqual(
expect.objectContaining({
details: expect.objectContaining({
error: 'Feishu doc is disabled for account "b".',
}),
}),
);
expect(createFeishuClientMock).not.toHaveBeenCalled();
});
});

View File

@ -20,7 +20,9 @@ import {
import { getFeishuRuntime } from "./runtime.js";
import {
createFeishuToolClient,
isFeishuToolEnabledForRoutedAccount,
resolveAnyEnabledFeishuToolsConfig,
resolveFeishuToolAccountConfigState,
resolveFeishuToolAccount,
} from "./tool-account.js";
@ -1273,6 +1275,23 @@ export function registerFeishuDocTools(api: OpenClawPluginApi) {
async execute(_toolCallId, params) {
const p = params as FeishuDocExecuteParams;
try {
const account = resolveFeishuToolAccountConfigState({
api,
executeParams: p,
defaultAccountId,
});
if (
!isFeishuToolEnabledForRoutedAccount({
api,
executeParams: p,
defaultAccountId,
tool: "doc",
})
) {
return json({
error: `Feishu doc is disabled for account "${account.accountId}".`,
});
}
const client = getClient(p, defaultAccountId);
switch (p.action) {
case "read":
@ -1442,6 +1461,21 @@ export function registerFeishuDocTools(api: OpenClawPluginApi) {
parameters: Type.Object({}),
async execute() {
try {
const account = resolveFeishuToolAccountConfigState({
api,
defaultAccountId: ctx.agentAccountId,
});
if (
!isFeishuToolEnabledForRoutedAccount({
api,
defaultAccountId: ctx.agentAccountId,
tool: "scopes",
})
) {
return json({
error: `Feishu scopes are disabled for account "${account.accountId}".`,
});
}
const result = await listAppScopes(getClient(undefined, ctx.agentAccountId));
return json(result);
} catch (err) {

View File

@ -2,7 +2,12 @@ import type * as Lark from "@larksuiteoapi/node-sdk";
import type { OpenClawPluginApi } from "../runtime-api.js";
import { listEnabledFeishuAccountConfigs } from "./accounts.js";
import { FeishuDriveSchema, type FeishuDriveParams } from "./drive-schema.js";
import { createFeishuToolClient, resolveAnyEnabledFeishuToolsConfig } from "./tool-account.js";
import {
createFeishuToolClient,
isFeishuToolEnabledForRoutedAccount,
resolveAnyEnabledFeishuToolsConfig,
resolveFeishuToolAccountConfigState,
} from "./tool-account.js";
import {
jsonToolResult,
toolExecutionErrorResult,
@ -195,6 +200,23 @@ export function registerFeishuDriveTools(api: OpenClawPluginApi) {
async execute(_toolCallId, params) {
const p = params as FeishuDriveExecuteParams;
try {
const account = resolveFeishuToolAccountConfigState({
api,
executeParams: p,
defaultAccountId,
});
if (
!isFeishuToolEnabledForRoutedAccount({
api,
executeParams: p,
defaultAccountId,
tool: "drive",
})
) {
return jsonToolResult({
error: `Feishu drive is disabled for account "${account.accountId}".`,
});
}
const client = createFeishuToolClient({
api,
executeParams: p,

View File

@ -2,7 +2,12 @@ import type * as Lark from "@larksuiteoapi/node-sdk";
import type { OpenClawPluginApi } from "../runtime-api.js";
import { listEnabledFeishuAccountConfigs } from "./accounts.js";
import { FeishuPermSchema, type FeishuPermParams } from "./perm-schema.js";
import { createFeishuToolClient, resolveAnyEnabledFeishuToolsConfig } from "./tool-account.js";
import {
createFeishuToolClient,
isFeishuToolEnabledForRoutedAccount,
resolveAnyEnabledFeishuToolsConfig,
resolveFeishuToolAccountConfigState,
} from "./tool-account.js";
import {
jsonToolResult,
toolExecutionErrorResult,
@ -143,6 +148,23 @@ export function registerFeishuPermTools(api: OpenClawPluginApi) {
async execute(_toolCallId, params) {
const p = params as FeishuPermExecuteParams;
try {
const account = resolveFeishuToolAccountConfigState({
api,
executeParams: p,
defaultAccountId,
});
if (
!isFeishuToolEnabledForRoutedAccount({
api,
executeParams: p,
defaultAccountId,
tool: "perm",
})
) {
return jsonToolResult({
error: `Feishu perm is disabled for account "${account.accountId}".`,
});
}
const client = createFeishuToolClient({
api,
executeParams: p,

View File

@ -162,6 +162,29 @@ describe("feishu tool account routing", () => {
expect(createFeishuClientMock).not.toHaveBeenCalled();
});
test("chat tool checks the same routed account precedence as execution", async () => {
const { api, resolveTool } = createToolFactoryHarness(
createConfig({
defaultAccount: "b",
toolsA: { chat: true },
toolsB: { chat: false },
}),
);
registerFeishuChatTools(api);
const tool = resolveTool("feishu_chat", { agentAccountId: "a" });
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({

View File

@ -1,6 +1,6 @@
import type * as Lark from "@larksuiteoapi/node-sdk";
import type { OpenClawPluginApi } from "../runtime-api.js";
import { resolveFeishuAccount } from "./accounts.js";
import { resolveFeishuAccount, resolveFeishuAccountConfigState } from "./accounts.js";
import { createFeishuClient } from "./client.js";
import { resolveToolsConfig } from "./tools-config.js";
import type { FeishuToolsConfig, ResolvedFeishuAccount } from "./types.js";
@ -38,6 +38,33 @@ export function resolveFeishuToolAccount(params: {
});
}
export function resolveFeishuToolAccountConfigState(params: {
api: Pick<OpenClawPluginApi, "config">;
executeParams?: AccountAwareParams;
defaultAccountId?: string;
}) {
if (!params.api.config) {
throw new Error("Feishu config unavailable");
}
return resolveFeishuAccountConfigState({
cfg: params.api.config,
accountId:
normalizeOptionalAccountId(params.executeParams?.accountId) ??
readConfiguredDefaultAccountId(params.api.config) ??
normalizeOptionalAccountId(params.defaultAccountId),
});
}
export function isFeishuToolEnabledForRoutedAccount(params: {
api: Pick<OpenClawPluginApi, "config">;
executeParams?: AccountAwareParams;
defaultAccountId?: string;
tool: keyof Required<FeishuToolsConfig>;
}): boolean {
const account = resolveFeishuToolAccountConfigState(params);
return resolveToolsConfig(account.config.tools)[params.tool];
}
export function createFeishuToolClient(params: {
api: Pick<OpenClawPluginApi, "config">;
executeParams?: AccountAwareParams;

View File

@ -1,7 +1,12 @@
import type * as Lark from "@larksuiteoapi/node-sdk";
import type { OpenClawPluginApi } from "../runtime-api.js";
import { listEnabledFeishuAccountConfigs } from "./accounts.js";
import { createFeishuToolClient, resolveAnyEnabledFeishuToolsConfig } from "./tool-account.js";
import {
createFeishuToolClient,
isFeishuToolEnabledForRoutedAccount,
resolveAnyEnabledFeishuToolsConfig,
resolveFeishuToolAccountConfigState,
} from "./tool-account.js";
import {
jsonToolResult,
toolExecutionErrorResult,
@ -183,6 +188,23 @@ export function registerFeishuWikiTools(api: OpenClawPluginApi) {
async execute(_toolCallId, params) {
const p = params as FeishuWikiExecuteParams;
try {
const account = resolveFeishuToolAccountConfigState({
api,
executeParams: p,
defaultAccountId,
});
if (
!isFeishuToolEnabledForRoutedAccount({
api,
executeParams: p,
defaultAccountId,
tool: "wiki",
})
) {
return jsonToolResult({
error: `Feishu wiki is disabled for account "${account.accountId}".`,
});
}
const client = createFeishuToolClient({
api,
executeParams: p,