@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,18 +1,18 @@
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";
1
+ import { o as resolveFeishuAccount, s as resolveFeishuRuntimeAccount } from "./accounts-cCMNFBKg.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-CYaKkbUN.js";
4
+ import { a as normalizeFeishuWebhookPath, o as normalizeFeishuExternalKey } from "./doctor-contract-w6FpMypv.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-DDhIcPQy.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-rPJ_Mz4M.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";
@@ -44,6 +44,7 @@ import { formatReasoningMessage, resolveHumanDelayConfig } from "openclaw/plugin
44
44
  import { logTypingFailure } from "openclaw/plugin-sdk/channel-feedback";
45
45
  import { getGlobalHookRunner } from "openclaw/plugin-sdk/plugin-runtime";
46
46
  import * as http from "node:http";
47
+ import { createRuntimeConfigReader } from "openclaw/plugin-sdk/runtime-config-snapshot";
47
48
  import { channelBlockedPatch, channelReadyPatch } from "openclaw/plugin-sdk/gateway-runtime";
48
49
  import { installRequestBodyLimitGuard, readWebhookBodyOrReject } from "openclaw/plugin-sdk/webhook-request-guards";
49
50
  import { isAbortRequestText, isBtwRequestText } from "openclaw/plugin-sdk/command-primitives-runtime";
@@ -53,7 +54,7 @@ function formatMentionNameForAgentContext(name) {
53
54
  const normalized = Array.from(name, (char) => {
54
55
  return char.charCodeAt(0) < 32 || char === "[" || char === "]" ? " " : char;
55
56
  }).join("").replace(/\s+/g, " ").trim();
56
- const bounded = normalized.length > MAX_MENTION_CONTEXT_NAME_LENGTH ? `${truncateUtf16Safe(normalized, MAX_MENTION_CONTEXT_NAME_LENGTH - 3)}...` : normalized;
57
+ const bounded = normalized.length > MAX_MENTION_CONTEXT_NAME_LENGTH ? `${truncateUtf16Safe(normalized, 77)}...` : normalized;
57
58
  return JSON.stringify(bounded || "unknown");
58
59
  }
59
60
  function buildFeishuAgentBody(params) {
@@ -196,6 +197,7 @@ function createFeishuBroadcastIngressSettlement(params) {
196
197
  lane.status = "deferred";
197
198
  defer();
198
199
  },
200
+ onDeferredHeartbeat: () => params.lifecycle?.onDeferredHeartbeat?.(),
199
201
  onAdoptionFinalizing: beginFinalizing,
200
202
  onAbandoned: async () => {
201
203
  if (lane.status === "completed" || lane.status === "failed" || lane.status === "abandoned") return;
@@ -289,9 +291,7 @@ function resolveFeishuGroupSession(params) {
289
291
  senderOpenId
290
292
  });
291
293
  break;
292
- default:
293
- peerId = chatId;
294
- break;
294
+ default: peerId = chatId;
295
295
  }
296
296
  return {
297
297
  peerId,
@@ -540,7 +540,7 @@ async function resolveFeishuMediaList(params) {
540
540
  const feishuGroupNameCache = /* @__PURE__ */ new Map();
541
541
  //#endregion
542
542
  //#region extensions/feishu/src/bot-group-name.ts
543
- const GROUP_NAME_CACHE_TTL_MS = 1800 * 1e3;
543
+ const GROUP_NAME_CACHE_TTL_MS = 18e5;
544
544
  const GROUP_NAME_CACHE_MAX_SIZE = 500;
545
545
  function evictGroupNameCache() {
546
546
  const now = asDateTimestampMs(Date.now());
@@ -582,7 +582,8 @@ async function resolveGroupName(params) {
582
582
  }
583
583
  let resolvedName;
584
584
  try {
585
- const name = (await getChatInfo(createFeishuClient(account), chatId))?.name?.trim();
585
+ const client = createFeishuClient(account);
586
+ const name = (await getChatInfo(client, chatId))?.name?.trim();
586
587
  if (name) {
587
588
  setCacheEntry(cacheKey, name);
588
589
  resolvedName = name;
@@ -596,11 +597,11 @@ async function resolveGroupName(params) {
596
597
  }
597
598
  //#endregion
598
599
  //#region extensions/feishu/src/bot-name.ts
599
- const POSITIVE_TTL_MS = 10 * 6e4;
600
+ const POSITIVE_TTL_MS = 6e5;
600
601
  const NEGATIVE_TTL_MS = 6e4;
601
602
  const MAX_CACHE_ENTRIES = 5e3;
602
603
  const BREAKER_FAILURE_THRESHOLD = 10;
603
- const BREAKER_OPEN_MS = 60 * 6e4;
604
+ const BREAKER_OPEN_MS = 36e5;
604
605
  const PERMISSION_BACKOFF_MS = 6e4;
605
606
  const REQUEST_TIMEOUT_MS = 1500;
606
607
  const cache = /* @__PURE__ */ new Map();
@@ -738,8 +739,8 @@ async function resolveFeishuBotName(params) {
738
739
  const IGNORED_PERMISSION_SCOPE_TOKENS = ["contact:contact.base:readonly"];
739
740
  const FEISHU_SCOPE_CORRECTIONS = { "contact:contact.base:readonly": "contact:user.base:readonly" };
740
741
  const FEISHU_USER_LOOKUP_UNAUTHORIZED_CODE = 41050;
741
- const SENDER_NAME_TTL_MS = 600 * 1e3;
742
- const SENDER_NAME_NEGATIVE_TTL_MS = 1800 * 1e3;
742
+ const SENDER_NAME_TTL_MS = 6e5;
743
+ const SENDER_NAME_NEGATIVE_TTL_MS = 18e5;
743
744
  const SENDER_NAME_CACHE_MAX_SIZE = 500;
744
745
  const senderNameCache = /* @__PURE__ */ new Map();
745
746
  function correctFeishuScopeInUrl(url) {
@@ -840,7 +841,7 @@ async function resolveFeishuSenderName(params) {
840
841
  //#endregion
841
842
  //#region extensions/feishu/src/dedup-state.ts
842
843
  const DEDUPE_NAMESPACE_PREFIX = "feishu.dedup";
843
- const DEDUP_TTL_MS = 1440 * 60 * 1e3;
844
+ const DEDUP_TTL_MS = 864e5;
844
845
  const MEMORY_MAX_SIZE = 1e3;
845
846
  const STORE_MAX_ENTRIES = 1e4;
846
847
  function createFeishuDedupeGuard() {
@@ -1145,48 +1146,6 @@ function resolveFeishuReasoningPreviewEnabled(params) {
1145
1146
  return configDefault === "stream";
1146
1147
  }
1147
1148
  //#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
1149
  //#region extensions/feishu/src/reply-dispatcher-state.ts
1191
1150
  const streamingStartBackoffUntilByAccount = /* @__PURE__ */ new Map();
1192
1151
  //#endregion
@@ -1662,21 +1621,10 @@ var FeishuStreamingSession = class {
1662
1621
  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
1622
  return result;
1664
1623
  }
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
1624
  async discard() {
1674
- if (!this.state || this.closed) return;
1625
+ if (!this.state || this.closed) return { visibleReplySent: false };
1675
1626
  const { cardId, messageId } = this.state;
1676
- if (!messageId) {
1677
- await this.close("");
1678
- return;
1679
- }
1627
+ if (!messageId) return this.closeWithResult("");
1680
1628
  this.closed = true;
1681
1629
  this.clearFlushTimer();
1682
1630
  await this.queue;
@@ -1686,10 +1634,11 @@ var FeishuStreamingSession = class {
1686
1634
  this.state = null;
1687
1635
  this.pendingText = null;
1688
1636
  this.log?.(`Discarded streaming card: cardId=${cardId}`);
1637
+ return { visibleReplySent: false };
1689
1638
  } catch (error) {
1690
1639
  this.log?.(`Discard failed: ${String(error)}`);
1691
1640
  this.closed = false;
1692
- await this.close("");
1641
+ return this.closeWithResult("");
1693
1642
  }
1694
1643
  }
1695
1644
  isActive() {
@@ -1820,7 +1769,7 @@ function mergeStreamingFinalText(previousText, nextText, appendError) {
1820
1769
  }
1821
1770
  /** Maximum age (ms) for a message to receive a typing indicator reaction.
1822
1771
  * Messages older than this are likely replays after context compaction (#30418). */
1823
- const TYPING_INDICATOR_MAX_AGE_MS = 2 * 6e4;
1772
+ const TYPING_INDICATOR_MAX_AGE_MS = 12e4;
1824
1773
  const MS_EPOCH_MIN = 0xe8d4a51000;
1825
1774
  const STREAMING_START_FAILURE_BACKOFF_MS = 6e4;
1826
1775
  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 +1896,7 @@ function createFeishuReplyDispatcher(params) {
1947
1896
  let activeStreamingGeneration;
1948
1897
  let inFlightStreamingClose;
1949
1898
  let visibleReplySent = false;
1950
- let skippedFinalReason = null;
1951
- let skippedFinalAssistantMessageIndex;
1952
- let preparedDeliveryAssistantMessageIndex;
1899
+ let replyOutcome;
1953
1900
  let idleSideEffectsPromise = Promise.resolve();
1954
1901
  let activeIdleSideEffectsPromise = null;
1955
1902
  let idleRequestedForReply = false;
@@ -2049,97 +1996,86 @@ function createFeishuReplyDispatcher(params) {
2049
1996
  lastSnapshotTextLength = 0;
2050
1997
  hasStreamingFinalText = false;
2051
1998
  };
2052
- const rememberClosedStreamingSettlement = (generation, content, result, error) => {
2053
- if (generation === void 0 || !content) return;
1999
+ const rememberClosedStreamingSettlement = (generation, content, result, error, disposition = "closed") => {
2000
+ if (generation === void 0 || !content && disposition === "closed") return;
2054
2001
  closedStreamingSettlements.set(generation, {
2002
+ disposition,
2055
2003
  result,
2056
2004
  content,
2057
2005
  ...error === void 0 ? {} : { error }
2058
2006
  });
2059
2007
  };
2060
- const performStreamingClose = async () => {
2008
+ const performStreamingClose = async (disposition) => {
2061
2009
  const streamingToClose = streaming;
2062
2010
  const generationToClose = activeStreamingGeneration;
2063
2011
  const startPromiseToClose = streamingStartPromise;
2064
2012
  const updateQueueToClose = partialUpdateQueue;
2065
2013
  const finalizedAnswerText = streamText;
2066
2014
  const finalizedReasoningText = reasoningText;
2015
+ const outcome = {
2016
+ disposition,
2017
+ ...generationToClose === void 0 ? {} : { generation: generationToClose }
2018
+ };
2067
2019
  if (generationToClose !== void 0 && activeStreamingGeneration === generationToClose) activeStreamingGeneration = void 0;
2068
2020
  try {
2069
2021
  if (startPromiseToClose) await startPromiseToClose;
2070
2022
  await updateQueueToClose;
2023
+ let result = noVisibleFeishuReplyDelivery;
2024
+ let finalizationError;
2071
2025
  if (streamingToClose?.isActive()) {
2072
2026
  statusLine = "";
2073
2027
  const text = buildCombinedStreamText(finalizedReasoningText, finalizedAnswerText);
2074
- const finalNote = resolveCardNote(agentId, identity, responsePrefixContextProvider());
2075
2028
  let closed;
2076
2029
  try {
2077
- closed = await streamingToClose.closeWithResult(text, { note: finalNote });
2030
+ if (disposition === "discarded") closed = await streamingToClose.discard();
2031
+ else {
2032
+ const finalNote = resolveCardNote(agentId, identity, responsePrefixContextProvider());
2033
+ closed = await streamingToClose.closeWithResult(text, { note: finalNote });
2034
+ }
2078
2035
  } catch (error) {
2079
2036
  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
- };
2037
+ closed = error.result;
2038
+ finalizationError = error;
2103
2039
  }
2104
- const result = createFeishuReplyDeliveryResult({
2040
+ result = createFeishuReplyDeliveryResult({
2105
2041
  results: [closed],
2106
2042
  visibleReplySent: closed.visibleReplySent,
2107
2043
  content: closed.content,
2108
2044
  kind: "card"
2109
2045
  });
2110
2046
  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
- };
2047
+ if (disposition === "closed" && result.visibleReplySent && finalizedAnswerText && (finalizationError === void 0 || result.content === text)) deliveredFinalTexts.add(finalizedAnswerText);
2048
+ if (finalizationError instanceof FeishuStreamingFinalizationError && result.visibleReplySent) finalizationError = createFeishuPartialReplyDeliveryError(finalizationError.cause ?? finalizationError, result);
2119
2049
  }
2050
+ if (disposition === "discarded" || finalizationError !== void 0 || result.visibleReplySent && finalizedAnswerText) rememberClosedStreamingSettlement(generationToClose, finalizedAnswerText, result, finalizationError, disposition);
2120
2051
  return {
2121
- result: noVisibleFeishuReplyDelivery,
2122
- ...generationToClose === void 0 ? {} : { generation: generationToClose }
2052
+ ...outcome,
2053
+ result,
2054
+ ...finalizationError === void 0 ? {} : { error: finalizationError }
2123
2055
  };
2124
2056
  } catch (error) {
2057
+ if (disposition === "discarded") rememberClosedStreamingSettlement(generationToClose, finalizedAnswerText, noVisibleFeishuReplyDelivery, error, disposition);
2125
2058
  return {
2059
+ ...outcome,
2126
2060
  result: noVisibleFeishuReplyDelivery,
2127
- ...generationToClose === void 0 ? {} : { generation: generationToClose },
2128
2061
  error
2129
2062
  };
2130
2063
  } finally {
2131
2064
  if (streaming === streamingToClose) resetStreamingState();
2132
2065
  }
2133
2066
  };
2134
- const closeStreaming = () => {
2067
+ const closeStreaming = (disposition = "closed") => {
2068
+ const session = streaming;
2135
2069
  const generation = activeStreamingGeneration;
2136
- if (generation !== void 0 && inFlightStreamingClose?.generation === generation) return inFlightStreamingClose.promise;
2070
+ if (session && inFlightStreamingClose?.session === session) return inFlightStreamingClose.promise;
2137
2071
  const content = streamText;
2138
- const closePromise = performStreamingClose();
2139
- if (generation !== void 0) {
2072
+ const closePromise = performStreamingClose(disposition);
2073
+ if (session && generation !== void 0) {
2140
2074
  const closing = {
2075
+ session,
2141
2076
  generation,
2142
2077
  content,
2078
+ disposition,
2143
2079
  promise: closePromise
2144
2080
  };
2145
2081
  inFlightStreamingClose = closing;
@@ -2171,13 +2107,12 @@ function createFeishuReplyDispatcher(params) {
2171
2107
  };
2172
2108
  };
2173
2109
  const discardStreamingPreview = async () => {
2174
- try {
2175
- if (streamingStartPromise) await streamingStartPromise;
2176
- await partialUpdateQueue;
2177
- if (streaming?.isActive()) await streaming.discard();
2178
- } finally {
2110
+ if (streaming && inFlightStreamingClose?.session === streaming && inFlightStreamingClose.disposition === "closed") {
2179
2111
  resetStreamingState();
2112
+ return;
2180
2113
  }
2114
+ const outcome = await closeStreaming("discarded");
2115
+ if (outcome.error !== void 0) throw toStringifiedError(outcome.error);
2181
2116
  };
2182
2117
  const updateStreamingStatusLine = (nextStatusLine, options) => {
2183
2118
  statusLine = nextStatusLine;
@@ -2189,14 +2124,19 @@ function createFeishuReplyDispatcher(params) {
2189
2124
  const sendChunkedTextReply = async (paramsLocal) => {
2190
2125
  const chunkSource = paramsLocal.useCard ? paramsLocal.text : materializeFeishuPostMarkdownSoftBreaks(core.channel.text.convertMarkdownTables(paramsLocal.text, tableMode));
2191
2126
  const initialChunks = core.channel.text.chunkMarkdownTextWithMode(chunkSource, textChunkLimit, chunkMode);
2192
- const chunks = resolveTextChunksWithFallback(chunkSource, paramsLocal.useCard ? initialChunks : chunkFeishuPostMarkdown({
2127
+ const chunkOptions = {
2193
2128
  text: chunkSource,
2194
2129
  limit: textChunkLimit,
2195
2130
  mode: chunkMode,
2196
2131
  firstChunkMentions: paramsLocal.firstChunkMentions,
2197
2132
  chunkMentions: paramsLocal.chunkMentions,
2198
2133
  initialChunks
2199
- }));
2134
+ };
2135
+ const chunks = resolveTextChunksWithFallback(chunkSource, paramsLocal.useCard ? chunkFeishuCardMarkdown({
2136
+ ...chunkOptions,
2137
+ header: paramsLocal.header,
2138
+ note: paramsLocal.note
2139
+ }) : chunkFeishuPostMarkdown(chunkOptions));
2200
2140
  const results = [];
2201
2141
  const acceptedChunks = [];
2202
2142
  for (const [index, chunk] of chunks.entries()) {
@@ -2235,27 +2175,28 @@ function createFeishuReplyDispatcher(params) {
2235
2175
  kind: paramsLocal.useCard ? "card" : "text"
2236
2176
  });
2237
2177
  };
2178
+ const sendPostReply = (text, infoKind, firstChunkMentions) => sendChunkedTextReply({
2179
+ text,
2180
+ useCard: false,
2181
+ infoKind,
2182
+ firstChunkMentions,
2183
+ chunkMentions: requiredMentionTargets,
2184
+ sendChunk: ({ chunk, mentions }) => sendMessageFeishu({
2185
+ cfg,
2186
+ to: sendTarget,
2187
+ text: chunk,
2188
+ replyToMessageId: sendReplyToMessageId,
2189
+ replyInThread: effectiveReplyInThread,
2190
+ allowTopLevelReplyFallback,
2191
+ accountId,
2192
+ ...mentions ? { mentions } : {}
2193
+ })
2194
+ });
2238
2195
  const sendMediaReplies = async (payload, options) => {
2239
2196
  const mediaUrls = resolveSendableOutboundReplyParts(payload).mediaUrls;
2240
2197
  let sentFallbackText = false;
2241
2198
  let degradedVoiceFallbackText;
2242
2199
  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
2200
  try {
2260
2201
  await sendMediaWithLeadingCaption({
2261
2202
  mediaUrls,
@@ -2289,12 +2230,12 @@ function createFeishuReplyDispatcher(params) {
2289
2230
  mediaUrl
2290
2231
  });
2291
2232
  sentFallbackText = true;
2292
- results.push(await sendFallbackText(fallbackText));
2233
+ results.push(await sendPostReply(fallbackText, "final"));
2293
2234
  }
2294
2235
  });
2295
2236
  if (degradedVoiceFallbackText && !sentFallbackText) {
2296
2237
  sentFallbackText = true;
2297
- results.push(await sendFallbackText(degradedVoiceFallbackText));
2238
+ results.push(await sendPostReply(degradedVoiceFallbackText, "final"));
2298
2239
  }
2299
2240
  } catch (error) {
2300
2241
  const partial = isChannelPartialDeliveryError(error) ? error.deliveryResult : void 0;
@@ -2306,8 +2247,8 @@ function createFeishuReplyDispatcher(params) {
2306
2247
  const ensureNoVisibleReplyFallback = async (reason) => {
2307
2248
  await idleSideEffectsPromise;
2308
2249
  if (visibleReplySent) return false;
2309
- if (skippedFinalReason === "silent") {
2310
- params.runtime.log?.(`feishu[${account.accountId}]: no-visible-reply fallback skipped for intentional silence (${reason})`);
2250
+ if (replyOutcome?.kind === "suppressed" || replyOutcome?.kind === "skipped" && replyOutcome.reason === "silent") {
2251
+ params.runtime.log?.(`feishu[${account.accountId}]: no-visible-reply fallback skipped for ${replyOutcome.reason} (${reason})`);
2311
2252
  return false;
2312
2253
  }
2313
2254
  await sendMessageFeishu({
@@ -2351,6 +2292,8 @@ function createFeishuReplyDispatcher(params) {
2351
2292
  text: content,
2352
2293
  useCard: true,
2353
2294
  infoKind,
2295
+ header: cardHeader,
2296
+ note: cardNote,
2354
2297
  chunkMentions: requiredMentionTargets,
2355
2298
  sendChunk: async ({ chunk, mentions }) => await sendStructuredCardFeishu({
2356
2299
  cfg,
@@ -2379,10 +2322,17 @@ function createFeishuReplyDispatcher(params) {
2379
2322
  if (completions.some((completion) => ownsCurrentClose(completion))) markClosedStreamingContentClaimed(closeOutcome.generation);
2380
2323
  for (const completion of completions) {
2381
2324
  const claimedSettlement = ownsCurrentClose(completion) ? {
2325
+ disposition: closeOutcome.disposition,
2382
2326
  result: finalized,
2383
2327
  ...closeOutcome.error === void 0 ? {} : { error: closeOutcome.error }
2384
2328
  } : claimClosedStreamingResult(completion.streamingGeneration, completion.result.content);
2385
2329
  const deliveryError = claimedSettlement?.error;
2330
+ if (claimedSettlement?.disposition === "discarded") {
2331
+ const retained = mergeFeishuReplyDeliveryResults([claimedSettlement.result, completion.result], claimedSettlement.result.content ?? "");
2332
+ if (deliveryError !== void 0) completion.reject(createFeishuPartialReplyDeliveryError(deliveryError, retained));
2333
+ else completion.resolve(retained);
2334
+ continue;
2335
+ }
2386
2336
  let providerFinalized = claimedSettlement?.result;
2387
2337
  try {
2388
2338
  providerFinalized = await ensureVisibleStreamingDelivery(providerFinalized, completion.result.content, completion.infoKind);
@@ -2419,22 +2369,16 @@ function createFeishuReplyDispatcher(params) {
2419
2369
  let finalized = noVisibleFeishuReplyDelivery;
2420
2370
  let finalizationError;
2421
2371
  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) {
2372
+ let settlement = claimClosedStreamingResult(paramsLocal.ownerGeneration, paramsLocal.content);
2373
+ if (!settlement && paramsLocal.ownerGeneration !== void 0 && inFlightStreamingClose?.generation === paramsLocal.ownerGeneration) {
2427
2374
  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
- }
2375
+ settlement = claimClosedStreamingResult(paramsLocal.ownerGeneration, paramsLocal.content) ?? closeOutcome;
2376
+ } else if (!settlement && paramsLocal.ownerGeneration !== void 0 && activeStreamingGeneration === paramsLocal.ownerGeneration) settlement = await closeStreaming();
2377
+ finalized = settlement?.result ?? finalized;
2378
+ finalizationError = settlement?.error;
2379
+ const discarded = settlement?.disposition === "discarded";
2436
2380
  try {
2437
- finalized = await ensureVisibleStreamingDelivery(finalized, paramsLocal.content, paramsLocal.infoKind) ?? finalized;
2381
+ if (!discarded) finalized = await ensureVisibleStreamingDelivery(finalized, paramsLocal.content, paramsLocal.infoKind) ?? finalized;
2438
2382
  } catch (fallbackError) {
2439
2383
  fallbackPartial = isChannelPartialDeliveryError(fallbackError) ? fallbackError.deliveryResult : void 0;
2440
2384
  const fallbackCause = fallbackPartial && fallbackError instanceof Error ? fallbackError.cause ?? fallbackError : fallbackError;
@@ -2445,9 +2389,10 @@ function createFeishuReplyDispatcher(params) {
2445
2389
  finalized,
2446
2390
  ...fallbackPartial ? [fallbackPartial] : [],
2447
2391
  ...mediaPartial ? [mediaPartial] : []
2448
- ], fallbackPartial?.visibleReplySent === true ? fallbackPartial.content : finalized.visibleReplySent === true ? finalized.content : paramsLocal.content);
2392
+ ], discarded ? finalized.content ?? "" : fallbackPartial?.visibleReplySent === true ? fallbackPartial.content : finalized.visibleReplySent === true ? finalized.content : paramsLocal.content);
2449
2393
  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);
2394
+ const cause = finalizationError ? new AggregateError([mediaCause, finalizationError instanceof Error ? finalizationError.cause ?? finalizationError : finalizationError], "Feishu reply delivery and streaming finalization failed") : mediaCause;
2395
+ throw createFeishuPartialReplyDeliveryError(cause, accepted);
2451
2396
  };
2452
2397
  const dispatcherOptions = {
2453
2398
  responsePrefix,
@@ -2460,13 +2405,14 @@ function createFeishuReplyDispatcher(params) {
2460
2405
  conversationType: chatId.startsWith("oc_") ? "group" : "direct"
2461
2406
  },
2462
2407
  onSkip: (_payload, info) => {
2463
- if (info.kind === "final" || info.kind === "block" && info.reason === "silent") {
2464
- skippedFinalReason = info.reason;
2465
- skippedFinalAssistantMessageIndex = info.assistantMessageIndex;
2466
- }
2408
+ if (replyOutcome?.kind !== "failed" && (info.kind === "final" || info.kind === "block" && info.reason === "silent")) replyOutcome = {
2409
+ kind: "skipped",
2410
+ reason: info.reason,
2411
+ assistantMessageIndex: info.assistantMessageIndex
2412
+ };
2467
2413
  },
2468
2414
  beforeDeliver: (payload, info) => {
2469
- preparedDeliveryAssistantMessageIndex = info.assistantMessageIndex;
2415
+ 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
2416
  return payload;
2471
2417
  },
2472
2418
  onReplyStart: async () => {
@@ -2477,9 +2423,7 @@ function createFeishuReplyDispatcher(params) {
2477
2423
  sentIndependentBlockText = false;
2478
2424
  idleRequestedForReply = false;
2479
2425
  visibleReplySent = false;
2480
- skippedFinalReason = null;
2481
- skippedFinalAssistantMessageIndex = void 0;
2482
- preparedDeliveryAssistantMessageIndex = void 0;
2426
+ replyOutcome = void 0;
2483
2427
  }
2484
2428
  if (previewStreamingEnabled && renderMode === "card") startStreaming();
2485
2429
  await Promise.resolve(typingCallbacks?.onReplyStart?.());
@@ -2490,6 +2434,7 @@ function createFeishuReplyDispatcher(params) {
2490
2434
  }
2491
2435
  };
2492
2436
  const handleDeliveryError = async (error, info) => {
2437
+ if (info.kind === "final") replyOutcome = { kind: "failed" };
2493
2438
  if (isChannelPartialDeliveryError(error)) markVisibleReplySent();
2494
2439
  params.runtime.error?.(`feishu[${account.accountId}] ${info.kind} reply failed: ${String(error)}`);
2495
2440
  await queueIdleSideEffects().catch((cleanupError) => params.runtime.error?.(`feishu[${account.accountId}] reply error cleanup failed: ${String(cleanupError)}`));
@@ -2498,19 +2443,36 @@ function createFeishuReplyDispatcher(params) {
2498
2443
  dispatcherOptions,
2499
2444
  delivery: {
2500
2445
  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;
2446
+ onDelivered: (_payload, info, result) => {
2447
+ if (result?.visibleReplySent) {
2448
+ markVisibleReplySent();
2449
+ if (info.kind === "final") replyOutcome = void 0;
2450
+ return;
2507
2451
  }
2508
- const payloadText = payload.isReasoning && payload.text ? formatReasoningMessage(payload.text) : payload.text;
2452
+ const reason = result?.suppression?.reason;
2453
+ 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 = {
2454
+ kind: "suppressed",
2455
+ reason
2456
+ };
2457
+ },
2458
+ deliver: async (inputPayload, info) => {
2459
+ const prepared = await renderFeishuReplyPayload(inputPayload, {
2460
+ to: sendTarget,
2461
+ identity,
2462
+ mentions: requiredMentionTargets
2463
+ });
2464
+ const rendered = consumeFeishuPresentationFallbackMarker(prepared.payload);
2465
+ const payload = rendered.payload;
2466
+ const presentationCard = prepared.card;
2467
+ const hasPresentationFallback = rendered.presentationFallback?.hasVisibleContent === true;
2468
+ const hasIndependentPresentation = presentationCard !== void 0 || hasPresentationFallback;
2469
+ const resolvedText = payload.text;
2470
+ const payloadText = payload.isReasoning && resolvedText ? formatReasoningMessage(resolvedText) : resolvedText;
2509
2471
  const reply = resolveSendableOutboundReplyParts({
2510
2472
  ...payload,
2511
2473
  text: payloadText
2512
2474
  });
2513
- const text = info?.kind === "final" ? mergeStreamingFinalText(streamText, reply.text, payload.isError === true && hasStreamingFinalText) : reply.text;
2475
+ const text = info?.kind === "final" && !hasIndependentPresentation ? mergeStreamingFinalText(streamText, reply.text, payload.isError === true && hasStreamingFinalText) : reply.text;
2514
2476
  const hasText = reply.hasText;
2515
2477
  const hasMedia = reply.hasMedia;
2516
2478
  const ttsSupplement = getReplyPayloadTtsSupplement(payload);
@@ -2524,52 +2486,61 @@ function createFeishuReplyDispatcher(params) {
2524
2486
  const useStaticCard = hasText && (renderMode === "card" || info?.kind === "block" && coreBlockStreamingEnabled && renderMode !== "raw" || renderMode === "auto" && shouldUseCard(text));
2525
2487
  const useStreamingCard = hasText && streamingEnabled && !finalTextExceedsStreamingLimit && (info?.kind === "final" || useStaticCard);
2526
2488
  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));
2489
+ const skipTextForDuplicateFinal = !hasIndependentPresentation && info?.kind === "final" && hasText && deliveredFinalTexts.has(text);
2490
+ const shouldDeliverText = hasText && (!hasVoiceMedia || hasPresentationFallback) && !skipTextForDuplicateFinal;
2491
+ const shouldDiscardStreamingPreview = info?.kind === "final" && !(hasIndependentPresentation && payload.isError === true && hasStreamingFinalText) && (hasIndependentPresentation || finalTextExceedsStreamingLimit || hasMedia && (hasVoiceMedia && !shouldDeliverText && !ttsTextAlreadyVisible || skipTextForDuplicateFinal));
2530
2492
  const priorClosedStreamingSettlement = info?.kind === "final" && hasText && skipTextForDuplicateFinal ? claimClosedStreamingResult(void 0, text) : void 0;
2531
- if (!shouldDeliverText && !hasMedia) {
2493
+ if (!shouldDeliverText && !hasMedia && !presentationCard) {
2532
2494
  if (priorClosedStreamingSettlement?.error !== void 0) throw toStringifiedError(priorClosedStreamingSettlement.error);
2533
2495
  return priorClosedStreamingSettlement?.result ?? noVisibleFeishuReplyDelivery;
2534
2496
  }
2535
2497
  const deliveredResults = priorClosedStreamingSettlement ? [priorClosedStreamingSettlement.result] : [];
2536
- const collectMediaDelivery = async (mediaPayload, mediaOptions) => {
2498
+ const collectDelivery = async (pending, acceptedContent) => {
2537
2499
  try {
2538
- deliveredResults.push(await sendMediaReplies(mediaPayload, mediaOptions));
2500
+ deliveredResults.push(await pending);
2539
2501
  } catch (error) {
2540
2502
  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);
2503
+ const accumulated = mergeFeishuReplyDeliveryResults([...deliveredResults, ...partial ? [{
2504
+ ...partial,
2505
+ content: partial.content ?? acceptedContent
2506
+ }] : []]);
2507
+ throw createFeishuPartialReplyDeliveryError(partial && error instanceof Error ? error.cause ?? error : error, {
2508
+ ...accumulated,
2509
+ content: accumulated.content ?? ""
2510
+ });
2543
2511
  }
2544
2512
  };
2545
2513
  if (shouldDiscardStreamingPreview) await discardStreamingPreview();
2514
+ if (presentationCard) {
2515
+ if (hasMedia) await collectDelivery(sendMediaReplies(payload));
2516
+ await collectDelivery(sendCardFeishu({
2517
+ cfg,
2518
+ to: sendTarget,
2519
+ card: presentationCard,
2520
+ replyToMessageId: sendReplyToMessageId,
2521
+ replyInThread: effectiveReplyInThread,
2522
+ allowTopLevelReplyFallback,
2523
+ accountId
2524
+ }).then((result) => createFeishuReplyDeliveryResult({
2525
+ results: [result],
2526
+ visibleReplySent: true,
2527
+ content: resolvedText,
2528
+ kind: "card"
2529
+ })), resolvedText);
2530
+ markVisibleReplySent();
2531
+ return mergeFeishuReplyDeliveryResults(deliveredResults, resolvedText);
2532
+ }
2546
2533
  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);
2534
+ if (hasPresentationFallback || info?.kind === "block" && !useStreamingCard) {
2535
+ if (hasPresentationFallback || coreBlockStreamingEnabled) {
2536
+ const firstChunkMentions = info?.kind === "final" || info?.kind === "block" && !sentIndependentBlockText ? mentionTargets : void 0;
2537
+ await collectDelivery(sendPostReply(text, info?.kind, firstChunkMentions));
2538
+ if (info?.kind === "block") sentIndependentBlockText = true;
2539
+ if (hasMedia) await collectDelivery(sendMediaReplies(payload));
2572
2540
  }
2541
+ return mergeFeishuReplyDeliveryResults(deliveredResults, text);
2542
+ }
2543
+ if (info?.kind === "block") {
2573
2544
  startStreaming();
2574
2545
  if (streamingStartPromise) await streamingStartPromise;
2575
2546
  }
@@ -2578,7 +2549,7 @@ function createFeishuReplyDispatcher(params) {
2578
2549
  if (streamingStartPromise) await streamingStartPromise;
2579
2550
  }
2580
2551
  const shouldStreamText = info?.kind === "block" || info?.kind === "final";
2581
- const matchingInFlightClose = info?.kind === "final" && inFlightStreamingClose?.content === text ? inFlightStreamingClose : void 0;
2552
+ const matchingInFlightClose = info?.kind === "final" && inFlightStreamingClose?.disposition === "closed" && inFlightStreamingClose.content === text ? inFlightStreamingClose : void 0;
2582
2553
  const ownerGeneration = activeStreamingGeneration ?? matchingInFlightClose?.generation;
2583
2554
  if (shouldStreamText && ownerGeneration !== void 0 && (streaming?.isActive() || matchingInFlightClose !== void 0)) {
2584
2555
  if (activeStreamingGeneration !== void 0) {
@@ -2595,7 +2566,7 @@ function createFeishuReplyDispatcher(params) {
2595
2566
  }
2596
2567
  }
2597
2568
  if (hasMedia) try {
2598
- await collectMediaDelivery(payload);
2569
+ await collectDelivery(sendMediaReplies(payload));
2599
2570
  } catch (error) {
2600
2571
  await throwStreamingDeliveryFailure({
2601
2572
  error,
@@ -2613,6 +2584,8 @@ function createFeishuReplyDispatcher(params) {
2613
2584
  text,
2614
2585
  useCard: true,
2615
2586
  infoKind: info?.kind,
2587
+ header: cardHeader,
2588
+ note: cardNote,
2616
2589
  chunkMentions: requiredMentionTargets,
2617
2590
  sendChunk: async ({ chunk, mentions }) => await sendStructuredCardFeishu({
2618
2591
  cfg,
@@ -2629,27 +2602,12 @@ function createFeishuReplyDispatcher(params) {
2629
2602
  }));
2630
2603
  } else {
2631
2604
  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
- }));
2605
+ deliveredResults.push(await sendPostReply(text, info?.kind, firstChunkMentions));
2649
2606
  }
2650
2607
  }
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);
2608
+ if (hasMedia) await collectDelivery(sendMediaReplies(payload, !shouldDeliverText && !ttsTextAlreadyVisible && hasVoiceMedia && hasText ? { fallbackText: text } : void 0));
2609
+ const deliveredContent = hasVoiceMedia ? deliveredResults.at(-1)?.content ?? text : text;
2610
+ const result = mergeFeishuReplyDeliveryResults(deliveredResults, deliveredContent);
2653
2611
  if (priorClosedStreamingSettlement?.error !== void 0) throw createFeishuPartialReplyDeliveryError(isChannelPartialDeliveryError(priorClosedStreamingSettlement.error) && priorClosedStreamingSettlement.error instanceof Error ? priorClosedStreamingSettlement.error.cause ?? priorClosedStreamingSettlement.error : priorClosedStreamingSettlement.error, result);
2654
2612
  return result;
2655
2613
  },
@@ -2697,7 +2655,7 @@ function createFeishuReplyDispatcher(params) {
2697
2655
  ensureNoVisibleReplyFallback,
2698
2656
  getVisibleReplyState: () => ({
2699
2657
  visibleReplySent,
2700
- skippedFinalReason
2658
+ skippedFinalReason: replyOutcome?.kind === "skipped" || replyOutcome?.kind === "suppressed" ? replyOutcome.reason : null
2701
2659
  })
2702
2660
  };
2703
2661
  }
@@ -2715,7 +2673,7 @@ function getFeishuSyntheticDirectPreDispatchTarget(event) {
2715
2673
  //#endregion
2716
2674
  //#region extensions/feishu/src/bot.ts
2717
2675
  const permissionErrorNotifiedAt = /* @__PURE__ */ new Map();
2718
- const PERMISSION_ERROR_COOLDOWN_MS = 300 * 1e3;
2676
+ const PERMISSION_ERROR_COOLDOWN_MS = 3e5;
2719
2677
  function shouldSendNoVisibleReplyFallback(dispatchResult) {
2720
2678
  const emptyEligibleDispatch = dispatchResult.noVisibleReplyFallbackEligible === true && dispatchResult.settledReceipt?.anyVisibleDelivered !== true;
2721
2679
  const finalFailedBeforeSend = (dispatchResult.settledReceipt?.counts.final.failedBeforeSend ?? 0) > 0;
@@ -2724,10 +2682,11 @@ function shouldSendNoVisibleReplyFallback(dispatchResult) {
2724
2682
  function isFeishuTopicSessionScope(scope) {
2725
2683
  return scope === "group_topic" || scope === "group_topic_sender";
2726
2684
  }
2727
- async function resolveFeishuAudioPreflightTranscript(params) {
2728
- if (params.messageType !== "audio" || params.content.trim()) return;
2685
+ async function resolveFeishuAudioTranscript(params) {
2686
+ if (params.messageType !== "audio") return;
2729
2687
  const audioMedia = params.mediaList.filter((media) => Boolean(media.path) && (media.kind === "audio" || media.contentType?.startsWith("audio/")));
2730
2688
  if (audioMedia.length === 0) return;
2689
+ if (params.content.trim()) return params.content;
2731
2690
  try {
2732
2691
  const { transcribeFirstAudio } = await import("./audio-preflight.runtime-BBfC9elv.js");
2733
2692
  return await transcribeFirstAudio({
@@ -2922,32 +2881,34 @@ async function handleFeishuMessage(params) {
2922
2881
  }
2923
2882
  }
2924
2883
  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;
2884
+ if (feishuCfg?.resolveSenderNames ?? true) {
2885
+ if (ctx.senderType === "bot") {
2886
+ const senderName = await resolveFeishuBotName({
2887
+ account,
2888
+ openId: ctx.senderOpenId,
2889
+ log
2890
+ });
2891
+ if (senderName) ctx = {
2892
+ ...ctx,
2893
+ senderName
2894
+ };
2895
+ } else {
2896
+ const senderResult = await resolveFeishuSenderName({
2897
+ account,
2898
+ senderId: ctx.senderOpenId,
2899
+ log
2900
+ });
2901
+ if (senderResult.name) ctx = {
2902
+ ...ctx,
2903
+ senderName: senderResult.name
2904
+ };
2905
+ if (senderResult.permissionError) {
2906
+ const appKey = account.appId ?? "default";
2907
+ const now = Date.now();
2908
+ if (now - (permissionErrorNotifiedAt.get(appKey) ?? 0) > PERMISSION_ERROR_COOLDOWN_MS) {
2909
+ permissionErrorNotifiedAt.set(appKey, now);
2910
+ permissionErrorForAgent = senderResult.permissionError;
2911
+ }
2951
2912
  }
2952
2913
  }
2953
2914
  }
@@ -3335,7 +3296,7 @@ async function handleFeishuMessage(params) {
3335
3296
  log(`feishu[${account.accountId}]: skipping empty message (no text, no media, no quoted) from ${ctx.senderOpenId}`);
3336
3297
  return;
3337
3298
  }
3338
- const audioTranscript = await resolveFeishuAudioPreflightTranscript({
3299
+ const audioTranscript = await resolveFeishuAudioTranscript({
3339
3300
  cfg: effectiveCfg,
3340
3301
  mediaList,
3341
3302
  content: ctx.content,
@@ -3343,8 +3304,8 @@ async function handleFeishuMessage(params) {
3343
3304
  chatType: isGroup ? "group" : "direct",
3344
3305
  log
3345
3306
  });
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 });
3307
+ const transcribedAudioIndex = audioTranscript === void 0 ? -1 : mediaList.findIndex((media) => media.kind === "audio" || media.contentType?.startsWith("audio/"));
3308
+ const inboundMedia = await toInboundMediaFactsWithMetadata(mediaList, { transcribed: (_media, index) => index === transcribedAudioIndex });
3348
3309
  const requiredMentionTargets = isGroup && ctx.senderType === "bot" && ctx.senderOpenId ? [{
3349
3310
  openId: ctx.senderOpenId,
3350
3311
  name: ctx.senderName ?? ctx.senderOpenId,
@@ -4018,8 +3979,8 @@ function createApprovalCard(params) {
4018
3979
  }
4019
3980
  //#endregion
4020
3981
  //#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;
3982
+ const FEISHU_APPROVAL_CARD_TTL_MS = 3e5;
3983
+ const FEISHU_CARD_ACTION_TOKEN_TTL_MS = 9e5;
4023
3984
  function pruneProcessedCardActionTokens(now) {
4024
3985
  const validNow = asDateTimestampMs(now);
4025
3986
  if (validNow === void 0) {
@@ -4111,7 +4072,7 @@ async function dispatchSyntheticCommand(params) {
4111
4072
  });
4112
4073
  }
4113
4074
  const resolvedChatTypeCache = resolvedCardActionChatTypes;
4114
- const CHAT_TYPE_CACHE_TTL_MS = 30 * 6e4;
4075
+ const CHAT_TYPE_CACHE_TTL_MS = 18e5;
4115
4076
  const CHAT_TYPE_CACHE_MAX_SIZE = 5e3;
4116
4077
  function pruneChatTypeCache(now) {
4117
4078
  const validNow = asDateTimestampMs(now);
@@ -4366,7 +4327,8 @@ function inspectFeishuIngressEnvelope(rawEnvelope, encryptKey, allowInvalidLane
4366
4327
  if (!eventId) throw new FeishuIngressPermanentError("invalid-event", `Feishu ${eventType} envelope is missing header.event_id.`);
4367
4328
  const event = nestedEvent ?? envelope;
4368
4329
  if (eventType === "im.message.receive_v1") {
4369
- const chatId = normalizeNullableString((isRecord$1(event.message) ? event.message : null)?.chat_id);
4330
+ const message = isRecord$1(event.message) ? event.message : null;
4331
+ const chatId = normalizeNullableString(message?.chat_id);
4370
4332
  if (!chatId) {
4371
4333
  if (allowInvalidLane) return {
4372
4334
  eventId,
@@ -4490,6 +4452,7 @@ function buildFeishuFlushIngressLifecycle(sources, options) {
4490
4452
  handedOff = true;
4491
4453
  transportLifecycle.onDeferred();
4492
4454
  },
4455
+ onDeferredHeartbeat: () => transportLifecycle.onDeferredHeartbeat?.(),
4493
4456
  onAdoptionFinalizing: () => {
4494
4457
  transportLifecycle.onAdoptionFinalizing();
4495
4458
  },
@@ -4642,7 +4605,7 @@ const FEISHU_WEBHOOK_RATE_LIMIT_FALLBACK_DEFAULTS = {
4642
4605
  };
4643
4606
  const FEISHU_WEBHOOK_ANOMALY_FALLBACK_DEFAULTS = {
4644
4607
  maxTrackedKeys: 4096,
4645
- ttlMs: 360 * 6e4,
4608
+ ttlMs: 216e5,
4646
4609
  logEvery: 25
4647
4610
  };
4648
4611
  function coercePositiveInt(value, fallback) {
@@ -4673,7 +4636,7 @@ const httpServers = /* @__PURE__ */ new Map();
4673
4636
  const botOpenIds = /* @__PURE__ */ new Map();
4674
4637
  const botNames = /* @__PURE__ */ new Map();
4675
4638
  const botIdentityRevisions = /* @__PURE__ */ new Map();
4676
- const FEISHU_WEBHOOK_MAX_BODY_BYTES = 64 * 1024;
4639
+ const FEISHU_WEBHOOK_MAX_BODY_BYTES = 65536;
4677
4640
  const FEISHU_WEBHOOK_BODY_TIMEOUT_MS = 5e3;
4678
4641
  const FEISHU_HTTP_SERVER_CLOSE_TIMEOUT_MS = 5e3;
4679
4642
  const feishuWebhookRateLimitDefaults = resolveFeishuWebhookRateLimitDefaults(WEBHOOK_RATE_LIMIT_DEFAULTS);
@@ -4821,7 +4784,7 @@ function startBotIdentityRecovery(params) {
4821
4784
  }
4822
4785
  //#endregion
4823
4786
  //#region extensions/feishu/src/card-ux-launcher.ts
4824
- const FEISHU_QUICK_ACTION_CARD_TTL_MS = 10 * 6e4;
4787
+ const FEISHU_QUICK_ACTION_CARD_TTL_MS = 6e5;
4825
4788
  const QUICK_ACTION_MENU_KEYS = /* @__PURE__ */ new Set([
4826
4789
  "quick-actions",
4827
4790
  "quick_actions",
@@ -5043,10 +5006,11 @@ function createFeishuCommentReplyDispatcher(params) {
5043
5006
  channel: "feishu",
5044
5007
  accountId: params.accountId
5045
5008
  });
5046
- const client = createFeishuClient(resolveFeishuRuntimeAccount({
5009
+ const account = resolveFeishuRuntimeAccount({
5047
5010
  cfg: params.cfg,
5048
5011
  accountId: params.accountId
5049
- }));
5012
+ });
5013
+ const client = createFeishuClient(account);
5050
5014
  const textChunkLimit = core.channel.text.resolveTextChunkLimit(params.cfg, "feishu", params.accountId, { fallbackLimit: 4e3 });
5051
5015
  const chunkMode = core.channel.text.resolveChunkMode(params.cfg, "feishu", params.accountId);
5052
5016
  const typingReaction = createCommentTypingReactionLifecycle({
@@ -5075,7 +5039,7 @@ function createFeishuCommentReplyDispatcher(params) {
5075
5039
  if (info.kind !== "final") return noVisibleFeishuReplyDelivery;
5076
5040
  const reply = resolveSendableOutboundReplyParts(payload);
5077
5041
  const { presentation } = resolveFeishuRichReply(payload);
5078
- let { text } = buildFeishuCommentPresentationFallback({
5042
+ let { commentText: text } = buildFeishuPresentationFallback({
5079
5043
  text: reply.text,
5080
5044
  presentation
5081
5045
  });
@@ -5634,7 +5598,7 @@ async function resolveDriveCommentEventCore(params) {
5634
5598
  return null;
5635
5599
  }
5636
5600
  const context = await fetchDriveCommentContext({
5637
- client: createClient ? createClient(account ?? { accountId }) : createFeishuClient((await import("./accounts-RwSyseKr.js").then((n) => n.t)).resolveFeishuAccount({
5601
+ client: createClient ? createClient(account ?? { accountId }) : createFeishuClient((await import("./accounts-cCMNFBKg.js").then((n) => n.t)).resolveFeishuAccount({
5638
5602
  cfg,
5639
5603
  accountId
5640
5604
  })),
@@ -5994,7 +5958,8 @@ async function handleFeishuCommentEvent(params) {
5994
5958
  })
5995
5959
  }
5996
5960
  });
5997
- const counts = resolveInboundReplyDispatchCounts(turnResult.dispatched ? turnResult.dispatchResult : void 0);
5961
+ const dispatchResult = turnResult.dispatched ? turnResult.dispatchResult : void 0;
5962
+ const counts = resolveInboundReplyDispatchCounts(dispatchResult);
5998
5963
  log(`feishu[${account.accountId}]: drive comment dispatch complete (replies=${counts.final}, session=${commentSessionKey})`);
5999
5964
  } finally {
6000
5965
  cleanupTypingReaction();
@@ -6019,7 +5984,7 @@ async function handleFeishuCommentEvent(params) {
6019
5984
  * resolves) keeps later same-chat messages in `queued` state until the
6020
5985
  * gateway is restarted. See #70133.
6021
5986
  */
6022
- const DEFAULT_TASK_TIMEOUT_MS = 300 * 1e3;
5987
+ const DEFAULT_TASK_TIMEOUT_MS = 3e5;
6023
5988
  function createSequentialQueue(options = {}) {
6024
5989
  const queues = /* @__PURE__ */ new Map();
6025
5990
  const taskTimeoutMs = options.taskTimeoutMs ?? DEFAULT_TASK_TIMEOUT_MS;
@@ -6165,8 +6130,9 @@ function resolveFeishuDebounceMentions(params) {
6165
6130
  return botMentions.length > 0 ? botMentions : void 0;
6166
6131
  }
6167
6132
  function createFeishuMessageReceiveHandler({ cfg, channelRuntime, accountId, runtime, chatHistories, fireAndForget, handleMessage, resolveDebounceText: resolveText, hasProcessedMessage, getBotOpenId = () => void 0, getBotName = () => void 0, resolveSequentialKey = ({ accountId: accountIdLocal, event }) => `feishu:${accountIdLocal}:${event.message.chat_id?.trim() || "unknown"}`, statusSink, resolveIngressLifecycle }) {
6168
- const inboundDebounceMs = channelRuntime.debounce.resolveInboundDebounceMs({
6169
- cfg,
6133
+ const readConfig = createRuntimeConfigReader(cfg);
6134
+ const resolveDebounceMs = () => channelRuntime.debounce.resolveInboundDebounceMs({
6135
+ cfg: readConfig(),
6170
6136
  channel: "feishu"
6171
6137
  });
6172
6138
  const log = runtime?.log ?? console.log;
@@ -6225,13 +6191,14 @@ function createFeishuMessageReceiveHandler({ cfg, channelRuntime, accountId, run
6225
6191
  }
6226
6192
  };
6227
6193
  const inboundDebouncer = channelRuntime.debounce.createInboundDebouncer({
6228
- debounceMs: inboundDebounceMs,
6194
+ debounceMs: resolveDebounceMs(),
6195
+ resolveDebounceMs,
6229
6196
  buildKey: ({ event }) => {
6230
6197
  const chatId = event.message.chat_id?.trim();
6231
6198
  const senderId = resolveSenderDebounceId(event);
6232
6199
  if (!chatId || !senderId) return null;
6233
- const rootId = event.message.root_id?.trim();
6234
- return `feishu:${accountId}:${chatId}:${rootId ? `thread:${rootId}` : "chat"}:${senderId}`;
6200
+ const threadId = event.message.root_id?.trim() || event.message.thread_id?.trim();
6201
+ return `feishu:${accountId}:${chatId}:${threadId ? `thread:${threadId}` : "chat"}:${senderId}`;
6235
6202
  },
6236
6203
  shouldDebounce: ({ event }) => {
6237
6204
  if (event.message.message_type !== "text") return false;
@@ -6427,7 +6394,8 @@ function isFeishuWebhookSignatureValid(params) {
6427
6394
  const nonce = Array.isArray(nonceHeader) ? nonceHeader[0] : nonceHeader;
6428
6395
  const signature = Array.isArray(signatureHeader) ? signatureHeader[0] : signatureHeader;
6429
6396
  if (!timestamp || !nonce || !signature) return false;
6430
- return safeEqualSecret(crypto.createHash("sha256").update(timestamp + nonce + encryptKey + params.rawBody).digest("hex"), signature);
6397
+ const computedSignature = crypto.createHash("sha256").update(timestamp + nonce + encryptKey + params.rawBody).digest("hex");
6398
+ return safeEqualSecret(computedSignature, signature);
6431
6399
  }
6432
6400
  function respondText(res, statusCode, body) {
6433
6401
  res.statusCode = statusCode;
@@ -6617,8 +6585,10 @@ async function monitorWebhook({ account, accountId, runtime, abortSignal, eventD
6617
6585
  respondText(res, 404, "Not Found");
6618
6586
  return;
6619
6587
  }
6620
- res.on("finish", () => {
6588
+ res.once("close", () => {
6621
6589
  recordWebhookStatus(runtime, accountId, path, res.statusCode);
6590
+ });
6591
+ res.once("finish", () => {
6622
6592
  if (res.statusCode >= 200 && res.statusCode < 300) {
6623
6593
  const inboundAt = Date.now();
6624
6594
  statusSink?.({
@@ -6627,16 +6597,17 @@ async function monitorWebhook({ account, accountId, runtime, abortSignal, eventD
6627
6597
  });
6628
6598
  }
6629
6599
  });
6600
+ const rateLimitKey = buildFeishuWebhookRateLimitKey({
6601
+ accountId,
6602
+ path,
6603
+ clientIp: resolveRequestClientIp(req)
6604
+ });
6630
6605
  if (!applyBasicWebhookRequestGuards({
6631
6606
  req,
6632
6607
  res,
6633
6608
  allowMethods: ["POST"],
6634
6609
  rateLimiter: feishuWebhookRateLimiter,
6635
- rateLimitKey: buildFeishuWebhookRateLimitKey({
6636
- accountId,
6637
- path,
6638
- clientIp: resolveRequestClientIp(req)
6639
- }),
6610
+ rateLimitKey,
6640
6611
  nowMs: Date.now(),
6641
6612
  requireJsonContentType: true
6642
6613
  })) return;
@@ -6869,7 +6840,7 @@ async function resolveReactionSyntheticEvent(params) {
6869
6840
  const senderUserId = event.user_id?.user_id?.trim();
6870
6841
  const senderId = senderOpenId || senderUserId;
6871
6842
  if (!emoji || !messageId || !senderId) return null;
6872
- const { resolveFeishuAccount } = await import("./accounts-RwSyseKr.js").then((n) => n.t);
6843
+ const { resolveFeishuAccount } = await import("./accounts-cCMNFBKg.js").then((n) => n.t);
6873
6844
  const reactionNotifications = resolveFeishuAccount({
6874
6845
  cfg,
6875
6846
  accountId