@openclaw/feishu 2026.7.2-beta.7 → 2026.8.1-beta.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 (35) hide show
  1. package/dist/accounts-CCCdMen2.js +203 -0
  2. package/dist/api.js +52 -51
  3. package/dist/{channel-CScE82zY.js → channel-Dzb5jv3i.js} +53 -30
  4. package/dist/channel-plugin-api.js +1 -1
  5. package/dist/{channel.runtime-B21e1E0r.js → channel.runtime-CoPaHQQ6.js} +181 -142
  6. package/dist/{client-Dcbs6vml.js → client-Hp7uo_cl.js} +27 -16
  7. package/dist/contract-api.js +2 -2
  8. package/dist/{conversation-id-BeJL-wq7.js → conversation-id-VYgGQ-GX.js} +11 -15
  9. package/dist/doctor-contract-api.js +1 -1
  10. package/dist/doctor-contract-bEQXIXyP.js +158 -0
  11. package/dist/{drive-hSi_Utp0.js → drive-B0JkoiRj.js} +318 -41
  12. package/dist/{media-CEUraFOR.js → media-BjSy8fiy.js} +298 -182
  13. package/dist/{monitor-DngbaA6a.js → monitor-CeHCfROt.js} +3 -3
  14. package/dist/{monitor.account-BnTWCw55.js → monitor.account-DwdqexLU.js} +237 -158
  15. package/dist/{monitor.startup-CqH2tiJq.js → monitor.startup-BGErejNH.js} +1 -1
  16. package/dist/{probe-p3POS2RN.js → probe-DVpy58s0.js} +2 -2
  17. package/dist/security-audit-D6Fz2h6p.js +23 -0
  18. package/dist/{send-result-B9_BpUPx.js → send-result-DEAycmOk.js} +18 -9
  19. package/dist/session-binding-contract-api.js +1 -1
  20. package/dist/{session-conversation-BksWrfzm.js → session-conversation-DFCIvQK-.js} +1 -1
  21. package/dist/session-key-api.js +1 -1
  22. package/dist/setup-api.js +1 -1
  23. package/dist/{subagent-hooks-Cx1cX7rW.js → subagent-hooks-B867acTt.js} +2 -2
  24. package/dist/subagent-hooks-api.js +1 -1
  25. package/dist/{thread-bindings-N3wkgkIN.js → thread-bindings-Itvfmx6_.js} +6 -3
  26. package/openclaw.plugin.json +143 -1
  27. package/package.json +4 -4
  28. package/skills/feishu-doc/SKILL.md +20 -195
  29. package/skills/feishu-doc/references/block-types.md +9 -14
  30. package/skills/feishu-drive/SKILL.md +16 -102
  31. package/skills/feishu-perm/SKILL.md +10 -110
  32. package/skills/feishu-wiki/SKILL.md +13 -109
  33. package/dist/accounts-u9X5Wsan.js +0 -469
  34. package/dist/doctor-contract-BiD9tyIv.js +0 -102
  35. package/dist/security-audit-D7WK_BHh.js +0 -11
@@ -1,21 +1,22 @@
1
- import { d as isRecord$1, h as requestFeishuApi, s as resolveFeishuRuntimeAccount } from "./accounts-u9X5Wsan.js";
1
+ import { s as resolveFeishuRuntimeAccount } from "./accounts-CCCdMen2.js";
2
2
  import { i as resolveReceiveIdType, r as normalizeFeishuTarget } from "./targets-BUjQ1TcA.js";
3
- import { c as assertFeishuPostWithinEnvelope, f as materializeFeishuPostMarkdownSoftBreaks, i as toFeishuSendResult, l as buildFeishuPostMessageContent, o as resolveFeishuCardTemplate, r as resolveFeishuReceiptKind, t as assertFeishuMessageApiSuccess } from "./send-result-B9_BpUPx.js";
4
- import { r as createFeishuClient } from "./client-Dcbs6vml.js";
3
+ import { c as assertFeishuPostWithinEnvelope, f as materializeFeishuPostMarkdownSoftBreaks, i as toFeishuSendResult, l as buildFeishuPostMessageContent, o as resolveFeishuCardTemplate, r as resolveFeishuReceiptKind, t as assertFeishuMessageApiSuccess } from "./send-result-DEAycmOk.js";
4
+ import { r as createFeishuClient } from "./client-Hp7uo_cl.js";
5
5
  import { t as getFeishuRuntime } from "./runtime-C5JxBWZp.js";
6
+ import { m as requestFeishuApi } from "./drive-B0JkoiRj.js";
7
+ import { PlatformMessageNotDispatchedError } from "openclaw/plugin-sdk/error-runtime";
6
8
  import { parseStrictNonNegativeInteger } from "openclaw/plugin-sdk/number-runtime";
7
9
  import { isRecord, normalizeLowercaseStringOrEmpty } from "openclaw/plugin-sdk/string-coerce-runtime";
8
10
  import { convertMarkdownTables } from "openclaw/plugin-sdk/text-chunking";
9
11
  import fs from "node:fs";
10
12
  import path from "node:path";
11
- import { PlatformMessageNotDispatchedError } from "openclaw/plugin-sdk/error-runtime";
12
- import { MEDIA_FFMPEG_MAX_AUDIO_DURATION_SECS, runFfmpeg, runFfprobe } from "openclaw/plugin-sdk/media-runtime";
13
+ import { MEDIA_FFMPEG_MAX_AUDIO_DURATION_SECS, buildOutboundMediaLoadOptions, runFfmpeg, runFfprobe } from "openclaw/plugin-sdk/media-runtime";
14
+ import { readRegularFile, writeExternalFileWithinRoot } from "openclaw/plugin-sdk/security-runtime";
13
15
  import { logVerbose } from "openclaw/plugin-sdk/runtime-env";
14
16
  import { isBlockedHostnameOrIp, resolvePinnedHostnameWithPolicy } from "openclaw/plugin-sdk/ssrf-runtime";
15
17
  import { resolveMarkdownTableMode } from "openclaw/plugin-sdk/markdown-table-runtime";
16
- import { readRegularFile, writeExternalFileWithinRoot } from "openclaw/plugin-sdk/security-runtime";
17
18
  import { Readable } from "node:stream";
18
- import { mediaKindFromMime } from "openclaw/plugin-sdk/media-mime";
19
+ import { detectMime, mediaKindFromMime } from "openclaw/plugin-sdk/media-mime";
19
20
  import { saveMediaBuffer, saveMediaStream } from "openclaw/plugin-sdk/media-store";
20
21
  import { resolvePreferredOpenClawTmpDir, withTempDownloadPath, withTempWorkspace } from "openclaw/plugin-sdk/temp-path";
21
22
  //#region extensions/feishu/src/identity-header.ts
@@ -147,66 +148,6 @@ function resolveFeishuSendTarget(params) {
147
148
  };
148
149
  }
149
150
  //#endregion
150
- //#region extensions/feishu/src/types.ts
151
- function isFeishuGroupChatType(chatType) {
152
- return chatType === "group" || chatType === "topic_group";
153
- }
154
- //#endregion
155
- //#region extensions/feishu/src/mention.ts
156
- function isFeishuBroadcastMention(mention) {
157
- const normalizedKey = mention.key?.trim().toLowerCase();
158
- if (normalizedKey === "@all" || normalizedKey === "@_all") return true;
159
- return [
160
- mention.id?.open_id,
161
- mention.id?.user_id,
162
- mention.id?.union_id
163
- ].some((id) => id?.trim().toLowerCase() === "all");
164
- }
165
- /**
166
- * Extract mention targets from message event (excluding the bot itself)
167
- */
168
- function extractMentionTargets(event, botOpenId) {
169
- return (event.message.mentions ?? []).filter((m) => {
170
- if (isFeishuBroadcastMention(m)) return false;
171
- if (m.id.open_id === botOpenId) return false;
172
- return Boolean(m.id.open_id);
173
- }).map((m) => ({
174
- openId: m.id.open_id,
175
- name: m.name,
176
- key: m.key
177
- }));
178
- }
179
- /**
180
- * Check if message is a mention forward request
181
- * Rules:
182
- * - Group: message mentions bot + at least one other user
183
- * - DM: message mentions any user (no need to mention bot)
184
- */
185
- function isMentionForwardRequest(event, botOpenId) {
186
- const mentions = event.message.mentions ?? [];
187
- if (mentions.length === 0) return false;
188
- const normalizedBotOpenId = botOpenId?.trim();
189
- if (!normalizedBotOpenId) return false;
190
- const isDirectMessage = !isFeishuGroupChatType(event.message.chat_type);
191
- const userMentions = mentions.filter((m) => !isFeishuBroadcastMention(m));
192
- const hasOtherMention = userMentions.some((m) => m.id.open_id !== normalizedBotOpenId);
193
- if (isDirectMessage) return hasOtherMention;
194
- return userMentions.some((m) => m.id.open_id === normalizedBotOpenId) && hasOtherMention;
195
- }
196
- /**
197
- * Format @mention for card message (lark_md)
198
- */
199
- function formatMentionForCard(target) {
200
- return `<at id=${target.openId}></at>`;
201
- }
202
- /**
203
- * Build card content with @mentions (Markdown format)
204
- */
205
- function buildMentionedCardContent(targets, message) {
206
- if (targets.length === 0) return message;
207
- return `${targets.map((t) => formatMentionForCard(t)).join(" ")} ${message}`;
208
- }
209
- //#endregion
210
151
  //#region extensions/feishu/src/post.ts
211
152
  const FALLBACK_POST_TEXT = "[Rich text message]";
212
153
  const MARKDOWN_SPECIAL_CHARS = /([\\`*_{}[\]()#+\-!|>~])/g;
@@ -233,7 +174,7 @@ function sanitizeFenceLanguage(language) {
233
174
  }
234
175
  function renderTextElement(element) {
235
176
  const text = toStringOrEmpty(element.text);
236
- const style = isRecord$1(element.style) ? element.style : void 0;
177
+ const style = isRecord(element.style) ? element.style : void 0;
237
178
  if (isStyleEnabled(style, "code")) return wrapInlineCode(text);
238
179
  let rendered = escapeMarkdownText(text);
239
180
  if (!rendered) return "";
@@ -264,7 +205,7 @@ function renderCodeBlockElement(element) {
264
205
  return `\`\`\`${language}\n${code}${code.endsWith("\n") ? "" : "\n"}\`\`\``;
265
206
  }
266
207
  function renderElement(element, imageKeys, mediaKeys, mentionedOpenIds, renderMediaPlaceholders) {
267
- if (!isRecord$1(element)) return escapeMarkdownText(toStringOrEmpty(element));
208
+ if (!isRecord(element)) return escapeMarkdownText(toStringOrEmpty(element));
268
209
  switch (normalizeLowercaseStringOrEmpty(toStringOrEmpty(element.tag))) {
269
210
  case "text": return renderTextElement(element);
270
211
  case "a": return renderLinkElement(element);
@@ -305,7 +246,7 @@ function renderElement(element, imageKeys, mediaKeys, mentionedOpenIds, renderMe
305
246
  }
306
247
  }
307
248
  function toPostPayload(candidate) {
308
- if (!isRecord$1(candidate) || !Array.isArray(candidate.content)) return null;
249
+ if (!isRecord(candidate) || !Array.isArray(candidate.content)) return null;
309
250
  return {
310
251
  title: toStringOrEmpty(candidate.title),
311
252
  content: candidate.content
@@ -314,7 +255,7 @@ function toPostPayload(candidate) {
314
255
  function resolveLocalePayload(candidate) {
315
256
  const direct = toPostPayload(candidate);
316
257
  if (direct) return direct;
317
- if (!isRecord$1(candidate)) return null;
258
+ if (!isRecord(candidate)) return null;
318
259
  for (const value of Object.values(candidate)) {
319
260
  const localePayload = toPostPayload(value);
320
261
  if (localePayload) return localePayload;
@@ -324,7 +265,7 @@ function resolveLocalePayload(candidate) {
324
265
  function resolvePostPayload(parsed) {
325
266
  const direct = toPostPayload(parsed);
326
267
  if (direct) return direct;
327
- if (!isRecord$1(parsed)) return null;
268
+ if (!isRecord(parsed)) return null;
328
269
  const wrappedPost = resolveLocalePayload(parsed.post);
329
270
  if (wrappedPost) return wrappedPost;
330
271
  return resolveLocalePayload(parsed);
@@ -364,64 +305,9 @@ function parsePostContent(content, options = {}) {
364
305
  }
365
306
  }
366
307
  //#endregion
367
- //#region extensions/feishu/src/send.ts
368
- const WITHDRAWN_REPLY_ERROR_CODES = /* @__PURE__ */ new Set([230011, 231003]);
308
+ //#region extensions/feishu/src/interactive-message-content.ts
369
309
  const INTERACTIVE_CARD_FALLBACK_TEXT = "[Interactive Card]";
370
310
  const POST_FALLBACK_TEXT = "[Rich text message]";
371
- function shouldFallbackFromReplyTarget(response) {
372
- if (response.code !== void 0 && WITHDRAWN_REPLY_ERROR_CODES.has(response.code)) return true;
373
- const msg = normalizeLowercaseStringOrEmpty(response.msg);
374
- return msg.includes("withdrawn") || msg.includes("not found");
375
- }
376
- /** Check whether a thrown error indicates a withdrawn/not-found reply target. */
377
- function isWithdrawnReplyError(err) {
378
- if (typeof err !== "object" || err === null) return false;
379
- const code = err.code;
380
- if (typeof code === "number" && WITHDRAWN_REPLY_ERROR_CODES.has(code)) return true;
381
- const response = err.response;
382
- if (typeof response?.data?.code === "number" && WITHDRAWN_REPLY_ERROR_CODES.has(response.data.code)) return true;
383
- const cause = err.cause;
384
- if (cause && cause !== err) return isWithdrawnReplyError(cause);
385
- return false;
386
- }
387
- /** Send a direct message as a fallback when a reply target is unavailable. */
388
- async function sendFallbackDirect(client, params, errorPrefix) {
389
- const response = await requestFeishuApi(() => client.im.message.create({
390
- params: { receive_id_type: params.receiveIdType },
391
- data: {
392
- receive_id: params.receiveId,
393
- content: params.content,
394
- msg_type: params.msgType
395
- }
396
- }), errorPrefix, { includeNestedErrorLogId: true });
397
- assertFeishuMessageApiSuccess(response, errorPrefix);
398
- return toFeishuSendResult(response, params.receiveId, resolveFeishuReceiptKind(params.msgType));
399
- }
400
- async function sendReplyOrFallbackDirect(client, params) {
401
- if (!params.replyToMessageId) return sendFallbackDirect(client, params.directParams, params.directErrorPrefix);
402
- const replyTargetFallbackError = params.replyInThread && params.allowTopLevelReplyFallback !== true ? /* @__PURE__ */ new Error("Feishu thread reply failed: reply target is unavailable and cannot safely fall back to a top-level send.") : null;
403
- let response;
404
- try {
405
- response = await requestFeishuApi(() => client.im.message.reply({
406
- path: { message_id: params.replyToMessageId },
407
- data: {
408
- content: params.content,
409
- msg_type: params.msgType,
410
- ...params.replyInThread ? { reply_in_thread: true } : {}
411
- }
412
- }), params.replyErrorPrefix, { includeNestedErrorLogId: true });
413
- } catch (err) {
414
- if (!isWithdrawnReplyError(err)) throw err;
415
- if (replyTargetFallbackError) throw replyTargetFallbackError;
416
- return sendFallbackDirect(client, params.directParams, params.directErrorPrefix);
417
- }
418
- if (shouldFallbackFromReplyTarget(response)) {
419
- if (replyTargetFallbackError) throw replyTargetFallbackError;
420
- return sendFallbackDirect(client, params.directParams, params.directErrorPrefix);
421
- }
422
- assertFeishuMessageApiSuccess(response, params.replyErrorPrefix);
423
- return toFeishuSendResult(response, params.directParams.receiveId, resolveFeishuReceiptKind(params.msgType));
424
- }
425
311
  function normalizeCardTemplateVariable(value) {
426
312
  if (typeof value === "string") return value;
427
313
  if (typeof value === "number" || typeof value === "boolean" || typeof value === "bigint") return String(value);
@@ -444,17 +330,79 @@ function applyCardTemplateVariables(text, variables) {
444
330
  return variables.get(variableName) ?? match;
445
331
  });
446
332
  }
333
+ function normalizeInteractiveValue(value, variables) {
334
+ const scalar = normalizeCardTemplateVariable(value);
335
+ if (scalar !== void 0) return applyCardTemplateVariables(scalar, variables);
336
+ if (Array.isArray(value)) return value.map((entry) => normalizeInteractiveValue(entry, variables)).filter(Boolean).join(", ");
337
+ if (!isRecord(value)) return "";
338
+ for (const key of [
339
+ "content",
340
+ "text",
341
+ "label",
342
+ "name",
343
+ "display_name",
344
+ "user_name",
345
+ "user_id",
346
+ "open_id",
347
+ "id",
348
+ "value"
349
+ ]) {
350
+ const text = normalizeInteractiveValue(value[key], variables);
351
+ if (text) return text;
352
+ }
353
+ return "";
354
+ }
355
+ function extractInteractiveTableText(element, variables) {
356
+ if (!Array.isArray(element.columns) || !Array.isArray(element.rows)) return;
357
+ const columns = element.columns.flatMap((column) => {
358
+ if (!isRecord(column) || typeof column.name !== "string") return [];
359
+ return [{
360
+ name: column.name,
361
+ title: typeof column.display_name === "string" ? column.display_name : column.name
362
+ }];
363
+ });
364
+ if (columns.length === 0) return;
365
+ const lines = [columns.map((column) => applyCardTemplateVariables(column.title, variables)).join(" | ")];
366
+ for (const row of element.rows) {
367
+ if (!isRecord(row)) continue;
368
+ const cells = columns.map((column) => normalizeInteractiveValue(row[column.name], variables));
369
+ if (cells.some(Boolean)) lines.push(cells.join(" | "));
370
+ }
371
+ return lines.join("\n");
372
+ }
447
373
  function extractInteractiveElementText(element, variables) {
448
374
  if (!isRecord(element)) return;
449
375
  const tag = typeof element.tag === "string" ? element.tag : "";
450
376
  const text = isRecord(element.text) ? element.text : void 0;
451
- if (tag === "div" && typeof text?.content === "string") return applyCardTemplateVariables(text.content, variables);
377
+ if (tag === "div") {
378
+ const parts = [normalizeInteractiveValue(element.text, variables)];
379
+ if (Array.isArray(element.fields)) parts.push(extractInteractiveElementsText(element.fields, variables));
380
+ return parts.filter(Boolean).join("\n") || void 0;
381
+ }
452
382
  if ((tag === "markdown" || tag === "lark_md") && typeof element.content === "string") return applyCardTemplateVariables(element.content, variables);
383
+ if ((tag === "text" || tag === "a" || tag === "button") && element.text !== void 0) return normalizeInteractiveValue(element.text, variables) || void 0;
384
+ if (tag === "at") {
385
+ const mention = normalizeInteractiveValue(element.user_name ?? element.user_id, variables);
386
+ return mention ? mention.startsWith("@") ? mention : `@${mention}` : void 0;
387
+ }
453
388
  if (tag === "plain_text" && typeof element.content === "string") return applyCardTemplateVariables(element.content, variables);
389
+ if (tag === "table") return extractInteractiveTableText(element, variables);
390
+ return [
391
+ element.elements,
392
+ element.columns,
393
+ element.children,
394
+ element.fields,
395
+ element.actions
396
+ ].filter(Array.isArray).map((children) => extractInteractiveElementsText(children, variables)).filter(Boolean).join("\n") || (typeof text?.content === "string" ? text.content : void 0);
454
397
  }
455
398
  function extractInteractiveElementsText(elements, variables) {
456
399
  const texts = [];
457
400
  for (const element of elements) {
401
+ if (Array.isArray(element)) {
402
+ const row = element.map((part) => extractInteractiveElementText(part, variables)).filter((part) => Boolean(part)).join(" ").trim();
403
+ if (row) texts.push(row);
404
+ continue;
405
+ }
458
406
  const text = extractInteractiveElementText(element, variables);
459
407
  if (text !== void 0) texts.push(text);
460
408
  }
@@ -470,18 +418,140 @@ function readInteractiveElementArrays(parsed) {
470
418
  }
471
419
  return elementArrays;
472
420
  }
473
- function parseInteractivePostFallback(parsed) {
474
- const textContent = parsePostContent(JSON.stringify(parsed)).textContent.trim();
475
- return textContent && textContent !== POST_FALLBACK_TEXT ? textContent : void 0;
421
+ function readInteractiveCardTitle(parsed, variables) {
422
+ if (typeof parsed.title === "string") return applyCardTemplateVariables(parsed.title, variables).trim();
423
+ const header = isRecord(parsed.header) ? parsed.header : void 0;
424
+ const title = isRecord(header?.title) ? header.title : void 0;
425
+ return typeof title?.content === "string" ? applyCardTemplateVariables(title.content, variables).trim() : "";
476
426
  }
477
427
  function parseInteractiveCardContent(parsed) {
478
428
  if (!isRecord(parsed)) return INTERACTIVE_CARD_FALLBACK_TEXT;
479
429
  const variables = readCardTemplateVariables(parsed);
430
+ const title = readInteractiveCardTitle(parsed, variables);
480
431
  for (const elements of readInteractiveElementArrays(parsed)) {
481
432
  const text = extractInteractiveElementsText(elements, variables);
482
- if (text) return text;
433
+ if (text) return title ? `${title}\n${text}` : text;
483
434
  }
484
- return parseInteractivePostFallback(parsed) ?? INTERACTIVE_CARD_FALLBACK_TEXT;
435
+ const postText = parsePostContent(JSON.stringify(parsed)).textContent.trim();
436
+ if (postText && postText !== POST_FALLBACK_TEXT) return postText;
437
+ return title || INTERACTIVE_CARD_FALLBACK_TEXT;
438
+ }
439
+ //#endregion
440
+ //#region extensions/feishu/src/types.ts
441
+ function isFeishuGroupChatType(chatType) {
442
+ return chatType === "group" || chatType === "topic_group";
443
+ }
444
+ //#endregion
445
+ //#region extensions/feishu/src/mention.ts
446
+ function isFeishuBroadcastMention(mention) {
447
+ const normalizedKey = mention.key?.trim().toLowerCase();
448
+ if (normalizedKey === "@all" || normalizedKey === "@_all") return true;
449
+ return [
450
+ mention.id?.open_id,
451
+ mention.id?.user_id,
452
+ mention.id?.union_id
453
+ ].some((id) => id?.trim().toLowerCase() === "all");
454
+ }
455
+ /**
456
+ * Extract mention targets from message event (excluding the bot itself)
457
+ */
458
+ function extractMentionTargets(event, botOpenId) {
459
+ return (event.message.mentions ?? []).filter((m) => {
460
+ if (isFeishuBroadcastMention(m)) return false;
461
+ if (m.id.open_id === botOpenId) return false;
462
+ return Boolean(m.id.open_id);
463
+ }).map((m) => ({
464
+ openId: m.id.open_id,
465
+ name: m.name,
466
+ key: m.key
467
+ }));
468
+ }
469
+ /**
470
+ * Check if message is a mention forward request
471
+ * Rules:
472
+ * - Group: message mentions bot + at least one other user
473
+ * - DM: message mentions any user (no need to mention bot)
474
+ */
475
+ function isMentionForwardRequest(event, botOpenId) {
476
+ const mentions = event.message.mentions ?? [];
477
+ if (mentions.length === 0) return false;
478
+ const normalizedBotOpenId = botOpenId?.trim();
479
+ if (!normalizedBotOpenId) return false;
480
+ const isDirectMessage = !isFeishuGroupChatType(event.message.chat_type);
481
+ const userMentions = mentions.filter((m) => !isFeishuBroadcastMention(m));
482
+ const hasOtherMention = userMentions.some((m) => m.id.open_id !== normalizedBotOpenId);
483
+ if (isDirectMessage) return hasOtherMention;
484
+ return userMentions.some((m) => m.id.open_id === normalizedBotOpenId) && hasOtherMention;
485
+ }
486
+ /**
487
+ * Format @mention for card message (lark_md)
488
+ */
489
+ function formatMentionForCard(target) {
490
+ return `<at id=${target.openId}></at>`;
491
+ }
492
+ /**
493
+ * Build card content with @mentions (Markdown format)
494
+ */
495
+ function buildMentionedCardContent(targets, message) {
496
+ if (targets.length === 0) return message;
497
+ return `${targets.map((t) => formatMentionForCard(t)).join(" ")} ${message}`;
498
+ }
499
+ //#endregion
500
+ //#region extensions/feishu/src/send.ts
501
+ const WITHDRAWN_REPLY_ERROR_CODES = /* @__PURE__ */ new Set([230011, 231003]);
502
+ function shouldFallbackFromReplyTarget(response) {
503
+ if (response.code !== void 0 && WITHDRAWN_REPLY_ERROR_CODES.has(response.code)) return true;
504
+ const msg = normalizeLowercaseStringOrEmpty(response.msg);
505
+ return msg.includes("withdrawn") || msg.includes("not found");
506
+ }
507
+ /** Check whether a thrown error indicates a withdrawn/not-found reply target. */
508
+ function isWithdrawnReplyError(err) {
509
+ if (typeof err !== "object" || err === null) return false;
510
+ const code = err.code;
511
+ if (typeof code === "number" && WITHDRAWN_REPLY_ERROR_CODES.has(code)) return true;
512
+ const response = err.response;
513
+ if (typeof response?.data?.code === "number" && WITHDRAWN_REPLY_ERROR_CODES.has(response.data.code)) return true;
514
+ const cause = err.cause;
515
+ if (cause && cause !== err) return isWithdrawnReplyError(cause);
516
+ return false;
517
+ }
518
+ /** Send a direct message as a fallback when a reply target is unavailable. */
519
+ async function sendFallbackDirect(client, params, errorPrefix) {
520
+ const response = await requestFeishuApi(() => client.im.message.create({
521
+ params: { receive_id_type: params.receiveIdType },
522
+ data: {
523
+ receive_id: params.receiveId,
524
+ content: params.content,
525
+ msg_type: params.msgType
526
+ }
527
+ }), errorPrefix, { includeNestedErrorLogId: true });
528
+ assertFeishuMessageApiSuccess(response, errorPrefix);
529
+ return toFeishuSendResult(response, params.receiveId, resolveFeishuReceiptKind(params.msgType), errorPrefix);
530
+ }
531
+ async function sendReplyOrFallbackDirect(client, params) {
532
+ if (!params.replyToMessageId) return sendFallbackDirect(client, params.directParams, params.directErrorPrefix);
533
+ const replyTargetFallbackError = params.replyInThread && params.allowTopLevelReplyFallback !== true ? /* @__PURE__ */ new Error("Feishu thread reply failed: reply target is unavailable and cannot safely fall back to a top-level send.") : null;
534
+ let response;
535
+ try {
536
+ response = await requestFeishuApi(() => client.im.message.reply({
537
+ path: { message_id: params.replyToMessageId },
538
+ data: {
539
+ content: params.content,
540
+ msg_type: params.msgType,
541
+ ...params.replyInThread ? { reply_in_thread: true } : {}
542
+ }
543
+ }), params.replyErrorPrefix, { includeNestedErrorLogId: true });
544
+ } catch (err) {
545
+ if (!isWithdrawnReplyError(err)) throw err;
546
+ if (replyTargetFallbackError) throw replyTargetFallbackError;
547
+ return sendFallbackDirect(client, params.directParams, params.directErrorPrefix);
548
+ }
549
+ if (shouldFallbackFromReplyTarget(response)) {
550
+ if (replyTargetFallbackError) throw replyTargetFallbackError;
551
+ return sendFallbackDirect(client, params.directParams, params.directErrorPrefix);
552
+ }
553
+ assertFeishuMessageApiSuccess(response, params.replyErrorPrefix);
554
+ return toFeishuSendResult(response, params.directParams.receiveId, resolveFeishuReceiptKind(params.msgType), params.replyErrorPrefix);
485
555
  }
486
556
  function parseFeishuMessageContent(rawContent, msgType, messageId) {
487
557
  if (!rawContent) return "";
@@ -518,6 +588,7 @@ function parseFeishuMessageItem(item, fallbackMessageId) {
518
588
  content: parseFeishuMessageContent(rawContent, msgType, item.message_id),
519
589
  contentType: msgType,
520
590
  createTime: parseStrictNonNegativeInteger(item.create_time),
591
+ ...item.root_id ? { rootId: item.root_id } : {},
521
592
  threadId: item.thread_id || void 0
522
593
  };
523
594
  }
@@ -559,29 +630,40 @@ async function listFeishuThreadMessages(params) {
559
630
  accountId
560
631
  });
561
632
  if (!account.configured) throw new Error(`Feishu account "${account.accountId}" not configured`);
562
- const response = await createFeishuClient(account).im.message.list({ params: {
563
- container_id_type: "thread",
564
- container_id: threadId,
565
- sort_type: "ByCreateTimeDesc",
566
- page_size: Math.min(limit + 1, 50),
567
- card_msg_content_type: "user_card_content"
568
- } });
569
- if (response.code !== 0) throw new Error(`Feishu thread list failed: code=${response.code} msg=${response.msg ?? "unknown"}`);
570
- const items = response.data?.items ?? [];
633
+ const client = createFeishuClient(account);
571
634
  const results = [];
572
- for (const item of items) {
573
- if (currentMessageId && item.message_id === currentMessageId) continue;
574
- if (rootMessageId && item.message_id === rootMessageId) continue;
575
- const parsed = parseFeishuMessageItem(item);
576
- results.push({
577
- messageId: parsed.messageId,
578
- senderId: parsed.senderId,
579
- senderType: parsed.senderType,
580
- content: parsed.content,
581
- contentType: parsed.contentType,
582
- createTime: parsed.createTime
583
- });
584
- if (results.length >= limit) break;
635
+ const seenMessageIds = /* @__PURE__ */ new Set();
636
+ const seenPageTokens = /* @__PURE__ */ new Set();
637
+ let pageToken;
638
+ while (results.length < limit) {
639
+ const response = await client.im.message.list({ params: {
640
+ container_id_type: "thread",
641
+ container_id: threadId,
642
+ sort_type: "ByCreateTimeDesc",
643
+ page_size: Math.min(limit + 1, 50),
644
+ ...pageToken ? { page_token: pageToken } : {},
645
+ card_msg_content_type: "user_card_content"
646
+ } });
647
+ if (response.code !== 0) throw new Error(`Feishu thread list failed: code=${response.code} msg=${response.msg ?? "unknown"}`);
648
+ for (const item of response.data?.items ?? []) {
649
+ if (currentMessageId && item.message_id === currentMessageId || rootMessageId && item.message_id === rootMessageId || item.message_id && seenMessageIds.has(item.message_id)) continue;
650
+ const parsed = parseFeishuMessageItem(item);
651
+ if (parsed.messageId) seenMessageIds.add(parsed.messageId);
652
+ results.push({
653
+ messageId: parsed.messageId,
654
+ senderId: parsed.senderId,
655
+ senderType: parsed.senderType,
656
+ content: parsed.content,
657
+ contentType: parsed.contentType,
658
+ createTime: parsed.createTime
659
+ });
660
+ if (results.length >= limit) break;
661
+ }
662
+ if (results.length >= limit || response.data?.has_more !== true) break;
663
+ const nextPageToken = response.data.page_token?.trim();
664
+ if (!nextPageToken || seenPageTokens.has(nextPageToken)) throw new Error(`Feishu thread history pagination returned a ${nextPageToken ? "repeated" : "missing"} page token`);
665
+ seenPageTokens.add(nextPageToken);
666
+ pageToken = nextPageToken;
585
667
  }
586
668
  results.reverse();
587
669
  return results;
@@ -765,9 +847,26 @@ async function sendMarkdownCardFeishu(params) {
765
847
  //#endregion
766
848
  //#region extensions/feishu/src/media.ts
767
849
  const FEISHU_MEDIA_HTTP_TIMEOUT_MS = 12e4;
850
+ const FEISHU_MAX_FILE_UPLOAD_BYTES = 30 * 1024 * 1024;
851
+ const FEISHU_MAX_IMAGE_UPLOAD_BYTES = 10 * 1024 * 1024;
768
852
  const FEISHU_VOICE_FILE_NAME = "voice.ogg";
769
853
  const FEISHU_VOICE_SAMPLE_RATE_HZ = 48e3;
770
854
  const FEISHU_VOICE_BITRATE = "64k";
855
+ const FEISHU_SUPPORTED_IMAGE_CONTENT_TYPES = /* @__PURE__ */ new Set([
856
+ "image/jpeg",
857
+ "image/jpg",
858
+ "image/png",
859
+ "image/gif",
860
+ "image/webp",
861
+ "image/bmp",
862
+ "image/x-ms-bmp",
863
+ "image/tiff",
864
+ "image/tif",
865
+ "image/heic",
866
+ "image/x-icon",
867
+ "image/ico",
868
+ "image/vnd.microsoft.icon"
869
+ ]);
771
870
  const FEISHU_TRANSCODABLE_AUDIO_EXTS = /* @__PURE__ */ new Set([
772
871
  ".aac",
773
872
  ".aiff",
@@ -1035,7 +1134,7 @@ async function sendImageFeishu(params) {
1035
1134
  }
1036
1135
  }), "Feishu image send failed", { includeNestedErrorLogId: true });
1037
1136
  assertFeishuMessageApiSuccess(response, "Feishu image send failed");
1038
- return toFeishuSendResult(response, receiveId, "media");
1137
+ return toFeishuSendResult(response, receiveId, "media", "Feishu image send failed");
1039
1138
  }
1040
1139
  /**
1041
1140
  * Send a file message using a file_key
@@ -1073,7 +1172,7 @@ async function sendFileFeishu(params) {
1073
1172
  }
1074
1173
  }), "Feishu file send failed", { includeNestedErrorLogId: true });
1075
1174
  assertFeishuMessageApiSuccess(response, "Feishu file send failed");
1076
- return toFeishuSendResult(response, receiveId, resolveFeishuReceiptKind(msgType));
1175
+ return toFeishuSendResult(response, receiveId, resolveFeishuReceiptKind(msgType), "Feishu file send failed");
1077
1176
  }
1078
1177
  /**
1079
1178
  * Helper to detect file type from extension
@@ -1095,20 +1194,11 @@ function detectFileType(fileName) {
1095
1194
  default: return "stream";
1096
1195
  }
1097
1196
  }
1098
- function resolveFeishuOutboundMediaKind(params) {
1099
- const { fileName, contentType } = params;
1197
+ async function resolveFeishuOutboundMediaKind(params) {
1198
+ const { buffer, fileName, contentType } = params;
1100
1199
  const ext = normalizeLowercaseStringOrEmpty(path.extname(fileName));
1101
- const mimeKind = mediaKindFromMime(contentType);
1102
- if ([
1103
- ".jpg",
1104
- ".jpeg",
1105
- ".png",
1106
- ".gif",
1107
- ".webp",
1108
- ".bmp",
1109
- ".ico",
1110
- ".tiff"
1111
- ].includes(ext) || mimeKind === "image") return { msgType: "image" };
1200
+ const detectedContentType = await detectMime({ buffer }) ?? "";
1201
+ if (FEISHU_SUPPORTED_IMAGE_CONTENT_TYPES.has(detectedContentType)) return { msgType: "image" };
1112
1202
  if (ext === ".opus" || ext === ".ogg" || contentType === "audio/ogg" || contentType === "audio/opus") return {
1113
1203
  fileType: "opus",
1114
1204
  msgType: "audio"
@@ -1127,6 +1217,14 @@ function resolveFeishuOutboundMediaKind(params) {
1127
1217
  msgType: fileType === "stream" ? "file" : fileType === "opus" ? "audio" : fileType === "mp4" ? "media" : "file"
1128
1218
  };
1129
1219
  }
1220
+ function assertFeishuUploadWithinEnvelope(params) {
1221
+ if (params.buffer.byteLength === 0) throw new Error("Feishu attachments cannot be empty");
1222
+ const maxBytes = params.msgType === "image" ? Math.min(params.mediaMaxBytes, FEISHU_MAX_IMAGE_UPLOAD_BYTES) : params.mediaMaxBytes;
1223
+ if (params.buffer.byteLength > maxBytes) {
1224
+ const label = params.msgType === "image" ? "image" : "file";
1225
+ throw new Error(`Feishu ${label} exceeds its ${String(maxBytes)}-byte upload limit`);
1226
+ }
1227
+ }
1130
1228
  function isFeishuNativeVoiceAudio(params) {
1131
1229
  const ext = normalizeLowercaseStringOrEmpty(path.extname(params.fileName));
1132
1230
  const contentType = normalizeLowercaseStringOrEmpty(params.contentType);
@@ -1244,19 +1342,19 @@ async function maybeProbeUploadDurationMs(params) {
1244
1342
  }
1245
1343
  /**
1246
1344
  * Upload and send media (image or file) from URL, local path, or buffer.
1247
- * When mediaUrl is a local path, mediaLocalRoots (from core outbound context)
1248
- * must be passed so loadWebMedia allows the path (post CVE-2026-26321).
1345
+ * Local paths require host-owned mediaAccess or approved legacy roots/readers.
1249
1346
  */
1250
1347
  async function sendMediaFeishu(params) {
1251
1348
  const { cfg, to, mediaUrl, mediaBuffer, fileName, replyToMessageId, replyInThread, allowTopLevelReplyFallback, accountId, mediaLocalRoots, audioAsVoice } = params;
1252
- const mediaMaxBytes = ((await runBeforeFeishuMessageDispatch(() => {
1349
+ const account = await runBeforeFeishuMessageDispatch(() => {
1253
1350
  const resolved = resolveFeishuRuntimeAccount({
1254
1351
  cfg,
1255
1352
  accountId
1256
1353
  });
1257
1354
  if (!resolved.configured) throw new Error(`Feishu account "${resolved.accountId}" not configured`);
1258
1355
  return resolved;
1259
- })).config?.mediaMaxMb ?? 30) * 1024 * 1024;
1356
+ });
1357
+ const mediaMaxBytes = Math.min((account.config?.mediaMaxMb ?? 30) * 1024 * 1024, FEISHU_MAX_FILE_UPLOAD_BYTES);
1260
1358
  let buffer;
1261
1359
  let name;
1262
1360
  let contentType;
@@ -1267,11 +1365,13 @@ async function sendMediaFeishu(params) {
1267
1365
  contentType: void 0
1268
1366
  };
1269
1367
  if (mediaUrl) {
1270
- const media = await getFeishuRuntime().media.loadWebMedia(mediaUrl, {
1368
+ const media = await getFeishuRuntime().media.loadWebMedia(mediaUrl, buildOutboundMediaLoadOptions({
1271
1369
  maxBytes: mediaMaxBytes,
1272
- optimizeImages: false,
1273
- localRoots: mediaLocalRoots?.length ? mediaLocalRoots : void 0
1274
- });
1370
+ mediaAccess: params.mediaAccess,
1371
+ mediaLocalRoots,
1372
+ mediaReadFile: params.mediaReadFile,
1373
+ optimizeImages: false
1374
+ }));
1275
1375
  return {
1276
1376
  buffer: media.buffer,
1277
1377
  name: fileName ?? media.fileName ?? "file",
@@ -1283,6 +1383,16 @@ async function sendMediaFeishu(params) {
1283
1383
  buffer = loaded.buffer;
1284
1384
  name = loaded.name;
1285
1385
  contentType = loaded.contentType;
1386
+ const loadedRouting = await runBeforeFeishuMessageDispatch(() => resolveFeishuOutboundMediaKind({
1387
+ buffer,
1388
+ fileName: name,
1389
+ contentType
1390
+ }));
1391
+ await runBeforeFeishuMessageDispatch(() => assertFeishuUploadWithinEnvelope({
1392
+ buffer,
1393
+ mediaMaxBytes,
1394
+ msgType: loadedRouting.msgType
1395
+ }));
1286
1396
  const prepared = await runBeforeFeishuMessageDispatch(() => prepareFeishuVoiceMedia({
1287
1397
  buffer,
1288
1398
  fileName: name,
@@ -1292,11 +1402,17 @@ async function sendMediaFeishu(params) {
1292
1402
  buffer = prepared.buffer;
1293
1403
  name = prepared.fileName;
1294
1404
  contentType = prepared.contentType;
1295
- const routing = resolveFeishuOutboundMediaKind({
1405
+ const routing = prepared.buffer === loaded.buffer && prepared.fileName === loaded.name && prepared.contentType === loaded.contentType ? loadedRouting : await runBeforeFeishuMessageDispatch(() => resolveFeishuOutboundMediaKind({
1406
+ buffer,
1296
1407
  fileName: name,
1297
1408
  contentType
1298
- });
1409
+ }));
1299
1410
  const voiceIntentDegradedToFile = audioAsVoice === true && routing.msgType !== "audio";
1411
+ await runBeforeFeishuMessageDispatch(() => assertFeishuUploadWithinEnvelope({
1412
+ buffer,
1413
+ mediaMaxBytes,
1414
+ msgType: routing.msgType
1415
+ }));
1300
1416
  if (routing.msgType === "image") {
1301
1417
  const { imageKey } = await runBeforeFeishuMessageDispatch(() => uploadImageFeishu({
1302
1418
  cfg,
@@ -1345,4 +1461,4 @@ async function sendMediaFeishu(params) {
1345
1461
  };
1346
1462
  }
1347
1463
  //#endregion
1348
- export { buildFeishuMediaFallbackText as _, getMessageFeishu as a, sendMarkdownCardFeishu as c, parsePostContent as d, extractMentionTargets as f, normalizeFeishuExternalKey as g, isFeishuGroupChatType as h, editMessageFeishu as i, sendMessageFeishu as l, isMentionForwardRequest as m, sendMediaFeishu as n, listFeishuThreadMessages as o, isFeishuBroadcastMention as p, shouldSuppressFeishuTextForVoiceMedia as r, sendCardFeishu as s, saveMessageResourceFeishu as t, sendStructuredCardFeishu as u, resolveFeishuIdentityEmoji as v, resolveFeishuIdentityHeaderTitle as y };
1464
+ export { normalizeFeishuExternalKey as _, getMessageFeishu as a, resolveFeishuIdentityHeaderTitle as b, sendMarkdownCardFeishu as c, extractMentionTargets as d, isFeishuBroadcastMention as f, parsePostContent as g, parseInteractiveCardContent as h, editMessageFeishu as i, sendMessageFeishu as l, isFeishuGroupChatType as m, sendMediaFeishu as n, listFeishuThreadMessages as o, isMentionForwardRequest as p, shouldSuppressFeishuTextForVoiceMedia as r, sendCardFeishu as s, saveMessageResourceFeishu as t, sendStructuredCardFeishu as u, buildFeishuMediaFallbackText as v, resolveFeishuIdentityEmoji as y };