diff --git a/extensions/feishu/index.ts b/extensions/feishu/index.ts index 6174129852c9..74c7062f4cd1 100644 --- a/extensions/feishu/index.ts +++ b/extensions/feishu/index.ts @@ -6,54 +6,6 @@ import { import type { OpenClawPluginApi } from "openclaw/plugin-sdk/channel-entry-contract"; import { registerFeishuSubagentHooks } from "./subagent-hooks-api.js"; -function registerFeishuDocTools(api: OpenClawPluginApi) { - const register = loadBundledEntryExportSync<(api: OpenClawPluginApi) => void>(import.meta.url, { - specifier: "./api.js", - exportName: "registerFeishuDocTools", - }); - register(api); -} - -function registerFeishuChatTools(api: OpenClawPluginApi) { - const register = loadBundledEntryExportSync<(api: OpenClawPluginApi) => void>(import.meta.url, { - specifier: "./api.js", - exportName: "registerFeishuChatTools", - }); - register(api); -} - -function registerFeishuWikiTools(api: OpenClawPluginApi) { - const register = loadBundledEntryExportSync<(api: OpenClawPluginApi) => void>(import.meta.url, { - specifier: "./api.js", - exportName: "registerFeishuWikiTools", - }); - register(api); -} - -function registerFeishuDriveTools(api: OpenClawPluginApi) { - const register = loadBundledEntryExportSync<(api: OpenClawPluginApi) => void>(import.meta.url, { - specifier: "./api.js", - exportName: "registerFeishuDriveTools", - }); - register(api); -} - -function registerFeishuPermTools(api: OpenClawPluginApi) { - const register = loadBundledEntryExportSync<(api: OpenClawPluginApi) => void>(import.meta.url, { - specifier: "./api.js", - exportName: "registerFeishuPermTools", - }); - register(api); -} - -function registerFeishuBitableTools(api: OpenClawPluginApi) { - const register = loadBundledEntryExportSync<(api: OpenClawPluginApi) => void>(import.meta.url, { - specifier: "./api.js", - exportName: "registerFeishuBitableTools", - }); - register(api); -} - export default defineBundledChannelEntry({ id: "feishu", name: "Feishu", @@ -73,11 +25,19 @@ export default defineBundledChannelEntry({ }, registerFull(api) { registerFeishuSubagentHooks(api); - registerFeishuDocTools(api); - registerFeishuChatTools(api); - registerFeishuWikiTools(api); - registerFeishuDriveTools(api); - registerFeishuPermTools(api); - registerFeishuBitableTools(api); + for (const exportName of [ + "registerFeishuDocTools", + "registerFeishuChatTools", + "registerFeishuWikiTools", + "registerFeishuDriveTools", + "registerFeishuPermTools", + "registerFeishuBitableTools", + ]) { + const register = loadBundledEntryExportSync<(api: OpenClawPluginApi) => void>( + import.meta.url, + { specifier: "./api.js", exportName }, + ); + register(api); + } }, }); diff --git a/extensions/feishu/src/bitable.ts b/extensions/feishu/src/bitable.ts index c2fafaa5ce59..42ce3474593b 100644 --- a/extensions/feishu/src/bitable.ts +++ b/extensions/feishu/src/bitable.ts @@ -5,7 +5,6 @@ import { formatErrorMessage } from "openclaw/plugin-sdk/error-runtime"; import { readPositiveIntegerParam } from "openclaw/plugin-sdk/param-readers"; import { Type, type TSchema } from "typebox"; import type { OpenClawPluginApi } from "../runtime-api.js"; -import { listEnabledFeishuAccounts } from "./accounts.js"; import { createFeishuToolClient, resolveAnyEnabledFeishuToolsConfig } from "./tool-account.js"; import { feishuExternalToolResult as json } from "./tool-result.js"; @@ -586,12 +585,7 @@ export function registerFeishuBitableTools(api: OpenClawPluginApi) { return; } - const accounts = listEnabledFeishuAccounts(api.config); - if (accounts.length === 0) { - return; - } - - const toolsCfg = resolveAnyEnabledFeishuToolsConfig(accounts); + const toolsCfg = resolveAnyEnabledFeishuToolsConfig(api.config); if (!toolsCfg.bitable) { return; } diff --git a/extensions/feishu/src/chat.ts b/extensions/feishu/src/chat.ts index 2c1dcc21c34c..9cf80dc31004 100644 --- a/extensions/feishu/src/chat.ts +++ b/extensions/feishu/src/chat.ts @@ -3,7 +3,6 @@ import type * as Lark from "@larksuiteoapi/node-sdk"; import { readPositiveIntegerParam } from "openclaw/plugin-sdk/param-readers"; import type { OpenClawPluginToolContext } from "openclaw/plugin-sdk/plugin-entry"; import type { OpenClawPluginApi } from "../runtime-api.js"; -import { listEnabledFeishuAccounts } from "./accounts.js"; import { FeishuChatSchema, type FeishuChatParams } from "./chat-schema.js"; import { resolveFeishuChatType } from "./chat-type.js"; import { createFeishuClient } from "./client.js"; @@ -244,12 +243,7 @@ export function registerFeishuChatTools(api: OpenClawPluginApi) { } const cfg = api.config; - const accounts = listEnabledFeishuAccounts(cfg); - if (accounts.length === 0) { - return; - } - - const toolsCfg = resolveAnyEnabledFeishuToolsConfig(accounts); + const toolsCfg = resolveAnyEnabledFeishuToolsConfig(cfg); if (!toolsCfg.chat) { return; } diff --git a/extensions/feishu/src/docx.ts b/extensions/feishu/src/docx.ts index 7c90936d53eb..af550a8c1da6 100644 --- a/extensions/feishu/src/docx.ts +++ b/extensions/feishu/src/docx.ts @@ -5,7 +5,6 @@ import { formatErrorMessage } from "openclaw/plugin-sdk/error-runtime"; import { normalizeOptionalString, uniqueStrings } from "openclaw/plugin-sdk/string-coerce-runtime"; import { Type } from "typebox"; import type { OpenClawPluginApi } from "../runtime-api.js"; -import { listEnabledFeishuAccounts } from "./accounts.js"; import { resolveConfiguredHttpTimeoutMs } from "./client-timeout.js"; import { FeishuDocSchema, type FeishuDocParams } from "./doc-schema.js"; import { BATCH_SIZE, insertBlocksInBatches } from "./docx-batch-insert.js"; @@ -76,28 +75,20 @@ const BLOCK_TYPE_NAMES: Record = { // Block types that cannot be created via documentBlockChildren.create API const UNSUPPORTED_CREATE_TYPES = new Set([31, 32]); -/** Clean blocks for insertion (remove unsupported types and read-only fields) */ +/** Remove block types unsupported by the children insertion API. */ function cleanBlocksForInsert(blocks: FeishuDocxBlock[]): { cleaned: FeishuDocxBlock[]; skipped: string[]; } { const skipped: string[] = []; - const cleaned = blocks - .filter((block) => { - if (UNSUPPORTED_CREATE_TYPES.has(block.block_type)) { - const typeName = BLOCK_TYPE_NAMES[block.block_type] || `type_${block.block_type}`; - skipped.push(typeName); - return false; - } - return true; - }) - .map((block) => { - if (block.block_type === 31 && block.table?.merge_info) { - const { merge_info: _merge_info, ...tableRest } = block.table; - return Object.assign({}, block, { table: tableRest }); - } - return block; - }); + const cleaned = blocks.filter((block) => { + if (UNSUPPORTED_CREATE_TYPES.has(block.block_type)) { + const typeName = BLOCK_TYPE_NAMES[block.block_type] || `type_${block.block_type}`; + skipped.push(typeName); + return false; + } + return true; + }); return { cleaned, skipped }; } @@ -1200,16 +1191,9 @@ export function registerFeishuDocTools(api: OpenClawPluginApi) { return; } - // Check if any account is configured - const accounts = listEnabledFeishuAccounts(api.config); - if (accounts.length === 0) { - return; - } - // Register if enabled on any account; account routing is resolved per execution. - const toolsCfg = resolveAnyEnabledFeishuToolsConfig(accounts); + const toolsCfg = resolveAnyEnabledFeishuToolsConfig(api.config); - const registered: string[] = []; type FeishuDocExecuteParams = FeishuDocParams & { accountId?: string }; const getClient = (params: { accountId?: string } | undefined, defaultAccountId?: string) => @@ -1434,7 +1418,6 @@ export function registerFeishuDocTools(api: OpenClawPluginApi) { }, { name: "feishu_doc" }, ); - registered.push("feishu_doc"); } // Keep feishu_app_scopes as independent tool @@ -1464,7 +1447,6 @@ export function registerFeishuDocTools(api: OpenClawPluginApi) { }), { name: "feishu_app_scopes" }, ); - registered.push("feishu_app_scopes"); } } /* oxlint-disable max-lines -- TODO: split this grandfathered oversized file. */ diff --git a/extensions/feishu/src/drive.ts b/extensions/feishu/src/drive.ts index 6cab3cf67fc0..e139cc954296 100644 --- a/extensions/feishu/src/drive.ts +++ b/extensions/feishu/src/drive.ts @@ -4,7 +4,6 @@ import { formatErrorMessage } from "openclaw/plugin-sdk/error-runtime"; import { readPositiveIntegerParam } from "openclaw/plugin-sdk/param-readers"; import { isRecord, readStringValue as readString } from "openclaw/plugin-sdk/string-coerce-runtime"; import type { OpenClawPluginApi } from "../runtime-api.js"; -import { listEnabledFeishuAccounts } from "./accounts.js"; import { cleanupAmbientCommentTypingReaction } from "./comment-reaction.js"; import { encodeQuery, extractReplyText, formatFeishuApiError } from "./comment-shared.js"; import { parseFeishuCommentTarget, type CommentFileType } from "./comment-target.js"; @@ -798,12 +797,7 @@ export function registerFeishuDriveTools(api: OpenClawPluginApi) { return; } - const accounts = listEnabledFeishuAccounts(api.config); - if (accounts.length === 0) { - return; - } - - const toolsCfg = resolveAnyEnabledFeishuToolsConfig(accounts); + const toolsCfg = resolveAnyEnabledFeishuToolsConfig(api.config); if (!toolsCfg.drive) { return; } diff --git a/extensions/feishu/src/perm.ts b/extensions/feishu/src/perm.ts index 2ee6680d5eb6..06e963694891 100644 --- a/extensions/feishu/src/perm.ts +++ b/extensions/feishu/src/perm.ts @@ -1,7 +1,6 @@ // Feishu plugin module implements perm behavior. import type * as Lark from "@larksuiteoapi/node-sdk"; import type { OpenClawPluginApi } from "../runtime-api.js"; -import { listEnabledFeishuAccounts } from "./accounts.js"; import { FeishuPermSchema, type FeishuPermParams } from "./perm-schema.js"; import { createFeishuToolClient, resolveAnyEnabledFeishuToolsConfig } from "./tool-account.js"; import { @@ -118,12 +117,7 @@ export function registerFeishuPermTools(api: OpenClawPluginApi) { return; } - const accounts = listEnabledFeishuAccounts(api.config); - if (accounts.length === 0) { - return; - } - - const toolsCfg = resolveAnyEnabledFeishuToolsConfig(accounts); + const toolsCfg = resolveAnyEnabledFeishuToolsConfig(api.config); if (!toolsCfg.perm) { return; } diff --git a/extensions/feishu/src/tool-account-routing.test.ts b/extensions/feishu/src/tool-account-routing.test.ts index eb408e70d809..938b482459e7 100644 --- a/extensions/feishu/src/tool-account-routing.test.ts +++ b/extensions/feishu/src/tool-account-routing.test.ts @@ -20,6 +20,8 @@ vi.mock("./client.js", () => ({ })); let registerFeishuBitableTools: typeof import("./bitable.js").registerFeishuBitableTools; +let registerFeishuChatTools: typeof import("./chat.js").registerFeishuChatTools; +let registerFeishuDocTools: typeof import("./docx.js").registerFeishuDocTools; let registerFeishuDriveTools: typeof import("./drive.js").registerFeishuDriveTools; let registerFeishuPermTools: typeof import("./perm.js").registerFeishuPermTools; let registerFeishuWikiTools: typeof import("./wiki.js").registerFeishuWikiTools; @@ -92,6 +94,8 @@ describe("feishu tool account routing", () => { }), )); ({ registerFeishuWikiTools } = await import("./wiki.js")); + ({ registerFeishuChatTools } = await import("./chat.js")); + ({ registerFeishuDocTools } = await import("./docx.js")); }); afterAll(() => { @@ -103,6 +107,41 @@ describe("feishu tool account routing", () => { vi.clearAllMocks(); }); + test.each([ + ["missing channel", {}], + ["unconfigured account", { channels: { feishu: { accounts: { a: { enabled: true } } } } }], + [ + "disabled channel", + { channels: { feishu: { ...createConfig({}).channels?.feishu, enabled: false } } }, + ], + [ + "disabled accounts", + { + channels: { + feishu: { + accounts: { + a: { enabled: false, appId: "app-a", appSecret: "sec-a" }, // pragma: allowlist secret + }, + }, + }, + }, + ], + ])("does not register workplace tools for %s", (_label, config) => { + const { api, registered } = createToolFactoryHarness(config); + for (const register of [ + registerFeishuDocTools, + registerFeishuChatTools, + registerFeishuWikiTools, + registerFeishuDriveTools, + registerFeishuPermTools, + registerFeishuBitableTools, + ]) { + register(api); + } + expect(registered).toEqual([]); + expect(createFeishuClientMock).not.toHaveBeenCalled(); + }); + test("wiki tool registers when first account disables 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 74c64985c891..1caa751bc207 100644 --- a/extensions/feishu/src/tool-account.ts +++ b/extensions/feishu/src/tool-account.ts @@ -4,6 +4,7 @@ import { normalizeOptionalAccountId } from "openclaw/plugin-sdk/account-resoluti import { normalizeOptionalString } from "openclaw/plugin-sdk/string-coerce-runtime"; import type { OpenClawPluginApi } from "../runtime-api.js"; import { + listEnabledFeishuAccounts, listFeishuAccountIds, resolveDefaultFeishuAccountId, resolveFeishuAccount, @@ -128,8 +129,9 @@ export function createFeishuToolClient(params: { } export function resolveAnyEnabledFeishuToolsConfig( - accounts: ResolvedFeishuAccount[], + config: OpenClawPluginApi["config"], ): Required { + const accounts = listEnabledFeishuAccounts(config); const merged: Required = { doc: false, chat: false, diff --git a/extensions/feishu/src/wiki.ts b/extensions/feishu/src/wiki.ts index 7401561dbdb6..76d410b1f19e 100644 --- a/extensions/feishu/src/wiki.ts +++ b/extensions/feishu/src/wiki.ts @@ -2,7 +2,6 @@ import type * as Lark from "@larksuiteoapi/node-sdk"; import { readPositiveIntegerParam } from "openclaw/plugin-sdk/param-readers"; import type { OpenClawPluginApi } from "../runtime-api.js"; -import { listEnabledFeishuAccounts } from "./accounts.js"; import { createFeishuToolClient, resolveAnyEnabledFeishuToolsConfig } from "./tool-account.js"; import { feishuExternalToolResult as jsonResult, @@ -209,12 +208,7 @@ export function registerFeishuWikiTools(api: OpenClawPluginApi) { return; } - const accounts = listEnabledFeishuAccounts(api.config); - if (accounts.length === 0) { - return; - } - - const toolsCfg = resolveAnyEnabledFeishuToolsConfig(accounts); + const toolsCfg = resolveAnyEnabledFeishuToolsConfig(api.config); if (!toolsCfg.wiki) { return; }