@openclaw/feishu 2026.7.2-beta.7 → 2026.8.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.
Files changed (35) hide show
  1. package/dist/accounts-CCCdMen2.js +203 -0
  2. package/dist/api.js +52 -51
  3. package/dist/{channel-CScE82zY.js → channel-Dzb5jv3i.js} +53 -30
  4. package/dist/channel-plugin-api.js +1 -1
  5. package/dist/{channel.runtime-B21e1E0r.js → channel.runtime-CoPaHQQ6.js} +181 -142
  6. package/dist/{client-Dcbs6vml.js → client-Hp7uo_cl.js} +27 -16
  7. package/dist/contract-api.js +2 -2
  8. package/dist/{conversation-id-BeJL-wq7.js → conversation-id-VYgGQ-GX.js} +11 -15
  9. package/dist/doctor-contract-api.js +1 -1
  10. package/dist/doctor-contract-bEQXIXyP.js +158 -0
  11. package/dist/{drive-hSi_Utp0.js → drive-B0JkoiRj.js} +318 -41
  12. package/dist/{media-CEUraFOR.js → media-BjSy8fiy.js} +298 -182
  13. package/dist/{monitor-DngbaA6a.js → monitor-CeHCfROt.js} +3 -3
  14. package/dist/{monitor.account-BnTWCw55.js → monitor.account-DwdqexLU.js} +237 -158
  15. package/dist/{monitor.startup-CqH2tiJq.js → monitor.startup-BGErejNH.js} +1 -1
  16. package/dist/{probe-p3POS2RN.js → probe-DVpy58s0.js} +2 -2
  17. package/dist/security-audit-D6Fz2h6p.js +23 -0
  18. package/dist/{send-result-B9_BpUPx.js → send-result-DEAycmOk.js} +18 -9
  19. package/dist/session-binding-contract-api.js +1 -1
  20. package/dist/{session-conversation-BksWrfzm.js → session-conversation-DFCIvQK-.js} +1 -1
  21. package/dist/session-key-api.js +1 -1
  22. package/dist/setup-api.js +1 -1
  23. package/dist/{subagent-hooks-Cx1cX7rW.js → subagent-hooks-B867acTt.js} +2 -2
  24. package/dist/subagent-hooks-api.js +1 -1
  25. package/dist/{thread-bindings-N3wkgkIN.js → thread-bindings-Itvfmx6_.js} +6 -3
  26. package/openclaw.plugin.json +143 -1
  27. package/package.json +4 -4
  28. package/skills/feishu-doc/SKILL.md +20 -195
  29. package/skills/feishu-doc/references/block-types.md +9 -14
  30. package/skills/feishu-drive/SKILL.md +16 -102
  31. package/skills/feishu-perm/SKILL.md +10 -110
  32. package/skills/feishu-wiki/SKILL.md +13 -109
  33. package/dist/accounts-u9X5Wsan.js +0 -469
  34. package/dist/doctor-contract-BiD9tyIv.js +0 -102
  35. package/dist/security-audit-D7WK_BHh.js +0 -11
@@ -1,48 +1,50 @@
1
- import { _ as normalizeCommentFileType, c as encodeQuery, d as isRecord$2, f as normalizeString, g as buildFeishuCommentTarget, h as requestFeishuApi, l as extractReplyText, m as readString$1, o as resolveFeishuAccount, p as parseCommentContentElements, s as resolveFeishuRuntimeAccount } from "./accounts-u9X5Wsan.js";
1
+ import { o as resolveFeishuAccount, s as resolveFeishuRuntimeAccount } from "./accounts-CCCdMen2.js";
2
2
  import { i as resolveReceiveIdType } from "./targets-BUjQ1TcA.js";
3
- import { A as resolveFeishuChatType, C as resolveFeishuDmIngressAccess, E as resolveFeishuGroupSenderActivationIngressAccess, M as buildFeishuCardActionTextFallback, N as createFeishuCardInteractionEnvelope, O as resolveFeishuReplyPolicy, P as decodeFeishuCardAction, S as normalizeFeishuAllowEntry, T as resolveFeishuGroupConversationIngressAccess, d as chunkFeishuPostMarkdown, f as materializeFeishuPostMarkdownSoftBreaks, k as normalizeFeishuChatType$2, o as resolveFeishuCardTemplate, w as resolveFeishuGroupConfig, x as hasExplicitFeishuGroupConfig } from "./send-result-B9_BpUPx.js";
4
- import { o as resolveConfiguredFeishuGroupSessionScope, t as buildFeishuConversationId } from "./conversation-id-BeJL-wq7.js";
5
- import { a as getFeishuUserAgent, i as createFeishuWSClient, n as createEventDispatcher, r as createFeishuClient, s as resolveConfiguredHttpTimeoutMs } from "./client-Dcbs6vml.js";
3
+ import { A as resolveFeishuChatType, C as resolveFeishuDmIngressAccess, E as resolveFeishuGroupSenderActivationIngressAccess, M as buildFeishuCardActionTextFallback, N as createFeishuCardInteractionEnvelope, O as resolveFeishuReplyPolicy, P as decodeFeishuCardAction, S as normalizeFeishuAllowEntry, T as resolveFeishuGroupConversationIngressAccess, d as chunkFeishuPostMarkdown, f as materializeFeishuPostMarkdownSoftBreaks, k as normalizeFeishuChatType$2, o as resolveFeishuCardTemplate, w as resolveFeishuGroupConfig, x as hasExplicitFeishuGroupConfig } from "./send-result-DEAycmOk.js";
4
+ import { i as normalizeFeishuWebhookPath } from "./doctor-contract-bEQXIXyP.js";
5
+ import { o as resolveConfiguredFeishuGroupSessionScope, t as buildFeishuConversationId } from "./conversation-id-VYgGQ-GX.js";
6
+ import { a as getFeishuUserAgent, i as createFeishuWSClient, n as createEventDispatcher, r as createFeishuClient, s as resolveConfiguredHttpTimeoutMs } from "./client-Hp7uo_cl.js";
6
7
  import { t as getFeishuRuntime } from "./runtime-C5JxBWZp.js";
7
- import { i as createCommentTypingReactionLifecycle, l as getChatInfo, t as deliverCommentThreadText } from "./drive-hSi_Utp0.js";
8
- import { t as createFeishuThreadBindingManager } from "./thread-bindings-N3wkgkIN.js";
9
- import { createReplyPrefixContext, evaluateSupplementalContextVisibility, getSessionEntry, normalizeAgentId as normalizeAgentId$2, resolveChannelContextVisibilityMode } from "./runtime-api.js";
8
+ import { d as encodeQuery, f as extractReplyText, g as normalizeCommentFileType, h as buildFeishuCommentTarget, i as createCommentTypingReactionLifecycle, m as requestFeishuApi, p as parseCommentContentElements, s as getChatInfo, t as deliverCommentThreadText } from "./drive-B0JkoiRj.js";
9
+ import { t as createFeishuThreadBindingManager } from "./thread-bindings-Itvfmx6_.js";
10
+ import { createReplyPrefixContext, evaluateSupplementalContextVisibility, getSessionEntry, normalizeAgentId as normalizeAgentId$1, resolveChannelContextVisibilityMode } from "./runtime-api.js";
10
11
  import { t as readFeishuJsonResponse } from "./json-response-CheljwGr.js";
11
- import { _ as buildFeishuMediaFallbackText, a as getMessageFeishu, d as parsePostContent, f as extractMentionTargets, g as normalizeFeishuExternalKey, h as isFeishuGroupChatType, l as sendMessageFeishu, m as isMentionForwardRequest, n as sendMediaFeishu, o as listFeishuThreadMessages, p as isFeishuBroadcastMention, r as shouldSuppressFeishuTextForVoiceMedia, s as sendCardFeishu, t as saveMessageResourceFeishu, u as sendStructuredCardFeishu, v as resolveFeishuIdentityEmoji } from "./media-CEUraFOR.js";
12
- import { a as waitForAbortableDelay, i as raceWithTimeoutAndAbort } from "./probe-p3POS2RN.js";
13
- import { t as fetchBotIdentityForMonitor } from "./monitor.startup-CqH2tiJq.js";
12
+ import { _ as normalizeFeishuExternalKey, a as getMessageFeishu, d as extractMentionTargets, f as isFeishuBroadcastMention, g as parsePostContent, h as parseInteractiveCardContent, l as sendMessageFeishu, m as isFeishuGroupChatType, n as sendMediaFeishu, o as listFeishuThreadMessages, p as isMentionForwardRequest, r as shouldSuppressFeishuTextForVoiceMedia, s as sendCardFeishu, t as saveMessageResourceFeishu, u as sendStructuredCardFeishu, v as buildFeishuMediaFallbackText, y as resolveFeishuIdentityEmoji } from "./media-BjSy8fiy.js";
13
+ import { a as waitForAbortableDelay, i as raceWithTimeoutAndAbort } from "./probe-DVpy58s0.js";
14
+ import { t as fetchBotIdentityForMonitor } from "./monitor.startup-BGErejNH.js";
14
15
  import { DEFAULT_INGRESS_ADOPTION_STALL_MS, bindIngressLifecycleToReplyOptions, createChannelIngressError, createChannelIngressMonitor, createChannelMessageReplyPipeline, createMessageReceiptFromOutboundResults, formatChannelProgressDraftLineForEntry, isChannelProgressDraftWorkToolName, resolveAgentOutboundIdentity, resolveChannelPreviewStreamMode, resolveChannelStreamingBlockEnabled } from "openclaw/plugin-sdk/channel-outbound";
15
16
  import { createChannelPairingController, createChannelPairingController as createChannelPairingController$1 } from "openclaw/plugin-sdk/channel-pairing";
16
17
  import { ensureConfiguredBindingRouteReady, resolveConfiguredBindingRoute, resolveRuntimeConversationBindingRoute } from "openclaw/plugin-sdk/conversation-runtime";
18
+ import { collectErrorGraphCandidates, formatErrorMessage, toStringifiedError } from "openclaw/plugin-sdk/error-runtime";
17
19
  import { asDateTimestampMs, isFutureDateTimestampMs, parseStrictNonNegativeInteger, resolveDateTimestampMs, resolveExpiresAtMsFromDurationMs, resolveExpiresAtMsFromDurationSeconds, resolveTimerTimeoutMs } from "openclaw/plugin-sdk/number-runtime";
18
- import { asBoolean, asNullableRecord, isRecord, normalizeLowercaseStringOrEmpty, normalizeOptionalLowercaseString, normalizeOptionalString, readStringValue, uniqueStrings } from "openclaw/plugin-sdk/string-coerce-runtime";
20
+ import { asBoolean, asNullableRecord, isRecord, normalizeLowercaseStringOrEmpty, normalizeNullableString, normalizeOptionalLowercaseString, normalizeOptionalString, readStringValue, uniqueStrings } from "openclaw/plugin-sdk/string-coerce-runtime";
19
21
  import { stripReasoningTagsFromText } from "openclaw/plugin-sdk/text-chunking";
20
22
  import { resolveDefaultGroupPolicy, resolveOpenProviderRuntimeGroupPolicy, warnMissingProviderGroupPolicyFallbackOnce } from "openclaw/plugin-sdk/runtime-group-policy";
21
23
  import { fanInChannelIngressLifecycles } from "openclaw/plugin-sdk/channel-ingress-runtime";
22
24
  import fs from "node:fs";
23
25
  import os from "node:os";
24
26
  import path from "node:path";
25
- import { normalizeAccountId, resolveAgentRoute, resolveInboundLastRouteSessionKey } from "openclaw/plugin-sdk/routing";
27
+ import { normalizeAccountId, normalizeAgentId, resolveAgentRoute, resolveInboundLastRouteSessionKey } from "openclaw/plugin-sdk/routing";
26
28
  import { resolveStorePath } from "openclaw/plugin-sdk/session-store-runtime";
27
29
  import { isRecord as isRecord$1 } from "openclaw/plugin-sdk/channel-secret-basic-runtime";
28
- import { collectErrorGraphCandidates, formatErrorMessage } from "openclaw/plugin-sdk/error-runtime";
30
+ import { createChannelPartialDeliveryError, formatAgentEnvelope, formatInboundMediaUnavailableText, isChannelPartialDeliveryError, recordChannelBotPairLoopAndCheckSuppression, resolveEnvelopeFormatOptions, toInboundMediaFactsWithMetadata } from "openclaw/plugin-sdk/channel-inbound";
29
31
  import * as Lark from "@larksuiteoapi/node-sdk";
30
- import { formatReasoningMessage, resolveHumanDelayConfig } from "openclaw/plugin-sdk/agent-runtime";
32
+ import { resolvePinnedMainDmOwnerFromAllowlist, safeEqualSecret } from "openclaw/plugin-sdk/security-runtime";
31
33
  import { WEBHOOK_ANOMALY_COUNTER_DEFAULTS, WEBHOOK_RATE_LIMIT_DEFAULTS, applyBasicWebhookRequestGuards, createFixedWindowRateLimiter, createWebhookAnomalyTracker, resolveRequestClientIp } from "openclaw/plugin-sdk/webhook-ingress";
32
34
  import { fetchWithSsrFGuard } from "openclaw/plugin-sdk/ssrf-runtime";
33
35
  import { getReplyPayloadTtsSupplement, resolveSendableOutboundReplyParts, resolveTextChunksWithFallback, sendMediaWithLeadingCaption } from "openclaw/plugin-sdk/reply-payload";
34
- import { resolvePinnedMainDmOwnerFromAllowlist, safeEqualSecret } from "openclaw/plugin-sdk/security-runtime";
35
36
  import * as crypto$1 from "node:crypto";
36
37
  import crypto, { createHash } from "node:crypto";
37
- import { buildChannelInboundEventContext, createChannelPartialDeliveryError, formatAgentEnvelope, formatInboundMediaUnavailableText, isChannelPartialDeliveryError, recordChannelBotPairLoopAndCheckSuppression, resolveEnvelopeFormatOptions, toInboundMediaFactsWithMetadata } from "openclaw/plugin-sdk/channel-inbound";
38
38
  import { DEFAULT_GROUP_HISTORY_LIMIT, createChannelHistoryWindow } from "openclaw/plugin-sdk/reply-history";
39
39
  import { sliceUtf16Safe, truncateUtf16Safe } from "openclaw/plugin-sdk/text-utility-runtime";
40
40
  import { pruneMapToMaxSize } from "openclaw/plugin-sdk/collection-runtime";
41
41
  import { createChannelReplayGuard } from "openclaw/plugin-sdk/persistent-dedupe";
42
42
  import { resolveChannelConfigWrites } from "openclaw/plugin-sdk/channel-config-writes";
43
+ import { formatReasoningMessage, resolveHumanDelayConfig } from "openclaw/plugin-sdk/agent-runtime";
43
44
  import { logTypingFailure } from "openclaw/plugin-sdk/channel-feedback";
44
45
  import { getGlobalHookRunner } from "openclaw/plugin-sdk/plugin-runtime";
45
46
  import * as http from "node:http";
47
+ import { channelBlockedPatch, channelReadyPatch } from "openclaw/plugin-sdk/gateway-runtime";
46
48
  import { installRequestBodyLimitGuard, readWebhookBodyOrReject } from "openclaw/plugin-sdk/webhook-request-guards";
47
49
  import { isAbortRequestText, isBtwRequestText } from "openclaw/plugin-sdk/command-primitives-runtime";
48
50
  //#region extensions/feishu/src/bot-agent-body.ts
@@ -321,6 +323,7 @@ function parseMessageContent(content, messageType) {
321
323
  return "[Forwarded message]";
322
324
  }
323
325
  if (messageType === "merge_forward") return "[Merged and Forwarded Message - loading...]";
326
+ if (messageType === "interactive") return parseInteractiveCardContent(parsed);
324
327
  return content;
325
328
  } catch {
326
329
  return FEISHU_MEDIA_MESSAGE_TYPES.has(messageType) ? "" : content;
@@ -345,6 +348,7 @@ function formatSubMessageContent(content, contentType) {
345
348
  switch (contentType) {
346
349
  case "text": return parsed.text || content;
347
350
  case "post": return parsePostContent(content).textContent;
351
+ case "interactive": return parseInteractiveCardContent(parsed);
348
352
  case "image": return "[Image]";
349
353
  case "file": return `[File: ${parsed.file_name || "unknown"}]`;
350
354
  case "audio": return "[Audio]";
@@ -369,7 +373,8 @@ function parseMergeForwardContent(params) {
369
373
  return "[Merged and Forwarded Message - parse error]";
370
374
  }
371
375
  if (!Array.isArray(items) || items.length === 0) return "[Merged and Forwarded Message - no sub-messages]";
372
- const subMessages = items.filter((item) => item.upper_message_id);
376
+ const container = items.find((item) => item.msg_type === "merge_forward" && !item.upper_message_id);
377
+ const subMessages = container ? items.filter((item) => item !== container) : items.filter((item) => item.upper_message_id);
373
378
  if (subMessages.length === 0) return "[Merged and Forwarded Message - no sub-messages found]";
374
379
  log?.(`feishu: merge_forward contains ${subMessages.length} sub-messages`);
375
380
  subMessages.sort((a, b) => (parseStrictNonNegativeInteger(a.create_time) ?? 0) - (parseStrictNonNegativeInteger(b.create_time) ?? 0));
@@ -1133,13 +1138,9 @@ function resolveUserPath(p) {
1133
1138
  }
1134
1139
  //#endregion
1135
1140
  //#region extensions/feishu/src/agent-config.ts
1136
- const DEFAULT_AGENT_ID = "main";
1137
- function normalizeAgentId$1(value) {
1138
- return (value ?? "").trim().toLowerCase() || DEFAULT_AGENT_ID;
1139
- }
1140
1141
  function resolveFeishuConfigReasoningDefault(cfg, agentId) {
1141
- const id = normalizeAgentId$1(agentId);
1142
- return cfg.agents?.list?.find((entry) => normalizeAgentId$1(entry?.id) === id)?.reasoningDefault ?? cfg.agents?.defaults?.reasoningDefault ?? "off";
1142
+ const id = normalizeAgentId(agentId);
1143
+ return cfg.agents?.list?.find((entry) => normalizeAgentId(entry?.id) === id)?.reasoningDefault ?? cfg.agents?.defaults?.reasoningDefault ?? "off";
1143
1144
  }
1144
1145
  //#endregion
1145
1146
  //#region extensions/feishu/src/reasoning-preview.ts
@@ -1242,8 +1243,14 @@ function resolveAllowedHostnames(domain) {
1242
1243
  }
1243
1244
  return ["open.feishu.cn"];
1244
1245
  }
1246
+ function cancelUnreadResponseBody(response) {
1247
+ if (!response.bodyUsed) response.body?.cancel().catch(() => void 0);
1248
+ }
1245
1249
  async function assertSuccessfulCardKitResponse(response, auditContext, action) {
1246
- if (!response.ok) throw new Error(`${action} failed with HTTP ${response.status}`);
1250
+ if (!response.ok) {
1251
+ cancelUnreadResponseBody(response);
1252
+ throw new Error(`${action} failed with HTTP ${response.status}`);
1253
+ }
1247
1254
  const data = await readFeishuJsonResponse(response, auditContext);
1248
1255
  if (data.code !== 0) throw new Error(`${action} failed: ${data.msg ?? "unknown error"} (code=${String(data.code)})`);
1249
1256
  }
@@ -1276,7 +1283,10 @@ async function getToken(creds, deps) {
1276
1283
  });
1277
1284
  let data;
1278
1285
  try {
1279
- if (!response.ok) throw new Error(`Token request failed with HTTP ${response.status}`);
1286
+ if (!response.ok) {
1287
+ cancelUnreadResponseBody(response);
1288
+ throw new Error(`Token request failed with HTTP ${response.status}`);
1289
+ }
1280
1290
  data = await readFeishuJsonResponse(response, "feishu.streaming-card.token");
1281
1291
  } finally {
1282
1292
  await release();
@@ -1384,7 +1394,10 @@ var FeishuStreamingSession = class {
1384
1394
  });
1385
1395
  let createData;
1386
1396
  try {
1387
- if (!createRes.ok) throw new Error(`Create card request failed with HTTP ${createRes.status}`);
1397
+ if (!createRes.ok) {
1398
+ cancelUnreadResponseBody(createRes);
1399
+ throw new Error(`Create card request failed with HTTP ${createRes.status}`);
1400
+ }
1388
1401
  createData = await readFeishuJsonResponse(createRes, "feishu.streaming-card.create");
1389
1402
  } finally {
1390
1403
  await releaseCreate();
@@ -1821,9 +1834,6 @@ function mergeStreamingFinalText(previousText, nextText, appendError) {
1821
1834
  if (previousText.endsWith(`\n\n${nextText}`)) return previousText;
1822
1835
  return `${previousText}\n\n${nextText}`;
1823
1836
  }
1824
- function toError(error) {
1825
- return error instanceof Error ? error : new Error(String(error));
1826
- }
1827
1837
  /** Maximum age (ms) for a message to receive a typing indicator reaction.
1828
1838
  * Messages older than this are likely replays after context compaction (#30418). */
1829
1839
  const TYPING_INDICATOR_MAX_AGE_MS = 2 * 6e4;
@@ -2191,9 +2201,10 @@ function createFeishuReplyDispatcher(params) {
2191
2201
  };
2192
2202
  const updateStreamingStatusLine = (nextStatusLine, options) => {
2193
2203
  statusLine = nextStatusLine;
2194
- if (!Boolean(streaming?.isActive() || streamingStartPromise) && (options?.startIfNeeded === false || renderMode !== "card")) return;
2204
+ if (!Boolean(streaming?.isActive() || streamingStartPromise) && (options?.startIfNeeded === false || renderMode !== "card")) return false;
2195
2205
  startStreaming();
2196
2206
  flushStreamingCardUpdate(buildCombinedStreamText(reasoningText, streamText));
2207
+ return false;
2197
2208
  };
2198
2209
  const sendChunkedTextReply = async (paramsLocal) => {
2199
2210
  const chunkSource = paramsLocal.useCard ? paramsLocal.text : materializeFeishuPostMarkdownSoftBreaks(core.channel.text.convertMarkdownTables(paramsLocal.text, tableMode));
@@ -2220,6 +2231,7 @@ function createFeishuReplyDispatcher(params) {
2220
2231
  acceptedChunks.push(chunk);
2221
2232
  markVisibleReplySent();
2222
2233
  } catch (error) {
2234
+ if (isChannelPartialDeliveryError(error)) markVisibleReplySent();
2223
2235
  throw createFeishuPartialReplyDeliveryError(error, createFeishuReplyDeliveryResult({
2224
2236
  results,
2225
2237
  visibleReplySent: results.length > 0,
@@ -2280,7 +2292,11 @@ function createFeishuReplyDispatcher(params) {
2280
2292
  markVisibleReplySent();
2281
2293
  if (result?.voiceIntentDegradedToFile && options?.fallbackText && !sentFallbackText) degradedVoiceFallbackText = options.fallbackText;
2282
2294
  },
2283
- onError: options?.fallbackText === void 0 ? void 0 : async ({ mediaUrl }) => {
2295
+ onError: options?.fallbackText === void 0 ? void 0 : async ({ error, mediaUrl }) => {
2296
+ if (isChannelPartialDeliveryError(error)) {
2297
+ markVisibleReplySent();
2298
+ throw toStringifiedError(error);
2299
+ }
2284
2300
  const fallbackText = await buildFeishuMediaFallbackText({
2285
2301
  text: sentFallbackText ? void 0 : options.fallbackText,
2286
2302
  mediaUrl
@@ -2295,6 +2311,7 @@ function createFeishuReplyDispatcher(params) {
2295
2311
  }
2296
2312
  } catch (error) {
2297
2313
  const partial = isChannelPartialDeliveryError(error) ? error.deliveryResult : void 0;
2314
+ if (partial) markVisibleReplySent();
2298
2315
  throw createFeishuPartialReplyDeliveryError(error, mergeFeishuReplyDeliveryResults([...results, ...partial ? [partial] : []]));
2299
2316
  }
2300
2317
  return mergeFeishuReplyDeliveryResults(results);
@@ -2396,7 +2413,7 @@ function createFeishuReplyDispatcher(params) {
2396
2413
  if (deliveryError !== void 0) completion.reject(createFeishuPartialReplyDeliveryError(isChannelPartialDeliveryError(deliveryError) && deliveryError instanceof Error ? deliveryError.cause ?? deliveryError : deliveryError instanceof FeishuStreamingFinalizationError ? deliveryError.cause ?? deliveryError : deliveryError, settledResult));
2397
2414
  else completion.resolve(settledResult);
2398
2415
  }
2399
- if (closeOutcome.error !== void 0) throw toError(closeOutcome.error);
2416
+ if (closeOutcome.error !== void 0) throw toStringifiedError(closeOutcome.error);
2400
2417
  } while (pendingStreamingDeliveries.length > 0);
2401
2418
  } finally {
2402
2419
  typingCallbacks?.onIdle?.();
@@ -2486,6 +2503,7 @@ function createFeishuReplyDispatcher(params) {
2486
2503
  }
2487
2504
  };
2488
2505
  const handleDeliveryError = async (error, info) => {
2506
+ if (isChannelPartialDeliveryError(error)) markVisibleReplySent();
2489
2507
  params.runtime.error?.(`feishu[${account.accountId}] ${info.kind} reply failed: ${String(error)}`);
2490
2508
  await queueIdleSideEffects().catch((cleanupError) => params.runtime.error?.(`feishu[${account.accountId}] reply error cleanup failed: ${String(cleanupError)}`));
2491
2509
  };
@@ -2524,7 +2542,7 @@ function createFeishuReplyDispatcher(params) {
2524
2542
  const shouldDiscardStreamingPreview = info?.kind === "final" && (finalTextExceedsStreamingLimit || hasMedia && (hasVoiceMedia && !shouldDeliverText && !ttsTextAlreadyVisible || skipTextForDuplicateFinal));
2525
2543
  const priorClosedStreamingSettlement = info?.kind === "final" && hasText && skipTextForDuplicateFinal ? claimClosedStreamingResult(void 0, text) : void 0;
2526
2544
  if (!shouldDeliverText && !hasMedia) {
2527
- if (priorClosedStreamingSettlement?.error !== void 0) throw toError(priorClosedStreamingSettlement.error);
2545
+ if (priorClosedStreamingSettlement?.error !== void 0) throw toStringifiedError(priorClosedStreamingSettlement.error);
2528
2546
  return priorClosedStreamingSettlement?.result ?? noVisibleFeishuReplyDelivery;
2529
2547
  }
2530
2548
  const deliveredResults = priorClosedStreamingSettlement ? [priorClosedStreamingSettlement.result] : [];
@@ -2654,43 +2672,40 @@ function createFeishuReplyDispatcher(params) {
2654
2672
  onModelSelected: prefixContext.onModelSelected,
2655
2673
  disableBlockStreaming: typeof blockStreamingEnabled === "boolean" ? !blockStreamingEnabled : true,
2656
2674
  onPartialReply: previewStreamingEnabled ? (payload) => {
2657
- if (!payload.text) return;
2675
+ if (!payload.text) return false;
2658
2676
  const cleaned = stripReasoningTagsFromText(payload.text, {
2659
2677
  mode: "strict",
2660
2678
  trim: "both"
2661
2679
  });
2662
- if (!cleaned) return;
2680
+ if (!cleaned) return false;
2663
2681
  startStreaming();
2664
2682
  queueStreamingUpdate(cleaned, {
2665
2683
  dedupeWithLastPartial: true,
2666
2684
  mode: "snapshot"
2667
2685
  });
2686
+ return false;
2668
2687
  } : void 0,
2669
2688
  onReasoningStream: reasoningPreviewEnabled ? (payload) => {
2670
- if (!payload.text) return;
2689
+ if (!payload.text) return false;
2671
2690
  startStreaming();
2672
2691
  queueReasoningUpdate(formatReasoningMessage(payload.text));
2692
+ return false;
2673
2693
  } : void 0,
2674
- onReasoningEnd: reasoningPreviewEnabled ? () => {} : void 0,
2694
+ onReasoningEnd: reasoningPreviewEnabled ? () => false : void 0,
2675
2695
  onToolStart: previewStreamingEnabled ? (payload) => {
2676
- if (!isChannelProgressDraftWorkToolName(payload.name)) return;
2696
+ if (!isChannelProgressDraftWorkToolName(payload.name)) return false;
2677
2697
  const statusLineLocal = formatChannelProgressDraftLineForEntry(account.config, {
2678
2698
  event: "tool",
2679
2699
  name: payload.name,
2680
2700
  phase: payload.phase,
2681
2701
  args: payload.args
2682
2702
  }, { detailMode: payload.detailMode });
2683
- if (statusLineLocal) updateStreamingStatusLine(statusLineLocal);
2703
+ if (statusLineLocal) return updateStreamingStatusLine(statusLineLocal);
2704
+ return false;
2684
2705
  } : void 0,
2685
- onAssistantMessageStart: previewStreamingEnabled ? () => {
2686
- updateStreamingStatusLine("", { startIfNeeded: false });
2687
- } : void 0,
2688
- onCompactionStart: previewStreamingEnabled ? () => {
2689
- updateStreamingStatusLine("📦 **Compacting context...**");
2690
- } : void 0,
2691
- onCompactionEnd: previewStreamingEnabled ? () => {
2692
- updateStreamingStatusLine("");
2693
- } : void 0
2706
+ onAssistantMessageStart: previewStreamingEnabled ? () => updateStreamingStatusLine("", { startIfNeeded: false }) : void 0,
2707
+ onCompactionStart: previewStreamingEnabled ? () => updateStreamingStatusLine("📦 **Compacting context...**") : void 0,
2708
+ onCompactionEnd: previewStreamingEnabled ? () => updateStreamingStatusLine("") : void 0
2694
2709
  },
2695
2710
  ensureNoVisibleReplyFallback,
2696
2711
  getVisibleReplyState: () => ({
@@ -2892,7 +2907,10 @@ async function handleFeishuMessage(params) {
2892
2907
  if (event.message.message_type === "merge_forward") {
2893
2908
  log(`feishu[${account.accountId}]: processing merge_forward message, fetching full content via API`);
2894
2909
  try {
2895
- const response = await createFeishuClient(account).im.message.get({ path: { message_id: event.message.message_id } });
2910
+ const response = await createFeishuClient(account).im.message.get({
2911
+ params: { card_msg_content_type: "user_card_content" },
2912
+ path: { message_id: event.message.message_id }
2913
+ });
2896
2914
  if (response.code === 0 && response.data?.items && response.data.items.length > 0) {
2897
2915
  log(`feishu[${account.accountId}]: merge_forward API returned ${response.data.items.length} items`);
2898
2916
  const expandedContent = parseMergeForwardContent({
@@ -2963,22 +2981,25 @@ async function handleFeishuMessage(params) {
2963
2981
  feishuCfg
2964
2982
  }) : null;
2965
2983
  let effectiveThreadId = ctx.threadId;
2966
- if (isGroup && ctx.chatType === "topic_group" && !effectiveThreadId && isFeishuTopicSessionScope(groupSessionScope ?? "group")) try {
2967
- const hydratedThreadId = (await getMessageFeishu({
2968
- cfg,
2969
- accountId: account.accountId,
2970
- messageId: ctx.messageId
2971
- }))?.threadId?.trim();
2972
- if (hydratedThreadId) {
2973
- ctx = {
2974
- ...ctx,
2975
- threadId: hydratedThreadId
2976
- };
2977
- effectiveThreadId = hydratedThreadId;
2978
- log(`feishu[${account.accountId}]: hydrated topic thread_id=${hydratedThreadId} for message=${ctx.messageId}`);
2984
+ if (isGroup && ctx.chatType === "topic_group" && !effectiveThreadId && isFeishuTopicSessionScope(groupSessionScope ?? "group")) {
2985
+ const topicHydrationMessageId = ctx.replyTargetMessageId ?? ctx.messageId;
2986
+ try {
2987
+ const hydratedThreadId = (await getMessageFeishu({
2988
+ cfg,
2989
+ accountId: account.accountId,
2990
+ messageId: topicHydrationMessageId
2991
+ }))?.threadId?.trim();
2992
+ if (hydratedThreadId) {
2993
+ ctx = {
2994
+ ...ctx,
2995
+ threadId: hydratedThreadId
2996
+ };
2997
+ effectiveThreadId = hydratedThreadId;
2998
+ log(`feishu[${account.accountId}]: hydrated topic thread_id=${hydratedThreadId} for message=${topicHydrationMessageId}`);
2999
+ }
3000
+ } catch (err) {
3001
+ log(`feishu[${account.accountId}]: failed to hydrate topic thread_id for message=${topicHydrationMessageId}: ${String(err)}`);
2979
3002
  }
2980
- } catch (err) {
2981
- log(`feishu[${account.accountId}]: failed to hydrate topic thread_id for message=${ctx.messageId}: ${String(err)}`);
2982
3003
  }
2983
3004
  const effectiveGroupSenderAllowFrom = isGroup ? (groupConfig?.allowFrom?.length ?? 0) > 0 ? groupConfig?.allowFrom ?? [] : feishuCfg?.groupSenderAllowFrom ?? [] : [];
2984
3005
  const groupSession = isGroup ? resolveFeishuGroupSession({
@@ -2995,7 +3016,7 @@ async function handleFeishuMessage(params) {
2995
3016
  const dmPolicy = feishuCfg?.dmPolicy ?? "pairing";
2996
3017
  const configAllowFrom = feishuCfg?.allowFrom ?? [];
2997
3018
  const rawBroadcastAgents = isGroup ? resolveBroadcastAgents(cfg, ctx.chatId) : null;
2998
- const broadcastAgents = rawBroadcastAgents ? uniqueStrings(rawBroadcastAgents.map((id) => normalizeAgentId$2(id))) : null;
3019
+ const broadcastAgents = rawBroadcastAgents ? uniqueStrings(rawBroadcastAgents.map((id) => normalizeAgentId$1(id))) : null;
2999
3020
  const messageCreateTimeMs = parseStrictNonNegativeInteger(event.message.create_time) ?? Date.now();
3000
3021
  let requireMention = false;
3001
3022
  if (isGroup) {
@@ -3524,7 +3545,37 @@ async function handleFeishuMessage(params) {
3524
3545
  const buildCtxPayloadForAgent = async (agentId, agentSessionKey, agentAccountId, wasMentioned) => {
3525
3546
  const groupName = await resolveGroupNameForLabel();
3526
3547
  const threadContext = await resolveThreadContextForAgent(agentId, agentSessionKey, groupName);
3527
- return buildChannelInboundEventContext({
3548
+ const contextBinding = {
3549
+ agentId,
3550
+ sessionKey: agentSessionKey,
3551
+ messageId: ctx.messageId,
3552
+ inboundEventKind: "user_request"
3553
+ };
3554
+ const boundChannelIngress = isGroup ? await resolveFeishuGroupSenderActivationIngressAccess({
3555
+ cfg: effectiveCfg,
3556
+ accountId: account.accountId,
3557
+ chatId: ctx.chatId,
3558
+ allowFrom: effectiveGroupSenderAllowFrom,
3559
+ senderOpenId: ctx.senderOpenId,
3560
+ senderUserId,
3561
+ requireMention,
3562
+ mentionedBot: ctx.mentionedBot,
3563
+ contextBinding,
3564
+ threadId: ctx.rootId && isTopicSessionForThread ? ctx.rootId : void 0
3565
+ }) : await resolveFeishuDmIngressAccess({
3566
+ cfg: effectiveCfg,
3567
+ accountId: account.accountId,
3568
+ dmPolicy: effectiveDmPolicy,
3569
+ allowFrom: effectiveConfigAllowFrom,
3570
+ readAllowFromStore: pairing.readAllowFromStore,
3571
+ senderOpenId: ctx.senderOpenId,
3572
+ senderUserId,
3573
+ conversationId: ctx.senderOpenId,
3574
+ mayPair: false,
3575
+ contextBinding
3576
+ });
3577
+ return core.channel.inbound.buildContext({
3578
+ channelIngress: boundChannelIngress,
3528
3579
  channel: "feishu",
3529
3580
  supplemental: {
3530
3581
  quote: quotedContent ? {
@@ -3651,8 +3702,8 @@ async function handleFeishuMessage(params) {
3651
3702
  }
3652
3703
  };
3653
3704
  const strategy = cfg.broadcast?.strategy === "sequential" ? "sequential" : "parallel";
3654
- const activeAgentId = ctx.mentionedBot || !requireMention ? normalizeAgentId$2(route.agentId) : null;
3655
- const agentIds = (cfg.agents?.list ?? []).map((a) => normalizeAgentId$2(a.id));
3705
+ const activeAgentId = ctx.mentionedBot || !requireMention ? normalizeAgentId$1(route.agentId) : null;
3706
+ const agentIds = (cfg.agents?.list ?? []).map((a) => normalizeAgentId$1(a.id));
3656
3707
  const hasKnownAgents = agentIds.length > 0;
3657
3708
  log(`feishu[${account.accountId}]: broadcasting to ${broadcastAgents.length} agents (strategy=${strategy}, active=${activeAgentId ?? "none"})`);
3658
3709
  const createBroadcastInboundAdapter = (paramsLocal) => ({
@@ -3692,7 +3743,7 @@ async function handleFeishuMessage(params) {
3692
3743
  })
3693
3744
  });
3694
3745
  const dispatchForAgent = async (agentId) => {
3695
- const normalizedAgentId = normalizeAgentId$2(agentId);
3746
+ const normalizedAgentId = normalizeAgentId$1(agentId);
3696
3747
  if (hasKnownAgents && !agentIds.includes(normalizedAgentId)) {
3697
3748
  log(`feishu[${account.accountId}]: broadcast agent ${agentId} not found in agents.list; skipping`);
3698
3749
  return;
@@ -4293,9 +4344,6 @@ const FEISHU_INGRESS_PAYLOAD_VERSION = 1;
4293
4344
  const FEISHU_INGRESS_POLL_INTERVAL_MS = 500;
4294
4345
  const FEISHU_DURABLE_EVENT_TYPES = /* @__PURE__ */ new Set(["drive.notice.comment_add_v1", "im.message.receive_v1"]);
4295
4346
  const FeishuIngressPermanentError = createChannelIngressError("FeishuIngressPermanentError", { withReason: true });
4296
- function readString(value) {
4297
- return typeof value === "string" && value.trim() ? value.trim() : null;
4298
- }
4299
4347
  function parseRawEnvelope(rawEnvelope) {
4300
4348
  let parsed;
4301
4349
  try {
@@ -4307,7 +4355,7 @@ function parseRawEnvelope(rawEnvelope) {
4307
4355
  return parsed;
4308
4356
  }
4309
4357
  function decryptEnvelope(envelope, encryptKey) {
4310
- const encrypted = readString(envelope.encrypt);
4358
+ const encrypted = normalizeNullableString(envelope.encrypt);
4311
4359
  if (!encrypted) return envelope;
4312
4360
  const key = encryptKey?.trim();
4313
4361
  if (!key) throw new FeishuIngressPermanentError("authentication-failed", "Feishu encrypted ingress envelope has no configured encrypt key.");
@@ -4322,13 +4370,13 @@ function inspectFeishuIngressEnvelope(rawEnvelope, encryptKey, allowInvalidLane
4322
4370
  const envelope = decryptEnvelope(parseRawEnvelope(rawEnvelope), encryptKey);
4323
4371
  const nestedHeader = isRecord$1(envelope.header) ? envelope.header : null;
4324
4372
  const nestedEvent = isRecord$1(envelope.event) ? envelope.event : null;
4325
- const eventType = readString(nestedHeader?.event_type) ?? readString(envelope.event_type);
4373
+ const eventType = normalizeNullableString(nestedHeader?.event_type) ?? normalizeNullableString(envelope.event_type);
4326
4374
  if (!eventType || !FEISHU_DURABLE_EVENT_TYPES.has(eventType)) return null;
4327
- const eventId = readString(nestedHeader?.event_id) ?? readString(envelope.event_id);
4375
+ const eventId = normalizeNullableString(nestedHeader?.event_id) ?? normalizeNullableString(envelope.event_id);
4328
4376
  if (!eventId) throw new FeishuIngressPermanentError("invalid-event", `Feishu ${eventType} envelope is missing header.event_id.`);
4329
4377
  const event = nestedEvent ?? envelope;
4330
4378
  if (eventType === "im.message.receive_v1") {
4331
- const chatId = readString((isRecord$1(event.message) ? event.message : null)?.chat_id);
4379
+ const chatId = normalizeNullableString((isRecord$1(event.message) ? event.message : null)?.chat_id);
4332
4380
  if (!chatId) {
4333
4381
  if (allowInvalidLane) return {
4334
4382
  eventId,
@@ -4344,8 +4392,8 @@ function inspectFeishuIngressEnvelope(rawEnvelope, encryptKey, allowInvalidLane
4344
4392
  };
4345
4393
  }
4346
4394
  const noticeMeta = isRecord$1(event.notice_meta) ? event.notice_meta : null;
4347
- const fileType = readString(noticeMeta?.file_type);
4348
- const documentId = readString(noticeMeta?.file_token);
4395
+ const fileType = normalizeNullableString(noticeMeta?.file_type);
4396
+ const documentId = normalizeNullableString(noticeMeta?.file_token);
4349
4397
  if (!fileType || !documentId) {
4350
4398
  if (allowInvalidLane) return {
4351
4399
  eventId,
@@ -4580,7 +4628,7 @@ function createFeishuDurableIngress(options) {
4580
4628
  invoke,
4581
4629
  invokeWebhook,
4582
4630
  resolveLifecycle: (data) => {
4583
- const eventId = isRecord$1(data) ? readString(data.event_id) : null;
4631
+ const eventId = isRecord$1(data) ? normalizeNullableString(data.event_id) : null;
4584
4632
  return eventId ? activeLifecycles.get(eventId) : void 0;
4585
4633
  },
4586
4634
  setSocketTerminator: (terminate) => {
@@ -4777,7 +4825,9 @@ function startBotIdentityRecovery(params) {
4777
4825
  const log = runtime?.log ?? console.log;
4778
4826
  log(`feishu[${accountId}]: bot open_id ${currentSource === "cache" ? "loaded from cache" : "unknown"}; starting background provider refresh (delays: ${BOT_IDENTITY_RETRY_DELAYS_MS.map((delay) => `${delay / 1e3}s`).join(", ")})`);
4779
4827
  if (currentSource !== "cache") log(`feishu[${accountId}]: requireMention group messages stay gated until bot identity recovery succeeds`);
4780
- retryBotIdentityProbe(account, accountId, runtime, abortSignal);
4828
+ retryBotIdentityProbe(account, accountId, runtime, abortSignal).catch((err) => {
4829
+ (runtime?.error ?? console.error)(`feishu[${accountId}]: bot identity background retry failed unexpectedly: ${String(err)}`);
4830
+ });
4781
4831
  }
4782
4832
  //#endregion
4783
4833
  //#region extensions/feishu/src/card-ux-launcher.ts
@@ -5094,7 +5144,7 @@ function safeJsonStringify(value) {
5094
5144
  }
5095
5145
  }
5096
5146
  function truncatePromptText(text, maxLength = FEISHU_PROMPT_TEXT_LIMIT) {
5097
- const normalized = normalizeString(text);
5147
+ const normalized = normalizeOptionalString(text);
5098
5148
  if (!normalized) return "";
5099
5149
  return normalized.length > maxLength ? `${sliceUtf16Safe(normalized, 0, maxLength - 1)}…` : normalized;
5100
5150
  }
@@ -5160,7 +5210,7 @@ async function resolveParsedCommentContent(params) {
5160
5210
  return null;
5161
5211
  }
5162
5212
  const objType = normalizeCommentFileType(response.data?.node?.obj_type);
5163
- const objToken = normalizeString(response.data?.node?.obj_token);
5213
+ const objToken = normalizeOptionalString(response.data?.node?.obj_token);
5164
5214
  if (!objType || !objToken) return null;
5165
5215
  return {
5166
5216
  resolvedObjType: objType,
@@ -5229,18 +5279,18 @@ async function fetchDriveComments(params) {
5229
5279
  }
5230
5280
  async function requestFeishuOpenApi(params) {
5231
5281
  const formatErrorDetails = (error) => {
5232
- if (!isRecord$2(error)) return typeof error === "string" ? error : JSON.stringify(error);
5233
- const response = isRecord$2(error.response) ? error.response : void 0;
5234
- const responseData = isRecord$2(response?.data) ? response?.data : void 0;
5282
+ if (!isRecord(error)) return typeof error === "string" ? error : JSON.stringify(error);
5283
+ const response = isRecord(error.response) ? error.response : void 0;
5284
+ const responseData = isRecord(response?.data) ? response?.data : void 0;
5235
5285
  return safeJsonStringify({
5236
5286
  message: typeof error.message === "string" ? error.message : typeof error === "string" ? error : JSON.stringify(error),
5237
- code: readString$1(error.code),
5238
- method: readString$1(isRecord$2(error.config) ? error.config.method : void 0),
5239
- url: readString$1(isRecord$2(error.config) ? error.config.url : void 0),
5287
+ code: readStringValue(error.code),
5288
+ method: readStringValue(isRecord(error.config) ? error.config.method : void 0),
5289
+ url: readStringValue(isRecord(error.config) ? error.config.url : void 0),
5240
5290
  http_status: typeof response?.status === "number" ? response.status : void 0,
5241
- feishu_code: typeof responseData?.code === "number" ? responseData.code : readString$1(responseData?.code),
5242
- feishu_msg: readString$1(responseData?.msg),
5243
- feishu_log_id: readString$1(responseData?.log_id)
5291
+ feishu_code: typeof responseData?.code === "number" ? responseData.code : readStringValue(responseData?.code),
5292
+ feishu_msg: readStringValue(responseData?.msg),
5293
+ feishu_log_id: readStringValue(responseData?.log_id)
5244
5294
  });
5245
5295
  };
5246
5296
  const result = await raceWithTimeoutAndAbort(params.client.request({
@@ -5288,15 +5338,15 @@ async function fetchDriveCommentReplies(params) {
5288
5338
  };
5289
5339
  }
5290
5340
  async function resolveCommentReplyContext(params) {
5291
- const userId = normalizeString(params.reply.user_id);
5292
- const normalizedBotOpenIds = new Set(Array.from(params.botOpenIds ?? []).map((botId) => normalizeString(botId)).filter((botId) => Boolean(botId)));
5341
+ const userId = normalizeOptionalString(params.reply.user_id);
5342
+ const normalizedBotOpenIds = new Set(Array.from(params.botOpenIds ?? []).map((botId) => normalizeOptionalString(botId)).filter((botId) => Boolean(botId)));
5293
5343
  return {
5294
- replyId: normalizeString(params.reply.reply_id),
5344
+ replyId: normalizeOptionalString(params.reply.reply_id),
5295
5345
  userId,
5296
5346
  createTime: typeof params.reply.create_time === "number" ? params.reply.create_time : void 0,
5297
5347
  isBotAuthored: typeof userId === "string" && normalizedBotOpenIds.has(userId),
5298
5348
  content: await resolveParsedCommentContent({
5299
- elements: isRecord$2(params.reply.content) ? params.reply.content.elements : void 0,
5349
+ elements: isRecord(params.reply.content) ? params.reply.content.elements : void 0,
5300
5350
  botOpenIds: params.botOpenIds,
5301
5351
  currentDocument: params.currentDocument,
5302
5352
  client: params.client,
@@ -5451,8 +5501,8 @@ async function fetchDriveCommentContext(params) {
5451
5501
  createTime: right.createTime,
5452
5502
  stableId: right.replyId
5453
5503
  }));
5454
- const rootReplyContext = resolvedReplies.find((reply) => reply.replyId === normalizeString(rootReply?.reply_id)) ?? resolvedReplies[0];
5455
- const targetReplyContext = resolvedReplies.find((reply) => reply.replyId === normalizeString(targetReply?.reply_id)) ?? (params.replyId ? void 0 : resolvedReplies.at(-1) ?? rootReplyContext);
5504
+ const rootReplyContext = resolvedReplies.find((reply) => reply.replyId === normalizeOptionalString(rootReply?.reply_id)) ?? resolvedReplies[0];
5505
+ const targetReplyContext = resolvedReplies.find((reply) => reply.replyId === normalizeOptionalString(targetReply?.reply_id)) ?? (params.replyId ? void 0 : resolvedReplies.at(-1) ?? rootReplyContext);
5456
5506
  let wholeCommentTimeline = [];
5457
5507
  if (commentCard?.is_whole === true) {
5458
5508
  const wholeComments = (await fetchDriveComments({
@@ -5466,9 +5516,9 @@ async function fetchDriveCommentContext(params) {
5466
5516
  })).filter((comment) => comment.is_whole === true);
5467
5517
  wholeCommentTimeline = await Promise.all(wholeComments.map(async (comment) => {
5468
5518
  const rootWholeReply = comment.reply_list?.replies?.[0];
5469
- const normalizedBotOpenIds = new Set(Array.from(params.botOpenIds ?? []).map((botId) => normalizeString(botId)).filter((botId) => Boolean(botId)));
5519
+ const normalizedBotOpenIds = new Set(Array.from(params.botOpenIds ?? []).map((botId) => normalizeOptionalString(botId)).filter((botId) => Boolean(botId)));
5470
5520
  const content = await resolveParsedCommentContent({
5471
- elements: isRecord$2(rootWholeReply?.content) ? rootWholeReply.content.elements : void 0,
5521
+ elements: isRecord(rootWholeReply?.content) ? rootWholeReply.content.elements : void 0,
5472
5522
  botOpenIds: params.botOpenIds,
5473
5523
  currentDocument,
5474
5524
  client: params.client,
@@ -5476,12 +5526,12 @@ async function fetchDriveCommentContext(params) {
5476
5526
  logger: params.logger,
5477
5527
  accountId: params.accountId
5478
5528
  });
5479
- const commentUserId = normalizeString(rootWholeReply?.user_id) || normalizeString(comment.user_id);
5529
+ const commentUserId = normalizeOptionalString(rootWholeReply?.user_id) || normalizeOptionalString(comment.user_id);
5480
5530
  return {
5481
- commentId: normalizeString(comment.comment_id) ?? "",
5531
+ commentId: normalizeOptionalString(comment.comment_id) ?? "",
5482
5532
  userId: commentUserId,
5483
5533
  createTime: typeof comment.create_time === "number" ? comment.create_time : typeof rootWholeReply?.create_time === "number" ? rootWholeReply.create_time : void 0,
5484
- isCurrentComment: normalizeString(comment.comment_id) === params.commentId,
5534
+ isCurrentComment: normalizeOptionalString(comment.comment_id) === params.commentId,
5485
5535
  isBotAuthored: typeof commentUserId === "string" && normalizedBotOpenIds.has(commentUserId),
5486
5536
  content
5487
5537
  };
@@ -5496,10 +5546,10 @@ async function fetchDriveCommentContext(params) {
5496
5546
  }
5497
5547
  const currentWholeCommentIndex = wholeCommentTimeline.findIndex((entry) => entry.commentId === params.commentId);
5498
5548
  return {
5499
- documentTitle: normalizeString(meta?.title),
5500
- documentUrl: normalizeString(meta?.url),
5549
+ documentTitle: normalizeOptionalString(meta?.title),
5550
+ documentUrl: normalizeOptionalString(meta?.url),
5501
5551
  isWholeComment: commentCard?.is_whole,
5502
- quoteText: normalizeString(commentCard?.quote),
5552
+ quoteText: normalizeOptionalString(commentCard?.quote),
5503
5553
  rootCommentText: rootReplyContext?.content.semanticText ?? rootReplyContext?.content.plainText,
5504
5554
  targetReplyText: targetReplyContext?.content.semanticText ?? targetReplyContext?.content.plainText,
5505
5555
  rootCommentContent: rootReplyContext?.content,
@@ -5566,7 +5616,7 @@ async function resolveDriveCommentEventCore(params) {
5566
5616
  const fileToken = event.notice_meta?.file_token?.trim();
5567
5617
  const fileType = normalizeCommentFileType(event.notice_meta?.file_type);
5568
5618
  const senderId = event.notice_meta?.from_user_id?.open_id?.trim();
5569
- const senderUserId = normalizeString(event.notice_meta?.from_user_id?.user_id);
5619
+ const senderUserId = normalizeOptionalString(event.notice_meta?.from_user_id?.user_id);
5570
5620
  if (!eventId || !commentId || !noticeType || !fileToken || !fileType || !senderId) {
5571
5621
  logger?.(`feishu[${accountId}]: drive comment notice missing required fields event=${eventId ?? "unknown"} comment=${commentId ?? "unknown"}`);
5572
5622
  return null;
@@ -5587,7 +5637,7 @@ async function resolveDriveCommentEventCore(params) {
5587
5637
  return null;
5588
5638
  }
5589
5639
  const context = await fetchDriveCommentContext({
5590
- client: createClient ? createClient(account ?? { accountId }) : createFeishuClient((await import("./accounts-u9X5Wsan.js").then((n) => n.t)).resolveFeishuAccount({
5640
+ client: createClient ? createClient(account ?? { accountId }) : createFeishuClient((await import("./accounts-CCCdMen2.js").then((n) => n.t)).resolveFeishuAccount({
5591
5641
  cfg,
5592
5642
  accountId
5593
5643
  })),
@@ -5617,32 +5667,32 @@ async function resolveDriveCommentEventCore(params) {
5617
5667
  };
5618
5668
  }
5619
5669
  function parseFeishuDriveCommentNoticeEventPayload(value) {
5620
- if (!isRecord$2(value) || !isRecord$2(value.notice_meta)) return null;
5670
+ if (!isRecord(value) || !isRecord(value.notice_meta)) return null;
5621
5671
  const noticeMeta = value.notice_meta;
5622
- const fromUserId = isRecord$2(noticeMeta.from_user_id) ? noticeMeta.from_user_id : void 0;
5623
- const toUserId = isRecord$2(noticeMeta.to_user_id) ? noticeMeta.to_user_id : void 0;
5672
+ const fromUserId = isRecord(noticeMeta.from_user_id) ? noticeMeta.from_user_id : void 0;
5673
+ const toUserId = isRecord(noticeMeta.to_user_id) ? noticeMeta.to_user_id : void 0;
5624
5674
  return {
5625
- comment_id: readString$1(value.comment_id),
5626
- event_id: readString$1(value.event_id),
5675
+ comment_id: readStringValue(value.comment_id),
5676
+ event_id: readStringValue(value.event_id),
5627
5677
  is_mentioned: asBoolean(value.is_mentioned),
5628
5678
  notice_meta: {
5629
- file_token: readString$1(noticeMeta.file_token),
5630
- file_type: readString$1(noticeMeta.file_type),
5679
+ file_token: readStringValue(noticeMeta.file_token),
5680
+ file_type: readStringValue(noticeMeta.file_type),
5631
5681
  from_user_id: fromUserId ? {
5632
- open_id: readString$1(fromUserId.open_id),
5633
- user_id: readString$1(fromUserId.user_id),
5634
- union_id: readString$1(fromUserId.union_id)
5682
+ open_id: readStringValue(fromUserId.open_id),
5683
+ user_id: readStringValue(fromUserId.user_id),
5684
+ union_id: readStringValue(fromUserId.union_id)
5635
5685
  } : void 0,
5636
- notice_type: readString$1(noticeMeta.notice_type),
5686
+ notice_type: readStringValue(noticeMeta.notice_type),
5637
5687
  to_user_id: toUserId ? {
5638
- open_id: readString$1(toUserId.open_id),
5639
- user_id: readString$1(toUserId.user_id),
5640
- union_id: readString$1(toUserId.union_id)
5688
+ open_id: readStringValue(toUserId.open_id),
5689
+ user_id: readStringValue(toUserId.user_id),
5690
+ union_id: readStringValue(toUserId.union_id)
5641
5691
  } : void 0
5642
5692
  },
5643
- reply_id: readString$1(value.reply_id),
5644
- timestamp: readString$1(value.timestamp),
5645
- type: readString$1(value.type)
5693
+ reply_id: readStringValue(value.reply_id),
5694
+ timestamp: readStringValue(value.timestamp),
5695
+ type: readStringValue(value.type)
5646
5696
  };
5647
5697
  }
5648
5698
  async function resolveDriveCommentEventTurn(params) {
@@ -5742,7 +5792,7 @@ async function handleFeishuCommentEvent(params) {
5742
5792
  channel: "feishu",
5743
5793
  accountId: account.accountId
5744
5794
  });
5745
- const resolveCommentAuthorization = async (candidateCfg, mayPair) => {
5795
+ const resolveCommentAuthorization = async (candidateCfg, mayPair, contextBinding) => {
5746
5796
  const candidateAccount = resolveFeishuRuntimeAccount({
5747
5797
  cfg: candidateCfg,
5748
5798
  accountId: account.accountId
@@ -5761,7 +5811,8 @@ async function handleFeishuCommentEvent(params) {
5761
5811
  senderOpenId: turn.senderId,
5762
5812
  senderUserId: turn.senderUserId,
5763
5813
  conversationId: turn.senderId,
5764
- mayPair
5814
+ mayPair,
5815
+ ...contextBinding ? { contextBinding } : {}
5765
5816
  })
5766
5817
  };
5767
5818
  };
@@ -5853,7 +5904,14 @@ async function handleFeishuCommentEvent(params) {
5853
5904
  const bodyForAgent = `[message_id: ${turn.messageId}]\n${turn.prompt}`;
5854
5905
  const rawBody = turn.targetReplyText ?? turn.rootCommentText ?? turn.prompt;
5855
5906
  const conversationLabel = turn.documentTitle ? `Feishu comment · ${turn.documentTitle}` : "Feishu comment";
5856
- const ctxPayload = buildChannelInboundEventContext({
5907
+ const boundAuthorization = await resolveCommentAuthorization(effectiveCfg, false, {
5908
+ agentId: route.agentId,
5909
+ sessionKey: commentSessionKey,
5910
+ messageId: turn.messageId,
5911
+ inboundEventKind: "user_request"
5912
+ });
5913
+ const ctxPayload = core.channel.inbound.buildContext({
5914
+ channelIngress: boundAuthorization.ingress,
5857
5915
  channel: "feishu",
5858
5916
  accountId: route.accountId,
5859
5917
  surface: "feishu-comment",
@@ -6347,7 +6405,7 @@ const FEISHU_WS_LOG_ERROR_MAX_LENGTH = 500;
6347
6405
  const FEISHU_WS_RECONNECT_EXHAUSTED_RE = /^WebSocket reconnect exhausted after \d+ attempts?/;
6348
6406
  const FEISHU_WS_AUTORECONNECT_DISABLED_ERROR = "WebSocket connect failed and autoReconnect is disabled";
6349
6407
  function isFeishuWebhookPayload(value) {
6350
- return Boolean(value) && typeof value === "object" && !Array.isArray(value);
6408
+ return isRecord(value);
6351
6409
  }
6352
6410
  const BLOCKED_FEISHU_WEBHOOK_PAYLOAD_KEYS = /* @__PURE__ */ new Set([
6353
6411
  "__proto__",
@@ -6452,16 +6510,15 @@ async function monitorWebSocket({ account, accountId, runtime, abortSignal, even
6452
6510
  };
6453
6511
  const publishWsConnected = () => {
6454
6512
  const connectedAt = Date.now();
6455
- statusSink?.({
6456
- connected: true,
6513
+ statusSink?.(channelReadyPatch({
6457
6514
  lastConnectedAt: connectedAt,
6458
- lastEventAt: connectedAt,
6459
- lastError: null
6460
- });
6515
+ lastEventAt: connectedAt
6516
+ }));
6461
6517
  };
6462
6518
  const publishWsReconnecting = () => {
6463
6519
  statusSink?.({
6464
6520
  connected: false,
6521
+ lifecycle: "recovering",
6465
6522
  lastEventAt: Date.now()
6466
6523
  });
6467
6524
  };
@@ -6509,10 +6566,11 @@ async function monitorWebSocket({ account, accountId, runtime, abortSignal, even
6509
6566
  });
6510
6567
  setSocketTerminator?.(void 0);
6511
6568
  if (abortSignal?.aborted) break;
6512
- statusSink?.({
6569
+ const disconnectedAt = Date.now();
6570
+ statusSink?.(channelBlockedPatch(formatFeishuWsErrorForLog(cycleEnd), {
6513
6571
  connected: false,
6514
- lastEventAt: Date.now()
6515
- });
6572
+ lastEventAt: disconnectedAt
6573
+ }));
6516
6574
  attempt += 1;
6517
6575
  const delayMs = getFeishuWsReconnectDelayMs(attempt);
6518
6576
  error(`feishu[${accountId}]: WebSocket connection ended, recreating client in ${delayMs}ms: ${formatFeishuWsErrorForLog(cycleEnd)}`);
@@ -6526,9 +6584,14 @@ async function monitorWebSocket({ account, accountId, runtime, abortSignal, even
6526
6584
  });
6527
6585
  setSocketTerminator?.(void 0);
6528
6586
  if (abortSignal?.aborted) break;
6587
+ const failedAt = Date.now();
6588
+ const terminal = err instanceof Error && isFeishuWsTerminalError(err);
6529
6589
  statusSink?.({
6530
6590
  connected: false,
6531
- lastEventAt: Date.now()
6591
+ lifecycle: terminal ? "blocked" : "recovering",
6592
+ ...terminal ? { terminalDisconnect: true } : {},
6593
+ lastEventAt: failedAt,
6594
+ lastError: formatFeishuWsErrorForLog(err)
6532
6595
  });
6533
6596
  attempt += 1;
6534
6597
  const delayMs = getFeishuWsReconnectDelayMs(attempt);
@@ -6551,10 +6614,18 @@ async function monitorWebhook({ account, accountId, runtime, abortSignal, eventD
6551
6614
  if (!encryptKey) throw new Error(`Feishu account "${accountId}" webhook mode requires encryptKey`);
6552
6615
  const port = account.config.webhookPort ?? 3e3;
6553
6616
  const path = account.config.webhookPath ?? "/feishu/events";
6617
+ if (normalizeFeishuWebhookPath(path) !== path) throw new Error(`Feishu account "${accountId}" webhookPath must be a canonical HTTP request path; run "openclaw doctor --fix" to repair it`);
6554
6618
  const host = account.config.webhookHost ?? "127.0.0.1";
6555
6619
  log(`feishu[${accountId}]: starting Webhook server on ${host}:${port}, path ${path}...`);
6556
6620
  const server = http.createServer();
6557
6621
  server.on("request", (req, res) => {
6622
+ const requestUrl = req.url ?? "/";
6623
+ const requestPath = requestUrl.split("?", 1)[0];
6624
+ const requestRoute = path.includes("?") ? requestUrl : requestPath;
6625
+ if (!requestPath?.startsWith("/") || requestUrl.includes("#") || requestRoute !== path) {
6626
+ respondText(res, 404, "Not Found");
6627
+ return;
6628
+ }
6558
6629
  res.on("finish", () => {
6559
6630
  recordWebhookStatus(runtime, accountId, path, res.statusCode);
6560
6631
  if (res.statusCode >= 200 && res.statusCode < 300) {
@@ -6568,6 +6639,7 @@ async function monitorWebhook({ account, accountId, runtime, abortSignal, eventD
6568
6639
  if (!applyBasicWebhookRequestGuards({
6569
6640
  req,
6570
6641
  res,
6642
+ allowMethods: ["POST"],
6571
6643
  rateLimiter: feishuWebhookRateLimiter,
6572
6644
  rateLimitKey: buildFeishuWebhookRateLimitKey({
6573
6645
  accountId,
@@ -6654,15 +6726,18 @@ async function monitorWebhook({ account, accountId, runtime, abortSignal, eventD
6654
6726
  server.listen(port, host, () => {
6655
6727
  log(`feishu[${accountId}]: Webhook server listening on ${host}:${port}`);
6656
6728
  const webhookConnectedAt = Date.now();
6657
- statusSink?.({
6658
- connected: true,
6729
+ statusSink?.(channelReadyPatch({
6659
6730
  lastConnectedAt: webhookConnectedAt,
6660
- lastEventAt: webhookConnectedAt,
6661
- lastError: null
6662
- });
6731
+ lastEventAt: webhookConnectedAt
6732
+ }));
6663
6733
  });
6664
6734
  server.on("error", (err) => {
6665
6735
  error(`feishu[${accountId}]: Webhook server error: ${err}`);
6736
+ statusSink?.({
6737
+ connected: false,
6738
+ lifecycle: "recovering",
6739
+ lastError: formatFeishuWsErrorForLog(err)
6740
+ });
6666
6741
  abortSignal?.removeEventListener("abort", handleAbort);
6667
6742
  reject(err);
6668
6743
  });
@@ -6799,10 +6874,11 @@ async function resolveReactionSyntheticEvent(params) {
6799
6874
  const { cfg, accountId, event, botOpenId, fetchMessage = getMessageFeishu, verificationTimeoutMs = FEISHU_REACTION_VERIFY_TIMEOUT_MS, logger, uuid = () => crypto$1.randomUUID(), action = "created" } = params;
6800
6875
  const emoji = event.reaction_type?.emoji_type;
6801
6876
  const messageId = event.message_id;
6802
- const senderId = event.user_id?.open_id;
6803
- const senderUserId = event.user_id?.user_id;
6877
+ const senderOpenId = event.user_id?.open_id?.trim();
6878
+ const senderUserId = event.user_id?.user_id?.trim();
6879
+ const senderId = senderOpenId || senderUserId;
6804
6880
  if (!emoji || !messageId || !senderId) return null;
6805
- const { resolveFeishuAccount } = await import("./accounts-u9X5Wsan.js").then((n) => n.t);
6881
+ const { resolveFeishuAccount } = await import("./accounts-CCCdMen2.js").then((n) => n.t);
6806
6882
  const reactionNotifications = resolveFeishuAccount({
6807
6883
  cfg,
6808
6884
  accountId
@@ -6836,14 +6912,17 @@ async function resolveReactionSyntheticEvent(params) {
6836
6912
  return {
6837
6913
  sender: {
6838
6914
  sender_id: {
6839
- open_id: senderId,
6915
+ ...senderOpenId ? { open_id: senderOpenId } : {},
6840
6916
  ...senderUserId ? { user_id: senderUserId } : {}
6841
6917
  },
6842
6918
  sender_type: "user"
6843
6919
  },
6844
6920
  message: {
6845
6921
  message_id: `${messageId}:reaction:${emoji}:${uuid()}`,
6922
+ reply_target_message_id: messageId,
6846
6923
  typing_target_message_id: messageId,
6924
+ ...reactedMsg.rootId ? { root_id: reactedMsg.rootId } : {},
6925
+ ...reactedMsg.threadId ? { thread_id: reactedMsg.threadId } : {},
6847
6926
  chat_id: syntheticChatId,
6848
6927
  chat_type: syntheticChatType,
6849
6928
  message_type: "text",
@@ -6855,41 +6934,41 @@ function normalizeFeishuChatType(value) {
6855
6934
  return value === "group" || value === "topic_group" || value === "private" || value === "p2p" ? value : void 0;
6856
6935
  }
6857
6936
  function parseFeishuBotAddedEventPayload(value) {
6858
- if (!isRecord$2(value) || !readString$1(value.chat_id) || !isRecord$2(value.operator_id)) return null;
6937
+ if (!isRecord(value) || !readStringValue(value.chat_id) || !isRecord(value.operator_id)) return null;
6859
6938
  return value;
6860
6939
  }
6861
6940
  function parseFeishuBotRemovedChatId(value) {
6862
- if (!isRecord$2(value)) return null;
6863
- return readString$1(value.chat_id) ?? null;
6941
+ if (!isRecord(value)) return null;
6942
+ return readStringValue(value.chat_id) ?? null;
6864
6943
  }
6865
6944
  function firstString(...values) {
6866
6945
  for (const value of values) {
6867
- const trimmed = readString$1(value)?.trim();
6946
+ const trimmed = readStringValue(value)?.trim();
6868
6947
  if (trimmed) return trimmed;
6869
6948
  }
6870
6949
  }
6871
6950
  function readFeishuIdentityField(value, field) {
6872
- if (!isRecord$2(value)) return;
6951
+ if (!isRecord(value)) return;
6873
6952
  return firstString(value[field]);
6874
6953
  }
6875
6954
  function parseFeishuCardActionEventPayload(value) {
6876
- if (!isRecord$2(value)) return null;
6877
- const operator = isRecord$2(value.operator) ? value.operator : {};
6955
+ if (!isRecord(value)) return null;
6956
+ const operator = isRecord(value.operator) ? value.operator : {};
6878
6957
  const action = value.action;
6879
- const context = isRecord$2(value.context) ? value.context : {};
6880
- if (!isRecord$2(action)) return null;
6958
+ const context = isRecord(value.context) ? value.context : {};
6959
+ if (!isRecord(action)) return null;
6881
6960
  const operatorUserId = operator.user_id;
6882
- const token = readString$1(value.token);
6961
+ const token = readStringValue(value.token);
6883
6962
  const openId = firstString(operator.open_id, readFeishuIdentityField(operatorUserId, "open_id"), value.open_id, context.open_id);
6884
6963
  const userId = firstString(operator.user_id, readFeishuIdentityField(operatorUserId, "user_id"), value.user_id, context.user_id);
6885
6964
  const unionId = firstString(operator.union_id, readFeishuIdentityField(operatorUserId, "union_id"));
6886
- const tag = readString$1(action.tag);
6965
+ const tag = readStringValue(action.tag);
6887
6966
  const actionValue = action.value;
6888
6967
  const openMessageId = firstString(context.open_message_id, value.open_message_id);
6889
6968
  const contextOpenId = firstString(context.open_id, openId);
6890
6969
  const contextUserId = firstString(context.user_id, userId);
6891
6970
  const chatId = firstString(context.chat_id, context.open_chat_id);
6892
- if (!token || !openId || !tag || !isRecord$2(actionValue)) return null;
6971
+ if (!token || !openId || !tag || !isRecord(actionValue)) return null;
6893
6972
  return {
6894
6973
  operator: {
6895
6974
  open_id: openId,