@openclaw/feishu 2026.3.1 → 2026.3.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@openclaw/feishu",
3
- "version": "2026.3.1",
3
+ "version": "2026.3.2",
4
4
  "description": "OpenClaw Feishu/Lark channel plugin (community maintained by @m1heng)",
5
5
  "type": "module",
6
6
  "dependencies": {
@@ -1,5 +1,9 @@
1
1
  import { describe, expect, it } from "vitest";
2
- import { resolveDefaultFeishuAccountId, resolveFeishuAccount } from "./accounts.js";
2
+ import {
3
+ resolveDefaultFeishuAccountId,
4
+ resolveDefaultFeishuAccountSelection,
5
+ resolveFeishuAccount,
6
+ } from "./accounts.js";
3
7
 
4
8
  describe("resolveDefaultFeishuAccountId", () => {
5
9
  it("prefers channels.feishu.defaultAccount when configured", () => {
@@ -33,11 +37,26 @@ describe("resolveDefaultFeishuAccountId", () => {
33
37
  expect(resolveDefaultFeishuAccountId(cfg as never)).toBe("router-d");
34
38
  });
35
39
 
36
- it("falls back to literal default account id when preferred is missing", () => {
40
+ it("keeps configured defaultAccount even when not present in accounts map", () => {
41
+ const cfg = {
42
+ channels: {
43
+ feishu: {
44
+ defaultAccount: "router-d",
45
+ accounts: {
46
+ default: { appId: "cli_default", appSecret: "secret_default" },
47
+ zeta: { appId: "cli_zeta", appSecret: "secret_zeta" },
48
+ },
49
+ },
50
+ },
51
+ };
52
+
53
+ expect(resolveDefaultFeishuAccountId(cfg as never)).toBe("router-d");
54
+ });
55
+
56
+ it("falls back to literal default account id when present", () => {
37
57
  const cfg = {
38
58
  channels: {
39
59
  feishu: {
40
- defaultAccount: "missing",
41
60
  accounts: {
42
61
  default: { appId: "cli_default", appSecret: "secret_default" },
43
62
  zeta: { appId: "cli_zeta", appSecret: "secret_zeta" },
@@ -48,9 +67,59 @@ describe("resolveDefaultFeishuAccountId", () => {
48
67
 
49
68
  expect(resolveDefaultFeishuAccountId(cfg as never)).toBe("default");
50
69
  });
70
+
71
+ it("reports selection source for configured defaults and mapped defaults", () => {
72
+ const explicitDefaultCfg = {
73
+ channels: {
74
+ feishu: {
75
+ defaultAccount: "router-d",
76
+ accounts: {},
77
+ },
78
+ },
79
+ };
80
+ expect(resolveDefaultFeishuAccountSelection(explicitDefaultCfg as never)).toEqual({
81
+ accountId: "router-d",
82
+ source: "explicit-default",
83
+ });
84
+
85
+ const mappedDefaultCfg = {
86
+ channels: {
87
+ feishu: {
88
+ accounts: {
89
+ default: { appId: "cli_default", appSecret: "secret_default" },
90
+ },
91
+ },
92
+ },
93
+ };
94
+ expect(resolveDefaultFeishuAccountSelection(mappedDefaultCfg as never)).toEqual({
95
+ accountId: "default",
96
+ source: "mapped-default",
97
+ });
98
+ });
51
99
  });
52
100
 
53
101
  describe("resolveFeishuAccount", () => {
102
+ it("uses top-level credentials with configured default account id even without account map entry", () => {
103
+ const cfg = {
104
+ channels: {
105
+ feishu: {
106
+ defaultAccount: "router-d",
107
+ appId: "top_level_app",
108
+ appSecret: "top_level_secret",
109
+ accounts: {
110
+ default: { appId: "cli_default", appSecret: "secret_default" },
111
+ },
112
+ },
113
+ },
114
+ };
115
+
116
+ const account = resolveFeishuAccount({ cfg: cfg as never, accountId: undefined });
117
+ expect(account.accountId).toBe("router-d");
118
+ expect(account.selectionSource).toBe("explicit-default");
119
+ expect(account.configured).toBe(true);
120
+ expect(account.appId).toBe("top_level_app");
121
+ });
122
+
54
123
  it("uses configured default account when accountId is omitted", () => {
55
124
  const cfg = {
56
125
  channels: {
@@ -66,6 +135,7 @@ describe("resolveFeishuAccount", () => {
66
135
 
67
136
  const account = resolveFeishuAccount({ cfg: cfg as never, accountId: undefined });
68
137
  expect(account.accountId).toBe("router-d");
138
+ expect(account.selectionSource).toBe("explicit-default");
69
139
  expect(account.configured).toBe(true);
70
140
  expect(account.appId).toBe("cli_router");
71
141
  });
@@ -85,6 +155,7 @@ describe("resolveFeishuAccount", () => {
85
155
 
86
156
  const account = resolveFeishuAccount({ cfg: cfg as never, accountId: "default" });
87
157
  expect(account.accountId).toBe("default");
158
+ expect(account.selectionSource).toBe("explicit");
88
159
  expect(account.appId).toBe("cli_default");
89
160
  });
90
161
  });
package/src/accounts.ts CHANGED
@@ -1,8 +1,10 @@
1
1
  import type { ClawdbotConfig } from "openclaw/plugin-sdk";
2
2
  import { DEFAULT_ACCOUNT_ID, normalizeAccountId } from "openclaw/plugin-sdk/account-id";
3
+ import { normalizeResolvedSecretInputString, normalizeSecretInputString } from "./secret-input.js";
3
4
  import type {
4
5
  FeishuConfig,
5
6
  FeishuAccountConfig,
7
+ FeishuDefaultAccountSelectionSource,
6
8
  FeishuDomain,
7
9
  ResolvedFeishuAccount,
8
10
  } from "./types.js";
@@ -32,19 +34,38 @@ export function listFeishuAccountIds(cfg: ClawdbotConfig): string[] {
32
34
  }
33
35
 
34
36
  /**
35
- * Resolve the default account ID.
37
+ * Resolve the default account selection and its source.
36
38
  */
37
- export function resolveDefaultFeishuAccountId(cfg: ClawdbotConfig): string {
39
+ export function resolveDefaultFeishuAccountSelection(cfg: ClawdbotConfig): {
40
+ accountId: string;
41
+ source: FeishuDefaultAccountSelectionSource;
42
+ } {
38
43
  const preferredRaw = (cfg.channels?.feishu as FeishuConfig | undefined)?.defaultAccount?.trim();
39
44
  const preferred = preferredRaw ? normalizeAccountId(preferredRaw) : undefined;
40
- const ids = listFeishuAccountIds(cfg);
41
- if (preferred && ids.includes(preferred)) {
42
- return preferred;
45
+ if (preferred) {
46
+ return {
47
+ accountId: preferred,
48
+ source: "explicit-default",
49
+ };
43
50
  }
51
+ const ids = listFeishuAccountIds(cfg);
44
52
  if (ids.includes(DEFAULT_ACCOUNT_ID)) {
45
- return DEFAULT_ACCOUNT_ID;
53
+ return {
54
+ accountId: DEFAULT_ACCOUNT_ID,
55
+ source: "mapped-default",
56
+ };
46
57
  }
47
- return ids[0] ?? DEFAULT_ACCOUNT_ID;
58
+ return {
59
+ accountId: ids[0] ?? DEFAULT_ACCOUNT_ID,
60
+ source: "fallback",
61
+ };
62
+ }
63
+
64
+ /**
65
+ * Resolve the default account ID.
66
+ */
67
+ export function resolveDefaultFeishuAccountId(cfg: ClawdbotConfig): string {
68
+ return resolveDefaultFeishuAccountSelection(cfg).accountId;
48
69
  }
49
70
 
50
71
  /**
@@ -87,9 +108,34 @@ export function resolveFeishuCredentials(cfg?: FeishuConfig): {
87
108
  encryptKey?: string;
88
109
  verificationToken?: string;
89
110
  domain: FeishuDomain;
111
+ } | null;
112
+ export function resolveFeishuCredentials(
113
+ cfg: FeishuConfig | undefined,
114
+ options: { allowUnresolvedSecretRef?: boolean },
115
+ ): {
116
+ appId: string;
117
+ appSecret: string;
118
+ encryptKey?: string;
119
+ verificationToken?: string;
120
+ domain: FeishuDomain;
121
+ } | null;
122
+ export function resolveFeishuCredentials(
123
+ cfg?: FeishuConfig,
124
+ options?: { allowUnresolvedSecretRef?: boolean },
125
+ ): {
126
+ appId: string;
127
+ appSecret: string;
128
+ encryptKey?: string;
129
+ verificationToken?: string;
130
+ domain: FeishuDomain;
90
131
  } | null {
91
132
  const appId = cfg?.appId?.trim();
92
- const appSecret = cfg?.appSecret?.trim();
133
+ const appSecret = options?.allowUnresolvedSecretRef
134
+ ? normalizeSecretInputString(cfg?.appSecret)
135
+ : normalizeResolvedSecretInputString({
136
+ value: cfg?.appSecret,
137
+ path: "channels.feishu.appSecret",
138
+ });
93
139
  if (!appId || !appSecret) {
94
140
  return null;
95
141
  }
@@ -97,7 +143,13 @@ export function resolveFeishuCredentials(cfg?: FeishuConfig): {
97
143
  appId,
98
144
  appSecret,
99
145
  encryptKey: cfg?.encryptKey?.trim() || undefined,
100
- verificationToken: cfg?.verificationToken?.trim() || undefined,
146
+ verificationToken:
147
+ (options?.allowUnresolvedSecretRef
148
+ ? normalizeSecretInputString(cfg?.verificationToken)
149
+ : normalizeResolvedSecretInputString({
150
+ value: cfg?.verificationToken,
151
+ path: "channels.feishu.verificationToken",
152
+ })) || undefined,
101
153
  domain: cfg?.domain ?? "feishu",
102
154
  };
103
155
  }
@@ -111,9 +163,15 @@ export function resolveFeishuAccount(params: {
111
163
  }): ResolvedFeishuAccount {
112
164
  const hasExplicitAccountId =
113
165
  typeof params.accountId === "string" && params.accountId.trim() !== "";
166
+ const defaultSelection = hasExplicitAccountId
167
+ ? null
168
+ : resolveDefaultFeishuAccountSelection(params.cfg);
114
169
  const accountId = hasExplicitAccountId
115
170
  ? normalizeAccountId(params.accountId)
116
- : resolveDefaultFeishuAccountId(params.cfg);
171
+ : (defaultSelection?.accountId ?? DEFAULT_ACCOUNT_ID);
172
+ const selectionSource = hasExplicitAccountId
173
+ ? "explicit"
174
+ : (defaultSelection?.source ?? "fallback");
117
175
  const feishuCfg = params.cfg.channels?.feishu as FeishuConfig | undefined;
118
176
 
119
177
  // Base enabled state (top-level)
@@ -131,6 +189,7 @@ export function resolveFeishuAccount(params: {
131
189
 
132
190
  return {
133
191
  accountId,
192
+ selectionSource,
134
193
  enabled,
135
194
  configured: Boolean(creds),
136
195
  name: (merged as FeishuAccountConfig).name?.trim() || undefined,
@@ -3,7 +3,7 @@ import { parseFeishuMessageEvent } from "./bot.js";
3
3
 
4
4
  // Helper to build a minimal FeishuMessageEvent for testing
5
5
  function makeEvent(
6
- chatType: "p2p" | "group",
6
+ chatType: "p2p" | "group" | "private",
7
7
  mentions?: Array<{ key: string; name: string; id: { open_id?: string } }>,
8
8
  text = "hello",
9
9
  ) {