@openclaw/feishu 2026.5.31-beta.3 → 2026.6.1-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.
package/dist/api.js CHANGED
@@ -1,5 +1,5 @@
1
1
  import { r as listEnabledFeishuAccounts } from "./accounts-Bpe6CjpS.js";
2
- import { a as setFeishuNamedAccountEnabled, i as feishuSetupAdapter, n as feishuSetupWizard, r as runFeishuLogin, t as feishuPlugin } from "./channel-YU5bdC_o.js";
2
+ import { a as setFeishuNamedAccountEnabled, i as feishuSetupAdapter, n as feishuSetupWizard, r as runFeishuLogin, t as feishuPlugin } from "./channel-B3ngoThy.js";
3
3
  import { a as parseFeishuTargetId, i as parseFeishuDirectConversationId, n as buildFeishuModelOverrideParentCandidates, r as parseFeishuConversationId, t as buildFeishuConversationId } from "./conversation-id-DuL575sn.js";
4
4
  import { t as getFeishuRuntime } from "./runtime-C5JxBWZp.js";
5
5
  import { r as createFeishuClient } from "./client-BhMNZBJD.js";
@@ -182,7 +182,9 @@ async function getAppOwnerOpenId(params) {
182
182
  }
183
183
  }
184
184
  function sleep(ms) {
185
- return new Promise((resolve) => setTimeout(resolve, ms));
185
+ return new Promise((resolve) => {
186
+ setTimeout(resolve, ms);
187
+ });
186
188
  }
187
189
  function sleepRegistrationPollInterval(intervalSeconds) {
188
190
  return sleep(finiteSecondsToTimerSafeMilliseconds(intervalSeconds) ?? finiteSecondsToTimerSafeMilliseconds(DEFAULT_REGISTRATION_POLL_INTERVAL_SECONDS) ?? REQUEST_TIMEOUT_MS);
@@ -574,7 +574,7 @@ function inspectSessionTranscript(params) {
574
574
  reason: "not a file"
575
575
  };
576
576
  if (stat.size > SESSION_FILE_INSPECTION_MAX_BYTES) return null;
577
- let raw = "";
577
+ let raw;
578
578
  try {
579
579
  raw = fs.readFileSync(params.transcriptPath, "utf-8");
580
580
  } catch {
@@ -1433,7 +1433,7 @@ function applyNewAppSecurityPolicy(cfg, accountId, openId, groupPolicy) {
1433
1433
  }
1434
1434
  let appRegistrationModulePromise = null;
1435
1435
  const loadAppRegistrationModule = async () => {
1436
- appRegistrationModulePromise ??= import("./app-registration-DCy5-X_C.js");
1436
+ appRegistrationModulePromise ??= import("./app-registration-tgQJUcv3.js");
1437
1437
  return await appRegistrationModulePromise;
1438
1438
  };
1439
1439
  async function promptFeishuDomain(params) {
@@ -1504,7 +1504,7 @@ async function runNewAppFlow(params) {
1504
1504
  const { prompter, options } = params;
1505
1505
  let next = params.cfg;
1506
1506
  const targetAccountId = resolveDefaultFeishuAccountId(next);
1507
- let appId = null;
1507
+ let appId;
1508
1508
  let appSecret = null;
1509
1509
  let appSecretProbeValue = null;
1510
1510
  let scanDomain;
@@ -1520,7 +1520,6 @@ async function runNewAppFlow(params) {
1520
1520
  if (scanResult) {
1521
1521
  appId = scanResult.appId;
1522
1522
  appSecret = scanResult.appSecret;
1523
- appSecretProbeValue = scanResult.appSecret;
1524
1523
  scanDomain = scanResult.domain;
1525
1524
  scanOpenId = scanResult.openId;
1526
1525
  } else {
@@ -1575,7 +1574,9 @@ async function runNewAppFlow(params) {
1575
1574
  initialValue: "allowlist"
1576
1575
  });
1577
1576
  const configProgress = prompter.progress(t("wizard.feishu.configuring"));
1578
- await new Promise((resolve) => setTimeout(resolve, 50));
1577
+ await new Promise((resolve) => {
1578
+ setTimeout(resolve, 50);
1579
+ });
1579
1580
  if (appId && appSecret) next = patchFeishuConfig(next, targetAccountId, {
1580
1581
  appId,
1581
1582
  appSecret,
@@ -1734,7 +1735,7 @@ const meta = {
1734
1735
  order: 70,
1735
1736
  preferSessionLookupForAnnounceTarget: true
1736
1737
  };
1737
- const loadFeishuChannelRuntime = createLazyRuntimeNamedExport(() => import("./channel.runtime-BBY1Yd01.js"), "feishuChannelRuntime");
1738
+ const loadFeishuChannelRuntime = createLazyRuntimeNamedExport(() => import("./channel.runtime-D_5rduJm.js"), "feishuChannelRuntime");
1738
1739
  function toFeishuMessageSendResult(result, kind) {
1739
1740
  const receipt = result.receipt ?? createFeishuSendReceipt({
1740
1741
  messageId: result.messageId,
@@ -2550,7 +2551,7 @@ const feishuPlugin = createChatChannelPlugin({
2550
2551
  })
2551
2552
  }),
2552
2553
  gateway: { startAccount: async (ctx) => {
2553
- const { monitorFeishuProvider } = await import("./monitor-CMwQEm1t.js");
2554
+ const { monitorFeishuProvider } = await import("./monitor-CKQ1C1aY.js");
2554
2555
  const account = resolveFeishuRuntimeAccount({
2555
2556
  cfg: ctx.cfg,
2556
2557
  accountId: ctx.accountId
@@ -1,2 +1,2 @@
1
- import { t as feishuPlugin } from "./channel-YU5bdC_o.js";
1
+ import { t as feishuPlugin } from "./channel-B3ngoThy.js";
2
2
  export { feishuPlugin };
@@ -1,5 +1,5 @@
1
1
  import { o as resolveFeishuAccount, s as resolveFeishuRuntimeAccount, y as parseFeishuCommentTarget } from "./accounts-Bpe6CjpS.js";
2
- import { h as listFeishuDirectoryPeers, m as listFeishuDirectoryGroups, o as buildFeishuPresentationCardElements } from "./channel-YU5bdC_o.js";
2
+ import { h as listFeishuDirectoryPeers, m as listFeishuDirectoryGroups, o as buildFeishuPresentationCardElements } from "./channel-B3ngoThy.js";
3
3
  import { r as createFeishuClient } from "./client-BhMNZBJD.js";
4
4
  import { c as getChatInfo, l as getChatMembers, r as cleanupAmbientCommentTypingReaction, t as deliverCommentThreadText, u as getFeishuMemberInfo } from "./drive-BIrffRwc.js";
5
5
  import { chunkTextForOutbound } from "./runtime-api.js";
@@ -42,7 +42,7 @@ function buildMigrationEntries(namespace, sourcePath, now) {
42
42
  }
43
43
  const detectFeishuLegacyStateMigrations = ({ stateDir }) => {
44
44
  const dedupDir = path.join(stateDir, "feishu", "dedup");
45
- let entries = [];
45
+ let entries;
46
46
  try {
47
47
  entries = fs.readdirSync(dedupDir, { withFileTypes: true });
48
48
  } catch {
@@ -3,7 +3,7 @@ import { l as fetchBotIdentityForMonitor } from "./monitor.state-r4OLFBfg.js";
3
3
  //#region extensions/feishu/src/monitor.ts
4
4
  let monitorAccountRuntimePromise;
5
5
  async function loadMonitorAccountRuntime() {
6
- monitorAccountRuntimePromise ??= import("./monitor.account-DrLtrcbg.js");
6
+ monitorAccountRuntimePromise ??= import("./monitor.account-CZN6TDMA.js");
7
7
  return await monitorAccountRuntimePromise;
8
8
  }
9
9
  async function monitorFeishuProvider(opts = {}) {
@@ -1,6 +1,6 @@
1
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 { c as normalizeFeishuAllowEntry, d as resolveFeishuGroupConversationIngressAccess, f as resolveFeishuGroupSenderActivationIngressAccess, l as resolveFeishuDmIngressAccess, p as resolveFeishuReplyPolicy, s as hasExplicitFeishuGroupConfig, u as resolveFeishuGroupConfig } from "./channel-YU5bdC_o.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-B3ngoThy.js";
4
4
  import { c as decodeFeishuCardAction, o as buildFeishuCardActionTextFallback, s as createFeishuCardInteractionEnvelope } from "./send-result-D9rgEUlm.js";
5
5
  import { t as buildFeishuConversationId } from "./conversation-id-DuL575sn.js";
6
6
  import { t as getFeishuRuntime } from "./runtime-C5JxBWZp.js";
@@ -49,7 +49,7 @@ function resolveFeishuGroupSession(params) {
49
49
  const legacyTopicSessionMode = groupConfig?.topicSessionMode ?? feishuCfg?.topicSessionMode ?? "disabled";
50
50
  const groupSessionScope = groupConfig?.groupSessionScope ?? feishuCfg?.groupSessionScope ?? (legacyTopicSessionMode === "enabled" ? "group_topic" : "group");
51
51
  const topicScope = groupSessionScope === "group_topic" || groupSessionScope === "group_topic_sender" ? (chatType === "topic_group" ? normalizedThreadId ?? normalizedRootId : void 0) ?? normalizedRootId ?? normalizedThreadId ?? (replyInThread ? messageId : null) : null;
52
- let peerId = chatId;
52
+ let peerId;
53
53
  switch (groupSessionScope) {
54
54
  case "group_sender":
55
55
  peerId = buildFeishuConversationId({
@@ -1176,17 +1176,21 @@ var FeishuStreamingSession = class {
1176
1176
  }).catch((e) => this.log?.(`Note update failed: ${String(e)}`));
1177
1177
  }
1178
1178
  async close(finalText, options) {
1179
- if (!this.state || this.closed) return;
1179
+ if (!this.state || this.closed) return false;
1180
1180
  this.closed = true;
1181
1181
  this.clearFlushTimer();
1182
1182
  await this.queue;
1183
1183
  const pendingMerged = mergeStreamingText(this.state.currentText, this.pendingText ?? void 0);
1184
1184
  const text = finalText ?? pendingMerged;
1185
1185
  const apiBase = resolveApiBase(this.creds.domain);
1186
+ let visibleContentSent = Boolean(this.state.sentText.trim());
1186
1187
  if ((text || finalText !== void 0) && text !== this.state.sentText) {
1187
1188
  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)}`));
1188
1189
  this.state.currentText = text;
1189
- if (sent) this.state.sentText = text;
1190
+ if (sent) {
1191
+ this.state.sentText = text;
1192
+ visibleContentSent = Boolean(text.trim());
1193
+ }
1190
1194
  }
1191
1195
  if (options?.note) await this.updateNoteContent(options.note);
1192
1196
  this.state.sequence += 1;
@@ -1217,6 +1221,7 @@ var FeishuStreamingSession = class {
1217
1221
  this.state = null;
1218
1222
  this.pendingText = null;
1219
1223
  this.log?.(`Closed streaming: cardId=${finalState.cardId}`);
1224
+ return visibleContentSent;
1220
1225
  }
1221
1226
  async discard() {
1222
1227
  if (!this.state || this.closed) return;
@@ -1390,6 +1395,7 @@ function shouldUseCard(text) {
1390
1395
  const TYPING_INDICATOR_MAX_AGE_MS = 2 * 6e4;
1391
1396
  const MS_EPOCH_MIN = 0xe8d4a51000;
1392
1397
  const STREAMING_START_FAILURE_BACKOFF_MS = 6e4;
1398
+ 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.";
1393
1399
  const streamingStartBackoffUntilByAccount = /* @__PURE__ */ new Map();
1394
1400
  function isStreamingStartBackedOff(accountId, now = Date.now()) {
1395
1401
  const backoffUntil = streamingStartBackoffUntilByAccount.get(accountId);
@@ -1513,6 +1519,13 @@ function createFeishuReplyDispatcher(params) {
1513
1519
  let streamingStartPromise = null;
1514
1520
  let streamingClosedForReply = false;
1515
1521
  let streamingCloseErroredForReply = false;
1522
+ let visibleReplySent = false;
1523
+ let skippedFinalReason = null;
1524
+ let idleSideEffectsPromise = Promise.resolve();
1525
+ let replyLifecycleStateInitialized = false;
1526
+ const markVisibleReplySent = () => {
1527
+ visibleReplySent = true;
1528
+ };
1516
1529
  const formatReasoningPrefix = (thinking) => {
1517
1530
  if (!thinking) return "";
1518
1531
  return `> 💭 **Thinking**\n${thinking.replace(/^(?:Reasoning:|Thinking\.{0,3})\s*/u, "").replace(/^_(.*)_$/gm, "$1").split("\n").map((line) => `> ${line}`).join("\n")}`;
@@ -1599,8 +1612,9 @@ function createFeishuReplyDispatcher(params) {
1599
1612
  statusLine = "";
1600
1613
  const text = buildCombinedStreamText(reasoningText, streamText);
1601
1614
  const finalNote = resolveCardNote(agentId, identity, prefixContext.prefixContext);
1602
- await streaming.close(text, { note: finalNote });
1603
- if (streamText) {
1615
+ const contentVisible = await streaming.close(text, { note: finalNote });
1616
+ if (contentVisible) markVisibleReplySent();
1617
+ if (contentVisible && streamText) {
1604
1618
  deliveredFinalTexts.add(streamText);
1605
1619
  if (options?.markClosedForReply !== false && !streamingCloseErroredForReply) streamingClosedForReply = true;
1606
1620
  }
@@ -1626,11 +1640,14 @@ function createFeishuReplyDispatcher(params) {
1626
1640
  };
1627
1641
  const sendChunkedTextReply = async (paramsLocal) => {
1628
1642
  const chunkSource = paramsLocal.useCard ? paramsLocal.text : core.channel.text.convertMarkdownTables(paramsLocal.text, tableMode);
1629
- const chunks = resolveTextChunksWithFallback(chunkSource, core.channel.text.chunkTextWithMode(chunkSource, textChunkLimit, chunkMode));
1630
- for (const [index, chunk] of chunks.entries()) await paramsLocal.sendChunk({
1631
- chunk,
1632
- isFirst: index === 0
1633
- });
1643
+ const chunks = resolveTextChunksWithFallback(chunkSource, (paramsLocal.useCard ? core.channel.text.chunkMarkdownTextWithMode : core.channel.text.chunkTextWithMode)(chunkSource, textChunkLimit, chunkMode));
1644
+ for (const [index, chunk] of chunks.entries()) {
1645
+ await paramsLocal.sendChunk({
1646
+ chunk,
1647
+ isFirst: index === 0
1648
+ });
1649
+ markVisibleReplySent();
1650
+ }
1634
1651
  if (paramsLocal.infoKind === "final") deliveredFinalTexts.add(paramsLocal.text);
1635
1652
  };
1636
1653
  const sendMediaReplies = async (payload, options) => {
@@ -1640,7 +1657,7 @@ function createFeishuReplyDispatcher(params) {
1640
1657
  mediaUrls,
1641
1658
  caption: "",
1642
1659
  send: async ({ mediaUrl }) => {
1643
- if ((await sendMediaFeishu({
1660
+ const result = await sendMediaFeishu({
1644
1661
  cfg,
1645
1662
  to: chatId,
1646
1663
  mediaUrl,
@@ -1648,7 +1665,9 @@ function createFeishuReplyDispatcher(params) {
1648
1665
  replyInThread: effectiveReplyInThread,
1649
1666
  accountId,
1650
1667
  ...payload.audioAsVoice === true ? { audioAsVoice: true } : {}
1651
- }))?.voiceIntentDegradedToFile && options?.fallbackText && !sentFallbackText) {
1668
+ });
1669
+ markVisibleReplySent();
1670
+ if (result?.voiceIntentDegradedToFile && options?.fallbackText && !sentFallbackText) {
1652
1671
  sentFallbackText = true;
1653
1672
  await sendChunkedTextReply({
1654
1673
  text: options.fallbackText,
@@ -1690,18 +1709,61 @@ function createFeishuReplyDispatcher(params) {
1690
1709
  }
1691
1710
  });
1692
1711
  };
1712
+ const ensureNoVisibleReplyFallback = async (reason) => {
1713
+ await idleSideEffectsPromise;
1714
+ if (visibleReplySent) return false;
1715
+ if (skippedFinalReason === "silent") {
1716
+ params.runtime.log?.(`feishu[${account.accountId}]: no-visible-reply fallback skipped for intentional silence (${reason})`);
1717
+ return false;
1718
+ }
1719
+ await sendMessageFeishu({
1720
+ cfg,
1721
+ to: chatId,
1722
+ text: NO_VISIBLE_REPLY_FALLBACK_TEXT,
1723
+ replyToMessageId: sendReplyToMessageId,
1724
+ replyInThread: effectiveReplyInThread,
1725
+ allowTopLevelReplyFallback,
1726
+ accountId
1727
+ });
1728
+ markVisibleReplySent();
1729
+ params.runtime.error?.(`feishu[${account.accountId}]: sent no-visible-reply fallback (${reason})`);
1730
+ return true;
1731
+ };
1732
+ const queueIdleSideEffects = (options) => {
1733
+ const nextIdleSideEffects = idleSideEffectsPromise.then(async () => {
1734
+ await closeStreaming(options);
1735
+ await Promise.resolve(typingCallbacks?.onIdle?.());
1736
+ });
1737
+ idleSideEffectsPromise = nextIdleSideEffects.catch(() => {});
1738
+ return nextIdleSideEffects;
1739
+ };
1693
1740
  const { dispatcher, replyOptions, markDispatchIdle } = core.channel.reply.createReplyDispatcherWithTyping({
1694
1741
  responsePrefix: prefixContext.responsePrefix,
1695
1742
  responsePrefixContextProvider: prefixContext.responsePrefixContextProvider,
1696
1743
  humanDelay: core.channel.reply.resolveHumanDelayConfig(cfg, agentId),
1744
+ silentReplyContext: {
1745
+ cfg,
1746
+ sessionKey: params.sessionKey,
1747
+ surface: "feishu",
1748
+ conversationType: chatId.startsWith("oc_") ? "group" : "direct"
1749
+ },
1750
+ onSkip: (_payload, info) => {
1751
+ if (info.kind === "final") skippedFinalReason = info.reason;
1752
+ },
1697
1753
  onReplyStart: async () => {
1698
- deliveredFinalTexts.clear();
1699
- streamingClosedForReply = false;
1700
- streamingCloseErroredForReply = false;
1754
+ if (!replyLifecycleStateInitialized) {
1755
+ replyLifecycleStateInitialized = true;
1756
+ deliveredFinalTexts.clear();
1757
+ streamingClosedForReply = false;
1758
+ streamingCloseErroredForReply = false;
1759
+ visibleReplySent = false;
1760
+ skippedFinalReason = null;
1761
+ }
1701
1762
  if (streamingEnabled && renderMode === "card") startStreaming();
1702
- await typingCallbacks?.onReplyStart?.();
1763
+ await Promise.resolve(typingCallbacks?.onReplyStart?.());
1703
1764
  },
1704
1765
  deliver: async (payload, info) => {
1766
+ if (info?.kind === "final") skippedFinalReason = null;
1705
1767
  const payloadText = payload.isReasoning && payload.text ? formatReasoningMessage(payload.text) : payload.text;
1706
1768
  const reply = resolveSendableOutboundReplyParts({
1707
1769
  ...payload,
@@ -1714,21 +1776,24 @@ function createFeishuReplyDispatcher(params) {
1714
1776
  mediaUrl,
1715
1777
  ...payload.audioAsVoice === true ? { audioAsVoice: true } : {}
1716
1778
  }));
1717
- const streamingCardEnabledForReplyKind = streamingEnabled && info?.kind === "final";
1718
- const useCard = hasText && (streamingCardEnabledForReplyKind || renderMode === "card" || info?.kind === "block" && coreBlockStreamingEnabled && renderMode !== "raw" || renderMode === "auto" && shouldUseCard(text));
1779
+ const finalTextExceedsStreamingLimit = info?.kind === "final" && hasText && text.length > textChunkLimit;
1780
+ const useStaticCard = hasText && (renderMode === "card" || info?.kind === "block" && coreBlockStreamingEnabled && renderMode !== "raw" || renderMode === "auto" && shouldUseCard(text));
1781
+ const useStreamingCard = hasText && streamingEnabled && !finalTextExceedsStreamingLimit && (info?.kind === "final" || useStaticCard);
1782
+ const finalTextWouldUseStreamingCard = info?.kind === "final" && hasText && streamingEnabled;
1783
+ const useCard = useStaticCard || useStreamingCard;
1719
1784
  const skipTextForDuplicateFinal = info?.kind === "final" && hasText && deliveredFinalTexts.has(text);
1720
- const skipTextForClosedStreamingFinal = info?.kind === "final" && hasText && streamingClosedForReply && !streamingCloseErroredForReply && streamingEnabled && useCard;
1785
+ const skipTextForClosedStreamingFinal = info?.kind === "final" && hasText && streamingClosedForReply && !streamingCloseErroredForReply && finalTextWouldUseStreamingCard;
1721
1786
  const shouldDeliverText = hasText && !hasVoiceMedia && !skipTextForDuplicateFinal && !skipTextForClosedStreamingFinal;
1722
- const shouldDiscardStreamingPreview = info?.kind === "final" && hasMedia && (hasVoiceMedia && !shouldDeliverText || skipTextForDuplicateFinal);
1787
+ const shouldDiscardStreamingPreview = info?.kind === "final" && (finalTextExceedsStreamingLimit || hasMedia && (hasVoiceMedia && !shouldDeliverText || skipTextForDuplicateFinal));
1723
1788
  if (!shouldDeliverText && !hasMedia) return;
1724
1789
  if (shouldDiscardStreamingPreview) await discardStreamingPreview();
1725
1790
  if (shouldDeliverText) {
1726
1791
  if (info?.kind === "block") {
1727
- if (!(streamingEnabled && useCard)) return;
1792
+ if (!useStreamingCard) return;
1728
1793
  startStreaming();
1729
1794
  if (streamingStartPromise) await streamingStartPromise;
1730
1795
  }
1731
- if (info?.kind === "final" && streamingEnabled && useCard) {
1796
+ if (info?.kind === "final" && useStreamingCard) {
1732
1797
  startStreaming();
1733
1798
  if (streamingStartPromise) await streamingStartPromise;
1734
1799
  }
@@ -1791,13 +1856,9 @@ function createFeishuReplyDispatcher(params) {
1791
1856
  streamingCloseErroredForReply = true;
1792
1857
  streamingClosedForReply = false;
1793
1858
  params.runtime.error?.(`feishu[${account.accountId}] ${info.kind} reply failed: ${String(error)}`);
1794
- await closeStreaming({ markClosedForReply: false });
1795
- typingCallbacks?.onIdle?.();
1796
- },
1797
- onIdle: async () => {
1798
- await closeStreaming();
1799
- typingCallbacks?.onIdle?.();
1859
+ await queueIdleSideEffects({ markClosedForReply: false });
1800
1860
  },
1861
+ onIdle: () => queueIdleSideEffects(),
1801
1862
  onCleanup: () => {
1802
1863
  typingCallbacks?.onCleanup?.();
1803
1864
  }
@@ -1847,7 +1908,12 @@ function createFeishuReplyDispatcher(params) {
1847
1908
  updateStreamingStatusLine("");
1848
1909
  } : void 0
1849
1910
  },
1850
- markDispatchIdle
1911
+ markDispatchIdle,
1912
+ ensureNoVisibleReplyFallback,
1913
+ getVisibleReplyState: () => ({
1914
+ visibleReplySent,
1915
+ skippedFinalReason
1916
+ })
1851
1917
  };
1852
1918
  }
1853
1919
  //#endregion
@@ -1857,6 +1923,13 @@ const PERMISSION_ERROR_COOLDOWN_MS = 300 * 1e3;
1857
1923
  const groupNameCache = /* @__PURE__ */ new Map();
1858
1924
  const GROUP_NAME_CACHE_TTL_MS = 1800 * 1e3;
1859
1925
  const GROUP_NAME_CACHE_MAX_SIZE = 500;
1926
+ function shouldSendNoVisibleReplyFallback(dispatchResult) {
1927
+ const finalCount = dispatchResult.counts.final ?? 0;
1928
+ const failedFinalCount = dispatchResult.failedCounts?.final ?? 0;
1929
+ const emptyEligibleDispatch = dispatchResult.noVisibleReplyFallbackEligible === true && dispatchResult.queuedFinal !== true && finalCount === 0;
1930
+ const queuedFinalFailed = dispatchResult.queuedFinal === true && failedFinalCount > 0;
1931
+ return dispatchResult.sendPolicyDenied !== true && dispatchResult.sourceReplyDeliveryMode !== "message_tool_only" && (emptyEligibleDispatch || queuedFinalFailed);
1932
+ }
1860
1933
  function resolveConfiguredFeishuGroupSessionScope(params) {
1861
1934
  const legacyTopicSessionMode = params.groupConfig?.topicSessionMode ?? params.feishuCfg?.topicSessionMode ?? "disabled";
1862
1935
  return params.groupConfig?.groupSessionScope ?? params.feishuCfg?.groupSessionScope ?? (legacyTopicSessionMode === "enabled" ? "group_topic" : "group");
@@ -2740,7 +2813,7 @@ async function handleFeishuMessage(params) {
2740
2813
  const agentCtx = await buildCtxPayloadForAgent(agentId, agentSessionKey, route.accountId, ctx.mentionedBot && agentId === activeAgentId);
2741
2814
  if (agentId === activeAgentId) {
2742
2815
  const identity = resolveAgentOutboundIdentity(cfg, agentId);
2743
- const { dispatcher, replyOptions, markDispatchIdle } = createFeishuReplyDispatcher({
2816
+ const { dispatcher, replyOptions, markDispatchIdle, ensureNoVisibleReplyFallback } = createFeishuReplyDispatcher({
2744
2817
  cfg,
2745
2818
  agentId,
2746
2819
  runtime,
@@ -2753,10 +2826,11 @@ async function handleFeishuMessage(params) {
2753
2826
  threadReply,
2754
2827
  accountId: account.accountId,
2755
2828
  identity,
2756
- messageCreateTimeMs
2829
+ messageCreateTimeMs,
2830
+ sessionKey: agentSessionKey
2757
2831
  });
2758
2832
  log(`feishu[${account.accountId}]: broadcast active dispatch agent=${agentId} (session=${agentSessionKey})`);
2759
- await core.channel.inbound.run({
2833
+ const turnResult = await core.channel.inbound.run({
2760
2834
  channel: "feishu",
2761
2835
  accountId: route.accountId,
2762
2836
  raw: ctx,
@@ -2794,6 +2868,10 @@ async function handleFeishuMessage(params) {
2794
2868
  })
2795
2869
  }
2796
2870
  });
2871
+ if (turnResult.dispatched && shouldSendNoVisibleReplyFallback({
2872
+ ...turnResult.dispatchResult,
2873
+ failedCounts: dispatcher.getFailedCounts()
2874
+ })) await ensureNoVisibleReplyFallback("broadcast-dispatch-complete-no-visible-reply");
2797
2875
  } else {
2798
2876
  delete agentCtx.CommandAuthorized;
2799
2877
  const noopDispatcher = {
@@ -2872,7 +2950,7 @@ async function handleFeishuMessage(params) {
2872
2950
  storePath,
2873
2951
  sessionKey: route.sessionKey
2874
2952
  });
2875
- const { dispatcher, replyOptions, markDispatchIdle } = createFeishuReplyDispatcher({
2953
+ const { dispatcher, replyOptions, markDispatchIdle, ensureNoVisibleReplyFallback } = createFeishuReplyDispatcher({
2876
2954
  cfg,
2877
2955
  agentId: route.agentId,
2878
2956
  runtime,
@@ -2885,7 +2963,8 @@ async function handleFeishuMessage(params) {
2885
2963
  threadReply,
2886
2964
  accountId: account.accountId,
2887
2965
  identity,
2888
- messageCreateTimeMs
2966
+ messageCreateTimeMs,
2967
+ sessionKey: route.sessionKey
2889
2968
  });
2890
2969
  log(`feishu[${account.accountId}]: dispatching to agent (session=${route.sessionKey})`);
2891
2970
  const turnResult = await core.channel.inbound.run({
@@ -2945,6 +3024,10 @@ async function handleFeishuMessage(params) {
2945
3024
  if (!turnResult.dispatched) return;
2946
3025
  const { dispatchResult } = turnResult;
2947
3026
  const { queuedFinal, counts } = dispatchResult;
3027
+ if (shouldSendNoVisibleReplyFallback({
3028
+ ...dispatchResult,
3029
+ failedCounts: dispatcher.getFailedCounts()
3030
+ })) await ensureNoVisibleReplyFallback("dispatch-complete-no-visible-reply");
2948
3031
  log(`feishu[${account.accountId}]: dispatch complete (queuedFinal=${queuedFinal}, replies=${counts.final})`);
2949
3032
  }
2950
3033
  } catch (err) {
@@ -3805,7 +3888,9 @@ async function resolveParsedCommentContent(params) {
3805
3888
  };
3806
3889
  }
3807
3890
  async function delayMs(ms) {
3808
- await new Promise((resolve) => setTimeout(resolve, ms));
3891
+ await new Promise((resolve) => {
3892
+ setTimeout(resolve, ms);
3893
+ });
3809
3894
  }
3810
3895
  function buildDriveCommentTargetUrl(params) {
3811
3896
  return `/open-apis/drive/v1/files/${encodeURIComponent(params.fileToken)}/comments/batch_query` + encodeQuery({
@@ -5429,7 +5514,7 @@ async function monitorSingleAccount(params) {
5429
5514
  if (connectionMode === "webhook" && !account.encryptKey?.trim()) throw new Error(`Feishu account "${accountId}" webhook mode requires encryptKey`);
5430
5515
  const warmupCount = await warmupDedupFromDisk(accountId, log);
5431
5516
  if (warmupCount > 0) log(`feishu[${accountId}]: dedup warmup loaded ${warmupCount} entries from disk`);
5432
- let threadBindingManager = null;
5517
+ let threadBindingManager;
5433
5518
  try {
5434
5519
  const eventDispatcher = createEventDispatcher(account);
5435
5520
  const chatHistories = /* @__PURE__ */ new Map();
package/dist/setup-api.js CHANGED
@@ -1,2 +1,2 @@
1
- import { i as feishuSetupAdapter, n as feishuSetupWizard, t as feishuPlugin } from "./channel-YU5bdC_o.js";
1
+ import { i as feishuSetupAdapter, n as feishuSetupWizard, t as feishuPlugin } from "./channel-B3ngoThy.js";
2
2
  export { feishuPlugin, feishuSetupAdapter, feishuSetupWizard };
@@ -1,19 +1,19 @@
1
1
  {
2
2
  "name": "@openclaw/feishu",
3
- "version": "2026.5.31-beta.3",
3
+ "version": "2026.6.1-beta.1",
4
4
  "lockfileVersion": 3,
5
5
  "requires": true,
6
6
  "packages": {
7
7
  "": {
8
8
  "name": "@openclaw/feishu",
9
- "version": "2026.5.31-beta.3",
9
+ "version": "2026.6.1-beta.1",
10
10
  "dependencies": {
11
11
  "@larksuiteoapi/node-sdk": "1.66.0",
12
12
  "typebox": "1.1.39",
13
13
  "zod": "4.4.3"
14
14
  },
15
15
  "peerDependencies": {
16
- "openclaw": ">=2026.5.31-beta.3"
16
+ "openclaw": ">=2026.6.1-beta.1"
17
17
  },
18
18
  "peerDependenciesMeta": {
19
19
  "openclaw": {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@openclaw/feishu",
3
- "version": "2026.5.31-beta.3",
3
+ "version": "2026.6.1-beta.1",
4
4
  "description": "OpenClaw Feishu/Lark channel plugin for chats and workplace tools (community maintained by @m1heng).",
5
5
  "repository": {
6
6
  "type": "git",
@@ -13,7 +13,7 @@
13
13
  "zod": "4.4.3"
14
14
  },
15
15
  "peerDependencies": {
16
- "openclaw": ">=2026.5.31-beta.3"
16
+ "openclaw": ">=2026.6.1-beta.1"
17
17
  },
18
18
  "peerDependenciesMeta": {
19
19
  "openclaw": {
@@ -47,10 +47,10 @@
47
47
  "minHostVersion": ">=2026.5.29"
48
48
  },
49
49
  "compat": {
50
- "pluginApi": ">=2026.5.31-beta.3"
50
+ "pluginApi": ">=2026.6.1-beta.1"
51
51
  },
52
52
  "build": {
53
- "openclawVersion": "2026.5.31-beta.3"
53
+ "openclawVersion": "2026.6.1-beta.1"
54
54
  },
55
55
  "release": {
56
56
  "publishToClawHub": true,