@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,13 +1,14 @@
1
- import { a as resolveDefaultFeishuAccountId, i as listFeishuAccountIds, o as resolveFeishuAccount, r as listEnabledFeishuAccounts, s as resolveFeishuRuntimeAccount } from "./accounts-RwSyseKr.js";
2
- import { i as resolveReceiveIdType, r as normalizeFeishuTarget, t as detectIdType } from "./targets-BUjQ1TcA.js";
3
- import { r as createFeishuClient } from "./client-DalLmpMf.js";
1
+ import { a as resolveDefaultFeishuAccountId, i as listFeishuAccountIds, o as resolveFeishuAccount, r as listEnabledFeishuAccounts, s as resolveFeishuRuntimeAccount } from "./accounts-cCMNFBKg.js";
2
+ import { i as resolveReceiveIdType, r as normalizeFeishuTarget, t as detectIdType } from "./targets-BTQCYRZQ.js";
3
+ import { o as normalizeFeishuExternalKey } from "./doctor-contract-w6FpMypv.js";
4
+ import { r as createFeishuClient } from "./client-DMbIL3UH.js";
4
5
  import { t as getFeishuRuntime } from "./runtime-C5JxBWZp.js";
5
6
  import { normalizeAccountId, normalizeOptionalAccountId, resolveMergedAccountConfig } from "openclaw/plugin-sdk/account-resolution";
6
7
  import { compileAllowlist, resolveAllowlistMatchByCandidates } from "openclaw/plugin-sdk/allow-from";
7
8
  import { ToolAuthorizationError } from "openclaw/plugin-sdk/channel-actions";
8
9
  import { createMessageReceiptFromOutboundResults } from "openclaw/plugin-sdk/channel-outbound";
9
10
  import { PlatformMessageNotDispatchedError, formatErrorMessage } from "openclaw/plugin-sdk/error-runtime";
10
- import { legacyInteractiveReplyToPresentation, normalizeLegacyInteractiveReply, normalizeMessagePresentation, renderMessagePresentationChartFallbackText, renderMessagePresentationFallbackText, renderMessagePresentationTableFallbackText } from "openclaw/plugin-sdk/interactive-runtime";
11
+ import { legacyInteractiveReplyToPresentation, normalizeLegacyInteractiveReply, normalizeMessagePresentation, renderMessagePresentationChartFallbackText, renderMessagePresentationFallbackText, renderMessagePresentationTableFallbackText, renderPresentationForDelivery, resolveLegacyInteractiveTextFallback } from "openclaw/plugin-sdk/interactive-runtime";
11
12
  import { parseStrictNonNegativeInteger } from "openclaw/plugin-sdk/number-runtime";
12
13
  import { isRecord, normalizeLowercaseStringOrEmpty, normalizeOptionalLowercaseString, normalizeOptionalString, normalizeStringEntries, readStringValue } from "openclaw/plugin-sdk/string-coerce-runtime";
13
14
  import { convertMarkdownTables } from "openclaw/plugin-sdk/text-chunking";
@@ -152,7 +153,7 @@ const feishuIngressIdentity = defineStableChannelIngressIdentity({
152
153
  aliases: [{
153
154
  key: "feishu-alt-id",
154
155
  kind: FEISHU_ID_KIND,
155
- normalizeEntry: () => null,
156
+ normalizeEntry: normalizeFeishuAllowEntry,
156
157
  normalizeSubject: normalizeFeishuAllowEntry,
157
158
  sensitivity: "pii"
158
159
  }],
@@ -460,20 +461,8 @@ function canEnumerateAllFeishuPeers(account) {
460
461
  return isDmUniversallyAllowed(account);
461
462
  }
462
463
  //#endregion
463
- //#region extensions/feishu/src/external-keys.ts
464
- const CONTROL_CHARS_RE = /\p{Cc}/u;
465
- const MAX_EXTERNAL_KEY_LENGTH = 512;
466
- function normalizeFeishuExternalKey(value) {
467
- if (typeof value !== "string") return;
468
- const normalized = value.trim();
469
- if (!normalized || normalized.length > MAX_EXTERNAL_KEY_LENGTH) return;
470
- if (CONTROL_CHARS_RE.test(normalized)) return;
471
- if (normalized.includes("/") || normalized.includes("\\") || normalized.includes("..")) return;
472
- return normalized;
473
- }
474
- //#endregion
475
464
  //#region extensions/feishu/src/markdown.ts
476
- const FEISHU_POST_MAX_BYTES = 30 * 1024;
465
+ const FEISHU_POST_MAX_BYTES = 30720;
477
466
  /** One parser contract for Feishu message and document Markdown decisions. */
478
467
  function parseFeishuMarkdown(text) {
479
468
  return fromMarkdown(text, {
@@ -566,22 +555,21 @@ function postContentBytes(messageText, mentions) {
566
555
  mentions
567
556
  }), "utf8");
568
557
  }
569
- /**
570
- * Honor both configured character chunking and Feishu's serialized post envelope.
571
- * Markdown wrappers and first-chunk mentions count toward the byte budget.
572
- */
573
558
  function chunkFeishuPostMarkdown(params) {
574
- const { text, firstChunkMentions, chunkMentions } = params;
559
+ return chunkFeishuMarkdownByEnvelope({
560
+ ...params,
561
+ contentBytes: (text, isFirst) => postContentBytes(text, [...params.chunkMentions ?? [], ...isFirst ? params.firstChunkMentions ?? [] : []])
562
+ });
563
+ }
564
+ /** Measure the actual transport envelope, including UTF-8, JSON escapes and fence wrappers. */
565
+ function chunkFeishuMarkdownByEnvelope(params) {
566
+ const { text } = params;
575
567
  if (!text) return [];
576
568
  const requestedLimit = Number.isFinite(params.limit) && params.limit > 0 ? Math.floor(params.limit) : text.length;
577
569
  const initialChunks = params.initialChunks ?? chunkFeishuMarkdownWithMode(text, requestedLimit, params.mode ?? "length");
578
570
  const output = [];
579
- const resolveMentions = (isFirst) => {
580
- const mentions = [...chunkMentions ?? [], ...isFirst ? firstChunkMentions ?? [] : []];
581
- return mentions.length > 0 ? mentions : void 0;
582
- };
583
571
  for (const initialChunk of initialChunks) {
584
- if (postContentBytes(initialChunk, resolveMentions(output.length === 0)) <= FEISHU_POST_MAX_BYTES) {
572
+ if (params.contentBytes(initialChunk, output.length === 0) <= FEISHU_POST_MAX_BYTES) {
585
573
  output.push(initialChunk);
586
574
  continue;
587
575
  }
@@ -590,27 +578,16 @@ function chunkFeishuPostMarkdown(params) {
590
578
  const chunks = chunkFeishuMarkdownWithMode(initialChunk, adaptiveLimit, params.mode ?? "length");
591
579
  let largestContentBytes = 0;
592
580
  let oversizedChunk;
593
- let oversizedMentions;
594
581
  for (const [index, chunk] of chunks.entries()) {
595
- const mentionsForChunk = resolveMentions(output.length === 0 && index === 0);
596
- const contentBytes = postContentBytes(chunk, mentionsForChunk);
582
+ const contentBytes = params.contentBytes(chunk, output.length === 0 && index === 0);
597
583
  largestContentBytes = Math.max(largestContentBytes, contentBytes);
598
- if (contentBytes > FEISHU_POST_MAX_BYTES && oversizedChunk === void 0) {
599
- oversizedChunk = chunk;
600
- oversizedMentions = mentionsForChunk;
601
- }
584
+ if (contentBytes > FEISHU_POST_MAX_BYTES && oversizedChunk === void 0) oversizedChunk = chunk;
602
585
  }
603
586
  if (oversizedChunk === void 0) {
604
587
  output.push(...chunks);
605
588
  break;
606
589
  }
607
- if (adaptiveLimit === 1) {
608
- assertFeishuPostWithinEnvelope(buildFeishuPostMessageContent({
609
- messageText: oversizedChunk,
610
- mentions: oversizedMentions
611
- }), "Feishu post chunk");
612
- return [...output, ...chunks];
613
- }
590
+ if (adaptiveLimit === 1) throw new Error("Feishu Markdown chunk exceeds the 30 KB API limit");
614
591
  adaptiveLimit = Math.max(1, Math.min(adaptiveLimit - 1, Math.floor(adaptiveLimit * FEISHU_POST_MAX_BYTES / largestContentBytes) - 1));
615
592
  }
616
593
  }
@@ -638,7 +615,7 @@ function resolveFeishuCardTemplate(template) {
638
615
  if (!normalized || !FEISHU_CARD_TEMPLATES.has(normalized)) return;
639
616
  return normalized;
640
617
  }
641
- function escapeFeishuCardMarkdownText$1(text) {
618
+ function escapeFeishuCardMarkdownText(text) {
642
619
  return text.replace(/[&<>]/g, (char) => {
643
620
  switch (char) {
644
621
  case "&": return "&amp;";
@@ -649,9 +626,9 @@ function escapeFeishuCardMarkdownText$1(text) {
649
626
  });
650
627
  }
651
628
  function escapeFeishuCardPlainText(text) {
652
- return escapeFeishuCardMarkdownText$1(text).replace(/([\\`*_{}[\]()#+\-!|>~])/g, "\\$1");
629
+ return escapeFeishuCardMarkdownText(text).replace(/([\\`*_{}[\]()#+\-!|>~])/g, "\\$1");
653
630
  }
654
- function resolveSafeFeishuButtonUrl$1(url) {
631
+ function resolveSafeFeishuButtonUrl(url) {
655
632
  const trimmed = typeof url === "string" ? url.trim() : "";
656
633
  if (!trimmed) return;
657
634
  try {
@@ -664,7 +641,7 @@ function resolveSafeFeishuButtonUrl$1(url) {
664
641
  function sanitizeNativeFeishuButtonBehavior(behavior) {
665
642
  if (!isRecord(behavior)) return;
666
643
  if (behavior.type === "open_url") {
667
- const safeUrl = resolveSafeFeishuButtonUrl$1(behavior.default_url) ?? resolveSafeFeishuButtonUrl$1(behavior.url);
644
+ const safeUrl = resolveSafeFeishuButtonUrl(behavior.default_url) ?? resolveSafeFeishuButtonUrl(behavior.url);
668
645
  return safeUrl ? {
669
646
  type: "open_url",
670
647
  default_url: safeUrl
@@ -681,7 +658,7 @@ function sanitizeNativeFeishuCardButton(button) {
681
658
  if (!text?.trim()) return;
682
659
  const style = button.type === "danger" ? "danger" : button.type === "primary" || button.type === "success" ? "primary" : void 0;
683
660
  const behaviors = Array.isArray(button.behaviors) ? button.behaviors.map((behavior) => sanitizeNativeFeishuButtonBehavior(behavior)).filter((behavior) => Boolean(behavior)) : [];
684
- const rootSafeUrl = resolveSafeFeishuButtonUrl$1(button.url);
661
+ const rootSafeUrl = resolveSafeFeishuButtonUrl(button.url);
685
662
  if (rootSafeUrl) behaviors.push({
686
663
  type: "open_url",
687
664
  default_url: rootSafeUrl
@@ -706,13 +683,13 @@ function sanitizeNativeFeishuCardElements(element) {
706
683
  if (element.tag === "hr") return [{ tag: "hr" }];
707
684
  if (element.tag === "markdown" && typeof element.content === "string") return [{
708
685
  tag: "markdown",
709
- content: escapeFeishuCardMarkdownText$1(element.content)
686
+ content: escapeFeishuCardMarkdownText(element.content)
710
687
  }];
711
688
  if (element.tag === "div" && isRecord(element.text)) {
712
689
  const text = element.text;
713
690
  if (text.tag === "lark_md" && typeof text.content === "string") return [{
714
691
  tag: "markdown",
715
- content: escapeFeishuCardMarkdownText$1(text.content)
692
+ content: escapeFeishuCardMarkdownText(text.content)
716
693
  }];
717
694
  if (text.tag === "plain_text" && typeof text.content === "string") return [{
718
695
  tag: "markdown",
@@ -857,7 +834,7 @@ async function requestFeishuApi(request, errorPrefix, options = {}) {
857
834
  return result;
858
835
  }, {
859
836
  attempts: 3,
860
- minDelayMs: options.retryDelayMs ?? FEISHU_SEND_RETRY_BASE_MS,
837
+ minDelayMs: FEISHU_SEND_RETRY_BASE_MS,
861
838
  shouldRetry: (error) => getFeishuSendRateLimitCode(error) !== void 0
862
839
  });
863
840
  } catch (error) {
@@ -870,7 +847,8 @@ function readDocsLinkUrl(element) {
870
847
  }
871
848
  function readMentionUserId(element) {
872
849
  const mention = isRecord(element.mention) ? element.mention : void 0;
873
- return normalizeOptionalString((isRecord(element.person) ? element.person : void 0)?.user_id) || normalizeOptionalString(mention?.user_id) || normalizeOptionalString(mention?.open_id) || normalizeOptionalString(element.mention_user) || normalizeOptionalString(element.user_id) || void 0;
850
+ const person = isRecord(element.person) ? element.person : void 0;
851
+ return normalizeOptionalString(person?.user_id) || normalizeOptionalString(mention?.user_id) || normalizeOptionalString(mention?.open_id) || normalizeOptionalString(element.mention_user) || normalizeOptionalString(element.user_id) || void 0;
874
852
  }
875
853
  function readMentionDisplayText(element, userId) {
876
854
  const mention = isRecord(element.mention) ? element.mention : void 0;
@@ -1332,7 +1310,8 @@ function resolveFeishuToolAccount(params) {
1332
1310
  function createFeishuToolClient(params) {
1333
1311
  return createFeishuClient(resolveFeishuToolAccount(params));
1334
1312
  }
1335
- function resolveAnyEnabledFeishuToolsConfig(accounts) {
1313
+ function resolveAnyEnabledFeishuToolsConfig(config) {
1314
+ const accounts = listEnabledFeishuAccounts(config);
1336
1315
  const merged = {
1337
1316
  doc: false,
1338
1317
  chat: false,
@@ -1444,38 +1423,19 @@ function resolveAmbientCommentTarget(context) {
1444
1423
  if (deliveryContext?.channel && deliveryContext.channel !== "feishu") return null;
1445
1424
  return parseFeishuCommentTarget(deliveryContext?.to);
1446
1425
  }
1447
- function applyAmbientCommentDefaults(params, context) {
1426
+ function resolveDriveCommentParams(params, context, action) {
1448
1427
  const ambient = resolveAmbientCommentTarget(context);
1449
- if (!ambient) return params;
1450
- return {
1428
+ let resolved = params;
1429
+ if (ambient && (action !== "add_comment" || ambient.fileType === "doc" || ambient.fileType === "docx")) resolved = {
1451
1430
  ...params,
1452
1431
  file_token: params.file_token?.trim() || ambient.fileToken,
1453
1432
  file_type: params.file_type ?? ambient.fileType,
1454
- comment_id: params.comment_id?.trim() || ambient.commentId
1455
- };
1456
- }
1457
- function applyAddCommentAmbientDefaults(params, context) {
1458
- const ambient = resolveAmbientCommentTarget(context);
1459
- if (!ambient || ambient.fileType !== "doc" && ambient.fileType !== "docx") return params;
1460
- return {
1461
- ...params,
1462
- file_token: params.file_token?.trim() || ambient.fileToken,
1463
- file_type: params.file_type ?? ambient.fileType
1464
- };
1465
- }
1466
- function applyAddCommentDefaults(params) {
1467
- const fileType = params.file_type ?? "docx";
1468
- if (!params.file_type) console.info(`[feishu_drive] add_comment missing file_type; defaulting to docx file_token=${params.file_token ?? "unknown"}`);
1469
- return {
1470
- ...params,
1471
- file_type: fileType
1433
+ ...action !== "add_comment" && { comment_id: params.comment_id?.trim() || ambient.commentId }
1472
1434
  };
1473
- }
1474
- function applyCommentFileTypeDefault(params, action) {
1475
- const fileType = params.file_type ?? "docx";
1476
- if (!params.file_type) console.info(`[feishu_drive] ${action} missing file_type; defaulting to docx file_token=${params.file_token ?? "unknown"}`);
1435
+ const fileType = resolved.file_type ?? "docx";
1436
+ if (!resolved.file_type) console.info(`[feishu_drive] ${action} missing file_type; defaulting to docx file_token=${resolved.file_token ?? "unknown"}`);
1477
1437
  return {
1478
- ...params,
1438
+ ...resolved,
1479
1439
  file_type: fileType
1480
1440
  };
1481
1441
  }
@@ -1619,28 +1579,13 @@ async function deleteFile(client, fileToken, type) {
1619
1579
  task_id: res.data?.task_id
1620
1580
  };
1621
1581
  }
1622
- async function listComments(client, params) {
1623
- const response = assertDriveApiSuccess(await requestDriveApi({
1624
- client,
1625
- method: "GET",
1626
- url: `/open-apis/drive/v1/files/${encodeURIComponent(params.file_token)}/comments` + encodeQuery({
1627
- file_type: params.file_type,
1628
- page_size: normalizeCommentPageSize(params.page_size),
1629
- page_token: params.page_token,
1630
- user_id_type: "open_id"
1631
- })
1632
- }));
1633
- return {
1634
- has_more: response.data?.has_more ?? false,
1635
- page_token: response.data?.page_token,
1636
- comments: (response.data?.items ?? []).map(normalizeCommentCard)
1637
- };
1638
- }
1639
- async function listCommentReplies(client, params) {
1582
+ async function listDriveCommentPage(client, params) {
1583
+ const filePath = `/open-apis/drive/v1/files/${encodeURIComponent(params.file_token)}/comments`;
1584
+ const isReplies = params.action === "list_comment_replies";
1640
1585
  const response = assertDriveApiSuccess(await requestDriveApi({
1641
1586
  client,
1642
1587
  method: "GET",
1643
- url: `/open-apis/drive/v1/files/${encodeURIComponent(params.file_token)}/comments/${encodeURIComponent(params.comment_id)}/replies` + encodeQuery({
1588
+ url: (isReplies ? `${filePath}/${encodeURIComponent(params.comment_id)}/replies` : filePath) + encodeQuery({
1644
1589
  file_type: params.file_type,
1645
1590
  page_size: normalizeCommentPageSize(params.page_size),
1646
1591
  page_token: params.page_token,
@@ -1650,7 +1595,7 @@ async function listCommentReplies(client, params) {
1650
1595
  return {
1651
1596
  has_more: response.data?.has_more ?? false,
1652
1597
  page_token: response.data?.page_token,
1653
- replies: (response.data?.items ?? []).map(normalizeCommentReply)
1598
+ ...isReplies ? { replies: (response.data?.items ?? []).map(normalizeCommentReply) } : { comments: (response.data?.items ?? []).map(normalizeCommentCard) }
1654
1599
  };
1655
1600
  }
1656
1601
  async function addComment(client, params) {
@@ -1763,9 +1708,7 @@ async function deliverCommentThreadText(client, params) {
1763
1708
  }
1764
1709
  function registerFeishuDriveTools(api) {
1765
1710
  if (!api.config) return;
1766
- const accounts = listEnabledFeishuAccounts(api.config);
1767
- if (accounts.length === 0) return;
1768
- if (!resolveAnyEnabledFeishuToolsConfig(accounts).drive) return;
1711
+ if (!resolveAnyEnabledFeishuToolsConfig(api.config).drive) return;
1769
1712
  api.registerTool((ctx) => {
1770
1713
  const defaultAccountId = ctx.agentAccountId;
1771
1714
  return {
@@ -1796,23 +1739,13 @@ function registerFeishuDriveTools(api) {
1796
1739
  case "create_folder": return feishuExternalToolResult(await createFolder(client, p.name, p.folder_token));
1797
1740
  case "move": return feishuExternalToolResult(await moveFile(client, p.file_token, p.type, p.folder_token));
1798
1741
  case "delete": return feishuExternalToolResult(await deleteFile(client, p.file_token, p.type));
1799
- case "list_comments": return feishuExternalToolResult(await listComments(client, applyCommentFileTypeDefault(applyAmbientCommentDefaults(p, ctx), "list_comments")));
1800
- case "list_comment_replies": return feishuExternalToolResult(await listCommentReplies(client, applyCommentFileTypeDefault(applyAmbientCommentDefaults(p, ctx), "list_comment_replies")));
1801
- case "add_comment": {
1802
- const resolved = applyAddCommentDefaults(applyAddCommentAmbientDefaults(p, ctx));
1803
- try {
1804
- return feishuExternalToolResult(await addComment(client, resolved));
1805
- } finally {
1806
- cleanupAmbientCommentTypingReaction({
1807
- client: getDriveInternalClient(client),
1808
- deliveryContext: ctx.deliveryContext
1809
- });
1810
- }
1811
- }
1742
+ case "list_comments":
1743
+ case "list_comment_replies": return feishuExternalToolResult(await listDriveCommentPage(client, resolveDriveCommentParams(p, ctx, p.action)));
1744
+ case "add_comment":
1812
1745
  case "reply_comment": {
1813
- const resolved = applyCommentFileTypeDefault(applyAmbientCommentDefaults(p, ctx), "reply_comment");
1746
+ const resolved = resolveDriveCommentParams(p, ctx, p.action);
1814
1747
  try {
1815
- return feishuExternalToolResult(await deliverCommentThreadText(client, resolved));
1748
+ return feishuExternalToolResult(await (resolved.action === "add_comment" ? addComment(client, resolved) : deliverCommentThreadText(client, resolved)));
1816
1749
  } finally {
1817
1750
  cleanupAmbientCommentTypingReaction({
1818
1751
  client: getDriveInternalClient(client),
@@ -1880,6 +1813,11 @@ async function buildFeishuMediaFallbackText(params) {
1880
1813
  return [params.text?.trim(), attachmentText].filter(Boolean).join("\n\n");
1881
1814
  }
1882
1815
  //#endregion
1816
+ //#region extensions/feishu/src/api-response.ts
1817
+ function assertFeishuApiSuccess(response, errorPrefix) {
1818
+ if (response.code !== 0) throw new Error(`${errorPrefix}: ${response.msg || `code ${response.code}`}`);
1819
+ }
1820
+ //#endregion
1883
1821
  //#region extensions/feishu/src/media-chunk-idle.ts
1884
1822
  var FeishuInboundMediaTimeoutError = class extends Error {
1885
1823
  constructor(chunkTimeoutMs) {
@@ -1953,14 +1891,11 @@ function createFeishuSendReceipt(params) {
1953
1891
  chatId,
1954
1892
  conversationId: chatId
1955
1893
  }] : [],
1956
- ...chatId ? { threadId: chatId } : {},
1894
+ ...params.replyToId?.trim() ? { replyToId: params.replyToId.trim() } : {},
1957
1895
  kind: params.kind ?? "unknown"
1958
1896
  });
1959
1897
  }
1960
- function assertFeishuMessageApiSuccess(response, errorPrefix) {
1961
- if (response.code !== 0) throw new Error(`${errorPrefix}: ${response.msg || `code ${response.code}`}`);
1962
- }
1963
- function toFeishuSendResult(response, chatId, kind, errorPrefix = "Feishu send failed") {
1898
+ function toFeishuSendResult(response, chatId, kind, errorPrefix = "Feishu send failed", replyToId) {
1964
1899
  const messageId = response.data?.message_id?.trim();
1965
1900
  if (!messageId) throw createChannelPartialDeliveryError(/* @__PURE__ */ new Error(`${errorPrefix}: no message_id returned`), {
1966
1901
  messageIds: [],
@@ -1972,7 +1907,8 @@ function toFeishuSendResult(response, chatId, kind, errorPrefix = "Feishu send f
1972
1907
  receipt: createFeishuSendReceipt({
1973
1908
  messageId,
1974
1909
  chatId,
1975
- kind
1910
+ kind,
1911
+ replyToId
1976
1912
  })
1977
1913
  };
1978
1914
  }
@@ -1988,10 +1924,11 @@ function resolveFeishuSendTarget(params) {
1988
1924
  const client = createFeishuClient(account);
1989
1925
  const receiveId = normalizeFeishuTarget(target);
1990
1926
  if (!receiveId) throw new Error(`Invalid Feishu target: ${params.to}`);
1927
+ const withoutProviderPrefix = target.replace(/^(feishu|lark):/i, "");
1991
1928
  return {
1992
1929
  client,
1993
1930
  receiveId,
1994
- receiveIdType: resolveReceiveIdType(target.replace(/^(feishu|lark):/i, ""))
1931
+ receiveIdType: resolveReceiveIdType(withoutProviderPrefix)
1995
1932
  };
1996
1933
  }
1997
1934
  //#endregion
@@ -2058,7 +1995,8 @@ function renderElement(element, attachments, mentionedOpenIds, renderMediaPlaceh
2058
1995
  case "a": return renderLinkElement(element);
2059
1996
  case "at":
2060
1997
  {
2061
- const normalizedMention = normalizeFeishuExternalKey(toStringOrEmpty(element.open_id) || toStringOrEmpty(element.user_id));
1998
+ const mentioned = toStringOrEmpty(element.open_id) || toStringOrEmpty(element.user_id);
1999
+ const normalizedMention = normalizeFeishuExternalKey(mentioned);
2062
2000
  if (normalizedMention) mentionedOpenIds.push(normalizedMention);
2063
2001
  }
2064
2002
  return renderMentionElement(element);
@@ -2372,7 +2310,7 @@ async function sendFallbackDirect(client, params, errorPrefix) {
2372
2310
  msg_type: params.msgType
2373
2311
  }
2374
2312
  }), errorPrefix, { includeNestedErrorLogId: true });
2375
- assertFeishuMessageApiSuccess(response, errorPrefix);
2313
+ assertFeishuApiSuccess(response, errorPrefix);
2376
2314
  return toFeishuSendResult(response, params.receiveId, resolveFeishuReceiptKind(params.msgType), errorPrefix);
2377
2315
  }
2378
2316
  async function sendReplyOrFallbackDirect(client, params) {
@@ -2397,8 +2335,8 @@ async function sendReplyOrFallbackDirect(client, params) {
2397
2335
  if (replyTargetFallbackError) throw replyTargetFallbackError;
2398
2336
  return sendFallbackDirect(client, params.directParams, params.directErrorPrefix);
2399
2337
  }
2400
- assertFeishuMessageApiSuccess(response, params.replyErrorPrefix);
2401
- return toFeishuSendResult(response, params.directParams.receiveId, resolveFeishuReceiptKind(params.msgType), params.replyErrorPrefix);
2338
+ assertFeishuApiSuccess(response, params.replyErrorPrefix);
2339
+ return toFeishuSendResult(response, params.directParams.receiveId, resolveFeishuReceiptKind(params.msgType), params.replyErrorPrefix, params.replyToMessageId);
2402
2340
  }
2403
2341
  function parseFeishuMessageContent(rawContent, msgType, messageId) {
2404
2342
  if (!rawContent) return "";
@@ -2406,7 +2344,8 @@ function parseFeishuMessageContent(rawContent, msgType, messageId) {
2406
2344
  try {
2407
2345
  parsed = JSON.parse(rawContent);
2408
2346
  } catch {
2409
- logVerbose(`feishu message content parse failed for ${msgType} message${messageId ? ` (id: ${messageId})` : ""}`);
2347
+ const safeId = messageId ? ` (id: ${messageId})` : "";
2348
+ logVerbose(`feishu message content parse failed for ${msgType} message${safeId}`);
2410
2349
  return rawContent;
2411
2350
  }
2412
2351
  if (msgType === "text") {
@@ -2587,57 +2526,40 @@ async function editMessageFeishu(params) {
2587
2526
  const client = createFeishuClient(account);
2588
2527
  if (card) {
2589
2528
  const content = JSON.stringify(card);
2590
- const response = await client.im.message.patch({
2529
+ assertFeishuApiSuccess(await client.im.message.patch({
2591
2530
  path: { message_id: messageId },
2592
2531
  data: { content }
2593
- });
2594
- if (response.code !== 0) throw new Error(`Feishu message edit failed: ${response.msg || `code ${response.code}`}`);
2532
+ }), "Feishu message edit failed");
2595
2533
  return {
2596
2534
  messageId,
2597
2535
  contentType: "interactive"
2598
2536
  };
2599
2537
  }
2600
- const content = buildFeishuPostMessageContent({ messageText: materializeFeishuPostMarkdownSoftBreaks(convertMarkdownTables(text, resolveMarkdownTableMode({
2538
+ const tableMode = resolveMarkdownTableMode({
2601
2539
  cfg,
2602
2540
  channel: "feishu"
2603
- }))) });
2541
+ });
2542
+ const content = buildFeishuPostMessageContent({ messageText: materializeFeishuPostMarkdownSoftBreaks(convertMarkdownTables(text, tableMode)) });
2604
2543
  assertFeishuPostWithinEnvelope(content, "Feishu message edit");
2605
- const response = await client.im.message.update({
2544
+ assertFeishuApiSuccess(await client.im.message.update({
2606
2545
  path: { message_id: messageId },
2607
2546
  data: {
2608
2547
  msg_type: "post",
2609
2548
  content
2610
2549
  }
2611
- });
2612
- if (response.code !== 0) throw new Error(`Feishu message edit failed: ${response.msg || `code ${response.code}`}`);
2550
+ }), "Feishu message edit failed");
2613
2551
  return {
2614
2552
  messageId,
2615
2553
  contentType: "post"
2616
2554
  };
2617
2555
  }
2618
2556
  /**
2619
- * Build a Feishu interactive card with markdown content.
2620
- * Cards render markdown properly (code blocks, tables, links, etc.)
2621
- * Uses schema 2.0 format for proper markdown rendering.
2622
- */
2623
- function buildMarkdownCard(text) {
2624
- return {
2625
- schema: "2.0",
2626
- config: { width_mode: "fill" },
2627
- body: { elements: [{
2628
- tag: "markdown",
2629
- content: text
2630
- }] }
2631
- };
2632
- }
2633
- /**
2634
2557
  * Build a Feishu interactive card with optional header and note footer.
2635
- * When header/note are omitted, behaves identically to buildMarkdownCard.
2636
2558
  */
2637
2559
  function buildStructuredCard(text, options) {
2638
2560
  const elements = [{
2639
2561
  tag: "markdown",
2640
- content: text
2562
+ content: options?.mentions?.length ? buildMentionedCardContent(options.mentions, text) : text
2641
2563
  }];
2642
2564
  if (options?.note) {
2643
2565
  elements.push({ tag: "hr" });
@@ -2660,19 +2582,28 @@ function buildStructuredCard(text, options) {
2660
2582
  };
2661
2583
  return card;
2662
2584
  }
2585
+ function chunkFeishuCardMarkdown(params) {
2586
+ return chunkFeishuMarkdownByEnvelope({
2587
+ ...params,
2588
+ contentBytes: (text, isFirst) => Buffer.byteLength(JSON.stringify(buildStructuredCard(text, {
2589
+ header: params.header,
2590
+ note: params.note,
2591
+ mentions: [...params.chunkMentions ?? [], ...isFirst ? params.firstChunkMentions ?? [] : []]
2592
+ })), "utf8")
2593
+ });
2594
+ }
2663
2595
  /**
2664
2596
  * Send a message as a structured card with optional header and note.
2665
2597
  */
2666
2598
  async function sendStructuredCardFeishu(params) {
2667
2599
  const { cfg, to, text, replyToMessageId, replyInThread, allowTopLevelReplyFallback, mentions, accountId, header, note } = params;
2668
- let cardText = text;
2669
- if (mentions && mentions.length > 0) cardText = buildMentionedCardContent(mentions, text);
2670
2600
  return sendCardFeishu({
2671
2601
  cfg,
2672
2602
  to,
2673
- card: buildStructuredCard(cardText, {
2603
+ card: buildStructuredCard(text, {
2674
2604
  header,
2675
- note
2605
+ note,
2606
+ mentions
2676
2607
  }),
2677
2608
  replyToMessageId,
2678
2609
  replyInThread,
@@ -2680,29 +2611,11 @@ async function sendStructuredCardFeishu(params) {
2680
2611
  accountId
2681
2612
  });
2682
2613
  }
2683
- /**
2684
- * Send a message as a markdown card (interactive message).
2685
- * This renders markdown properly in Feishu (code blocks, tables, bold/italic, etc.)
2686
- */
2687
- async function sendMarkdownCardFeishu(params) {
2688
- const { cfg, to, text, replyToMessageId, replyInThread, allowTopLevelReplyFallback, mentions, accountId } = params;
2689
- let cardText = text;
2690
- if (mentions && mentions.length > 0) cardText = buildMentionedCardContent(mentions, text);
2691
- return sendCardFeishu({
2692
- cfg,
2693
- to,
2694
- card: buildMarkdownCard(cardText),
2695
- replyToMessageId,
2696
- replyInThread,
2697
- allowTopLevelReplyFallback,
2698
- accountId
2699
- });
2700
- }
2701
2614
  //#endregion
2702
2615
  //#region extensions/feishu/src/media.ts
2703
2616
  const FEISHU_MEDIA_HTTP_TIMEOUT_MS = 12e4;
2704
- const FEISHU_MAX_FILE_UPLOAD_BYTES = 30 * 1024 * 1024;
2705
- const FEISHU_MAX_IMAGE_UPLOAD_BYTES = 10 * 1024 * 1024;
2617
+ const FEISHU_MAX_FILE_UPLOAD_BYTES = 31457280;
2618
+ const FEISHU_MAX_IMAGE_UPLOAD_BYTES = 10485760;
2706
2619
  const FEISHU_VOICE_FILE_NAME = "voice.ogg";
2707
2620
  const FEISHU_VOICE_SAMPLE_RATE_HZ = 48e3;
2708
2621
  const FEISHU_VOICE_BITRATE = "64k";
@@ -2823,7 +2736,7 @@ function extractFeishuDownloadMetadata(response) {
2823
2736
  };
2824
2737
  }
2825
2738
  function mediaLimitError(maxBytes) {
2826
- return /* @__PURE__ */ new Error(`Media exceeds ${Math.round(maxBytes / (1024 * 1024))}MB limit`);
2739
+ return /* @__PURE__ */ new Error(`Media exceeds ${Math.round(maxBytes / 1048576)}MB limit`);
2827
2740
  }
2828
2741
  async function saveFeishuResponseMedia(params) {
2829
2742
  const { response, maxBytes, contentType, fileName } = params;
@@ -2987,7 +2900,7 @@ async function sendImageFeishu(params) {
2987
2900
  msg_type: "image"
2988
2901
  }
2989
2902
  }), "Feishu image send failed", { includeNestedErrorLogId: true });
2990
- assertFeishuMessageApiSuccess(response, "Feishu image send failed");
2903
+ assertFeishuApiSuccess(response, "Feishu image send failed");
2991
2904
  return toFeishuSendResult(response, receiveId, "media", "Feishu image send failed");
2992
2905
  }
2993
2906
  /**
@@ -3168,6 +3081,7 @@ async function probeMediaDurationMs(params) {
3168
3081
  }, async (workspace) => {
3169
3082
  const ext = normalizeLowercaseStringOrEmpty(path.extname(params.fileName));
3170
3083
  const inferredExt = ext && ext.length <= 12 ? ext : mediaKindFromMime(params.contentType) === "video" ? ".mp4" : ".ogg";
3084
+ const inputPath = await workspace.write(`input${inferredExt}`, params.buffer);
3171
3085
  const stdout = await runFfprobe([
3172
3086
  "-v",
3173
3087
  "error",
@@ -3175,7 +3089,7 @@ async function probeMediaDurationMs(params) {
3175
3089
  "format=duration",
3176
3090
  "-of",
3177
3091
  "csv=p=0",
3178
- await workspace.write(`input${inferredExt}`, params.buffer)
3092
+ inputPath
3179
3093
  ], { timeoutMs: 5e3 });
3180
3094
  const seconds = Number.parseFloat(stdout.trim());
3181
3095
  if (!Number.isFinite(seconds) || seconds <= 0) return;
@@ -3315,7 +3229,29 @@ async function sendMediaFeishu(params) {
3315
3229
  }
3316
3230
  //#endregion
3317
3231
  //#region extensions/feishu/src/presentation-card.ts
3318
- const FEISHU_CARD_MAX_BYTES = 30 * 1024;
3232
+ const RENDERED_FEISHU_CARD = Symbol("openclaw.renderedFeishuCard");
3233
+ const FEISHU_PRESENTATION_FALLBACK_MARKER = "__openclawPresentationFallback";
3234
+ const FEISHU_PRESENTATION_CAPABILITIES = {
3235
+ supported: true,
3236
+ buttons: true,
3237
+ selects: false,
3238
+ context: true,
3239
+ divider: true,
3240
+ limits: {
3241
+ actions: {
3242
+ maxActions: 20,
3243
+ maxActionsPerRow: 5,
3244
+ maxLabelLength: 40,
3245
+ maxValueBytes: 1024
3246
+ },
3247
+ text: {
3248
+ maxLength: 4e3,
3249
+ encoding: "characters",
3250
+ markdownDialect: "markdown"
3251
+ }
3252
+ }
3253
+ };
3254
+ const FEISHU_CARD_MAX_BYTES = 30720;
3319
3255
  const FEISHU_CARD_MAX_ELEMENTS = 200;
3320
3256
  function resolveFeishuRichReply(payload) {
3321
3257
  const interactive = normalizeLegacyInteractiveReply(payload.interactive);
@@ -3324,23 +3260,22 @@ function resolveFeishuRichReply(payload) {
3324
3260
  presentation: normalizeMessagePresentation(payload.presentation) ?? (interactive ? legacyInteractiveReplyToPresentation(interactive) : void 0)
3325
3261
  };
3326
3262
  }
3327
- function buildFeishuCommentPresentationFallback(params) {
3328
- const fallbackText = renderMessagePresentationFallbackText(params);
3329
- const fallbackHasCommand = params.fallbackHasCommand === true || params.presentation?.blocks.some((block) => block.type === "select" ? block.options.some(({ action }) => action?.type === "command") : block.type === "buttons" && block.buttons.some(({ action, disabled, url, webApp, web_app }) => !disabled && action?.type === "command" && !url && !webApp?.url && !web_app?.url)) === true;
3263
+ function buildFeishuPresentationFallback(params) {
3264
+ const fallbackText = renderFeishuPresentationFallbackText(params, params.textFormat);
3265
+ const fallbackHasCommand = params.fallbackHasCommand === true || params.presentation?.blocks.some((block) => block.type === "select" ? block.options.some(({ action }) => action?.type === "command") : block.type === "buttons" && block.buttons.some(({ action, disabled }) => !disabled && action?.type === "command")) === true;
3330
3266
  return {
3331
3267
  fallbackText,
3332
3268
  fallbackHasCommand,
3333
- text: fallbackHasCommand ? `${fallbackText}\n\n> Interactive buttons are unavailable in Feishu document comments. You can type the command shown above manually.` : fallbackText
3269
+ commentText: fallbackHasCommand ? `${fallbackText}\n\n> Interactive buttons are unavailable in Feishu document comments. You can type the command shown above manually.` : fallbackText
3334
3270
  };
3335
3271
  }
3336
3272
  function countFeishuCardElements(value, ancestors = /* @__PURE__ */ new Set()) {
3337
3273
  if (Array.isArray(value)) return value.reduce((count, entry) => count + countFeishuCardElements(entry, ancestors), 0);
3338
- if (!value || typeof value !== "object") return 0;
3274
+ if (!isRecord(value)) return 0;
3339
3275
  if (ancestors.has(value)) return 201;
3340
3276
  ancestors.add(value);
3341
- const record = value;
3342
- let count = typeof record.tag === "string" ? 1 : 0;
3343
- for (const entry of Object.values(record)) {
3277
+ let count = typeof value.tag === "string" ? 1 : 0;
3278
+ for (const entry of Object.values(value)) {
3344
3279
  count += countFeishuCardElements(entry, ancestors);
3345
3280
  if (count > FEISHU_CARD_MAX_ELEMENTS) break;
3346
3281
  }
@@ -3357,26 +3292,6 @@ function isFeishuCardWithinEnvelope(card) {
3357
3292
  function assertFeishuCardWithinEnvelope(card, label = "Feishu card") {
3358
3293
  if (!isFeishuCardWithinEnvelope(card)) throw new Error(`${label} exceeds the 30 KB or 200-element API limit.`);
3359
3294
  }
3360
- function escapeFeishuCardMarkdownText(text) {
3361
- return text.replace(/[&<>]/g, (char) => {
3362
- switch (char) {
3363
- case "&": return "&amp;";
3364
- case "<": return "&lt;";
3365
- case ">": return "&gt;";
3366
- default: return char;
3367
- }
3368
- });
3369
- }
3370
- function resolveSafeFeishuButtonUrl(url) {
3371
- const trimmed = url?.trim();
3372
- if (!trimmed) return;
3373
- try {
3374
- const parsed = new URL(trimmed);
3375
- return parsed.protocol === "https:" || parsed.protocol === "http:" ? trimmed : void 0;
3376
- } catch {
3377
- return;
3378
- }
3379
- }
3380
3295
  function resolveFeishuButtonUrl(button) {
3381
3296
  if (button.action?.type === "url" || button.action?.type === "web-app") return button.action.url;
3382
3297
  if (button.action) return;
@@ -3387,30 +3302,43 @@ function resolveFeishuCommandButtonValue(button) {
3387
3302
  if (button.action) return;
3388
3303
  return button.value;
3389
3304
  }
3305
+ function renderFeishuPresentationFallbackText(params, textFormat = "plain") {
3306
+ const presentation = params.presentation;
3307
+ return renderMessagePresentationFallbackText({
3308
+ ...params,
3309
+ presentation: presentation && {
3310
+ ...presentation,
3311
+ blocks: presentation.blocks.map((block) => block.type === "buttons" ? {
3312
+ type: block.type,
3313
+ buttons: block.buttons.map((button) => {
3314
+ const url = resolveFeishuButtonUrl(button);
3315
+ return {
3316
+ ...button,
3317
+ ...textFormat === "markdown" ? { label: escapeFeishuCardPlainText(button.label) } : {},
3318
+ ...url && !resolveSafeFeishuButtonUrl(url) ? { disabled: true } : {}
3319
+ };
3320
+ })
3321
+ } : block)
3322
+ }
3323
+ });
3324
+ }
3390
3325
  function mapFeishuButtonType(style) {
3391
3326
  if (style === "primary" || style === "success") return "primary";
3392
3327
  if (style === "danger") return "danger";
3393
3328
  return "default";
3394
3329
  }
3395
3330
  function buildFeishuPayloadButton(button) {
3396
- const behaviors = [];
3397
- const rendered = {
3398
- tag: "button",
3399
- text: {
3400
- tag: "plain_text",
3401
- content: button.label
3402
- },
3403
- type: mapFeishuButtonType(button.style)
3404
- };
3405
- const url = resolveFeishuButtonUrl(button);
3406
- if (url) {
3407
- const safeUrl = resolveSafeFeishuButtonUrl(url);
3408
- if (safeUrl) behaviors.push({
3409
- type: "open_url",
3410
- default_url: safeUrl
3411
- });
3412
- }
3331
+ const url = resolveSafeFeishuButtonUrl(resolveFeishuButtonUrl(button));
3413
3332
  const value = resolveFeishuCommandButtonValue(button);
3333
+ if (button.disabled || !url && !value) return {
3334
+ tag: "markdown",
3335
+ content: `- ${escapeFeishuCardPlainText(button.label)}`
3336
+ };
3337
+ const behaviors = [];
3338
+ if (url) behaviors.push({
3339
+ type: "open_url",
3340
+ default_url: url
3341
+ });
3414
3342
  if (value) behaviors.push({
3415
3343
  type: "callback",
3416
3344
  value: createFeishuCardInteractionEnvelope({
@@ -3419,9 +3347,15 @@ function buildFeishuPayloadButton(button) {
3419
3347
  q: value
3420
3348
  })
3421
3349
  });
3422
- if (behaviors.length === 0) return;
3423
- rendered.behaviors = behaviors;
3424
- return rendered;
3350
+ return {
3351
+ tag: "button",
3352
+ text: {
3353
+ tag: "plain_text",
3354
+ content: button.label
3355
+ },
3356
+ type: mapFeishuButtonType(button.style),
3357
+ behaviors
3358
+ };
3425
3359
  }
3426
3360
  function buildFeishuCardElementsForBlock(block) {
3427
3361
  if (block.type === "text") return [{
@@ -3433,7 +3367,7 @@ function buildFeishuCardElementsForBlock(block) {
3433
3367
  content: `<font color='grey'>${escapeFeishuCardMarkdownText(block.text)}</font>`
3434
3368
  }];
3435
3369
  if (block.type === "divider") return [{ tag: "hr" }];
3436
- if (block.type === "buttons") return block.buttons.map((button) => buildFeishuPayloadButton(button)).filter((button) => Boolean(button));
3370
+ if (block.type === "buttons") return block.buttons.map(buildFeishuPayloadButton);
3437
3371
  if (block.type === "chart") return [{
3438
3372
  tag: "markdown",
3439
3373
  content: escapeFeishuCardMarkdownText(renderMessagePresentationChartFallbackText(block))
@@ -3442,10 +3376,9 @@ function buildFeishuCardElementsForBlock(block) {
3442
3376
  tag: "markdown",
3443
3377
  content: escapeFeishuCardMarkdownText(renderMessagePresentationTableFallbackText(block))
3444
3378
  }];
3445
- const labels = block.options.map((option) => `- ${option.label}`).join("\n");
3446
3379
  return [{
3447
3380
  tag: "markdown",
3448
- content: `${escapeFeishuCardMarkdownText(block.placeholder?.trim() || "Options")}:\n${escapeFeishuCardMarkdownText(labels)}`
3381
+ content: escapeFeishuCardMarkdownText(renderMessagePresentationFallbackText({ presentation: { blocks: [block] } }))
3449
3382
  }];
3450
3383
  }
3451
3384
  function resolvePresentationHeaderTemplate(tone) {
@@ -3465,13 +3398,7 @@ function buildFeishuPresentationCardElements(params) {
3465
3398
  if (elements.length > 0) return elements;
3466
3399
  return [{
3467
3400
  tag: "markdown",
3468
- content: renderMessagePresentationFallbackText({
3469
- text: params.fallbackText,
3470
- presentation: params.presentation.title ? {
3471
- ...params.presentation.tone ? { tone: params.presentation.tone } : {},
3472
- blocks: params.presentation.blocks
3473
- } : params.presentation
3474
- })
3401
+ content: ""
3475
3402
  }];
3476
3403
  }
3477
3404
  function buildFeishuPresentationCard(params) {
@@ -3488,5 +3415,172 @@ function buildFeishuPresentationCard(params) {
3488
3415
  body: { elements: buildFeishuPresentationCardElements(params) }
3489
3416
  };
3490
3417
  }
3418
+ function markRenderedFeishuCard(card) {
3419
+ Object.defineProperty(card, RENDERED_FEISHU_CARD, {
3420
+ value: true,
3421
+ enumerable: false
3422
+ });
3423
+ return card;
3424
+ }
3425
+ function readNativeFeishuCard(payload) {
3426
+ const feishuData = payload.channelData?.feishu;
3427
+ if (!isRecord(feishuData)) return;
3428
+ const card = feishuData.card ?? feishuData.interactiveCard;
3429
+ if (!isRecord(card)) return;
3430
+ const rendered = card;
3431
+ if (rendered[RENDERED_FEISHU_CARD] === true) return rendered;
3432
+ const sanitizedCard = sanitizeNativeFeishuCard(card);
3433
+ return sanitizedCard ? markRenderedFeishuCard(sanitizedCard) : void 0;
3434
+ }
3435
+ function consumeFeishuPresentationFallbackMarker(payload) {
3436
+ const feishuData = isRecord(payload.channelData?.feishu) ? payload.channelData.feishu : void 0;
3437
+ const presentationFallback = feishuData?.[FEISHU_PRESENTATION_FALLBACK_MARKER];
3438
+ if (!isRecord(presentationFallback) || typeof presentationFallback.hasVisibleContent !== "boolean") return { payload };
3439
+ const nextFeishuData = { ...feishuData };
3440
+ delete nextFeishuData[FEISHU_PRESENTATION_FALLBACK_MARKER];
3441
+ const nextChannelData = { ...payload.channelData };
3442
+ if (Object.keys(nextFeishuData).length > 0) nextChannelData.feishu = nextFeishuData;
3443
+ else delete nextChannelData.feishu;
3444
+ return {
3445
+ payload: {
3446
+ ...payload,
3447
+ channelData: Object.keys(nextChannelData).length > 0 ? nextChannelData : void 0
3448
+ },
3449
+ presentationFallback: { hasVisibleContent: presentationFallback.hasVisibleContent }
3450
+ };
3451
+ }
3452
+ function buildFeishuPayloadCard(params) {
3453
+ const nativeCard = readNativeFeishuCard(params.payload);
3454
+ const rawText = params.text ?? params.payload.text;
3455
+ const textCard = readNativeFeishuCardJson(rawText);
3456
+ const { interactive, presentation } = resolveFeishuRichReply(params.payload);
3457
+ let card = nativeCard ?? (!presentation ? textCard : void 0);
3458
+ const isNativeCard = card !== void 0;
3459
+ if (!card && presentation) card = buildFeishuPresentationCard({
3460
+ presentation: {
3461
+ ...presentation,
3462
+ title: presentation.title ?? resolveFeishuIdentityHeaderTitle(params.identity)
3463
+ },
3464
+ fallbackText: textCard ? void 0 : resolveLegacyInteractiveTextFallback({
3465
+ text: rawText,
3466
+ interactive
3467
+ })
3468
+ });
3469
+ if (!card) return;
3470
+ if (params.mentions?.length) card = {
3471
+ ...card,
3472
+ body: {
3473
+ ...card.body,
3474
+ elements: [{
3475
+ tag: "markdown",
3476
+ content: buildMentionedCardContent(params.mentions, "").trimEnd()
3477
+ }, ...card.body.elements]
3478
+ }
3479
+ };
3480
+ if (isNativeCard) assertFeishuCardWithinEnvelope(card, "Feishu native card");
3481
+ return isFeishuCardWithinEnvelope(card) ? markRenderedFeishuCard(card) : void 0;
3482
+ }
3483
+ function renderFeishuPresentationPayload({ payload, presentation, sourcePresentation, ctx }) {
3484
+ const card = buildFeishuPayloadCard({
3485
+ payload,
3486
+ text: payload.text,
3487
+ identity: ctx.identity,
3488
+ mentions: ctx.mentions
3489
+ });
3490
+ const isComment = Boolean(parseFeishuCommentTarget(ctx.to));
3491
+ const fallbackPresentation = !card || isComment ? sourcePresentation ?? presentation : presentation;
3492
+ const { fallbackText, fallbackHasCommand } = buildFeishuPresentationFallback({
3493
+ text: readNativeFeishuCardJson(payload.text) ? void 0 : payload.text,
3494
+ presentation: fallbackPresentation,
3495
+ textFormat: isComment ? "plain" : "markdown"
3496
+ });
3497
+ const existingFeishuData = isRecord(payload.channelData?.feishu) ? payload.channelData.feishu : void 0;
3498
+ if (!card) return {
3499
+ ...payload,
3500
+ text: fallbackText,
3501
+ channelData: {
3502
+ ...payload.channelData,
3503
+ feishu: {
3504
+ ...existingFeishuData,
3505
+ [FEISHU_PRESENTATION_FALLBACK_MARKER]: { hasVisibleContent: Boolean(renderFeishuPresentationFallbackText({ presentation: fallbackPresentation }).trim()) },
3506
+ ...fallbackHasCommand ? { fallbackHasCommand: true } : {}
3507
+ }
3508
+ }
3509
+ };
3510
+ return {
3511
+ ...payload,
3512
+ text: fallbackText,
3513
+ channelData: {
3514
+ ...payload.channelData,
3515
+ feishu: {
3516
+ ...existingFeishuData,
3517
+ card,
3518
+ ...fallbackHasCommand ? { fallbackHasCommand: true } : {}
3519
+ }
3520
+ }
3521
+ };
3522
+ }
3523
+ async function renderFeishuReplyPayload(payload, ctx) {
3524
+ const { presentation } = resolveFeishuRichReply(payload);
3525
+ if (!presentation) return { payload };
3526
+ const { interactive: _interactive, ...withoutInteractive } = await renderPresentationForDelivery({
3527
+ presentationCapabilities: FEISHU_PRESENTATION_CAPABILITIES,
3528
+ renderPresentation: (adapted, sourcePresentation) => renderFeishuPresentationPayload({
3529
+ payload: adapted,
3530
+ presentation: adapted.presentation,
3531
+ sourcePresentation,
3532
+ ctx
3533
+ })
3534
+ }, {
3535
+ ...payload,
3536
+ presentation
3537
+ });
3538
+ return {
3539
+ payload: withoutInteractive,
3540
+ card: readNativeFeishuCard(withoutInteractive)
3541
+ };
3542
+ }
3543
+ //#endregion
3544
+ //#region extensions/feishu/src/reply-delivery-result.ts
3545
+ const noVisibleFeishuReplyDelivery = { visibleReplySent: false };
3546
+ function hasProviderIdentity(result) {
3547
+ return Boolean(result && (result.messageId?.trim() || result.receipt?.primaryPlatformMessageId?.trim() || result.receipt?.platformMessageIds.length));
3548
+ }
3549
+ /** Normalizes every physical Lark send behind one logical reply payload. */
3550
+ function createFeishuReplyDeliveryResult(params) {
3551
+ const results = params.visibleReplySent ? (params.results ?? []).filter(hasProviderIdentity) : [];
3552
+ const receipt = results.length > 0 ? createMessageReceiptFromOutboundResults({
3553
+ results,
3554
+ ...params.kind ? { kind: params.kind } : {}
3555
+ }) : void 0;
3556
+ if (!receipt) return {
3557
+ visibleReplySent: params.visibleReplySent,
3558
+ ...params.content === void 0 ? {} : { content: params.content }
3559
+ };
3560
+ return {
3561
+ messageIds: [...receipt.platformMessageIds],
3562
+ receipt,
3563
+ visibleReplySent: params.visibleReplySent,
3564
+ ...params.content === void 0 ? {} : { content: params.content }
3565
+ };
3566
+ }
3567
+ /** Preserves the first result's provider identity while retaining supplemental ids. */
3568
+ function mergeFeishuReplyDeliveryResults(results, content) {
3569
+ const visible = results.filter((result) => result.visibleReplySent === true);
3570
+ const acceptedContent = visible.flatMap((result) => result.content === void 0 ? [] : [result.content]);
3571
+ return createFeishuReplyDeliveryResult({
3572
+ results: visible,
3573
+ visibleReplySent: visible.length > 0,
3574
+ content: content === void 0 ? acceptedContent.length > 0 ? acceptedContent.join("\n\n") : void 0 : content
3575
+ });
3576
+ }
3577
+ /** Keeps a failed outcome while exposing the provider-visible subset to core lifecycle hooks. */
3578
+ function createFeishuPartialReplyDeliveryError(cause, result) {
3579
+ if (result.visibleReplySent !== true) return cause instanceof Error ? cause : new Error(formatErrorMessage(cause), { cause });
3580
+ return createChannelPartialDeliveryError(cause, {
3581
+ ...result,
3582
+ visibleReplySent: true
3583
+ });
3584
+ }
3491
3585
  //#endregion
3492
- export { normalizeFeishuExternalKey as $, feishuExternalToolResult as A, formatFeishuApiError as B, parsePostContent as C, resolveFeishuIdentityHeaderTitle as D, resolveFeishuIdentityEmoji as E, resolveFeishuToolAccount as F, parseFeishuCommentTarget as G, requestFeishuApi as H, cleanupAmbientCommentTypingReaction as I, sanitizeNativeFeishuCard as J, readNativeFeishuCardJson as K, createCommentTypingReactionLifecycle as L, unknownToolActionResult as M, createFeishuToolClient as N, deliverCommentThreadText as O, resolveAnyEnabledFeishuToolsConfig as P, parseFeishuMarkdown as Q, encodeQuery as R, parseInteractiveCardContent as S, buildFeishuMediaFallbackText as T, buildFeishuCommentTarget as U, parseCommentContentElements as V, normalizeCommentFileType as W, chunkFeishuPostMarkdown as X, chunkFeishuMarkdown as Y, materializeFeishuPostMarkdownSoftBreaks as Z, sendStructuredCardFeishu as _, FEISHU_CARD_INTERACTION_VERSION as _t, isFeishuCardWithinEnvelope as a, isFeishuGroupReadEnabled as at, isMentionForwardRequest as b, decodeFeishuCardAction as bt, sendMediaFeishu as c, normalizeFeishuAllowEntry as ct, editMessageFeishu as d, resolveFeishuGroupConversationIngressAccess as dt, assertFeishuChatReadAllowed as et, getMessageFeishu as f, resolveFeishuGroupSenderActivationIngressAccess as ft, sendMessageFeishu as g, resolveFeishuChatType as gt, sendMarkdownCardFeishu as h, normalizeFeishuChatType as ht, buildFeishuPresentationCardElements as i, isFeishuGroupReadAllowed as it, toolExecutionErrorResult as j, registerFeishuDriveTools as k, sendStickerFeishu as l, resolveFeishuDmIngressAccess as lt, sendCardFeishu as m, resolveFeishuReplyPolicy as mt, buildFeishuCommentPresentationFallback as n, canEnumerateAllFeishuGroups as nt, resolveFeishuRichReply as o, resolveFeishuChatReadPreliminaryAuthorization as ot, listFeishuThreadMessages as p, resolveFeishuGroupToolPolicy as pt, resolveFeishuCardTemplate as q, buildFeishuPresentationCard as r, canEnumerateAllFeishuPeers as rt, saveMessageResourceFeishu as s, hasExplicitFeishuGroupConfig as st, assertFeishuCardWithinEnvelope as t, authorizeFeishuChatMemberRead as tt, shouldSuppressFeishuTextForVoiceMedia as u, resolveFeishuGroupConfig as ut, extractMentionTargets as v, buildFeishuCardActionTextFallback as vt, createFeishuSendReceipt as w, isFeishuGroupChatType as x, isFeishuBroadcastMention as y, createFeishuCardInteractionEnvelope as yt, extractReplyText as z };
3586
+ export { parseCommentContentElements as $, isFeishuBroadcastMention as A, decodeFeishuCardAction as At, deliverCommentThreadText as B, editMessageFeishu as C, resolveFeishuGroupToolPolicy as Ct, sendMessageFeishu as D, FEISHU_CARD_INTERACTION_VERSION as Dt, sendCardFeishu as E, resolveFeishuChatType as Et, createFeishuSendReceipt as F, createFeishuToolClient as G, feishuExternalToolResult as H, assertFeishuApiSuccess as I, cleanupAmbientCommentTypingReaction as J, resolveAnyEnabledFeishuToolsConfig as K, buildFeishuMediaFallbackText as L, isFeishuGroupChatType as M, parseInteractiveCardContent as N, sendStructuredCardFeishu as O, buildFeishuCardActionTextFallback as Ot, parsePostContent as P, formatFeishuApiError as Q, resolveFeishuIdentityEmoji as R, chunkFeishuCardMarkdown as S, resolveFeishuGroupSenderActivationIngressAccess as St, listFeishuThreadMessages as T, normalizeFeishuChatType as Tt, toolExecutionErrorResult as U, registerFeishuDriveTools as V, unknownToolActionResult as W, encodeQuery as X, createCommentTypingReactionLifecycle as Y, extractReplyText as Z, resolveFeishuRichReply as _, hasExplicitFeishuGroupConfig as _t, FEISHU_PRESENTATION_CAPABILITIES as a, resolveFeishuCardTemplate as at, sendStickerFeishu as b, resolveFeishuGroupConfig as bt, buildFeishuPresentationCard as c, materializeFeishuPostMarkdownSoftBreaks as ct, isFeishuCardWithinEnvelope as d, authorizeFeishuChatMemberRead as dt, requestFeishuApi as et, markRenderedFeishuCard as f, canEnumerateAllFeishuGroups as ft, renderFeishuReplyPayload as g, resolveFeishuChatReadPreliminaryAuthorization as gt, renderFeishuPresentationPayload as h, isFeishuGroupReadEnabled as ht, noVisibleFeishuReplyDelivery as i, readNativeFeishuCardJson as it, isMentionForwardRequest as j, extractMentionTargets as k, createFeishuCardInteractionEnvelope as kt, buildFeishuPresentationFallback as l, parseFeishuMarkdown as lt, renderFeishuPresentationFallbackText as m, isFeishuGroupReadAllowed as mt, createFeishuReplyDeliveryResult as n, normalizeCommentFileType as nt, assertFeishuCardWithinEnvelope as o, chunkFeishuMarkdown as ot, readNativeFeishuCard as p, canEnumerateAllFeishuPeers as pt, resolveFeishuToolAccount as q, mergeFeishuReplyDeliveryResults as r, parseFeishuCommentTarget as rt, buildFeishuPayloadCard as s, chunkFeishuPostMarkdown as st, createFeishuPartialReplyDeliveryError as t, buildFeishuCommentTarget as tt, consumeFeishuPresentationFallbackMarker as u, assertFeishuChatReadAllowed as ut, saveMessageResourceFeishu as v, normalizeFeishuAllowEntry as vt, getMessageFeishu as w, resolveFeishuReplyPolicy as wt, shouldSuppressFeishuTextForVoiceMedia as x, resolveFeishuGroupConversationIngressAccess as xt, sendMediaFeishu as y, resolveFeishuDmIngressAccess as yt, resolveFeishuIdentityHeaderTitle as z };