diff --git a/extensions/feishu/src/chat.ts b/extensions/feishu/src/chat.ts index 7e2f9e87dcc..2706ed9d08b 100644 --- a/extensions/feishu/src/chat.ts +++ b/extensions/feishu/src/chat.ts @@ -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}".`, }); diff --git a/extensions/feishu/src/docx.account-selection.test.ts b/extensions/feishu/src/docx.account-selection.test.ts index 1f11e290815..118b194f8f0 100644 --- a/extensions/feishu/src/docx.account-selection.test.ts +++ b/extensions/feishu/src/docx.account-selection.test.ts @@ -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(); + }); }); diff --git a/extensions/feishu/src/docx.ts b/extensions/feishu/src/docx.ts index 74c8e0d3d8a..aaa0be53952 100644 --- a/extensions/feishu/src/docx.ts +++ b/extensions/feishu/src/docx.ts @@ -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) { diff --git a/extensions/feishu/src/drive.ts b/extensions/feishu/src/drive.ts index 2d9cd7004f6..842015fec8f 100644 --- a/extensions/feishu/src/drive.ts +++ b/extensions/feishu/src/drive.ts @@ -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, diff --git a/extensions/feishu/src/perm.ts b/extensions/feishu/src/perm.ts index 553796009e3..edee640bb9f 100644 --- a/extensions/feishu/src/perm.ts +++ b/extensions/feishu/src/perm.ts @@ -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, diff --git a/extensions/feishu/src/tool-account-routing.test.ts b/extensions/feishu/src/tool-account-routing.test.ts index 1c49393c88b..4b20d422037 100644 --- a/extensions/feishu/src/tool-account-routing.test.ts +++ b/extensions/feishu/src/tool-account-routing.test.ts @@ -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({ diff --git a/extensions/feishu/src/tool-account.ts b/extensions/feishu/src/tool-account.ts index 41b8e158dd1..a6d3b84d15d 100644 --- a/extensions/feishu/src/tool-account.ts +++ b/extensions/feishu/src/tool-account.ts @@ -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; + 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; + executeParams?: AccountAwareParams; + defaultAccountId?: string; + tool: keyof Required; +}): boolean { + const account = resolveFeishuToolAccountConfigState(params); + return resolveToolsConfig(account.config.tools)[params.tool]; +} + export function createFeishuToolClient(params: { api: Pick; executeParams?: AccountAwareParams; diff --git a/extensions/feishu/src/wiki.ts b/extensions/feishu/src/wiki.ts index 09c6ddea498..1ec4187987c 100644 --- a/extensions/feishu/src/wiki.ts +++ b/extensions/feishu/src/wiki.ts @@ -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,