From 3e6451f2d8182778754cdf8bec7fc49ebf24991b Mon Sep 17 00:00:00 2001 From: Peter Steinberger Date: Tue, 3 Mar 2026 01:37:00 +0000 Subject: [PATCH] refactor(feishu): expose default-account selection source --- extensions/feishu/src/accounts.test.ts | 38 ++++++++++++++++++++++++- extensions/feishu/src/accounts.ts | 39 ++++++++++++++++++++++---- extensions/feishu/src/types.ts | 7 +++++ 3 files changed, 77 insertions(+), 7 deletions(-) diff --git a/extensions/feishu/src/accounts.test.ts b/extensions/feishu/src/accounts.test.ts index 3b83bb9a931..3fd9f1fba65 100644 --- a/extensions/feishu/src/accounts.test.ts +++ b/extensions/feishu/src/accounts.test.ts @@ -1,5 +1,9 @@ import { describe, expect, it } from "vitest"; -import { resolveDefaultFeishuAccountId, resolveFeishuAccount } from "./accounts.js"; +import { + resolveDefaultFeishuAccountId, + resolveDefaultFeishuAccountSelection, + resolveFeishuAccount, +} from "./accounts.js"; describe("resolveDefaultFeishuAccountId", () => { it("prefers channels.feishu.defaultAccount when configured", () => { @@ -63,6 +67,35 @@ describe("resolveDefaultFeishuAccountId", () => { expect(resolveDefaultFeishuAccountId(cfg as never)).toBe("default"); }); + + it("reports selection source for configured defaults and mapped defaults", () => { + const explicitDefaultCfg = { + channels: { + feishu: { + defaultAccount: "router-d", + accounts: {}, + }, + }, + }; + expect(resolveDefaultFeishuAccountSelection(explicitDefaultCfg as never)).toEqual({ + accountId: "router-d", + source: "explicit-default", + }); + + const mappedDefaultCfg = { + channels: { + feishu: { + accounts: { + default: { appId: "cli_default", appSecret: "secret_default" }, + }, + }, + }, + }; + expect(resolveDefaultFeishuAccountSelection(mappedDefaultCfg as never)).toEqual({ + accountId: "default", + source: "mapped-default", + }); + }); }); describe("resolveFeishuAccount", () => { @@ -82,6 +115,7 @@ describe("resolveFeishuAccount", () => { const account = resolveFeishuAccount({ cfg: cfg as never, accountId: undefined }); expect(account.accountId).toBe("router-d"); + expect(account.selectionSource).toBe("explicit-default"); expect(account.configured).toBe(true); expect(account.appId).toBe("top_level_app"); }); @@ -101,6 +135,7 @@ describe("resolveFeishuAccount", () => { const account = resolveFeishuAccount({ cfg: cfg as never, accountId: undefined }); expect(account.accountId).toBe("router-d"); + expect(account.selectionSource).toBe("explicit-default"); expect(account.configured).toBe(true); expect(account.appId).toBe("cli_router"); }); @@ -120,6 +155,7 @@ describe("resolveFeishuAccount", () => { const account = resolveFeishuAccount({ cfg: cfg as never, accountId: "default" }); expect(account.accountId).toBe("default"); + expect(account.selectionSource).toBe("explicit"); expect(account.appId).toBe("cli_default"); }); }); diff --git a/extensions/feishu/src/accounts.ts b/extensions/feishu/src/accounts.ts index ca7c33a4c07..4116e77e712 100644 --- a/extensions/feishu/src/accounts.ts +++ b/extensions/feishu/src/accounts.ts @@ -3,6 +3,7 @@ import { DEFAULT_ACCOUNT_ID, normalizeAccountId } from "openclaw/plugin-sdk/acco import type { FeishuConfig, FeishuAccountConfig, + FeishuDefaultAccountSelectionSource, FeishuDomain, ResolvedFeishuAccount, } from "./types.js"; @@ -32,19 +33,38 @@ export function listFeishuAccountIds(cfg: ClawdbotConfig): string[] { } /** - * Resolve the default account ID. + * Resolve the default account selection and its source. */ -export function resolveDefaultFeishuAccountId(cfg: ClawdbotConfig): string { +export function resolveDefaultFeishuAccountSelection(cfg: ClawdbotConfig): { + accountId: string; + source: FeishuDefaultAccountSelectionSource; +} { const preferredRaw = (cfg.channels?.feishu as FeishuConfig | undefined)?.defaultAccount?.trim(); const preferred = preferredRaw ? normalizeAccountId(preferredRaw) : undefined; if (preferred) { - return preferred; + return { + accountId: preferred, + source: "explicit-default", + }; } const ids = listFeishuAccountIds(cfg); if (ids.includes(DEFAULT_ACCOUNT_ID)) { - return DEFAULT_ACCOUNT_ID; + return { + accountId: DEFAULT_ACCOUNT_ID, + source: "mapped-default", + }; } - return ids[0] ?? DEFAULT_ACCOUNT_ID; + return { + accountId: ids[0] ?? DEFAULT_ACCOUNT_ID, + source: "fallback", + }; +} + +/** + * Resolve the default account ID. + */ +export function resolveDefaultFeishuAccountId(cfg: ClawdbotConfig): string { + return resolveDefaultFeishuAccountSelection(cfg).accountId; } /** @@ -111,9 +131,15 @@ export function resolveFeishuAccount(params: { }): ResolvedFeishuAccount { const hasExplicitAccountId = typeof params.accountId === "string" && params.accountId.trim() !== ""; + const defaultSelection = hasExplicitAccountId + ? null + : resolveDefaultFeishuAccountSelection(params.cfg); const accountId = hasExplicitAccountId ? normalizeAccountId(params.accountId) - : resolveDefaultFeishuAccountId(params.cfg); + : (defaultSelection?.accountId ?? DEFAULT_ACCOUNT_ID); + const selectionSource = hasExplicitAccountId + ? "explicit" + : (defaultSelection?.source ?? "fallback"); const feishuCfg = params.cfg.channels?.feishu as FeishuConfig | undefined; // Base enabled state (top-level) @@ -131,6 +157,7 @@ export function resolveFeishuAccount(params: { return { accountId, + selectionSource, enabled, configured: Boolean(creds), name: (merged as FeishuAccountConfig).name?.trim() || undefined, diff --git a/extensions/feishu/src/types.ts b/extensions/feishu/src/types.ts index 796fbbbebc6..cfdbd6e8c1d 100644 --- a/extensions/feishu/src/types.ts +++ b/extensions/feishu/src/types.ts @@ -14,8 +14,15 @@ export type FeishuAccountConfig = z.infer; export type FeishuDomain = "feishu" | "lark" | (string & {}); export type FeishuConnectionMode = "websocket" | "webhook"; +export type FeishuDefaultAccountSelectionSource = + | "explicit-default" + | "mapped-default" + | "fallback"; +export type FeishuAccountSelectionSource = "explicit" | FeishuDefaultAccountSelectionSource; + export type ResolvedFeishuAccount = { accountId: string; + selectionSource: FeishuAccountSelectionSource; enabled: boolean; configured: boolean; name?: string;