From 4d5f762b7dcb6de8f0d002d29c74bd4b013aa2db Mon Sep 17 00:00:00 2001 From: Ted Li Date: Fri, 27 Mar 2026 02:39:36 -0700 Subject: [PATCH] fix: fall back from synthetic tool accounts (#55627) (thanks @MonkeyLeeT) * feishu: fall back from synthetic tool accounts * feishu: validate implicit tool accounts by config id * fix: fall back from synthetic tool accounts (#55627) (thanks @MonkeyLeeT) --------- Co-authored-by: Ayaan Zaidi --- CHANGELOG.md | 1 + .../feishu/src/tool-account-routing.test.ts | 44 +++++++++++++++++++ extensions/feishu/src/tool-account.ts | 42 +++++++++++++++--- 3 files changed, 82 insertions(+), 5 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 330eb23ade2..7e556264276 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -78,6 +78,7 @@ Docs: https://docs.openclaw.ai - Config/Doctor: rewrite stale bundled plugin load paths from legacy `extensions/*` locations to the packaged bundled path, including directory-name mismatches and slash-suffixed config entries. (#55054) Thanks @SnowSky1. - WhatsApp/mentions: stop treating mentions embedded in quoted messages as direct mentions so replying to a message that @mentioned the bot no longer falsely triggers mention gating. (#52711) Thanks @lurebat. - Agents/ollama fallback: surface non-2xx Ollama HTTP errors with a leading status code so HTTP 503 responses trigger model fallback again. (#55214) Thanks @bugkill3r. +- Feishu/tools: stop synthetic agent ids like `agent-spawner` from being treated as Feishu account ids during tool execution, so tools fall back to the configured/default Feishu account unless the contextual id is a real enabled Feishu account. (#55627) Thanks @MonkeyLeeT. ## 2026.3.24 diff --git a/extensions/feishu/src/tool-account-routing.test.ts b/extensions/feishu/src/tool-account-routing.test.ts index 6cc9172de3e..bba6b2a0b27 100644 --- a/extensions/feishu/src/tool-account-routing.test.ts +++ b/extensions/feishu/src/tool-account-routing.test.ts @@ -126,4 +126,48 @@ describe("feishu tool account routing", () => { expect(createFeishuClientMock.mock.calls[0]?.[0]?.appId).toBe("app-b"); expect(createFeishuClientMock.mock.calls[1]?.[0]?.appId).toBe("app-a"); }); + + test("falls back to the configured Feishu default selection when agentAccountId is not a real account", async () => { + const { api, resolveTool } = createToolFactoryHarness( + createConfig({ + toolsA: { wiki: true }, + toolsB: { wiki: true }, + }), + ); + registerFeishuWikiTools(api); + + const tool = resolveTool("feishu_wiki", { agentAccountId: "agent-spawner" }); + await tool.execute("call", { action: "search" }); + + expect(createFeishuClientMock.mock.calls.at(-1)?.[0]?.appId).toBe("app-a"); + }); + + test("does not silently fall back when the contextual account is real but uses non-env SecretRefs", async () => { + const { api, resolveTool } = createToolFactoryHarness({ + channels: { + feishu: { + enabled: true, + accounts: { + a: { + appId: "app-a", + appSecret: "sec-a", // pragma: allowlist secret + tools: { wiki: true }, + }, + b: { + appId: "app-b", + appSecret: { source: "file", provider: "default", id: "feishu/b-secret" }, + tools: { wiki: true }, + } as never, + }, + }, + }, + } as OpenClawPluginApi["config"]); + registerFeishuWikiTools(api); + + const tool = resolveTool("feishu_wiki", { agentAccountId: "b" }); + const result = await tool.execute("call", { action: "search" }); + + expect(createFeishuClientMock).not.toHaveBeenCalled(); + expect(String(result.details.error ?? "")).toContain("unresolved SecretRef"); + }); }); diff --git a/extensions/feishu/src/tool-account.ts b/extensions/feishu/src/tool-account.ts index d884b938688..305b9fb4f99 100644 --- a/extensions/feishu/src/tool-account.ts +++ b/extensions/feishu/src/tool-account.ts @@ -1,6 +1,10 @@ import type * as Lark from "@larksuiteoapi/node-sdk"; import type { OpenClawPluginApi } from "../runtime-api.js"; -import { resolveFeishuRuntimeAccount } from "./accounts.js"; +import { + listFeishuAccountIds, + resolveFeishuAccount, + resolveFeishuRuntimeAccount, +} from "./accounts.js"; import { createFeishuClient } from "./client.js"; import { resolveToolsConfig } from "./tools-config.js"; import type { FeishuToolsConfig, ResolvedFeishuAccount } from "./types.js"; @@ -21,6 +25,37 @@ function readConfiguredDefaultAccountId(config: OpenClawPluginApi["config"]): st return normalizeOptionalAccountId(value); } +function resolveImplicitToolAccountId(params: { + api: Pick; + executeParams?: AccountAwareParams; + defaultAccountId?: string; +}): string | undefined { + const explicitAccountId = normalizeOptionalAccountId(params.executeParams?.accountId); + if (explicitAccountId) { + return explicitAccountId; + } + + const configuredDefaultAccountId = readConfiguredDefaultAccountId(params.api.config); + if (configuredDefaultAccountId) { + return configuredDefaultAccountId; + } + + const contextualAccountId = normalizeOptionalAccountId(params.defaultAccountId); + if (!contextualAccountId) { + return undefined; + } + + if (!listFeishuAccountIds(params.api.config).includes(contextualAccountId)) { + return undefined; + } + + const contextualAccount = resolveFeishuAccount({ + cfg: params.api.config, + accountId: contextualAccountId, + }); + return contextualAccount.enabled ? contextualAccountId : undefined; +} + export function resolveFeishuToolAccount(params: { api: Pick; executeParams?: AccountAwareParams; @@ -31,10 +66,7 @@ export function resolveFeishuToolAccount(params: { } return resolveFeishuRuntimeAccount({ cfg: params.api.config, - accountId: - normalizeOptionalAccountId(params.executeParams?.accountId) ?? - readConfiguredDefaultAccountId(params.api.config) ?? - normalizeOptionalAccountId(params.defaultAccountId), + accountId: resolveImplicitToolAccountId(params), }); }