@openclaw/feishu 2026.3.12 → 2026.5.1-beta.1

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.
Files changed (188) hide show
  1. package/api.ts +31 -0
  2. package/channel-entry.ts +20 -0
  3. package/channel-plugin-api.ts +1 -0
  4. package/contract-api.ts +16 -0
  5. package/index.ts +70 -53
  6. package/openclaw.plugin.json +1653 -4
  7. package/package.json +32 -7
  8. package/runtime-api.ts +55 -0
  9. package/secret-contract-api.ts +5 -0
  10. package/security-contract-api.ts +1 -0
  11. package/session-key-api.ts +1 -0
  12. package/setup-api.ts +3 -0
  13. package/setup-entry.test.ts +14 -0
  14. package/setup-entry.ts +13 -0
  15. package/src/accounts.test.ts +115 -22
  16. package/src/accounts.ts +199 -117
  17. package/src/app-registration.ts +331 -0
  18. package/src/approval-auth.test.ts +24 -0
  19. package/src/approval-auth.ts +25 -0
  20. package/src/async.test.ts +35 -0
  21. package/src/async.ts +43 -1
  22. package/src/audio-preflight.runtime.ts +9 -0
  23. package/src/bitable.test.ts +131 -0
  24. package/src/bitable.ts +59 -22
  25. package/src/bot-content.ts +474 -0
  26. package/src/bot-group-name.test.ts +108 -0
  27. package/src/bot-runtime-api.ts +12 -0
  28. package/src/bot-sender-name.ts +125 -0
  29. package/src/bot.broadcast.test.ts +463 -0
  30. package/src/bot.card-action.test.ts +519 -5
  31. package/src/bot.checkBotMentioned.test.ts +92 -20
  32. package/src/bot.helpers.test.ts +118 -0
  33. package/src/bot.stripBotMention.test.ts +13 -21
  34. package/src/bot.test.ts +1334 -401
  35. package/src/bot.ts +798 -786
  36. package/src/card-action.ts +408 -40
  37. package/src/card-interaction.test.ts +129 -0
  38. package/src/card-interaction.ts +159 -0
  39. package/src/card-test-helpers.ts +47 -0
  40. package/src/card-ux-approval.ts +65 -0
  41. package/src/card-ux-launcher.test.ts +99 -0
  42. package/src/card-ux-launcher.ts +121 -0
  43. package/src/card-ux-shared.ts +33 -0
  44. package/src/channel-runtime-api.ts +16 -0
  45. package/src/channel.runtime.ts +47 -0
  46. package/src/channel.test.ts +914 -3
  47. package/src/channel.ts +1252 -309
  48. package/src/chat-schema.ts +5 -4
  49. package/src/chat.test.ts +84 -28
  50. package/src/chat.ts +68 -10
  51. package/src/client.test.ts +212 -103
  52. package/src/client.ts +115 -21
  53. package/src/comment-dispatcher-runtime-api.ts +6 -0
  54. package/src/comment-dispatcher.test.ts +169 -0
  55. package/src/comment-dispatcher.ts +107 -0
  56. package/src/comment-handler-runtime-api.ts +3 -0
  57. package/src/comment-handler.test.ts +486 -0
  58. package/src/comment-handler.ts +309 -0
  59. package/src/comment-reaction.test.ts +166 -0
  60. package/src/comment-reaction.ts +259 -0
  61. package/src/comment-shared.test.ts +182 -0
  62. package/src/comment-shared.ts +365 -0
  63. package/src/comment-target.ts +44 -0
  64. package/src/config-schema.test.ts +77 -25
  65. package/src/config-schema.ts +31 -4
  66. package/src/conversation-id.test.ts +18 -0
  67. package/src/conversation-id.ts +199 -0
  68. package/src/dedup-runtime-api.ts +1 -0
  69. package/src/dedup.ts +76 -35
  70. package/src/directory.static.ts +61 -0
  71. package/src/directory.test.ts +119 -20
  72. package/src/directory.ts +61 -91
  73. package/src/doc-schema.ts +1 -1
  74. package/src/docx-batch-insert.test.ts +39 -38
  75. package/src/docx-batch-insert.ts +55 -19
  76. package/src/docx-color-text.ts +9 -4
  77. package/src/docx-table-ops.test.ts +53 -0
  78. package/src/docx-table-ops.ts +52 -34
  79. package/src/docx-types.ts +38 -0
  80. package/src/docx.account-selection.test.ts +12 -3
  81. package/src/docx.test.ts +314 -74
  82. package/src/docx.ts +278 -122
  83. package/src/drive-schema.ts +47 -1
  84. package/src/drive.test.ts +1219 -0
  85. package/src/drive.ts +614 -13
  86. package/src/dynamic-agent.ts +10 -4
  87. package/src/event-types.ts +45 -0
  88. package/src/external-keys.ts +1 -1
  89. package/src/lifecycle.test-support.ts +220 -0
  90. package/src/media.test.ts +413 -87
  91. package/src/media.ts +488 -154
  92. package/src/mention-target.types.ts +5 -0
  93. package/src/mention.ts +32 -51
  94. package/src/message-action-contract.ts +13 -0
  95. package/src/monitor-state-runtime-api.ts +7 -0
  96. package/src/monitor-transport-runtime-api.ts +7 -0
  97. package/src/monitor.account.ts +220 -313
  98. package/src/monitor.acp-init-failure.lifecycle.test-support.ts +219 -0
  99. package/src/monitor.bot-identity.ts +86 -0
  100. package/src/monitor.bot-menu-handler.ts +165 -0
  101. package/src/monitor.bot-menu.lifecycle.test-support.ts +224 -0
  102. package/src/monitor.bot-menu.test.ts +178 -0
  103. package/src/monitor.broadcast.reply-once.lifecycle.test-support.ts +264 -0
  104. package/src/monitor.card-action.lifecycle.test-support.ts +373 -0
  105. package/src/monitor.cleanup.test.ts +376 -0
  106. package/src/monitor.comment-notice-handler.ts +105 -0
  107. package/src/monitor.comment.test.ts +937 -0
  108. package/src/monitor.comment.ts +1386 -0
  109. package/src/monitor.lifecycle.test.ts +4 -0
  110. package/src/monitor.message-handler.ts +339 -0
  111. package/src/monitor.reaction.lifecycle.test-support.ts +68 -0
  112. package/src/monitor.reaction.test.ts +194 -92
  113. package/src/monitor.reply-once.lifecycle.test-support.ts +190 -0
  114. package/src/monitor.startup.test.ts +24 -36
  115. package/src/monitor.startup.ts +26 -16
  116. package/src/monitor.state.ts +20 -5
  117. package/src/monitor.synthetic-error.ts +18 -0
  118. package/src/monitor.test-mocks.ts +2 -2
  119. package/src/monitor.transport.ts +297 -39
  120. package/src/monitor.ts +15 -10
  121. package/src/monitor.webhook-e2e.test.ts +272 -0
  122. package/src/monitor.webhook-security.test.ts +125 -91
  123. package/src/monitor.webhook.test-helpers.ts +116 -0
  124. package/src/outbound-runtime-api.ts +1 -0
  125. package/src/outbound.test.ts +627 -53
  126. package/src/outbound.ts +623 -81
  127. package/src/perm-schema.ts +1 -1
  128. package/src/perm.ts +1 -7
  129. package/src/pins.ts +108 -0
  130. package/src/policy.test.ts +297 -117
  131. package/src/policy.ts +142 -29
  132. package/src/post.ts +7 -6
  133. package/src/probe.test.ts +122 -118
  134. package/src/probe.ts +26 -16
  135. package/src/processing-claims.ts +59 -0
  136. package/src/qr-terminal.ts +1 -0
  137. package/src/reactions.ts +23 -60
  138. package/src/reasoning-preview.test.ts +59 -0
  139. package/src/reasoning-preview.ts +20 -0
  140. package/src/reply-dispatcher-runtime-api.ts +7 -0
  141. package/src/reply-dispatcher.test.ts +721 -168
  142. package/src/reply-dispatcher.ts +422 -172
  143. package/src/runtime.ts +6 -3
  144. package/src/secret-contract.ts +145 -0
  145. package/src/secret-input.ts +1 -13
  146. package/src/security-audit-shared.ts +69 -0
  147. package/src/security-audit.test.ts +61 -0
  148. package/src/security-audit.ts +1 -0
  149. package/src/send-result.ts +1 -1
  150. package/src/send-target.test.ts +9 -3
  151. package/src/send-target.ts +10 -4
  152. package/src/send.reply-fallback.test.ts +127 -42
  153. package/src/send.test.ts +386 -4
  154. package/src/send.ts +486 -164
  155. package/src/sequential-key.test.ts +72 -0
  156. package/src/sequential-key.ts +28 -0
  157. package/src/sequential-queue.test.ts +92 -0
  158. package/src/sequential-queue.ts +16 -0
  159. package/src/session-conversation.ts +42 -0
  160. package/src/session-route.ts +48 -0
  161. package/src/setup-core.ts +51 -0
  162. package/src/{onboarding.test.ts → setup-surface.test.ts} +52 -21
  163. package/src/setup-surface.ts +581 -0
  164. package/src/streaming-card.test.ts +138 -2
  165. package/src/streaming-card.ts +134 -18
  166. package/src/subagent-hooks.test.ts +603 -0
  167. package/src/subagent-hooks.ts +397 -0
  168. package/src/targets.ts +3 -13
  169. package/src/test-support/lifecycle-test-support.ts +479 -0
  170. package/src/thread-bindings.test.ts +143 -0
  171. package/src/thread-bindings.ts +330 -0
  172. package/src/tool-account-routing.test.ts +66 -8
  173. package/src/tool-account.test.ts +44 -0
  174. package/src/tool-account.ts +40 -17
  175. package/src/tool-factory-test-harness.ts +11 -8
  176. package/src/tool-result.ts +3 -1
  177. package/src/tools-config.ts +1 -1
  178. package/src/types.ts +16 -15
  179. package/src/typing.ts +10 -6
  180. package/src/wiki-schema.ts +1 -1
  181. package/src/wiki.ts +1 -7
  182. package/subagent-hooks-api.ts +31 -0
  183. package/tsconfig.json +16 -0
  184. package/src/feishu-command-handler.ts +0 -59
  185. package/src/onboarding.status.test.ts +0 -25
  186. package/src/onboarding.ts +0 -489
  187. package/src/send-message.ts +0 -71
  188. package/src/targets.test.ts +0 -70
package/src/accounts.ts CHANGED
@@ -1,6 +1,13 @@
1
- import { DEFAULT_ACCOUNT_ID, normalizeAccountId } from "openclaw/plugin-sdk/account-id";
2
- import type { ClawdbotConfig } from "openclaw/plugin-sdk/feishu";
3
- import { normalizeResolvedSecretInputString, normalizeSecretInputString } from "./secret-input.js";
1
+ import {
2
+ DEFAULT_ACCOUNT_ID,
3
+ type OpenClawConfig as ClawdbotConfig,
4
+ createAccountListHelpers,
5
+ normalizeAccountId,
6
+ normalizeOptionalAccountId,
7
+ resolveMergedAccountConfig,
8
+ } from "openclaw/plugin-sdk/account-resolution";
9
+ import { coerceSecretRef } from "openclaw/plugin-sdk/provider-auth";
10
+ import { normalizeString } from "./comment-shared.js";
4
11
  import type {
5
12
  FeishuConfig,
6
13
  FeishuAccountConfig,
@@ -9,28 +16,126 @@ import type {
9
16
  ResolvedFeishuAccount,
10
17
  } from "./types.js";
11
18
 
12
- /**
13
- * List all configured account IDs from the accounts field.
14
- */
15
- function listConfiguredAccountIds(cfg: ClawdbotConfig): string[] {
16
- const accounts = (cfg.channels?.feishu as FeishuConfig)?.accounts;
17
- if (!accounts || typeof accounts !== "object") {
18
- return [];
19
+ const { listAccountIds: listFeishuAccountIds, resolveDefaultAccountId } = createAccountListHelpers(
20
+ "feishu",
21
+ {
22
+ allowUnlistedDefaultAccount: true,
23
+ },
24
+ );
25
+
26
+ export { listFeishuAccountIds };
27
+
28
+ type FeishuCredentialResolutionMode = "inspect" | "strict";
29
+ type FeishuResolvedSecretRef = NonNullable<ReturnType<typeof coerceSecretRef>>;
30
+
31
+ function formatSecretRefLabel(ref: FeishuResolvedSecretRef): string {
32
+ return `${ref.source}:${ref.provider}:${ref.id}`;
33
+ }
34
+
35
+ export class FeishuSecretRefUnavailableError extends Error {
36
+ path: string;
37
+
38
+ constructor(path: string, ref: FeishuResolvedSecretRef) {
39
+ super(
40
+ `${path}: unresolved SecretRef "${formatSecretRefLabel(ref)}". ` +
41
+ "Resolve this command against an active gateway runtime snapshot before reading it.",
42
+ );
43
+ this.name = "FeishuSecretRefUnavailableError";
44
+ this.path = path;
19
45
  }
20
- return Object.keys(accounts).filter(Boolean);
21
46
  }
22
47
 
23
- /**
24
- * List all Feishu account IDs.
25
- * If no accounts are configured, returns [DEFAULT_ACCOUNT_ID] for backward compatibility.
26
- */
27
- export function listFeishuAccountIds(cfg: ClawdbotConfig): string[] {
28
- const ids = listConfiguredAccountIds(cfg);
29
- if (ids.length === 0) {
30
- // Backward compatibility: no accounts configured, use default
31
- return [DEFAULT_ACCOUNT_ID];
48
+ export function isFeishuSecretRefUnavailableError(
49
+ error: unknown,
50
+ ): error is FeishuSecretRefUnavailableError {
51
+ return error instanceof FeishuSecretRefUnavailableError;
52
+ }
53
+
54
+ function resolveFeishuSecretLike(params: {
55
+ value: unknown;
56
+ path: string;
57
+ mode: FeishuCredentialResolutionMode;
58
+ allowEnvSecretRefRead?: boolean;
59
+ }): string | undefined {
60
+ const asString = normalizeString(params.value);
61
+ if (asString) {
62
+ return asString;
63
+ }
64
+
65
+ const ref = coerceSecretRef(params.value);
66
+ if (!ref) {
67
+ return undefined;
68
+ }
69
+
70
+ if (params.mode === "inspect") {
71
+ if (params.allowEnvSecretRefRead && ref.source === "env") {
72
+ const envValue = normalizeString(process.env[ref.id]);
73
+ if (envValue) {
74
+ return envValue;
75
+ }
76
+ }
77
+ return undefined;
78
+ }
79
+
80
+ throw new FeishuSecretRefUnavailableError(params.path, ref);
81
+ }
82
+
83
+ function resolveFeishuBaseCredentials(
84
+ cfg: FeishuConfig | undefined,
85
+ mode: FeishuCredentialResolutionMode,
86
+ ): {
87
+ appId: string;
88
+ appSecret: string;
89
+ domain: FeishuDomain;
90
+ } | null {
91
+ const appId = resolveFeishuSecretLike({
92
+ value: cfg?.appId,
93
+ path: "channels.feishu.appId",
94
+ mode,
95
+ allowEnvSecretRefRead: true,
96
+ });
97
+ const appSecret = resolveFeishuSecretLike({
98
+ value: cfg?.appSecret,
99
+ path: "channels.feishu.appSecret",
100
+ mode,
101
+ allowEnvSecretRefRead: true,
102
+ });
103
+
104
+ if (!appId || !appSecret) {
105
+ return null;
32
106
  }
33
- return [...ids].toSorted((a, b) => a.localeCompare(b));
107
+
108
+ return {
109
+ appId,
110
+ appSecret,
111
+ domain: cfg?.domain ?? "feishu",
112
+ };
113
+ }
114
+
115
+ function resolveFeishuEventSecrets(
116
+ cfg: FeishuConfig | undefined,
117
+ mode: FeishuCredentialResolutionMode,
118
+ ): {
119
+ encryptKey?: string;
120
+ verificationToken?: string;
121
+ } {
122
+ return {
123
+ encryptKey:
124
+ (cfg?.connectionMode ?? "websocket") === "webhook"
125
+ ? resolveFeishuSecretLike({
126
+ value: cfg?.encryptKey,
127
+ path: "channels.feishu.encryptKey",
128
+ mode,
129
+ allowEnvSecretRefRead: true,
130
+ })
131
+ : normalizeString(cfg?.encryptKey),
132
+ verificationToken: resolveFeishuSecretLike({
133
+ value: cfg?.verificationToken,
134
+ path: "channels.feishu.verificationToken",
135
+ mode,
136
+ allowEnvSecretRefRead: true,
137
+ }),
138
+ };
34
139
  }
35
140
 
36
141
  /**
@@ -40,8 +145,9 @@ export function resolveDefaultFeishuAccountSelection(cfg: ClawdbotConfig): {
40
145
  accountId: string;
41
146
  source: FeishuDefaultAccountSelectionSource;
42
147
  } {
43
- const preferredRaw = (cfg.channels?.feishu as FeishuConfig | undefined)?.defaultAccount?.trim();
44
- const preferred = preferredRaw ? normalizeAccountId(preferredRaw) : undefined;
148
+ const preferred = normalizeOptionalAccountId(
149
+ (cfg.channels?.feishu as FeishuConfig | undefined)?.defaultAccount,
150
+ );
45
151
  if (preferred) {
46
152
  return {
47
153
  accountId: preferred,
@@ -65,21 +171,7 @@ export function resolveDefaultFeishuAccountSelection(cfg: ClawdbotConfig): {
65
171
  * Resolve the default account ID.
66
172
  */
67
173
  export function resolveDefaultFeishuAccountId(cfg: ClawdbotConfig): string {
68
- return resolveDefaultFeishuAccountSelection(cfg).accountId;
69
- }
70
-
71
- /**
72
- * Get the raw account-specific config.
73
- */
74
- function resolveAccountConfig(
75
- cfg: ClawdbotConfig,
76
- accountId: string,
77
- ): FeishuAccountConfig | undefined {
78
- const accounts = (cfg.channels?.feishu as FeishuConfig)?.accounts;
79
- if (!accounts || typeof accounts !== "object") {
80
- return undefined;
81
- }
82
- return accounts[accountId];
174
+ return resolveDefaultAccountId(cfg);
83
175
  }
84
176
 
85
177
  /**
@@ -88,15 +180,12 @@ function resolveAccountConfig(
88
180
  */
89
181
  function mergeFeishuAccountConfig(cfg: ClawdbotConfig, accountId: string): FeishuConfig {
90
182
  const feishuCfg = cfg.channels?.feishu as FeishuConfig | undefined;
91
-
92
- // Extract base config (exclude accounts field to avoid recursion)
93
- const { accounts: _ignored, defaultAccount: _ignoredDefaultAccount, ...base } = feishuCfg ?? {};
94
-
95
- // Get account-specific overrides
96
- const account = resolveAccountConfig(cfg, accountId) ?? {};
97
-
98
- // Merge: account config overrides base config
99
- return { ...base, ...account } as FeishuConfig;
183
+ return resolveMergedAccountConfig<FeishuConfig>({
184
+ channelConfig: feishuCfg,
185
+ accounts: feishuCfg?.accounts as Record<string, Partial<FeishuConfig>> | undefined,
186
+ accountId,
187
+ omitKeys: ["defaultAccount"],
188
+ });
100
189
  }
101
190
 
102
191
  /**
@@ -111,7 +200,10 @@ export function resolveFeishuCredentials(cfg?: FeishuConfig): {
111
200
  } | null;
112
201
  export function resolveFeishuCredentials(
113
202
  cfg: FeishuConfig | undefined,
114
- options: { allowUnresolvedSecretRef?: boolean },
203
+ options: {
204
+ mode?: FeishuCredentialResolutionMode;
205
+ allowUnresolvedSecretRef?: boolean;
206
+ },
115
207
  ): {
116
208
  appId: string;
117
209
  appSecret: string;
@@ -121,7 +213,10 @@ export function resolveFeishuCredentials(
121
213
  } | null;
122
214
  export function resolveFeishuCredentials(
123
215
  cfg?: FeishuConfig,
124
- options?: { allowUnresolvedSecretRef?: boolean },
216
+ options?: {
217
+ mode?: FeishuCredentialResolutionMode;
218
+ allowUnresolvedSecretRef?: boolean;
219
+ },
125
220
  ): {
126
221
  appId: string;
127
222
  appSecret: string;
@@ -129,68 +224,28 @@ export function resolveFeishuCredentials(
129
224
  verificationToken?: string;
130
225
  domain: FeishuDomain;
131
226
  } | null {
132
- const normalizeString = (value: unknown): string | undefined => {
133
- if (typeof value !== "string") {
134
- return undefined;
135
- }
136
- const trimmed = value.trim();
137
- return trimmed ? trimmed : undefined;
138
- };
139
-
140
- const resolveSecretLike = (value: unknown, path: string): string | undefined => {
141
- const asString = normalizeString(value);
142
- if (asString) {
143
- return asString;
144
- }
145
-
146
- // In relaxed/onboarding paths only: allow direct env SecretRef reads for UX.
147
- // Default resolution path must preserve unresolved-ref diagnostics/policy semantics.
148
- if (options?.allowUnresolvedSecretRef && typeof value === "object" && value !== null) {
149
- const rec = value as Record<string, unknown>;
150
- const source = normalizeString(rec.source)?.toLowerCase();
151
- const id = normalizeString(rec.id);
152
- if (source === "env" && id) {
153
- const envValue = normalizeString(process.env[id]);
154
- if (envValue) {
155
- return envValue;
156
- }
157
- }
158
- }
159
-
160
- if (options?.allowUnresolvedSecretRef) {
161
- return normalizeSecretInputString(value);
162
- }
163
- return normalizeResolvedSecretInputString({ value, path });
164
- };
165
-
166
- const appId = resolveSecretLike(cfg?.appId, "channels.feishu.appId");
167
- const appSecret = resolveSecretLike(cfg?.appSecret, "channels.feishu.appSecret");
168
-
169
- if (!appId || !appSecret) {
227
+ const mode = options?.mode ?? (options?.allowUnresolvedSecretRef ? "inspect" : "strict");
228
+ const base = resolveFeishuBaseCredentials(cfg, mode);
229
+ if (!base) {
170
230
  return null;
171
231
  }
172
- const connectionMode = cfg?.connectionMode ?? "websocket";
232
+ const eventSecrets = resolveFeishuEventSecrets(cfg, mode);
233
+
173
234
  return {
174
- appId,
175
- appSecret,
176
- encryptKey:
177
- connectionMode === "webhook"
178
- ? resolveSecretLike(cfg?.encryptKey, "channels.feishu.encryptKey")
179
- : normalizeString(cfg?.encryptKey),
180
- verificationToken: resolveSecretLike(
181
- cfg?.verificationToken,
182
- "channels.feishu.verificationToken",
183
- ),
184
- domain: cfg?.domain ?? "feishu",
235
+ ...base,
236
+ ...eventSecrets,
185
237
  };
186
238
  }
187
239
 
188
- /**
189
- * Resolve a complete Feishu account with merged config.
190
- */
191
- export function resolveFeishuAccount(params: {
240
+ export function inspectFeishuCredentials(cfg?: FeishuConfig) {
241
+ return resolveFeishuCredentials(cfg, { mode: "inspect" });
242
+ }
243
+
244
+ function buildResolvedFeishuAccount(params: {
192
245
  cfg: ClawdbotConfig;
193
246
  accountId?: string | null;
247
+ baseMode: FeishuCredentialResolutionMode;
248
+ eventSecretMode: FeishuCredentialResolutionMode;
194
249
  }): ResolvedFeishuAccount {
195
250
  const hasExplicitAccountId =
196
251
  typeof params.accountId === "string" && params.accountId.trim() !== "";
@@ -205,35 +260,62 @@ export function resolveFeishuAccount(params: {
205
260
  : (defaultSelection?.source ?? "fallback");
206
261
  const feishuCfg = params.cfg.channels?.feishu as FeishuConfig | undefined;
207
262
 
208
- // Base enabled state (top-level)
209
263
  const baseEnabled = feishuCfg?.enabled !== false;
210
-
211
- // Merge configs
212
264
  const merged = mergeFeishuAccountConfig(params.cfg, accountId);
213
-
214
- // Account-level enabled state
215
265
  const accountEnabled = merged.enabled !== false;
216
266
  const enabled = baseEnabled && accountEnabled;
217
-
218
- // Resolve credentials from merged config
219
- const creds = resolveFeishuCredentials(merged);
267
+ const baseCreds = resolveFeishuBaseCredentials(merged, params.baseMode);
268
+ const eventSecrets = resolveFeishuEventSecrets(merged, params.eventSecretMode);
220
269
  const accountName = (merged as FeishuAccountConfig).name;
221
270
 
222
271
  return {
223
272
  accountId,
224
273
  selectionSource,
225
274
  enabled,
226
- configured: Boolean(creds),
275
+ configured: Boolean(baseCreds),
227
276
  name: typeof accountName === "string" ? accountName.trim() || undefined : undefined,
228
- appId: creds?.appId,
229
- appSecret: creds?.appSecret,
230
- encryptKey: creds?.encryptKey,
231
- verificationToken: creds?.verificationToken,
232
- domain: creds?.domain ?? "feishu",
277
+ appId: baseCreds?.appId,
278
+ appSecret: baseCreds?.appSecret,
279
+ encryptKey: eventSecrets.encryptKey,
280
+ verificationToken: eventSecrets.verificationToken,
281
+ domain: baseCreds?.domain ?? "feishu",
233
282
  config: merged,
234
283
  };
235
284
  }
236
285
 
286
+ /**
287
+ * Resolve a read-only Feishu account snapshot for CLI/config surfaces.
288
+ * Unresolved SecretRefs are treated as unavailable instead of throwing.
289
+ */
290
+ export function resolveFeishuAccount(params: {
291
+ cfg: ClawdbotConfig;
292
+ accountId?: string | null;
293
+ }): ResolvedFeishuAccount {
294
+ return buildResolvedFeishuAccount({
295
+ ...params,
296
+ baseMode: "inspect",
297
+ eventSecretMode: "inspect",
298
+ });
299
+ }
300
+
301
+ /**
302
+ * Resolve a runtime Feishu account.
303
+ * Required app credentials stay strict; event-only secrets can be required by callers.
304
+ */
305
+ export function resolveFeishuRuntimeAccount(
306
+ params: {
307
+ cfg: ClawdbotConfig;
308
+ accountId?: string | null;
309
+ },
310
+ options?: { requireEventSecrets?: boolean },
311
+ ): ResolvedFeishuAccount {
312
+ return buildResolvedFeishuAccount({
313
+ ...params,
314
+ baseMode: "strict",
315
+ eventSecretMode: options?.requireEventSecrets ? "strict" : "inspect",
316
+ });
317
+ }
318
+
237
319
  /**
238
320
  * List all enabled and configured accounts.
239
321
  */