@openclaw/feishu 2026.5.31-beta.2 → 2026.5.31-beta.4

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,11 +1,11 @@
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-TXwK9jIG.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";
6
6
  import { a as jsonToolResult, d as registerFeishuChatTools, f as createFeishuToolClient, h as resolveToolsConfig, m as resolveFeishuToolAccount, n as registerFeishuDriveTools, o as toolExecutionErrorResult, p as resolveAnyEnabledFeishuToolsConfig, s as unknownToolActionResult } from "./drive-BIrffRwc.js";
7
- import { n as getFeishuThreadBindingManager, r as testing, t as createFeishuThreadBindingManager } from "./thread-bindings-C58Uq5Y1.js";
8
- import { n as handleFeishuSubagentEnded, r as handleFeishuSubagentSpawning, t as handleFeishuSubagentDeliveryTarget } from "./subagent-hooks-Bw3Dg2mS.js";
7
+ import { n as getFeishuThreadBindingManager, r as testing, t as createFeishuThreadBindingManager } from "./thread-bindings-V0bwk0A1.js";
8
+ import { n as handleFeishuSubagentEnded, r as handleFeishuSubagentSpawning, t as handleFeishuSubagentDeliveryTarget } from "./subagent-hooks-1pqt5tAA.js";
9
9
  import { normalizeLowercaseStringOrEmpty, normalizeOptionalString, readStringValue, uniqueStrings } from "openclaw/plugin-sdk/string-coerce-runtime";
10
10
  import { optionalPositiveIntegerSchema } from "openclaw/plugin-sdk/channel-actions";
11
11
  import { existsSync } from "node:fs";
@@ -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 {
@@ -832,10 +832,7 @@ async function repairFeishuDoctorState(params) {
832
832
  if (entry) removed.push(entry);
833
833
  }
834
834
  return removed;
835
- }, {
836
- skipMaintenance: true,
837
- allowDropAcpMetaSessionKeys: [...keys]
838
- });
835
+ }, { skipMaintenance: true });
839
836
  const removed = removedEntries.length;
840
837
  removedSessionEntries += removed;
841
838
  if (removed > 0) {
@@ -1125,6 +1122,11 @@ function resolveSafeFeishuButtonUrl(url) {
1125
1122
  function resolveFeishuButtonUrl(button) {
1126
1123
  return button.url ?? button.webApp?.url ?? button.web_app?.url;
1127
1124
  }
1125
+ function resolveFeishuCommandButtonValue(button) {
1126
+ if (button.action?.type === "callback") return;
1127
+ if (button.action?.type === "command") return button.action.command;
1128
+ return button.value;
1129
+ }
1128
1130
  function mapFeishuButtonType(style) {
1129
1131
  if (style === "primary" || style === "success") return "primary";
1130
1132
  if (style === "danger") return "danger";
@@ -1148,12 +1150,13 @@ function buildFeishuPayloadButton(button) {
1148
1150
  default_url: safeUrl
1149
1151
  });
1150
1152
  }
1151
- if (button.value) behaviors.push({
1153
+ const value = resolveFeishuCommandButtonValue(button);
1154
+ if (value) behaviors.push({
1152
1155
  type: "callback",
1153
1156
  value: createFeishuCardInteractionEnvelope({
1154
1157
  k: "quick",
1155
1158
  a: "feishu.payload.button",
1156
- q: button.value
1159
+ q: value
1157
1160
  })
1158
1161
  });
1159
1162
  if (behaviors.length === 0) return;
@@ -1430,7 +1433,7 @@ function applyNewAppSecurityPolicy(cfg, accountId, openId, groupPolicy) {
1430
1433
  }
1431
1434
  let appRegistrationModulePromise = null;
1432
1435
  const loadAppRegistrationModule = async () => {
1433
- appRegistrationModulePromise ??= import("./app-registration-DCy5-X_C.js");
1436
+ appRegistrationModulePromise ??= import("./app-registration-tgQJUcv3.js");
1434
1437
  return await appRegistrationModulePromise;
1435
1438
  };
1436
1439
  async function promptFeishuDomain(params) {
@@ -1501,7 +1504,7 @@ async function runNewAppFlow(params) {
1501
1504
  const { prompter, options } = params;
1502
1505
  let next = params.cfg;
1503
1506
  const targetAccountId = resolveDefaultFeishuAccountId(next);
1504
- let appId = null;
1507
+ let appId;
1505
1508
  let appSecret = null;
1506
1509
  let appSecretProbeValue = null;
1507
1510
  let scanDomain;
@@ -1517,7 +1520,6 @@ async function runNewAppFlow(params) {
1517
1520
  if (scanResult) {
1518
1521
  appId = scanResult.appId;
1519
1522
  appSecret = scanResult.appSecret;
1520
- appSecretProbeValue = scanResult.appSecret;
1521
1523
  scanDomain = scanResult.domain;
1522
1524
  scanOpenId = scanResult.openId;
1523
1525
  } else {
@@ -1572,7 +1574,9 @@ async function runNewAppFlow(params) {
1572
1574
  initialValue: "allowlist"
1573
1575
  });
1574
1576
  const configProgress = prompter.progress(t("wizard.feishu.configuring"));
1575
- await new Promise((resolve) => setTimeout(resolve, 50));
1577
+ await new Promise((resolve) => {
1578
+ setTimeout(resolve, 50);
1579
+ });
1576
1580
  if (appId && appSecret) next = patchFeishuConfig(next, targetAccountId, {
1577
1581
  appId,
1578
1582
  appSecret,
@@ -1731,7 +1735,7 @@ const meta = {
1731
1735
  order: 70,
1732
1736
  preferSessionLookupForAnnounceTarget: true
1733
1737
  };
1734
- const loadFeishuChannelRuntime = createLazyRuntimeNamedExport(() => import("./channel.runtime-AAff9iBZ.js"), "feishuChannelRuntime");
1738
+ const loadFeishuChannelRuntime = createLazyRuntimeNamedExport(() => import("./channel.runtime-D_5rduJm.js"), "feishuChannelRuntime");
1735
1739
  function toFeishuMessageSendResult(result, kind) {
1736
1740
  const receipt = result.receipt ?? createFeishuSendReceipt({
1737
1741
  messageId: result.messageId,
@@ -2547,7 +2551,7 @@ const feishuPlugin = createChatChannelPlugin({
2547
2551
  })
2548
2552
  }),
2549
2553
  gateway: { startAccount: async (ctx) => {
2550
- const { monitorFeishuProvider } = await import("./monitor-Ccev4ogO.js");
2554
+ const { monitorFeishuProvider } = await import("./monitor-CKQ1C1aY.js");
2551
2555
  const account = resolveFeishuRuntimeAccount({
2552
2556
  cfg: ctx.cfg,
2553
2557
  accountId: ctx.accountId
@@ -1,2 +1,2 @@
1
- import { t as feishuPlugin } from "./channel-TXwK9jIG.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-TXwK9jIG.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";
@@ -2,7 +2,7 @@ import { a as parseFeishuTargetId, i as parseFeishuDirectConversationId, r as pa
2
2
  import { t as messageActionTargetAliases } from "./security-audit-BIeA3W3Q.js";
3
3
  import { n as collectRuntimeConfigAssignments, r as secretTargetRegistryEntries } from "./secret-contract-ChjJKAJ9.js";
4
4
  import { t as collectFeishuSecurityAuditFindings } from "./security-audit-shared-BIHeF-S_.js";
5
- import { r as testing, t as createFeishuThreadBindingManager } from "./thread-bindings-C58Uq5Y1.js";
5
+ import { r as testing, t as createFeishuThreadBindingManager } from "./thread-bindings-V0bwk0A1.js";
6
6
  //#region extensions/feishu/contract-api.ts
7
7
  const feishuSessionBindingAdapterChannels = ["feishu"];
8
8
  //#endregion
@@ -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-DYaT0fVd.js");
6
+ monitorAccountRuntimePromise ??= import("./monitor.account-CZN6TDMA.js");
7
7
  return await monitorAccountRuntimePromise;
8
8
  }
9
9
  async function monitorFeishuProvider(opts = {}) {
@@ -1,12 +1,12 @@
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-TXwK9jIG.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";
7
7
  import { a as getFeishuUserAgent, i as createFeishuWSClient, n as createEventDispatcher, r as createFeishuClient } from "./client-BhMNZBJD.js";
8
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";
9
+ import { t as createFeishuThreadBindingManager } from "./thread-bindings-V0bwk0A1.js";
10
10
  import { createReplyPrefixContext, evaluateSupplementalContextVisibility, loadSessionStore, normalizeAgentId as normalizeAgentId$2, resolveChannelContextVisibilityMode, resolveSessionStoreEntry } from "./runtime-api.js";
11
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
12
  import { i as waitForAbortableDelay, r as raceWithTimeoutAndAbort } from "./probe-BjKRV7em.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
  }
@@ -1624,14 +1638,17 @@ function createFeishuReplyDispatcher(params) {
1624
1638
  startStreaming();
1625
1639
  flushStreamingCardUpdate(buildCombinedStreamText(reasoningText, streamText));
1626
1640
  };
1627
- const sendChunkedTextReply = async (params) => {
1628
- const chunkSource = params.useCard ? params.text : core.channel.text.convertMarkdownTables(params.text, tableMode);
1629
- const chunks = resolveTextChunksWithFallback(chunkSource, core.channel.text.chunkTextWithMode(chunkSource, textChunkLimit, chunkMode));
1630
- for (const [index, chunk] of chunks.entries()) await params.sendChunk({
1631
- chunk,
1632
- isFirst: index === 0
1633
- });
1634
- if (params.infoKind === "final") deliveredFinalTexts.add(params.text);
1641
+ const sendChunkedTextReply = async (paramsLocal) => {
1642
+ const chunkSource = paramsLocal.useCard ? paramsLocal.text : core.channel.text.convertMarkdownTables(paramsLocal.text, tableMode);
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
+ }
1651
+ if (paramsLocal.infoKind === "final") deliveredFinalTexts.add(paramsLocal.text);
1635
1652
  };
1636
1653
  const sendMediaReplies = async (payload, options) => {
1637
1654
  const mediaUrls = resolveSendableOutboundReplyParts(payload).mediaUrls;
@@ -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
  }
@@ -1829,13 +1890,13 @@ function createFeishuReplyDispatcher(params) {
1829
1890
  onReasoningEnd: reasoningPreviewEnabled ? () => {} : void 0,
1830
1891
  onToolStart: streamingEnabled ? (payload) => {
1831
1892
  if (!isChannelProgressDraftWorkToolName(payload.name)) return;
1832
- const statusLine = formatChannelProgressDraftLineForEntry(account.config, {
1893
+ const statusLineLocal = formatChannelProgressDraftLineForEntry(account.config, {
1833
1894
  event: "tool",
1834
1895
  name: payload.name,
1835
1896
  phase: payload.phase,
1836
1897
  args: payload.args
1837
1898
  }, { detailMode: payload.detailMode });
1838
- if (statusLine) updateStreamingStatusLine(statusLine);
1899
+ if (statusLineLocal) updateStreamingStatusLine(statusLineLocal);
1839
1900
  } : void 0,
1840
1901
  onAssistantMessageStart: streamingEnabled ? () => {
1841
1902
  updateStreamingStatusLine("", { startIfNeeded: false });
@@ -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");
@@ -2685,16 +2758,16 @@ async function handleFeishuMessage(params) {
2685
2758
  normalizeEntry: normalizeFeishuAllowEntry
2686
2759
  }) : null;
2687
2760
  const pinnedMainDmSenderRecipient = pinnedMainDmOwner ? [ctx.senderOpenId, senderUserId].map((id) => id ? normalizeFeishuAllowEntry(id) : "").find((recipient) => recipient === pinnedMainDmOwner) : void 0;
2688
- const buildFeishuInboundLastRouteUpdate = (params) => {
2689
- const inboundLastRouteSessionKey = params.sessionKey === route.sessionKey ? resolveInboundLastRouteSessionKey({
2761
+ const buildFeishuInboundLastRouteUpdate = (paramsLocal) => {
2762
+ const inboundLastRouteSessionKey = paramsLocal.sessionKey === route.sessionKey ? resolveInboundLastRouteSessionKey({
2690
2763
  route,
2691
- sessionKey: params.sessionKey
2692
- }) : params.sessionKey;
2764
+ sessionKey: paramsLocal.sessionKey
2765
+ }) : paramsLocal.sessionKey;
2693
2766
  return {
2694
2767
  sessionKey: inboundLastRouteSessionKey,
2695
2768
  channel: "feishu",
2696
2769
  to: feishuTo,
2697
- accountId: params.accountId,
2770
+ accountId: paramsLocal.accountId,
2698
2771
  ...lastRouteThreadId ? { threadId: lastRouteThreadId } : {},
2699
2772
  mainDmOwnerPin: !isGroup && inboundLastRouteSessionKey === route.mainSessionKey && pinnedMainDmOwner ? {
2700
2773
  ownerRecipient: pinnedMainDmOwner,
@@ -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({
@@ -4722,7 +4807,7 @@ function resolveFeishuDebounceMentions(params) {
4722
4807
  const botMentions = merged.filter((mention) => mention.id.open_id?.trim() === normalizedBotOpenId);
4723
4808
  return botMentions.length > 0 ? botMentions : void 0;
4724
4809
  }
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"}` }) {
4810
+ 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"}` }) {
4726
4811
  const inboundDebounceMs = channelRuntime.debounce.resolveInboundDebounceMs({
4727
4812
  cfg,
4728
4813
  channel: "feishu"
@@ -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-TXwK9jIG.js";
1
+ import { i as feishuSetupAdapter, n as feishuSetupWizard, t as feishuPlugin } from "./channel-B3ngoThy.js";
2
2
  export { feishuPlugin, feishuSetupAdapter, feishuSetupWizard };
@@ -1,7 +1,7 @@
1
1
  import { t as __exportAll } from "./rolldown-runtime-8H4AJuhK.js";
2
2
  import { r as normalizeFeishuTarget } from "./targets-BUjQ1TcA.js";
3
3
  import { r as parseFeishuConversationId, t as buildFeishuConversationId } from "./conversation-id-DuL575sn.js";
4
- import { n as getFeishuThreadBindingManager } from "./thread-bindings-C58Uq5Y1.js";
4
+ import { n as getFeishuThreadBindingManager } from "./thread-bindings-V0bwk0A1.js";
5
5
  import { normalizeOptionalLowercaseString, normalizeOptionalString } from "openclaw/plugin-sdk/string-coerce-runtime";
6
6
  //#region extensions/feishu/src/subagent-hooks.ts
7
7
  var subagent_hooks_exports = /* @__PURE__ */ __exportAll({
@@ -1,7 +1,7 @@
1
1
  //#region extensions/feishu/subagent-hooks-api.ts
2
2
  let feishuSubagentHooksPromise = null;
3
3
  function loadFeishuSubagentHooksModule() {
4
- feishuSubagentHooksPromise ??= import("./subagent-hooks-Bw3Dg2mS.js").then((n) => n.i);
4
+ feishuSubagentHooksPromise ??= import("./subagent-hooks-1pqt5tAA.js").then((n) => n.i);
5
5
  return feishuSubagentHooksPromise;
6
6
  }
7
7
  function registerFeishuSubagentHooks(api) {
@@ -81,7 +81,7 @@ function createFeishuThreadBindingManager(params) {
81
81
  const normalizedConversationId = conversationId.trim();
82
82
  const normalizedTargetSessionKey = targetSessionKey.trim();
83
83
  if (!normalizedConversationId || !normalizedTargetSessionKey) return null;
84
- const existing = getState().bindingsByAccountConversation.get(resolveBindingKey({
84
+ const existingLocal = getState().bindingsByAccountConversation.get(resolveBindingKey({
85
85
  accountId,
86
86
  conversationId: normalizedConversationId
87
87
  }));
@@ -89,14 +89,14 @@ function createFeishuThreadBindingManager(params) {
89
89
  const record = {
90
90
  accountId,
91
91
  conversationId: normalizedConversationId,
92
- parentConversationId: normalizeOptionalString(parentConversationId) ?? existing?.parentConversationId,
93
- deliveryTo: typeof metadata?.deliveryTo === "string" && metadata.deliveryTo.trim() ? metadata.deliveryTo.trim() : existing?.deliveryTo,
94
- deliveryThreadId: typeof metadata?.deliveryThreadId === "string" && metadata.deliveryThreadId.trim() ? metadata.deliveryThreadId.trim() : existing?.deliveryThreadId,
92
+ parentConversationId: normalizeOptionalString(parentConversationId) ?? existingLocal?.parentConversationId,
93
+ deliveryTo: typeof metadata?.deliveryTo === "string" && metadata.deliveryTo.trim() ? metadata.deliveryTo.trim() : existingLocal?.deliveryTo,
94
+ deliveryThreadId: typeof metadata?.deliveryThreadId === "string" && metadata.deliveryThreadId.trim() ? metadata.deliveryThreadId.trim() : existingLocal?.deliveryThreadId,
95
95
  targetKind: toFeishuTargetKind(targetKind),
96
96
  targetSessionKey: normalizedTargetSessionKey,
97
- agentId: typeof metadata?.agentId === "string" && metadata.agentId.trim() ? metadata.agentId.trim() : existing?.agentId ?? resolveAgentIdFromSessionKey(normalizedTargetSessionKey),
98
- label: typeof metadata?.label === "string" && metadata.label.trim() ? metadata.label.trim() : existing?.label,
99
- boundBy: typeof metadata?.boundBy === "string" && metadata.boundBy.trim() ? metadata.boundBy.trim() : existing?.boundBy,
97
+ agentId: typeof metadata?.agentId === "string" && metadata.agentId.trim() ? metadata.agentId.trim() : existingLocal?.agentId ?? resolveAgentIdFromSessionKey(normalizedTargetSessionKey),
98
+ label: typeof metadata?.label === "string" && metadata.label.trim() ? metadata.label.trim() : existingLocal?.label,
99
+ boundBy: typeof metadata?.boundBy === "string" && metadata.boundBy.trim() ? metadata.boundBy.trim() : existingLocal?.boundBy,
100
100
  boundAt: now,
101
101
  lastActivityAt: now
102
102
  };
@@ -1,19 +1,19 @@
1
1
  {
2
2
  "name": "@openclaw/feishu",
3
- "version": "2026.5.31-beta.2",
3
+ "version": "2026.5.31-beta.4",
4
4
  "lockfileVersion": 3,
5
5
  "requires": true,
6
6
  "packages": {
7
7
  "": {
8
8
  "name": "@openclaw/feishu",
9
- "version": "2026.5.31-beta.2",
9
+ "version": "2026.5.31-beta.4",
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.2"
16
+ "openclaw": ">=2026.5.31-beta.4"
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.2",
3
+ "version": "2026.5.31-beta.4",
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.2"
16
+ "openclaw": ">=2026.5.31-beta.4"
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.2"
50
+ "pluginApi": ">=2026.5.31-beta.4"
51
51
  },
52
52
  "build": {
53
- "openclawVersion": "2026.5.31-beta.2"
53
+ "openclawVersion": "2026.5.31-beta.4"
54
54
  },
55
55
  "release": {
56
56
  "publishToClawHub": true,