@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/package.json CHANGED
@@ -1,18 +1,33 @@
1
1
  {
2
2
  "name": "@openclaw/feishu",
3
- "version": "2026.3.12",
3
+ "version": "2026.5.1-beta.1",
4
4
  "description": "OpenClaw Feishu/Lark channel plugin (community maintained by @m1heng)",
5
+ "repository": {
6
+ "type": "git",
7
+ "url": "https://github.com/openclaw/openclaw"
8
+ },
5
9
  "type": "module",
6
10
  "dependencies": {
7
- "@larksuiteoapi/node-sdk": "^1.59.0",
8
- "@sinclair/typebox": "0.34.48",
9
- "https-proxy-agent": "^8.0.0",
10
- "zod": "^4.3.6"
11
+ "@larksuiteoapi/node-sdk": "^1.62.1",
12
+ "typebox": "1.1.37"
13
+ },
14
+ "devDependencies": {
15
+ "@openclaw/plugin-sdk": "workspace:*",
16
+ "openclaw": "workspace:*"
17
+ },
18
+ "peerDependencies": {
19
+ "openclaw": ">=2026.4.25"
20
+ },
21
+ "peerDependenciesMeta": {
22
+ "openclaw": {
23
+ "optional": true
24
+ }
11
25
  },
12
26
  "openclaw": {
13
27
  "extensions": [
14
28
  "./index.ts"
15
29
  ],
30
+ "setupEntry": "./setup-entry.ts",
16
31
  "channel": {
17
32
  "id": "feishu",
18
33
  "label": "Feishu",
@@ -28,8 +43,18 @@
28
43
  },
29
44
  "install": {
30
45
  "npmSpec": "@openclaw/feishu",
31
- "localPath": "extensions/feishu",
32
- "defaultChoice": "npm"
46
+ "defaultChoice": "npm",
47
+ "minHostVersion": ">=2026.4.25"
48
+ },
49
+ "compat": {
50
+ "pluginApi": ">=2026.4.25"
51
+ },
52
+ "build": {
53
+ "openclawVersion": "2026.5.1-beta.1"
54
+ },
55
+ "release": {
56
+ "publishToClawHub": true,
57
+ "publishToNpm": true
33
58
  }
34
59
  }
35
60
  }
package/runtime-api.ts ADDED
@@ -0,0 +1,55 @@
1
+ // Private runtime barrel for the bundled Feishu extension.
2
+ // Keep this barrel thin and generic-only.
3
+
4
+ export type {
5
+ AllowlistMatch,
6
+ AnyAgentTool,
7
+ BaseProbeResult,
8
+ ChannelGroupContext,
9
+ ChannelMessageActionName,
10
+ ChannelMeta,
11
+ ChannelOutboundAdapter,
12
+ ChannelPlugin,
13
+ HistoryEntry,
14
+ OpenClawConfig,
15
+ OpenClawPluginApi,
16
+ OutboundIdentity,
17
+ PluginRuntime,
18
+ ReplyPayload,
19
+ } from "openclaw/plugin-sdk/core";
20
+ export type { OpenClawConfig as ClawdbotConfig } from "openclaw/plugin-sdk/core";
21
+ export type { RuntimeEnv } from "openclaw/plugin-sdk/runtime";
22
+ export type { GroupToolPolicyConfig } from "openclaw/plugin-sdk/config-types";
23
+ export {
24
+ DEFAULT_ACCOUNT_ID,
25
+ buildChannelConfigSchema,
26
+ createActionGate,
27
+ createDedupeCache,
28
+ } from "openclaw/plugin-sdk/core";
29
+ export {
30
+ PAIRING_APPROVED_MESSAGE,
31
+ buildProbeChannelStatusSummary,
32
+ createDefaultChannelRuntimeState,
33
+ } from "openclaw/plugin-sdk/channel-status";
34
+ export { buildAgentMediaPayload } from "openclaw/plugin-sdk/agent-media-payload";
35
+ export { createChannelPairingController } from "openclaw/plugin-sdk/channel-pairing";
36
+ export { createReplyPrefixContext } from "openclaw/plugin-sdk/channel-reply-pipeline";
37
+ export {
38
+ evaluateSupplementalContextVisibility,
39
+ filterSupplementalContextItems,
40
+ resolveChannelContextVisibilityMode,
41
+ } from "openclaw/plugin-sdk/context-visibility-runtime";
42
+ export {
43
+ loadSessionStore,
44
+ resolveSessionStoreEntry,
45
+ } from "openclaw/plugin-sdk/session-store-runtime";
46
+ export { readJsonFileWithFallback } from "openclaw/plugin-sdk/json-store";
47
+ export { createPersistentDedupe } from "openclaw/plugin-sdk/persistent-dedupe";
48
+ export { normalizeAgentId } from "openclaw/plugin-sdk/routing";
49
+ export { chunkTextForOutbound } from "openclaw/plugin-sdk/text-chunking";
50
+ export {
51
+ isRequestBodyLimitError,
52
+ readRequestBodyWithLimit,
53
+ requestBodyErrorToText,
54
+ } from "openclaw/plugin-sdk/webhook-ingress";
55
+ export { setFeishuRuntime } from "./src/runtime.js";
@@ -0,0 +1,5 @@
1
+ export {
2
+ channelSecrets,
3
+ collectRuntimeConfigAssignments,
4
+ secretTargetRegistryEntries,
5
+ } from "./src/secret-contract.js";
@@ -0,0 +1 @@
1
+ export { collectFeishuSecurityAuditFindings } from "./src/security-audit-shared.js";
@@ -0,0 +1 @@
1
+ export { resolveFeishuSessionConversation as resolveSessionConversation } from "./src/session-conversation.js";
package/setup-api.ts ADDED
@@ -0,0 +1,3 @@
1
+ export { feishuPlugin } from "./src/channel.js";
2
+ export { feishuSetupAdapter } from "./src/setup-core.js";
3
+ export { feishuSetupWizard } from "./src/setup-surface.js";
@@ -0,0 +1,14 @@
1
+ import { describe, expect, it, vi } from "vitest";
2
+
3
+ vi.mock("@larksuiteoapi/node-sdk", () => {
4
+ throw new Error("setup entry must not load the Feishu SDK");
5
+ });
6
+
7
+ describe("feishu setup entry", () => {
8
+ it("declares the setup entry without importing Feishu runtime dependencies", async () => {
9
+ const { default: setupEntry } = await import("./setup-entry.js");
10
+
11
+ expect(setupEntry.kind).toBe("bundled-channel-setup-entry");
12
+ expect(typeof setupEntry.loadSetupPlugin).toBe("function");
13
+ });
14
+ });
package/setup-entry.ts ADDED
@@ -0,0 +1,13 @@
1
+ import { defineBundledChannelSetupEntry } from "openclaw/plugin-sdk/channel-entry-contract";
2
+
3
+ export default defineBundledChannelSetupEntry({
4
+ importMetaUrl: import.meta.url,
5
+ plugin: {
6
+ specifier: "./setup-api.js",
7
+ exportName: "feishuPlugin",
8
+ },
9
+ secrets: {
10
+ specifier: "./secret-contract-api.js",
11
+ exportName: "channelSecrets",
12
+ },
13
+ });
@@ -1,13 +1,31 @@
1
1
  import { describe, expect, it } from "vitest";
2
2
  import {
3
+ FeishuSecretRefUnavailableError,
4
+ inspectFeishuCredentials,
3
5
  resolveDefaultFeishuAccountId,
4
6
  resolveDefaultFeishuAccountSelection,
5
7
  resolveFeishuAccount,
6
8
  resolveFeishuCredentials,
9
+ resolveFeishuRuntimeAccount,
7
10
  } from "./accounts.js";
8
11
  import type { FeishuConfig } from "./types.js";
9
12
 
10
- const asConfig = (value: Partial<FeishuConfig>) => value as FeishuConfig;
13
+ function makeDefaultAndRouterAccounts() {
14
+ return {
15
+ default: { appId: "cli_default", appSecret: "secret_default" }, // pragma: allowlist secret
16
+ "router-d": { appId: "cli_router", appSecret: "secret_router" }, // pragma: allowlist secret
17
+ };
18
+ }
19
+
20
+ function expectExplicitDefaultAccountSelection(
21
+ account: ReturnType<typeof resolveFeishuAccount>,
22
+ appId: string,
23
+ ) {
24
+ expect(account.accountId).toBe("router-d");
25
+ expect(account.selectionSource).toBe("explicit-default");
26
+ expect(account.configured).toBe(true);
27
+ expect(account.appId).toBe(appId);
28
+ }
11
29
 
12
30
  function withEnvVar(key: string, value: string | undefined, run: () => void) {
13
31
  const prev = process.env[key];
@@ -27,6 +45,10 @@ function withEnvVar(key: string, value: string | undefined, run: () => void) {
27
45
  }
28
46
  }
29
47
 
48
+ function asConfig(config: Partial<FeishuConfig>): FeishuConfig {
49
+ return config as unknown as FeishuConfig;
50
+ }
51
+
30
52
  function expectUnresolvedEnvSecretRefError(key: string) {
31
53
  expect(() =>
32
54
  resolveFeishuCredentials(
@@ -44,10 +66,7 @@ describe("resolveDefaultFeishuAccountId", () => {
44
66
  channels: {
45
67
  feishu: {
46
68
  defaultAccount: "router-d",
47
- accounts: {
48
- default: { appId: "cli_default", appSecret: "secret_default" }, // pragma: allowlist secret
49
- "router-d": { appId: "cli_router", appSecret: "secret_router" }, // pragma: allowlist secret
50
- },
69
+ accounts: makeDefaultAndRouterAccounts(),
51
70
  },
52
71
  },
53
72
  };
@@ -155,6 +174,18 @@ describe("resolveFeishuCredentials", () => {
155
174
  expect(creds).toBeNull();
156
175
  });
157
176
 
177
+ it("supports explicit inspect mode for unresolved SecretRefs", () => {
178
+ const creds = resolveFeishuCredentials(
179
+ asConfig({
180
+ appId: "cli_123",
181
+ appSecret: { source: "file", provider: "default", id: "path/to/secret" } as never,
182
+ }),
183
+ { mode: "inspect" },
184
+ );
185
+
186
+ expect(creds).toBeNull();
187
+ });
188
+
158
189
  it("throws unresolved SecretRef error when env SecretRef points to missing env var", () => {
159
190
  const key = "FEISHU_APP_SECRET_MISSING_TEST";
160
191
  withEnvVar(key, undefined, () => {
@@ -260,6 +291,24 @@ describe("resolveFeishuCredentials", () => {
260
291
  domain: "feishu",
261
292
  });
262
293
  });
294
+
295
+ it("keeps required credentials when optional event SecretRefs are unresolved in inspect mode", () => {
296
+ const creds = inspectFeishuCredentials(
297
+ asConfig({
298
+ appId: "cli_123",
299
+ appSecret: "secret_456",
300
+ verificationToken: { source: "file", provider: "default", id: "path/to/token" } as never,
301
+ }),
302
+ );
303
+
304
+ expect(creds).toEqual({
305
+ appId: "cli_123",
306
+ appSecret: "secret_456", // pragma: allowlist secret
307
+ encryptKey: undefined,
308
+ verificationToken: undefined,
309
+ domain: "feishu",
310
+ });
311
+ });
263
312
  });
264
313
 
265
314
  describe("resolveFeishuAccount", () => {
@@ -278,10 +327,7 @@ describe("resolveFeishuAccount", () => {
278
327
  };
279
328
 
280
329
  const account = resolveFeishuAccount({ cfg: cfg as never, accountId: undefined });
281
- expect(account.accountId).toBe("router-d");
282
- expect(account.selectionSource).toBe("explicit-default");
283
- expect(account.configured).toBe(true);
284
- expect(account.appId).toBe("top_level_app");
330
+ expectExplicitDefaultAccountSelection(account, "top_level_app");
285
331
  });
286
332
 
287
333
  it("uses configured default account when accountId is omitted", () => {
@@ -298,10 +344,7 @@ describe("resolveFeishuAccount", () => {
298
344
  };
299
345
 
300
346
  const account = resolveFeishuAccount({ cfg: cfg as never, accountId: undefined });
301
- expect(account.accountId).toBe("router-d");
302
- expect(account.selectionSource).toBe("explicit-default");
303
- expect(account.configured).toBe(true);
304
- expect(account.appId).toBe("cli_router");
347
+ expectExplicitDefaultAccountSelection(account, "cli_router");
305
348
  });
306
349
 
307
350
  it("keeps explicit accountId selection", () => {
@@ -309,10 +352,7 @@ describe("resolveFeishuAccount", () => {
309
352
  channels: {
310
353
  feishu: {
311
354
  defaultAccount: "router-d",
312
- accounts: {
313
- default: { appId: "cli_default", appSecret: "secret_default" }, // pragma: allowlist secret
314
- "router-d": { appId: "cli_router", appSecret: "secret_router" }, // pragma: allowlist secret
315
- },
355
+ accounts: makeDefaultAndRouterAccounts(),
316
356
  },
317
357
  },
318
358
  };
@@ -323,9 +363,57 @@ describe("resolveFeishuAccount", () => {
323
363
  expect(account.appId).toBe("cli_default");
324
364
  });
325
365
 
326
- it("surfaces unresolved SecretRef errors in account resolution", () => {
327
- expect(() =>
328
- resolveFeishuAccount({
366
+ it("treats unresolved SecretRef as not configured in account resolution", () => {
367
+ const account = resolveFeishuAccount({
368
+ cfg: {
369
+ channels: {
370
+ feishu: {
371
+ accounts: {
372
+ main: {
373
+ appId: "cli_123",
374
+ appSecret: { source: "file", provider: "default", id: "path/to/secret" },
375
+ } as never,
376
+ },
377
+ },
378
+ },
379
+ } as never,
380
+ accountId: "main",
381
+ });
382
+ expect(account.configured).toBe(false);
383
+ expect(account.appSecret).toBeUndefined();
384
+ });
385
+
386
+ it("keeps account configured when optional event SecretRefs are unresolved in inspect mode", () => {
387
+ const account = resolveFeishuAccount({
388
+ cfg: {
389
+ channels: {
390
+ feishu: {
391
+ accounts: {
392
+ main: {
393
+ appId: "cli_123",
394
+ appSecret: "secret_456",
395
+ verificationToken: {
396
+ source: "file",
397
+ provider: "default",
398
+ id: "path/to/token",
399
+ },
400
+ } as never,
401
+ },
402
+ },
403
+ },
404
+ } as never,
405
+ accountId: "main",
406
+ });
407
+
408
+ expect(account.configured).toBe(true);
409
+ expect(account.appSecret).toBe("secret_456");
410
+ expect(account.verificationToken).toBeUndefined();
411
+ });
412
+
413
+ it("throws typed SecretRef errors in runtime account resolution", () => {
414
+ let caught: unknown;
415
+ try {
416
+ resolveFeishuRuntimeAccount({
329
417
  cfg: {
330
418
  channels: {
331
419
  feishu: {
@@ -339,8 +427,13 @@ describe("resolveFeishuAccount", () => {
339
427
  },
340
428
  } as never,
341
429
  accountId: "main",
342
- }),
343
- ).toThrow(/unresolved SecretRef/i);
430
+ });
431
+ } catch (error) {
432
+ caught = error;
433
+ }
434
+
435
+ expect(caught).toBeInstanceOf(FeishuSecretRefUnavailableError);
436
+ expect((caught as Error).message).toMatch(/channels\.feishu\.appSecret: unresolved SecretRef/i);
344
437
  });
345
438
 
346
439
  it("does not throw when account name is non-string", () => {