@openclaw/feishu 2026.7.2-beta.1 → 2026.7.2-beta.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,16 +1,16 @@
1
1
  import { _ as normalizeCommentFileType, c as encodeQuery, d as isRecord$1, f as normalizeString, g as buildFeishuCommentTarget, h as requestFeishuApi, l as extractReplyText, m as readString, o as resolveFeishuAccount, p as parseCommentContentElements, s as resolveFeishuRuntimeAccount } from "./accounts-BDoGHJDk.js";
2
2
  import { i as resolveReceiveIdType } from "./targets-BUjQ1TcA.js";
3
- import { C as resolveFeishuGroupConfig, D as resolveFeishuReplyPolicy, M as createFeishuCardInteractionEnvelope, N as decodeFeishuCardAction, O as normalizeFeishuChatType$2, S as resolveFeishuDmIngressAccess, T as resolveFeishuGroupSenderActivationIngressAccess, b as hasExplicitFeishuGroupConfig, d as chunkFeishuPostMarkdown, f as materializeFeishuPostMarkdownSoftBreaks, j as buildFeishuCardActionTextFallback, k as resolveFeishuChatType, o as resolveFeishuCardTemplate, w as resolveFeishuGroupConversationIngressAccess, x as normalizeFeishuAllowEntry } from "./send-result-CPS7AozW.js";
3
+ import { A as resolveFeishuChatType, C as resolveFeishuDmIngressAccess, E as resolveFeishuGroupSenderActivationIngressAccess, M as buildFeishuCardActionTextFallback, N as createFeishuCardInteractionEnvelope, O as resolveFeishuReplyPolicy, P as decodeFeishuCardAction, S as normalizeFeishuAllowEntry, T as resolveFeishuGroupConversationIngressAccess, d as chunkFeishuPostMarkdown, f as materializeFeishuPostMarkdownSoftBreaks, k as normalizeFeishuChatType$2, o as resolveFeishuCardTemplate, w as resolveFeishuGroupConfig, x as hasExplicitFeishuGroupConfig } from "./send-result-DsqTk27v.js";
4
4
  import { o as resolveConfiguredFeishuGroupSessionScope, t as buildFeishuConversationId } from "./conversation-id-BeJL-wq7.js";
5
+ import { a as getFeishuUserAgent, i as createFeishuWSClient, n as createEventDispatcher, r as createFeishuClient, s as resolveConfiguredHttpTimeoutMs } from "./client-87BmeMpj.js";
5
6
  import { t as getFeishuRuntime } from "./runtime-C5JxBWZp.js";
6
- import { a as getFeishuUserAgent, i as createFeishuWSClient, n as createEventDispatcher, r as createFeishuClient, s as resolveConfiguredHttpTimeoutMs } from "./client-BUC-R2Wi.js";
7
- import { i as createCommentTypingReactionLifecycle, l as getChatInfo, t as deliverCommentThreadText } from "./drive-Bpe8ONK_.js";
7
+ import { i as createCommentTypingReactionLifecycle, l as getChatInfo, t as deliverCommentThreadText } from "./drive-B4E8OXZ0.js";
8
8
  import { t as createFeishuThreadBindingManager } from "./thread-bindings-V0bwk0A1.js";
9
9
  import { createReplyPrefixContext, evaluateSupplementalContextVisibility, getSessionEntry, normalizeAgentId as normalizeAgentId$2, resolveChannelContextVisibilityMode } from "./runtime-api.js";
10
10
  import { t as readFeishuJsonResponse } from "./json-response-CheljwGr.js";
11
- import { _ as resolveFeishuIdentityEmoji, c as parsePostContent, d as isMentionForwardRequest, f as isFeishuGroupChatType, g as normalizeFeishuExternalKey, h as shouldSuppressFeishuTextForVoiceMedia, i as sendCardFeishu, l as extractMentionTargets, m as sendMediaFeishu, n as getMessageFeishu, o as sendMessageFeishu, p as saveMessageResourceFeishu, r as listFeishuThreadMessages, s as sendStructuredCardFeishu, u as isFeishuBroadcastMention } from "./send-CpnLYueT.js";
12
- import { i as waitForAbortableDelay, r as raceWithTimeoutAndAbort } from "./probe-CAloGqgG.js";
13
- import { t as fetchBotIdentityForMonitor } from "./monitor.startup-DpZzunQn.js";
11
+ import { _ as resolveFeishuIdentityEmoji, c as parsePostContent, d as isMentionForwardRequest, f as isFeishuGroupChatType, g as normalizeFeishuExternalKey, h as shouldSuppressFeishuTextForVoiceMedia, i as sendCardFeishu, l as extractMentionTargets, m as sendMediaFeishu, n as getMessageFeishu, o as sendMessageFeishu, p as saveMessageResourceFeishu, r as listFeishuThreadMessages, s as sendStructuredCardFeishu, u as isFeishuBroadcastMention } from "./send-DrWM_Ilf.js";
12
+ import { i as waitForAbortableDelay, r as raceWithTimeoutAndAbort } from "./probe-BgboTHIn.js";
13
+ import { t as fetchBotIdentityForMonitor } from "./monitor.startup-Cy8NPGAX.js";
14
14
  import { createChannelMessageReplyPipeline, formatChannelProgressDraftLineForEntry, isChannelProgressDraftWorkToolName, resolveAgentOutboundIdentity, resolveChannelPreviewStreamMode, resolveChannelStreamingBlockEnabled } from "openclaw/plugin-sdk/channel-outbound";
15
15
  import { createChannelPairingController, createChannelPairingController as createChannelPairingController$1 } from "openclaw/plugin-sdk/channel-pairing";
16
16
  import { ensureConfiguredBindingRouteReady, resolveConfiguredBindingRoute, resolveRuntimeConversationBindingRoute } from "openclaw/plugin-sdk/conversation-runtime";
@@ -33,7 +33,7 @@ import crypto, { createHash } from "node:crypto";
33
33
  import { buildChannelInboundEventContext, formatInboundMediaUnavailableText, toInboundMediaFacts } from "openclaw/plugin-sdk/channel-inbound";
34
34
  import { DEFAULT_GROUP_HISTORY_LIMIT, createChannelHistoryWindow } from "openclaw/plugin-sdk/reply-history";
35
35
  import { sliceUtf16Safe, truncateUtf16Safe } from "openclaw/plugin-sdk/text-utility-runtime";
36
- import { createClaimableDedupe } from "openclaw/plugin-sdk/persistent-dedupe";
36
+ import { createChannelReplayGuard } from "openclaw/plugin-sdk/persistent-dedupe";
37
37
  import { resolveChannelConfigWrites } from "openclaw/plugin-sdk/channel-config-writes";
38
38
  import { formatReasoningMessage } from "openclaw/plugin-sdk/agent-runtime";
39
39
  import { logTypingFailure } from "openclaw/plugin-sdk/channel-feedback";
@@ -563,12 +563,15 @@ const DEDUP_TTL_MS = 1440 * 60 * 1e3;
563
563
  const MEMORY_MAX_SIZE = 1e3;
564
564
  const STORE_MAX_ENTRIES = 1e4;
565
565
  function createFeishuDedupeGuard() {
566
- return createClaimableDedupe({
567
- pluginId: "feishu",
568
- namespacePrefix: DEDUPE_NAMESPACE_PREFIX,
569
- ttlMs: DEDUP_TTL_MS,
570
- memoryMaxSize: MEMORY_MAX_SIZE,
571
- stateMaxEntries: STORE_MAX_ENTRIES
566
+ return createChannelReplayGuard({
567
+ dedupe: {
568
+ pluginId: "feishu",
569
+ namespacePrefix: DEDUPE_NAMESPACE_PREFIX,
570
+ ttlMs: DEDUP_TTL_MS,
571
+ memoryMaxSize: MEMORY_MAX_SIZE,
572
+ stateMaxEntries: STORE_MAX_ENTRIES
573
+ },
574
+ buildReplayKey: (messageId) => messageId
572
575
  });
573
576
  }
574
577
  const feishuDedupeState = {
@@ -590,18 +593,13 @@ function dedupeOptions(namespace, log) {
590
593
  }
591
594
  /**
592
595
  * Claims a dedupe key for exclusive handling. Duplicate (already committed)
593
- * and in-flight keys are reported; blank keys fail open as claimed so an
596
+ * and in-flight keys are reported; blank keys fail open as invalid so an
594
597
  * unidentifiable event is never suppressed.
595
598
  */
596
599
  async function claimUnprocessedFeishuMessage(params) {
597
- const key = dedupeKey(params.messageId);
598
- if (!key) return "claimed";
599
- return (await feishuDedupeState.guard.claim(key, dedupeOptions(params.namespace, params.log))).kind;
600
- }
601
- /** Drops an uncommitted claim so a failed handler can retry the message. */
602
- function releaseFeishuMessageProcessing(messageId, namespace = "global") {
603
- const key = dedupeKey(messageId);
604
- if (key) feishuDedupeState.guard.release(key, { namespace });
600
+ const claim = await feishuDedupeState.guard.claim(params.messageId, dedupeOptions(params.namespace, params.log));
601
+ if (claim.kind === "inflight") return { kind: "inflight" };
602
+ return claim;
605
603
  }
606
604
  /**
607
605
  * Claims (unless the caller already holds the claim) and commits a message.
@@ -612,26 +610,22 @@ async function finalizeFeishuMessageProcessing(params) {
612
610
  const key = dedupeKey(params.messageId);
613
611
  if (!key) return false;
614
612
  const options = dedupeOptions(params.namespace, params.log);
615
- if (!params.claimHeld && (await feishuDedupeState.guard.claim(key, options)).kind !== "claimed") return false;
616
- return await feishuDedupeState.guard.commit(key, options);
613
+ const claim = params.processingClaim ?? await feishuDedupeState.guard.claim(key, options);
614
+ if ("kind" in claim && claim.kind !== "claimed") return false;
615
+ return await ("kind" in claim ? claim.handle : claim).commit();
617
616
  }
618
617
  /** Records a handled message so restart/replay cannot dispatch it again; false when already recorded. */
619
618
  async function recordProcessedFeishuMessage(messageId, namespace = "global", log) {
620
- const key = dedupeKey(messageId);
621
- if (!key) return false;
622
- return await feishuDedupeState.guard.commit(key, dedupeOptions(namespace, log));
619
+ const claim = await feishuDedupeState.guard.claim(messageId, dedupeOptions(namespace, log));
620
+ return claim.kind === "claimed" ? await claim.handle.commit() : false;
623
621
  }
624
622
  /** Forgets a recorded message so a retryable synthetic event can be handled on redelivery. */
625
623
  async function forgetProcessedFeishuMessage(messageId, namespace = "global", log) {
626
- const key = dedupeKey(messageId);
627
- if (!key) return false;
628
- return await feishuDedupeState.guard.forget(key, dedupeOptions(namespace, log));
624
+ return await feishuDedupeState.guard.forget(messageId, dedupeOptions(namespace, log));
629
625
  }
630
626
  /** Checks recency without claiming or recording. */
631
627
  async function hasProcessedFeishuMessage(messageId, namespace = "global", log) {
632
- const key = dedupeKey(messageId);
633
- if (!key) return false;
634
- return await feishuDedupeState.guard.hasRecent(key, dedupeOptions(namespace, log));
628
+ return await feishuDedupeState.guard.hasRecent(messageId, dedupeOptions(namespace, log));
635
629
  }
636
630
  /** Loads recent persisted entries into memory at account start; returns the loaded count. */
637
631
  async function warmupDedupFromPluginState(namespace, log) {
@@ -2148,7 +2142,7 @@ async function filterFetchedGroupContextMessages(messages, params) {
2148
2142
  }) ? message : void 0))).filter((message) => message !== void 0);
2149
2143
  }
2150
2144
  async function handleFeishuMessage(params) {
2151
- const { cfg, event, botOpenId, botName, runtime, channelRuntime, chatHistories, accountId, processingClaimHeld = false, messageDedupeKey: messageDedupeKeyOverride } = params;
2145
+ const { cfg, event, botOpenId, botName, runtime, channelRuntime, chatHistories, accountId, processingClaim, messageDedupeKey: messageDedupeKeyOverride } = params;
2152
2146
  const account = resolveFeishuRuntimeAccount({
2153
2147
  cfg,
2154
2148
  accountId
@@ -2162,7 +2156,7 @@ async function handleFeishuMessage(params) {
2162
2156
  messageId: messageDedupeKey,
2163
2157
  namespace: account.accountId,
2164
2158
  log,
2165
- claimHeld: processingClaimHeld
2159
+ processingClaim
2166
2160
  })) {
2167
2161
  log(`feishu: skipping duplicate message ${messageId}`);
2168
2162
  return;
@@ -2827,7 +2821,7 @@ async function handleFeishuMessage(params) {
2827
2821
  wasMentioned,
2828
2822
  requireMention
2829
2823
  },
2830
- commands: { authorized: commandAuthorized }
2824
+ commands: { authorized: commandAuthorized === true }
2831
2825
  },
2832
2826
  extra: {
2833
2827
  RootMessageId: ctx.rootId,
@@ -3863,11 +3857,11 @@ function createFeishuBotMenuHandler(params) {
3863
3857
  namespace: accountId,
3864
3858
  log
3865
3859
  });
3866
- if (claim === "duplicate") {
3860
+ if (claim.kind === "duplicate") {
3867
3861
  log(`feishu[${accountId}]: dropping duplicate bot-menu event for ${syntheticMessageId}`);
3868
3862
  return;
3869
3863
  }
3870
- if (claim === "inflight") {
3864
+ if (claim.kind === "inflight") {
3871
3865
  log(`feishu[${accountId}]: dropping in-flight bot-menu event for ${syntheticMessageId}`);
3872
3866
  return;
3873
3867
  }
@@ -3880,7 +3874,7 @@ function createFeishuBotMenuHandler(params) {
3880
3874
  channelRuntime: params.channelRuntime,
3881
3875
  chatHistories,
3882
3876
  accountId,
3883
- processingClaimHeld: true
3877
+ processingClaim: claim.kind === "claimed" ? claim.handle : void 0
3884
3878
  });
3885
3879
  const promise = maybeHandleFeishuQuickActionMenu({
3886
3880
  cfg,
@@ -3890,16 +3884,16 @@ function createFeishuBotMenuHandler(params) {
3890
3884
  accountId
3891
3885
  }).then(async (handledMenu) => {
3892
3886
  if (handledMenu) {
3893
- await recordProcessedFeishuMessage(syntheticMessageId, accountId, log);
3887
+ if (claim.kind === "claimed") await claim.handle.commit();
3894
3888
  return;
3895
3889
  }
3896
3890
  return await handleLegacyMenu();
3897
3891
  }).catch(async (err) => {
3898
- if (isFeishuRetryableSyntheticEventError(err)) await forgetProcessedFeishuMessage(syntheticMessageId, accountId, log);
3899
- else await recordProcessedFeishuMessage(syntheticMessageId, accountId, log);
3892
+ if (isFeishuRetryableSyntheticEventError(err)) {
3893
+ await forgetProcessedFeishuMessage(syntheticMessageId, accountId, log);
3894
+ if (claim.kind === "claimed") claim.handle.release({ error: err });
3895
+ } else if (claim.kind === "claimed") await claim.handle.commit();
3900
3896
  throw err;
3901
- }).finally(() => {
3902
- releaseFeishuMessageProcessing(syntheticMessageId, accountId);
3903
3897
  });
3904
3898
  if (fireAndForget) {
3905
3899
  promise.catch((err) => {
@@ -4088,11 +4082,6 @@ async function resolveParsedCommentContent(params) {
4088
4082
  linkedDocuments: resolvedLinkedDocuments
4089
4083
  };
4090
4084
  }
4091
- async function delayMs(ms) {
4092
- await new Promise((resolve) => {
4093
- setTimeout(resolve, ms);
4094
- });
4095
- }
4096
4085
  function buildDriveCommentTargetUrl(params) {
4097
4086
  return `/open-apis/drive/v1/files/${encodeURIComponent(params.fileToken)}/comments/batch_query` + encodeQuery({
4098
4087
  file_type: params.fileType,
@@ -4315,8 +4304,9 @@ async function fetchDriveCommentContext(params) {
4315
4304
  fetchedMatchedReply = params.replyId ? replies.find((reply) => reply.reply_id?.trim() === params.replyId?.trim()) : void 0;
4316
4305
  }
4317
4306
  if (params.replyId && !embeddedTargetReply && !fetchedMatchedReply) for (let attempt = 1; attempt <= FEISHU_COMMENT_REPLY_MISS_RETRY_LIMIT; attempt += 1) {
4307
+ if (params.abortSignal?.aborted) break;
4318
4308
  params.logger?.(`feishu[${params.accountId}]: retrying comment reply lookup comment=${params.commentId} requested_reply=${params.replyId} attempt=${attempt}/${FEISHU_COMMENT_REPLY_MISS_RETRY_LIMIT} delay_ms=${FEISHU_COMMENT_REPLY_MISS_RETRY_DELAY_MS}`);
4319
- await params.waitMs(FEISHU_COMMENT_REPLY_MISS_RETRY_DELAY_MS);
4309
+ if (!await waitForAbortableDelay(FEISHU_COMMENT_REPLY_MISS_RETRY_DELAY_MS, params.abortSignal)) break;
4320
4310
  const retried = await fetchDriveCommentReplies(params);
4321
4311
  if (retried.replies.length > 0) {
4322
4312
  params.logger?.(`feishu[${params.accountId}]: fetched retried comment replies comment=${params.commentId} attempt=${attempt} count=${retried.replies.length} log_ids=${safeJsonStringify(retried.logIds)} summary=${summarizeCommentRepliesForLog(retried.replies)}`);
@@ -4464,7 +4454,7 @@ function buildDriveCommentSurfacePrompt(params) {
4464
4454
  return lines.join("\n");
4465
4455
  }
4466
4456
  async function resolveDriveCommentEventCore(params) {
4467
- const { cfg, accountId, event, account, botOpenId, createClient, verificationTimeoutMs = FEISHU_COMMENT_VERIFY_TIMEOUT_MS, logger, waitMs = delayMs } = params;
4457
+ const { cfg, accountId, event, account, botOpenId, createClient, verificationTimeoutMs = FEISHU_COMMENT_VERIFY_TIMEOUT_MS, logger, abortSignal } = params;
4468
4458
  const eventId = event.event_id?.trim();
4469
4459
  const commentId = event.comment_id?.trim();
4470
4460
  const replyId = event.reply_id?.trim();
@@ -4502,7 +4492,7 @@ async function resolveDriveCommentEventCore(params) {
4502
4492
  timeoutMs: verificationTimeoutMs,
4503
4493
  logger,
4504
4494
  accountId,
4505
- waitMs
4495
+ abortSignal
4506
4496
  });
4507
4497
  return {
4508
4498
  eventId,
@@ -4628,7 +4618,8 @@ async function handleFeishuCommentEvent(params) {
4628
4618
  accountId: account.accountId,
4629
4619
  event: params.event,
4630
4620
  botOpenId: params.botOpenId,
4631
- logger: log
4621
+ logger: log,
4622
+ abortSignal: params.abortSignal
4632
4623
  });
4633
4624
  if (!turn) {
4634
4625
  log(`feishu[${account.accountId}]: drive comment notice skipped event=${params.event.event_id ?? "unknown"} comment=${params.event.comment_id ?? "unknown"}`);
@@ -4912,7 +4903,7 @@ function buildCommentNoticeQueueKey(event) {
4912
4903
  return `comment-doc:${event.notice_meta?.file_type?.trim() || "unknown"}:${event.notice_meta?.file_token?.trim() || "unknown"}`;
4913
4904
  }
4914
4905
  function createFeishuDriveCommentNoticeHandler(params) {
4915
- const { cfg, accountId, runtime, fireAndForget } = params;
4906
+ const { cfg, accountId, runtime, fireAndForget, abortSignal } = params;
4916
4907
  const log = runtime?.log ?? console.log;
4917
4908
  const error = runtime?.error ?? console.error;
4918
4909
  const enqueue = createSequentialQueue();
@@ -4932,20 +4923,22 @@ function createFeishuDriveCommentNoticeHandler(params) {
4932
4923
  }
4933
4924
  const eventId = event.event_id?.trim();
4934
4925
  const syntheticMessageId = eventId ? `drive-comment:${eventId}` : void 0;
4926
+ let processingClaim;
4935
4927
  if (syntheticMessageId) {
4936
4928
  const claim = await claimUnprocessedFeishuMessage({
4937
4929
  messageId: syntheticMessageId,
4938
4930
  namespace: accountId,
4939
4931
  log
4940
4932
  });
4941
- if (claim === "duplicate") {
4933
+ if (claim.kind === "duplicate") {
4942
4934
  log(`feishu[${accountId}]: dropping duplicate comment event ${syntheticMessageId}`);
4943
4935
  return;
4944
4936
  }
4945
- if (claim === "inflight") {
4937
+ if (claim.kind === "inflight") {
4946
4938
  log(`feishu[${accountId}]: dropping in-flight comment event ${syntheticMessageId}`);
4947
4939
  return;
4948
4940
  }
4941
+ processingClaim = claim.kind === "claimed" ? claim.handle : void 0;
4949
4942
  }
4950
4943
  log(`feishu[${accountId}]: received drive comment notice event=${event.event_id ?? "unknown"} type=${event.notice_meta?.notice_type ?? "unknown"} file=${event.notice_meta?.file_type ?? "unknown"}:${event.notice_meta?.file_token ?? "unknown"} comment=${event.comment_id ?? "unknown"} reply=${event.reply_id ?? "none"} from=${event.notice_meta?.from_user_id?.open_id ?? "unknown"} mentioned=${event.is_mentioned === true ? "yes" : "no"}`);
4951
4944
  try {
@@ -4955,15 +4948,15 @@ function createFeishuDriveCommentNoticeHandler(params) {
4955
4948
  accountId,
4956
4949
  event,
4957
4950
  botOpenId: getBotOpenId(accountId),
4958
- runtime
4951
+ runtime,
4952
+ abortSignal
4959
4953
  });
4960
4954
  });
4961
- if (syntheticMessageId) await recordProcessedFeishuMessage(syntheticMessageId, accountId, log);
4955
+ await processingClaim?.commit();
4962
4956
  } catch (err) {
4963
- if (syntheticMessageId && !isFeishuRetryableSyntheticEventError(err)) await recordProcessedFeishuMessage(syntheticMessageId, accountId, log);
4957
+ if (isFeishuRetryableSyntheticEventError(err)) processingClaim?.release({ error: err });
4958
+ else await processingClaim?.commit();
4964
4959
  throw err;
4965
- } finally {
4966
- if (syntheticMessageId) releaseFeishuMessageProcessing(syntheticMessageId, accountId);
4967
4960
  }
4968
4961
  });
4969
4962
  };
@@ -5005,7 +4998,7 @@ function dedupeFeishuDebounceEntriesByDedupeKey(entries) {
5005
4998
  const seen = /* @__PURE__ */ new Set();
5006
4999
  const deduped = [];
5007
5000
  for (const entry of entries) {
5008
- const dedupeKey = resolveFeishuMessageDedupeKey(entry);
5001
+ const dedupeKey = resolveFeishuMessageDedupeKey(entry.event);
5009
5002
  if (!dedupeKey) {
5010
5003
  deduped.push(entry);
5011
5004
  continue;
@@ -5027,7 +5020,7 @@ function resolveFeishuDebounceMentions(params) {
5027
5020
  const botMentions = merged.filter((mention) => mention.id.open_id?.trim() === normalizedBotOpenId);
5028
5021
  return botMentions.length > 0 ? botMentions : void 0;
5029
5022
  }
5030
- function createFeishuMessageReceiveHandler({ cfg, channelRuntime, accountId, runtime, chatHistories, fireAndForget, handleMessage, resolveDebounceText: resolveText, hasProcessedMessage, recordProcessedMessage, getBotOpenId = () => void 0, getBotName = () => void 0, resolveSequentialKey = ({ accountId: accountIdLocal, event }) => `feishu:${accountIdLocal}:${event.message.chat_id?.trim() || "unknown"}`, statusSink }) {
5023
+ 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 }) {
5031
5024
  const inboundDebounceMs = channelRuntime.debounce.resolveInboundDebounceMs({
5032
5025
  cfg,
5033
5026
  channel: "feishu"
@@ -5037,7 +5030,7 @@ function createFeishuMessageReceiveHandler({ cfg, channelRuntime, accountId, run
5037
5030
  const enqueue = createSequentialQueue({ onTaskTimeout: (key, timeoutMs) => {
5038
5031
  log(`feishu[${accountId}]: per-chat task exceeded ${timeoutMs}ms cap (key=${key}); evicting from queue so later same-key messages can proceed (#70133)`);
5039
5032
  } });
5040
- const dispatchFeishuMessage = async (event, messageDedupeKey) => {
5033
+ const dispatchFeishuMessage = async (event, messageDedupeKey, processingClaim) => {
5041
5034
  const sequentialKey = resolveSequentialKey({
5042
5035
  accountId,
5043
5036
  event,
@@ -5053,7 +5046,7 @@ function createFeishuMessageReceiveHandler({ cfg, channelRuntime, accountId, run
5053
5046
  channelRuntime,
5054
5047
  chatHistories,
5055
5048
  accountId,
5056
- processingClaimHeld: true,
5049
+ processingClaim,
5057
5050
  messageDedupeKey
5058
5051
  });
5059
5052
  await enqueue(sequentialKey, task);
@@ -5070,23 +5063,26 @@ function createFeishuMessageReceiveHandler({ cfg, channelRuntime, accountId, run
5070
5063
  };
5071
5064
  const recordSuppressedMessageIds = async (entries, dispatchDedupeKey) => {
5072
5065
  const keepDedupeKey = dispatchDedupeKey?.trim();
5073
- const suppressedIds = new Set(entries.map((entry) => resolveFeishuMessageDedupeKey(entry)).filter((id) => Boolean(id) && (!keepDedupeKey || id !== keepDedupeKey)));
5074
- for (const messageId of suppressedIds) try {
5075
- await recordProcessedMessage(messageId, accountId, log);
5066
+ const suppressedIds = new Set(entries.map((entry) => ({
5067
+ id: resolveFeishuMessageDedupeKey(entry.event),
5068
+ claim: entry.processingClaim
5069
+ })).filter(({ id }) => Boolean(id) && (!keepDedupeKey || id !== keepDedupeKey)));
5070
+ for (const suppressed of suppressedIds) try {
5071
+ await suppressed.claim?.commit();
5076
5072
  } catch (err) {
5077
- error(`feishu[${accountId}]: failed to record merged dedupe id ${messageId}: ${String(err)}`);
5073
+ error(`feishu[${accountId}]: failed to record merged dedupe id ${suppressed.id}: ${String(err)}`);
5078
5074
  }
5079
5075
  };
5080
5076
  const inboundDebouncer = channelRuntime.debounce.createInboundDebouncer({
5081
5077
  debounceMs: inboundDebounceMs,
5082
- buildKey: (event) => {
5078
+ buildKey: ({ event }) => {
5083
5079
  const chatId = event.message.chat_id?.trim();
5084
5080
  const senderId = resolveSenderDebounceId(event);
5085
5081
  if (!chatId || !senderId) return null;
5086
5082
  const rootId = event.message.root_id?.trim();
5087
5083
  return `feishu:${accountId}:${chatId}:${rootId ? `thread:${rootId}` : "chat"}:${senderId}`;
5088
5084
  },
5089
- shouldDebounce: (event) => {
5085
+ shouldDebounce: ({ event }) => {
5090
5086
  if (event.message.message_type !== "text") return false;
5091
5087
  const text = resolveDebounceText(event);
5092
5088
  return Boolean(text) && !channelRuntime.commands.isControlCommandMessage(text, cfg);
@@ -5095,35 +5091,35 @@ function createFeishuMessageReceiveHandler({ cfg, channelRuntime, accountId, run
5095
5091
  const last = entries.at(-1);
5096
5092
  if (!last) return;
5097
5093
  if (entries.length === 1) {
5098
- await dispatchFeishuMessage(last, resolveFeishuMessageDedupeKey(last));
5094
+ await dispatchFeishuMessage(last.event, resolveFeishuMessageDedupeKey(last.event), last.processingClaim);
5099
5095
  return;
5100
5096
  }
5101
5097
  const dedupedEntries = dedupeFeishuDebounceEntriesByDedupeKey(entries);
5102
5098
  const freshEntries = [];
5103
- for (const entry of dedupedEntries) if (!await hasProcessedMessage(resolveFeishuMessageDedupeKey(entry), accountId, log)) freshEntries.push(entry);
5099
+ for (const entry of dedupedEntries) if (!await hasProcessedMessage(resolveFeishuMessageDedupeKey(entry.event), accountId, log)) freshEntries.push(entry);
5104
5100
  const dispatchEntry = freshEntries.at(-1);
5105
5101
  if (!dispatchEntry) return;
5106
- const dispatchDedupeKey = resolveFeishuMessageDedupeKey(dispatchEntry);
5102
+ const dispatchDedupeKey = resolveFeishuMessageDedupeKey(dispatchEntry.event);
5107
5103
  await recordSuppressedMessageIds(dedupedEntries, dispatchDedupeKey);
5108
- const combinedText = freshEntries.map((entry) => resolveDebounceText(entry)).filter(Boolean).join("\n");
5104
+ const combinedText = freshEntries.map((entry) => resolveDebounceText(entry.event)).filter(Boolean).join("\n");
5109
5105
  const mergedMentions = resolveFeishuDebounceMentions({
5110
- entries: freshEntries,
5106
+ entries: freshEntries.map((entry) => entry.event),
5111
5107
  botOpenId: getBotOpenId(accountId)
5112
5108
  });
5113
5109
  await dispatchFeishuMessage({
5114
- ...dispatchEntry,
5110
+ ...dispatchEntry.event,
5115
5111
  message: {
5116
- ...dispatchEntry.message,
5112
+ ...dispatchEntry.event.message,
5117
5113
  ...combinedText.trim() ? {
5118
5114
  message_type: "text",
5119
5115
  content: JSON.stringify({ text: combinedText })
5120
5116
  } : {},
5121
- mentions: mergedMentions ?? dispatchEntry.message.mentions
5117
+ mentions: mergedMentions ?? dispatchEntry.event.message.mentions
5122
5118
  }
5123
- }, dispatchDedupeKey);
5119
+ }, dispatchDedupeKey, dispatchEntry.processingClaim);
5124
5120
  },
5125
5121
  onError: (err, entries) => {
5126
- for (const entry of entries) releaseFeishuMessageProcessing(resolveFeishuMessageDedupeKey(entry), accountId);
5122
+ for (const entry of entries) entry.processingClaim?.release({ error: err });
5127
5123
  error(`feishu[${accountId}]: inbound debounce flush failed: ${String(err)}`);
5128
5124
  }
5129
5125
  });
@@ -5141,22 +5137,24 @@ function createFeishuMessageReceiveHandler({ cfg, channelRuntime, accountId, run
5141
5137
  log(`feishu[${accountId}]: dropping self-authored message ${messageId ?? "unknown"}`);
5142
5138
  return;
5143
5139
  }
5144
- const messageDedupeKey = resolveFeishuMessageDedupeKey(event);
5145
5140
  const claim = await claimUnprocessedFeishuMessage({
5146
- messageId: messageDedupeKey,
5141
+ messageId: resolveFeishuMessageDedupeKey(event),
5147
5142
  namespace: accountId,
5148
5143
  log
5149
5144
  });
5150
- if (claim !== "claimed") {
5151
- log(`feishu[${accountId}]: dropping ${claim} event for message ${messageId}`);
5145
+ if (claim.kind === "duplicate" || claim.kind === "inflight") {
5146
+ log(`feishu[${accountId}]: dropping ${claim.kind} event for message ${messageId}`);
5152
5147
  return;
5153
5148
  }
5154
5149
  const processMessage = async () => {
5155
- await inboundDebouncer.enqueue(event);
5150
+ await inboundDebouncer.enqueue({
5151
+ event,
5152
+ ...claim.kind === "claimed" ? { processingClaim: claim.handle } : {}
5153
+ });
5156
5154
  };
5157
5155
  if (fireAndForget) {
5158
5156
  processMessage().catch((err) => {
5159
- releaseFeishuMessageProcessing(messageDedupeKey, accountId);
5157
+ if (claim.kind === "claimed") claim.handle.release({ error: err });
5160
5158
  error(`feishu[${accountId}]: error handling message: ${String(err)}`);
5161
5159
  });
5162
5160
  return;
@@ -5164,7 +5162,7 @@ function createFeishuMessageReceiveHandler({ cfg, channelRuntime, accountId, run
5164
5162
  try {
5165
5163
  await processMessage();
5166
5164
  } catch (err) {
5167
- releaseFeishuMessageProcessing(messageDedupeKey, accountId);
5165
+ if (claim.kind === "claimed") claim.handle.release({ error: err });
5168
5166
  error(`feishu[${accountId}]: error handling message: ${String(err)}`);
5169
5167
  }
5170
5168
  };
@@ -5619,7 +5617,7 @@ function parseFeishuCardActionEventPayload(value) {
5619
5617
  };
5620
5618
  }
5621
5619
  function registerEventHandlers(eventDispatcher, context) {
5622
- const { cfg, accountId, channelRuntime, runtime, chatHistories, fireAndForget } = context;
5620
+ const { cfg, accountId, channelRuntime, runtime, chatHistories, fireAndForget, abortSignal } = context;
5623
5621
  const log = runtime?.log ?? console.log;
5624
5622
  const error = runtime?.error ?? console.error;
5625
5623
  const runFeishuHandler = async (params) => {
@@ -5646,7 +5644,6 @@ function registerEventHandlers(eventDispatcher, context) {
5646
5644
  handleMessage: handleFeishuMessage,
5647
5645
  resolveDebounceText: ({ event, botOpenId, botName }) => parseFeishuMessageEvent(event, botOpenId, botName).content,
5648
5646
  hasProcessedMessage: hasProcessedFeishuMessage,
5649
- recordProcessedMessage: recordProcessedFeishuMessage,
5650
5647
  getBotOpenId: (id) => botOpenIds.get(id),
5651
5648
  getBotName: (id) => botNames.get(id),
5652
5649
  resolveSequentialKey: getFeishuSequentialKey,
@@ -5676,7 +5673,8 @@ function registerEventHandlers(eventDispatcher, context) {
5676
5673
  cfg,
5677
5674
  accountId,
5678
5675
  runtime,
5679
- fireAndForget
5676
+ fireAndForget,
5677
+ abortSignal
5680
5678
  }),
5681
5679
  "im.message.reaction.created_v1": async (data) => {
5682
5680
  await runFeishuHandler({
@@ -5805,6 +5803,7 @@ async function monitorSingleAccount(params) {
5805
5803
  runtime,
5806
5804
  chatHistories,
5807
5805
  fireAndForget: params.fireAndForget ?? true,
5806
+ abortSignal,
5808
5807
  ...params.statusSink ? { statusSink: params.statusSink } : {}
5809
5808
  });
5810
5809
  if (connectionMode === "webhook") return await monitorWebhook({
@@ -1,4 +1,4 @@
1
- import { t as probeFeishu } from "./probe-CAloGqgG.js";
1
+ import { t as probeFeishu } from "./probe-BgboTHIn.js";
2
2
  import { parseStrictPositiveInteger } from "openclaw/plugin-sdk/number-runtime";
3
3
  import { normalizeLowercaseStringOrEmpty } from "openclaw/plugin-sdk/string-coerce-runtime";
4
4
  //#region extensions/feishu/src/monitor-startup-timeout.ts
@@ -1,5 +1,5 @@
1
1
  import { t as __exportAll } from "./rolldown-runtime-8H4AJuhK.js";
2
- import { r as createFeishuClient } from "./client-BUC-R2Wi.js";
2
+ import { r as createFeishuClient } from "./client-87BmeMpj.js";
3
3
  import { asDateTimestampMs, resolveExpiresAtMsFromDurationMs, resolveTimerTimeoutMs } from "openclaw/plugin-sdk/number-runtime";
4
4
  import { formatErrorMessage } from "openclaw/plugin-sdk/error-runtime";
5
5
  //#region extensions/feishu/src/async.ts
@@ -1,18 +1,18 @@
1
1
  import { d as isRecord$1, h as requestFeishuApi, s as resolveFeishuRuntimeAccount } from "./accounts-BDoGHJDk.js";
2
2
  import { i as resolveReceiveIdType, r as normalizeFeishuTarget } from "./targets-BUjQ1TcA.js";
3
- import { c as assertFeishuPostWithinEnvelope, f as materializeFeishuPostMarkdownSoftBreaks, i as toFeishuSendResult, l as buildFeishuPostMessageContent, o as resolveFeishuCardTemplate, r as resolveFeishuReceiptKind, t as assertFeishuMessageApiSuccess } from "./send-result-CPS7AozW.js";
3
+ import { c as assertFeishuPostWithinEnvelope, f as materializeFeishuPostMarkdownSoftBreaks, i as toFeishuSendResult, l as buildFeishuPostMessageContent, o as resolveFeishuCardTemplate, r as resolveFeishuReceiptKind, t as assertFeishuMessageApiSuccess } from "./send-result-DsqTk27v.js";
4
+ import { r as createFeishuClient } from "./client-87BmeMpj.js";
4
5
  import { t as getFeishuRuntime } from "./runtime-C5JxBWZp.js";
5
- import { r as createFeishuClient } from "./client-BUC-R2Wi.js";
6
6
  import { parseStrictNonNegativeInteger } from "openclaw/plugin-sdk/number-runtime";
7
7
  import { isRecord, normalizeLowercaseStringOrEmpty } from "openclaw/plugin-sdk/string-coerce-runtime";
8
8
  import { convertMarkdownTables } from "openclaw/plugin-sdk/text-chunking";
9
9
  import fs from "node:fs";
10
10
  import path from "node:path";
11
- import { mediaKindFromMime } from "openclaw/plugin-sdk/media-mime";
12
11
  import { MEDIA_FFMPEG_MAX_AUDIO_DURATION_SECS, runFfmpeg, runFfprobe } from "openclaw/plugin-sdk/media-runtime";
13
12
  import { resolveMarkdownTableMode } from "openclaw/plugin-sdk/markdown-table-runtime";
14
13
  import { readRegularFile, writeExternalFileWithinRoot } from "openclaw/plugin-sdk/security-runtime";
15
14
  import { Readable } from "node:stream";
15
+ import { mediaKindFromMime } from "openclaw/plugin-sdk/media-mime";
16
16
  import { saveMediaBuffer, saveMediaStream } from "openclaw/plugin-sdk/media-store";
17
17
  import { resolvePreferredOpenClawTmpDir, withTempDownloadPath, withTempWorkspace } from "openclaw/plugin-sdk/temp-path";
18
18
  //#region extensions/feishu/src/identity-header.ts
@@ -50,6 +50,55 @@ function normalizeFeishuExternalKey(value) {
50
50
  return normalized;
51
51
  }
52
52
  //#endregion
53
+ //#region extensions/feishu/src/media-chunk-idle.ts
54
+ var FeishuInboundMediaTimeoutError = class extends Error {
55
+ constructor(chunkTimeoutMs) {
56
+ super(`Feishu media download stalled: no data received for ${chunkTimeoutMs}ms`);
57
+ this.name = "FeishuInboundMediaTimeoutError";
58
+ this.chunkTimeoutMs = chunkTimeoutMs;
59
+ }
60
+ };
61
+ function destroySource(source) {
62
+ const s = source;
63
+ if (typeof s.destroy === "function") s.destroy();
64
+ }
65
+ function withChunkIdleTimeout(source, chunkTimeoutMs) {
66
+ return { async *[Symbol.asyncIterator]() {
67
+ const iterator = source[Symbol.asyncIterator]();
68
+ let exhausted = false;
69
+ try {
70
+ while (true) {
71
+ const nextPromise = iterator.next();
72
+ let timeoutHandle;
73
+ const timeoutPromise = new Promise((_, reject) => {
74
+ timeoutHandle = setTimeout(() => {
75
+ reject(new FeishuInboundMediaTimeoutError(chunkTimeoutMs));
76
+ try {
77
+ destroySource(source);
78
+ } catch {}
79
+ }, chunkTimeoutMs);
80
+ });
81
+ let result;
82
+ try {
83
+ result = await Promise.race([nextPromise, timeoutPromise]);
84
+ } finally {
85
+ if (timeoutHandle !== void 0) clearTimeout(timeoutHandle);
86
+ }
87
+ if (result.done) {
88
+ exhausted = true;
89
+ return;
90
+ }
91
+ yield result.value;
92
+ }
93
+ } finally {
94
+ if (!exhausted && typeof iterator.return === "function") iterator.return().catch(() => void 0);
95
+ }
96
+ } };
97
+ }
98
+ function saveMediaStreamWithIdleTimeout(stream, contentType, maxBytes, fileName, chunkTimeoutMs) {
99
+ return saveMediaStream(withChunkIdleTimeout(stream, chunkTimeoutMs), contentType, "inbound", maxBytes, fileName);
100
+ }
101
+ //#endregion
53
102
  //#region extensions/feishu/src/send-target.ts
54
103
  function resolveFeishuSendTarget(params) {
55
104
  const target = params.to.trim();
@@ -177,14 +226,15 @@ async function saveFeishuResponseMedia(params) {
177
226
  if (responseWithOptionalFields.code !== void 0 && responseWithOptionalFields.code !== 0) throw new Error(`${params.errorPrefix}: ${responseWithOptionalFields.msg || `code ${responseWithOptionalFields.code}`}`);
178
227
  if (responseWithOptionalFields.data && Buffer.isBuffer(responseWithOptionalFields.data)) return saveMediaBuffer(responseWithOptionalFields.data, contentType, "inbound", maxBytes, fileName);
179
228
  if (responseWithOptionalFields.data instanceof ArrayBuffer) return saveMediaBuffer(Buffer.from(responseWithOptionalFields.data), contentType, "inbound", maxBytes, fileName);
180
- if (typeof response.getReadableStream === "function") return saveMediaStream(response.getReadableStream(), contentType, "inbound", maxBytes, fileName);
229
+ const save = (stream, ct = contentType, mb = maxBytes, fn = fileName) => saveMediaStreamWithIdleTimeout(stream, ct, mb, fn, FEISHU_MEDIA_HTTP_TIMEOUT_MS);
230
+ if (typeof response.getReadableStream === "function") return save(response.getReadableStream());
181
231
  if (typeof response.writeFile === "function") return await withTempDownloadPath({ prefix: params.tmpDirPrefix }, async (tmpPath) => {
182
232
  await response.writeFile(tmpPath);
183
233
  if ((await fs.promises.stat(tmpPath)).size > maxBytes) throw mediaLimitError(maxBytes);
184
- return await saveMediaStream(fs.createReadStream(tmpPath), contentType, "inbound", maxBytes, fileName);
234
+ return await save(fs.createReadStream(tmpPath));
185
235
  });
186
- if (responseWithOptionalFields[Symbol.asyncIterator]) return saveMediaStream(responseWithOptionalFields, contentType, "inbound", maxBytes, fileName);
187
- if (response instanceof Readable) return saveMediaStream(response, contentType, "inbound", maxBytes, fileName);
236
+ if (responseWithOptionalFields[Symbol.asyncIterator]) return save(responseWithOptionalFields);
237
+ if (response instanceof Readable) return save(response);
188
238
  const keys = Object.keys(response);
189
239
  throw new Error(`${params.errorPrefix}: unexpected response format. Keys: [${keys.join(", ")}]`);
190
240
  }
@@ -439,6 +439,13 @@ function canEnumerateAllFeishuPeers(account) {
439
439
  //#endregion
440
440
  //#region extensions/feishu/src/markdown.ts
441
441
  const FEISHU_POST_MAX_BYTES = 30 * 1024;
442
+ /** One parser contract for Feishu message and document Markdown decisions. */
443
+ function parseFeishuMarkdown(text) {
444
+ return fromMarkdown(text, {
445
+ extensions: [gfmTable()],
446
+ mdastExtensions: [gfmTableFromMarkdown()]
447
+ });
448
+ }
442
449
  function buildFeishuPostMentionElements(mentions) {
443
450
  if (!mentions?.length) return [];
444
451
  const elements = [];
@@ -465,10 +472,7 @@ function assertFeishuPostWithinEnvelope(content, label) {
465
472
  if (Buffer.byteLength(content, "utf8") > FEISHU_POST_MAX_BYTES) throw new Error(`${label} exceeds the 30 KB rich-post API limit`);
466
473
  }
467
474
  function collectSoftBreakOffsets(text) {
468
- const root = fromMarkdown(text, {
469
- extensions: [gfmTable()],
470
- mdastExtensions: [gfmTableFromMarkdown()]
471
- });
475
+ const root = parseFeishuMarkdown(text);
472
476
  const offsets = [];
473
477
  const pending = [root];
474
478
  while (pending.length > 0) {
@@ -763,4 +767,4 @@ function toFeishuSendResult(response, chatId, kind) {
763
767
  };
764
768
  }
765
769
  //#endregion
766
- export { FEISHU_CARD_INTERACTION_VERSION as A, resolveFeishuGroupConfig as C, resolveFeishuReplyPolicy as D, resolveFeishuGroupToolPolicy as E, createFeishuCardInteractionEnvelope as M, decodeFeishuCardAction as N, normalizeFeishuChatType as O, resolveFeishuDmIngressAccess as S, resolveFeishuGroupSenderActivationIngressAccess as T, isFeishuGroupReadAllowed as _, readNativeFeishuCardJson as a, hasExplicitFeishuGroupConfig as b, assertFeishuPostWithinEnvelope as c, chunkFeishuPostMarkdown as d, materializeFeishuPostMarkdownSoftBreaks as f, canEnumerateAllFeishuPeers as g, canEnumerateAllFeishuGroups as h, toFeishuSendResult as i, buildFeishuCardActionTextFallback as j, resolveFeishuChatType as k, buildFeishuPostMessageContent as l, authorizeFeishuChatMemberRead as m, createFeishuSendReceipt as n, resolveFeishuCardTemplate as o, assertFeishuChatReadAllowed as p, resolveFeishuReceiptKind as r, sanitizeNativeFeishuCard as s, assertFeishuMessageApiSuccess as t, chunkFeishuMarkdown as u, isFeishuGroupReadEnabled as v, resolveFeishuGroupConversationIngressAccess as w, normalizeFeishuAllowEntry as x, resolveFeishuChatReadPreliminaryAuthorization as y };
770
+ export { resolveFeishuChatType as A, resolveFeishuDmIngressAccess as C, resolveFeishuGroupToolPolicy as D, resolveFeishuGroupSenderActivationIngressAccess as E, buildFeishuCardActionTextFallback as M, createFeishuCardInteractionEnvelope as N, resolveFeishuReplyPolicy as O, decodeFeishuCardAction as P, normalizeFeishuAllowEntry as S, resolveFeishuGroupConversationIngressAccess as T, canEnumerateAllFeishuPeers as _, readNativeFeishuCardJson as a, resolveFeishuChatReadPreliminaryAuthorization as b, assertFeishuPostWithinEnvelope as c, chunkFeishuPostMarkdown as d, materializeFeishuPostMarkdownSoftBreaks as f, canEnumerateAllFeishuGroups as g, authorizeFeishuChatMemberRead as h, toFeishuSendResult as i, FEISHU_CARD_INTERACTION_VERSION as j, normalizeFeishuChatType as k, buildFeishuPostMessageContent as l, assertFeishuChatReadAllowed as m, createFeishuSendReceipt as n, resolveFeishuCardTemplate as o, parseFeishuMarkdown as p, resolveFeishuReceiptKind as r, sanitizeNativeFeishuCard as s, assertFeishuMessageApiSuccess as t, chunkFeishuMarkdown as u, isFeishuGroupReadAllowed as v, resolveFeishuGroupConfig as w, hasExplicitFeishuGroupConfig as x, isFeishuGroupReadEnabled as y };
package/dist/setup-api.js CHANGED
@@ -1,2 +1,2 @@
1
- import { i as feishuSetupAdapter, n as feishuSetupWizard, t as feishuPlugin } from "./channel-DdPQ2HGh.js";
1
+ import { i as feishuSetupAdapter, n as feishuSetupWizard, t as feishuPlugin } from "./channel-X30GQVwc.js";
2
2
  export { feishuPlugin, feishuSetupAdapter, feishuSetupWizard };