@openclaw/feishu 2026.9.1-beta.1 → 2026.9.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.
Files changed (47) hide show
  1. package/dist/{accounts-RwSyseKr.js → accounts-cCMNFBKg.js} +1 -1
  2. package/dist/api.js +222 -284
  3. package/dist/{app-registration-BpfUzS9m.js → app-registration-BI99e_JI.js} +12 -9
  4. package/dist/{channel-Dpgf8wLD.js → channel-VytDDz_B.js} +244 -582
  5. package/dist/channel-plugin-api.js +1 -1
  6. package/dist/{channel.runtime-BIEERwaT.js → channel.runtime-CAD74kBO.js} +15 -19
  7. package/dist/{chat-C1nuW5UY.js → chat-DDhIcPQy.js} +13 -13
  8. package/dist/{client-DalLmpMf.js → client-DMbIL3UH.js} +3 -6
  9. package/dist/contract-api.js +2 -2
  10. package/dist/doctor-contract-api.js +1 -1
  11. package/dist/doctor-contract-w6FpMypv.js +465 -0
  12. package/dist/index.js +11 -42
  13. package/dist/{json-response-CheljwGr.js → json-response-DXajw_FU.js} +1 -1
  14. package/dist/{monitor.startup-D-gL82OO.js → monitor-rPJ_Mz4M.js} +62 -2
  15. package/dist/{monitor.account-DhhotI_Q.js → monitor.account-v0XIDyfs.js} +270 -299
  16. package/dist/{probe-CfhhR_0a.js → probe-D_SatdZl.js} +3 -3
  17. package/dist/{presentation-card-C8Z7Tgru.js → reply-delivery-result-CYaKkbUN.js} +334 -240
  18. package/dist/{security-audit-D6Fz2h6p.js → security-audit-DKfR4Cv3.js} +1 -1
  19. package/dist/{security-audit-shared-BgpY7AiJ.js → security-audit-shared-CK5rVR-1.js} +2 -1
  20. package/dist/security-contract-api.js +1 -1
  21. package/dist/session-binding-contract-api.js +1 -1
  22. package/dist/setup-api.js +1 -1
  23. package/dist/{subagent-hooks-KUi-7vUy.js → subagent-hooks-Buadpie_.js} +3 -6
  24. package/dist/subagent-hooks-api.js +1 -1
  25. package/dist/{targets-BUjQ1TcA.js → targets-BTQCYRZQ.js} +4 -4
  26. package/dist/{thread-bindings-CptM_YU6.js → thread-bindings-BiL1wGqK.js} +20 -9
  27. package/node_modules/@larksuiteoapi/node-sdk/node_modules/ws/lib/permessage-deflate.js +3 -1
  28. package/node_modules/@larksuiteoapi/node-sdk/node_modules/ws/package.json +1 -1
  29. package/node_modules/typebox/build/format/email.mjs +1 -1
  30. package/node_modules/typebox/build/format/idna/idn-hostname.mjs +19 -1
  31. package/node_modules/typebox/build/format/json_pointer.d.mts +2 -2
  32. package/node_modules/typebox/build/format/json_pointer.mjs +2 -2
  33. package/node_modules/typebox/build/format/json_pointer_uri_fragment.d.mts +2 -2
  34. package/node_modules/typebox/build/format/json_pointer_uri_fragment.mjs +2 -2
  35. package/node_modules/typebox/build/format/relative_json_pointer.d.mts +2 -2
  36. package/node_modules/typebox/build/format/relative_json_pointer.mjs +2 -2
  37. package/node_modules/typebox/build/system/memory/assign.mjs +2 -1
  38. package/node_modules/typebox/build/system/memory/create.mjs +3 -4
  39. package/node_modules/typebox/build/system/memory/discard.mjs +2 -2
  40. package/node_modules/typebox/build/system/memory/freeze.d.mts +4 -0
  41. package/node_modules/typebox/build/system/memory/freeze.mjs +6 -0
  42. package/node_modules/typebox/build/system/memory/update.mjs +3 -2
  43. package/node_modules/typebox/package.json +1 -1
  44. package/openclaw.plugin.json +45 -238
  45. package/package.json +6 -5
  46. package/dist/doctor-contract-bEQXIXyP.js +0 -158
  47. package/dist/monitor-BRg3sHH7.js +0 -62
@@ -1,2 +1,2 @@
1
- import { t as feishuPlugin } from "./channel-Dpgf8wLD.js";
1
+ import { t as feishuPlugin } from "./channel-VytDDz_B.js";
2
2
  export { feishuPlugin };
@@ -1,9 +1,9 @@
1
- import { o as resolveFeishuAccount, s as resolveFeishuRuntimeAccount } from "./accounts-RwSyseKr.js";
2
- import { c as listFeishuDirectoryPeers, o as feishuOutbound, s as listFeishuDirectoryGroups } from "./channel-Dpgf8wLD.js";
3
- import { d as editMessageFeishu, f as getMessageFeishu, g as sendMessageFeishu, l as sendStickerFeishu, m as sendCardFeishu } from "./presentation-card-C8Z7Tgru.js";
4
- import { r as createFeishuClient } from "./client-DalLmpMf.js";
5
- import { a as getFeishuMemberInfo, i as getChatMembers, n as buildFeishuDirectChatMembers, r as getChatInfo, t as assertFeishuChatMember } from "./chat-C1nuW5UY.js";
6
- import { t as probeFeishu } from "./probe-CfhhR_0a.js";
1
+ import { o as resolveFeishuAccount, s as resolveFeishuRuntimeAccount } from "./accounts-cCMNFBKg.js";
2
+ import { c as listFeishuDirectoryPeers, o as feishuOutbound, s as listFeishuDirectoryGroups } from "./channel-VytDDz_B.js";
3
+ import { C as editMessageFeishu, D as sendMessageFeishu, E as sendCardFeishu, I as assertFeishuApiSuccess, b as sendStickerFeishu, w as getMessageFeishu } from "./reply-delivery-result-CYaKkbUN.js";
4
+ import { r as createFeishuClient } from "./client-DMbIL3UH.js";
5
+ import { a as getFeishuMemberInfo, i as getChatMembers, n as buildFeishuDirectChatMembers, r as getChatInfo, t as assertFeishuChatMember } from "./chat-DDhIcPQy.js";
6
+ import { t as probeFeishu } from "./probe-D_SatdZl.js";
7
7
  import { normalizeLowercaseStringOrEmpty } from "openclaw/plugin-sdk/string-coerce-runtime";
8
8
  //#region extensions/feishu/src/directory.ts
9
9
  const MAX_FEISHU_DIRECTORY_PAGES = 100;
@@ -84,9 +84,6 @@ async function listFeishuDirectoryGroupsLive(params) {
84
84
  }
85
85
  //#endregion
86
86
  //#region extensions/feishu/src/pins.ts
87
- function assertFeishuPinApiSuccess(response, action) {
88
- if (response.code !== 0) throw new Error(`Feishu ${action} failed: ${response.msg || `code ${response.code}`}`);
89
- }
90
87
  function normalizePin(pin) {
91
88
  return {
92
89
  messageId: pin.message_id,
@@ -103,7 +100,7 @@ async function createPinFeishu(params) {
103
100
  });
104
101
  if (!account.configured) throw new Error(`Feishu account "${account.accountId}" not configured`);
105
102
  const response = await createFeishuClient(account).im.pin.create({ data: { message_id: params.messageId } });
106
- assertFeishuPinApiSuccess(response, "pin create");
103
+ assertFeishuApiSuccess(response, "Feishu pin create failed");
107
104
  return response.data?.pin ? normalizePin(response.data.pin) : null;
108
105
  }
109
106
  async function removePinFeishu(params) {
@@ -112,7 +109,8 @@ async function removePinFeishu(params) {
112
109
  accountId: params.accountId
113
110
  });
114
111
  if (!account.configured) throw new Error(`Feishu account "${account.accountId}" not configured`);
115
- assertFeishuPinApiSuccess(await createFeishuClient(account).im.pin.delete({ path: { message_id: params.messageId } }), "pin delete");
112
+ const response = await createFeishuClient(account).im.pin.delete({ path: { message_id: params.messageId } });
113
+ assertFeishuApiSuccess(response, "Feishu pin delete failed");
116
114
  }
117
115
  async function listPinsFeishu(params) {
118
116
  const account = resolveFeishuRuntimeAccount({
@@ -127,7 +125,7 @@ async function listPinsFeishu(params) {
127
125
  ...typeof params.pageSize === "number" ? { page_size: Math.max(1, Math.min(100, Math.floor(params.pageSize))) } : {},
128
126
  ...params.pageToken ? { page_token: params.pageToken } : {}
129
127
  } });
130
- assertFeishuPinApiSuccess(response, "pin list");
128
+ assertFeishuApiSuccess(response, "Feishu pin list failed");
131
129
  return {
132
130
  chatId: params.chatId,
133
131
  pins: (response.data?.items ?? []).map(normalizePin),
@@ -142,9 +140,6 @@ function resolveConfiguredFeishuClient(params) {
142
140
  if (!account.configured) throw new Error(`Feishu account "${account.accountId}" not configured`);
143
141
  return createFeishuClient(account);
144
142
  }
145
- function assertFeishuReactionApiSuccess(response, action) {
146
- if (response.code !== 0) throw new Error(`Feishu ${action} failed: ${response.msg || `code ${response.code}`}`);
147
- }
148
143
  /**
149
144
  * Add a reaction (emoji) to a message.
150
145
  * @param emojiType - Feishu emoji type, e.g., "SMILE", "THUMBSUP", "HEART"
@@ -159,7 +154,7 @@ async function addReactionFeishu(params) {
159
154
  path: { message_id: messageId },
160
155
  data: { reaction_type: { emoji_type: emojiType } }
161
156
  });
162
- assertFeishuReactionApiSuccess(response, "add reaction");
157
+ assertFeishuApiSuccess(response, "Feishu add reaction failed");
163
158
  const reactionId = response.data?.reaction_id;
164
159
  if (!reactionId) throw new Error("Feishu add reaction failed: no reaction_id returned");
165
160
  return { reactionId };
@@ -169,13 +164,14 @@ async function addReactionFeishu(params) {
169
164
  */
170
165
  async function removeReactionFeishu(params) {
171
166
  const { cfg, messageId, reactionId, accountId } = params;
172
- assertFeishuReactionApiSuccess(await resolveConfiguredFeishuClient({
167
+ const response = await resolveConfiguredFeishuClient({
173
168
  cfg,
174
169
  accountId
175
170
  }).im.messageReaction.delete({ path: {
176
171
  message_id: messageId,
177
172
  reaction_id: reactionId
178
- } }), "remove reaction");
173
+ } });
174
+ assertFeishuApiSuccess(response, "Feishu remove reaction failed");
179
175
  }
180
176
  /**
181
177
  * List all reactions for a message.
@@ -197,7 +193,7 @@ async function listReactionsFeishu(params) {
197
193
  ...pageToken ? { page_token: pageToken } : {}
198
194
  } : void 0
199
195
  });
200
- assertFeishuReactionApiSuccess(response, "list reactions");
196
+ assertFeishuApiSuccess(response, "Feishu list reactions failed");
201
197
  for (const item of response.data?.items ?? []) reactions.push({
202
198
  reactionId: item.reaction_id ?? "",
203
199
  emojiType: item.reaction_type?.emoji_type ?? "",
@@ -1,6 +1,5 @@
1
- import { r as listEnabledFeishuAccounts } from "./accounts-RwSyseKr.js";
2
- import { A as feishuExternalToolResult, B as formatFeishuApiError, F as resolveFeishuToolAccount, P as resolveAnyEnabledFeishuToolsConfig, et as assertFeishuChatReadAllowed, gt as resolveFeishuChatType, ot as resolveFeishuChatReadPreliminaryAuthorization, tt as authorizeFeishuChatMemberRead } from "./presentation-card-C8Z7Tgru.js";
3
- import { r as createFeishuClient } from "./client-DalLmpMf.js";
1
+ import { Et as resolveFeishuChatType, H as feishuExternalToolResult, K as resolveAnyEnabledFeishuToolsConfig, Q as formatFeishuApiError, dt as authorizeFeishuChatMemberRead, gt as resolveFeishuChatReadPreliminaryAuthorization, q as resolveFeishuToolAccount, ut as assertFeishuChatReadAllowed } from "./reply-delivery-result-CYaKkbUN.js";
2
+ import { r as createFeishuClient } from "./client-DMbIL3UH.js";
4
3
  import { optionalPositiveIntegerSchema } from "openclaw/plugin-sdk/channel-actions";
5
4
  import { readPositiveIntegerParam } from "openclaw/plugin-sdk/param-readers";
6
5
  import { Type } from "typebox";
@@ -192,9 +191,7 @@ async function getFeishuMemberInfo(client, memberId, memberIdType = "open_id") {
192
191
  function registerFeishuChatTools(api) {
193
192
  if (!api.config) return;
194
193
  const cfg = api.config;
195
- const accounts = listEnabledFeishuAccounts(cfg);
196
- if (accounts.length === 0) return;
197
- if (!resolveAnyEnabledFeishuToolsConfig(accounts).chat) return;
194
+ if (!resolveAnyEnabledFeishuToolsConfig(cfg).chat) return;
198
195
  api.registerTool((toolContext) => ({
199
196
  name: "feishu_chat",
200
197
  resultContentSource: "network",
@@ -238,13 +235,16 @@ function registerFeishuChatTools(api) {
238
235
  return feishuExternalToolResult(await getChatMembers(client, p.chat_id, readChatPageSize(rawParams), p.page_token, p.member_id_type));
239
236
  case "info":
240
237
  if (!p.chat_id) return feishuExternalToolResult({ error: "chat_id is required for action info" });
241
- return feishuExternalToolResult(await getAuthorizedFeishuChatInfo({
242
- client,
243
- cfg,
244
- account,
245
- chatId: p.chat_id,
246
- ctx: toolContext
247
- }));
238
+ {
239
+ const chat = await getAuthorizedFeishuChatInfo({
240
+ client,
241
+ cfg,
242
+ account,
243
+ chatId: p.chat_id,
244
+ ctx: toolContext
245
+ });
246
+ return feishuExternalToolResult(chat);
247
+ }
248
248
  case "member_info":
249
249
  if (!p.member_id) return feishuExternalToolResult({ error: "member_id is required for action member_info" });
250
250
  if (!p.chat_id) return feishuExternalToolResult({ error: "chat_id is required for action member_info" });
@@ -2,6 +2,7 @@ import { t as __exportAll } from "./rolldown-runtime-8H4AJuhK.js";
2
2
  import { createRequire } from "node:module";
3
3
  import { parseStrictPositiveInteger } from "openclaw/plugin-sdk/number-runtime";
4
4
  import * as Lark from "@larksuiteoapi/node-sdk";
5
+ import { bufferToBlobPart } from "openclaw/plugin-sdk/blob-runtime";
5
6
  import { isRecord } from "openclaw/plugin-sdk/channel-secret-basic-runtime";
6
7
  import { readPluginPackageVersion, resolveAmbientNodeProxyAgent } from "openclaw/plugin-sdk/extension-shared";
7
8
  //#region extensions/feishu/src/client-timeout.ts
@@ -32,7 +33,8 @@ var client_exports = /* @__PURE__ */ __exportAll({
32
33
  createFeishuWSClient: () => createFeishuWSClient,
33
34
  getFeishuUserAgent: () => getFeishuUserAgent
34
35
  });
35
- const FEISHU_USER_AGENT = `openclaw-feishu-builtin/${readPluginPackageVersion({ require: createRequire(import.meta.url) })}/${process.platform}`;
36
+ const require = createRequire(import.meta.url);
37
+ const FEISHU_USER_AGENT = `openclaw-feishu-builtin/${readPluginPackageVersion({ require })}/${process.platform}`;
36
38
  const FEISHU_SDK_ORIGIN = "https://open.feishu.cn";
37
39
  const FEISHU_WS_CONFIG = { pingTimeout: 3 };
38
40
  /** User-Agent header value for all Feishu API requests. */
@@ -99,11 +101,6 @@ function stringifyMultipartFieldValue(value) {
99
101
  default: return;
100
102
  }
101
103
  }
102
- function bufferToBlobPart(value) {
103
- const bytes = new Uint8Array(value.byteLength);
104
- bytes.set(value);
105
- return bytes;
106
- }
107
104
  function normalizeMultipartUploadData(opts) {
108
105
  if (!isMultipartFormRequest(opts) || !isRecord(opts.data) || !isFeishuMessageMediaUploadRequest(opts, opts.data)) return opts;
109
106
  const form = new FormData();
@@ -1,4 +1,4 @@
1
1
  import { a as parseFeishuTargetId, i as parseFeishuDirectConversationId, r as parseFeishuConversationId, t as buildFeishuConversationId } from "./conversation-id-VYgGQ-GX.js";
2
- import { t as messageActionTargetAliases } from "./security-audit-D6Fz2h6p.js";
3
- import { t as collectFeishuSecurityAuditFindings } from "./security-audit-shared-BgpY7AiJ.js";
2
+ import { t as messageActionTargetAliases } from "./security-audit-DKfR4Cv3.js";
3
+ import { t as collectFeishuSecurityAuditFindings } from "./security-audit-shared-CK5rVR-1.js";
4
4
  export { buildFeishuConversationId, collectFeishuSecurityAuditFindings, messageActionTargetAliases, parseFeishuConversationId, parseFeishuDirectConversationId, parseFeishuTargetId };
@@ -1,2 +1,2 @@
1
- import { n as normalizeCompatibilityConfig, t as legacyConfigRules } from "./doctor-contract-bEQXIXyP.js";
1
+ import { n as normalizeCompatibilityConfig, t as legacyConfigRules } from "./doctor-contract-w6FpMypv.js";
2
2
  export { legacyConfigRules, normalizeCompatibilityConfig };
@@ -0,0 +1,465 @@
1
+ import { normalizeAccountId } from "openclaw/plugin-sdk/account-id";
2
+ import { ContextVisibilityModeSchema, DmPolicySchema, GroupPolicySchema, ReplyToModeSchema, buildChannelConfigSchema, buildGroupEntrySchema, buildMultiAccountChannelSchema } from "openclaw/plugin-sdk/channel-config-schema";
3
+ import { z } from "zod";
4
+ import { buildSecretInputSchema, hasConfiguredSecretInput as hasConfiguredSecretInput$1 } from "openclaw/plugin-sdk/secret-input";
5
+ import { asObjectRecord, defineChannelAliasMigration, defineKeyMoveMigration, defineStrayPluginEntryConfigMigration, hasLegacyAccountStreamingAliases, normalizeChannelConfigEntries } from "openclaw/plugin-sdk/runtime-doctor-migrations";
6
+ //#region extensions/feishu/src/external-keys.ts
7
+ const FEISHU_EXTERNAL_KEY_PATTERN = /^(?!\s)(?![\s\S]*\s$)(?![\s\S]*\.\.)[^\p{Cc}\p{Cs}/\\]{1,512}$/u;
8
+ function normalizeFeishuExternalKey(value) {
9
+ if (typeof value !== "string") return;
10
+ const normalized = value.trim();
11
+ return FEISHU_EXTERNAL_KEY_PATTERN.test(normalized) ? normalized : void 0;
12
+ }
13
+ //#endregion
14
+ //#region extensions/feishu/src/webhook-path.ts
15
+ const DEFAULT_FEISHU_WEBHOOK_PATH = "/feishu/events";
16
+ /** Normalize trusted configuration only; incoming request targets must remain unmodified. */
17
+ function normalizeFeishuWebhookPath(value) {
18
+ const configured = value?.trim();
19
+ if (!configured) return DEFAULT_FEISHU_WEBHOOK_PATH;
20
+ try {
21
+ const parsed = new URL(configured, "http://localhost");
22
+ if (parsed.protocol !== "http:" && parsed.protocol !== "https:") return null;
23
+ const emptyQuery = !parsed.search && parsed.href.endsWith("?") && !configured.includes("#") ? "?" : "";
24
+ return `${parsed.pathname}${parsed.search}${emptyQuery}`;
25
+ } catch {
26
+ return null;
27
+ }
28
+ }
29
+ //#endregion
30
+ //#region extensions/feishu/src/config-schema.ts
31
+ const ChannelActionsSchema = z.object({
32
+ reactions: z.boolean().optional(),
33
+ sticker: z.boolean().optional()
34
+ }).strict().optional();
35
+ const MAX_STICKER_SETS = 32;
36
+ const MAX_STICKERS_PER_SET = 256;
37
+ function canonicalTextPattern(maxLength) {
38
+ return new RegExp(`^(?!\\s)(?![\\s\\S]*\\s$)[^\\p{Cs}]{1,${maxLength}}$`, "u");
39
+ }
40
+ const FeishuStickerSetSchema = z.record(z.string().regex(FEISHU_EXTERNAL_KEY_PATTERN), z.array(z.string().regex(canonicalTextPattern(64))).min(1).max(8)).refine((set) => Object.keys(set).length <= MAX_STICKERS_PER_SET, { message: `At most ${MAX_STICKERS_PER_SET} stickers per bot set are allowed` }).meta({ maxProperties: MAX_STICKERS_PER_SET });
41
+ const FeishuStickerSetsSchema = z.record(z.string().regex(canonicalTextPattern(128)), FeishuStickerSetSchema).refine((sets) => Object.keys(sets).length <= MAX_STICKER_SETS, { message: `At most ${MAX_STICKER_SETS} bot sticker sets are allowed` }).meta({ maxProperties: MAX_STICKER_SETS });
42
+ const FeishuGroupPolicySchema = z.union([GroupPolicySchema, z.literal("allowall").transform(() => "open")]);
43
+ const FeishuDomainSchema = z.union([z.enum(["feishu", "lark"]), z.string().regex(/^[Hh][Tt][Tt][Pp][Ss]:\/\//).url()]);
44
+ const FeishuConnectionModeSchema = z.enum(["websocket", "webhook"]);
45
+ const FeishuWebhookPathSchema = z.string().refine((value) => normalizeFeishuWebhookPath(value) === value, { message: "webhookPath must be a canonical HTTP request path; run \"openclaw doctor --fix\" to repair it" });
46
+ const TtsOverrideSchema = z.object({
47
+ auto: z.enum([
48
+ "off",
49
+ "always",
50
+ "inbound",
51
+ "tagged"
52
+ ]).optional(),
53
+ enabled: z.boolean().optional(),
54
+ mode: z.enum(["final", "all"]).optional(),
55
+ provider: z.string().optional(),
56
+ persona: z.string().optional(),
57
+ personas: z.record(z.string(), z.record(z.string(), z.unknown())).optional(),
58
+ summaryModel: z.string().optional(),
59
+ modelOverrides: z.record(z.string(), z.unknown()).optional(),
60
+ providers: z.record(z.string(), z.record(z.string(), z.unknown())).optional(),
61
+ prefsPath: z.string().optional(),
62
+ maxTextLength: z.number().int().min(1).optional(),
63
+ timeoutMs: z.number().int().min(1e3).max(12e4).optional()
64
+ }).strict().optional();
65
+ const ToolPolicySchema = z.object({
66
+ allow: z.array(z.string()).optional(),
67
+ deny: z.array(z.string()).optional()
68
+ }).strict().optional();
69
+ const DmConfigSchema = z.object({
70
+ enabled: z.boolean().optional(),
71
+ systemPrompt: z.string().optional()
72
+ }).strict().optional();
73
+ const MarkdownConfigSchema = z.object({
74
+ mode: z.enum([
75
+ "native",
76
+ "escape",
77
+ "strip"
78
+ ]).optional(),
79
+ tableMode: z.enum([
80
+ "native",
81
+ "ascii",
82
+ "simple"
83
+ ]).optional()
84
+ }).strict().optional();
85
+ const RenderModeSchema = z.enum([
86
+ "auto",
87
+ "raw",
88
+ "card"
89
+ ]).optional();
90
+ const BlockStreamingCoalesceSchema = z.object({
91
+ minChars: z.number().int().positive().optional(),
92
+ maxChars: z.number().int().positive().optional(),
93
+ idleMs: z.number().int().nonnegative().optional()
94
+ }).strict().optional();
95
+ const FeishuStreamingSchema = z.object({
96
+ mode: z.enum(["off", "partial"]).optional(),
97
+ chunkMode: z.enum(["length", "newline"]).optional(),
98
+ block: z.object({
99
+ enabled: z.boolean().optional(),
100
+ coalesce: BlockStreamingCoalesceSchema
101
+ }).strict().optional()
102
+ }).strict().optional();
103
+ const ChannelHeartbeatVisibilitySchema = z.object({
104
+ visibility: z.enum(["visible", "hidden"]).optional(),
105
+ intervalMs: z.number().int().positive().optional()
106
+ }).strict().optional();
107
+ /**
108
+ * Dynamic agent creation configuration.
109
+ * When enabled, a new agent is created for each unique DM user.
110
+ */
111
+ const DynamicAgentCreationSchema = z.object({
112
+ enabled: z.boolean().optional(),
113
+ workspaceTemplate: z.string().optional(),
114
+ agentDirTemplate: z.string().optional(),
115
+ maxAgents: z.number().int().positive().optional()
116
+ }).strict().optional();
117
+ /**
118
+ * Feishu tools configuration.
119
+ * Controls which tool categories are enabled.
120
+ *
121
+ * Dependencies:
122
+ * - wiki requires doc (wiki content is edited via doc tools)
123
+ * - perm can work independently but is typically used with drive
124
+ */
125
+ const FeishuToolsConfigSchema = z.object({
126
+ doc: z.boolean().optional(),
127
+ chat: z.boolean().optional(),
128
+ wiki: z.boolean().optional(),
129
+ drive: z.boolean().optional(),
130
+ perm: z.boolean().optional(),
131
+ scopes: z.boolean().optional(),
132
+ bitable: z.boolean().optional()
133
+ }).strict().optional();
134
+ /**
135
+ * Group session scope for routing Feishu group messages.
136
+ * - "group" (default): one session per group chat
137
+ * - "group_sender": one session per (group + sender)
138
+ * - "group_topic": one session per group topic thread (falls back to group if no topic)
139
+ * - "group_topic_sender": one session per (group + topic thread + sender),
140
+ * falls back to (group + sender) if no topic
141
+ */
142
+ const GroupSessionScopeSchema = z.enum([
143
+ "group",
144
+ "group_sender",
145
+ "group_topic",
146
+ "group_topic_sender"
147
+ ]).optional();
148
+ /**
149
+ * @deprecated Use groupSessionScope instead.
150
+ *
151
+ * Topic session isolation mode for group chats.
152
+ * - "disabled" (default): All messages in a group share one session
153
+ * - "enabled": Messages in different topics get separate sessions
154
+ *
155
+ * Topic routing uses Feishu topic-group `thread_id` when the event identifies a
156
+ * native topic group, and keeps `root_id` precedence for normal groups so
157
+ * reply-created threads stay on the initiating message session.
158
+ */
159
+ const TopicSessionModeSchema = z.enum(["disabled", "enabled"]).optional();
160
+ const ReactionNotificationModeSchema = z.enum([
161
+ "off",
162
+ "own",
163
+ "all"
164
+ ]).optional();
165
+ /**
166
+ * Reply-in-thread mode for group chats.
167
+ * - "disabled" (default): Bot replies are normal inline replies
168
+ * - "enabled": Bot replies create or continue a Feishu topic thread
169
+ *
170
+ * When enabled, the Feishu reply API is called with `reply_in_thread: true`,
171
+ * causing the reply to appear as a topic (话题) under the original message.
172
+ */
173
+ const ReplyInThreadSchema = z.enum(["disabled", "enabled"]).optional();
174
+ const FeishuGroupSchema = buildGroupEntrySchema({
175
+ tools: ToolPolicySchema,
176
+ groupSessionScope: GroupSessionScopeSchema,
177
+ topicSessionMode: TopicSessionModeSchema,
178
+ replyInThread: ReplyInThreadSchema
179
+ }).omit({ toolsBySender: true });
180
+ const FeishuSharedConfigShape = {
181
+ webhookHost: z.string().optional(),
182
+ webhookPort: z.number().int().positive().optional(),
183
+ capabilities: z.array(z.string()).optional(),
184
+ markdown: MarkdownConfigSchema,
185
+ configWrites: z.boolean().optional(),
186
+ contextVisibility: ContextVisibilityModeSchema.optional(),
187
+ replyToMode: ReplyToModeSchema.optional(),
188
+ responsePrefix: z.string().optional(),
189
+ dmPolicy: DmPolicySchema.optional(),
190
+ allowFrom: z.array(z.union([z.string(), z.number()])).optional(),
191
+ groupPolicy: FeishuGroupPolicySchema.optional(),
192
+ groupAllowFrom: z.array(z.union([z.string(), z.number()])).optional(),
193
+ groupSenderAllowFrom: z.array(z.union([z.string(), z.number()])).optional(),
194
+ requireMention: z.boolean().optional(),
195
+ groups: z.record(z.string(), FeishuGroupSchema.optional()).optional(),
196
+ historyLimit: z.number().int().min(0).optional(),
197
+ dmHistoryLimit: z.number().int().min(0).optional(),
198
+ dms: z.record(z.string(), DmConfigSchema).optional(),
199
+ textChunkLimit: z.number().int().positive().optional(),
200
+ mediaMaxMb: z.number().positive().optional(),
201
+ httpTimeoutMs: z.number().int().positive().max(3e5).optional(),
202
+ heartbeatVisibility: ChannelHeartbeatVisibilitySchema,
203
+ renderMode: RenderModeSchema,
204
+ streaming: FeishuStreamingSchema,
205
+ tools: FeishuToolsConfigSchema,
206
+ actions: ChannelActionsSchema,
207
+ replyInThread: ReplyInThreadSchema,
208
+ reactionNotifications: ReactionNotificationModeSchema,
209
+ typingIndicator: z.boolean().optional(),
210
+ resolveSenderNames: z.boolean().optional(),
211
+ allowBots: z.boolean().optional(),
212
+ vcAutoJoin: z.boolean().optional(),
213
+ tts: TtsOverrideSchema
214
+ };
215
+ /**
216
+ * Per-account configuration.
217
+ * All fields are optional - missing fields inherit from top-level config.
218
+ */
219
+ const FeishuAccountConfigSchema = z.object({
220
+ enabled: z.boolean().optional(),
221
+ name: z.string().optional(),
222
+ appId: z.string().optional(),
223
+ appSecret: buildSecretInputSchema().optional(),
224
+ encryptKey: buildSecretInputSchema().optional(),
225
+ verificationToken: buildSecretInputSchema().optional(),
226
+ domain: FeishuDomainSchema.optional(),
227
+ connectionMode: FeishuConnectionModeSchema.optional(),
228
+ webhookPath: FeishuWebhookPathSchema.optional(),
229
+ ...FeishuSharedConfigShape,
230
+ groupSessionScope: GroupSessionScopeSchema,
231
+ topicSessionMode: TopicSessionModeSchema
232
+ }).strict();
233
+ const FeishuConfigSchemaBase = z.object({
234
+ enabled: z.boolean().optional(),
235
+ defaultAccount: z.string().optional(),
236
+ stickerSets: FeishuStickerSetsSchema.optional(),
237
+ appId: z.string().optional(),
238
+ appSecret: buildSecretInputSchema().optional(),
239
+ encryptKey: buildSecretInputSchema().optional(),
240
+ verificationToken: buildSecretInputSchema().optional(),
241
+ domain: FeishuDomainSchema.optional().default("feishu"),
242
+ connectionMode: FeishuConnectionModeSchema.optional().default("websocket"),
243
+ webhookPath: FeishuWebhookPathSchema.optional().default(DEFAULT_FEISHU_WEBHOOK_PATH),
244
+ ...FeishuSharedConfigShape,
245
+ dmPolicy: DmPolicySchema.optional().default("pairing"),
246
+ reactionNotifications: ReactionNotificationModeSchema.optional().default("own"),
247
+ groupPolicy: FeishuGroupPolicySchema.optional().default("allowlist"),
248
+ requireMention: z.boolean().optional(),
249
+ groupSessionScope: GroupSessionScopeSchema,
250
+ topicSessionMode: TopicSessionModeSchema,
251
+ dynamicAgentCreation: DynamicAgentCreationSchema,
252
+ typingIndicator: z.boolean().optional().default(true),
253
+ resolveSenderNames: z.boolean().optional().default(true)
254
+ }).strict();
255
+ const FeishuConfigSchema = buildMultiAccountChannelSchema(FeishuConfigSchemaBase, {
256
+ accountSchema: FeishuAccountConfigSchema,
257
+ optionalAccount: true
258
+ }).superRefine((value, ctx) => {
259
+ const defaultAccount = value.defaultAccount?.trim();
260
+ if (defaultAccount && value.accounts && Object.keys(value.accounts).length > 0) {
261
+ const normalizedDefaultAccount = normalizeAccountId(defaultAccount);
262
+ if (!Object.hasOwn(value.accounts, normalizedDefaultAccount)) ctx.addIssue({
263
+ code: z.ZodIssueCode.custom,
264
+ path: ["defaultAccount"],
265
+ message: `channels.feishu.defaultAccount="${defaultAccount}" does not match a configured account key`
266
+ });
267
+ }
268
+ const defaultConnectionMode = value.connectionMode ?? "websocket";
269
+ const defaultVerificationTokenConfigured = hasConfiguredSecretInput$1(value.verificationToken);
270
+ const defaultEncryptKeyConfigured = hasConfiguredSecretInput$1(value.encryptKey);
271
+ if (defaultConnectionMode === "webhook") {
272
+ if (!defaultVerificationTokenConfigured) ctx.addIssue({
273
+ code: z.ZodIssueCode.custom,
274
+ path: ["verificationToken"],
275
+ message: "channels.feishu.connectionMode=\"webhook\" requires channels.feishu.verificationToken"
276
+ });
277
+ if (!defaultEncryptKeyConfigured) ctx.addIssue({
278
+ code: z.ZodIssueCode.custom,
279
+ path: ["encryptKey"],
280
+ message: "channels.feishu.connectionMode=\"webhook\" requires channels.feishu.encryptKey"
281
+ });
282
+ }
283
+ for (const [accountId, account] of Object.entries(value.accounts ?? {})) {
284
+ if (!account) continue;
285
+ if ((account.connectionMode ?? defaultConnectionMode) !== "webhook") continue;
286
+ const accountVerificationTokenConfigured = hasConfiguredSecretInput$1(account.verificationToken) || defaultVerificationTokenConfigured;
287
+ const accountEncryptKeyConfigured = hasConfiguredSecretInput$1(account.encryptKey) || defaultEncryptKeyConfigured;
288
+ if (!accountVerificationTokenConfigured) ctx.addIssue({
289
+ code: z.ZodIssueCode.custom,
290
+ path: [
291
+ "accounts",
292
+ accountId,
293
+ "verificationToken"
294
+ ],
295
+ message: `channels.feishu.accounts.${accountId}.connectionMode="webhook" requires a verificationToken (account-level or top-level)`
296
+ });
297
+ if (!accountEncryptKeyConfigured) ctx.addIssue({
298
+ code: z.ZodIssueCode.custom,
299
+ path: [
300
+ "accounts",
301
+ accountId,
302
+ "encryptKey"
303
+ ],
304
+ message: `channels.feishu.accounts.${accountId}.connectionMode="webhook" requires an encryptKey (account-level or top-level)`
305
+ });
306
+ }
307
+ if (value.dmPolicy === "open") {
308
+ if (!(value.allowFrom ?? []).some((entry) => String(entry).trim() === "*")) ctx.addIssue({
309
+ code: z.ZodIssueCode.custom,
310
+ path: ["allowFrom"],
311
+ message: "channels.feishu.dmPolicy=\"open\" requires channels.feishu.allowFrom to include \"*\""
312
+ });
313
+ }
314
+ });
315
+ const FeishuChannelConfigSchema = buildChannelConfigSchema(FeishuConfigSchema, { jsonSchemaMode: "input" });
316
+ //#endregion
317
+ //#region extensions/feishu/src/doctor-contract.ts
318
+ const streamingAliasMigration = defineChannelAliasMigration({
319
+ channelId: "feishu",
320
+ streaming: { defaultMode: "partial" },
321
+ accountStreamingReplacesRoot: true
322
+ });
323
+ const LEGACY_COALESCE_FIELDS = [
324
+ "enabled",
325
+ "minDelayMs",
326
+ "maxDelayMs"
327
+ ];
328
+ const LEGACY_HEARTBEAT_FIELDS = ["visibility", "intervalMs"];
329
+ const toolsBaseMigration = defineKeyMoveMigration({
330
+ from: ["tools", "base"],
331
+ to: ["tools", "bitable"],
332
+ match: (value) => typeof value === "boolean",
333
+ sourceOwn: false
334
+ });
335
+ function sanitizeLegacyHeartbeatFields(params) {
336
+ const heartbeat = asObjectRecord(params.entry.heartbeat);
337
+ if (!heartbeat || Object.keys(heartbeat).length > 0 && !LEGACY_HEARTBEAT_FIELDS.some((field) => Object.hasOwn(heartbeat, field))) return {
338
+ entry: params.entry,
339
+ changed: false
340
+ };
341
+ const next = { ...params.entry };
342
+ delete next.heartbeat;
343
+ params.changes.push(`Removed ${params.pathPrefix}.heartbeat (legacy Feishu fields were never read by runtime).`);
344
+ return {
345
+ entry: next,
346
+ changed: true
347
+ };
348
+ }
349
+ function sanitizeLegacyCoalesceFields(params) {
350
+ const streaming = asObjectRecord(params.entry.streaming);
351
+ const block = asObjectRecord(streaming?.block);
352
+ const coalesce = asObjectRecord(block?.coalesce);
353
+ if (!streaming || !block || !coalesce) return {
354
+ entry: params.entry,
355
+ changed: false
356
+ };
357
+ const removed = LEGACY_COALESCE_FIELDS.filter((field) => coalesce[field] !== void 0);
358
+ if (removed.length === 0) return {
359
+ entry: params.entry,
360
+ changed: false
361
+ };
362
+ const nextCoalesce = { ...coalesce };
363
+ for (const field of removed) delete nextCoalesce[field];
364
+ params.changes.push(`Removed ${params.pathPrefix}.streaming.block.coalesce.{${removed.join(",")}} (legacy Feishu-only fields; block delivery reads minChars/maxChars/idleMs).`);
365
+ return {
366
+ entry: {
367
+ ...params.entry,
368
+ streaming: {
369
+ ...streaming,
370
+ block: {
371
+ ...block,
372
+ coalesce: nextCoalesce
373
+ }
374
+ }
375
+ },
376
+ changed: true
377
+ };
378
+ }
379
+ function hasLegacyWebhookPath(value) {
380
+ const path = asObjectRecord(value)?.webhookPath;
381
+ return typeof path === "string" && normalizeFeishuWebhookPath(path) !== path;
382
+ }
383
+ function normalizeLegacyWebhookPath(params) {
384
+ const path = params.entry.webhookPath;
385
+ if (typeof path !== "string") return {
386
+ entry: params.entry,
387
+ changed: false
388
+ };
389
+ const normalized = normalizeFeishuWebhookPath(path);
390
+ const canonical = normalized ?? "/feishu/events";
391
+ if (canonical === path) return {
392
+ entry: params.entry,
393
+ changed: false
394
+ };
395
+ params.changes.push(normalized === null ? `Reset invalid ${params.pathPrefix}.webhookPath to ${DEFAULT_FEISHU_WEBHOOK_PATH}.` : `Normalized ${params.pathPrefix}.webhookPath to its HTTP request path.`);
396
+ return {
397
+ entry: {
398
+ ...params.entry,
399
+ webhookPath: canonical
400
+ },
401
+ changed: true
402
+ };
403
+ }
404
+ function normalizeFeishuLegacyConfigEntries(cfg, changes) {
405
+ return normalizeChannelConfigEntries({
406
+ cfg,
407
+ channelId: "feishu",
408
+ changes,
409
+ normalizeEntry: (params) => {
410
+ const tools = toolsBaseMigration.normalize(params);
411
+ const coalesce = sanitizeLegacyCoalesceFields({
412
+ ...params,
413
+ entry: tools.entry
414
+ });
415
+ const heartbeat = sanitizeLegacyHeartbeatFields({
416
+ ...params,
417
+ entry: coalesce.entry
418
+ });
419
+ const webhook = normalizeLegacyWebhookPath({
420
+ ...params,
421
+ entry: heartbeat.entry
422
+ });
423
+ return {
424
+ entry: webhook.entry,
425
+ changed: tools.changed || coalesce.changed || heartbeat.changed || webhook.changed
426
+ };
427
+ }
428
+ }).config;
429
+ }
430
+ const feishuStrayEntryConfigMigration = defineStrayPluginEntryConfigMigration({
431
+ pluginId: "feishu",
432
+ channelId: "feishu",
433
+ validateMergedChannelConfig: (merged) => FeishuConfigSchema.safeParse(merged).success
434
+ });
435
+ const legacyConfigRules = [
436
+ ...streamingAliasMigration.legacyConfigRules,
437
+ feishuStrayEntryConfigMigration.legacyConfigRule,
438
+ {
439
+ path: ["channels", "feishu"],
440
+ message: "channels.feishu[.accounts.<id>].webhookPath must be a canonical HTTP request path; run \"openclaw doctor --fix\".",
441
+ match: (value) => {
442
+ const entry = asObjectRecord(value);
443
+ return hasLegacyWebhookPath(entry) || hasLegacyAccountStreamingAliases(entry?.accounts, hasLegacyWebhookPath);
444
+ }
445
+ },
446
+ {
447
+ path: ["channels", "feishu"],
448
+ message: "channels.feishu[.accounts.<id>].tools.base is legacy; use tools.bitable. Run \"openclaw doctor --fix\".",
449
+ match: (value) => {
450
+ const entry = asObjectRecord(value);
451
+ return toolsBaseMigration.hasLegacy(entry) || hasLegacyAccountStreamingAliases(entry?.accounts, toolsBaseMigration.hasLegacy);
452
+ }
453
+ }
454
+ ];
455
+ function normalizeCompatibilityConfig({ cfg }) {
456
+ const aliases = streamingAliasMigration.normalizeChannelConfig({ cfg });
457
+ const entries = normalizeFeishuLegacyConfigEntries(aliases.config, aliases.changes);
458
+ const stray = feishuStrayEntryConfigMigration.normalizeConfig({ cfg: entries });
459
+ return {
460
+ config: stray.config,
461
+ changes: [...aliases.changes, ...stray.changes]
462
+ };
463
+ }
464
+ //#endregion
465
+ export { normalizeFeishuWebhookPath as a, DEFAULT_FEISHU_WEBHOOK_PATH as i, normalizeCompatibilityConfig as n, normalizeFeishuExternalKey as o, FeishuChannelConfigSchema as r, legacyConfigRules as t };