@openclaw/feishu 2026.6.11 → 2026.7.1-beta.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/api.js CHANGED
@@ -1,5 +1,5 @@
1
1
  import { r as listEnabledFeishuAccounts } from "./accounts-Cfzht2Hc.js";
2
- import { a as setFeishuNamedAccountEnabled, i as feishuSetupAdapter, n as feishuSetupWizard, r as runFeishuLogin, t as feishuPlugin } from "./channel-C1MthXp0.js";
2
+ import { a as setFeishuNamedAccountEnabled, i as feishuSetupAdapter, n as feishuSetupWizard, r as runFeishuLogin, t as feishuPlugin } from "./channel-Bwm5rvHg.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-BwgAJuvo.js";
@@ -1,4 +1,5 @@
1
1
  import { finiteSecondsToTimerSafeMilliseconds } from "openclaw/plugin-sdk/number-runtime";
2
+ import { sleep } from "openclaw/plugin-sdk/runtime-env";
2
3
  import { fetchWithSsrFGuard } from "openclaw/plugin-sdk/ssrf-runtime";
3
4
  import { renderQrTerminal } from "openclaw/plugin-sdk/media-runtime";
4
5
  //#region extensions/feishu/src/app-registration.ts
@@ -135,7 +136,7 @@ async function pollAppRegistration(params) {
135
136
  * otherwise the pattern is corrupted and cannot be scanned.
136
137
  */
137
138
  async function printQrCode(url) {
138
- const output = await renderQrTerminal(url);
139
+ const output = await renderQrTerminal(url, { small: true });
139
140
  process.stdout.write(output.endsWith("\n") ? output : `${output}\n`);
140
141
  }
141
142
  /**
@@ -181,11 +182,6 @@ async function getAppOwnerOpenId(params) {
181
182
  return;
182
183
  }
183
184
  }
184
- function sleep(ms) {
185
- return new Promise((resolve) => {
186
- setTimeout(resolve, ms);
187
- });
188
- }
189
185
  function sleepRegistrationPollInterval(intervalSeconds) {
190
186
  return sleep(finiteSecondsToTimerSafeMilliseconds(intervalSeconds) ?? finiteSecondsToTimerSafeMilliseconds(DEFAULT_REGISTRATION_POLL_INTERVAL_SECONDS) ?? REQUEST_TIMEOUT_MS);
191
187
  }
@@ -1,25 +1,25 @@
1
- import { a as resolveDefaultFeishuAccountId, d as isRecord$2, i as listFeishuAccountIds, n as inspectFeishuCredentials, o as resolveFeishuAccount, r as listEnabledFeishuAccounts, s as resolveFeishuRuntimeAccount } from "./accounts-Cfzht2Hc.js";
1
+ import { a as resolveDefaultFeishuAccountId, d as isRecord$1, i as listFeishuAccountIds, n as inspectFeishuCredentials, o as resolveFeishuAccount, r as listEnabledFeishuAccounts, s as resolveFeishuRuntimeAccount } from "./accounts-Cfzht2Hc.js";
2
2
  import { n as looksLikeFeishuId, r as normalizeFeishuTarget, t as detectIdType } from "./targets-BUjQ1TcA.js";
3
3
  import { n as createFeishuSendReceipt, s as createFeishuCardInteractionEnvelope } from "./send-result-DSsIa4-p.js";
4
4
  import { a as parseFeishuTargetId, i as parseFeishuDirectConversationId, n as buildFeishuModelOverrideParentCandidates, r as parseFeishuConversationId, t as buildFeishuConversationId } from "./conversation-id-DuL575sn.js";
5
5
  import { t as messageActionTargetAliases } from "./security-audit-BIeA3W3Q.js";
6
- import { n as collectRuntimeConfigAssignments, r as secretTargetRegistryEntries } from "./secret-contract-ChjJKAJ9.js";
6
+ import { n as collectRuntimeConfigAssignments, r as secretTargetRegistryEntries } from "./secret-contract-DLaBl9m4.js";
7
7
  import { t as collectFeishuSecurityAuditFindings } from "./security-audit-shared-BIHeF-S_.js";
8
8
  import { t as resolveFeishuSessionConversation } from "./session-conversation-CZSMgac-.js";
9
+ import { createLazyRuntimeModule, createLazyRuntimeNamedExport } from "openclaw/plugin-sdk/lazy-runtime";
9
10
  import { describeAccountSnapshot } from "openclaw/plugin-sdk/account-helpers";
10
11
  import { formatAllowFromLowercase } from "openclaw/plugin-sdk/allow-from";
11
12
  import { adaptScopedAccountAccessor, createHybridChannelConfigAdapter } from "openclaw/plugin-sdk/channel-config-helpers";
12
13
  import { buildChannelOutboundSessionRoute, createChatChannelPlugin, stripChannelTargetPrefix } from "openclaw/plugin-sdk/channel-core";
13
- import { createRuntimeOutboundDelegates, defineChannelMessageAdapter } from "openclaw/plugin-sdk/channel-outbound";
14
+ import { createAccountStatusSink, createRuntimeOutboundDelegates, defineChannelMessageAdapter } from "openclaw/plugin-sdk/channel-outbound";
14
15
  import { createPairingPrefixStripper } from "openclaw/plugin-sdk/channel-pairing";
15
16
  import { createAllowlistProviderGroupPolicyWarningCollector, projectConfigAccountIdWarningCollector } from "openclaw/plugin-sdk/channel-policy";
16
17
  import { getSessionBindingService } from "openclaw/plugin-sdk/conversation-runtime";
17
18
  import { createChannelDirectoryAdapter, createRuntimeDirectoryLiveAdapter, listDirectoryGroupEntriesFromMapKeysAndAllowFrom, listDirectoryUserEntriesFromAllowFromAndMapKeys } from "openclaw/plugin-sdk/directory-runtime";
18
19
  import { normalizeMessagePresentation, renderMessagePresentationFallbackText } from "openclaw/plugin-sdk/interactive-runtime";
19
- import { createLazyRuntimeNamedExport } from "openclaw/plugin-sdk/lazy-runtime";
20
20
  import { parseStrictPositiveInteger } from "openclaw/plugin-sdk/number-runtime";
21
21
  import { buildProbeChannelStatusSummary, createComputedAccountStatusAdapter, createDefaultChannelRuntimeState } from "openclaw/plugin-sdk/status-helpers";
22
- import { normalizeLowercaseStringOrEmpty, normalizeOptionalLowercaseString, normalizeOptionalString } from "openclaw/plugin-sdk/string-coerce-runtime";
22
+ import { isRecord, normalizeLowercaseStringOrEmpty, normalizeOptionalLowercaseString, normalizeOptionalString } from "openclaw/plugin-sdk/string-coerce-runtime";
23
23
  import { DEFAULT_ACCOUNT_ID as DEFAULT_ACCOUNT_ID$2, normalizeAccountId, resolveMergedAccountConfig } from "openclaw/plugin-sdk/account-resolution";
24
24
  import { createResolvedApproverActionAuthAdapter, resolveApprovalApprovers } from "openclaw/plugin-sdk/approval-auth-runtime";
25
25
  import { createActionGate } from "openclaw/plugin-sdk/channel-actions";
@@ -383,11 +383,8 @@ const SESSION_FILE_INSPECTION_MAX_BYTES = 16 * 1024 * 1024;
383
383
  function timestampForPath(now = /* @__PURE__ */ new Date()) {
384
384
  return now.toISOString().replaceAll(":", "-");
385
385
  }
386
- function isRecord$1(value) {
387
- return Boolean(value && typeof value === "object" && !Array.isArray(value));
388
- }
389
386
  function toFeishuSessionEntry(value) {
390
- if (!isRecord$1(value)) return {};
387
+ if (!isRecord(value)) return {};
391
388
  return {
392
389
  sessionId: value.sessionId,
393
390
  sessionFile: value.sessionFile
@@ -450,12 +447,12 @@ function normalizeMetadataString(value) {
450
447
  function isFeishuSessionEntry(key, value) {
451
448
  if (isFeishuAcpBindingSessionKey(key)) return false;
452
449
  if (isFeishuSessionStoreKey(key)) return true;
453
- if (!isRecord$1(value)) return false;
450
+ if (!isRecord(value)) return false;
454
451
  if (normalizeMetadataString(value.channel) === "feishu" || normalizeMetadataString(value.lastChannel) === "feishu") return true;
455
- if (normalizeMetadataString((isRecord$1(value.route) ? value.route : null)?.channel) === "feishu") return true;
456
- if (normalizeMetadataString((isRecord$1(value.deliveryContext) ? value.deliveryContext : null)?.channel) === "feishu") return true;
457
- if (normalizeMetadataString((isRecord$1(value.pendingFinalDeliveryContext) ? value.pendingFinalDeliveryContext : null)?.channel) === "feishu") return true;
458
- const origin = isRecord$1(value.origin) ? value.origin : null;
452
+ if (normalizeMetadataString((isRecord(value.route) ? value.route : null)?.channel) === "feishu") return true;
453
+ if (normalizeMetadataString((isRecord(value.deliveryContext) ? value.deliveryContext : null)?.channel) === "feishu") return true;
454
+ if (normalizeMetadataString((isRecord(value.pendingFinalDeliveryContext) ? value.pendingFinalDeliveryContext : null)?.channel) === "feishu") return true;
455
+ const origin = isRecord(value.origin) ? value.origin : null;
459
456
  const originProvider = normalizeMetadataString(origin?.provider);
460
457
  const originSurface = normalizeMetadataString(origin?.surface);
461
458
  const originFrom = normalizeMetadataString(origin?.from);
@@ -547,17 +544,17 @@ function resolveSessionTranscriptCandidates(params) {
547
544
  return [...candidates].toSorted();
548
545
  }
549
546
  function isSessionHeader(value) {
550
- return isRecord$1(value) && value.type === "session" && typeof value.id === "string";
547
+ return isRecord(value) && value.type === "session" && typeof value.id === "string";
551
548
  }
552
549
  function isBlankUserMessage(value) {
553
- if (!isRecord$1(value) || value.type !== "message" || !isRecord$1(value.message)) return false;
550
+ if (!isRecord(value) || value.type !== "message" || !isRecord(value.message)) return false;
554
551
  if (value.message.role !== "user") return false;
555
552
  const content = value.message.content;
556
553
  if (typeof content === "string") return content.trim().length === 0;
557
554
  return Array.isArray(content) && content.length === 0;
558
555
  }
559
556
  function isUserMessage(value) {
560
- return isRecord$1(value) && value.type === "message" && isRecord$1(value.message) && value.message.role === "user";
557
+ return isRecord(value) && value.type === "message" && isRecord(value.message) && value.message.role === "user";
561
558
  }
562
559
  function inspectSessionTranscript(params) {
563
560
  let stat;
@@ -1431,11 +1428,7 @@ function applyNewAppSecurityPolicy(cfg, accountId, openId, groupPolicy) {
1431
1428
  next = patchFeishuConfig(next, accountId, groupPatch);
1432
1429
  return next;
1433
1430
  }
1434
- let appRegistrationModulePromise = null;
1435
- const loadAppRegistrationModule = async () => {
1436
- appRegistrationModulePromise ??= import("./app-registration-tgQJUcv3.js");
1437
- return await appRegistrationModulePromise;
1438
- };
1431
+ const loadAppRegistrationModule = createLazyRuntimeModule(() => import("./app-registration-DuEyfVY1.js"));
1439
1432
  async function promptFeishuDomain(params) {
1440
1433
  return await params.prompter.select({
1441
1434
  message: t("wizard.feishu.domainPrompt"),
@@ -1659,7 +1652,7 @@ const feishuSetupWizard = {
1659
1652
  });
1660
1653
  let probeResult = null;
1661
1654
  if (configured && account.configured) try {
1662
- const { probeFeishu } = await import("./probe-EyeJQb3C.js").then((n) => n.n);
1655
+ const { probeFeishu } = await import("./probe-Cv2M2Ivr.js").then((n) => n.n);
1663
1656
  probeResult = await probeFeishu(account);
1664
1657
  } catch {}
1665
1658
  if (!configured) return [formatFeishuStatusLine("needs-credentials")];
@@ -1715,13 +1708,13 @@ function readBooleanParam(params, keys) {
1715
1708
  }
1716
1709
  }
1717
1710
  function hasLegacyFeishuCardCommandValue(actionValue) {
1718
- return isRecord$2(actionValue) && actionValue.oc !== "ocf1" && (Boolean(typeof actionValue.command === "string" && actionValue.command.trim()) || Boolean(typeof actionValue.text === "string" && actionValue.text.trim()));
1711
+ return isRecord$1(actionValue) && actionValue.oc !== "ocf1" && (Boolean(typeof actionValue.command === "string" && actionValue.command.trim()) || Boolean(typeof actionValue.text === "string" && actionValue.text.trim()));
1719
1712
  }
1720
1713
  function containsLegacyFeishuCardCommandValue(node) {
1721
1714
  if (Array.isArray(node)) return node.some((item) => containsLegacyFeishuCardCommandValue(item));
1722
- if (!isRecord$2(node)) return false;
1715
+ if (!isRecord$1(node)) return false;
1723
1716
  if (node.tag === "button" && hasLegacyFeishuCardCommandValue(node.value)) return true;
1724
- if (node.tag === "button" && Array.isArray(node.behaviors) && node.behaviors.some((behavior) => isRecord$2(behavior) && hasLegacyFeishuCardCommandValue(behavior.value))) return true;
1717
+ if (node.tag === "button" && Array.isArray(node.behaviors) && node.behaviors.some((behavior) => isRecord$1(behavior) && hasLegacyFeishuCardCommandValue(behavior.value))) return true;
1725
1718
  return Object.values(node).some((value) => containsLegacyFeishuCardCommandValue(value));
1726
1719
  }
1727
1720
  const meta = {
@@ -1735,7 +1728,7 @@ const meta = {
1735
1728
  order: 70,
1736
1729
  preferSessionLookupForAnnounceTarget: true
1737
1730
  };
1738
- const loadFeishuChannelRuntime = createLazyRuntimeNamedExport(() => import("./channel.runtime-CiQqumBC.js"), "feishuChannelRuntime");
1731
+ const loadFeishuChannelRuntime = createLazyRuntimeNamedExport(() => import("./channel.runtime-C6SITclp.js"), "feishuChannelRuntime");
1739
1732
  function toFeishuMessageSendResult(result, kind) {
1740
1733
  const receipt = result.receipt ?? createFeishuSendReceipt({
1741
1734
  messageId: result.messageId,
@@ -2551,7 +2544,7 @@ const feishuPlugin = createChatChannelPlugin({
2551
2544
  })
2552
2545
  }),
2553
2546
  gateway: { startAccount: async (ctx) => {
2554
- const { monitorFeishuProvider } = await import("./monitor-CxUQtU--.js");
2547
+ const { monitorFeishuProvider } = await import("./monitor-Ckch4qvr.js");
2555
2548
  const account = resolveFeishuRuntimeAccount({
2556
2549
  cfg: ctx.cfg,
2557
2550
  accountId: ctx.accountId
@@ -2562,12 +2555,17 @@ const feishuPlugin = createChatChannelPlugin({
2562
2555
  port
2563
2556
  });
2564
2557
  ctx.log?.info(`starting feishu[${ctx.accountId}] (mode: ${account.config?.connectionMode ?? "websocket"})`);
2558
+ const statusSink = createAccountStatusSink({
2559
+ accountId: ctx.accountId,
2560
+ setStatus: ctx.setStatus
2561
+ });
2565
2562
  return monitorFeishuProvider({
2566
2563
  config: ctx.cfg,
2567
2564
  runtime: ctx.runtime,
2568
2565
  channelRuntime: ctx.channelRuntime,
2569
2566
  abortSignal: ctx.abortSignal,
2570
- accountId: ctx.accountId
2567
+ accountId: ctx.accountId,
2568
+ statusSink
2571
2569
  });
2572
2570
  } },
2573
2571
  message: feishuMessageAdapter
@@ -1,2 +1,2 @@
1
- import { t as feishuPlugin } from "./channel-C1MthXp0.js";
1
+ import { t as feishuPlugin } from "./channel-Bwm5rvHg.js";
2
2
  export { feishuPlugin };
@@ -1,10 +1,10 @@
1
1
  import { o as resolveFeishuAccount, s as resolveFeishuRuntimeAccount, v as parseFeishuCommentTarget } from "./accounts-Cfzht2Hc.js";
2
- import { h as listFeishuDirectoryPeers, m as listFeishuDirectoryGroups, o as buildFeishuPresentationCardElements } from "./channel-C1MthXp0.js";
2
+ import { h as listFeishuDirectoryPeers, m as listFeishuDirectoryGroups, o as buildFeishuPresentationCardElements } from "./channel-Bwm5rvHg.js";
3
3
  import { r as createFeishuClient } from "./client-BwgAJuvo.js";
4
4
  import { c as getChatInfo, l as getChatMembers, r as cleanupAmbientCommentTypingReaction, t as deliverCommentThreadText, u as getFeishuMemberInfo } from "./drive-djUl8IRC.js";
5
5
  import { chunkTextForOutbound } from "./runtime-api.js";
6
- import { a as sendCardFeishu, c as sendStructuredCardFeishu, g as shouldSuppressFeishuTextForVoiceMedia, h as sendMediaFeishu, i as resolveFeishuCardTemplate, n as getMessageFeishu, o as sendMarkdownCardFeishu, s as sendMessageFeishu, t as editMessageFeishu } from "./send-B0nDqief.js";
7
- import { t as probeFeishu } from "./probe-EyeJQb3C.js";
6
+ import { a as sendCardFeishu, c as sendStructuredCardFeishu, g as shouldSuppressFeishuTextForVoiceMedia, h as sendMediaFeishu, i as resolveFeishuCardTemplate, n as getMessageFeishu, o as sendMarkdownCardFeishu, s as sendMessageFeishu, t as editMessageFeishu, y as resolveFeishuIdentityHeaderTitle } from "./send-BJTjDWKi.js";
7
+ import { t as probeFeishu } from "./probe-Cv2M2Ivr.js";
8
8
  import { interactiveReplyToPresentation, normalizeInteractiveReply, normalizeMessagePresentation, renderMessagePresentationFallbackText, resolveInteractiveTextFallback } from "openclaw/plugin-sdk/interactive-runtime";
9
9
  import { isRecord, normalizeLowercaseStringOrEmpty, normalizeStringEntries } from "openclaw/plugin-sdk/string-coerce-runtime";
10
10
  import path from "node:path";
@@ -230,7 +230,7 @@ function buildFeishuPayloadCard(params) {
230
230
  presentation
231
231
  })
232
232
  }];
233
- const identityTitle = params.identity ? params.identity.emoji ? `${params.identity.emoji} ${params.identity.name ?? ""}`.trim() : params.identity.name ?? "" : "";
233
+ const identityTitle = resolveFeishuIdentityHeaderTitle(params.identity);
234
234
  const title = presentation?.title ?? identityTitle;
235
235
  const template = resolveFeishuCardTemplate(presentation?.tone === "danger" ? "red" : presentation?.tone === "warning" ? "orange" : presentation?.tone === "success" ? "green" : "blue");
236
236
  return markRenderedFeishuCard({
@@ -246,6 +246,9 @@ function buildFeishuPayloadCard(params) {
246
246
  body: { elements }
247
247
  });
248
248
  }
249
+ function hasVisibleFallbackCommand(blocks) {
250
+ return blocks?.some((block) => block.type === "buttons" && block.buttons.some((button) => !button.disabled && button.action?.type === "command" && !button.url && !button.webApp?.url && !button.web_app?.url)) ?? false;
251
+ }
249
252
  function renderFeishuPresentationPayload({ payload, presentation, ctx }) {
250
253
  const card = buildFeishuPayloadCard({
251
254
  payload,
@@ -254,6 +257,7 @@ function renderFeishuPresentationPayload({ payload, presentation, ctx }) {
254
257
  });
255
258
  if (!card) return null;
256
259
  const existingFeishuData = isRecord(payload.channelData?.feishu) ? payload.channelData.feishu : void 0;
260
+ const fallbackHasCommand = hasVisibleFallbackCommand(presentation?.blocks);
257
261
  return {
258
262
  ...payload,
259
263
  text: renderMessagePresentationFallbackText({
@@ -264,7 +268,8 @@ function renderFeishuPresentationPayload({ payload, presentation, ctx }) {
264
268
  ...payload.channelData,
265
269
  feishu: {
266
270
  ...existingFeishuData,
267
- card
271
+ card,
272
+ ...fallbackHasCommand ? { fallbackHasCommand: true } : {}
268
273
  }
269
274
  }
270
275
  };
@@ -385,26 +390,31 @@ const feishuOutbound = {
385
390
  replyToId: ctx.replyToId,
386
391
  threadId: ctx.threadId
387
392
  });
388
- if (parseFeishuCommentTarget(ctx.to)) return await sendTextMediaPayload({
389
- channel: "feishu",
390
- ctx: {
391
- ...ctx,
392
- payload: {
393
- ...ctx.payload,
394
- text: renderMessagePresentationFallbackText({
395
- text: ctx.payload.text,
396
- presentation: normalizeMessagePresentation(ctx.payload.presentation) ?? (() => {
397
- const interactive = normalizeInteractiveReply(ctx.payload.interactive);
398
- return interactive ? interactiveReplyToPresentation(interactive) : void 0;
399
- })()
400
- }),
401
- interactive: void 0,
402
- presentation: void 0,
403
- channelData: void 0
404
- }
405
- },
406
- adapter: feishuOutbound
407
- });
393
+ if (parseFeishuCommentTarget(ctx.to)) {
394
+ const normalizedPresentation = normalizeMessagePresentation(ctx.payload.presentation) ?? (() => {
395
+ const interactive = normalizeInteractiveReply(ctx.payload.interactive);
396
+ return interactive ? interactiveReplyToPresentation(interactive) : void 0;
397
+ })();
398
+ const presentationFallbackText = renderMessagePresentationFallbackText({
399
+ text: ctx.payload.text,
400
+ presentation: normalizedPresentation
401
+ });
402
+ const text = hasVisibleFallbackCommand(normalizedPresentation?.blocks) || isRecord(ctx.payload.channelData?.feishu) && ctx.payload.channelData.feishu.fallbackHasCommand === true ? `${presentationFallbackText}\n\n> Interactive buttons are unavailable in Feishu document comments. You can type the command shown above manually.` : presentationFallbackText;
403
+ return await sendTextMediaPayload({
404
+ channel: "feishu",
405
+ ctx: {
406
+ ...ctx,
407
+ payload: {
408
+ ...ctx.payload,
409
+ text,
410
+ interactive: void 0,
411
+ presentation: void 0,
412
+ channelData: void 0
413
+ }
414
+ },
415
+ adapter: feishuOutbound
416
+ });
417
+ }
408
418
  const mediaUrls = normalizeStringEntries(resolvePayloadMediaUrls(ctx.payload));
409
419
  return attachChannelToResult("feishu", await sendPayloadMediaSequenceAndFinalize({
410
420
  text: ctx.payload.text ?? "",
@@ -463,7 +473,7 @@ const feishuOutbound = {
463
473
  }).config?.renderMode ?? "auto";
464
474
  if (renderMode === "card" || renderMode === "auto" && shouldUseCard(text)) {
465
475
  const header = identity ? {
466
- title: identity.emoji ? `${identity.emoji} ${identity.name ?? ""}`.trim() : identity.name ?? "",
476
+ title: resolveFeishuIdentityHeaderTitle(identity),
467
477
  template: "blue"
468
478
  } : void 0;
469
479
  return await sendStructuredCardFeishu({
@@ -1,11 +1,8 @@
1
1
  import { r as listEnabledFeishuAccounts, s as resolveFeishuRuntimeAccount } from "./accounts-Cfzht2Hc.js";
2
- import { f as fetchBotIdentityForMonitor } from "./monitor.state-CGL1occz.js";
2
+ import { f as fetchBotIdentityForMonitor } from "./monitor.state-R4ZgNpac.js";
3
+ import { createLazyRuntimeModule } from "openclaw/plugin-sdk/lazy-runtime";
3
4
  //#region extensions/feishu/src/monitor.ts
4
- let monitorAccountRuntimePromise;
5
- async function loadMonitorAccountRuntime() {
6
- monitorAccountRuntimePromise ??= import("./monitor.account-C3SH96dD.js");
7
- return await monitorAccountRuntimePromise;
8
- }
5
+ const loadMonitorAccountRuntime = createLazyRuntimeModule(() => import("./monitor.account-GXGIJrgw.js"));
9
6
  async function monitorFeishuProvider(opts = {}) {
10
7
  const cfg = opts.config;
11
8
  if (!cfg) throw new Error("Config is required for Feishu monitor");
@@ -22,7 +19,8 @@ async function monitorFeishuProvider(opts = {}) {
22
19
  account,
23
20
  channelRuntime: opts.channelRuntime,
24
21
  runtime: opts.runtime,
25
- abortSignal: opts.abortSignal
22
+ abortSignal: opts.abortSignal,
23
+ ...opts.statusSink ? { statusSink: opts.statusSink } : {}
26
24
  });
27
25
  }
28
26
  const accounts = listEnabledFeishuAccounts(cfg);
@@ -53,7 +51,8 @@ async function monitorFeishuProvider(opts = {}) {
53
51
  kind: "prefetched",
54
52
  botOpenId,
55
53
  botName
56
- }
54
+ },
55
+ ...opts.statusSink ? { statusSink: opts.statusSink } : {}
57
56
  }));
58
57
  }
59
58
  await Promise.all(monitorPromises);
@@ -1,16 +1,16 @@
1
1
  import { _ as normalizeCommentFileType, c as encodeQuery, d as isRecord$1, f as normalizeString, g as buildFeishuCommentTarget, h as requestFeishuApi, l as extractReplyText, m as readString, o as resolveFeishuAccount, p as parseCommentContentElements, s as resolveFeishuRuntimeAccount } from "./accounts-Cfzht2Hc.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-C1MthXp0.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-Bwm5rvHg.js";
4
4
  import { c as decodeFeishuCardAction, o as buildFeishuCardActionTextFallback, s as createFeishuCardInteractionEnvelope } from "./send-result-DSsIa4-p.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-BwgAJuvo.js";
8
8
  import { c as getChatInfo, i as createCommentTypingReactionLifecycle, t as deliverCommentThreadText } from "./drive-djUl8IRC.js";
9
9
  import { t as createFeishuThreadBindingManager } from "./thread-bindings-V0bwk0A1.js";
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-B0nDqief.js";
12
- import { i as waitForAbortableDelay, r as raceWithTimeoutAndAbort } from "./probe-EyeJQb3C.js";
13
- import { a as clearFeishuBotIdentityState, c as httpServers, d as wsClients, f as fetchBotIdentityForMonitor, i as botOpenIds, l as recordWebhookStatus, n as FEISHU_WEBHOOK_MAX_BODY_BYTES, o as closeTrackedFeishuHttpServer, r as botNames, s as feishuWebhookRateLimiter, t as FEISHU_WEBHOOK_BODY_TIMEOUT_MS, u as setFeishuBotIdentityState } from "./monitor.state-CGL1occz.js";
10
+ import { createReplyPrefixContext, evaluateSupplementalContextVisibility, getSessionEntry, normalizeAgentId as normalizeAgentId$2, resolveChannelContextVisibilityMode } 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, v as resolveFeishuIdentityEmoji } from "./send-BJTjDWKi.js";
12
+ import { i as waitForAbortableDelay, r as raceWithTimeoutAndAbort } from "./probe-Cv2M2Ivr.js";
13
+ import { a as clearFeishuBotIdentityState, c as httpServers, d as wsClients, f as fetchBotIdentityForMonitor, i as botOpenIds, l as recordWebhookStatus, n as FEISHU_WEBHOOK_MAX_BODY_BYTES, o as closeTrackedFeishuHttpServer, r as botNames, s as feishuWebhookRateLimiter, t as FEISHU_WEBHOOK_BODY_TIMEOUT_MS, u as setFeishuBotIdentityState } from "./monitor.state-R4ZgNpac.js";
14
14
  import { createChannelMessageReplyPipeline, formatChannelProgressDraftLineForEntry, isChannelProgressDraftWorkToolName, resolveAgentOutboundIdentity } from "openclaw/plugin-sdk/channel-outbound";
15
15
  import { createChannelPairingController, createChannelPairingController as createChannelPairingController$1 } from "openclaw/plugin-sdk/channel-pairing";
16
16
  import { ensureConfiguredBindingRouteReady, resolveConfiguredBindingRoute, resolveRuntimeConversationBindingRoute } from "openclaw/plugin-sdk/conversation-runtime";
@@ -32,6 +32,7 @@ import { resolvePinnedMainDmOwnerFromAllowlist, safeEqualSecret } from "openclaw
32
32
  import { buildChannelInboundEventContext, toInboundMediaFacts } from "openclaw/plugin-sdk/channel-inbound";
33
33
  import { DEFAULT_GROUP_HISTORY_LIMIT, createChannelHistoryWindow } from "openclaw/plugin-sdk/reply-history";
34
34
  import { resolveDefaultGroupPolicy, resolveOpenProviderRuntimeGroupPolicy, warnMissingProviderGroupPolicyFallbackOnce } from "openclaw/plugin-sdk/runtime-group-policy";
35
+ import { sliceUtf16Safe, truncateUtf16Safe } from "openclaw/plugin-sdk/text-utility-runtime";
35
36
  import { resolveChannelConfigWrites } from "openclaw/plugin-sdk/channel-config-writes";
36
37
  import { formatReasoningMessage } from "openclaw/plugin-sdk/agent-runtime";
37
38
  import { logTypingFailure } from "openclaw/plugin-sdk/channel-feedback";
@@ -844,10 +845,11 @@ function resolveFeishuReasoningPreviewEnabled(params) {
844
845
  const configDefault = resolveFeishuConfigReasoningDefault(params.cfg, params.agentId);
845
846
  if (!params.sessionKey) return configDefault === "stream";
846
847
  try {
847
- const level = resolveSessionStoreEntry({
848
- store: loadSessionStore(params.storePath, { skipCache: true }),
849
- sessionKey: params.sessionKey
850
- }).existing?.reasoningLevel;
848
+ const level = getSessionEntry({
849
+ storePath: params.storePath,
850
+ sessionKey: params.sessionKey,
851
+ readConsistency: "latest"
852
+ })?.reasoningLevel;
851
853
  if (level === "on" || level === "stream" || level === "off") return level === "stream";
852
854
  } catch {
853
855
  return false;
@@ -919,7 +921,7 @@ async function getToken(creds) {
919
921
  function truncateSummary(text, max = 50) {
920
922
  if (!text) return "";
921
923
  const clean = text.replace(/\n/g, " ").trim();
922
- return clean.length <= max ? clean : clean.slice(0, max - 3) + "...";
924
+ return clean.length <= max ? clean : sliceUtf16Safe(clean, 0, max - 3) + "...";
923
925
  }
924
926
  function hasNaturalStreamingBoundary(text) {
925
927
  return /[\n。!?!?;;::]$/.test(text);
@@ -1148,7 +1150,7 @@ var FeishuStreamingSession = class {
1148
1150
  this.flushTimer = null;
1149
1151
  const pending = this.pendingText;
1150
1152
  if (!pending || this.closed) return;
1151
- this.update(pending);
1153
+ this.update(pending).catch((error) => this.log?.(`Scheduled flush update failed: ${String(error)}`));
1152
1154
  }, delayMs);
1153
1155
  }
1154
1156
  async update(text) {
@@ -1455,7 +1457,7 @@ function normalizeEpochMs(timestamp) {
1455
1457
  /** Build a card header from agent identity config. */
1456
1458
  function resolveCardHeader(agentId, identity) {
1457
1459
  const name = identity?.name?.trim() || (agentId === "main" ? "" : agentId);
1458
- const emoji = identity?.emoji?.trim();
1460
+ const emoji = resolveFeishuIdentityEmoji(identity?.emoji);
1459
1461
  const title = (emoji ? `${emoji} ${name}` : name).trim();
1460
1462
  if (!title) return;
1461
1463
  return {
@@ -1472,7 +1474,7 @@ function resolveCardNote(agentId, identity, prefixCtx) {
1472
1474
  }
1473
1475
  function createFeishuReplyDispatcher(params) {
1474
1476
  const core = getFeishuRuntime();
1475
- const { cfg, agentId, chatId, replyToMessageId, typingTargetMessageId: explicitTypingTargetMessageId, skipReplyToInMessages, replyInThread, threadReply, rootId, accountId, identity, mentionTargets } = params;
1477
+ const { cfg, agentId, chatId, sendTarget, replyToMessageId, typingTargetMessageId: explicitTypingTargetMessageId, skipReplyToInMessages, replyInThread, threadReply, rootId, accountId, identity, mentionTargets } = params;
1476
1478
  const sendReplyToMessageId = skipReplyToInMessages ? void 0 : replyToMessageId;
1477
1479
  const typingTargetMessageId = explicitTypingTargetMessageId?.trim() || replyToMessageId;
1478
1480
  const threadReplyMode = threadReply === true;
@@ -1549,6 +1551,7 @@ function createFeishuReplyDispatcher(params) {
1549
1551
  let lastSnapshotTextLength = 0;
1550
1552
  let hasStreamingFinalText = false;
1551
1553
  const deliveredFinalTexts = /* @__PURE__ */ new Set();
1554
+ let sentIndependentBlockText = false;
1552
1555
  let partialUpdateQueue = Promise.resolve();
1553
1556
  let streamingStartPromise = null;
1554
1557
  let streamingClosedForReply = false;
@@ -1611,8 +1614,9 @@ function createFeishuReplyDispatcher(params) {
1611
1614
  try {
1612
1615
  const cardHeader = resolveCardHeader(agentId, identity);
1613
1616
  const cardNote = resolveCardNote(agentId, identity, prefixContext.prefixContext);
1614
- await streaming.start(chatId, resolveReceiveIdType(chatId), {
1615
- replyToMessageId,
1617
+ const streamingTarget = sendTarget.replace(/^(feishu|lark):/i, "").replace(/^(chat|user|group|dm|open_id):/i, "").trim();
1618
+ await streaming.start(streamingTarget, resolveReceiveIdType(sendTarget), {
1619
+ replyToMessageId: sendReplyToMessageId,
1616
1620
  replyInThread: effectiveReplyInThread,
1617
1621
  rootId,
1618
1622
  header: cardHeader,
@@ -1694,7 +1698,7 @@ function createFeishuReplyDispatcher(params) {
1694
1698
  send: async ({ mediaUrl }) => {
1695
1699
  const result = await sendMediaFeishu({
1696
1700
  cfg,
1697
- to: chatId,
1701
+ to: sendTarget,
1698
1702
  mediaUrl,
1699
1703
  replyToMessageId: sendReplyToMessageId,
1700
1704
  replyInThread: effectiveReplyInThread,
@@ -1711,7 +1715,7 @@ function createFeishuReplyDispatcher(params) {
1711
1715
  sendChunk: async ({ chunk }) => {
1712
1716
  await sendMessageFeishu({
1713
1717
  cfg,
1714
- to: chatId,
1718
+ to: sendTarget,
1715
1719
  text: chunk,
1716
1720
  replyToMessageId: sendReplyToMessageId,
1717
1721
  replyInThread: effectiveReplyInThread,
@@ -1732,7 +1736,7 @@ function createFeishuReplyDispatcher(params) {
1732
1736
  sendChunk: async ({ chunk }) => {
1733
1737
  await sendMessageFeishu({
1734
1738
  cfg,
1735
- to: chatId,
1739
+ to: sendTarget,
1736
1740
  text: chunk,
1737
1741
  replyToMessageId: sendReplyToMessageId,
1738
1742
  replyInThread: effectiveReplyInThread,
@@ -1753,7 +1757,7 @@ function createFeishuReplyDispatcher(params) {
1753
1757
  }
1754
1758
  await sendMessageFeishu({
1755
1759
  cfg,
1756
- to: chatId,
1760
+ to: sendTarget,
1757
1761
  text: NO_VISIBLE_REPLY_FALLBACK_TEXT,
1758
1762
  replyToMessageId: sendReplyToMessageId,
1759
1763
  replyInThread: effectiveReplyInThread,
@@ -1789,6 +1793,7 @@ function createFeishuReplyDispatcher(params) {
1789
1793
  if (!replyLifecycleStateInitialized) {
1790
1794
  replyLifecycleStateInitialized = true;
1791
1795
  deliveredFinalTexts.clear();
1796
+ sentIndependentBlockText = false;
1792
1797
  streamingClosedForReply = false;
1793
1798
  streamingCloseErroredForReply = false;
1794
1799
  visibleReplySent = false;
@@ -1824,7 +1829,31 @@ function createFeishuReplyDispatcher(params) {
1824
1829
  if (shouldDiscardStreamingPreview) await discardStreamingPreview();
1825
1830
  if (shouldDeliverText) {
1826
1831
  if (info?.kind === "block") {
1827
- if (!useStreamingCard) return;
1832
+ if (!useStreamingCard) {
1833
+ if (coreBlockStreamingEnabled) {
1834
+ const isFirstBlock = !sentIndependentBlockText;
1835
+ await sendChunkedTextReply({
1836
+ text,
1837
+ useCard: false,
1838
+ infoKind: "block",
1839
+ sendChunk: async ({ chunk, isFirst }) => {
1840
+ await sendMessageFeishu({
1841
+ cfg,
1842
+ to: sendTarget,
1843
+ text: chunk,
1844
+ replyToMessageId: sendReplyToMessageId,
1845
+ replyInThread: effectiveReplyInThread,
1846
+ allowTopLevelReplyFallback,
1847
+ accountId,
1848
+ ...isFirstBlock && isFirst && mentionTargets?.length ? { mentions: mentionTargets } : {}
1849
+ });
1850
+ }
1851
+ });
1852
+ sentIndependentBlockText = true;
1853
+ if (hasMedia) await sendMediaReplies(payload);
1854
+ }
1855
+ return;
1856
+ }
1828
1857
  startStreaming();
1829
1858
  if (streamingStartPromise) await streamingStartPromise;
1830
1859
  }
@@ -1858,7 +1887,7 @@ function createFeishuReplyDispatcher(params) {
1858
1887
  sendChunk: async ({ chunk }) => {
1859
1888
  await sendStructuredCardFeishu({
1860
1889
  cfg,
1861
- to: chatId,
1890
+ to: sendTarget,
1862
1891
  text: chunk,
1863
1892
  replyToMessageId: sendReplyToMessageId,
1864
1893
  replyInThread: effectiveReplyInThread,
@@ -1876,7 +1905,7 @@ function createFeishuReplyDispatcher(params) {
1876
1905
  sendChunk: async ({ chunk, isFirst }) => {
1877
1906
  await sendMessageFeishu({
1878
1907
  cfg,
1879
- to: chatId,
1908
+ to: sendTarget,
1880
1909
  text: chunk,
1881
1910
  replyToMessageId: sendReplyToMessageId,
1882
1911
  replyInThread: effectiveReplyInThread,
@@ -2098,7 +2127,7 @@ function formatMentionNameForAgentContext(name) {
2098
2127
  const normalized = Array.from(name, (char) => {
2099
2128
  return char.charCodeAt(0) < 32 || char === "[" || char === "]" ? " " : char;
2100
2129
  }).join("").replace(/\s+/g, " ").trim();
2101
- const bounded = normalized.length > MAX_MENTION_CONTEXT_NAME_LENGTH ? `${normalized.slice(0, MAX_MENTION_CONTEXT_NAME_LENGTH - 3)}...` : normalized;
2130
+ const bounded = normalized.length > MAX_MENTION_CONTEXT_NAME_LENGTH ? `${truncateUtf16Safe(normalized, MAX_MENTION_CONTEXT_NAME_LENGTH - 3)}...` : normalized;
2102
2131
  return JSON.stringify(bounded || "unknown");
2103
2132
  }
2104
2133
  function buildFeishuAgentBody(params) {
@@ -2538,7 +2567,7 @@ async function handleFeishuMessage(params) {
2538
2567
  return;
2539
2568
  }
2540
2569
  }
2541
- const preview = ctx.content.replace(/\s+/g, " ").slice(0, 160);
2570
+ const preview = truncateUtf16Safe(ctx.content.replace(/\s+/g, " "), 160);
2542
2571
  const inboundLabel = isGroup ? `Feishu[${account.accountId}] message in group ${ctx.chatId}` : `Feishu[${account.accountId}] DM from ${ctx.senderOpenId}`;
2543
2572
  const contextVisibilityMode = resolveChannelContextVisibilityMode({
2544
2573
  cfg: effectiveCfg,
@@ -2576,7 +2605,7 @@ async function handleFeishuMessage(params) {
2576
2605
  senderType: quotedMessageInfo.senderType
2577
2606
  })) {
2578
2607
  quotedContent = quotedMessageInfo.content;
2579
- log(`feishu[${account.accountId}]: fetched quoted message: ${quotedContent?.slice(0, 100)}`);
2608
+ log(`feishu[${account.accountId}]: fetched quoted message: ${truncateUtf16Safe(quotedContent, 100)}`);
2580
2609
  } else if (quotedMessageInfo) log(`feishu[${account.accountId}]: skipped quoted message from sender ${quotedMessageInfo.senderId ?? "unknown"} (mode=${contextVisibilityMode})`);
2581
2610
  } catch (err) {
2582
2611
  log(`feishu[${account.accountId}]: failed to fetch quoted message: ${String(err)}`);
@@ -2822,7 +2851,8 @@ async function handleFeishuMessage(params) {
2822
2851
  access: {
2823
2852
  mentions: {
2824
2853
  canDetectMention: isGroup,
2825
- wasMentioned
2854
+ wasMentioned,
2855
+ requireMention
2826
2856
  },
2827
2857
  commands: { authorized: commandAuthorized }
2828
2858
  },
@@ -2906,6 +2936,7 @@ async function handleFeishuMessage(params) {
2906
2936
  agentId,
2907
2937
  runtime,
2908
2938
  chatId: ctx.chatId,
2939
+ sendTarget: feishuTo,
2909
2940
  allowReasoningPreview,
2910
2941
  replyToMessageId: replyTargetMessageId,
2911
2942
  typingTargetMessageId,
@@ -3049,6 +3080,7 @@ async function handleFeishuMessage(params) {
3049
3080
  agentId: route.agentId,
3050
3081
  runtime,
3051
3082
  chatId: ctx.chatId,
3083
+ sendTarget: feishuTo,
3052
3084
  allowReasoningPreview,
3053
3085
  replyToMessageId: replyTargetMessageId,
3054
3086
  typingTargetMessageId,
@@ -3901,7 +3933,7 @@ function safeJsonStringify(value) {
3901
3933
  function truncatePromptText(text, maxLength = FEISHU_PROMPT_TEXT_LIMIT) {
3902
3934
  const normalized = normalizeString(text);
3903
3935
  if (!normalized) return "";
3904
- return normalized.length > maxLength ? `${normalized.slice(0, maxLength - 1)}…` : normalized;
3936
+ return normalized.length > maxLength ? `${sliceUtf16Safe(normalized, 0, maxLength - 1)}…` : normalized;
3905
3937
  }
3906
3938
  function formatPromptTextValue(text) {
3907
3939
  return safeJsonStringify(truncatePromptText(text) || "");
@@ -4474,7 +4506,7 @@ async function resolveDriveCommentEventTurn(params) {
4474
4506
  nearestBotWholeCommentAfter: resolved.context.nearestBotWholeCommentAfter,
4475
4507
  nearestBotWholeCommentBefore: resolved.context.nearestBotWholeCommentBefore
4476
4508
  });
4477
- const preview = prompt.replace(/\s+/g, " ").slice(0, 160);
4509
+ const preview = truncateUtf16Safe(prompt.replace(/\s+/g, " "), 160);
4478
4510
  return {
4479
4511
  eventId: resolved.eventId,
4480
4512
  messageId: `drive-comment:${resolved.eventId}`,
@@ -4933,7 +4965,7 @@ function resolveFeishuDebounceMentions(params) {
4933
4965
  const botMentions = merged.filter((mention) => mention.id.open_id?.trim() === normalizedBotOpenId);
4934
4966
  return botMentions.length > 0 ? botMentions : void 0;
4935
4967
  }
4936
- 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"}` }) {
4968
+ function createFeishuMessageReceiveHandler({ cfg, channelRuntime, accountId, runtime, chatHistories, fireAndForget, handleMessage, resolveDebounceText: resolveText, hasProcessedMessage, recordProcessedMessage, getBotOpenId = () => void 0, getBotName = () => void 0, resolveSequentialKey = ({ accountId: accountIdLocal, event }) => `feishu:${accountIdLocal}:${event.message.chat_id?.trim() || "unknown"}`, statusSink }) {
4937
4969
  const inboundDebounceMs = channelRuntime.debounce.resolveInboundDebounceMs({
4938
4970
  cfg,
4939
4971
  channel: "feishu"
@@ -5034,6 +5066,7 @@ function createFeishuMessageReceiveHandler({ cfg, channelRuntime, accountId, run
5034
5066
  }
5035
5067
  });
5036
5068
  return async (data) => {
5069
+ statusSink?.({ lastEventAt: Date.now() });
5037
5070
  const event = parseFeishuMessageEventPayload(data);
5038
5071
  if (!event) {
5039
5072
  error(`feishu[${accountId}]: ignoring malformed message event payload`);
@@ -5161,7 +5194,7 @@ function waitForFeishuWsCycleEnd(params) {
5161
5194
  params.terminalError.then(finish);
5162
5195
  });
5163
5196
  }
5164
- async function monitorWebSocket({ account, accountId, runtime, abortSignal, eventDispatcher }) {
5197
+ async function monitorWebSocket({ account, accountId, runtime, abortSignal, eventDispatcher, statusSink }) {
5165
5198
  const log = runtime?.log ?? console.log;
5166
5199
  const error = runtime?.error ?? console.error;
5167
5200
  let attempt = 0;
@@ -5180,8 +5213,28 @@ async function monitorWebSocket({ account, accountId, runtime, abortSignal, even
5180
5213
  }
5181
5214
  error(`feishu[${accountId}]: WebSocket SDK reported recoverable error: ${formatFeishuWsErrorForLog(err)}`);
5182
5215
  };
5216
+ const publishWsConnected = () => {
5217
+ const connectedAt = Date.now();
5218
+ statusSink?.({
5219
+ connected: true,
5220
+ lastConnectedAt: connectedAt,
5221
+ lastEventAt: connectedAt,
5222
+ lastError: null
5223
+ });
5224
+ };
5225
+ const publishWsReconnecting = () => {
5226
+ statusSink?.({
5227
+ connected: false,
5228
+ lastEventAt: Date.now()
5229
+ });
5230
+ };
5183
5231
  log(`feishu[${accountId}]: starting WebSocket connection...`);
5184
- wsClient = await createFeishuWSClient(account, { onError: handleWsError });
5232
+ wsClient = await createFeishuWSClient(account, {
5233
+ onError: handleWsError,
5234
+ onReady: publishWsConnected,
5235
+ onReconnected: publishWsConnected,
5236
+ onReconnecting: publishWsReconnecting
5237
+ });
5185
5238
  if (abortSignal?.aborted) {
5186
5239
  cleanupFeishuWsClient({
5187
5240
  accountId,
@@ -5216,6 +5269,10 @@ async function monitorWebSocket({ account, accountId, runtime, abortSignal, even
5216
5269
  clearIdentity: false
5217
5270
  });
5218
5271
  if (abortSignal?.aborted) break;
5272
+ statusSink?.({
5273
+ connected: false,
5274
+ lastEventAt: Date.now()
5275
+ });
5219
5276
  attempt += 1;
5220
5277
  const delayMs = getFeishuWsReconnectDelayMs(attempt);
5221
5278
  error(`feishu[${accountId}]: WebSocket connection ended, recreating client in ${delayMs}ms: ${formatFeishuWsErrorForLog(cycleEnd)}`);
@@ -5228,6 +5285,10 @@ async function monitorWebSocket({ account, accountId, runtime, abortSignal, even
5228
5285
  clearIdentity: false
5229
5286
  });
5230
5287
  if (abortSignal?.aborted) break;
5288
+ statusSink?.({
5289
+ connected: false,
5290
+ lastEventAt: Date.now()
5291
+ });
5231
5292
  attempt += 1;
5232
5293
  const delayMs = getFeishuWsReconnectDelayMs(attempt);
5233
5294
  error(`feishu[${accountId}]: WebSocket start failed, retrying in ${delayMs}ms: ${formatFeishuWsErrorForLog(err)}`);
@@ -5241,7 +5302,7 @@ async function monitorWebSocket({ account, accountId, runtime, abortSignal, even
5241
5302
  clearIdentity: true
5242
5303
  });
5243
5304
  }
5244
- async function monitorWebhook({ account, accountId, runtime, abortSignal, eventDispatcher }) {
5305
+ async function monitorWebhook({ account, accountId, runtime, abortSignal, eventDispatcher, statusSink }) {
5245
5306
  const log = runtime?.log ?? console.log;
5246
5307
  const error = runtime?.error ?? console.error;
5247
5308
  const encryptKey = account.encryptKey?.trim();
@@ -5254,6 +5315,13 @@ async function monitorWebhook({ account, accountId, runtime, abortSignal, eventD
5254
5315
  server.on("request", (req, res) => {
5255
5316
  res.on("finish", () => {
5256
5317
  recordWebhookStatus(runtime, accountId, path, res.statusCode);
5318
+ if (res.statusCode >= 200 && res.statusCode < 300) {
5319
+ const inboundAt = Date.now();
5320
+ statusSink?.({
5321
+ lastEventAt: inboundAt,
5322
+ lastTransportActivityAt: inboundAt
5323
+ });
5324
+ }
5257
5325
  });
5258
5326
  if (!applyBasicWebhookRequestGuards({
5259
5327
  req,
@@ -5338,6 +5406,13 @@ async function monitorWebhook({ account, accountId, runtime, abortSignal, eventD
5338
5406
  abortSignal?.addEventListener("abort", handleAbort, { once: true });
5339
5407
  server.listen(port, host, () => {
5340
5408
  log(`feishu[${accountId}]: Webhook server listening on ${host}:${port}`);
5409
+ const webhookConnectedAt = Date.now();
5410
+ statusSink?.({
5411
+ connected: true,
5412
+ lastConnectedAt: webhookConnectedAt,
5413
+ lastEventAt: webhookConnectedAt,
5414
+ lastError: null
5415
+ });
5341
5416
  });
5342
5417
  server.on("error", (err) => {
5343
5418
  error(`feishu[${accountId}]: Webhook server error: ${err}`);
@@ -5505,7 +5580,8 @@ function registerEventHandlers(eventDispatcher, context) {
5505
5580
  recordProcessedMessage: recordProcessedFeishuMessage,
5506
5581
  getBotOpenId: (id) => botOpenIds.get(id),
5507
5582
  getBotName: (id) => botNames.get(id),
5508
- resolveSequentialKey: getFeishuSequentialKey
5583
+ resolveSequentialKey: getFeishuSequentialKey,
5584
+ ...context.statusSink ? { statusSink: context.statusSink } : {}
5509
5585
  }),
5510
5586
  "im.message.message_read_v1": async () => {},
5511
5587
  "im.chat.access_event.bot_p2p_chat_entered_v1": async () => {},
@@ -5656,24 +5732,27 @@ async function monitorSingleAccount(params) {
5656
5732
  registerEventHandlers(eventDispatcher, {
5657
5733
  cfg,
5658
5734
  accountId,
5659
- channelRuntime: params.channelRuntime ?? getFeishuRuntime().channel,
5735
+ channelRuntime: params.channelRuntime?.inbound ? params.channelRuntime : getFeishuRuntime().channel,
5660
5736
  runtime,
5661
5737
  chatHistories,
5662
- fireAndForget: params.fireAndForget ?? true
5738
+ fireAndForget: params.fireAndForget ?? true,
5739
+ ...params.statusSink ? { statusSink: params.statusSink } : {}
5663
5740
  });
5664
5741
  if (connectionMode === "webhook") return await monitorWebhook({
5665
5742
  account,
5666
5743
  accountId,
5667
5744
  runtime,
5668
5745
  abortSignal,
5669
- eventDispatcher
5746
+ eventDispatcher,
5747
+ ...params.statusSink ? { statusSink: params.statusSink } : {}
5670
5748
  });
5671
5749
  return await monitorWebSocket({
5672
5750
  account,
5673
5751
  accountId,
5674
5752
  runtime,
5675
5753
  abortSignal,
5676
- eventDispatcher
5754
+ eventDispatcher,
5755
+ ...params.statusSink ? { statusSink: params.statusSink } : {}
5677
5756
  });
5678
5757
  } finally {
5679
5758
  threadBindingManager?.stop();
@@ -1,4 +1,4 @@
1
- import { t as probeFeishu } from "./probe-EyeJQb3C.js";
1
+ import { t as probeFeishu } from "./probe-Cv2M2Ivr.js";
2
2
  import { parseStrictPositiveInteger } from "openclaw/plugin-sdk/number-runtime";
3
3
  import { normalizeLowercaseStringOrEmpty } from "openclaw/plugin-sdk/string-coerce-runtime";
4
4
  import { WEBHOOK_ANOMALY_COUNTER_DEFAULTS, WEBHOOK_RATE_LIMIT_DEFAULTS, createFixedWindowRateLimiter, createWebhookAnomalyTracker } from "openclaw/plugin-sdk/webhook-ingress";
@@ -41,6 +41,7 @@ function waitForAbortableDelay(delayMs, abortSignal) {
41
41
  if (abortSignal?.aborted) return Promise.resolve(false);
42
42
  return new Promise((resolve) => {
43
43
  let settled = false;
44
+ let timer = void 0;
44
45
  const finish = (value) => {
45
46
  if (settled) return;
46
47
  settled = true;
@@ -56,7 +57,7 @@ function waitForAbortableDelay(delayMs, abortSignal) {
56
57
  finish(false);
57
58
  return;
58
59
  }
59
- const timer = setTimeout(() => finish(true), resolveTimerTimeoutMs(delayMs, 1));
60
+ timer = setTimeout(() => finish(true), resolveTimerTimeoutMs(delayMs, 1));
60
61
  timer.unref?.();
61
62
  });
62
63
  }
@@ -4,10 +4,10 @@ import { createChannelPairingController } from "openclaw/plugin-sdk/channel-pair
4
4
  import { PAIRING_APPROVED_MESSAGE, buildProbeChannelStatusSummary, createDefaultChannelRuntimeState } from "openclaw/plugin-sdk/channel-status";
5
5
  import { chunkTextForOutbound } from "openclaw/plugin-sdk/text-chunking";
6
6
  import { normalizeAgentId } from "openclaw/plugin-sdk/routing";
7
- import { loadSessionStore, resolveSessionStoreEntry } from "openclaw/plugin-sdk/session-store-runtime";
7
+ import { getSessionEntry } from "openclaw/plugin-sdk/session-store-runtime";
8
8
  import { DEFAULT_ACCOUNT_ID, buildChannelConfigSchema, createActionGate, createDedupeCache } from "openclaw/plugin-sdk/core";
9
9
  import { buildAgentMediaPayload } from "openclaw/plugin-sdk/agent-media-payload";
10
10
  import { evaluateSupplementalContextVisibility, filterSupplementalContextItems, resolveChannelContextVisibilityMode } from "openclaw/plugin-sdk/context-visibility-runtime";
11
11
  import { readJsonFileWithFallback } from "openclaw/plugin-sdk/json-store";
12
12
  import { isRequestBodyLimitError, readRequestBodyWithLimit, requestBodyErrorToText } from "openclaw/plugin-sdk/webhook-ingress";
13
- export { DEFAULT_ACCOUNT_ID, PAIRING_APPROVED_MESSAGE, buildAgentMediaPayload, buildChannelConfigSchema, buildProbeChannelStatusSummary, chunkTextForOutbound, createActionGate, createChannelPairingController, createDedupeCache, createDefaultChannelRuntimeState, createReplyPrefixContext, evaluateSupplementalContextVisibility, filterSupplementalContextItems, isRequestBodyLimitError, loadSessionStore, normalizeAgentId, readJsonFileWithFallback, readRequestBodyWithLimit, requestBodyErrorToText, resolveChannelContextVisibilityMode, resolveSessionStoreEntry, setFeishuRuntime };
13
+ export { DEFAULT_ACCOUNT_ID, PAIRING_APPROVED_MESSAGE, buildAgentMediaPayload, buildChannelConfigSchema, buildProbeChannelStatusSummary, chunkTextForOutbound, createActionGate, createChannelPairingController, createDedupeCache, createDefaultChannelRuntimeState, createReplyPrefixContext, evaluateSupplementalContextVisibility, filterSupplementalContextItems, getSessionEntry, isRequestBodyLimitError, normalizeAgentId, readJsonFileWithFallback, readRequestBodyWithLimit, requestBodyErrorToText, resolveChannelContextVisibilityMode, setFeishuRuntime };
@@ -1,4 +1,4 @@
1
- import { collectConditionalChannelFieldAssignments, collectSimpleChannelFieldAssignments, getChannelSurface, hasOwnProperty, normalizeSecretStringValue } from "openclaw/plugin-sdk/channel-secret-basic-runtime";
1
+ import { collectConditionalChannelFieldAssignments, collectSecretInputAssignment, getChannelSurface, hasConfiguredSecretInputValue, hasOwnProperty, isBaseFieldActiveForChannelSurface, normalizeSecretStringValue } from "openclaw/plugin-sdk/channel-secret-basic-runtime";
2
2
  //#region extensions/feishu/src/secret-contract.ts
3
3
  const secretTargetRegistryEntries = [
4
4
  {
@@ -72,16 +72,34 @@ function collectRuntimeConfigAssignments(params) {
72
72
  const resolved = getChannelSurface(params.config, "feishu");
73
73
  if (!resolved) return;
74
74
  const { channel: feishu, surface } = resolved;
75
- collectSimpleChannelFieldAssignments({
76
- channelKey: "feishu",
77
- field: "appSecret",
78
- channel: feishu,
79
- surface,
75
+ const topLevelAppSecretActive = surface.channelEnabled && hasConfiguredSecretInputValue(feishu.appId, params.defaults) && hasConfiguredSecretInputValue(feishu.appSecret, params.defaults) || isBaseFieldActiveForChannelSurface(surface, "appSecret");
76
+ collectSecretInputAssignment({
77
+ value: feishu.appSecret,
78
+ path: "channels.feishu.appSecret",
79
+ expected: "string",
80
80
  defaults: params.defaults,
81
81
  context: params.context,
82
- topInactiveReason: "no enabled account inherits this top-level Feishu appSecret.",
83
- accountInactiveReason: "Feishu account is disabled."
82
+ active: topLevelAppSecretActive,
83
+ inactiveReason: "no enabled account inherits this top-level Feishu appSecret.",
84
+ apply: (value) => {
85
+ feishu.appSecret = value;
86
+ }
84
87
  });
88
+ if (surface.hasExplicitAccounts) for (const { accountId, account, enabled } of surface.accounts) {
89
+ if (!hasOwnProperty(account, "appSecret")) continue;
90
+ collectSecretInputAssignment({
91
+ value: account.appSecret,
92
+ path: `channels.feishu.accounts.${accountId}.appSecret`,
93
+ expected: "string",
94
+ defaults: params.defaults,
95
+ context: params.context,
96
+ active: enabled,
97
+ inactiveReason: "Feishu account is disabled.",
98
+ apply: (value) => {
99
+ account.appSecret = value;
100
+ }
101
+ });
102
+ }
85
103
  const baseConnectionMode = normalizeSecretStringValue(feishu.connectionMode) === "webhook" ? "webhook" : "websocket";
86
104
  const resolveAccountMode = (account) => hasOwnProperty(account, "connectionMode") ? normalizeSecretStringValue(account.connectionMode) : baseConnectionMode;
87
105
  collectConditionalChannelFieldAssignments({
@@ -1,2 +1,2 @@
1
- import { n as collectRuntimeConfigAssignments, r as secretTargetRegistryEntries, t as channelSecrets } from "./secret-contract-ChjJKAJ9.js";
1
+ import { n as collectRuntimeConfigAssignments, r as secretTargetRegistryEntries, t as channelSecrets } from "./secret-contract-DLaBl9m4.js";
2
2
  export { channelSecrets, collectRuntimeConfigAssignments, secretTargetRegistryEntries };
@@ -15,6 +15,29 @@ import { Readable } from "node:stream";
15
15
  import { saveMediaBuffer, saveMediaStream } from "openclaw/plugin-sdk/media-store";
16
16
  import { resolvePreferredOpenClawTmpDir, withTempDownloadPath, withTempWorkspace } from "openclaw/plugin-sdk/temp-path";
17
17
  import { resolveMarkdownTableMode } from "openclaw/plugin-sdk/markdown-table-runtime";
18
+ //#region extensions/feishu/src/identity-header.ts
19
+ const emojiSegmenter = typeof Intl !== "undefined" && "Segmenter" in Intl ? new Intl.Segmenter(void 0, { granularity: "grapheme" }) : null;
20
+ const keycapEmojiPattern = /^[0-9#*]\uFE0F?\u20E3$/u;
21
+ const emojiLikeSegmentPattern = /[\p{Emoji_Presentation}\p{Extended_Pictographic}\p{Regional_Indicator}]/u;
22
+ function splitGraphemes(input) {
23
+ if (!emojiSegmenter) return Array.from(input);
24
+ return Array.from(emojiSegmenter.segment(input), (segment) => segment.segment);
25
+ }
26
+ function isEmojiSegment(segment) {
27
+ return keycapEmojiPattern.test(segment) || emojiLikeSegmentPattern.test(segment);
28
+ }
29
+ function resolveFeishuIdentityEmoji(raw) {
30
+ const trimmed = raw?.trim();
31
+ if (!trimmed) return;
32
+ return splitGraphemes(trimmed).filter(isEmojiSegment).join("") || void 0;
33
+ }
34
+ function resolveFeishuIdentityHeaderTitle(identity) {
35
+ if (!identity) return "";
36
+ const name = identity.name?.trim() ?? "";
37
+ const emoji = resolveFeishuIdentityEmoji(identity.emoji);
38
+ return (emoji ? `${emoji} ${name}` : name).trim();
39
+ }
40
+ //#endregion
18
41
  //#region extensions/feishu/src/external-keys.ts
19
42
  const CONTROL_CHARS_RE = /\p{Cc}/u;
20
43
  const MAX_EXTERNAL_KEY_LENGTH = 512;
@@ -478,12 +501,14 @@ async function prepareFeishuVoiceMedia(params) {
478
501
  return params;
479
502
  }
480
503
  }
481
- async function probeAudioDurationMs(buffer) {
504
+ async function probeMediaDurationMs(params) {
482
505
  try {
483
506
  return await withTempWorkspace({
484
507
  rootDir: resolvePreferredOpenClawTmpDir(),
485
- prefix: "feishu-audio-probe-"
508
+ prefix: "feishu-media-probe-"
486
509
  }, async (workspace) => {
510
+ const ext = normalizeLowercaseStringOrEmpty(path.extname(params.fileName));
511
+ const inferredExt = ext && ext.length <= 12 ? ext : mediaKindFromMime(params.contentType) === "video" ? ".mp4" : ".ogg";
487
512
  const stdout = await runFfprobe([
488
513
  "-v",
489
514
  "error",
@@ -491,17 +516,21 @@ async function probeAudioDurationMs(buffer) {
491
516
  "format=duration",
492
517
  "-of",
493
518
  "csv=p=0",
494
- await workspace.write("input.ogg", buffer)
519
+ await workspace.write(`input${inferredExt}`, params.buffer)
495
520
  ], { timeoutMs: 5e3 });
496
521
  const seconds = Number.parseFloat(stdout.trim());
497
522
  if (!Number.isFinite(seconds) || seconds <= 0) return;
498
523
  return Math.max(1, Math.round(seconds * 1e3));
499
524
  });
500
525
  } catch (err) {
501
- console.warn("[feishu] failed to probe audio duration; voice bubble will omit it:", err);
526
+ console.warn("[feishu] failed to probe media duration; upload will omit it:", err);
502
527
  return;
503
528
  }
504
529
  }
530
+ async function maybeProbeUploadDurationMs(params) {
531
+ if (params.msgType !== "audio" && params.msgType !== "media") return;
532
+ return await probeMediaDurationMs(params);
533
+ }
505
534
  /**
506
535
  * Upload and send media (image or file) from URL, local path, or buffer.
507
536
  * When mediaUrl is a local path, mediaLocalRoots (from core outbound context)
@@ -563,7 +592,12 @@ async function sendMediaFeishu(params) {
563
592
  ...voiceIntentDegradedToFile ? { voiceIntentDegradedToFile: true } : {}
564
593
  };
565
594
  }
566
- const durationMs = routing.msgType === "audio" ? await probeAudioDurationMs(buffer) : void 0;
595
+ const durationMs = await maybeProbeUploadDurationMs({
596
+ buffer,
597
+ fileName: name,
598
+ contentType,
599
+ msgType: routing.msgType
600
+ });
567
601
  const { fileKey } = await uploadFileFeishu({
568
602
  cfg,
569
603
  file: buffer,
@@ -1242,4 +1276,4 @@ async function sendMarkdownCardFeishu(params) {
1242
1276
  });
1243
1277
  }
1244
1278
  //#endregion
1245
- export { normalizeFeishuExternalKey as _, sendCardFeishu as a, sendStructuredCardFeishu as c, isFeishuBroadcastMention as d, isMentionForwardRequest as f, shouldSuppressFeishuTextForVoiceMedia as g, sendMediaFeishu as h, resolveFeishuCardTemplate as i, parsePostContent as l, saveMessageResourceFeishu as m, getMessageFeishu as n, sendMarkdownCardFeishu as o, isFeishuGroupChatType as p, listFeishuThreadMessages as r, sendMessageFeishu as s, editMessageFeishu as t, extractMentionTargets as u };
1279
+ export { normalizeFeishuExternalKey as _, sendCardFeishu as a, sendStructuredCardFeishu as c, isFeishuBroadcastMention as d, isMentionForwardRequest as f, shouldSuppressFeishuTextForVoiceMedia as g, sendMediaFeishu as h, resolveFeishuCardTemplate as i, parsePostContent as l, saveMessageResourceFeishu as m, getMessageFeishu as n, sendMarkdownCardFeishu as o, isFeishuGroupChatType as p, listFeishuThreadMessages as r, sendMessageFeishu as s, editMessageFeishu as t, extractMentionTargets as u, resolveFeishuIdentityEmoji as v, resolveFeishuIdentityHeaderTitle as y };
package/dist/setup-api.js CHANGED
@@ -1,2 +1,2 @@
1
- import { i as feishuSetupAdapter, n as feishuSetupWizard, t as feishuPlugin } from "./channel-C1MthXp0.js";
1
+ import { i as feishuSetupAdapter, n as feishuSetupWizard, t as feishuPlugin } from "./channel-Bwm5rvHg.js";
2
2
  export { feishuPlugin, feishuSetupAdapter, feishuSetupWizard };
@@ -1,9 +1,6 @@
1
+ import { createLazyRuntimeModule } from "openclaw/plugin-sdk/lazy-runtime";
1
2
  //#region extensions/feishu/subagent-hooks-api.ts
2
- let feishuSubagentHooksPromise = null;
3
- function loadFeishuSubagentHooksModule() {
4
- feishuSubagentHooksPromise ??= import("./subagent-hooks-1pqt5tAA.js").then((n) => n.i);
5
- return feishuSubagentHooksPromise;
6
- }
3
+ const loadFeishuSubagentHooksModule = createLazyRuntimeModule(() => import("./subagent-hooks-1pqt5tAA.js").then((n) => n.i));
7
4
  function registerFeishuSubagentHooks(api) {
8
5
  api.on("subagent_delivery_target", async (event) => {
9
6
  const { handleFeishuSubagentDeliveryTarget } = await loadFeishuSubagentHooksModule();
@@ -1,19 +1,19 @@
1
1
  {
2
2
  "name": "@openclaw/feishu",
3
- "version": "2026.6.11",
3
+ "version": "2026.7.1-beta.2",
4
4
  "lockfileVersion": 3,
5
5
  "requires": true,
6
6
  "packages": {
7
7
  "": {
8
8
  "name": "@openclaw/feishu",
9
- "version": "2026.6.11",
9
+ "version": "2026.7.1-beta.2",
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.6.11"
16
+ "openclaw": ">=2026.7.1-beta.2"
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.6.11",
3
+ "version": "2026.7.1-beta.2",
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.6.11"
16
+ "openclaw": ">=2026.7.1-beta.2"
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.6.11"
50
+ "pluginApi": ">=2026.7.1-beta.2"
51
51
  },
52
52
  "build": {
53
- "openclawVersion": "2026.6.11"
53
+ "openclawVersion": "2026.7.1-beta.2"
54
54
  },
55
55
  "release": {
56
56
  "publishToClawHub": true,