@openclaw/feishu 2026.5.28 → 2026.5.31-beta.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (33) hide show
  1. package/dist/{accounts-CXnY5H8g.js → accounts-Bpe6CjpS.js} +35 -3
  2. package/dist/api.js +18 -11
  3. package/dist/{channel-BfeesQob.js → channel-TXwK9jIG.js} +32 -24
  4. package/dist/channel-plugin-api.js +1 -1
  5. package/dist/{channel.runtime-BLnKcmtO.js → channel.runtime-AAff9iBZ.js} +6 -6
  6. package/dist/contract-api.js +1 -1
  7. package/dist/{drive-USxqssMG.js → drive-BIrffRwc.js} +18 -6
  8. package/dist/{monitor-CyD0CQxm.js → monitor-Ccev4ogO.js} +5 -3
  9. package/dist/{monitor.account-ByxawtBB.js → monitor.account-DYaT0fVd.js} +204 -88
  10. package/dist/{monitor.state-t4Ae5VIa.js → monitor.state-r4OLFBfg.js} +2 -2
  11. package/dist/{probe-956I34qH.js → probe-BjKRV7em.js} +21 -9
  12. package/dist/runtime-api.js +1 -1
  13. package/dist/{send-Djou0Z5a.js → send-Cze2qlca.js} +4 -4
  14. package/dist/{send-result-DSTSkRDM.js → send-result-D9rgEUlm.js} +1 -1
  15. package/dist/setup-api.js +1 -1
  16. package/dist/{subagent-hooks-fuyBHOVu.js → subagent-hooks-Bw3Dg2mS.js} +2 -2
  17. package/dist/subagent-hooks-api.js +2 -7
  18. package/node_modules/es-object-atoms/CHANGELOG.md +21 -14
  19. package/node_modules/es-object-atoms/isObject.d.ts +1 -1
  20. package/node_modules/es-object-atoms/package.json +6 -7
  21. package/node_modules/es-object-atoms/tsconfig.json +1 -0
  22. package/node_modules/hasown/CHANGELOG.md +7 -0
  23. package/node_modules/hasown/index.d.ts +0 -1
  24. package/node_modules/hasown/package.json +4 -5
  25. package/node_modules/typebox/build/type/script/mapping.d.mts +5 -2
  26. package/node_modules/typebox/build/type/script/mapping.mjs +15 -8
  27. package/node_modules/typebox/build/type/script/parser.d.mts +3 -1
  28. package/node_modules/typebox/build/type/script/parser.mjs +2 -1
  29. package/node_modules/typebox/package.json +29 -29
  30. package/npm-shrinkwrap.json +13 -13
  31. package/openclaw.plugin.json +124 -0
  32. package/package.json +5 -5
  33. package/dist/{thread-bindings-D24m3Cjy.js → thread-bindings-C58Uq5Y1.js} +1 -1
@@ -1,26 +1,26 @@
1
- import { t as buildFeishuConversationId } from "./conversation-id-DuL575sn.js";
1
+ import { _ as buildFeishuCommentTarget, f as isRecord$1, h as readString, l as encodeQuery, m as parseCommentContentElements, p as normalizeString, s as resolveFeishuRuntimeAccount, u as extractReplyText, v as normalizeCommentFileType } from "./accounts-Bpe6CjpS.js";
2
2
  import { i as resolveReceiveIdType } from "./targets-BUjQ1TcA.js";
3
- import { t as createFeishuThreadBindingManager } from "./thread-bindings-D24m3Cjy.js";
4
- import { _ as buildFeishuCommentTarget, f as isRecord$1, h as readString, l as encodeQuery, m as parseCommentContentElements, p as normalizeString, s as resolveFeishuRuntimeAccount, u as extractReplyText, v as normalizeCommentFileType } from "./accounts-CXnY5H8g.js";
5
- import { c as normalizeFeishuAllowEntry, d as resolveFeishuGroupConversationIngressAccess, f as resolveFeishuGroupSenderActivationIngressAccess, l as resolveFeishuDmIngressAccess, p as resolveFeishuReplyPolicy, s as hasExplicitFeishuGroupConfig, u as resolveFeishuGroupConfig } from "./channel-BfeesQob.js";
6
- import { c as decodeFeishuCardAction, o as buildFeishuCardActionTextFallback, s as createFeishuCardInteractionEnvelope } from "./send-result-DSTSkRDM.js";
3
+ import { c as normalizeFeishuAllowEntry, d as resolveFeishuGroupConversationIngressAccess, f as resolveFeishuGroupSenderActivationIngressAccess, l as resolveFeishuDmIngressAccess, p as resolveFeishuReplyPolicy, s as hasExplicitFeishuGroupConfig, u as resolveFeishuGroupConfig } from "./channel-TXwK9jIG.js";
4
+ import { c as decodeFeishuCardAction, o as buildFeishuCardActionTextFallback, s as createFeishuCardInteractionEnvelope } from "./send-result-D9rgEUlm.js";
5
+ import { t as buildFeishuConversationId } from "./conversation-id-DuL575sn.js";
7
6
  import { t as getFeishuRuntime } from "./runtime-C5JxBWZp.js";
8
7
  import { a as getFeishuUserAgent, i as createFeishuWSClient, n as createEventDispatcher, r as createFeishuClient } from "./client-BhMNZBJD.js";
9
- import { c as getChatInfo, i as createCommentTypingReactionLifecycle, t as deliverCommentThreadText } from "./drive-USxqssMG.js";
8
+ import { c as getChatInfo, i as createCommentTypingReactionLifecycle, t as deliverCommentThreadText } from "./drive-BIrffRwc.js";
9
+ import { t as createFeishuThreadBindingManager } from "./thread-bindings-C58Uq5Y1.js";
10
10
  import { createReplyPrefixContext, evaluateSupplementalContextVisibility, loadSessionStore, normalizeAgentId as normalizeAgentId$2, resolveChannelContextVisibilityMode, resolveSessionStoreEntry } from "./runtime-api.js";
11
- import { _ as normalizeFeishuExternalKey, a as sendCardFeishu, c as sendStructuredCardFeishu, d as isFeishuBroadcastMention, f as isMentionForwardRequest, g as shouldSuppressFeishuTextForVoiceMedia, h as sendMediaFeishu, i as resolveFeishuCardTemplate, l as parsePostContent, m as saveMessageResourceFeishu, n as getMessageFeishu, p as isFeishuGroupChatType, r as listFeishuThreadMessages, s as sendMessageFeishu, u as extractMentionTargets } from "./send-Djou0Z5a.js";
12
- import { i as waitForAbortableDelay, r as raceWithTimeoutAndAbort } from "./probe-956I34qH.js";
13
- import { a as feishuWebhookRateLimiter, c as wsClients, i as botOpenIds, l as fetchBotIdentityForMonitor, n as FEISHU_WEBHOOK_MAX_BODY_BYTES, o as httpServers, r as botNames, s as recordWebhookStatus, t as FEISHU_WEBHOOK_BODY_TIMEOUT_MS } from "./monitor.state-t4Ae5VIa.js";
14
- import { asBoolean, asNullableRecord, isRecord, normalizeLowercaseStringOrEmpty, normalizeOptionalLowercaseString, normalizeOptionalString, readStringValue, uniqueStrings } from "openclaw/plugin-sdk/string-coerce-runtime";
15
- import { ensureConfiguredBindingRouteReady, resolveConfiguredBindingRoute, resolveRuntimeConversationBindingRoute } from "openclaw/plugin-sdk/conversation-runtime";
16
- import { resolveInboundLastRouteSessionKey } from "openclaw/plugin-sdk/routing";
11
+ import { _ as normalizeFeishuExternalKey, a as sendCardFeishu, c as sendStructuredCardFeishu, d as isFeishuBroadcastMention, f as isMentionForwardRequest, g as shouldSuppressFeishuTextForVoiceMedia, h as sendMediaFeishu, i as resolveFeishuCardTemplate, l as parsePostContent, m as saveMessageResourceFeishu, n as getMessageFeishu, p as isFeishuGroupChatType, r as listFeishuThreadMessages, s as sendMessageFeishu, u as extractMentionTargets } from "./send-Cze2qlca.js";
12
+ import { i as waitForAbortableDelay, r as raceWithTimeoutAndAbort } from "./probe-BjKRV7em.js";
13
+ import { a as feishuWebhookRateLimiter, c as wsClients, i as botOpenIds, l as fetchBotIdentityForMonitor, n as FEISHU_WEBHOOK_MAX_BODY_BYTES, o as httpServers, r as botNames, s as recordWebhookStatus, t as FEISHU_WEBHOOK_BODY_TIMEOUT_MS } from "./monitor.state-r4OLFBfg.js";
17
14
  import { createChannelMessageReplyPipeline, formatChannelProgressDraftLineForEntry, isChannelProgressDraftWorkToolName, resolveAgentOutboundIdentity } from "openclaw/plugin-sdk/channel-outbound";
18
15
  import { createChannelPairingController, createChannelPairingController as createChannelPairingController$1 } from "openclaw/plugin-sdk/channel-pairing";
19
- import { parseStrictNonNegativeInteger, resolveExpiresAtMsFromDurationSeconds } from "openclaw/plugin-sdk/number-runtime";
16
+ import { ensureConfiguredBindingRouteReady, resolveConfiguredBindingRoute, resolveRuntimeConversationBindingRoute } from "openclaw/plugin-sdk/conversation-runtime";
17
+ import { asDateTimestampMs, isFutureDateTimestampMs, parseStrictNonNegativeInteger, resolveDateTimestampMs, resolveExpiresAtMsFromDurationMs, resolveExpiresAtMsFromDurationSeconds, resolveTimerTimeoutMs } from "openclaw/plugin-sdk/number-runtime";
18
+ import { asBoolean, asNullableRecord, isRecord, normalizeLowercaseStringOrEmpty, normalizeOptionalLowercaseString, normalizeOptionalString, readStringValue, uniqueStrings } from "openclaw/plugin-sdk/string-coerce-runtime";
20
19
  import { stripReasoningTagsFromText } from "openclaw/plugin-sdk/text-chunking";
21
20
  import fs from "node:fs";
22
21
  import os from "node:os";
23
22
  import path from "node:path";
23
+ import { resolveInboundLastRouteSessionKey } from "openclaw/plugin-sdk/routing";
24
24
  import { formatErrorMessage } from "openclaw/plugin-sdk/error-runtime";
25
25
  import * as Lark from "@larksuiteoapi/node-sdk";
26
26
  import * as crypto$1 from "node:crypto";
@@ -374,8 +374,10 @@ async function resolveFeishuSenderName(params) {
374
374
  const normalizedSenderId = senderId.trim();
375
375
  if (!normalizedSenderId) return {};
376
376
  const cached = senderNameCache.get(normalizedSenderId);
377
- const now = Date.now();
378
- if (cached && cached.expireAt > now) return { name: cached.name };
377
+ const now = asDateTimestampMs(Date.now());
378
+ const cachedExpireAt = cached ? asDateTimestampMs(cached.expireAt) : void 0;
379
+ if (cached && now !== void 0 && cachedExpireAt !== void 0 && cachedExpireAt > now) return { name: cached.name };
380
+ if (cached) senderNameCache.delete(normalizedSenderId);
379
381
  try {
380
382
  const client = createFeishuClient(account);
381
383
  const userIdType = resolveSenderLookupIdType(normalizedSenderId);
@@ -385,9 +387,10 @@ async function resolveFeishuSenderName(params) {
385
387
  })).data?.user;
386
388
  const name = user?.name ?? user?.nickname ?? user?.en_name;
387
389
  if (name) {
388
- senderNameCache.set(normalizedSenderId, {
390
+ const expireAt = resolveExpiresAtMsFromDurationMs(SENDER_NAME_TTL_MS);
391
+ if (expireAt !== void 0) senderNameCache.set(normalizedSenderId, {
389
392
  name,
390
- expireAt: now + SENDER_NAME_TTL_MS
393
+ expireAt
391
394
  });
392
395
  return { name };
393
396
  }
@@ -825,9 +828,10 @@ const STREAMING_UPDATE_THROTTLE_MS = 160;
825
828
  const STREAMING_SIGNIFICANT_DELTA_CHARS = 18;
826
829
  const FEISHU_STREAMING_TOKEN_DEFAULT_LIFETIME_SECONDS = 7200;
827
830
  const tokenCache = /* @__PURE__ */ new Map();
828
- function resolveStreamingTokenExpiresAt(value) {
829
- if (typeof value === "number" && Number.isFinite(value) && value <= 0) return Date.now();
830
- return resolveExpiresAtMsFromDurationSeconds(value) ?? Date.now() + FEISHU_STREAMING_TOKEN_DEFAULT_LIFETIME_SECONDS * 1e3;
831
+ function resolveStreamingTokenExpiresAt(value, nowMs = Date.now()) {
832
+ const now = resolveDateTimestampMs(nowMs);
833
+ if (typeof value === "number" && Number.isFinite(value) && value <= 0) return now;
834
+ return resolveExpiresAtMsFromDurationSeconds(value, { nowMs: now }) ?? resolveExpiresAtMsFromDurationSeconds(FEISHU_STREAMING_TOKEN_DEFAULT_LIFETIME_SECONDS, { nowMs: now }) ?? now;
831
835
  }
832
836
  function resolveApiBase(domain) {
833
837
  if (domain === "lark") return "https://open.larksuite.com/open-apis";
@@ -846,7 +850,11 @@ function resolveAllowedHostnames(domain) {
846
850
  async function getToken(creds) {
847
851
  const key = `${creds.domain ?? "feishu"}|${creds.appId}`;
848
852
  const cached = tokenCache.get(key);
849
- if (cached && cached.expiresAt > Date.now() + 6e4) return cached.token;
853
+ const rawNow = Date.now();
854
+ const hasValidClock = asDateTimestampMs(rawNow) !== void 0;
855
+ const now = resolveDateTimestampMs(rawNow);
856
+ const minUsableExpiresAt = resolveExpiresAtMsFromDurationSeconds(60, { nowMs: now }) ?? now;
857
+ if (cached && hasValidClock && cached.expiresAt > minUsableExpiresAt) return cached.token;
850
858
  const { response, release } = await fetchWithSsrFGuard({
851
859
  url: `${resolveApiBase(creds.domain)}/auth/v3/tenant_access_token/internal`,
852
860
  init: {
@@ -872,7 +880,7 @@ async function getToken(creds) {
872
880
  if (data.code !== 0 || !data.tenant_access_token) throw new Error(`Token error: ${data.msg}`);
873
881
  tokenCache.set(key, {
874
882
  token: data.tenant_access_token,
875
- expiresAt: resolveStreamingTokenExpiresAt(data.expire)
883
+ expiresAt: resolveStreamingTokenExpiresAt(data.expire, now)
876
884
  });
877
885
  return data.tenant_access_token;
878
886
  }
@@ -1024,7 +1032,7 @@ var FeishuStreamingSession = class {
1024
1032
  sequence: 1,
1025
1033
  currentText: "",
1026
1034
  sentText: "",
1027
- hasNote: !!options?.note
1035
+ hasNote: Boolean(options?.note)
1028
1036
  };
1029
1037
  this.log?.(`Started streaming: cardId=${cardId}, messageId=${sendRes.data.message_id}`);
1030
1038
  }
@@ -1175,7 +1183,7 @@ var FeishuStreamingSession = class {
1175
1183
  const pendingMerged = mergeStreamingText(this.state.currentText, this.pendingText ?? void 0);
1176
1184
  const text = finalText ?? pendingMerged;
1177
1185
  const apiBase = resolveApiBase(this.creds.domain);
1178
- if (text && text !== this.state.sentText) {
1186
+ if ((text || finalText !== void 0) && text !== this.state.sentText) {
1179
1187
  const sent = text.startsWith(this.state.sentText) ? await this.updateCardContent(resolveStreamingCardAppendContent(this.state.sentText, text), (e) => this.log?.(`Final update failed: ${String(e)}`)) : await this.replaceCardContent(text, (e) => this.log?.(`Final replace failed: ${String(e)}`));
1180
1188
  this.state.currentText = text;
1181
1189
  if (sent) this.state.sentText = text;
@@ -1210,6 +1218,24 @@ var FeishuStreamingSession = class {
1210
1218
  this.pendingText = null;
1211
1219
  this.log?.(`Closed streaming: cardId=${finalState.cardId}`);
1212
1220
  }
1221
+ async discard() {
1222
+ if (!this.state || this.closed) return;
1223
+ this.closed = true;
1224
+ this.clearFlushTimer();
1225
+ await this.queue;
1226
+ const currentState = this.state;
1227
+ try {
1228
+ const response = await this.client.im.message.delete({ path: { message_id: currentState.messageId } });
1229
+ if (response.code !== void 0 && response.code !== 0) throw new Error(`Delete streaming card message failed: ${response.msg ?? response.code}`);
1230
+ this.state = null;
1231
+ this.pendingText = null;
1232
+ this.log?.(`Discarded streaming card: cardId=${currentState.cardId}`);
1233
+ } catch (error) {
1234
+ this.log?.(`Discard failed: ${String(error)}`);
1235
+ this.closed = false;
1236
+ await this.close("");
1237
+ }
1238
+ }
1213
1239
  isActive() {
1214
1240
  return this.state !== null && !this.closed;
1215
1241
  }
@@ -1554,6 +1580,17 @@ function createFeishuReplyDispatcher(params) {
1554
1580
  }
1555
1581
  })();
1556
1582
  };
1583
+ const resetStreamingState = () => {
1584
+ streaming = null;
1585
+ streamingStartPromise = null;
1586
+ partialUpdateQueue = Promise.resolve();
1587
+ streamText = "";
1588
+ lastPartial = "";
1589
+ reasoningText = "";
1590
+ statusLine = "";
1591
+ snapshotBaseText = "";
1592
+ lastSnapshotTextLength = 0;
1593
+ };
1557
1594
  const closeStreaming = async (options) => {
1558
1595
  try {
1559
1596
  if (streamingStartPromise) await streamingStartPromise;
@@ -1569,20 +1606,21 @@ function createFeishuReplyDispatcher(params) {
1569
1606
  }
1570
1607
  }
1571
1608
  } finally {
1572
- streaming = null;
1573
- streamingStartPromise = null;
1574
- partialUpdateQueue = Promise.resolve();
1575
- streamText = "";
1576
- lastPartial = "";
1577
- reasoningText = "";
1578
- statusLine = "";
1579
- snapshotBaseText = "";
1580
- lastSnapshotTextLength = 0;
1609
+ resetStreamingState();
1581
1610
  }
1582
1611
  };
1583
- const updateStreamingStatusLine = (nextStatusLine) => {
1612
+ const discardStreamingPreview = async () => {
1613
+ try {
1614
+ if (streamingStartPromise) await streamingStartPromise;
1615
+ await partialUpdateQueue;
1616
+ if (streaming?.isActive()) await streaming.discard();
1617
+ } finally {
1618
+ resetStreamingState();
1619
+ }
1620
+ };
1621
+ const updateStreamingStatusLine = (nextStatusLine, options) => {
1584
1622
  statusLine = nextStatusLine;
1585
- if (!streaming?.isActive() && !streamingStartPromise && renderMode !== "card") return;
1623
+ if (!Boolean(streaming?.isActive() || streamingStartPromise) && (options?.startIfNeeded === false || renderMode !== "card")) return;
1586
1624
  startStreaming();
1587
1625
  flushStreamingCardUpdate(buildCombinedStreamText(reasoningText, streamText));
1588
1626
  };
@@ -1676,11 +1714,14 @@ function createFeishuReplyDispatcher(params) {
1676
1714
  mediaUrl,
1677
1715
  ...payload.audioAsVoice === true ? { audioAsVoice: true } : {}
1678
1716
  }));
1679
- const useCard = hasText && (renderMode === "card" || info?.kind === "block" && coreBlockStreamingEnabled && renderMode !== "raw" || renderMode === "auto" && shouldUseCard(text));
1717
+ const streamingCardEnabledForReplyKind = streamingEnabled && info?.kind === "final";
1718
+ const useCard = hasText && (streamingCardEnabledForReplyKind || renderMode === "card" || info?.kind === "block" && coreBlockStreamingEnabled && renderMode !== "raw" || renderMode === "auto" && shouldUseCard(text));
1680
1719
  const skipTextForDuplicateFinal = info?.kind === "final" && hasText && deliveredFinalTexts.has(text);
1681
1720
  const skipTextForClosedStreamingFinal = info?.kind === "final" && hasText && streamingClosedForReply && !streamingCloseErroredForReply && streamingEnabled && useCard;
1682
1721
  const shouldDeliverText = hasText && !hasVoiceMedia && !skipTextForDuplicateFinal && !skipTextForClosedStreamingFinal;
1722
+ const shouldDiscardStreamingPreview = info?.kind === "final" && hasMedia && (hasVoiceMedia && !shouldDeliverText || skipTextForDuplicateFinal);
1683
1723
  if (!shouldDeliverText && !hasMedia) return;
1724
+ if (shouldDiscardStreamingPreview) await discardStreamingPreview();
1684
1725
  if (shouldDeliverText) {
1685
1726
  if (info?.kind === "block") {
1686
1727
  if (!(streamingEnabled && useCard)) return;
@@ -1691,7 +1732,8 @@ function createFeishuReplyDispatcher(params) {
1691
1732
  startStreaming();
1692
1733
  if (streamingStartPromise) await streamingStartPromise;
1693
1734
  }
1694
- if (streaming?.isActive()) {
1735
+ const shouldStreamText = info?.kind === "block" || info?.kind === "final";
1736
+ if (streaming?.isActive() && shouldStreamText) {
1695
1737
  if (info?.kind === "block") queueStreamingUpdate(text, {
1696
1738
  mode: "delta",
1697
1739
  dedupeWithLastPartial: true
@@ -1773,6 +1815,7 @@ function createFeishuReplyDispatcher(params) {
1773
1815
  trim: "both"
1774
1816
  });
1775
1817
  if (!cleaned) return;
1818
+ startStreaming();
1776
1819
  queueStreamingUpdate(cleaned, {
1777
1820
  dedupeWithLastPartial: true,
1778
1821
  mode: "snapshot"
@@ -1795,7 +1838,7 @@ function createFeishuReplyDispatcher(params) {
1795
1838
  if (statusLine) updateStreamingStatusLine(statusLine);
1796
1839
  } : void 0,
1797
1840
  onAssistantMessageStart: streamingEnabled ? () => {
1798
- updateStreamingStatusLine("");
1841
+ updateStreamingStatusLine("", { startIfNeeded: false });
1799
1842
  } : void 0,
1800
1843
  onCompactionStart: streamingEnabled ? () => {
1801
1844
  updateStreamingStatusLine("📦 **Compacting context...**");
@@ -1822,8 +1865,15 @@ function isFeishuTopicSessionScope(scope) {
1822
1865
  return scope === "group_topic" || scope === "group_topic_sender";
1823
1866
  }
1824
1867
  function evictGroupNameCache() {
1825
- const now = Date.now();
1826
- for (const [key, val] of groupNameCache) if (val.expiresAt <= now) groupNameCache.delete(key);
1868
+ const now = asDateTimestampMs(Date.now());
1869
+ if (now === void 0) {
1870
+ groupNameCache.clear();
1871
+ return;
1872
+ }
1873
+ for (const [key, val] of groupNameCache) {
1874
+ const expiresAt = asDateTimestampMs(val.expiresAt);
1875
+ if (expiresAt === void 0 || expiresAt <= now) groupNameCache.delete(key);
1876
+ }
1827
1877
  if (groupNameCache.size > GROUP_NAME_CACHE_MAX_SIZE) {
1828
1878
  const excess = groupNameCache.size - GROUP_NAME_CACHE_MAX_SIZE;
1829
1879
  let removed = 0;
@@ -1834,36 +1884,38 @@ function evictGroupNameCache() {
1834
1884
  }
1835
1885
  }
1836
1886
  }
1837
- function setCacheEntry(key, value) {
1887
+ function setCacheEntry(key, name) {
1888
+ const expiresAt = resolveExpiresAtMsFromDurationMs(GROUP_NAME_CACHE_TTL_MS);
1838
1889
  groupNameCache.delete(key);
1839
- groupNameCache.set(key, value);
1890
+ if (expiresAt !== void 0) groupNameCache.set(key, {
1891
+ name,
1892
+ expiresAt
1893
+ });
1840
1894
  }
1841
1895
  async function resolveGroupName(params) {
1842
1896
  const { account, chatId, log } = params;
1843
1897
  if (!account.configured) return;
1844
1898
  const cacheKey = `${account.accountId}:${chatId}`;
1845
1899
  const cached = groupNameCache.get(cacheKey);
1846
- if (cached && cached.expiresAt > Date.now()) return cached.name || void 0;
1900
+ if (cached) {
1901
+ const now = asDateTimestampMs(Date.now());
1902
+ const expiresAt = asDateTimestampMs(cached.expiresAt);
1903
+ if (now !== void 0 && expiresAt !== void 0 && expiresAt > now) return cached.name || void 0;
1904
+ groupNameCache.delete(cacheKey);
1905
+ }
1906
+ let resolvedName;
1847
1907
  try {
1848
1908
  const name = (await getChatInfo(createFeishuClient(account), chatId))?.name?.trim();
1849
- if (name) setCacheEntry(cacheKey, {
1850
- name,
1851
- expiresAt: Date.now() + GROUP_NAME_CACHE_TTL_MS
1852
- });
1853
- else setCacheEntry(cacheKey, {
1854
- name: "",
1855
- expiresAt: Date.now() + GROUP_NAME_CACHE_TTL_MS
1856
- });
1909
+ if (name) {
1910
+ setCacheEntry(cacheKey, name);
1911
+ resolvedName = name;
1912
+ } else setCacheEntry(cacheKey, "");
1857
1913
  } catch (err) {
1858
1914
  log(`feishu[${account.accountId}]: getChatInfo failed for ${chatId}: ${String(err)}`);
1859
- setCacheEntry(cacheKey, {
1860
- name: "",
1861
- expiresAt: Date.now() + GROUP_NAME_CACHE_TTL_MS
1862
- });
1915
+ setCacheEntry(cacheKey, "");
1863
1916
  }
1864
- const result = groupNameCache.get(cacheKey)?.name || void 0;
1865
1917
  evictGroupNameCache();
1866
- return result;
1918
+ return resolvedName;
1867
1919
  }
1868
1920
  async function resolveFeishuAudioPreflightTranscript(params) {
1869
1921
  if (params.content.trim() !== "<media:audio>") return;
@@ -1992,7 +2044,7 @@ async function filterFetchedGroupContextMessages(messages, params) {
1992
2044
  }) ? message : void 0))).filter((message) => message !== void 0);
1993
2045
  }
1994
2046
  async function handleFeishuMessage(params) {
1995
- const { cfg, event, botOpenId, botName, runtime, chatHistories, accountId, processingClaimHeld = false } = params;
2047
+ const { cfg, event, botOpenId, botName, runtime, channelRuntime, chatHistories, accountId, processingClaimHeld = false } = params;
1996
2048
  const account = resolveFeishuRuntimeAccount({
1997
2049
  cfg,
1998
2050
  accountId
@@ -2184,7 +2236,7 @@ async function handleFeishuMessage(params) {
2184
2236
  }
2185
2237
  }
2186
2238
  try {
2187
- const core = getFeishuRuntime();
2239
+ const core = { channel: channelRuntime ?? getFeishuRuntime().channel };
2188
2240
  const pairing = createChannelPairingController({
2189
2241
  core,
2190
2242
  channel: "feishu",
@@ -2510,7 +2562,7 @@ async function handleFeishuMessage(params) {
2510
2562
  return threadContext;
2511
2563
  }
2512
2564
  const rootMsg = await getRootMessageInfo();
2513
- let feishuThreadId = ctx.threadId ?? rootMessageThreadId ?? rootMsg?.threadId;
2565
+ const feishuThreadId = ctx.threadId ?? rootMessageThreadId ?? rootMsg?.threadId;
2514
2566
  if (feishuThreadId) log(`feishu[${account.accountId}]: resolved thread ID: ${feishuThreadId}`);
2515
2567
  if (!feishuThreadId) {
2516
2568
  log(`feishu[${account.accountId}]: no threadId found for root message ${ctx.rootId ?? "none"}, skipping thread history`);
@@ -2975,7 +3027,15 @@ var FeishuRetryableCardActionError = class extends Error {
2975
3027
  }
2976
3028
  };
2977
3029
  function pruneProcessedCardActionTokens(now) {
2978
- for (const [key, entry] of processedCardActionTokens.entries()) if (entry.expiresAt <= now) processedCardActionTokens.delete(key);
3030
+ const validNow = asDateTimestampMs(now);
3031
+ if (validNow === void 0) {
3032
+ processedCardActionTokens.clear();
3033
+ return;
3034
+ }
3035
+ for (const [key, entry] of processedCardActionTokens.entries()) if (!isFutureDateTimestampMs(entry.expiresAt, { nowMs: validNow })) processedCardActionTokens.delete(key);
3036
+ }
3037
+ function resolveProcessedCardActionTokenExpiresAt(now) {
3038
+ return resolveExpiresAtMsFromDurationMs(FEISHU_CARD_ACTION_TOKEN_TTL_MS, { nowMs: now });
2979
3039
  }
2980
3040
  function beginFeishuCardActionToken(params) {
2981
3041
  const now = params.now ?? Date.now();
@@ -2984,10 +3044,12 @@ function beginFeishuCardActionToken(params) {
2984
3044
  if (!normalizedToken) return false;
2985
3045
  const key = `${params.accountId}:${normalizedToken}`;
2986
3046
  const existing = processedCardActionTokens.get(key);
2987
- if (existing && existing.expiresAt > now) return false;
2988
- processedCardActionTokens.set(key, {
3047
+ if (existing && isFutureDateTimestampMs(existing.expiresAt, { nowMs: now })) return false;
3048
+ processedCardActionTokens.delete(key);
3049
+ const expiresAt = resolveProcessedCardActionTokenExpiresAt(now);
3050
+ if (expiresAt !== void 0) processedCardActionTokens.set(key, {
2989
3051
  status: "inflight",
2990
- expiresAt: now + FEISHU_CARD_ACTION_TOKEN_TTL_MS
3052
+ expiresAt
2991
3053
  });
2992
3054
  return true;
2993
3055
  }
@@ -2995,9 +3057,15 @@ function completeFeishuCardActionToken(params) {
2995
3057
  const now = params.now ?? Date.now();
2996
3058
  const normalizedToken = params.token.trim();
2997
3059
  if (!normalizedToken) return;
2998
- processedCardActionTokens.set(`${params.accountId}:${normalizedToken}`, {
3060
+ const key = `${params.accountId}:${normalizedToken}`;
3061
+ const expiresAt = resolveProcessedCardActionTokenExpiresAt(now);
3062
+ if (expiresAt === void 0) {
3063
+ processedCardActionTokens.delete(key);
3064
+ return;
3065
+ }
3066
+ processedCardActionTokens.set(key, {
2999
3067
  status: "completed",
3000
- expiresAt: now + FEISHU_CARD_ACTION_TOKEN_TTL_MS
3068
+ expiresAt
3001
3069
  });
3002
3070
  }
3003
3071
  function releaseFeishuCardActionToken(params) {
@@ -3041,6 +3109,7 @@ async function dispatchSyntheticCommand(params) {
3041
3109
  event: buildSyntheticMessageEvent(params.event, params.command, resolvedChatType),
3042
3110
  botOpenId: params.botOpenId,
3043
3111
  runtime: params.runtime,
3112
+ channelRuntime: params.channelRuntime,
3044
3113
  accountId: params.accountId
3045
3114
  });
3046
3115
  }
@@ -3052,7 +3121,15 @@ const resolvedChatTypeCache = /* @__PURE__ */ new Map();
3052
3121
  const CHAT_TYPE_CACHE_TTL_MS = 30 * 6e4;
3053
3122
  const CHAT_TYPE_CACHE_MAX_SIZE = 5e3;
3054
3123
  function pruneChatTypeCache(now) {
3055
- for (const [key, entry] of resolvedChatTypeCache.entries()) if (entry.expiresAt <= now) resolvedChatTypeCache.delete(key);
3124
+ const validNow = asDateTimestampMs(now);
3125
+ if (validNow === void 0) {
3126
+ resolvedChatTypeCache.clear();
3127
+ return;
3128
+ }
3129
+ for (const [key, entry] of resolvedChatTypeCache.entries()) {
3130
+ const expiresAt = asDateTimestampMs(entry.expiresAt);
3131
+ if (expiresAt === void 0 || expiresAt <= validNow) resolvedChatTypeCache.delete(key);
3132
+ }
3056
3133
  if (resolvedChatTypeCache.size > CHAT_TYPE_CACHE_MAX_SIZE) {
3057
3134
  const excess = resolvedChatTypeCache.size - CHAT_TYPE_CACHE_MAX_SIZE;
3058
3135
  const iter = resolvedChatTypeCache.keys();
@@ -3065,6 +3142,18 @@ function pruneChatTypeCache(now) {
3065
3142
  function sanitizeLogValue(v) {
3066
3143
  return v.replace(/[\r\n]/g, " ").slice(0, 500);
3067
3144
  }
3145
+ function resolveFeishuApprovalCardExpiresAt(nowRaw = Date.now()) {
3146
+ const now = asDateTimestampMs(nowRaw);
3147
+ return now === void 0 ? void 0 : resolveExpiresAtMsFromDurationMs(FEISHU_APPROVAL_CARD_TTL_MS, { nowMs: now });
3148
+ }
3149
+ function cacheResolvedCardActionChatType(cacheKey, value, now) {
3150
+ const expiresAt = resolveExpiresAtMsFromDurationMs(CHAT_TYPE_CACHE_TTL_MS, { nowMs: now });
3151
+ resolvedChatTypeCache.delete(cacheKey);
3152
+ if (expiresAt !== void 0) resolvedChatTypeCache.set(cacheKey, {
3153
+ value,
3154
+ expiresAt
3155
+ });
3156
+ }
3068
3157
  async function resolveCardActionChatType(params) {
3069
3158
  const explicitChatType = normalizeResolvedCardActionChatType(params.chatType);
3070
3159
  if (explicitChatType) return explicitChatType;
@@ -3074,16 +3163,15 @@ async function resolveCardActionChatType(params) {
3074
3163
  const now = Date.now();
3075
3164
  pruneChatTypeCache(now);
3076
3165
  const cached = resolvedChatTypeCache.get(cacheKey);
3077
- if (cached) return cached.value;
3166
+ const cachedExpiresAt = cached ? asDateTimestampMs(cached.expiresAt) : void 0;
3167
+ if (cached && cachedExpiresAt !== void 0) return cached.value;
3168
+ if (cached) resolvedChatTypeCache.delete(cacheKey);
3078
3169
  try {
3079
3170
  const response = await createFeishuClient(params.account).im.chat.get({ path: { chat_id: chatId } });
3080
3171
  if (response.code === 0) {
3081
3172
  const resolvedChatType = normalizeResolvedCardActionChatType(response.data?.chat_mode) ?? normalizeResolvedCardActionChatType(response.data?.chat_type);
3082
3173
  if (resolvedChatType) {
3083
- resolvedChatTypeCache.set(cacheKey, {
3084
- value: resolvedChatType,
3085
- expiresAt: now + CHAT_TYPE_CACHE_TTL_MS
3086
- });
3174
+ cacheResolvedCardActionChatType(cacheKey, resolvedChatType, now);
3087
3175
  return resolvedChatType;
3088
3176
  }
3089
3177
  params.log(`feishu[${params.account.accountId}]: card action missing chat type for chat; defaulting to p2p`);
@@ -3156,6 +3244,20 @@ async function handleFeishuCardAction(params) {
3156
3244
  return;
3157
3245
  }
3158
3246
  const prompt = typeof envelope.m?.prompt === "string" && envelope.m.prompt.trim() ? envelope.m.prompt : `Run \`${command}\` in this Feishu conversation?`;
3247
+ const expiresAt = resolveFeishuApprovalCardExpiresAt();
3248
+ if (expiresAt === void 0) {
3249
+ await sendInvalidInteractionNotice({
3250
+ cfg,
3251
+ event,
3252
+ reason: "malformed",
3253
+ accountId
3254
+ });
3255
+ completeFeishuCardActionToken({
3256
+ token: event.token,
3257
+ accountId: account.accountId
3258
+ });
3259
+ return;
3260
+ }
3159
3261
  await sendCardFeishu({
3160
3262
  cfg,
3161
3263
  to: resolveCallbackTarget(event),
@@ -3165,7 +3267,7 @@ async function handleFeishuCardAction(params) {
3165
3267
  command,
3166
3268
  prompt,
3167
3269
  sessionKey: envelope.c?.s,
3168
- expiresAt: Date.now() + FEISHU_APPROVAL_CARD_TTL_MS,
3270
+ expiresAt,
3169
3271
  chatType: await resolveCardActionChatType({
3170
3272
  event,
3171
3273
  account,
@@ -3217,6 +3319,7 @@ async function handleFeishuCardAction(params) {
3217
3319
  account,
3218
3320
  botOpenId: params.botOpenId,
3219
3321
  runtime,
3322
+ channelRuntime: params.channelRuntime,
3220
3323
  accountId,
3221
3324
  chatType: envelope.c?.t
3222
3325
  });
@@ -3247,6 +3350,7 @@ async function handleFeishuCardAction(params) {
3247
3350
  account,
3248
3351
  botOpenId: params.botOpenId,
3249
3352
  runtime,
3353
+ channelRuntime: params.channelRuntime,
3250
3354
  accountId
3251
3355
  });
3252
3356
  completeFeishuCardActionToken({
@@ -3381,7 +3485,12 @@ function createQuickActionLauncherCard(params) {
3381
3485
  }
3382
3486
  async function maybeHandleFeishuQuickActionMenu(params) {
3383
3487
  if (!isFeishuQuickActionMenuEventKey(params.eventKey)) return false;
3384
- const expiresAt = (params.now ?? Date.now()) + FEISHU_QUICK_ACTION_CARD_TTL_MS;
3488
+ const now = asDateTimestampMs(params.now ?? Date.now());
3489
+ const expiresAt = now === void 0 ? void 0 : resolveExpiresAtMsFromDurationMs(FEISHU_QUICK_ACTION_CARD_TTL_MS, { nowMs: now });
3490
+ if (expiresAt === void 0) {
3491
+ params.runtime?.log?.(`feishu[${params.accountId ?? "default"}]: failed to open quick-action launcher for ${params.operatorOpenId}: invalid expiry clock`);
3492
+ return false;
3493
+ }
3385
3494
  try {
3386
3495
  await sendCardFeishu({
3387
3496
  cfg: params.cfg,
@@ -3484,6 +3593,7 @@ function createFeishuBotMenuHandler(params) {
3484
3593
  botOpenId: getBotOpenId(accountId),
3485
3594
  botName: getBotName(accountId),
3486
3595
  runtime,
3596
+ channelRuntime: params.channelRuntime,
3487
3597
  chatHistories,
3488
3598
  accountId,
3489
3599
  processingClaimHeld: true
@@ -4094,7 +4204,7 @@ async function resolveDriveCommentEventCore(params) {
4094
4204
  return null;
4095
4205
  }
4096
4206
  const context = await fetchDriveCommentContext({
4097
- client: createClient ? createClient(account ?? { accountId }) : createFeishuClient((await import("./accounts-CXnY5H8g.js").then((n) => n.t)).resolveFeishuAccount({
4207
+ client: createClient ? createClient(account ?? { accountId }) : createFeishuClient((await import("./accounts-Bpe6CjpS.js").then((n) => n.t)).resolveFeishuAccount({
4098
4208
  cfg,
4099
4209
  accountId
4100
4210
  })),
@@ -4472,14 +4582,15 @@ function createSequentialQueue(options = {}) {
4472
4582
  }
4473
4583
  async function boundedRun(key, task, timeoutMs, onTaskTimeout) {
4474
4584
  if (!Number.isFinite(timeoutMs) || timeoutMs <= 0) return task();
4585
+ const resolvedTimeoutMs = resolveTimerTimeoutMs(timeoutMs, DEFAULT_TASK_TIMEOUT_MS);
4475
4586
  let timeoutHandle;
4476
4587
  const timeoutPromise = new Promise((resolve) => {
4477
4588
  timeoutHandle = setTimeout(() => {
4478
4589
  try {
4479
- onTaskTimeout?.(key, timeoutMs);
4590
+ onTaskTimeout?.(key, resolvedTimeoutMs);
4480
4591
  } catch {}
4481
4592
  resolve();
4482
- }, timeoutMs);
4593
+ }, resolvedTimeoutMs);
4483
4594
  });
4484
4595
  try {
4485
4596
  await Promise.race([task(), timeoutPromise]);
@@ -4611,8 +4722,8 @@ function resolveFeishuDebounceMentions(params) {
4611
4722
  const botMentions = merged.filter((mention) => mention.id.open_id?.trim() === normalizedBotOpenId);
4612
4723
  return botMentions.length > 0 ? botMentions : void 0;
4613
4724
  }
4614
- function createFeishuMessageReceiveHandler({ cfg, core, accountId, runtime, chatHistories, fireAndForget, handleMessage, resolveDebounceText: resolveText, hasProcessedMessage, recordProcessedMessage, getBotOpenId = () => void 0, getBotName = () => void 0, resolveSequentialKey = ({ accountId, event }) => `feishu:${accountId}:${event.message.chat_id?.trim() || "unknown"}` }) {
4615
- const inboundDebounceMs = core.channel.debounce.resolveInboundDebounceMs({
4725
+ function createFeishuMessageReceiveHandler({ cfg, channelRuntime, accountId, runtime, chatHistories, fireAndForget, handleMessage, resolveDebounceText: resolveText, hasProcessedMessage, recordProcessedMessage, getBotOpenId = () => void 0, getBotName = () => void 0, resolveSequentialKey = ({ accountId, event }) => `feishu:${accountId}:${event.message.chat_id?.trim() || "unknown"}` }) {
4726
+ const inboundDebounceMs = channelRuntime.debounce.resolveInboundDebounceMs({
4616
4727
  cfg,
4617
4728
  channel: "feishu"
4618
4729
  });
@@ -4634,6 +4745,7 @@ function createFeishuMessageReceiveHandler({ cfg, core, accountId, runtime, chat
4634
4745
  botOpenId: getBotOpenId(accountId),
4635
4746
  botName: getBotName(accountId),
4636
4747
  runtime,
4748
+ channelRuntime,
4637
4749
  chatHistories,
4638
4750
  accountId,
4639
4751
  processingClaimHeld: true
@@ -4659,7 +4771,7 @@ function createFeishuMessageReceiveHandler({ cfg, core, accountId, runtime, chat
4659
4771
  error(`feishu[${accountId}]: failed to record merged dedupe id ${messageId}: ${String(err)}`);
4660
4772
  }
4661
4773
  };
4662
- const inboundDebouncer = core.channel.debounce.createInboundDebouncer({
4774
+ const inboundDebouncer = channelRuntime.debounce.createInboundDebouncer({
4663
4775
  debounceMs: inboundDebounceMs,
4664
4776
  buildKey: (event) => {
4665
4777
  const chatId = event.message.chat_id?.trim();
@@ -4671,7 +4783,7 @@ function createFeishuMessageReceiveHandler({ cfg, core, accountId, runtime, chat
4671
4783
  shouldDebounce: (event) => {
4672
4784
  if (event.message.message_type !== "text") return false;
4673
4785
  const text = resolveDebounceText(event);
4674
- return Boolean(text) && !core.channel.commands.isControlCommandMessage(text, cfg);
4786
+ return Boolean(text) && !channelRuntime.commands.isControlCommandMessage(text, cfg);
4675
4787
  },
4676
4788
  onFlush: async (entries) => {
4677
4789
  const last = entries.at(-1);
@@ -4746,7 +4858,7 @@ const FEISHU_WS_LOG_ERROR_MAX_LENGTH = 500;
4746
4858
  const FEISHU_WS_RECONNECT_EXHAUSTED_RE = /^WebSocket reconnect exhausted after \d+ attempts?/;
4747
4859
  const FEISHU_WS_AUTORECONNECT_DISABLED_ERROR = "WebSocket connect failed and autoReconnect is disabled";
4748
4860
  function isFeishuWebhookPayload(value) {
4749
- return !!value && typeof value === "object" && !Array.isArray(value);
4861
+ return Boolean(value) && typeof value === "object" && !Array.isArray(value);
4750
4862
  }
4751
4863
  function buildFeishuWebhookEnvelope(req, payload) {
4752
4864
  return Object.assign(Object.create({ headers: req.headers }), payload);
@@ -4818,14 +4930,13 @@ function waitForFeishuWsCycleEnd(params) {
4818
4930
  if (params.abortSignal?.aborted) return Promise.resolve("abort");
4819
4931
  return new Promise((resolve) => {
4820
4932
  let settled = false;
4821
- let handleAbort;
4822
4933
  const finish = (result) => {
4823
4934
  if (settled) return;
4824
4935
  settled = true;
4825
4936
  if (handleAbort) params.abortSignal?.removeEventListener("abort", handleAbort);
4826
4937
  resolve(result);
4827
4938
  };
4828
- handleAbort = () => finish("abort");
4939
+ const handleAbort = () => finish("abort");
4829
4940
  params.abortSignal?.addEventListener("abort", handleAbort, { once: true });
4830
4941
  if (params.abortSignal?.aborted) {
4831
4942
  finish("abort");
@@ -5041,7 +5152,7 @@ async function resolveReactionSyntheticEvent(params) {
5041
5152
  const senderId = event.user_id?.open_id;
5042
5153
  const senderUserId = event.user_id?.user_id;
5043
5154
  if (!emoji || !messageId || !senderId) return null;
5044
- const { resolveFeishuAccount } = await import("./accounts-CXnY5H8g.js").then((n) => n.t);
5155
+ const { resolveFeishuAccount } = await import("./accounts-Bpe6CjpS.js").then((n) => n.t);
5045
5156
  const reactionNotifications = resolveFeishuAccount({
5046
5157
  cfg,
5047
5158
  accountId
@@ -5149,7 +5260,7 @@ function parseFeishuCardActionEventPayload(value) {
5149
5260
  };
5150
5261
  }
5151
5262
  function registerEventHandlers(eventDispatcher, context) {
5152
- const { cfg, accountId, runtime, chatHistories, fireAndForget } = context;
5263
+ const { cfg, accountId, channelRuntime, runtime, chatHistories, fireAndForget } = context;
5153
5264
  const log = runtime?.log ?? console.log;
5154
5265
  const error = runtime?.error ?? console.error;
5155
5266
  const runFeishuHandler = async (params) => {
@@ -5168,7 +5279,7 @@ function registerEventHandlers(eventDispatcher, context) {
5168
5279
  eventDispatcher.register({
5169
5280
  "im.message.receive_v1": createFeishuMessageReceiveHandler({
5170
5281
  cfg,
5171
- core: getFeishuRuntime(),
5282
+ channelRuntime,
5172
5283
  accountId,
5173
5284
  runtime,
5174
5285
  chatHistories,
@@ -5226,6 +5337,7 @@ function registerEventHandlers(eventDispatcher, context) {
5226
5337
  botOpenId: myBotId,
5227
5338
  botName: botNames.get(accountId),
5228
5339
  runtime,
5340
+ channelRuntime,
5229
5341
  chatHistories,
5230
5342
  accountId
5231
5343
  });
@@ -5253,6 +5365,7 @@ function registerEventHandlers(eventDispatcher, context) {
5253
5365
  botOpenId: myBotId,
5254
5366
  botName: botNames.get(accountId),
5255
5367
  runtime,
5368
+ channelRuntime,
5256
5369
  chatHistories,
5257
5370
  accountId
5258
5371
  });
@@ -5264,7 +5377,8 @@ function registerEventHandlers(eventDispatcher, context) {
5264
5377
  accountId,
5265
5378
  runtime,
5266
5379
  chatHistories,
5267
- fireAndForget
5380
+ fireAndForget,
5381
+ channelRuntime
5268
5382
  }),
5269
5383
  "card.action.trigger": async (data) => {
5270
5384
  try {
@@ -5278,6 +5392,7 @@ function registerEventHandlers(eventDispatcher, context) {
5278
5392
  event,
5279
5393
  botOpenId: botOpenIds.get(accountId),
5280
5394
  runtime,
5395
+ channelRuntime,
5281
5396
  accountId
5282
5397
  });
5283
5398
  if (fireAndForget) promise.catch((err) => {
@@ -5325,6 +5440,7 @@ async function monitorSingleAccount(params) {
5325
5440
  registerEventHandlers(eventDispatcher, {
5326
5441
  cfg,
5327
5442
  accountId,
5443
+ channelRuntime: params.channelRuntime ?? getFeishuRuntime().channel,
5328
5444
  runtime,
5329
5445
  chatHistories,
5330
5446
  fireAndForget: params.fireAndForget ?? true