@openclaw/feishu 2026.5.2-beta.2 → 2026.5.3-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 (224) hide show
  1. package/dist/accounts-Ba3-WP1z.js +423 -0
  2. package/dist/api.js +2280 -0
  3. package/dist/app-registration-B8qc1MCM.js +184 -0
  4. package/dist/audio-preflight.runtime-BPlzkO3l.js +7 -0
  5. package/dist/card-interaction-BfRLgvw_.js +96 -0
  6. package/dist/channel-CSD_Jt8I.js +1668 -0
  7. package/dist/channel-entry.js +22 -0
  8. package/dist/channel-plugin-api.js +2 -0
  9. package/dist/channel.runtime-DYsXcD36.js +700 -0
  10. package/dist/client-DBVoQL5w.js +157 -0
  11. package/dist/contract-api.js +9 -0
  12. package/dist/conversation-id-DWS3Ep2A.js +139 -0
  13. package/dist/directory.static-f3EeoRJd.js +44 -0
  14. package/dist/drive-C5eJLJr7.js +883 -0
  15. package/dist/index.js +68 -0
  16. package/dist/monitor-CT189QfR.js +60 -0
  17. package/dist/monitor.account-dJV2jO8C.js +4990 -0
  18. package/dist/monitor.state-DYM02ipp.js +100 -0
  19. package/dist/policy-D6c-wMPl.js +118 -0
  20. package/dist/probe-BNzzU_uR.js +149 -0
  21. package/dist/rolldown-runtime-DUslC3ob.js +14 -0
  22. package/dist/runtime-CG0DuRCy.js +8 -0
  23. package/dist/runtime-api.js +14 -0
  24. package/dist/secret-contract-Dm4Z_zQN.js +119 -0
  25. package/dist/secret-contract-api.js +2 -0
  26. package/dist/security-audit-DqJdocrN.js +11 -0
  27. package/dist/security-audit-shared-ByuMx9cJ.js +38 -0
  28. package/dist/security-contract-api.js +2 -0
  29. package/dist/send-DowxxbpH.js +1218 -0
  30. package/dist/session-conversation-B4nrW-vo.js +27 -0
  31. package/dist/session-key-api.js +2 -0
  32. package/dist/setup-api.js +2 -0
  33. package/dist/setup-entry.js +15 -0
  34. package/dist/subagent-hooks-C3UhPVLV.js +227 -0
  35. package/dist/subagent-hooks-api.js +23 -0
  36. package/dist/targets-JMFJRKSe.js +48 -0
  37. package/dist/thread-bindings-BmS6TLes.js +222 -0
  38. package/package.json +15 -6
  39. package/api.ts +0 -31
  40. package/channel-entry.ts +0 -20
  41. package/channel-plugin-api.ts +0 -1
  42. package/contract-api.ts +0 -16
  43. package/index.ts +0 -82
  44. package/runtime-api.ts +0 -55
  45. package/secret-contract-api.ts +0 -5
  46. package/security-contract-api.ts +0 -1
  47. package/session-key-api.ts +0 -1
  48. package/setup-api.ts +0 -3
  49. package/setup-entry.test.ts +0 -14
  50. package/setup-entry.ts +0 -13
  51. package/src/accounts.test.ts +0 -459
  52. package/src/accounts.ts +0 -326
  53. package/src/app-registration.ts +0 -331
  54. package/src/approval-auth.test.ts +0 -24
  55. package/src/approval-auth.ts +0 -25
  56. package/src/async.test.ts +0 -35
  57. package/src/async.ts +0 -104
  58. package/src/audio-preflight.runtime.ts +0 -9
  59. package/src/bitable.test.ts +0 -131
  60. package/src/bitable.ts +0 -762
  61. package/src/bot-content.ts +0 -474
  62. package/src/bot-group-name.test.ts +0 -108
  63. package/src/bot-runtime-api.ts +0 -12
  64. package/src/bot-sender-name.ts +0 -125
  65. package/src/bot.broadcast.test.ts +0 -463
  66. package/src/bot.card-action.test.ts +0 -577
  67. package/src/bot.checkBotMentioned.test.ts +0 -265
  68. package/src/bot.helpers.test.ts +0 -118
  69. package/src/bot.stripBotMention.test.ts +0 -126
  70. package/src/bot.test.ts +0 -3040
  71. package/src/bot.ts +0 -1559
  72. package/src/card-action.ts +0 -447
  73. package/src/card-interaction.test.ts +0 -129
  74. package/src/card-interaction.ts +0 -159
  75. package/src/card-test-helpers.ts +0 -47
  76. package/src/card-ux-approval.ts +0 -65
  77. package/src/card-ux-launcher.test.ts +0 -99
  78. package/src/card-ux-launcher.ts +0 -121
  79. package/src/card-ux-shared.ts +0 -33
  80. package/src/channel-runtime-api.ts +0 -16
  81. package/src/channel.runtime.ts +0 -47
  82. package/src/channel.test.ts +0 -959
  83. package/src/channel.ts +0 -1313
  84. package/src/chat-schema.ts +0 -25
  85. package/src/chat.test.ts +0 -196
  86. package/src/chat.ts +0 -188
  87. package/src/client.test.ts +0 -433
  88. package/src/client.ts +0 -290
  89. package/src/comment-dispatcher-runtime-api.ts +0 -6
  90. package/src/comment-dispatcher.test.ts +0 -169
  91. package/src/comment-dispatcher.ts +0 -107
  92. package/src/comment-handler-runtime-api.ts +0 -3
  93. package/src/comment-handler.test.ts +0 -486
  94. package/src/comment-handler.ts +0 -309
  95. package/src/comment-reaction.test.ts +0 -166
  96. package/src/comment-reaction.ts +0 -259
  97. package/src/comment-shared.test.ts +0 -182
  98. package/src/comment-shared.ts +0 -406
  99. package/src/comment-target.ts +0 -44
  100. package/src/config-schema.test.ts +0 -309
  101. package/src/config-schema.ts +0 -333
  102. package/src/conversation-id.test.ts +0 -18
  103. package/src/conversation-id.ts +0 -199
  104. package/src/dedup-runtime-api.ts +0 -1
  105. package/src/dedup.ts +0 -141
  106. package/src/directory.static.ts +0 -61
  107. package/src/directory.test.ts +0 -136
  108. package/src/directory.ts +0 -124
  109. package/src/doc-schema.ts +0 -182
  110. package/src/docx-batch-insert.test.ts +0 -91
  111. package/src/docx-batch-insert.ts +0 -223
  112. package/src/docx-color-text.ts +0 -154
  113. package/src/docx-table-ops.test.ts +0 -53
  114. package/src/docx-table-ops.ts +0 -316
  115. package/src/docx-types.ts +0 -38
  116. package/src/docx.account-selection.test.ts +0 -79
  117. package/src/docx.test.ts +0 -685
  118. package/src/docx.ts +0 -1616
  119. package/src/drive-schema.ts +0 -92
  120. package/src/drive.test.ts +0 -1219
  121. package/src/drive.ts +0 -829
  122. package/src/dynamic-agent.ts +0 -137
  123. package/src/event-types.ts +0 -45
  124. package/src/external-keys.test.ts +0 -20
  125. package/src/external-keys.ts +0 -19
  126. package/src/lifecycle.test-support.ts +0 -220
  127. package/src/media.test.ts +0 -900
  128. package/src/media.ts +0 -861
  129. package/src/mention-target.types.ts +0 -5
  130. package/src/mention.ts +0 -114
  131. package/src/message-action-contract.ts +0 -13
  132. package/src/monitor-state-runtime-api.ts +0 -7
  133. package/src/monitor-transport-runtime-api.ts +0 -7
  134. package/src/monitor.account.ts +0 -468
  135. package/src/monitor.acp-init-failure.lifecycle.test-support.ts +0 -219
  136. package/src/monitor.bot-identity.ts +0 -86
  137. package/src/monitor.bot-menu-handler.ts +0 -165
  138. package/src/monitor.bot-menu.lifecycle.test-support.ts +0 -224
  139. package/src/monitor.bot-menu.test.ts +0 -178
  140. package/src/monitor.broadcast.reply-once.lifecycle.test-support.ts +0 -264
  141. package/src/monitor.card-action.lifecycle.test-support.ts +0 -373
  142. package/src/monitor.cleanup.test.ts +0 -376
  143. package/src/monitor.comment-notice-handler.ts +0 -105
  144. package/src/monitor.comment.test.ts +0 -937
  145. package/src/monitor.comment.ts +0 -1386
  146. package/src/monitor.lifecycle.test.ts +0 -4
  147. package/src/monitor.message-handler.ts +0 -339
  148. package/src/monitor.reaction.lifecycle.test-support.ts +0 -68
  149. package/src/monitor.reaction.test.ts +0 -713
  150. package/src/monitor.startup.test.ts +0 -192
  151. package/src/monitor.startup.ts +0 -74
  152. package/src/monitor.state.defaults.test.ts +0 -46
  153. package/src/monitor.state.ts +0 -170
  154. package/src/monitor.synthetic-error.ts +0 -18
  155. package/src/monitor.test-mocks.ts +0 -45
  156. package/src/monitor.transport.ts +0 -424
  157. package/src/monitor.ts +0 -100
  158. package/src/monitor.webhook-e2e.test.ts +0 -272
  159. package/src/monitor.webhook-security.test.ts +0 -264
  160. package/src/monitor.webhook.test-helpers.ts +0 -116
  161. package/src/outbound-runtime-api.ts +0 -1
  162. package/src/outbound.test.ts +0 -935
  163. package/src/outbound.ts +0 -718
  164. package/src/perm-schema.ts +0 -52
  165. package/src/perm.ts +0 -170
  166. package/src/pins.ts +0 -108
  167. package/src/policy.test.ts +0 -334
  168. package/src/policy.ts +0 -236
  169. package/src/post.test.ts +0 -105
  170. package/src/post.ts +0 -275
  171. package/src/probe.test.ts +0 -275
  172. package/src/probe.ts +0 -166
  173. package/src/processing-claims.ts +0 -59
  174. package/src/qr-terminal.ts +0 -1
  175. package/src/reactions.ts +0 -123
  176. package/src/reasoning-preview.test.ts +0 -59
  177. package/src/reasoning-preview.ts +0 -20
  178. package/src/reply-dispatcher-runtime-api.ts +0 -7
  179. package/src/reply-dispatcher.test.ts +0 -1144
  180. package/src/reply-dispatcher.ts +0 -650
  181. package/src/runtime.ts +0 -9
  182. package/src/secret-contract.ts +0 -145
  183. package/src/secret-input.ts +0 -1
  184. package/src/security-audit-shared.ts +0 -69
  185. package/src/security-audit.test.ts +0 -61
  186. package/src/security-audit.ts +0 -1
  187. package/src/send-result.ts +0 -29
  188. package/src/send-target.test.ts +0 -80
  189. package/src/send-target.ts +0 -35
  190. package/src/send.reply-fallback.test.ts +0 -292
  191. package/src/send.test.ts +0 -550
  192. package/src/send.ts +0 -800
  193. package/src/sequential-key.test.ts +0 -72
  194. package/src/sequential-key.ts +0 -28
  195. package/src/sequential-queue.test.ts +0 -92
  196. package/src/sequential-queue.ts +0 -16
  197. package/src/session-conversation.ts +0 -42
  198. package/src/session-route.ts +0 -48
  199. package/src/setup-core.ts +0 -51
  200. package/src/setup-surface.test.ts +0 -174
  201. package/src/setup-surface.ts +0 -581
  202. package/src/streaming-card.test.ts +0 -190
  203. package/src/streaming-card.ts +0 -490
  204. package/src/subagent-hooks.test.ts +0 -603
  205. package/src/subagent-hooks.ts +0 -397
  206. package/src/targets.ts +0 -97
  207. package/src/test-support/lifecycle-test-support.ts +0 -453
  208. package/src/thread-bindings.test.ts +0 -143
  209. package/src/thread-bindings.ts +0 -330
  210. package/src/tool-account-routing.test.ts +0 -187
  211. package/src/tool-account.test.ts +0 -44
  212. package/src/tool-account.ts +0 -93
  213. package/src/tool-factory-test-harness.ts +0 -79
  214. package/src/tool-result.test.ts +0 -32
  215. package/src/tool-result.ts +0 -16
  216. package/src/tools-config.test.ts +0 -21
  217. package/src/tools-config.ts +0 -22
  218. package/src/types.ts +0 -104
  219. package/src/typing.test.ts +0 -144
  220. package/src/typing.ts +0 -214
  221. package/src/wiki-schema.ts +0 -55
  222. package/src/wiki.ts +0 -227
  223. package/subagent-hooks-api.ts +0 -31
  224. package/tsconfig.json +0 -16
@@ -1,581 +0,0 @@
1
- import {
2
- DEFAULT_ACCOUNT_ID,
3
- formatDocsLink,
4
- hasConfiguredSecretInput,
5
- mergeAllowFromEntries,
6
- patchTopLevelChannelConfigSection,
7
- promptSingleChannelSecretInput,
8
- splitSetupEntries,
9
- type ChannelSetupDmPolicy,
10
- type ChannelSetupWizard,
11
- type DmPolicy,
12
- type OpenClawConfig,
13
- type SecretInput,
14
- } from "openclaw/plugin-sdk/setup";
15
- import { inspectFeishuCredentials, resolveDefaultFeishuAccountId } from "./accounts.js";
16
- import type { AppRegistrationResult } from "./app-registration.js";
17
- import type { FeishuConfig, FeishuDomain } from "./types.js";
18
-
19
- const channel = "feishu" as const;
20
-
21
- // ---------------------------------------------------------------------------
22
- // Helpers
23
- // ---------------------------------------------------------------------------
24
-
25
- function normalizeString(value: unknown): string | undefined {
26
- if (typeof value !== "string") {
27
- return undefined;
28
- }
29
- const trimmed = value.trim();
30
- return trimmed || undefined;
31
- }
32
-
33
- function isFeishuConfigured(cfg: OpenClawConfig): boolean {
34
- const feishuCfg = cfg.channels?.feishu as FeishuConfig | undefined;
35
-
36
- const isAppIdConfigured = (value: unknown): boolean => {
37
- const asString = normalizeString(value);
38
- if (asString) {
39
- return true;
40
- }
41
- if (!value || typeof value !== "object") {
42
- return false;
43
- }
44
- const rec = value as Record<string, unknown>;
45
- const source = normalizeString(rec.source)?.toLowerCase();
46
- const id = normalizeString(rec.id);
47
- if (source === "env" && id) {
48
- return Boolean(normalizeString(process.env[id]));
49
- }
50
- return hasConfiguredSecretInput(value);
51
- };
52
-
53
- const topLevelConfigured =
54
- isAppIdConfigured(feishuCfg?.appId) && hasConfiguredSecretInput(feishuCfg?.appSecret);
55
-
56
- const accountConfigured = Object.values(feishuCfg?.accounts ?? {}).some((account) => {
57
- if (!account || typeof account !== "object") {
58
- return false;
59
- }
60
- const hasOwnAppId = Object.prototype.hasOwnProperty.call(account, "appId");
61
- const hasOwnAppSecret = Object.prototype.hasOwnProperty.call(account, "appSecret");
62
- const accountAppIdConfigured = hasOwnAppId
63
- ? isAppIdConfigured((account as Record<string, unknown>).appId)
64
- : isAppIdConfigured(feishuCfg?.appId);
65
- const accountSecretConfigured = hasOwnAppSecret
66
- ? hasConfiguredSecretInput((account as Record<string, unknown>).appSecret)
67
- : hasConfiguredSecretInput(feishuCfg?.appSecret);
68
- return accountAppIdConfigured && accountSecretConfigured;
69
- });
70
-
71
- return topLevelConfigured || accountConfigured;
72
- }
73
-
74
- /**
75
- * Patch feishu config at the correct location based on accountId.
76
- * - DEFAULT_ACCOUNT_ID → writes to top-level channels.feishu
77
- * - named account → writes to channels.feishu.accounts[accountId]
78
- */
79
- function patchFeishuConfig(
80
- cfg: OpenClawConfig,
81
- accountId: string,
82
- patch: Record<string, unknown>,
83
- ): OpenClawConfig {
84
- const feishuCfg = cfg.channels?.feishu as FeishuConfig | undefined;
85
- if (accountId === DEFAULT_ACCOUNT_ID) {
86
- return patchTopLevelChannelConfigSection({
87
- cfg,
88
- channel,
89
- enabled: true,
90
- patch,
91
- });
92
- }
93
- const nextAccountPatch = {
94
- ...(feishuCfg?.accounts?.[accountId] as Record<string, unknown> | undefined),
95
- enabled: true,
96
- ...patch,
97
- };
98
- return patchTopLevelChannelConfigSection({
99
- cfg,
100
- channel,
101
- enabled: true,
102
- patch: {
103
- accounts: {
104
- ...feishuCfg?.accounts,
105
- [accountId]: nextAccountPatch,
106
- },
107
- },
108
- });
109
- }
110
-
111
- async function promptFeishuAllowFrom(params: {
112
- cfg: OpenClawConfig;
113
- accountId?: string;
114
- prompter: Parameters<NonNullable<ChannelSetupDmPolicy["promptAllowFrom"]>>[0]["prompter"];
115
- }): Promise<OpenClawConfig> {
116
- const feishuCfg = params.cfg.channels?.feishu as FeishuConfig | undefined;
117
- const resolvedAccountId = params.accountId ?? resolveDefaultFeishuAccountId(params.cfg);
118
- const account =
119
- resolvedAccountId !== DEFAULT_ACCOUNT_ID
120
- ? (feishuCfg?.accounts?.[resolvedAccountId] as Record<string, unknown> | undefined)
121
- : undefined;
122
- const existingAllowFrom = (account?.allowFrom ?? feishuCfg?.allowFrom ?? []) as Array<
123
- string | number
124
- >;
125
- await params.prompter.note(
126
- [
127
- "Allowlist Feishu DMs by open_id or user_id.",
128
- "You can find user open_id in Feishu admin console or via API.",
129
- "Examples:",
130
- "- ou_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx",
131
- "- on_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx",
132
- ].join("\n"),
133
- "Feishu allowlist",
134
- );
135
- const entry = await params.prompter.text({
136
- message: "Feishu allowFrom (user open_ids)",
137
- placeholder: "ou_xxxxx, ou_yyyyy",
138
- initialValue:
139
- existingAllowFrom.length > 0 ? existingAllowFrom.map(String).join(", ") : undefined,
140
- });
141
- const mergedAllowFrom = mergeAllowFromEntries(existingAllowFrom, splitSetupEntries(entry));
142
- return patchFeishuConfig(params.cfg, resolvedAccountId, { allowFrom: mergedAllowFrom });
143
- }
144
-
145
- async function noteFeishuCredentialHelp(
146
- prompter: Parameters<NonNullable<ChannelSetupWizard["finalize"]>>[0]["prompter"],
147
- ): Promise<void> {
148
- await prompter.note(
149
- [
150
- "1) Go to Feishu Open Platform (open.feishu.cn)",
151
- "2) Create a self-built app",
152
- "3) Get App ID and App Secret from Credentials page",
153
- "4) Enable required permissions: im:message, im:chat, contact:user.base:readonly",
154
- "5) Publish the app or add it to a test group",
155
- "Tip: you can also set FEISHU_APP_ID / FEISHU_APP_SECRET env vars.",
156
- `Docs: ${formatDocsLink("/channels/feishu", "feishu")}`,
157
- ].join("\n"),
158
- "Feishu credentials",
159
- );
160
- }
161
-
162
- async function promptFeishuAppId(params: {
163
- prompter: Parameters<NonNullable<ChannelSetupWizard["finalize"]>>[0]["prompter"];
164
- initialValue?: string;
165
- }): Promise<string> {
166
- return (
167
- await params.prompter.text({
168
- message: "Enter Feishu App ID",
169
- initialValue: params.initialValue,
170
- validate: (value) => (value?.trim() ? undefined : "Required"),
171
- })
172
- ).trim();
173
- }
174
-
175
- const feishuDmPolicy: ChannelSetupDmPolicy = {
176
- label: "Feishu",
177
- channel,
178
- policyKey: "channels.feishu.dmPolicy",
179
- allowFromKey: "channels.feishu.allowFrom",
180
- resolveConfigKeys: (_cfg, accountId) => {
181
- const resolvedAccountId = accountId ?? resolveDefaultFeishuAccountId(_cfg);
182
- return resolvedAccountId !== DEFAULT_ACCOUNT_ID
183
- ? {
184
- policyKey: `channels.feishu.accounts.${resolvedAccountId}.dmPolicy`,
185
- allowFromKey: `channels.feishu.accounts.${resolvedAccountId}.allowFrom`,
186
- }
187
- : {
188
- policyKey: "channels.feishu.dmPolicy",
189
- allowFromKey: "channels.feishu.allowFrom",
190
- };
191
- },
192
- getCurrent: (cfg, accountId) => {
193
- const feishuCfg = cfg.channels?.feishu as FeishuConfig | undefined;
194
- const resolvedAccountId = accountId ?? resolveDefaultFeishuAccountId(cfg);
195
- if (resolvedAccountId !== DEFAULT_ACCOUNT_ID) {
196
- const account = feishuCfg?.accounts?.[resolvedAccountId] as
197
- | Record<string, unknown>
198
- | undefined;
199
- if (account?.dmPolicy) {
200
- return account.dmPolicy as DmPolicy;
201
- }
202
- }
203
- return (feishuCfg?.dmPolicy as DmPolicy | undefined) ?? "pairing";
204
- },
205
- setPolicy: (cfg, policy, accountId) => {
206
- const resolvedAccountId = accountId ?? resolveDefaultFeishuAccountId(cfg);
207
- return patchFeishuConfig(cfg, resolvedAccountId, {
208
- dmPolicy: policy,
209
- ...(policy === "open" ? { allowFrom: mergeAllowFromEntries([], ["*"]) } : {}),
210
- });
211
- },
212
- promptAllowFrom: promptFeishuAllowFrom,
213
- };
214
-
215
- type WizardPrompter = Parameters<NonNullable<ChannelSetupWizard["finalize"]>>[0]["prompter"];
216
-
217
- // ---------------------------------------------------------------------------
218
- // Security policy helpers
219
- // ---------------------------------------------------------------------------
220
-
221
- function applyNewAppSecurityPolicy(
222
- cfg: OpenClawConfig,
223
- accountId: string,
224
- openId: string | undefined,
225
- groupPolicy: "allowlist" | "open" | "disabled",
226
- ): OpenClawConfig {
227
- let next = cfg;
228
-
229
- if (openId) {
230
- // dmPolicy=allowlist, allowFrom=[openId]
231
- next = patchFeishuConfig(next, accountId, { dmPolicy: "allowlist", allowFrom: [openId] });
232
- }
233
-
234
- // Apply group policy.
235
- const groupPatch: Record<string, unknown> = { groupPolicy };
236
- if (groupPolicy === "open") {
237
- groupPatch.requireMention = true;
238
- }
239
- next = patchFeishuConfig(next, accountId, groupPatch);
240
-
241
- return next;
242
- }
243
-
244
- // ---------------------------------------------------------------------------
245
- // Scan-to-create flow
246
- // ---------------------------------------------------------------------------
247
-
248
- async function runScanToCreate(prompter: WizardPrompter): Promise<AppRegistrationResult | null> {
249
- const { beginAppRegistration, initAppRegistration, pollAppRegistration, printQrCode } =
250
- await import("./app-registration.js");
251
- try {
252
- await initAppRegistration("feishu");
253
- } catch {
254
- await prompter.note(
255
- "Scan-to-create is not available in this environment. Falling back to manual input.",
256
- "Feishu setup",
257
- );
258
- return null;
259
- }
260
-
261
- const begin = await beginAppRegistration("feishu");
262
-
263
- await prompter.note("Scan the QR with Lark/Feishu on your phone.", "Feishu scan-to-create");
264
- await printQrCode(begin.qrUrl);
265
-
266
- const progress = prompter.progress("Fetching configuration results...");
267
-
268
- const outcome = await pollAppRegistration({
269
- deviceCode: begin.deviceCode,
270
- interval: begin.interval,
271
- expireIn: begin.expireIn,
272
- initialDomain: "feishu",
273
- tp: "ob_app",
274
- });
275
-
276
- switch (outcome.status) {
277
- case "success":
278
- progress.stop("Scan completed.");
279
- return outcome.result;
280
- case "access_denied":
281
- progress.stop("User denied authorization. Falling back to manual input.");
282
- return null;
283
- case "expired":
284
- progress.stop("Session expired. Falling back to manual input.");
285
- return null;
286
- case "timeout":
287
- progress.stop("Scan timed out. Falling back to manual input.");
288
- return null;
289
- case "error":
290
- progress.stop(`Registration error: ${outcome.message}. Falling back to manual input.`);
291
- return null;
292
- }
293
- return null;
294
- }
295
-
296
- // ---------------------------------------------------------------------------
297
- // New app configuration flow
298
- // ---------------------------------------------------------------------------
299
-
300
- async function runNewAppFlow(params: {
301
- cfg: OpenClawConfig;
302
- prompter: WizardPrompter;
303
- options: Parameters<NonNullable<ChannelSetupWizard["finalize"]>>[0]["options"];
304
- }): Promise<{ cfg: OpenClawConfig }> {
305
- const { prompter, options } = params;
306
- let next = params.cfg;
307
-
308
- // Resolve target account: defaultAccount > first account key > top-level.
309
- const targetAccountId = resolveDefaultFeishuAccountId(next);
310
-
311
- // ----- QR scan flow -----
312
- let appId: string | null = null;
313
- let appSecret: SecretInput | null = null;
314
- let appSecretProbeValue: string | null = null;
315
- let scanDomain: FeishuDomain | undefined;
316
- let scanOpenId: string | undefined;
317
-
318
- const scanResult = await runScanToCreate(prompter);
319
- if (scanResult) {
320
- appId = scanResult.appId;
321
- appSecret = scanResult.appSecret;
322
- appSecretProbeValue = scanResult.appSecret;
323
- scanDomain = scanResult.domain;
324
- scanOpenId = scanResult.openId;
325
- } else {
326
- // Fallback to manual input: collect domain, appId, appSecret.
327
- const feishuCfg = next.channels?.feishu as FeishuConfig | undefined;
328
- await noteFeishuCredentialHelp(prompter);
329
-
330
- // Domain selection first (needed for API calls).
331
- const currentDomain = feishuCfg?.domain ?? "feishu";
332
- const domain = (await prompter.select({
333
- message: "Which Feishu domain?",
334
- options: [
335
- { value: "feishu", label: "Feishu (feishu.cn) - China" },
336
- { value: "lark", label: "Lark (larksuite.com) - International" },
337
- ],
338
- initialValue: currentDomain,
339
- })) as FeishuDomain;
340
- scanDomain = domain;
341
-
342
- appId = await promptFeishuAppId({
343
- prompter,
344
- initialValue: normalizeString(process.env.FEISHU_APP_ID),
345
- });
346
-
347
- const appSecretResult = await promptSingleChannelSecretInput({
348
- cfg: next,
349
- prompter,
350
- providerHint: "feishu",
351
- credentialLabel: "App Secret",
352
- secretInputMode: options?.secretInputMode,
353
- accountConfigured: false,
354
- canUseEnv: false,
355
- hasConfigToken: false,
356
- envPrompt: "",
357
- keepPrompt: "Feishu App Secret already configured. Keep it?",
358
- inputPrompt: "Enter Feishu App Secret",
359
- preferredEnvVar: "FEISHU_APP_SECRET",
360
- });
361
- if (appSecretResult.action === "set") {
362
- appSecret = appSecretResult.value;
363
- appSecretProbeValue = appSecretResult.resolvedValue;
364
- }
365
-
366
- // Fetch openId via API for manual flow.
367
- if (appId && appSecretProbeValue) {
368
- const { getAppOwnerOpenId } = await import("./app-registration.js");
369
- scanOpenId = await getAppOwnerOpenId({
370
- appId,
371
- appSecret: appSecretProbeValue,
372
- domain: scanDomain,
373
- });
374
- }
375
- }
376
-
377
- // ----- Group chat policy -----
378
- const groupPolicy = (await prompter.select({
379
- message: "Group chat policy",
380
- options: [
381
- { value: "allowlist", label: "Allowlist - only respond in specific groups" },
382
- { value: "open", label: "Open - respond in all groups (requires mention)" },
383
- { value: "disabled", label: "Disabled - don't respond in groups" },
384
- ],
385
- initialValue: "allowlist",
386
- })) as "allowlist" | "open" | "disabled";
387
-
388
- // ----- Apply credentials & security policy -----
389
- const configProgress = prompter.progress("Configuring...");
390
- await new Promise((resolve) => setTimeout(resolve, 50));
391
-
392
- if (appId && appSecret) {
393
- next = patchFeishuConfig(next, targetAccountId, {
394
- appId,
395
- appSecret,
396
- connectionMode: "websocket",
397
- ...(scanDomain ? { domain: scanDomain } : {}),
398
- });
399
- } else if (scanDomain) {
400
- next = patchFeishuConfig(next, targetAccountId, { domain: scanDomain });
401
- }
402
-
403
- next = applyNewAppSecurityPolicy(next, targetAccountId, scanOpenId, groupPolicy);
404
-
405
- configProgress.stop("Bot configured.");
406
-
407
- return { cfg: next };
408
- }
409
-
410
- // ---------------------------------------------------------------------------
411
- // Edit configuration flow
412
- // ---------------------------------------------------------------------------
413
-
414
- async function runEditFlow(params: {
415
- cfg: OpenClawConfig;
416
- prompter: WizardPrompter;
417
- options: Parameters<NonNullable<ChannelSetupWizard["finalize"]>>[0]["options"];
418
- }): Promise<{ cfg: OpenClawConfig } | null> {
419
- const { prompter, options } = params;
420
- const next = params.cfg;
421
- const feishuCfg = next.channels?.feishu as FeishuConfig | undefined;
422
-
423
- // Check existing appId (top-level or first configured account).
424
- // Supports both plain string and SecretRef (env-backed) appId values.
425
- const resolveAppIdLabel = (value: unknown): string | undefined => {
426
- const asString = normalizeString(value);
427
- if (asString) {
428
- return asString;
429
- }
430
- if (value && typeof value === "object") {
431
- const rec = value as Record<string, unknown>;
432
- if (normalizeString(rec.source) && normalizeString(rec.id)) {
433
- const envValue = normalizeString(process.env[rec.id as string]);
434
- return envValue ?? `env:${String(rec.id)}`;
435
- }
436
- if (hasConfiguredSecretInput(value)) {
437
- return "(configured)";
438
- }
439
- }
440
- return undefined;
441
- };
442
- const existingAppId =
443
- resolveAppIdLabel(feishuCfg?.appId) ??
444
- Object.values(feishuCfg?.accounts ?? {}).reduce<string | undefined>((found, account) => {
445
- if (found) {
446
- return found;
447
- }
448
- if (account && typeof account === "object") {
449
- return resolveAppIdLabel((account as Record<string, unknown>).appId);
450
- }
451
- return undefined;
452
- }, undefined);
453
- if (existingAppId) {
454
- const useExisting = await prompter.confirm({
455
- message: `We found an existing bot (App ID: ${existingAppId}). Use it for this setup?`,
456
- initialValue: true,
457
- });
458
-
459
- if (!useExisting) {
460
- // User wants a new bot — run new app flow.
461
- return runNewAppFlow({ cfg: next, prompter, options });
462
- }
463
- } else {
464
- // No existing appId — run new app flow.
465
- return runNewAppFlow({ cfg: next, prompter, options });
466
- }
467
-
468
- await prompter.note("Bot configured.", "");
469
-
470
- return { cfg: next };
471
- }
472
-
473
- // ---------------------------------------------------------------------------
474
- // Standalone login entry point (for `channels login --channel feishu`)
475
- // ---------------------------------------------------------------------------
476
-
477
- export async function runFeishuLogin(params: {
478
- cfg: OpenClawConfig;
479
- prompter: WizardPrompter;
480
- }): Promise<OpenClawConfig> {
481
- const { cfg, prompter } = params;
482
- const options = {};
483
- const alreadyConfigured = isFeishuConfigured(cfg);
484
-
485
- if (alreadyConfigured) {
486
- const result = await runEditFlow({ cfg, prompter, options });
487
- if (result === null) {
488
- return cfg;
489
- }
490
- return result.cfg;
491
- }
492
-
493
- const result = await runNewAppFlow({ cfg, prompter, options });
494
- return result.cfg;
495
- }
496
-
497
- // ---------------------------------------------------------------------------
498
- // Exported wizard
499
- // ---------------------------------------------------------------------------
500
-
501
- export const feishuSetupWizard: ChannelSetupWizard = {
502
- channel,
503
- resolveAccountIdForConfigure: ({ accountOverride, defaultAccountId, cfg }) =>
504
- (typeof accountOverride === "string" && accountOverride.trim()
505
- ? accountOverride.trim()
506
- : undefined) ??
507
- resolveDefaultFeishuAccountId(cfg) ??
508
- defaultAccountId,
509
- resolveShouldPromptAccountIds: () => false,
510
- status: {
511
- configuredLabel: "configured",
512
- unconfiguredLabel: "needs app credentials",
513
- configuredHint: "configured",
514
- unconfiguredHint: "needs app creds",
515
- configuredScore: 2,
516
- unconfiguredScore: 0,
517
- resolveConfigured: ({ cfg }) => isFeishuConfigured(cfg),
518
- resolveStatusLines: async ({ cfg, configured }) => {
519
- const feishuCfg = cfg.channels?.feishu as FeishuConfig | undefined;
520
- const resolvedCredentials = inspectFeishuCredentials(feishuCfg);
521
- let probeResult = null;
522
- if (configured && resolvedCredentials) {
523
- try {
524
- const { probeFeishu } = await import("./probe.js");
525
- probeResult = await probeFeishu(resolvedCredentials);
526
- } catch {}
527
- }
528
- if (!configured) {
529
- return ["Feishu: needs app credentials"];
530
- }
531
- if (probeResult?.ok) {
532
- return [`Feishu: connected as ${probeResult.botName ?? probeResult.botOpenId ?? "bot"}`];
533
- }
534
- return ["Feishu: configured (connection not verified)"];
535
- },
536
- },
537
-
538
- // -------------------------------------------------------------------------
539
- // prepare: determine flow based on existing configuration
540
- // -------------------------------------------------------------------------
541
- prepare: async ({ cfg, credentialValues }) => {
542
- const alreadyConfigured = isFeishuConfigured(cfg);
543
-
544
- if (alreadyConfigured) {
545
- return {
546
- credentialValues: { ...credentialValues, _flow: "edit" },
547
- };
548
- }
549
-
550
- return {
551
- credentialValues: { ...credentialValues, _flow: "new" },
552
- };
553
- },
554
-
555
- credentials: [],
556
-
557
- // -------------------------------------------------------------------------
558
- // finalize: run the appropriate flow
559
- // -------------------------------------------------------------------------
560
- finalize: async ({ cfg, prompter, options, credentialValues }) => {
561
- const flow = credentialValues._flow ?? "new";
562
-
563
- if (flow === "edit") {
564
- const result = await runEditFlow({ cfg, prompter, options });
565
- if (result === null) {
566
- return { cfg };
567
- }
568
- return result;
569
- }
570
-
571
- return runNewAppFlow({ cfg, prompter, options });
572
- },
573
-
574
- dmPolicy: feishuDmPolicy,
575
- disable: (cfg) =>
576
- patchTopLevelChannelConfigSection({
577
- cfg,
578
- channel,
579
- patch: { enabled: false },
580
- }),
581
- };