@openclaw/feishu 2026.9.1-beta.1 → 2026.9.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (34) hide show
  1. package/dist/api.js +211 -275
  2. package/dist/{app-registration-BpfUzS9m.js → app-registration-BI99e_JI.js} +12 -9
  3. package/dist/{channel-Dpgf8wLD.js → channel-DSYCNah8.js} +239 -580
  4. package/dist/channel-plugin-api.js +1 -1
  5. package/dist/{channel.runtime-BIEERwaT.js → channel.runtime-EfDl2Q8q.js} +14 -18
  6. package/dist/{chat-C1nuW5UY.js → chat-CxXaPTuO.js} +13 -13
  7. package/dist/{client-DalLmpMf.js → client-DMbIL3UH.js} +3 -6
  8. package/dist/contract-api.js +2 -2
  9. package/dist/doctor-contract-CQAuBotF.js +465 -0
  10. package/dist/doctor-contract-api.js +1 -1
  11. package/dist/index.js +11 -42
  12. package/dist/{json-response-CheljwGr.js → json-response-DXajw_FU.js} +1 -1
  13. package/dist/{monitor.startup-D-gL82OO.js → monitor-DoMmOfaD.js} +62 -2
  14. package/dist/{monitor.account-DhhotI_Q.js → monitor.account-WFdlCDhR.js} +261 -293
  15. package/dist/{probe-CfhhR_0a.js → probe-D_SatdZl.js} +3 -3
  16. package/dist/{presentation-card-C8Z7Tgru.js → reply-delivery-result-CEFTDA3f.js} +333 -239
  17. package/dist/{security-audit-D6Fz2h6p.js → security-audit-DKfR4Cv3.js} +1 -1
  18. package/dist/{security-audit-shared-BgpY7AiJ.js → security-audit-shared-CK5rVR-1.js} +2 -1
  19. package/dist/security-contract-api.js +1 -1
  20. package/dist/session-binding-contract-api.js +1 -1
  21. package/dist/setup-api.js +1 -1
  22. package/dist/{subagent-hooks-KUi-7vUy.js → subagent-hooks-Buadpie_.js} +3 -6
  23. package/dist/subagent-hooks-api.js +1 -1
  24. package/dist/{targets-BUjQ1TcA.js → targets-BTQCYRZQ.js} +4 -4
  25. package/dist/{thread-bindings-CptM_YU6.js → thread-bindings-BiL1wGqK.js} +20 -9
  26. package/node_modules/@larksuiteoapi/node-sdk/node_modules/ws/lib/permessage-deflate.js +3 -1
  27. package/node_modules/@larksuiteoapi/node-sdk/node_modules/ws/package.json +1 -1
  28. package/node_modules/typebox/build/format/email.mjs +1 -1
  29. package/node_modules/typebox/build/format/idna/idn-hostname.mjs +19 -1
  30. package/node_modules/typebox/package.json +1 -1
  31. package/openclaw.plugin.json +43 -236
  32. package/package.json +6 -5
  33. package/dist/doctor-contract-bEQXIXyP.js +0 -158
  34. package/dist/monitor-BRg3sHH7.js +0 -62
@@ -1,18 +1,18 @@
1
1
  import { o as resolveFeishuAccount, s as resolveFeishuRuntimeAccount } from "./accounts-RwSyseKr.js";
2
- import { i as resolveReceiveIdType } from "./targets-BUjQ1TcA.js";
3
- import { $ as normalizeFeishuExternalKey, C as parsePostContent, E as resolveFeishuIdentityEmoji, H as requestFeishuApi, L as createCommentTypingReactionLifecycle, O as deliverCommentThreadText, R as encodeQuery, S as parseInteractiveCardContent, T as buildFeishuMediaFallbackText, U as buildFeishuCommentTarget, V as parseCommentContentElements, W as normalizeCommentFileType, X as chunkFeishuPostMarkdown, Z as materializeFeishuPostMarkdownSoftBreaks, _ as sendStructuredCardFeishu, b as isMentionForwardRequest, bt as decodeFeishuCardAction, c as sendMediaFeishu, ct as normalizeFeishuAllowEntry, dt as resolveFeishuGroupConversationIngressAccess, f as getMessageFeishu, ft as resolveFeishuGroupSenderActivationIngressAccess, g as sendMessageFeishu, gt as resolveFeishuChatType, ht as normalizeFeishuChatType$2, lt as resolveFeishuDmIngressAccess, m as sendCardFeishu, mt as resolveFeishuReplyPolicy, n as buildFeishuCommentPresentationFallback, o as resolveFeishuRichReply, p as listFeishuThreadMessages, q as resolveFeishuCardTemplate, s as saveMessageResourceFeishu, st as hasExplicitFeishuGroupConfig, u as shouldSuppressFeishuTextForVoiceMedia, ut as resolveFeishuGroupConfig, v as extractMentionTargets, vt as buildFeishuCardActionTextFallback, x as isFeishuGroupChatType, y as isFeishuBroadcastMention, yt as createFeishuCardInteractionEnvelope, z as extractReplyText } from "./presentation-card-C8Z7Tgru.js";
4
- import { i as normalizeFeishuWebhookPath } from "./doctor-contract-bEQXIXyP.js";
2
+ import { i as resolveReceiveIdType } from "./targets-BTQCYRZQ.js";
3
+ import { $ as parseCommentContentElements, A as isFeishuBroadcastMention, At as decodeFeishuCardAction, B as deliverCommentThreadText, D as sendMessageFeishu, E as sendCardFeishu, Et as resolveFeishuChatType, L as buildFeishuMediaFallbackText, M as isFeishuGroupChatType, N as parseInteractiveCardContent, O as sendStructuredCardFeishu, Ot as buildFeishuCardActionTextFallback, P as parsePostContent, R as resolveFeishuIdentityEmoji, S as chunkFeishuCardMarkdown, St as resolveFeishuGroupSenderActivationIngressAccess, T as listFeishuThreadMessages, Tt as normalizeFeishuChatType$2, X as encodeQuery, Y as createCommentTypingReactionLifecycle, Z as extractReplyText, _ as resolveFeishuRichReply, _t as hasExplicitFeishuGroupConfig, at as resolveFeishuCardTemplate, bt as resolveFeishuGroupConfig, ct as materializeFeishuPostMarkdownSoftBreaks, et as requestFeishuApi, g as renderFeishuReplyPayload, i as noVisibleFeishuReplyDelivery, j as isMentionForwardRequest, k as extractMentionTargets, kt as createFeishuCardInteractionEnvelope, l as buildFeishuPresentationFallback, n as createFeishuReplyDeliveryResult, nt as normalizeCommentFileType, r as mergeFeishuReplyDeliveryResults, st as chunkFeishuPostMarkdown, t as createFeishuPartialReplyDeliveryError, tt as buildFeishuCommentTarget, u as consumeFeishuPresentationFallbackMarker, v as saveMessageResourceFeishu, vt as normalizeFeishuAllowEntry, w as getMessageFeishu, wt as resolveFeishuReplyPolicy, x as shouldSuppressFeishuTextForVoiceMedia, xt as resolveFeishuGroupConversationIngressAccess, y as sendMediaFeishu, yt as resolveFeishuDmIngressAccess } from "./reply-delivery-result-CEFTDA3f.js";
4
+ import { a as normalizeFeishuWebhookPath, o as normalizeFeishuExternalKey } from "./doctor-contract-CQAuBotF.js";
5
5
  import { o as resolveConfiguredFeishuGroupSessionScope, t as buildFeishuConversationId } from "./conversation-id-VYgGQ-GX.js";
6
- import { a as getFeishuUserAgent, i as createFeishuWSClient, n as createEventDispatcher, r as createFeishuClient, s as resolveConfiguredHttpTimeoutMs } from "./client-DalLmpMf.js";
6
+ import { a as getFeishuUserAgent, i as createFeishuWSClient, n as createEventDispatcher, r as createFeishuClient, s as resolveConfiguredHttpTimeoutMs } from "./client-DMbIL3UH.js";
7
7
  import { t as getFeishuRuntime } from "./runtime-C5JxBWZp.js";
8
- import { r as getChatInfo } from "./chat-C1nuW5UY.js";
9
- import { t as createFeishuThreadBindingManager } from "./thread-bindings-CptM_YU6.js";
8
+ import { r as getChatInfo } from "./chat-CxXaPTuO.js";
9
+ import { t as createFeishuThreadBindingManager } from "./thread-bindings-BiL1wGqK.js";
10
10
  import { evaluateSupplementalContextVisibility, normalizeAgentId as normalizeAgentId$1, resolveChannelContextVisibilityMode } from "./runtime-api.js";
11
- import { t as readFeishuJsonResponse } from "./json-response-CheljwGr.js";
12
- import { a as waitForAbortableDelay, i as raceWithTimeoutAndAbort } from "./probe-CfhhR_0a.js";
13
- import { t as fetchBotIdentityForMonitor } from "./monitor.startup-D-gL82OO.js";
11
+ import { t as readFeishuJsonResponse } from "./json-response-DXajw_FU.js";
12
+ import { a as waitForAbortableDelay, i as raceWithTimeoutAndAbort } from "./probe-D_SatdZl.js";
13
+ import { t as fetchBotIdentityForMonitor } from "./monitor-DoMmOfaD.js";
14
14
  import { resolveAgentConfig } from "openclaw/plugin-sdk/agent-scope-runtime";
15
- import { DEFAULT_INGRESS_ADOPTION_STALL_MS, bindIngressLifecycleToReplyOptions, createChannelIngressError, createChannelIngressMonitor, createChannelMessageReplyPipeline, createMessageReceiptFromOutboundResults, createReplyPrefixContext, formatChannelProgressDraftLineForEntry, isChannelProgressDraftWorkToolName, resolveAgentOutboundIdentity, resolveChannelPreviewStreamMode, resolveChannelStreamingBlockEnabled } from "openclaw/plugin-sdk/channel-outbound";
15
+ import { DEFAULT_INGRESS_ADOPTION_STALL_MS, bindIngressLifecycleToReplyOptions, createChannelIngressError, createChannelIngressMonitor, createChannelMessageReplyPipeline, createReplyPrefixContext, formatChannelProgressDraftLineForEntry, isChannelProgressDraftWorkToolName, resolveAgentOutboundIdentity, resolveChannelPreviewStreamMode, resolveChannelStreamingBlockEnabled } from "openclaw/plugin-sdk/channel-outbound";
16
16
  import { createChannelPairingController } from "openclaw/plugin-sdk/channel-pairing";
17
17
  import { ensureConfiguredBindingRouteReady, resolveConfiguredBindingRoute, resolveRuntimeConversationBindingRoute } from "openclaw/plugin-sdk/conversation-runtime";
18
18
  import { collectErrorGraphCandidates, formatErrorMessage, toStringifiedError } from "openclaw/plugin-sdk/error-runtime";
@@ -26,7 +26,7 @@ import os from "node:os";
26
26
  import path from "node:path";
27
27
  import { normalizeAccountId, resolveAgentRoute, resolveInboundLastRouteSessionKey } from "openclaw/plugin-sdk/routing";
28
28
  import { getSessionEntry, resolveStorePath } from "openclaw/plugin-sdk/session-store-runtime";
29
- import { createChannelPartialDeliveryError, formatAgentEnvelope, formatInboundMediaUnavailableText, isChannelPartialDeliveryError, recordChannelBotPairLoopAndCheckSuppression, resolveEnvelopeFormatOptions, resolveInboundReplyDispatchCounts, toInboundMediaFactsWithMetadata } from "openclaw/plugin-sdk/channel-inbound";
29
+ import { formatAgentEnvelope, formatInboundMediaUnavailableText, isChannelPartialDeliveryError, recordChannelBotPairLoopAndCheckSuppression, resolveEnvelopeFormatOptions, resolveInboundReplyDispatchCounts, toInboundMediaFactsWithMetadata } from "openclaw/plugin-sdk/channel-inbound";
30
30
  import { getReplyPayloadTtsSupplement, resolveSendableOutboundReplyParts, resolveTextChunksWithFallback, sendMediaWithLeadingCaption } from "openclaw/plugin-sdk/reply-payload";
31
31
  import { resolvePinnedMainDmOwnerFromAllowlist, safeEqualSecret } from "openclaw/plugin-sdk/security-runtime";
32
32
  import * as Lark from "@larksuiteoapi/node-sdk";
@@ -53,7 +53,7 @@ function formatMentionNameForAgentContext(name) {
53
53
  const normalized = Array.from(name, (char) => {
54
54
  return char.charCodeAt(0) < 32 || char === "[" || char === "]" ? " " : char;
55
55
  }).join("").replace(/\s+/g, " ").trim();
56
- const bounded = normalized.length > MAX_MENTION_CONTEXT_NAME_LENGTH ? `${truncateUtf16Safe(normalized, MAX_MENTION_CONTEXT_NAME_LENGTH - 3)}...` : normalized;
56
+ const bounded = normalized.length > MAX_MENTION_CONTEXT_NAME_LENGTH ? `${truncateUtf16Safe(normalized, 77)}...` : normalized;
57
57
  return JSON.stringify(bounded || "unknown");
58
58
  }
59
59
  function buildFeishuAgentBody(params) {
@@ -196,6 +196,7 @@ function createFeishuBroadcastIngressSettlement(params) {
196
196
  lane.status = "deferred";
197
197
  defer();
198
198
  },
199
+ onDeferredHeartbeat: () => params.lifecycle?.onDeferredHeartbeat?.(),
199
200
  onAdoptionFinalizing: beginFinalizing,
200
201
  onAbandoned: async () => {
201
202
  if (lane.status === "completed" || lane.status === "failed" || lane.status === "abandoned") return;
@@ -289,9 +290,7 @@ function resolveFeishuGroupSession(params) {
289
290
  senderOpenId
290
291
  });
291
292
  break;
292
- default:
293
- peerId = chatId;
294
- break;
293
+ default: peerId = chatId;
295
294
  }
296
295
  return {
297
296
  peerId,
@@ -540,7 +539,7 @@ async function resolveFeishuMediaList(params) {
540
539
  const feishuGroupNameCache = /* @__PURE__ */ new Map();
541
540
  //#endregion
542
541
  //#region extensions/feishu/src/bot-group-name.ts
543
- const GROUP_NAME_CACHE_TTL_MS = 1800 * 1e3;
542
+ const GROUP_NAME_CACHE_TTL_MS = 18e5;
544
543
  const GROUP_NAME_CACHE_MAX_SIZE = 500;
545
544
  function evictGroupNameCache() {
546
545
  const now = asDateTimestampMs(Date.now());
@@ -582,7 +581,8 @@ async function resolveGroupName(params) {
582
581
  }
583
582
  let resolvedName;
584
583
  try {
585
- const name = (await getChatInfo(createFeishuClient(account), chatId))?.name?.trim();
584
+ const client = createFeishuClient(account);
585
+ const name = (await getChatInfo(client, chatId))?.name?.trim();
586
586
  if (name) {
587
587
  setCacheEntry(cacheKey, name);
588
588
  resolvedName = name;
@@ -596,11 +596,11 @@ async function resolveGroupName(params) {
596
596
  }
597
597
  //#endregion
598
598
  //#region extensions/feishu/src/bot-name.ts
599
- const POSITIVE_TTL_MS = 10 * 6e4;
599
+ const POSITIVE_TTL_MS = 6e5;
600
600
  const NEGATIVE_TTL_MS = 6e4;
601
601
  const MAX_CACHE_ENTRIES = 5e3;
602
602
  const BREAKER_FAILURE_THRESHOLD = 10;
603
- const BREAKER_OPEN_MS = 60 * 6e4;
603
+ const BREAKER_OPEN_MS = 36e5;
604
604
  const PERMISSION_BACKOFF_MS = 6e4;
605
605
  const REQUEST_TIMEOUT_MS = 1500;
606
606
  const cache = /* @__PURE__ */ new Map();
@@ -738,8 +738,8 @@ async function resolveFeishuBotName(params) {
738
738
  const IGNORED_PERMISSION_SCOPE_TOKENS = ["contact:contact.base:readonly"];
739
739
  const FEISHU_SCOPE_CORRECTIONS = { "contact:contact.base:readonly": "contact:user.base:readonly" };
740
740
  const FEISHU_USER_LOOKUP_UNAUTHORIZED_CODE = 41050;
741
- const SENDER_NAME_TTL_MS = 600 * 1e3;
742
- const SENDER_NAME_NEGATIVE_TTL_MS = 1800 * 1e3;
741
+ const SENDER_NAME_TTL_MS = 6e5;
742
+ const SENDER_NAME_NEGATIVE_TTL_MS = 18e5;
743
743
  const SENDER_NAME_CACHE_MAX_SIZE = 500;
744
744
  const senderNameCache = /* @__PURE__ */ new Map();
745
745
  function correctFeishuScopeInUrl(url) {
@@ -840,7 +840,7 @@ async function resolveFeishuSenderName(params) {
840
840
  //#endregion
841
841
  //#region extensions/feishu/src/dedup-state.ts
842
842
  const DEDUPE_NAMESPACE_PREFIX = "feishu.dedup";
843
- const DEDUP_TTL_MS = 1440 * 60 * 1e3;
843
+ const DEDUP_TTL_MS = 864e5;
844
844
  const MEMORY_MAX_SIZE = 1e3;
845
845
  const STORE_MAX_ENTRIES = 1e4;
846
846
  function createFeishuDedupeGuard() {
@@ -1145,48 +1145,6 @@ function resolveFeishuReasoningPreviewEnabled(params) {
1145
1145
  return configDefault === "stream";
1146
1146
  }
1147
1147
  //#endregion
1148
- //#region extensions/feishu/src/reply-delivery-result.ts
1149
- const noVisibleFeishuReplyDelivery = { visibleReplySent: false };
1150
- function hasProviderIdentity(result) {
1151
- return Boolean(result && (result.messageId?.trim() || result.receipt?.primaryPlatformMessageId?.trim() || result.receipt?.platformMessageIds.length));
1152
- }
1153
- /** Normalizes every physical Lark send behind one logical reply payload. */
1154
- function createFeishuReplyDeliveryResult(params) {
1155
- const results = params.visibleReplySent ? (params.results ?? []).filter(hasProviderIdentity) : [];
1156
- const receipt = results.length > 0 ? createMessageReceiptFromOutboundResults({
1157
- results,
1158
- ...params.kind ? { kind: params.kind } : {}
1159
- }) : void 0;
1160
- if (!receipt) return {
1161
- visibleReplySent: params.visibleReplySent,
1162
- ...params.content === void 0 ? {} : { content: params.content }
1163
- };
1164
- return {
1165
- messageIds: [...receipt.platformMessageIds],
1166
- receipt,
1167
- visibleReplySent: params.visibleReplySent,
1168
- ...params.content === void 0 ? {} : { content: params.content }
1169
- };
1170
- }
1171
- /** Preserves the first result's provider identity while retaining supplemental ids. */
1172
- function mergeFeishuReplyDeliveryResults(results, content) {
1173
- const visible = results.filter((result) => result.visibleReplySent === true);
1174
- const acceptedContent = visible.flatMap((result) => result.content === void 0 ? [] : [result.content]);
1175
- return createFeishuReplyDeliveryResult({
1176
- results: visible,
1177
- visibleReplySent: visible.length > 0,
1178
- content: content === void 0 ? acceptedContent.length > 0 ? acceptedContent.join("\n\n") : void 0 : content
1179
- });
1180
- }
1181
- /** Keeps a failed outcome while exposing the provider-visible subset to core lifecycle hooks. */
1182
- function createFeishuPartialReplyDeliveryError(cause, result) {
1183
- if (result.visibleReplySent !== true) return cause instanceof Error ? cause : new Error(formatErrorMessage(cause), { cause });
1184
- return createChannelPartialDeliveryError(cause, {
1185
- ...result,
1186
- visibleReplySent: true
1187
- });
1188
- }
1189
- //#endregion
1190
1148
  //#region extensions/feishu/src/reply-dispatcher-state.ts
1191
1149
  const streamingStartBackoffUntilByAccount = /* @__PURE__ */ new Map();
1192
1150
  //#endregion
@@ -1662,21 +1620,10 @@ var FeishuStreamingSession = class {
1662
1620
  if (finalWriteError !== void 0 || closeError !== void 0) throw new FeishuStreamingFinalizationError(finalWriteError !== void 0 && closeError !== void 0 ? new AggregateError([finalWriteError, closeError], "Feishu streaming card finalization failed") : finalWriteError ?? closeError, result);
1663
1621
  return result;
1664
1622
  }
1665
- async close(finalText, options) {
1666
- try {
1667
- return (await this.closeWithResult(finalText, options)).visibleReplySent;
1668
- } catch (error) {
1669
- if (error instanceof FeishuStreamingFinalizationError) return error.result.visibleReplySent;
1670
- throw error;
1671
- }
1672
- }
1673
1623
  async discard() {
1674
- if (!this.state || this.closed) return;
1624
+ if (!this.state || this.closed) return { visibleReplySent: false };
1675
1625
  const { cardId, messageId } = this.state;
1676
- if (!messageId) {
1677
- await this.close("");
1678
- return;
1679
- }
1626
+ if (!messageId) return this.closeWithResult("");
1680
1627
  this.closed = true;
1681
1628
  this.clearFlushTimer();
1682
1629
  await this.queue;
@@ -1686,10 +1633,11 @@ var FeishuStreamingSession = class {
1686
1633
  this.state = null;
1687
1634
  this.pendingText = null;
1688
1635
  this.log?.(`Discarded streaming card: cardId=${cardId}`);
1636
+ return { visibleReplySent: false };
1689
1637
  } catch (error) {
1690
1638
  this.log?.(`Discard failed: ${String(error)}`);
1691
1639
  this.closed = false;
1692
- await this.close("");
1640
+ return this.closeWithResult("");
1693
1641
  }
1694
1642
  }
1695
1643
  isActive() {
@@ -1820,7 +1768,7 @@ function mergeStreamingFinalText(previousText, nextText, appendError) {
1820
1768
  }
1821
1769
  /** Maximum age (ms) for a message to receive a typing indicator reaction.
1822
1770
  * Messages older than this are likely replays after context compaction (#30418). */
1823
- const TYPING_INDICATOR_MAX_AGE_MS = 2 * 6e4;
1771
+ const TYPING_INDICATOR_MAX_AGE_MS = 12e4;
1824
1772
  const MS_EPOCH_MIN = 0xe8d4a51000;
1825
1773
  const STREAMING_START_FAILURE_BACKOFF_MS = 6e4;
1826
1774
  const NO_VISIBLE_REPLY_FALLBACK_TEXT = "⚠️ This reply completed without visible content. The turn may have been interrupted; please retry or ask me to recover from recent context.";
@@ -1947,9 +1895,7 @@ function createFeishuReplyDispatcher(params) {
1947
1895
  let activeStreamingGeneration;
1948
1896
  let inFlightStreamingClose;
1949
1897
  let visibleReplySent = false;
1950
- let skippedFinalReason = null;
1951
- let skippedFinalAssistantMessageIndex;
1952
- let preparedDeliveryAssistantMessageIndex;
1898
+ let replyOutcome;
1953
1899
  let idleSideEffectsPromise = Promise.resolve();
1954
1900
  let activeIdleSideEffectsPromise = null;
1955
1901
  let idleRequestedForReply = false;
@@ -2049,97 +1995,86 @@ function createFeishuReplyDispatcher(params) {
2049
1995
  lastSnapshotTextLength = 0;
2050
1996
  hasStreamingFinalText = false;
2051
1997
  };
2052
- const rememberClosedStreamingSettlement = (generation, content, result, error) => {
2053
- if (generation === void 0 || !content) return;
1998
+ const rememberClosedStreamingSettlement = (generation, content, result, error, disposition = "closed") => {
1999
+ if (generation === void 0 || !content && disposition === "closed") return;
2054
2000
  closedStreamingSettlements.set(generation, {
2001
+ disposition,
2055
2002
  result,
2056
2003
  content,
2057
2004
  ...error === void 0 ? {} : { error }
2058
2005
  });
2059
2006
  };
2060
- const performStreamingClose = async () => {
2007
+ const performStreamingClose = async (disposition) => {
2061
2008
  const streamingToClose = streaming;
2062
2009
  const generationToClose = activeStreamingGeneration;
2063
2010
  const startPromiseToClose = streamingStartPromise;
2064
2011
  const updateQueueToClose = partialUpdateQueue;
2065
2012
  const finalizedAnswerText = streamText;
2066
2013
  const finalizedReasoningText = reasoningText;
2014
+ const outcome = {
2015
+ disposition,
2016
+ ...generationToClose === void 0 ? {} : { generation: generationToClose }
2017
+ };
2067
2018
  if (generationToClose !== void 0 && activeStreamingGeneration === generationToClose) activeStreamingGeneration = void 0;
2068
2019
  try {
2069
2020
  if (startPromiseToClose) await startPromiseToClose;
2070
2021
  await updateQueueToClose;
2022
+ let result = noVisibleFeishuReplyDelivery;
2023
+ let finalizationError;
2071
2024
  if (streamingToClose?.isActive()) {
2072
2025
  statusLine = "";
2073
2026
  const text = buildCombinedStreamText(finalizedReasoningText, finalizedAnswerText);
2074
- const finalNote = resolveCardNote(agentId, identity, responsePrefixContextProvider());
2075
2027
  let closed;
2076
2028
  try {
2077
- closed = await streamingToClose.closeWithResult(text, { note: finalNote });
2029
+ if (disposition === "discarded") closed = await streamingToClose.discard();
2030
+ else {
2031
+ const finalNote = resolveCardNote(agentId, identity, responsePrefixContextProvider());
2032
+ closed = await streamingToClose.closeWithResult(text, { note: finalNote });
2033
+ }
2078
2034
  } catch (error) {
2079
2035
  if (!(error instanceof FeishuStreamingFinalizationError)) throw error;
2080
- const failedResult = createFeishuReplyDeliveryResult({
2081
- results: [error.result],
2082
- visibleReplySent: error.result.visibleReplySent,
2083
- content: error.result.content,
2084
- kind: "card"
2085
- });
2086
- if (failedResult.visibleReplySent) markVisibleReplySent();
2087
- if (failedResult.visibleReplySent && finalizedAnswerText) {
2088
- const partialError = createFeishuPartialReplyDeliveryError(error.cause ?? error, failedResult);
2089
- if (failedResult.content === text) deliveredFinalTexts.add(finalizedAnswerText);
2090
- rememberClosedStreamingSettlement(generationToClose, finalizedAnswerText, failedResult, partialError);
2091
- return {
2092
- result: failedResult,
2093
- ...generationToClose === void 0 ? {} : { generation: generationToClose },
2094
- error: partialError
2095
- };
2096
- }
2097
- rememberClosedStreamingSettlement(generationToClose, finalizedAnswerText, failedResult, error);
2098
- return {
2099
- result: failedResult,
2100
- ...generationToClose === void 0 ? {} : { generation: generationToClose },
2101
- error
2102
- };
2036
+ closed = error.result;
2037
+ finalizationError = error;
2103
2038
  }
2104
- const result = createFeishuReplyDeliveryResult({
2039
+ result = createFeishuReplyDeliveryResult({
2105
2040
  results: [closed],
2106
2041
  visibleReplySent: closed.visibleReplySent,
2107
2042
  content: closed.content,
2108
2043
  kind: "card"
2109
2044
  });
2110
2045
  if (result.visibleReplySent) markVisibleReplySent();
2111
- if (result.visibleReplySent && finalizedAnswerText) {
2112
- deliveredFinalTexts.add(finalizedAnswerText);
2113
- rememberClosedStreamingSettlement(generationToClose, finalizedAnswerText, result);
2114
- }
2115
- return {
2116
- result,
2117
- ...generationToClose === void 0 ? {} : { generation: generationToClose }
2118
- };
2046
+ if (disposition === "closed" && result.visibleReplySent && finalizedAnswerText && (finalizationError === void 0 || result.content === text)) deliveredFinalTexts.add(finalizedAnswerText);
2047
+ if (finalizationError instanceof FeishuStreamingFinalizationError && result.visibleReplySent) finalizationError = createFeishuPartialReplyDeliveryError(finalizationError.cause ?? finalizationError, result);
2119
2048
  }
2049
+ if (disposition === "discarded" || finalizationError !== void 0 || result.visibleReplySent && finalizedAnswerText) rememberClosedStreamingSettlement(generationToClose, finalizedAnswerText, result, finalizationError, disposition);
2120
2050
  return {
2121
- result: noVisibleFeishuReplyDelivery,
2122
- ...generationToClose === void 0 ? {} : { generation: generationToClose }
2051
+ ...outcome,
2052
+ result,
2053
+ ...finalizationError === void 0 ? {} : { error: finalizationError }
2123
2054
  };
2124
2055
  } catch (error) {
2056
+ if (disposition === "discarded") rememberClosedStreamingSettlement(generationToClose, finalizedAnswerText, noVisibleFeishuReplyDelivery, error, disposition);
2125
2057
  return {
2058
+ ...outcome,
2126
2059
  result: noVisibleFeishuReplyDelivery,
2127
- ...generationToClose === void 0 ? {} : { generation: generationToClose },
2128
2060
  error
2129
2061
  };
2130
2062
  } finally {
2131
2063
  if (streaming === streamingToClose) resetStreamingState();
2132
2064
  }
2133
2065
  };
2134
- const closeStreaming = () => {
2066
+ const closeStreaming = (disposition = "closed") => {
2067
+ const session = streaming;
2135
2068
  const generation = activeStreamingGeneration;
2136
- if (generation !== void 0 && inFlightStreamingClose?.generation === generation) return inFlightStreamingClose.promise;
2069
+ if (session && inFlightStreamingClose?.session === session) return inFlightStreamingClose.promise;
2137
2070
  const content = streamText;
2138
- const closePromise = performStreamingClose();
2139
- if (generation !== void 0) {
2071
+ const closePromise = performStreamingClose(disposition);
2072
+ if (session && generation !== void 0) {
2140
2073
  const closing = {
2074
+ session,
2141
2075
  generation,
2142
2076
  content,
2077
+ disposition,
2143
2078
  promise: closePromise
2144
2079
  };
2145
2080
  inFlightStreamingClose = closing;
@@ -2171,13 +2106,12 @@ function createFeishuReplyDispatcher(params) {
2171
2106
  };
2172
2107
  };
2173
2108
  const discardStreamingPreview = async () => {
2174
- try {
2175
- if (streamingStartPromise) await streamingStartPromise;
2176
- await partialUpdateQueue;
2177
- if (streaming?.isActive()) await streaming.discard();
2178
- } finally {
2109
+ if (streaming && inFlightStreamingClose?.session === streaming && inFlightStreamingClose.disposition === "closed") {
2179
2110
  resetStreamingState();
2111
+ return;
2180
2112
  }
2113
+ const outcome = await closeStreaming("discarded");
2114
+ if (outcome.error !== void 0) throw toStringifiedError(outcome.error);
2181
2115
  };
2182
2116
  const updateStreamingStatusLine = (nextStatusLine, options) => {
2183
2117
  statusLine = nextStatusLine;
@@ -2189,14 +2123,19 @@ function createFeishuReplyDispatcher(params) {
2189
2123
  const sendChunkedTextReply = async (paramsLocal) => {
2190
2124
  const chunkSource = paramsLocal.useCard ? paramsLocal.text : materializeFeishuPostMarkdownSoftBreaks(core.channel.text.convertMarkdownTables(paramsLocal.text, tableMode));
2191
2125
  const initialChunks = core.channel.text.chunkMarkdownTextWithMode(chunkSource, textChunkLimit, chunkMode);
2192
- const chunks = resolveTextChunksWithFallback(chunkSource, paramsLocal.useCard ? initialChunks : chunkFeishuPostMarkdown({
2126
+ const chunkOptions = {
2193
2127
  text: chunkSource,
2194
2128
  limit: textChunkLimit,
2195
2129
  mode: chunkMode,
2196
2130
  firstChunkMentions: paramsLocal.firstChunkMentions,
2197
2131
  chunkMentions: paramsLocal.chunkMentions,
2198
2132
  initialChunks
2199
- }));
2133
+ };
2134
+ const chunks = resolveTextChunksWithFallback(chunkSource, paramsLocal.useCard ? chunkFeishuCardMarkdown({
2135
+ ...chunkOptions,
2136
+ header: paramsLocal.header,
2137
+ note: paramsLocal.note
2138
+ }) : chunkFeishuPostMarkdown(chunkOptions));
2200
2139
  const results = [];
2201
2140
  const acceptedChunks = [];
2202
2141
  for (const [index, chunk] of chunks.entries()) {
@@ -2235,27 +2174,28 @@ function createFeishuReplyDispatcher(params) {
2235
2174
  kind: paramsLocal.useCard ? "card" : "text"
2236
2175
  });
2237
2176
  };
2177
+ const sendPostReply = (text, infoKind, firstChunkMentions) => sendChunkedTextReply({
2178
+ text,
2179
+ useCard: false,
2180
+ infoKind,
2181
+ firstChunkMentions,
2182
+ chunkMentions: requiredMentionTargets,
2183
+ sendChunk: ({ chunk, mentions }) => sendMessageFeishu({
2184
+ cfg,
2185
+ to: sendTarget,
2186
+ text: chunk,
2187
+ replyToMessageId: sendReplyToMessageId,
2188
+ replyInThread: effectiveReplyInThread,
2189
+ allowTopLevelReplyFallback,
2190
+ accountId,
2191
+ ...mentions ? { mentions } : {}
2192
+ })
2193
+ });
2238
2194
  const sendMediaReplies = async (payload, options) => {
2239
2195
  const mediaUrls = resolveSendableOutboundReplyParts(payload).mediaUrls;
2240
2196
  let sentFallbackText = false;
2241
2197
  let degradedVoiceFallbackText;
2242
2198
  const results = [];
2243
- const sendFallbackText = async (text) => await sendChunkedTextReply({
2244
- text,
2245
- useCard: false,
2246
- infoKind: "final",
2247
- chunkMentions: requiredMentionTargets,
2248
- sendChunk: async ({ chunk, mentions }) => await sendMessageFeishu({
2249
- cfg,
2250
- to: sendTarget,
2251
- text: chunk,
2252
- replyToMessageId: sendReplyToMessageId,
2253
- replyInThread: effectiveReplyInThread,
2254
- allowTopLevelReplyFallback,
2255
- accountId,
2256
- ...mentions ? { mentions } : {}
2257
- })
2258
- });
2259
2199
  try {
2260
2200
  await sendMediaWithLeadingCaption({
2261
2201
  mediaUrls,
@@ -2289,12 +2229,12 @@ function createFeishuReplyDispatcher(params) {
2289
2229
  mediaUrl
2290
2230
  });
2291
2231
  sentFallbackText = true;
2292
- results.push(await sendFallbackText(fallbackText));
2232
+ results.push(await sendPostReply(fallbackText, "final"));
2293
2233
  }
2294
2234
  });
2295
2235
  if (degradedVoiceFallbackText && !sentFallbackText) {
2296
2236
  sentFallbackText = true;
2297
- results.push(await sendFallbackText(degradedVoiceFallbackText));
2237
+ results.push(await sendPostReply(degradedVoiceFallbackText, "final"));
2298
2238
  }
2299
2239
  } catch (error) {
2300
2240
  const partial = isChannelPartialDeliveryError(error) ? error.deliveryResult : void 0;
@@ -2306,8 +2246,8 @@ function createFeishuReplyDispatcher(params) {
2306
2246
  const ensureNoVisibleReplyFallback = async (reason) => {
2307
2247
  await idleSideEffectsPromise;
2308
2248
  if (visibleReplySent) return false;
2309
- if (skippedFinalReason === "silent") {
2310
- params.runtime.log?.(`feishu[${account.accountId}]: no-visible-reply fallback skipped for intentional silence (${reason})`);
2249
+ if (replyOutcome?.kind === "suppressed" || replyOutcome?.kind === "skipped" && replyOutcome.reason === "silent") {
2250
+ params.runtime.log?.(`feishu[${account.accountId}]: no-visible-reply fallback skipped for ${replyOutcome.reason} (${reason})`);
2311
2251
  return false;
2312
2252
  }
2313
2253
  await sendMessageFeishu({
@@ -2351,6 +2291,8 @@ function createFeishuReplyDispatcher(params) {
2351
2291
  text: content,
2352
2292
  useCard: true,
2353
2293
  infoKind,
2294
+ header: cardHeader,
2295
+ note: cardNote,
2354
2296
  chunkMentions: requiredMentionTargets,
2355
2297
  sendChunk: async ({ chunk, mentions }) => await sendStructuredCardFeishu({
2356
2298
  cfg,
@@ -2379,10 +2321,17 @@ function createFeishuReplyDispatcher(params) {
2379
2321
  if (completions.some((completion) => ownsCurrentClose(completion))) markClosedStreamingContentClaimed(closeOutcome.generation);
2380
2322
  for (const completion of completions) {
2381
2323
  const claimedSettlement = ownsCurrentClose(completion) ? {
2324
+ disposition: closeOutcome.disposition,
2382
2325
  result: finalized,
2383
2326
  ...closeOutcome.error === void 0 ? {} : { error: closeOutcome.error }
2384
2327
  } : claimClosedStreamingResult(completion.streamingGeneration, completion.result.content);
2385
2328
  const deliveryError = claimedSettlement?.error;
2329
+ if (claimedSettlement?.disposition === "discarded") {
2330
+ const retained = mergeFeishuReplyDeliveryResults([claimedSettlement.result, completion.result], claimedSettlement.result.content ?? "");
2331
+ if (deliveryError !== void 0) completion.reject(createFeishuPartialReplyDeliveryError(deliveryError, retained));
2332
+ else completion.resolve(retained);
2333
+ continue;
2334
+ }
2386
2335
  let providerFinalized = claimedSettlement?.result;
2387
2336
  try {
2388
2337
  providerFinalized = await ensureVisibleStreamingDelivery(providerFinalized, completion.result.content, completion.infoKind);
@@ -2419,22 +2368,16 @@ function createFeishuReplyDispatcher(params) {
2419
2368
  let finalized = noVisibleFeishuReplyDelivery;
2420
2369
  let finalizationError;
2421
2370
  let fallbackPartial;
2422
- const claimedSettlement = claimClosedStreamingResult(paramsLocal.ownerGeneration, paramsLocal.content);
2423
- if (claimedSettlement) {
2424
- finalized = claimedSettlement.result;
2425
- finalizationError = claimedSettlement.error;
2426
- } else if (paramsLocal.ownerGeneration !== void 0 && inFlightStreamingClose?.generation === paramsLocal.ownerGeneration) {
2371
+ let settlement = claimClosedStreamingResult(paramsLocal.ownerGeneration, paramsLocal.content);
2372
+ if (!settlement && paramsLocal.ownerGeneration !== void 0 && inFlightStreamingClose?.generation === paramsLocal.ownerGeneration) {
2427
2373
  const closeOutcome = await inFlightStreamingClose.promise;
2428
- const completedSettlement = claimClosedStreamingResult(paramsLocal.ownerGeneration, paramsLocal.content);
2429
- finalized = completedSettlement?.result ?? closeOutcome?.result ?? finalized;
2430
- finalizationError = completedSettlement?.error ?? closeOutcome?.error;
2431
- } else if (paramsLocal.ownerGeneration !== void 0 && activeStreamingGeneration === paramsLocal.ownerGeneration) {
2432
- const closeOutcome = await closeStreaming();
2433
- finalized = closeOutcome.result;
2434
- finalizationError = closeOutcome.error;
2435
- }
2374
+ settlement = claimClosedStreamingResult(paramsLocal.ownerGeneration, paramsLocal.content) ?? closeOutcome;
2375
+ } else if (!settlement && paramsLocal.ownerGeneration !== void 0 && activeStreamingGeneration === paramsLocal.ownerGeneration) settlement = await closeStreaming();
2376
+ finalized = settlement?.result ?? finalized;
2377
+ finalizationError = settlement?.error;
2378
+ const discarded = settlement?.disposition === "discarded";
2436
2379
  try {
2437
- finalized = await ensureVisibleStreamingDelivery(finalized, paramsLocal.content, paramsLocal.infoKind) ?? finalized;
2380
+ if (!discarded) finalized = await ensureVisibleStreamingDelivery(finalized, paramsLocal.content, paramsLocal.infoKind) ?? finalized;
2438
2381
  } catch (fallbackError) {
2439
2382
  fallbackPartial = isChannelPartialDeliveryError(fallbackError) ? fallbackError.deliveryResult : void 0;
2440
2383
  const fallbackCause = fallbackPartial && fallbackError instanceof Error ? fallbackError.cause ?? fallbackError : fallbackError;
@@ -2445,9 +2388,10 @@ function createFeishuReplyDispatcher(params) {
2445
2388
  finalized,
2446
2389
  ...fallbackPartial ? [fallbackPartial] : [],
2447
2390
  ...mediaPartial ? [mediaPartial] : []
2448
- ], fallbackPartial?.visibleReplySent === true ? fallbackPartial.content : finalized.visibleReplySent === true ? finalized.content : paramsLocal.content);
2391
+ ], discarded ? finalized.content ?? "" : fallbackPartial?.visibleReplySent === true ? fallbackPartial.content : finalized.visibleReplySent === true ? finalized.content : paramsLocal.content);
2449
2392
  const mediaCause = mediaPartial && paramsLocal.error instanceof Error ? paramsLocal.error.cause ?? paramsLocal.error : paramsLocal.error;
2450
- throw createFeishuPartialReplyDeliveryError(finalizationError ? new AggregateError([mediaCause, finalizationError instanceof Error ? finalizationError.cause ?? finalizationError : finalizationError], "Feishu reply delivery and streaming finalization failed") : mediaCause, accepted);
2393
+ const cause = finalizationError ? new AggregateError([mediaCause, finalizationError instanceof Error ? finalizationError.cause ?? finalizationError : finalizationError], "Feishu reply delivery and streaming finalization failed") : mediaCause;
2394
+ throw createFeishuPartialReplyDeliveryError(cause, accepted);
2451
2395
  };
2452
2396
  const dispatcherOptions = {
2453
2397
  responsePrefix,
@@ -2460,13 +2404,14 @@ function createFeishuReplyDispatcher(params) {
2460
2404
  conversationType: chatId.startsWith("oc_") ? "group" : "direct"
2461
2405
  },
2462
2406
  onSkip: (_payload, info) => {
2463
- if (info.kind === "final" || info.kind === "block" && info.reason === "silent") {
2464
- skippedFinalReason = info.reason;
2465
- skippedFinalAssistantMessageIndex = info.assistantMessageIndex;
2466
- }
2407
+ if (replyOutcome?.kind !== "failed" && (info.kind === "final" || info.kind === "block" && info.reason === "silent")) replyOutcome = {
2408
+ kind: "skipped",
2409
+ reason: info.reason,
2410
+ assistantMessageIndex: info.assistantMessageIndex
2411
+ };
2467
2412
  },
2468
2413
  beforeDeliver: (payload, info) => {
2469
- preparedDeliveryAssistantMessageIndex = info.assistantMessageIndex;
2414
+ if (!(replyOutcome?.kind === "skipped" && replyOutcome.reason === "silent" && info.kind !== "final" && replyOutcome.assistantMessageIndex !== void 0 && info.assistantMessageIndex !== void 0 && info.assistantMessageIndex < replyOutcome.assistantMessageIndex) && replyOutcome?.kind !== "failed") replyOutcome = void 0;
2470
2415
  return payload;
2471
2416
  },
2472
2417
  onReplyStart: async () => {
@@ -2477,9 +2422,7 @@ function createFeishuReplyDispatcher(params) {
2477
2422
  sentIndependentBlockText = false;
2478
2423
  idleRequestedForReply = false;
2479
2424
  visibleReplySent = false;
2480
- skippedFinalReason = null;
2481
- skippedFinalAssistantMessageIndex = void 0;
2482
- preparedDeliveryAssistantMessageIndex = void 0;
2425
+ replyOutcome = void 0;
2483
2426
  }
2484
2427
  if (previewStreamingEnabled && renderMode === "card") startStreaming();
2485
2428
  await Promise.resolve(typingCallbacks?.onReplyStart?.());
@@ -2490,6 +2433,7 @@ function createFeishuReplyDispatcher(params) {
2490
2433
  }
2491
2434
  };
2492
2435
  const handleDeliveryError = async (error, info) => {
2436
+ if (info.kind === "final") replyOutcome = { kind: "failed" };
2493
2437
  if (isChannelPartialDeliveryError(error)) markVisibleReplySent();
2494
2438
  params.runtime.error?.(`feishu[${account.accountId}] ${info.kind} reply failed: ${String(error)}`);
2495
2439
  await queueIdleSideEffects().catch((cleanupError) => params.runtime.error?.(`feishu[${account.accountId}] reply error cleanup failed: ${String(cleanupError)}`));
@@ -2498,19 +2442,36 @@ function createFeishuReplyDispatcher(params) {
2498
2442
  dispatcherOptions,
2499
2443
  delivery: {
2500
2444
  observeMessageSent: true,
2501
- deliver: async (payload, info) => {
2502
- const deliveryAssistantMessageIndex = preparedDeliveryAssistantMessageIndex;
2503
- preparedDeliveryAssistantMessageIndex = void 0;
2504
- if (info?.kind === "final" || skippedFinalReason !== "silent" || skippedFinalAssistantMessageIndex === void 0 || deliveryAssistantMessageIndex === void 0 || deliveryAssistantMessageIndex >= skippedFinalAssistantMessageIndex) {
2505
- skippedFinalReason = null;
2506
- skippedFinalAssistantMessageIndex = void 0;
2445
+ onDelivered: (_payload, info, result) => {
2446
+ if (result?.visibleReplySent) {
2447
+ markVisibleReplySent();
2448
+ if (info.kind === "final") replyOutcome = void 0;
2449
+ return;
2507
2450
  }
2508
- const payloadText = payload.isReasoning && payload.text ? formatReasoningMessage(payload.text) : payload.text;
2451
+ const reason = result?.suppression?.reason;
2452
+ if (info.kind === "final" && replyOutcome?.kind !== "failed" && (reason === "cancelled_by_reply_payload_sending_hook" || reason === "empty_after_reply_payload_sending_hook" || reason === "cancelled_by_message_sending_hook" || reason === "empty_after_message_sending_hook" || reason === "channel_transform")) replyOutcome = {
2453
+ kind: "suppressed",
2454
+ reason
2455
+ };
2456
+ },
2457
+ deliver: async (inputPayload, info) => {
2458
+ const prepared = await renderFeishuReplyPayload(inputPayload, {
2459
+ to: sendTarget,
2460
+ identity,
2461
+ mentions: requiredMentionTargets
2462
+ });
2463
+ const rendered = consumeFeishuPresentationFallbackMarker(prepared.payload);
2464
+ const payload = rendered.payload;
2465
+ const presentationCard = prepared.card;
2466
+ const hasPresentationFallback = rendered.presentationFallback?.hasVisibleContent === true;
2467
+ const hasIndependentPresentation = presentationCard !== void 0 || hasPresentationFallback;
2468
+ const resolvedText = payload.text;
2469
+ const payloadText = payload.isReasoning && resolvedText ? formatReasoningMessage(resolvedText) : resolvedText;
2509
2470
  const reply = resolveSendableOutboundReplyParts({
2510
2471
  ...payload,
2511
2472
  text: payloadText
2512
2473
  });
2513
- const text = info?.kind === "final" ? mergeStreamingFinalText(streamText, reply.text, payload.isError === true && hasStreamingFinalText) : reply.text;
2474
+ const text = info?.kind === "final" && !hasIndependentPresentation ? mergeStreamingFinalText(streamText, reply.text, payload.isError === true && hasStreamingFinalText) : reply.text;
2514
2475
  const hasText = reply.hasText;
2515
2476
  const hasMedia = reply.hasMedia;
2516
2477
  const ttsSupplement = getReplyPayloadTtsSupplement(payload);
@@ -2524,52 +2485,61 @@ function createFeishuReplyDispatcher(params) {
2524
2485
  const useStaticCard = hasText && (renderMode === "card" || info?.kind === "block" && coreBlockStreamingEnabled && renderMode !== "raw" || renderMode === "auto" && shouldUseCard(text));
2525
2486
  const useStreamingCard = hasText && streamingEnabled && !finalTextExceedsStreamingLimit && (info?.kind === "final" || useStaticCard);
2526
2487
  const useCard = useStaticCard || useStreamingCard;
2527
- const skipTextForDuplicateFinal = info?.kind === "final" && hasText && deliveredFinalTexts.has(text);
2528
- const shouldDeliverText = hasText && !hasVoiceMedia && !skipTextForDuplicateFinal;
2529
- const shouldDiscardStreamingPreview = info?.kind === "final" && (finalTextExceedsStreamingLimit || hasMedia && (hasVoiceMedia && !shouldDeliverText && !ttsTextAlreadyVisible || skipTextForDuplicateFinal));
2488
+ const skipTextForDuplicateFinal = !hasIndependentPresentation && info?.kind === "final" && hasText && deliveredFinalTexts.has(text);
2489
+ const shouldDeliverText = hasText && (!hasVoiceMedia || hasPresentationFallback) && !skipTextForDuplicateFinal;
2490
+ const shouldDiscardStreamingPreview = info?.kind === "final" && !(hasIndependentPresentation && payload.isError === true && hasStreamingFinalText) && (hasIndependentPresentation || finalTextExceedsStreamingLimit || hasMedia && (hasVoiceMedia && !shouldDeliverText && !ttsTextAlreadyVisible || skipTextForDuplicateFinal));
2530
2491
  const priorClosedStreamingSettlement = info?.kind === "final" && hasText && skipTextForDuplicateFinal ? claimClosedStreamingResult(void 0, text) : void 0;
2531
- if (!shouldDeliverText && !hasMedia) {
2492
+ if (!shouldDeliverText && !hasMedia && !presentationCard) {
2532
2493
  if (priorClosedStreamingSettlement?.error !== void 0) throw toStringifiedError(priorClosedStreamingSettlement.error);
2533
2494
  return priorClosedStreamingSettlement?.result ?? noVisibleFeishuReplyDelivery;
2534
2495
  }
2535
2496
  const deliveredResults = priorClosedStreamingSettlement ? [priorClosedStreamingSettlement.result] : [];
2536
- const collectMediaDelivery = async (mediaPayload, mediaOptions) => {
2497
+ const collectDelivery = async (pending, acceptedContent) => {
2537
2498
  try {
2538
- deliveredResults.push(await sendMediaReplies(mediaPayload, mediaOptions));
2499
+ deliveredResults.push(await pending);
2539
2500
  } catch (error) {
2540
2501
  const partial = isChannelPartialDeliveryError(error) ? error.deliveryResult : void 0;
2541
- const accumulated = mergeFeishuReplyDeliveryResults([...deliveredResults, ...partial ? [partial] : []]);
2542
- throw createFeishuPartialReplyDeliveryError(partial && error instanceof Error ? error.cause ?? error : error, accumulated);
2502
+ const accumulated = mergeFeishuReplyDeliveryResults([...deliveredResults, ...partial ? [{
2503
+ ...partial,
2504
+ content: partial.content ?? acceptedContent
2505
+ }] : []]);
2506
+ throw createFeishuPartialReplyDeliveryError(partial && error instanceof Error ? error.cause ?? error : error, {
2507
+ ...accumulated,
2508
+ content: accumulated.content ?? ""
2509
+ });
2543
2510
  }
2544
2511
  };
2545
2512
  if (shouldDiscardStreamingPreview) await discardStreamingPreview();
2513
+ if (presentationCard) {
2514
+ if (hasMedia) await collectDelivery(sendMediaReplies(payload));
2515
+ await collectDelivery(sendCardFeishu({
2516
+ cfg,
2517
+ to: sendTarget,
2518
+ card: presentationCard,
2519
+ replyToMessageId: sendReplyToMessageId,
2520
+ replyInThread: effectiveReplyInThread,
2521
+ allowTopLevelReplyFallback,
2522
+ accountId
2523
+ }).then((result) => createFeishuReplyDeliveryResult({
2524
+ results: [result],
2525
+ visibleReplySent: true,
2526
+ content: resolvedText,
2527
+ kind: "card"
2528
+ })), resolvedText);
2529
+ markVisibleReplySent();
2530
+ return mergeFeishuReplyDeliveryResults(deliveredResults, resolvedText);
2531
+ }
2546
2532
  if (shouldDeliverText) {
2547
- if (info?.kind === "block") {
2548
- if (!useStreamingCard) {
2549
- if (coreBlockStreamingEnabled) {
2550
- const firstChunkMentions = !sentIndependentBlockText && mentionTargets?.length ? mentionTargets : void 0;
2551
- deliveredResults.push(await sendChunkedTextReply({
2552
- text,
2553
- useCard: false,
2554
- infoKind: "block",
2555
- firstChunkMentions,
2556
- chunkMentions: requiredMentionTargets,
2557
- sendChunk: async ({ chunk, mentions }) => await sendMessageFeishu({
2558
- cfg,
2559
- to: sendTarget,
2560
- text: chunk,
2561
- replyToMessageId: sendReplyToMessageId,
2562
- replyInThread: effectiveReplyInThread,
2563
- allowTopLevelReplyFallback,
2564
- accountId,
2565
- ...mentions ? { mentions } : {}
2566
- })
2567
- }));
2568
- sentIndependentBlockText = true;
2569
- if (hasMedia) await collectMediaDelivery(payload);
2570
- }
2571
- return mergeFeishuReplyDeliveryResults(deliveredResults, text);
2533
+ if (hasPresentationFallback || info?.kind === "block" && !useStreamingCard) {
2534
+ if (hasPresentationFallback || coreBlockStreamingEnabled) {
2535
+ const firstChunkMentions = info?.kind === "final" || info?.kind === "block" && !sentIndependentBlockText ? mentionTargets : void 0;
2536
+ await collectDelivery(sendPostReply(text, info?.kind, firstChunkMentions));
2537
+ if (info?.kind === "block") sentIndependentBlockText = true;
2538
+ if (hasMedia) await collectDelivery(sendMediaReplies(payload));
2572
2539
  }
2540
+ return mergeFeishuReplyDeliveryResults(deliveredResults, text);
2541
+ }
2542
+ if (info?.kind === "block") {
2573
2543
  startStreaming();
2574
2544
  if (streamingStartPromise) await streamingStartPromise;
2575
2545
  }
@@ -2578,7 +2548,7 @@ function createFeishuReplyDispatcher(params) {
2578
2548
  if (streamingStartPromise) await streamingStartPromise;
2579
2549
  }
2580
2550
  const shouldStreamText = info?.kind === "block" || info?.kind === "final";
2581
- const matchingInFlightClose = info?.kind === "final" && inFlightStreamingClose?.content === text ? inFlightStreamingClose : void 0;
2551
+ const matchingInFlightClose = info?.kind === "final" && inFlightStreamingClose?.disposition === "closed" && inFlightStreamingClose.content === text ? inFlightStreamingClose : void 0;
2582
2552
  const ownerGeneration = activeStreamingGeneration ?? matchingInFlightClose?.generation;
2583
2553
  if (shouldStreamText && ownerGeneration !== void 0 && (streaming?.isActive() || matchingInFlightClose !== void 0)) {
2584
2554
  if (activeStreamingGeneration !== void 0) {
@@ -2595,7 +2565,7 @@ function createFeishuReplyDispatcher(params) {
2595
2565
  }
2596
2566
  }
2597
2567
  if (hasMedia) try {
2598
- await collectMediaDelivery(payload);
2568
+ await collectDelivery(sendMediaReplies(payload));
2599
2569
  } catch (error) {
2600
2570
  await throwStreamingDeliveryFailure({
2601
2571
  error,
@@ -2613,6 +2583,8 @@ function createFeishuReplyDispatcher(params) {
2613
2583
  text,
2614
2584
  useCard: true,
2615
2585
  infoKind: info?.kind,
2586
+ header: cardHeader,
2587
+ note: cardNote,
2616
2588
  chunkMentions: requiredMentionTargets,
2617
2589
  sendChunk: async ({ chunk, mentions }) => await sendStructuredCardFeishu({
2618
2590
  cfg,
@@ -2629,27 +2601,12 @@ function createFeishuReplyDispatcher(params) {
2629
2601
  }));
2630
2602
  } else {
2631
2603
  const firstChunkMentions = info?.kind === "final" && mentionTargets?.length ? mentionTargets : void 0;
2632
- deliveredResults.push(await sendChunkedTextReply({
2633
- text,
2634
- useCard: false,
2635
- infoKind: info?.kind,
2636
- firstChunkMentions,
2637
- chunkMentions: requiredMentionTargets,
2638
- sendChunk: async ({ chunk, mentions }) => await sendMessageFeishu({
2639
- cfg,
2640
- to: sendTarget,
2641
- text: chunk,
2642
- replyToMessageId: sendReplyToMessageId,
2643
- replyInThread: effectiveReplyInThread,
2644
- allowTopLevelReplyFallback,
2645
- accountId,
2646
- ...mentions ? { mentions } : {}
2647
- })
2648
- }));
2604
+ deliveredResults.push(await sendPostReply(text, info?.kind, firstChunkMentions));
2649
2605
  }
2650
2606
  }
2651
- if (hasMedia) await collectMediaDelivery(payload, !ttsTextAlreadyVisible && hasVoiceMedia && hasText ? { fallbackText: text } : void 0);
2652
- const result = mergeFeishuReplyDeliveryResults(deliveredResults, hasVoiceMedia ? deliveredResults.at(-1)?.content ?? text : text);
2607
+ if (hasMedia) await collectDelivery(sendMediaReplies(payload, !shouldDeliverText && !ttsTextAlreadyVisible && hasVoiceMedia && hasText ? { fallbackText: text } : void 0));
2608
+ const deliveredContent = hasVoiceMedia ? deliveredResults.at(-1)?.content ?? text : text;
2609
+ const result = mergeFeishuReplyDeliveryResults(deliveredResults, deliveredContent);
2653
2610
  if (priorClosedStreamingSettlement?.error !== void 0) throw createFeishuPartialReplyDeliveryError(isChannelPartialDeliveryError(priorClosedStreamingSettlement.error) && priorClosedStreamingSettlement.error instanceof Error ? priorClosedStreamingSettlement.error.cause ?? priorClosedStreamingSettlement.error : priorClosedStreamingSettlement.error, result);
2654
2611
  return result;
2655
2612
  },
@@ -2697,7 +2654,7 @@ function createFeishuReplyDispatcher(params) {
2697
2654
  ensureNoVisibleReplyFallback,
2698
2655
  getVisibleReplyState: () => ({
2699
2656
  visibleReplySent,
2700
- skippedFinalReason
2657
+ skippedFinalReason: replyOutcome?.kind === "skipped" || replyOutcome?.kind === "suppressed" ? replyOutcome.reason : null
2701
2658
  })
2702
2659
  };
2703
2660
  }
@@ -2715,7 +2672,7 @@ function getFeishuSyntheticDirectPreDispatchTarget(event) {
2715
2672
  //#endregion
2716
2673
  //#region extensions/feishu/src/bot.ts
2717
2674
  const permissionErrorNotifiedAt = /* @__PURE__ */ new Map();
2718
- const PERMISSION_ERROR_COOLDOWN_MS = 300 * 1e3;
2675
+ const PERMISSION_ERROR_COOLDOWN_MS = 3e5;
2719
2676
  function shouldSendNoVisibleReplyFallback(dispatchResult) {
2720
2677
  const emptyEligibleDispatch = dispatchResult.noVisibleReplyFallbackEligible === true && dispatchResult.settledReceipt?.anyVisibleDelivered !== true;
2721
2678
  const finalFailedBeforeSend = (dispatchResult.settledReceipt?.counts.final.failedBeforeSend ?? 0) > 0;
@@ -2724,10 +2681,11 @@ function shouldSendNoVisibleReplyFallback(dispatchResult) {
2724
2681
  function isFeishuTopicSessionScope(scope) {
2725
2682
  return scope === "group_topic" || scope === "group_topic_sender";
2726
2683
  }
2727
- async function resolveFeishuAudioPreflightTranscript(params) {
2728
- if (params.messageType !== "audio" || params.content.trim()) return;
2684
+ async function resolveFeishuAudioTranscript(params) {
2685
+ if (params.messageType !== "audio") return;
2729
2686
  const audioMedia = params.mediaList.filter((media) => Boolean(media.path) && (media.kind === "audio" || media.contentType?.startsWith("audio/")));
2730
2687
  if (audioMedia.length === 0) return;
2688
+ if (params.content.trim()) return params.content;
2731
2689
  try {
2732
2690
  const { transcribeFirstAudio } = await import("./audio-preflight.runtime-BBfC9elv.js");
2733
2691
  return await transcribeFirstAudio({
@@ -2922,32 +2880,34 @@ async function handleFeishuMessage(params) {
2922
2880
  }
2923
2881
  }
2924
2882
  let permissionErrorForAgent;
2925
- if (feishuCfg?.resolveSenderNames ?? true) if (ctx.senderType === "bot") {
2926
- const senderName = await resolveFeishuBotName({
2927
- account,
2928
- openId: ctx.senderOpenId,
2929
- log
2930
- });
2931
- if (senderName) ctx = {
2932
- ...ctx,
2933
- senderName
2934
- };
2935
- } else {
2936
- const senderResult = await resolveFeishuSenderName({
2937
- account,
2938
- senderId: ctx.senderOpenId,
2939
- log
2940
- });
2941
- if (senderResult.name) ctx = {
2942
- ...ctx,
2943
- senderName: senderResult.name
2944
- };
2945
- if (senderResult.permissionError) {
2946
- const appKey = account.appId ?? "default";
2947
- const now = Date.now();
2948
- if (now - (permissionErrorNotifiedAt.get(appKey) ?? 0) > PERMISSION_ERROR_COOLDOWN_MS) {
2949
- permissionErrorNotifiedAt.set(appKey, now);
2950
- permissionErrorForAgent = senderResult.permissionError;
2883
+ if (feishuCfg?.resolveSenderNames ?? true) {
2884
+ if (ctx.senderType === "bot") {
2885
+ const senderName = await resolveFeishuBotName({
2886
+ account,
2887
+ openId: ctx.senderOpenId,
2888
+ log
2889
+ });
2890
+ if (senderName) ctx = {
2891
+ ...ctx,
2892
+ senderName
2893
+ };
2894
+ } else {
2895
+ const senderResult = await resolveFeishuSenderName({
2896
+ account,
2897
+ senderId: ctx.senderOpenId,
2898
+ log
2899
+ });
2900
+ if (senderResult.name) ctx = {
2901
+ ...ctx,
2902
+ senderName: senderResult.name
2903
+ };
2904
+ if (senderResult.permissionError) {
2905
+ const appKey = account.appId ?? "default";
2906
+ const now = Date.now();
2907
+ if (now - (permissionErrorNotifiedAt.get(appKey) ?? 0) > PERMISSION_ERROR_COOLDOWN_MS) {
2908
+ permissionErrorNotifiedAt.set(appKey, now);
2909
+ permissionErrorForAgent = senderResult.permissionError;
2910
+ }
2951
2911
  }
2952
2912
  }
2953
2913
  }
@@ -3335,7 +3295,7 @@ async function handleFeishuMessage(params) {
3335
3295
  log(`feishu[${account.accountId}]: skipping empty message (no text, no media, no quoted) from ${ctx.senderOpenId}`);
3336
3296
  return;
3337
3297
  }
3338
- const audioTranscript = await resolveFeishuAudioPreflightTranscript({
3298
+ const audioTranscript = await resolveFeishuAudioTranscript({
3339
3299
  cfg: effectiveCfg,
3340
3300
  mediaList,
3341
3301
  content: ctx.content,
@@ -3343,8 +3303,8 @@ async function handleFeishuMessage(params) {
3343
3303
  chatType: isGroup ? "group" : "direct",
3344
3304
  log
3345
3305
  });
3346
- const preflightAudioIndex = audioTranscript === void 0 ? -1 : mediaList.findIndex((media) => media.kind === "audio" || media.contentType?.startsWith("audio/"));
3347
- const inboundMedia = await toInboundMediaFactsWithMetadata(mediaList, { transcribed: (_media, index) => index === preflightAudioIndex });
3306
+ const transcribedAudioIndex = audioTranscript === void 0 ? -1 : mediaList.findIndex((media) => media.kind === "audio" || media.contentType?.startsWith("audio/"));
3307
+ const inboundMedia = await toInboundMediaFactsWithMetadata(mediaList, { transcribed: (_media, index) => index === transcribedAudioIndex });
3348
3308
  const requiredMentionTargets = isGroup && ctx.senderType === "bot" && ctx.senderOpenId ? [{
3349
3309
  openId: ctx.senderOpenId,
3350
3310
  name: ctx.senderName ?? ctx.senderOpenId,
@@ -4018,8 +3978,8 @@ function createApprovalCard(params) {
4018
3978
  }
4019
3979
  //#endregion
4020
3980
  //#region extensions/feishu/src/card-action.ts
4021
- const FEISHU_APPROVAL_CARD_TTL_MS = 5 * 6e4;
4022
- const FEISHU_CARD_ACTION_TOKEN_TTL_MS = 15 * 6e4;
3981
+ const FEISHU_APPROVAL_CARD_TTL_MS = 3e5;
3982
+ const FEISHU_CARD_ACTION_TOKEN_TTL_MS = 9e5;
4023
3983
  function pruneProcessedCardActionTokens(now) {
4024
3984
  const validNow = asDateTimestampMs(now);
4025
3985
  if (validNow === void 0) {
@@ -4111,7 +4071,7 @@ async function dispatchSyntheticCommand(params) {
4111
4071
  });
4112
4072
  }
4113
4073
  const resolvedChatTypeCache = resolvedCardActionChatTypes;
4114
- const CHAT_TYPE_CACHE_TTL_MS = 30 * 6e4;
4074
+ const CHAT_TYPE_CACHE_TTL_MS = 18e5;
4115
4075
  const CHAT_TYPE_CACHE_MAX_SIZE = 5e3;
4116
4076
  function pruneChatTypeCache(now) {
4117
4077
  const validNow = asDateTimestampMs(now);
@@ -4366,7 +4326,8 @@ function inspectFeishuIngressEnvelope(rawEnvelope, encryptKey, allowInvalidLane
4366
4326
  if (!eventId) throw new FeishuIngressPermanentError("invalid-event", `Feishu ${eventType} envelope is missing header.event_id.`);
4367
4327
  const event = nestedEvent ?? envelope;
4368
4328
  if (eventType === "im.message.receive_v1") {
4369
- const chatId = normalizeNullableString((isRecord$1(event.message) ? event.message : null)?.chat_id);
4329
+ const message = isRecord$1(event.message) ? event.message : null;
4330
+ const chatId = normalizeNullableString(message?.chat_id);
4370
4331
  if (!chatId) {
4371
4332
  if (allowInvalidLane) return {
4372
4333
  eventId,
@@ -4490,6 +4451,7 @@ function buildFeishuFlushIngressLifecycle(sources, options) {
4490
4451
  handedOff = true;
4491
4452
  transportLifecycle.onDeferred();
4492
4453
  },
4454
+ onDeferredHeartbeat: () => transportLifecycle.onDeferredHeartbeat?.(),
4493
4455
  onAdoptionFinalizing: () => {
4494
4456
  transportLifecycle.onAdoptionFinalizing();
4495
4457
  },
@@ -4642,7 +4604,7 @@ const FEISHU_WEBHOOK_RATE_LIMIT_FALLBACK_DEFAULTS = {
4642
4604
  };
4643
4605
  const FEISHU_WEBHOOK_ANOMALY_FALLBACK_DEFAULTS = {
4644
4606
  maxTrackedKeys: 4096,
4645
- ttlMs: 360 * 6e4,
4607
+ ttlMs: 216e5,
4646
4608
  logEvery: 25
4647
4609
  };
4648
4610
  function coercePositiveInt(value, fallback) {
@@ -4673,7 +4635,7 @@ const httpServers = /* @__PURE__ */ new Map();
4673
4635
  const botOpenIds = /* @__PURE__ */ new Map();
4674
4636
  const botNames = /* @__PURE__ */ new Map();
4675
4637
  const botIdentityRevisions = /* @__PURE__ */ new Map();
4676
- const FEISHU_WEBHOOK_MAX_BODY_BYTES = 64 * 1024;
4638
+ const FEISHU_WEBHOOK_MAX_BODY_BYTES = 65536;
4677
4639
  const FEISHU_WEBHOOK_BODY_TIMEOUT_MS = 5e3;
4678
4640
  const FEISHU_HTTP_SERVER_CLOSE_TIMEOUT_MS = 5e3;
4679
4641
  const feishuWebhookRateLimitDefaults = resolveFeishuWebhookRateLimitDefaults(WEBHOOK_RATE_LIMIT_DEFAULTS);
@@ -4821,7 +4783,7 @@ function startBotIdentityRecovery(params) {
4821
4783
  }
4822
4784
  //#endregion
4823
4785
  //#region extensions/feishu/src/card-ux-launcher.ts
4824
- const FEISHU_QUICK_ACTION_CARD_TTL_MS = 10 * 6e4;
4786
+ const FEISHU_QUICK_ACTION_CARD_TTL_MS = 6e5;
4825
4787
  const QUICK_ACTION_MENU_KEYS = /* @__PURE__ */ new Set([
4826
4788
  "quick-actions",
4827
4789
  "quick_actions",
@@ -5043,10 +5005,11 @@ function createFeishuCommentReplyDispatcher(params) {
5043
5005
  channel: "feishu",
5044
5006
  accountId: params.accountId
5045
5007
  });
5046
- const client = createFeishuClient(resolveFeishuRuntimeAccount({
5008
+ const account = resolveFeishuRuntimeAccount({
5047
5009
  cfg: params.cfg,
5048
5010
  accountId: params.accountId
5049
- }));
5011
+ });
5012
+ const client = createFeishuClient(account);
5050
5013
  const textChunkLimit = core.channel.text.resolveTextChunkLimit(params.cfg, "feishu", params.accountId, { fallbackLimit: 4e3 });
5051
5014
  const chunkMode = core.channel.text.resolveChunkMode(params.cfg, "feishu", params.accountId);
5052
5015
  const typingReaction = createCommentTypingReactionLifecycle({
@@ -5075,7 +5038,7 @@ function createFeishuCommentReplyDispatcher(params) {
5075
5038
  if (info.kind !== "final") return noVisibleFeishuReplyDelivery;
5076
5039
  const reply = resolveSendableOutboundReplyParts(payload);
5077
5040
  const { presentation } = resolveFeishuRichReply(payload);
5078
- let { text } = buildFeishuCommentPresentationFallback({
5041
+ let { commentText: text } = buildFeishuPresentationFallback({
5079
5042
  text: reply.text,
5080
5043
  presentation
5081
5044
  });
@@ -5994,7 +5957,8 @@ async function handleFeishuCommentEvent(params) {
5994
5957
  })
5995
5958
  }
5996
5959
  });
5997
- const counts = resolveInboundReplyDispatchCounts(turnResult.dispatched ? turnResult.dispatchResult : void 0);
5960
+ const dispatchResult = turnResult.dispatched ? turnResult.dispatchResult : void 0;
5961
+ const counts = resolveInboundReplyDispatchCounts(dispatchResult);
5998
5962
  log(`feishu[${account.accountId}]: drive comment dispatch complete (replies=${counts.final}, session=${commentSessionKey})`);
5999
5963
  } finally {
6000
5964
  cleanupTypingReaction();
@@ -6019,7 +5983,7 @@ async function handleFeishuCommentEvent(params) {
6019
5983
  * resolves) keeps later same-chat messages in `queued` state until the
6020
5984
  * gateway is restarted. See #70133.
6021
5985
  */
6022
- const DEFAULT_TASK_TIMEOUT_MS = 300 * 1e3;
5986
+ const DEFAULT_TASK_TIMEOUT_MS = 3e5;
6023
5987
  function createSequentialQueue(options = {}) {
6024
5988
  const queues = /* @__PURE__ */ new Map();
6025
5989
  const taskTimeoutMs = options.taskTimeoutMs ?? DEFAULT_TASK_TIMEOUT_MS;
@@ -6230,8 +6194,8 @@ function createFeishuMessageReceiveHandler({ cfg, channelRuntime, accountId, run
6230
6194
  const chatId = event.message.chat_id?.trim();
6231
6195
  const senderId = resolveSenderDebounceId(event);
6232
6196
  if (!chatId || !senderId) return null;
6233
- const rootId = event.message.root_id?.trim();
6234
- return `feishu:${accountId}:${chatId}:${rootId ? `thread:${rootId}` : "chat"}:${senderId}`;
6197
+ const threadId = event.message.root_id?.trim() || event.message.thread_id?.trim();
6198
+ return `feishu:${accountId}:${chatId}:${threadId ? `thread:${threadId}` : "chat"}:${senderId}`;
6235
6199
  },
6236
6200
  shouldDebounce: ({ event }) => {
6237
6201
  if (event.message.message_type !== "text") return false;
@@ -6427,7 +6391,8 @@ function isFeishuWebhookSignatureValid(params) {
6427
6391
  const nonce = Array.isArray(nonceHeader) ? nonceHeader[0] : nonceHeader;
6428
6392
  const signature = Array.isArray(signatureHeader) ? signatureHeader[0] : signatureHeader;
6429
6393
  if (!timestamp || !nonce || !signature) return false;
6430
- return safeEqualSecret(crypto.createHash("sha256").update(timestamp + nonce + encryptKey + params.rawBody).digest("hex"), signature);
6394
+ const computedSignature = crypto.createHash("sha256").update(timestamp + nonce + encryptKey + params.rawBody).digest("hex");
6395
+ return safeEqualSecret(computedSignature, signature);
6431
6396
  }
6432
6397
  function respondText(res, statusCode, body) {
6433
6398
  res.statusCode = statusCode;
@@ -6617,8 +6582,10 @@ async function monitorWebhook({ account, accountId, runtime, abortSignal, eventD
6617
6582
  respondText(res, 404, "Not Found");
6618
6583
  return;
6619
6584
  }
6620
- res.on("finish", () => {
6585
+ res.once("close", () => {
6621
6586
  recordWebhookStatus(runtime, accountId, path, res.statusCode);
6587
+ });
6588
+ res.once("finish", () => {
6622
6589
  if (res.statusCode >= 200 && res.statusCode < 300) {
6623
6590
  const inboundAt = Date.now();
6624
6591
  statusSink?.({
@@ -6627,16 +6594,17 @@ async function monitorWebhook({ account, accountId, runtime, abortSignal, eventD
6627
6594
  });
6628
6595
  }
6629
6596
  });
6597
+ const rateLimitKey = buildFeishuWebhookRateLimitKey({
6598
+ accountId,
6599
+ path,
6600
+ clientIp: resolveRequestClientIp(req)
6601
+ });
6630
6602
  if (!applyBasicWebhookRequestGuards({
6631
6603
  req,
6632
6604
  res,
6633
6605
  allowMethods: ["POST"],
6634
6606
  rateLimiter: feishuWebhookRateLimiter,
6635
- rateLimitKey: buildFeishuWebhookRateLimitKey({
6636
- accountId,
6637
- path,
6638
- clientIp: resolveRequestClientIp(req)
6639
- }),
6607
+ rateLimitKey,
6640
6608
  nowMs: Date.now(),
6641
6609
  requireJsonContentType: true
6642
6610
  })) return;