From 77edd2dad15336bad3d61176c4ff013ee9e87c81 Mon Sep 17 00:00:00 2001 From: gaohongxiang <33713367+gaohongxiang@users.noreply.github.com> Date: Wed, 18 Mar 2026 09:38:38 +0000 Subject: [PATCH] fix(feishu): align preflight with supported fields --- extensions/feishu/index.secretref.test.ts | 2 +- extensions/feishu/src/accounts.test.ts | 22 +++++++++- extensions/feishu/src/accounts.ts | 2 +- .../feishu/src/docx.account-selection.test.ts | 41 +++++++++++++++++++ extensions/feishu/src/docx.ts | 11 +++-- 5 files changed, 72 insertions(+), 6 deletions(-) diff --git a/extensions/feishu/index.secretref.test.ts b/extensions/feishu/index.secretref.test.ts index 32c356bad35..36260b20e29 100644 --- a/extensions/feishu/index.secretref.test.ts +++ b/extensions/feishu/index.secretref.test.ts @@ -15,7 +15,7 @@ describe("feishu plugin register SecretRef regression", () => { enabled: true, accounts: { main: { - appId: { source: "file", provider: "default", id: "path/to/app-id" }, + appId: "app-id", appSecret: { source: "file", provider: "default", id: "path/to/app-secret" }, tools: { chat: true, diff --git a/extensions/feishu/src/accounts.test.ts b/extensions/feishu/src/accounts.test.ts index ad8ea3426d1..fcaee6b740c 100644 --- a/extensions/feishu/src/accounts.test.ts +++ b/extensions/feishu/src/accounts.test.ts @@ -377,7 +377,7 @@ describe("listEnabledFeishuAccountConfigs", () => { channels: { feishu: { enabled: true, - appId: { source: "file", provider: "default", id: "path/to/app-id" }, + appId: "app-id", appSecret: { source: "file", provider: "default", id: "path/to/app-secret" }, accounts: { main: { @@ -402,6 +402,26 @@ describe("listEnabledFeishuAccountConfigs", () => { ); }); + it("does not treat SecretRef-style appId objects as configured in config-only preflight", () => { + const accounts = listEnabledFeishuAccountConfigs({ + channels: { + feishu: { + enabled: true, + appId: { source: "file", provider: "default", id: "path/to/app-id" } as never, + appSecret: { source: "file", provider: "default", id: "path/to/app-secret" }, + accounts: { + main: { + enabled: true, + tools: { doc: true }, + }, + }, + }, + }, + } as never); + + expect(accounts).toHaveLength(0); + }); + it("preserves inherited tools flags when account tools only override a subset", () => { const accounts = listEnabledFeishuAccountConfigs({ channels: { diff --git a/extensions/feishu/src/accounts.ts b/extensions/feishu/src/accounts.ts index 72db0a8687f..6886ab39890 100644 --- a/extensions/feishu/src/accounts.ts +++ b/extensions/feishu/src/accounts.ts @@ -143,7 +143,7 @@ export function resolveFeishuAccountConfigState(params: { const accountEnabled = merged.enabled !== false; const enabled = baseEnabled && accountEnabled; const configured = Boolean( - hasConfiguredSecretInput(merged.appId) && hasConfiguredSecretInput(merged.appSecret), + normalizeSecretInputString(merged.appId) && hasConfiguredSecretInput(merged.appSecret), ); const accountName = (merged as FeishuAccountConfig).name; diff --git a/extensions/feishu/src/docx.account-selection.test.ts b/extensions/feishu/src/docx.account-selection.test.ts index 118b194f8f0..27cbd755b3d 100644 --- a/extensions/feishu/src/docx.account-selection.test.ts +++ b/extensions/feishu/src/docx.account-selection.test.ts @@ -5,6 +5,16 @@ import { createToolFactoryHarness } from "./tool-factory-test-harness.js"; const createFeishuClientMock = vi.fn((creds: { appId?: string } | undefined) => ({ __appId: creds?.appId, + application: { + scope: { + list: vi.fn().mockResolvedValue({ + code: 0, + data: { + scopes: [], + }, + }), + }, + }, })); vi.mock("./client.js", () => { @@ -100,4 +110,35 @@ describe("feishu_doc account selection", () => { ); expect(createFeishuClientMock).not.toHaveBeenCalled(); }); + + test("feishu_app_scopes allows explicit accountId override when defaultAccount disables scopes", async () => { + const cfg = { + channels: { + feishu: { + enabled: true, + defaultAccount: "b", + accounts: { + a: { appId: "app-a", appSecret: "sec-a", tools: { scopes: true } }, // pragma: allowlist secret + b: { appId: "app-b", appSecret: "sec-b", tools: { scopes: false } }, // pragma: allowlist secret + }, + }, + }, + } as OpenClawPluginApi["config"]; + + const { api, resolveTool } = createToolFactoryHarness(cfg); + registerFeishuDocTools(api); + + const scopesTool = resolveTool("feishu_app_scopes", { agentAccountId: "a" }); + await scopesTool.execute("call-enabled", { accountId: "a" }); + const blocked = await scopesTool.execute("call-blocked", {}); + + expect(createFeishuClientMock.mock.calls[0]?.[0]?.appId).toBe("app-a"); + expect(blocked).toEqual( + expect.objectContaining({ + details: expect.objectContaining({ + error: 'Feishu scopes are disabled for account "b".', + }), + }), + ); + }); }); diff --git a/extensions/feishu/src/docx.ts b/extensions/feishu/src/docx.ts index aaa0be53952..f1fea67d30c 100644 --- a/extensions/feishu/src/docx.ts +++ b/extensions/feishu/src/docx.ts @@ -1458,16 +1458,21 @@ export function registerFeishuDocTools(api: OpenClawPluginApi) { label: "Feishu App Scopes", description: "List current app permissions (scopes). Use to debug permission issues or check available capabilities.", - parameters: Type.Object({}), - async execute() { + parameters: Type.Object({ + accountId: Type.Optional(Type.String()), + }), + async execute(_toolCallId, params) { + const p = params as { accountId?: string }; try { const account = resolveFeishuToolAccountConfigState({ api, + executeParams: p, defaultAccountId: ctx.agentAccountId, }); if ( !isFeishuToolEnabledForRoutedAccount({ api, + executeParams: p, defaultAccountId: ctx.agentAccountId, tool: "scopes", }) @@ -1476,7 +1481,7 @@ export function registerFeishuDocTools(api: OpenClawPluginApi) { error: `Feishu scopes are disabled for account "${account.accountId}".`, }); } - const result = await listAppScopes(getClient(undefined, ctx.agentAccountId)); + const result = await listAppScopes(getClient(p, ctx.agentAccountId)); return json(result); } catch (err) { return json({ error: err instanceof Error ? err.message : String(err) });