@openclaw/feishu 2026.9.1-beta.1 → 2026.9.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/api.js +211 -275
- package/dist/{app-registration-BpfUzS9m.js → app-registration-BI99e_JI.js} +12 -9
- package/dist/{channel-Dpgf8wLD.js → channel-DSYCNah8.js} +239 -580
- package/dist/channel-plugin-api.js +1 -1
- package/dist/{channel.runtime-BIEERwaT.js → channel.runtime-EfDl2Q8q.js} +14 -18
- package/dist/{chat-C1nuW5UY.js → chat-CxXaPTuO.js} +13 -13
- package/dist/{client-DalLmpMf.js → client-DMbIL3UH.js} +3 -6
- package/dist/contract-api.js +2 -2
- package/dist/doctor-contract-CQAuBotF.js +465 -0
- package/dist/doctor-contract-api.js +1 -1
- package/dist/index.js +11 -42
- package/dist/{json-response-CheljwGr.js → json-response-DXajw_FU.js} +1 -1
- package/dist/{monitor.startup-D-gL82OO.js → monitor-DoMmOfaD.js} +62 -2
- package/dist/{monitor.account-DhhotI_Q.js → monitor.account-WFdlCDhR.js} +261 -293
- package/dist/{probe-CfhhR_0a.js → probe-D_SatdZl.js} +3 -3
- package/dist/{presentation-card-C8Z7Tgru.js → reply-delivery-result-CEFTDA3f.js} +333 -239
- package/dist/{security-audit-D6Fz2h6p.js → security-audit-DKfR4Cv3.js} +1 -1
- package/dist/{security-audit-shared-BgpY7AiJ.js → security-audit-shared-CK5rVR-1.js} +2 -1
- package/dist/security-contract-api.js +1 -1
- package/dist/session-binding-contract-api.js +1 -1
- package/dist/setup-api.js +1 -1
- package/dist/{subagent-hooks-KUi-7vUy.js → subagent-hooks-Buadpie_.js} +3 -6
- package/dist/subagent-hooks-api.js +1 -1
- package/dist/{targets-BUjQ1TcA.js → targets-BTQCYRZQ.js} +4 -4
- package/dist/{thread-bindings-CptM_YU6.js → thread-bindings-BiL1wGqK.js} +20 -9
- package/node_modules/@larksuiteoapi/node-sdk/node_modules/ws/lib/permessage-deflate.js +3 -1
- package/node_modules/@larksuiteoapi/node-sdk/node_modules/ws/package.json +1 -1
- package/node_modules/typebox/build/format/email.mjs +1 -1
- package/node_modules/typebox/build/format/idna/idn-hostname.mjs +19 -1
- package/node_modules/typebox/package.json +1 -1
- package/openclaw.plugin.json +43 -236
- package/package.json +6 -5
- package/dist/doctor-contract-bEQXIXyP.js +0 -158
- package/dist/monitor-BRg3sHH7.js +0 -62
|
@@ -1,18 +1,18 @@
|
|
|
1
1
|
import { o as resolveFeishuAccount, s as resolveFeishuRuntimeAccount } from "./accounts-RwSyseKr.js";
|
|
2
|
-
import { i as resolveReceiveIdType } from "./targets-
|
|
3
|
-
import { $ as
|
|
4
|
-
import {
|
|
2
|
+
import { i as resolveReceiveIdType } from "./targets-BTQCYRZQ.js";
|
|
3
|
+
import { $ as parseCommentContentElements, A as isFeishuBroadcastMention, At as decodeFeishuCardAction, B as deliverCommentThreadText, D as sendMessageFeishu, E as sendCardFeishu, Et as resolveFeishuChatType, L as buildFeishuMediaFallbackText, M as isFeishuGroupChatType, N as parseInteractiveCardContent, O as sendStructuredCardFeishu, Ot as buildFeishuCardActionTextFallback, P as parsePostContent, R as resolveFeishuIdentityEmoji, S as chunkFeishuCardMarkdown, St as resolveFeishuGroupSenderActivationIngressAccess, T as listFeishuThreadMessages, Tt as normalizeFeishuChatType$2, X as encodeQuery, Y as createCommentTypingReactionLifecycle, Z as extractReplyText, _ as resolveFeishuRichReply, _t as hasExplicitFeishuGroupConfig, at as resolveFeishuCardTemplate, bt as resolveFeishuGroupConfig, ct as materializeFeishuPostMarkdownSoftBreaks, et as requestFeishuApi, g as renderFeishuReplyPayload, i as noVisibleFeishuReplyDelivery, j as isMentionForwardRequest, k as extractMentionTargets, kt as createFeishuCardInteractionEnvelope, l as buildFeishuPresentationFallback, n as createFeishuReplyDeliveryResult, nt as normalizeCommentFileType, r as mergeFeishuReplyDeliveryResults, st as chunkFeishuPostMarkdown, t as createFeishuPartialReplyDeliveryError, tt as buildFeishuCommentTarget, u as consumeFeishuPresentationFallbackMarker, v as saveMessageResourceFeishu, vt as normalizeFeishuAllowEntry, w as getMessageFeishu, wt as resolveFeishuReplyPolicy, x as shouldSuppressFeishuTextForVoiceMedia, xt as resolveFeishuGroupConversationIngressAccess, y as sendMediaFeishu, yt as resolveFeishuDmIngressAccess } from "./reply-delivery-result-CEFTDA3f.js";
|
|
4
|
+
import { a as normalizeFeishuWebhookPath, o as normalizeFeishuExternalKey } from "./doctor-contract-CQAuBotF.js";
|
|
5
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-
|
|
6
|
+
import { a as getFeishuUserAgent, i as createFeishuWSClient, n as createEventDispatcher, r as createFeishuClient, s as resolveConfiguredHttpTimeoutMs } from "./client-DMbIL3UH.js";
|
|
7
7
|
import { t as getFeishuRuntime } from "./runtime-C5JxBWZp.js";
|
|
8
|
-
import { r as getChatInfo } from "./chat-
|
|
9
|
-
import { t as createFeishuThreadBindingManager } from "./thread-bindings-
|
|
8
|
+
import { r as getChatInfo } from "./chat-CxXaPTuO.js";
|
|
9
|
+
import { t as createFeishuThreadBindingManager } from "./thread-bindings-BiL1wGqK.js";
|
|
10
10
|
import { evaluateSupplementalContextVisibility, normalizeAgentId as normalizeAgentId$1, resolveChannelContextVisibilityMode } from "./runtime-api.js";
|
|
11
|
-
import { t as readFeishuJsonResponse } from "./json-response-
|
|
12
|
-
import { a as waitForAbortableDelay, i as raceWithTimeoutAndAbort } from "./probe-
|
|
13
|
-
import { t as fetchBotIdentityForMonitor } from "./monitor
|
|
11
|
+
import { t as readFeishuJsonResponse } from "./json-response-DXajw_FU.js";
|
|
12
|
+
import { a as waitForAbortableDelay, i as raceWithTimeoutAndAbort } from "./probe-D_SatdZl.js";
|
|
13
|
+
import { t as fetchBotIdentityForMonitor } from "./monitor-DoMmOfaD.js";
|
|
14
14
|
import { resolveAgentConfig } from "openclaw/plugin-sdk/agent-scope-runtime";
|
|
15
|
-
import { DEFAULT_INGRESS_ADOPTION_STALL_MS, bindIngressLifecycleToReplyOptions, createChannelIngressError, createChannelIngressMonitor, createChannelMessageReplyPipeline,
|
|
15
|
+
import { DEFAULT_INGRESS_ADOPTION_STALL_MS, bindIngressLifecycleToReplyOptions, createChannelIngressError, createChannelIngressMonitor, createChannelMessageReplyPipeline, createReplyPrefixContext, formatChannelProgressDraftLineForEntry, isChannelProgressDraftWorkToolName, resolveAgentOutboundIdentity, resolveChannelPreviewStreamMode, resolveChannelStreamingBlockEnabled } from "openclaw/plugin-sdk/channel-outbound";
|
|
16
16
|
import { createChannelPairingController } from "openclaw/plugin-sdk/channel-pairing";
|
|
17
17
|
import { ensureConfiguredBindingRouteReady, resolveConfiguredBindingRoute, resolveRuntimeConversationBindingRoute } from "openclaw/plugin-sdk/conversation-runtime";
|
|
18
18
|
import { collectErrorGraphCandidates, formatErrorMessage, toStringifiedError } from "openclaw/plugin-sdk/error-runtime";
|
|
@@ -26,7 +26,7 @@ import os from "node:os";
|
|
|
26
26
|
import path from "node:path";
|
|
27
27
|
import { normalizeAccountId, resolveAgentRoute, resolveInboundLastRouteSessionKey } from "openclaw/plugin-sdk/routing";
|
|
28
28
|
import { getSessionEntry, resolveStorePath } from "openclaw/plugin-sdk/session-store-runtime";
|
|
29
|
-
import {
|
|
29
|
+
import { formatAgentEnvelope, formatInboundMediaUnavailableText, isChannelPartialDeliveryError, recordChannelBotPairLoopAndCheckSuppression, resolveEnvelopeFormatOptions, resolveInboundReplyDispatchCounts, toInboundMediaFactsWithMetadata } from "openclaw/plugin-sdk/channel-inbound";
|
|
30
30
|
import { getReplyPayloadTtsSupplement, resolveSendableOutboundReplyParts, resolveTextChunksWithFallback, sendMediaWithLeadingCaption } from "openclaw/plugin-sdk/reply-payload";
|
|
31
31
|
import { resolvePinnedMainDmOwnerFromAllowlist, safeEqualSecret } from "openclaw/plugin-sdk/security-runtime";
|
|
32
32
|
import * as Lark from "@larksuiteoapi/node-sdk";
|
|
@@ -53,7 +53,7 @@ function formatMentionNameForAgentContext(name) {
|
|
|
53
53
|
const normalized = Array.from(name, (char) => {
|
|
54
54
|
return char.charCodeAt(0) < 32 || char === "[" || char === "]" ? " " : char;
|
|
55
55
|
}).join("").replace(/\s+/g, " ").trim();
|
|
56
|
-
const bounded = normalized.length > MAX_MENTION_CONTEXT_NAME_LENGTH ? `${truncateUtf16Safe(normalized,
|
|
56
|
+
const bounded = normalized.length > MAX_MENTION_CONTEXT_NAME_LENGTH ? `${truncateUtf16Safe(normalized, 77)}...` : normalized;
|
|
57
57
|
return JSON.stringify(bounded || "unknown");
|
|
58
58
|
}
|
|
59
59
|
function buildFeishuAgentBody(params) {
|
|
@@ -196,6 +196,7 @@ function createFeishuBroadcastIngressSettlement(params) {
|
|
|
196
196
|
lane.status = "deferred";
|
|
197
197
|
defer();
|
|
198
198
|
},
|
|
199
|
+
onDeferredHeartbeat: () => params.lifecycle?.onDeferredHeartbeat?.(),
|
|
199
200
|
onAdoptionFinalizing: beginFinalizing,
|
|
200
201
|
onAbandoned: async () => {
|
|
201
202
|
if (lane.status === "completed" || lane.status === "failed" || lane.status === "abandoned") return;
|
|
@@ -289,9 +290,7 @@ function resolveFeishuGroupSession(params) {
|
|
|
289
290
|
senderOpenId
|
|
290
291
|
});
|
|
291
292
|
break;
|
|
292
|
-
default:
|
|
293
|
-
peerId = chatId;
|
|
294
|
-
break;
|
|
293
|
+
default: peerId = chatId;
|
|
295
294
|
}
|
|
296
295
|
return {
|
|
297
296
|
peerId,
|
|
@@ -540,7 +539,7 @@ async function resolveFeishuMediaList(params) {
|
|
|
540
539
|
const feishuGroupNameCache = /* @__PURE__ */ new Map();
|
|
541
540
|
//#endregion
|
|
542
541
|
//#region extensions/feishu/src/bot-group-name.ts
|
|
543
|
-
const GROUP_NAME_CACHE_TTL_MS =
|
|
542
|
+
const GROUP_NAME_CACHE_TTL_MS = 18e5;
|
|
544
543
|
const GROUP_NAME_CACHE_MAX_SIZE = 500;
|
|
545
544
|
function evictGroupNameCache() {
|
|
546
545
|
const now = asDateTimestampMs(Date.now());
|
|
@@ -582,7 +581,8 @@ async function resolveGroupName(params) {
|
|
|
582
581
|
}
|
|
583
582
|
let resolvedName;
|
|
584
583
|
try {
|
|
585
|
-
const
|
|
584
|
+
const client = createFeishuClient(account);
|
|
585
|
+
const name = (await getChatInfo(client, chatId))?.name?.trim();
|
|
586
586
|
if (name) {
|
|
587
587
|
setCacheEntry(cacheKey, name);
|
|
588
588
|
resolvedName = name;
|
|
@@ -596,11 +596,11 @@ async function resolveGroupName(params) {
|
|
|
596
596
|
}
|
|
597
597
|
//#endregion
|
|
598
598
|
//#region extensions/feishu/src/bot-name.ts
|
|
599
|
-
const POSITIVE_TTL_MS =
|
|
599
|
+
const POSITIVE_TTL_MS = 6e5;
|
|
600
600
|
const NEGATIVE_TTL_MS = 6e4;
|
|
601
601
|
const MAX_CACHE_ENTRIES = 5e3;
|
|
602
602
|
const BREAKER_FAILURE_THRESHOLD = 10;
|
|
603
|
-
const BREAKER_OPEN_MS =
|
|
603
|
+
const BREAKER_OPEN_MS = 36e5;
|
|
604
604
|
const PERMISSION_BACKOFF_MS = 6e4;
|
|
605
605
|
const REQUEST_TIMEOUT_MS = 1500;
|
|
606
606
|
const cache = /* @__PURE__ */ new Map();
|
|
@@ -738,8 +738,8 @@ async function resolveFeishuBotName(params) {
|
|
|
738
738
|
const IGNORED_PERMISSION_SCOPE_TOKENS = ["contact:contact.base:readonly"];
|
|
739
739
|
const FEISHU_SCOPE_CORRECTIONS = { "contact:contact.base:readonly": "contact:user.base:readonly" };
|
|
740
740
|
const FEISHU_USER_LOOKUP_UNAUTHORIZED_CODE = 41050;
|
|
741
|
-
const SENDER_NAME_TTL_MS =
|
|
742
|
-
const SENDER_NAME_NEGATIVE_TTL_MS =
|
|
741
|
+
const SENDER_NAME_TTL_MS = 6e5;
|
|
742
|
+
const SENDER_NAME_NEGATIVE_TTL_MS = 18e5;
|
|
743
743
|
const SENDER_NAME_CACHE_MAX_SIZE = 500;
|
|
744
744
|
const senderNameCache = /* @__PURE__ */ new Map();
|
|
745
745
|
function correctFeishuScopeInUrl(url) {
|
|
@@ -840,7 +840,7 @@ async function resolveFeishuSenderName(params) {
|
|
|
840
840
|
//#endregion
|
|
841
841
|
//#region extensions/feishu/src/dedup-state.ts
|
|
842
842
|
const DEDUPE_NAMESPACE_PREFIX = "feishu.dedup";
|
|
843
|
-
const DEDUP_TTL_MS =
|
|
843
|
+
const DEDUP_TTL_MS = 864e5;
|
|
844
844
|
const MEMORY_MAX_SIZE = 1e3;
|
|
845
845
|
const STORE_MAX_ENTRIES = 1e4;
|
|
846
846
|
function createFeishuDedupeGuard() {
|
|
@@ -1145,48 +1145,6 @@ function resolveFeishuReasoningPreviewEnabled(params) {
|
|
|
1145
1145
|
return configDefault === "stream";
|
|
1146
1146
|
}
|
|
1147
1147
|
//#endregion
|
|
1148
|
-
//#region extensions/feishu/src/reply-delivery-result.ts
|
|
1149
|
-
const noVisibleFeishuReplyDelivery = { visibleReplySent: false };
|
|
1150
|
-
function hasProviderIdentity(result) {
|
|
1151
|
-
return Boolean(result && (result.messageId?.trim() || result.receipt?.primaryPlatformMessageId?.trim() || result.receipt?.platformMessageIds.length));
|
|
1152
|
-
}
|
|
1153
|
-
/** Normalizes every physical Lark send behind one logical reply payload. */
|
|
1154
|
-
function createFeishuReplyDeliveryResult(params) {
|
|
1155
|
-
const results = params.visibleReplySent ? (params.results ?? []).filter(hasProviderIdentity) : [];
|
|
1156
|
-
const receipt = results.length > 0 ? createMessageReceiptFromOutboundResults({
|
|
1157
|
-
results,
|
|
1158
|
-
...params.kind ? { kind: params.kind } : {}
|
|
1159
|
-
}) : void 0;
|
|
1160
|
-
if (!receipt) return {
|
|
1161
|
-
visibleReplySent: params.visibleReplySent,
|
|
1162
|
-
...params.content === void 0 ? {} : { content: params.content }
|
|
1163
|
-
};
|
|
1164
|
-
return {
|
|
1165
|
-
messageIds: [...receipt.platformMessageIds],
|
|
1166
|
-
receipt,
|
|
1167
|
-
visibleReplySent: params.visibleReplySent,
|
|
1168
|
-
...params.content === void 0 ? {} : { content: params.content }
|
|
1169
|
-
};
|
|
1170
|
-
}
|
|
1171
|
-
/** Preserves the first result's provider identity while retaining supplemental ids. */
|
|
1172
|
-
function mergeFeishuReplyDeliveryResults(results, content) {
|
|
1173
|
-
const visible = results.filter((result) => result.visibleReplySent === true);
|
|
1174
|
-
const acceptedContent = visible.flatMap((result) => result.content === void 0 ? [] : [result.content]);
|
|
1175
|
-
return createFeishuReplyDeliveryResult({
|
|
1176
|
-
results: visible,
|
|
1177
|
-
visibleReplySent: visible.length > 0,
|
|
1178
|
-
content: content === void 0 ? acceptedContent.length > 0 ? acceptedContent.join("\n\n") : void 0 : content
|
|
1179
|
-
});
|
|
1180
|
-
}
|
|
1181
|
-
/** Keeps a failed outcome while exposing the provider-visible subset to core lifecycle hooks. */
|
|
1182
|
-
function createFeishuPartialReplyDeliveryError(cause, result) {
|
|
1183
|
-
if (result.visibleReplySent !== true) return cause instanceof Error ? cause : new Error(formatErrorMessage(cause), { cause });
|
|
1184
|
-
return createChannelPartialDeliveryError(cause, {
|
|
1185
|
-
...result,
|
|
1186
|
-
visibleReplySent: true
|
|
1187
|
-
});
|
|
1188
|
-
}
|
|
1189
|
-
//#endregion
|
|
1190
1148
|
//#region extensions/feishu/src/reply-dispatcher-state.ts
|
|
1191
1149
|
const streamingStartBackoffUntilByAccount = /* @__PURE__ */ new Map();
|
|
1192
1150
|
//#endregion
|
|
@@ -1662,21 +1620,10 @@ var FeishuStreamingSession = class {
|
|
|
1662
1620
|
if (finalWriteError !== void 0 || closeError !== void 0) throw new FeishuStreamingFinalizationError(finalWriteError !== void 0 && closeError !== void 0 ? new AggregateError([finalWriteError, closeError], "Feishu streaming card finalization failed") : finalWriteError ?? closeError, result);
|
|
1663
1621
|
return result;
|
|
1664
1622
|
}
|
|
1665
|
-
async close(finalText, options) {
|
|
1666
|
-
try {
|
|
1667
|
-
return (await this.closeWithResult(finalText, options)).visibleReplySent;
|
|
1668
|
-
} catch (error) {
|
|
1669
|
-
if (error instanceof FeishuStreamingFinalizationError) return error.result.visibleReplySent;
|
|
1670
|
-
throw error;
|
|
1671
|
-
}
|
|
1672
|
-
}
|
|
1673
1623
|
async discard() {
|
|
1674
|
-
if (!this.state || this.closed) return;
|
|
1624
|
+
if (!this.state || this.closed) return { visibleReplySent: false };
|
|
1675
1625
|
const { cardId, messageId } = this.state;
|
|
1676
|
-
if (!messageId)
|
|
1677
|
-
await this.close("");
|
|
1678
|
-
return;
|
|
1679
|
-
}
|
|
1626
|
+
if (!messageId) return this.closeWithResult("");
|
|
1680
1627
|
this.closed = true;
|
|
1681
1628
|
this.clearFlushTimer();
|
|
1682
1629
|
await this.queue;
|
|
@@ -1686,10 +1633,11 @@ var FeishuStreamingSession = class {
|
|
|
1686
1633
|
this.state = null;
|
|
1687
1634
|
this.pendingText = null;
|
|
1688
1635
|
this.log?.(`Discarded streaming card: cardId=${cardId}`);
|
|
1636
|
+
return { visibleReplySent: false };
|
|
1689
1637
|
} catch (error) {
|
|
1690
1638
|
this.log?.(`Discard failed: ${String(error)}`);
|
|
1691
1639
|
this.closed = false;
|
|
1692
|
-
|
|
1640
|
+
return this.closeWithResult("");
|
|
1693
1641
|
}
|
|
1694
1642
|
}
|
|
1695
1643
|
isActive() {
|
|
@@ -1820,7 +1768,7 @@ function mergeStreamingFinalText(previousText, nextText, appendError) {
|
|
|
1820
1768
|
}
|
|
1821
1769
|
/** Maximum age (ms) for a message to receive a typing indicator reaction.
|
|
1822
1770
|
* Messages older than this are likely replays after context compaction (#30418). */
|
|
1823
|
-
const TYPING_INDICATOR_MAX_AGE_MS =
|
|
1771
|
+
const TYPING_INDICATOR_MAX_AGE_MS = 12e4;
|
|
1824
1772
|
const MS_EPOCH_MIN = 0xe8d4a51000;
|
|
1825
1773
|
const STREAMING_START_FAILURE_BACKOFF_MS = 6e4;
|
|
1826
1774
|
const NO_VISIBLE_REPLY_FALLBACK_TEXT = "⚠️ This reply completed without visible content. The turn may have been interrupted; please retry or ask me to recover from recent context.";
|
|
@@ -1947,9 +1895,7 @@ function createFeishuReplyDispatcher(params) {
|
|
|
1947
1895
|
let activeStreamingGeneration;
|
|
1948
1896
|
let inFlightStreamingClose;
|
|
1949
1897
|
let visibleReplySent = false;
|
|
1950
|
-
let
|
|
1951
|
-
let skippedFinalAssistantMessageIndex;
|
|
1952
|
-
let preparedDeliveryAssistantMessageIndex;
|
|
1898
|
+
let replyOutcome;
|
|
1953
1899
|
let idleSideEffectsPromise = Promise.resolve();
|
|
1954
1900
|
let activeIdleSideEffectsPromise = null;
|
|
1955
1901
|
let idleRequestedForReply = false;
|
|
@@ -2049,97 +1995,86 @@ function createFeishuReplyDispatcher(params) {
|
|
|
2049
1995
|
lastSnapshotTextLength = 0;
|
|
2050
1996
|
hasStreamingFinalText = false;
|
|
2051
1997
|
};
|
|
2052
|
-
const rememberClosedStreamingSettlement = (generation, content, result, error) => {
|
|
2053
|
-
if (generation === void 0 || !content) return;
|
|
1998
|
+
const rememberClosedStreamingSettlement = (generation, content, result, error, disposition = "closed") => {
|
|
1999
|
+
if (generation === void 0 || !content && disposition === "closed") return;
|
|
2054
2000
|
closedStreamingSettlements.set(generation, {
|
|
2001
|
+
disposition,
|
|
2055
2002
|
result,
|
|
2056
2003
|
content,
|
|
2057
2004
|
...error === void 0 ? {} : { error }
|
|
2058
2005
|
});
|
|
2059
2006
|
};
|
|
2060
|
-
const performStreamingClose = async () => {
|
|
2007
|
+
const performStreamingClose = async (disposition) => {
|
|
2061
2008
|
const streamingToClose = streaming;
|
|
2062
2009
|
const generationToClose = activeStreamingGeneration;
|
|
2063
2010
|
const startPromiseToClose = streamingStartPromise;
|
|
2064
2011
|
const updateQueueToClose = partialUpdateQueue;
|
|
2065
2012
|
const finalizedAnswerText = streamText;
|
|
2066
2013
|
const finalizedReasoningText = reasoningText;
|
|
2014
|
+
const outcome = {
|
|
2015
|
+
disposition,
|
|
2016
|
+
...generationToClose === void 0 ? {} : { generation: generationToClose }
|
|
2017
|
+
};
|
|
2067
2018
|
if (generationToClose !== void 0 && activeStreamingGeneration === generationToClose) activeStreamingGeneration = void 0;
|
|
2068
2019
|
try {
|
|
2069
2020
|
if (startPromiseToClose) await startPromiseToClose;
|
|
2070
2021
|
await updateQueueToClose;
|
|
2022
|
+
let result = noVisibleFeishuReplyDelivery;
|
|
2023
|
+
let finalizationError;
|
|
2071
2024
|
if (streamingToClose?.isActive()) {
|
|
2072
2025
|
statusLine = "";
|
|
2073
2026
|
const text = buildCombinedStreamText(finalizedReasoningText, finalizedAnswerText);
|
|
2074
|
-
const finalNote = resolveCardNote(agentId, identity, responsePrefixContextProvider());
|
|
2075
2027
|
let closed;
|
|
2076
2028
|
try {
|
|
2077
|
-
closed = await streamingToClose.
|
|
2029
|
+
if (disposition === "discarded") closed = await streamingToClose.discard();
|
|
2030
|
+
else {
|
|
2031
|
+
const finalNote = resolveCardNote(agentId, identity, responsePrefixContextProvider());
|
|
2032
|
+
closed = await streamingToClose.closeWithResult(text, { note: finalNote });
|
|
2033
|
+
}
|
|
2078
2034
|
} catch (error) {
|
|
2079
2035
|
if (!(error instanceof FeishuStreamingFinalizationError)) throw error;
|
|
2080
|
-
|
|
2081
|
-
|
|
2082
|
-
visibleReplySent: error.result.visibleReplySent,
|
|
2083
|
-
content: error.result.content,
|
|
2084
|
-
kind: "card"
|
|
2085
|
-
});
|
|
2086
|
-
if (failedResult.visibleReplySent) markVisibleReplySent();
|
|
2087
|
-
if (failedResult.visibleReplySent && finalizedAnswerText) {
|
|
2088
|
-
const partialError = createFeishuPartialReplyDeliveryError(error.cause ?? error, failedResult);
|
|
2089
|
-
if (failedResult.content === text) deliveredFinalTexts.add(finalizedAnswerText);
|
|
2090
|
-
rememberClosedStreamingSettlement(generationToClose, finalizedAnswerText, failedResult, partialError);
|
|
2091
|
-
return {
|
|
2092
|
-
result: failedResult,
|
|
2093
|
-
...generationToClose === void 0 ? {} : { generation: generationToClose },
|
|
2094
|
-
error: partialError
|
|
2095
|
-
};
|
|
2096
|
-
}
|
|
2097
|
-
rememberClosedStreamingSettlement(generationToClose, finalizedAnswerText, failedResult, error);
|
|
2098
|
-
return {
|
|
2099
|
-
result: failedResult,
|
|
2100
|
-
...generationToClose === void 0 ? {} : { generation: generationToClose },
|
|
2101
|
-
error
|
|
2102
|
-
};
|
|
2036
|
+
closed = error.result;
|
|
2037
|
+
finalizationError = error;
|
|
2103
2038
|
}
|
|
2104
|
-
|
|
2039
|
+
result = createFeishuReplyDeliveryResult({
|
|
2105
2040
|
results: [closed],
|
|
2106
2041
|
visibleReplySent: closed.visibleReplySent,
|
|
2107
2042
|
content: closed.content,
|
|
2108
2043
|
kind: "card"
|
|
2109
2044
|
});
|
|
2110
2045
|
if (result.visibleReplySent) markVisibleReplySent();
|
|
2111
|
-
if (result.visibleReplySent && finalizedAnswerText)
|
|
2112
|
-
|
|
2113
|
-
rememberClosedStreamingSettlement(generationToClose, finalizedAnswerText, result);
|
|
2114
|
-
}
|
|
2115
|
-
return {
|
|
2116
|
-
result,
|
|
2117
|
-
...generationToClose === void 0 ? {} : { generation: generationToClose }
|
|
2118
|
-
};
|
|
2046
|
+
if (disposition === "closed" && result.visibleReplySent && finalizedAnswerText && (finalizationError === void 0 || result.content === text)) deliveredFinalTexts.add(finalizedAnswerText);
|
|
2047
|
+
if (finalizationError instanceof FeishuStreamingFinalizationError && result.visibleReplySent) finalizationError = createFeishuPartialReplyDeliveryError(finalizationError.cause ?? finalizationError, result);
|
|
2119
2048
|
}
|
|
2049
|
+
if (disposition === "discarded" || finalizationError !== void 0 || result.visibleReplySent && finalizedAnswerText) rememberClosedStreamingSettlement(generationToClose, finalizedAnswerText, result, finalizationError, disposition);
|
|
2120
2050
|
return {
|
|
2121
|
-
|
|
2122
|
-
|
|
2051
|
+
...outcome,
|
|
2052
|
+
result,
|
|
2053
|
+
...finalizationError === void 0 ? {} : { error: finalizationError }
|
|
2123
2054
|
};
|
|
2124
2055
|
} catch (error) {
|
|
2056
|
+
if (disposition === "discarded") rememberClosedStreamingSettlement(generationToClose, finalizedAnswerText, noVisibleFeishuReplyDelivery, error, disposition);
|
|
2125
2057
|
return {
|
|
2058
|
+
...outcome,
|
|
2126
2059
|
result: noVisibleFeishuReplyDelivery,
|
|
2127
|
-
...generationToClose === void 0 ? {} : { generation: generationToClose },
|
|
2128
2060
|
error
|
|
2129
2061
|
};
|
|
2130
2062
|
} finally {
|
|
2131
2063
|
if (streaming === streamingToClose) resetStreamingState();
|
|
2132
2064
|
}
|
|
2133
2065
|
};
|
|
2134
|
-
const closeStreaming = () => {
|
|
2066
|
+
const closeStreaming = (disposition = "closed") => {
|
|
2067
|
+
const session = streaming;
|
|
2135
2068
|
const generation = activeStreamingGeneration;
|
|
2136
|
-
if (
|
|
2069
|
+
if (session && inFlightStreamingClose?.session === session) return inFlightStreamingClose.promise;
|
|
2137
2070
|
const content = streamText;
|
|
2138
|
-
const closePromise = performStreamingClose();
|
|
2139
|
-
if (generation !== void 0) {
|
|
2071
|
+
const closePromise = performStreamingClose(disposition);
|
|
2072
|
+
if (session && generation !== void 0) {
|
|
2140
2073
|
const closing = {
|
|
2074
|
+
session,
|
|
2141
2075
|
generation,
|
|
2142
2076
|
content,
|
|
2077
|
+
disposition,
|
|
2143
2078
|
promise: closePromise
|
|
2144
2079
|
};
|
|
2145
2080
|
inFlightStreamingClose = closing;
|
|
@@ -2171,13 +2106,12 @@ function createFeishuReplyDispatcher(params) {
|
|
|
2171
2106
|
};
|
|
2172
2107
|
};
|
|
2173
2108
|
const discardStreamingPreview = async () => {
|
|
2174
|
-
|
|
2175
|
-
if (streamingStartPromise) await streamingStartPromise;
|
|
2176
|
-
await partialUpdateQueue;
|
|
2177
|
-
if (streaming?.isActive()) await streaming.discard();
|
|
2178
|
-
} finally {
|
|
2109
|
+
if (streaming && inFlightStreamingClose?.session === streaming && inFlightStreamingClose.disposition === "closed") {
|
|
2179
2110
|
resetStreamingState();
|
|
2111
|
+
return;
|
|
2180
2112
|
}
|
|
2113
|
+
const outcome = await closeStreaming("discarded");
|
|
2114
|
+
if (outcome.error !== void 0) throw toStringifiedError(outcome.error);
|
|
2181
2115
|
};
|
|
2182
2116
|
const updateStreamingStatusLine = (nextStatusLine, options) => {
|
|
2183
2117
|
statusLine = nextStatusLine;
|
|
@@ -2189,14 +2123,19 @@ function createFeishuReplyDispatcher(params) {
|
|
|
2189
2123
|
const sendChunkedTextReply = async (paramsLocal) => {
|
|
2190
2124
|
const chunkSource = paramsLocal.useCard ? paramsLocal.text : materializeFeishuPostMarkdownSoftBreaks(core.channel.text.convertMarkdownTables(paramsLocal.text, tableMode));
|
|
2191
2125
|
const initialChunks = core.channel.text.chunkMarkdownTextWithMode(chunkSource, textChunkLimit, chunkMode);
|
|
2192
|
-
const
|
|
2126
|
+
const chunkOptions = {
|
|
2193
2127
|
text: chunkSource,
|
|
2194
2128
|
limit: textChunkLimit,
|
|
2195
2129
|
mode: chunkMode,
|
|
2196
2130
|
firstChunkMentions: paramsLocal.firstChunkMentions,
|
|
2197
2131
|
chunkMentions: paramsLocal.chunkMentions,
|
|
2198
2132
|
initialChunks
|
|
2199
|
-
}
|
|
2133
|
+
};
|
|
2134
|
+
const chunks = resolveTextChunksWithFallback(chunkSource, paramsLocal.useCard ? chunkFeishuCardMarkdown({
|
|
2135
|
+
...chunkOptions,
|
|
2136
|
+
header: paramsLocal.header,
|
|
2137
|
+
note: paramsLocal.note
|
|
2138
|
+
}) : chunkFeishuPostMarkdown(chunkOptions));
|
|
2200
2139
|
const results = [];
|
|
2201
2140
|
const acceptedChunks = [];
|
|
2202
2141
|
for (const [index, chunk] of chunks.entries()) {
|
|
@@ -2235,27 +2174,28 @@ function createFeishuReplyDispatcher(params) {
|
|
|
2235
2174
|
kind: paramsLocal.useCard ? "card" : "text"
|
|
2236
2175
|
});
|
|
2237
2176
|
};
|
|
2177
|
+
const sendPostReply = (text, infoKind, firstChunkMentions) => sendChunkedTextReply({
|
|
2178
|
+
text,
|
|
2179
|
+
useCard: false,
|
|
2180
|
+
infoKind,
|
|
2181
|
+
firstChunkMentions,
|
|
2182
|
+
chunkMentions: requiredMentionTargets,
|
|
2183
|
+
sendChunk: ({ chunk, mentions }) => sendMessageFeishu({
|
|
2184
|
+
cfg,
|
|
2185
|
+
to: sendTarget,
|
|
2186
|
+
text: chunk,
|
|
2187
|
+
replyToMessageId: sendReplyToMessageId,
|
|
2188
|
+
replyInThread: effectiveReplyInThread,
|
|
2189
|
+
allowTopLevelReplyFallback,
|
|
2190
|
+
accountId,
|
|
2191
|
+
...mentions ? { mentions } : {}
|
|
2192
|
+
})
|
|
2193
|
+
});
|
|
2238
2194
|
const sendMediaReplies = async (payload, options) => {
|
|
2239
2195
|
const mediaUrls = resolveSendableOutboundReplyParts(payload).mediaUrls;
|
|
2240
2196
|
let sentFallbackText = false;
|
|
2241
2197
|
let degradedVoiceFallbackText;
|
|
2242
2198
|
const results = [];
|
|
2243
|
-
const sendFallbackText = async (text) => await sendChunkedTextReply({
|
|
2244
|
-
text,
|
|
2245
|
-
useCard: false,
|
|
2246
|
-
infoKind: "final",
|
|
2247
|
-
chunkMentions: requiredMentionTargets,
|
|
2248
|
-
sendChunk: async ({ chunk, mentions }) => await sendMessageFeishu({
|
|
2249
|
-
cfg,
|
|
2250
|
-
to: sendTarget,
|
|
2251
|
-
text: chunk,
|
|
2252
|
-
replyToMessageId: sendReplyToMessageId,
|
|
2253
|
-
replyInThread: effectiveReplyInThread,
|
|
2254
|
-
allowTopLevelReplyFallback,
|
|
2255
|
-
accountId,
|
|
2256
|
-
...mentions ? { mentions } : {}
|
|
2257
|
-
})
|
|
2258
|
-
});
|
|
2259
2199
|
try {
|
|
2260
2200
|
await sendMediaWithLeadingCaption({
|
|
2261
2201
|
mediaUrls,
|
|
@@ -2289,12 +2229,12 @@ function createFeishuReplyDispatcher(params) {
|
|
|
2289
2229
|
mediaUrl
|
|
2290
2230
|
});
|
|
2291
2231
|
sentFallbackText = true;
|
|
2292
|
-
results.push(await
|
|
2232
|
+
results.push(await sendPostReply(fallbackText, "final"));
|
|
2293
2233
|
}
|
|
2294
2234
|
});
|
|
2295
2235
|
if (degradedVoiceFallbackText && !sentFallbackText) {
|
|
2296
2236
|
sentFallbackText = true;
|
|
2297
|
-
results.push(await
|
|
2237
|
+
results.push(await sendPostReply(degradedVoiceFallbackText, "final"));
|
|
2298
2238
|
}
|
|
2299
2239
|
} catch (error) {
|
|
2300
2240
|
const partial = isChannelPartialDeliveryError(error) ? error.deliveryResult : void 0;
|
|
@@ -2306,8 +2246,8 @@ function createFeishuReplyDispatcher(params) {
|
|
|
2306
2246
|
const ensureNoVisibleReplyFallback = async (reason) => {
|
|
2307
2247
|
await idleSideEffectsPromise;
|
|
2308
2248
|
if (visibleReplySent) return false;
|
|
2309
|
-
if (
|
|
2310
|
-
params.runtime.log?.(`feishu[${account.accountId}]: no-visible-reply fallback skipped for
|
|
2249
|
+
if (replyOutcome?.kind === "suppressed" || replyOutcome?.kind === "skipped" && replyOutcome.reason === "silent") {
|
|
2250
|
+
params.runtime.log?.(`feishu[${account.accountId}]: no-visible-reply fallback skipped for ${replyOutcome.reason} (${reason})`);
|
|
2311
2251
|
return false;
|
|
2312
2252
|
}
|
|
2313
2253
|
await sendMessageFeishu({
|
|
@@ -2351,6 +2291,8 @@ function createFeishuReplyDispatcher(params) {
|
|
|
2351
2291
|
text: content,
|
|
2352
2292
|
useCard: true,
|
|
2353
2293
|
infoKind,
|
|
2294
|
+
header: cardHeader,
|
|
2295
|
+
note: cardNote,
|
|
2354
2296
|
chunkMentions: requiredMentionTargets,
|
|
2355
2297
|
sendChunk: async ({ chunk, mentions }) => await sendStructuredCardFeishu({
|
|
2356
2298
|
cfg,
|
|
@@ -2379,10 +2321,17 @@ function createFeishuReplyDispatcher(params) {
|
|
|
2379
2321
|
if (completions.some((completion) => ownsCurrentClose(completion))) markClosedStreamingContentClaimed(closeOutcome.generation);
|
|
2380
2322
|
for (const completion of completions) {
|
|
2381
2323
|
const claimedSettlement = ownsCurrentClose(completion) ? {
|
|
2324
|
+
disposition: closeOutcome.disposition,
|
|
2382
2325
|
result: finalized,
|
|
2383
2326
|
...closeOutcome.error === void 0 ? {} : { error: closeOutcome.error }
|
|
2384
2327
|
} : claimClosedStreamingResult(completion.streamingGeneration, completion.result.content);
|
|
2385
2328
|
const deliveryError = claimedSettlement?.error;
|
|
2329
|
+
if (claimedSettlement?.disposition === "discarded") {
|
|
2330
|
+
const retained = mergeFeishuReplyDeliveryResults([claimedSettlement.result, completion.result], claimedSettlement.result.content ?? "");
|
|
2331
|
+
if (deliveryError !== void 0) completion.reject(createFeishuPartialReplyDeliveryError(deliveryError, retained));
|
|
2332
|
+
else completion.resolve(retained);
|
|
2333
|
+
continue;
|
|
2334
|
+
}
|
|
2386
2335
|
let providerFinalized = claimedSettlement?.result;
|
|
2387
2336
|
try {
|
|
2388
2337
|
providerFinalized = await ensureVisibleStreamingDelivery(providerFinalized, completion.result.content, completion.infoKind);
|
|
@@ -2419,22 +2368,16 @@ function createFeishuReplyDispatcher(params) {
|
|
|
2419
2368
|
let finalized = noVisibleFeishuReplyDelivery;
|
|
2420
2369
|
let finalizationError;
|
|
2421
2370
|
let fallbackPartial;
|
|
2422
|
-
|
|
2423
|
-
if (
|
|
2424
|
-
finalized = claimedSettlement.result;
|
|
2425
|
-
finalizationError = claimedSettlement.error;
|
|
2426
|
-
} else if (paramsLocal.ownerGeneration !== void 0 && inFlightStreamingClose?.generation === paramsLocal.ownerGeneration) {
|
|
2371
|
+
let settlement = claimClosedStreamingResult(paramsLocal.ownerGeneration, paramsLocal.content);
|
|
2372
|
+
if (!settlement && paramsLocal.ownerGeneration !== void 0 && inFlightStreamingClose?.generation === paramsLocal.ownerGeneration) {
|
|
2427
2373
|
const closeOutcome = await inFlightStreamingClose.promise;
|
|
2428
|
-
|
|
2429
|
-
|
|
2430
|
-
|
|
2431
|
-
|
|
2432
|
-
|
|
2433
|
-
finalized = closeOutcome.result;
|
|
2434
|
-
finalizationError = closeOutcome.error;
|
|
2435
|
-
}
|
|
2374
|
+
settlement = claimClosedStreamingResult(paramsLocal.ownerGeneration, paramsLocal.content) ?? closeOutcome;
|
|
2375
|
+
} else if (!settlement && paramsLocal.ownerGeneration !== void 0 && activeStreamingGeneration === paramsLocal.ownerGeneration) settlement = await closeStreaming();
|
|
2376
|
+
finalized = settlement?.result ?? finalized;
|
|
2377
|
+
finalizationError = settlement?.error;
|
|
2378
|
+
const discarded = settlement?.disposition === "discarded";
|
|
2436
2379
|
try {
|
|
2437
|
-
finalized = await ensureVisibleStreamingDelivery(finalized, paramsLocal.content, paramsLocal.infoKind) ?? finalized;
|
|
2380
|
+
if (!discarded) finalized = await ensureVisibleStreamingDelivery(finalized, paramsLocal.content, paramsLocal.infoKind) ?? finalized;
|
|
2438
2381
|
} catch (fallbackError) {
|
|
2439
2382
|
fallbackPartial = isChannelPartialDeliveryError(fallbackError) ? fallbackError.deliveryResult : void 0;
|
|
2440
2383
|
const fallbackCause = fallbackPartial && fallbackError instanceof Error ? fallbackError.cause ?? fallbackError : fallbackError;
|
|
@@ -2445,9 +2388,10 @@ function createFeishuReplyDispatcher(params) {
|
|
|
2445
2388
|
finalized,
|
|
2446
2389
|
...fallbackPartial ? [fallbackPartial] : [],
|
|
2447
2390
|
...mediaPartial ? [mediaPartial] : []
|
|
2448
|
-
], fallbackPartial?.visibleReplySent === true ? fallbackPartial.content : finalized.visibleReplySent === true ? finalized.content : paramsLocal.content);
|
|
2391
|
+
], discarded ? finalized.content ?? "" : fallbackPartial?.visibleReplySent === true ? fallbackPartial.content : finalized.visibleReplySent === true ? finalized.content : paramsLocal.content);
|
|
2449
2392
|
const mediaCause = mediaPartial && paramsLocal.error instanceof Error ? paramsLocal.error.cause ?? paramsLocal.error : paramsLocal.error;
|
|
2450
|
-
|
|
2393
|
+
const cause = finalizationError ? new AggregateError([mediaCause, finalizationError instanceof Error ? finalizationError.cause ?? finalizationError : finalizationError], "Feishu reply delivery and streaming finalization failed") : mediaCause;
|
|
2394
|
+
throw createFeishuPartialReplyDeliveryError(cause, accepted);
|
|
2451
2395
|
};
|
|
2452
2396
|
const dispatcherOptions = {
|
|
2453
2397
|
responsePrefix,
|
|
@@ -2460,13 +2404,14 @@ function createFeishuReplyDispatcher(params) {
|
|
|
2460
2404
|
conversationType: chatId.startsWith("oc_") ? "group" : "direct"
|
|
2461
2405
|
},
|
|
2462
2406
|
onSkip: (_payload, info) => {
|
|
2463
|
-
if (info.kind === "final" || info.kind === "block" && info.reason === "silent") {
|
|
2464
|
-
|
|
2465
|
-
|
|
2466
|
-
|
|
2407
|
+
if (replyOutcome?.kind !== "failed" && (info.kind === "final" || info.kind === "block" && info.reason === "silent")) replyOutcome = {
|
|
2408
|
+
kind: "skipped",
|
|
2409
|
+
reason: info.reason,
|
|
2410
|
+
assistantMessageIndex: info.assistantMessageIndex
|
|
2411
|
+
};
|
|
2467
2412
|
},
|
|
2468
2413
|
beforeDeliver: (payload, info) => {
|
|
2469
|
-
|
|
2414
|
+
if (!(replyOutcome?.kind === "skipped" && replyOutcome.reason === "silent" && info.kind !== "final" && replyOutcome.assistantMessageIndex !== void 0 && info.assistantMessageIndex !== void 0 && info.assistantMessageIndex < replyOutcome.assistantMessageIndex) && replyOutcome?.kind !== "failed") replyOutcome = void 0;
|
|
2470
2415
|
return payload;
|
|
2471
2416
|
},
|
|
2472
2417
|
onReplyStart: async () => {
|
|
@@ -2477,9 +2422,7 @@ function createFeishuReplyDispatcher(params) {
|
|
|
2477
2422
|
sentIndependentBlockText = false;
|
|
2478
2423
|
idleRequestedForReply = false;
|
|
2479
2424
|
visibleReplySent = false;
|
|
2480
|
-
|
|
2481
|
-
skippedFinalAssistantMessageIndex = void 0;
|
|
2482
|
-
preparedDeliveryAssistantMessageIndex = void 0;
|
|
2425
|
+
replyOutcome = void 0;
|
|
2483
2426
|
}
|
|
2484
2427
|
if (previewStreamingEnabled && renderMode === "card") startStreaming();
|
|
2485
2428
|
await Promise.resolve(typingCallbacks?.onReplyStart?.());
|
|
@@ -2490,6 +2433,7 @@ function createFeishuReplyDispatcher(params) {
|
|
|
2490
2433
|
}
|
|
2491
2434
|
};
|
|
2492
2435
|
const handleDeliveryError = async (error, info) => {
|
|
2436
|
+
if (info.kind === "final") replyOutcome = { kind: "failed" };
|
|
2493
2437
|
if (isChannelPartialDeliveryError(error)) markVisibleReplySent();
|
|
2494
2438
|
params.runtime.error?.(`feishu[${account.accountId}] ${info.kind} reply failed: ${String(error)}`);
|
|
2495
2439
|
await queueIdleSideEffects().catch((cleanupError) => params.runtime.error?.(`feishu[${account.accountId}] reply error cleanup failed: ${String(cleanupError)}`));
|
|
@@ -2498,19 +2442,36 @@ function createFeishuReplyDispatcher(params) {
|
|
|
2498
2442
|
dispatcherOptions,
|
|
2499
2443
|
delivery: {
|
|
2500
2444
|
observeMessageSent: true,
|
|
2501
|
-
|
|
2502
|
-
|
|
2503
|
-
|
|
2504
|
-
|
|
2505
|
-
|
|
2506
|
-
skippedFinalAssistantMessageIndex = void 0;
|
|
2445
|
+
onDelivered: (_payload, info, result) => {
|
|
2446
|
+
if (result?.visibleReplySent) {
|
|
2447
|
+
markVisibleReplySent();
|
|
2448
|
+
if (info.kind === "final") replyOutcome = void 0;
|
|
2449
|
+
return;
|
|
2507
2450
|
}
|
|
2508
|
-
const
|
|
2451
|
+
const reason = result?.suppression?.reason;
|
|
2452
|
+
if (info.kind === "final" && replyOutcome?.kind !== "failed" && (reason === "cancelled_by_reply_payload_sending_hook" || reason === "empty_after_reply_payload_sending_hook" || reason === "cancelled_by_message_sending_hook" || reason === "empty_after_message_sending_hook" || reason === "channel_transform")) replyOutcome = {
|
|
2453
|
+
kind: "suppressed",
|
|
2454
|
+
reason
|
|
2455
|
+
};
|
|
2456
|
+
},
|
|
2457
|
+
deliver: async (inputPayload, info) => {
|
|
2458
|
+
const prepared = await renderFeishuReplyPayload(inputPayload, {
|
|
2459
|
+
to: sendTarget,
|
|
2460
|
+
identity,
|
|
2461
|
+
mentions: requiredMentionTargets
|
|
2462
|
+
});
|
|
2463
|
+
const rendered = consumeFeishuPresentationFallbackMarker(prepared.payload);
|
|
2464
|
+
const payload = rendered.payload;
|
|
2465
|
+
const presentationCard = prepared.card;
|
|
2466
|
+
const hasPresentationFallback = rendered.presentationFallback?.hasVisibleContent === true;
|
|
2467
|
+
const hasIndependentPresentation = presentationCard !== void 0 || hasPresentationFallback;
|
|
2468
|
+
const resolvedText = payload.text;
|
|
2469
|
+
const payloadText = payload.isReasoning && resolvedText ? formatReasoningMessage(resolvedText) : resolvedText;
|
|
2509
2470
|
const reply = resolveSendableOutboundReplyParts({
|
|
2510
2471
|
...payload,
|
|
2511
2472
|
text: payloadText
|
|
2512
2473
|
});
|
|
2513
|
-
const text = info?.kind === "final" ? mergeStreamingFinalText(streamText, reply.text, payload.isError === true && hasStreamingFinalText) : reply.text;
|
|
2474
|
+
const text = info?.kind === "final" && !hasIndependentPresentation ? mergeStreamingFinalText(streamText, reply.text, payload.isError === true && hasStreamingFinalText) : reply.text;
|
|
2514
2475
|
const hasText = reply.hasText;
|
|
2515
2476
|
const hasMedia = reply.hasMedia;
|
|
2516
2477
|
const ttsSupplement = getReplyPayloadTtsSupplement(payload);
|
|
@@ -2524,52 +2485,61 @@ function createFeishuReplyDispatcher(params) {
|
|
|
2524
2485
|
const useStaticCard = hasText && (renderMode === "card" || info?.kind === "block" && coreBlockStreamingEnabled && renderMode !== "raw" || renderMode === "auto" && shouldUseCard(text));
|
|
2525
2486
|
const useStreamingCard = hasText && streamingEnabled && !finalTextExceedsStreamingLimit && (info?.kind === "final" || useStaticCard);
|
|
2526
2487
|
const useCard = useStaticCard || useStreamingCard;
|
|
2527
|
-
const skipTextForDuplicateFinal = info?.kind === "final" && hasText && deliveredFinalTexts.has(text);
|
|
2528
|
-
const shouldDeliverText = hasText && !hasVoiceMedia && !skipTextForDuplicateFinal;
|
|
2529
|
-
const shouldDiscardStreamingPreview = info?.kind === "final" && (finalTextExceedsStreamingLimit || hasMedia && (hasVoiceMedia && !shouldDeliverText && !ttsTextAlreadyVisible || skipTextForDuplicateFinal));
|
|
2488
|
+
const skipTextForDuplicateFinal = !hasIndependentPresentation && info?.kind === "final" && hasText && deliveredFinalTexts.has(text);
|
|
2489
|
+
const shouldDeliverText = hasText && (!hasVoiceMedia || hasPresentationFallback) && !skipTextForDuplicateFinal;
|
|
2490
|
+
const shouldDiscardStreamingPreview = info?.kind === "final" && !(hasIndependentPresentation && payload.isError === true && hasStreamingFinalText) && (hasIndependentPresentation || finalTextExceedsStreamingLimit || hasMedia && (hasVoiceMedia && !shouldDeliverText && !ttsTextAlreadyVisible || skipTextForDuplicateFinal));
|
|
2530
2491
|
const priorClosedStreamingSettlement = info?.kind === "final" && hasText && skipTextForDuplicateFinal ? claimClosedStreamingResult(void 0, text) : void 0;
|
|
2531
|
-
if (!shouldDeliverText && !hasMedia) {
|
|
2492
|
+
if (!shouldDeliverText && !hasMedia && !presentationCard) {
|
|
2532
2493
|
if (priorClosedStreamingSettlement?.error !== void 0) throw toStringifiedError(priorClosedStreamingSettlement.error);
|
|
2533
2494
|
return priorClosedStreamingSettlement?.result ?? noVisibleFeishuReplyDelivery;
|
|
2534
2495
|
}
|
|
2535
2496
|
const deliveredResults = priorClosedStreamingSettlement ? [priorClosedStreamingSettlement.result] : [];
|
|
2536
|
-
const
|
|
2497
|
+
const collectDelivery = async (pending, acceptedContent) => {
|
|
2537
2498
|
try {
|
|
2538
|
-
deliveredResults.push(await
|
|
2499
|
+
deliveredResults.push(await pending);
|
|
2539
2500
|
} catch (error) {
|
|
2540
2501
|
const partial = isChannelPartialDeliveryError(error) ? error.deliveryResult : void 0;
|
|
2541
|
-
const accumulated = mergeFeishuReplyDeliveryResults([...deliveredResults, ...partial ? [
|
|
2542
|
-
|
|
2502
|
+
const accumulated = mergeFeishuReplyDeliveryResults([...deliveredResults, ...partial ? [{
|
|
2503
|
+
...partial,
|
|
2504
|
+
content: partial.content ?? acceptedContent
|
|
2505
|
+
}] : []]);
|
|
2506
|
+
throw createFeishuPartialReplyDeliveryError(partial && error instanceof Error ? error.cause ?? error : error, {
|
|
2507
|
+
...accumulated,
|
|
2508
|
+
content: accumulated.content ?? ""
|
|
2509
|
+
});
|
|
2543
2510
|
}
|
|
2544
2511
|
};
|
|
2545
2512
|
if (shouldDiscardStreamingPreview) await discardStreamingPreview();
|
|
2513
|
+
if (presentationCard) {
|
|
2514
|
+
if (hasMedia) await collectDelivery(sendMediaReplies(payload));
|
|
2515
|
+
await collectDelivery(sendCardFeishu({
|
|
2516
|
+
cfg,
|
|
2517
|
+
to: sendTarget,
|
|
2518
|
+
card: presentationCard,
|
|
2519
|
+
replyToMessageId: sendReplyToMessageId,
|
|
2520
|
+
replyInThread: effectiveReplyInThread,
|
|
2521
|
+
allowTopLevelReplyFallback,
|
|
2522
|
+
accountId
|
|
2523
|
+
}).then((result) => createFeishuReplyDeliveryResult({
|
|
2524
|
+
results: [result],
|
|
2525
|
+
visibleReplySent: true,
|
|
2526
|
+
content: resolvedText,
|
|
2527
|
+
kind: "card"
|
|
2528
|
+
})), resolvedText);
|
|
2529
|
+
markVisibleReplySent();
|
|
2530
|
+
return mergeFeishuReplyDeliveryResults(deliveredResults, resolvedText);
|
|
2531
|
+
}
|
|
2546
2532
|
if (shouldDeliverText) {
|
|
2547
|
-
if (info?.kind === "block") {
|
|
2548
|
-
if (
|
|
2549
|
-
|
|
2550
|
-
|
|
2551
|
-
|
|
2552
|
-
|
|
2553
|
-
useCard: false,
|
|
2554
|
-
infoKind: "block",
|
|
2555
|
-
firstChunkMentions,
|
|
2556
|
-
chunkMentions: requiredMentionTargets,
|
|
2557
|
-
sendChunk: async ({ chunk, mentions }) => await sendMessageFeishu({
|
|
2558
|
-
cfg,
|
|
2559
|
-
to: sendTarget,
|
|
2560
|
-
text: chunk,
|
|
2561
|
-
replyToMessageId: sendReplyToMessageId,
|
|
2562
|
-
replyInThread: effectiveReplyInThread,
|
|
2563
|
-
allowTopLevelReplyFallback,
|
|
2564
|
-
accountId,
|
|
2565
|
-
...mentions ? { mentions } : {}
|
|
2566
|
-
})
|
|
2567
|
-
}));
|
|
2568
|
-
sentIndependentBlockText = true;
|
|
2569
|
-
if (hasMedia) await collectMediaDelivery(payload);
|
|
2570
|
-
}
|
|
2571
|
-
return mergeFeishuReplyDeliveryResults(deliveredResults, text);
|
|
2533
|
+
if (hasPresentationFallback || info?.kind === "block" && !useStreamingCard) {
|
|
2534
|
+
if (hasPresentationFallback || coreBlockStreamingEnabled) {
|
|
2535
|
+
const firstChunkMentions = info?.kind === "final" || info?.kind === "block" && !sentIndependentBlockText ? mentionTargets : void 0;
|
|
2536
|
+
await collectDelivery(sendPostReply(text, info?.kind, firstChunkMentions));
|
|
2537
|
+
if (info?.kind === "block") sentIndependentBlockText = true;
|
|
2538
|
+
if (hasMedia) await collectDelivery(sendMediaReplies(payload));
|
|
2572
2539
|
}
|
|
2540
|
+
return mergeFeishuReplyDeliveryResults(deliveredResults, text);
|
|
2541
|
+
}
|
|
2542
|
+
if (info?.kind === "block") {
|
|
2573
2543
|
startStreaming();
|
|
2574
2544
|
if (streamingStartPromise) await streamingStartPromise;
|
|
2575
2545
|
}
|
|
@@ -2578,7 +2548,7 @@ function createFeishuReplyDispatcher(params) {
|
|
|
2578
2548
|
if (streamingStartPromise) await streamingStartPromise;
|
|
2579
2549
|
}
|
|
2580
2550
|
const shouldStreamText = info?.kind === "block" || info?.kind === "final";
|
|
2581
|
-
const matchingInFlightClose = info?.kind === "final" && inFlightStreamingClose?.content === text ? inFlightStreamingClose : void 0;
|
|
2551
|
+
const matchingInFlightClose = info?.kind === "final" && inFlightStreamingClose?.disposition === "closed" && inFlightStreamingClose.content === text ? inFlightStreamingClose : void 0;
|
|
2582
2552
|
const ownerGeneration = activeStreamingGeneration ?? matchingInFlightClose?.generation;
|
|
2583
2553
|
if (shouldStreamText && ownerGeneration !== void 0 && (streaming?.isActive() || matchingInFlightClose !== void 0)) {
|
|
2584
2554
|
if (activeStreamingGeneration !== void 0) {
|
|
@@ -2595,7 +2565,7 @@ function createFeishuReplyDispatcher(params) {
|
|
|
2595
2565
|
}
|
|
2596
2566
|
}
|
|
2597
2567
|
if (hasMedia) try {
|
|
2598
|
-
await
|
|
2568
|
+
await collectDelivery(sendMediaReplies(payload));
|
|
2599
2569
|
} catch (error) {
|
|
2600
2570
|
await throwStreamingDeliveryFailure({
|
|
2601
2571
|
error,
|
|
@@ -2613,6 +2583,8 @@ function createFeishuReplyDispatcher(params) {
|
|
|
2613
2583
|
text,
|
|
2614
2584
|
useCard: true,
|
|
2615
2585
|
infoKind: info?.kind,
|
|
2586
|
+
header: cardHeader,
|
|
2587
|
+
note: cardNote,
|
|
2616
2588
|
chunkMentions: requiredMentionTargets,
|
|
2617
2589
|
sendChunk: async ({ chunk, mentions }) => await sendStructuredCardFeishu({
|
|
2618
2590
|
cfg,
|
|
@@ -2629,27 +2601,12 @@ function createFeishuReplyDispatcher(params) {
|
|
|
2629
2601
|
}));
|
|
2630
2602
|
} else {
|
|
2631
2603
|
const firstChunkMentions = info?.kind === "final" && mentionTargets?.length ? mentionTargets : void 0;
|
|
2632
|
-
deliveredResults.push(await
|
|
2633
|
-
text,
|
|
2634
|
-
useCard: false,
|
|
2635
|
-
infoKind: info?.kind,
|
|
2636
|
-
firstChunkMentions,
|
|
2637
|
-
chunkMentions: requiredMentionTargets,
|
|
2638
|
-
sendChunk: async ({ chunk, mentions }) => await sendMessageFeishu({
|
|
2639
|
-
cfg,
|
|
2640
|
-
to: sendTarget,
|
|
2641
|
-
text: chunk,
|
|
2642
|
-
replyToMessageId: sendReplyToMessageId,
|
|
2643
|
-
replyInThread: effectiveReplyInThread,
|
|
2644
|
-
allowTopLevelReplyFallback,
|
|
2645
|
-
accountId,
|
|
2646
|
-
...mentions ? { mentions } : {}
|
|
2647
|
-
})
|
|
2648
|
-
}));
|
|
2604
|
+
deliveredResults.push(await sendPostReply(text, info?.kind, firstChunkMentions));
|
|
2649
2605
|
}
|
|
2650
2606
|
}
|
|
2651
|
-
if (hasMedia) await
|
|
2652
|
-
const
|
|
2607
|
+
if (hasMedia) await collectDelivery(sendMediaReplies(payload, !shouldDeliverText && !ttsTextAlreadyVisible && hasVoiceMedia && hasText ? { fallbackText: text } : void 0));
|
|
2608
|
+
const deliveredContent = hasVoiceMedia ? deliveredResults.at(-1)?.content ?? text : text;
|
|
2609
|
+
const result = mergeFeishuReplyDeliveryResults(deliveredResults, deliveredContent);
|
|
2653
2610
|
if (priorClosedStreamingSettlement?.error !== void 0) throw createFeishuPartialReplyDeliveryError(isChannelPartialDeliveryError(priorClosedStreamingSettlement.error) && priorClosedStreamingSettlement.error instanceof Error ? priorClosedStreamingSettlement.error.cause ?? priorClosedStreamingSettlement.error : priorClosedStreamingSettlement.error, result);
|
|
2654
2611
|
return result;
|
|
2655
2612
|
},
|
|
@@ -2697,7 +2654,7 @@ function createFeishuReplyDispatcher(params) {
|
|
|
2697
2654
|
ensureNoVisibleReplyFallback,
|
|
2698
2655
|
getVisibleReplyState: () => ({
|
|
2699
2656
|
visibleReplySent,
|
|
2700
|
-
skippedFinalReason
|
|
2657
|
+
skippedFinalReason: replyOutcome?.kind === "skipped" || replyOutcome?.kind === "suppressed" ? replyOutcome.reason : null
|
|
2701
2658
|
})
|
|
2702
2659
|
};
|
|
2703
2660
|
}
|
|
@@ -2715,7 +2672,7 @@ function getFeishuSyntheticDirectPreDispatchTarget(event) {
|
|
|
2715
2672
|
//#endregion
|
|
2716
2673
|
//#region extensions/feishu/src/bot.ts
|
|
2717
2674
|
const permissionErrorNotifiedAt = /* @__PURE__ */ new Map();
|
|
2718
|
-
const PERMISSION_ERROR_COOLDOWN_MS =
|
|
2675
|
+
const PERMISSION_ERROR_COOLDOWN_MS = 3e5;
|
|
2719
2676
|
function shouldSendNoVisibleReplyFallback(dispatchResult) {
|
|
2720
2677
|
const emptyEligibleDispatch = dispatchResult.noVisibleReplyFallbackEligible === true && dispatchResult.settledReceipt?.anyVisibleDelivered !== true;
|
|
2721
2678
|
const finalFailedBeforeSend = (dispatchResult.settledReceipt?.counts.final.failedBeforeSend ?? 0) > 0;
|
|
@@ -2724,10 +2681,11 @@ function shouldSendNoVisibleReplyFallback(dispatchResult) {
|
|
|
2724
2681
|
function isFeishuTopicSessionScope(scope) {
|
|
2725
2682
|
return scope === "group_topic" || scope === "group_topic_sender";
|
|
2726
2683
|
}
|
|
2727
|
-
async function
|
|
2728
|
-
if (params.messageType !== "audio"
|
|
2684
|
+
async function resolveFeishuAudioTranscript(params) {
|
|
2685
|
+
if (params.messageType !== "audio") return;
|
|
2729
2686
|
const audioMedia = params.mediaList.filter((media) => Boolean(media.path) && (media.kind === "audio" || media.contentType?.startsWith("audio/")));
|
|
2730
2687
|
if (audioMedia.length === 0) return;
|
|
2688
|
+
if (params.content.trim()) return params.content;
|
|
2731
2689
|
try {
|
|
2732
2690
|
const { transcribeFirstAudio } = await import("./audio-preflight.runtime-BBfC9elv.js");
|
|
2733
2691
|
return await transcribeFirstAudio({
|
|
@@ -2922,32 +2880,34 @@ async function handleFeishuMessage(params) {
|
|
|
2922
2880
|
}
|
|
2923
2881
|
}
|
|
2924
2882
|
let permissionErrorForAgent;
|
|
2925
|
-
if (feishuCfg?.resolveSenderNames ?? true)
|
|
2926
|
-
|
|
2927
|
-
|
|
2928
|
-
|
|
2929
|
-
|
|
2930
|
-
|
|
2931
|
-
|
|
2932
|
-
|
|
2933
|
-
|
|
2934
|
-
|
|
2935
|
-
|
|
2936
|
-
|
|
2937
|
-
|
|
2938
|
-
|
|
2939
|
-
|
|
2940
|
-
|
|
2941
|
-
|
|
2942
|
-
|
|
2943
|
-
|
|
2944
|
-
|
|
2945
|
-
|
|
2946
|
-
|
|
2947
|
-
|
|
2948
|
-
|
|
2949
|
-
permissionErrorNotifiedAt.
|
|
2950
|
-
|
|
2883
|
+
if (feishuCfg?.resolveSenderNames ?? true) {
|
|
2884
|
+
if (ctx.senderType === "bot") {
|
|
2885
|
+
const senderName = await resolveFeishuBotName({
|
|
2886
|
+
account,
|
|
2887
|
+
openId: ctx.senderOpenId,
|
|
2888
|
+
log
|
|
2889
|
+
});
|
|
2890
|
+
if (senderName) ctx = {
|
|
2891
|
+
...ctx,
|
|
2892
|
+
senderName
|
|
2893
|
+
};
|
|
2894
|
+
} else {
|
|
2895
|
+
const senderResult = await resolveFeishuSenderName({
|
|
2896
|
+
account,
|
|
2897
|
+
senderId: ctx.senderOpenId,
|
|
2898
|
+
log
|
|
2899
|
+
});
|
|
2900
|
+
if (senderResult.name) ctx = {
|
|
2901
|
+
...ctx,
|
|
2902
|
+
senderName: senderResult.name
|
|
2903
|
+
};
|
|
2904
|
+
if (senderResult.permissionError) {
|
|
2905
|
+
const appKey = account.appId ?? "default";
|
|
2906
|
+
const now = Date.now();
|
|
2907
|
+
if (now - (permissionErrorNotifiedAt.get(appKey) ?? 0) > PERMISSION_ERROR_COOLDOWN_MS) {
|
|
2908
|
+
permissionErrorNotifiedAt.set(appKey, now);
|
|
2909
|
+
permissionErrorForAgent = senderResult.permissionError;
|
|
2910
|
+
}
|
|
2951
2911
|
}
|
|
2952
2912
|
}
|
|
2953
2913
|
}
|
|
@@ -3335,7 +3295,7 @@ async function handleFeishuMessage(params) {
|
|
|
3335
3295
|
log(`feishu[${account.accountId}]: skipping empty message (no text, no media, no quoted) from ${ctx.senderOpenId}`);
|
|
3336
3296
|
return;
|
|
3337
3297
|
}
|
|
3338
|
-
const audioTranscript = await
|
|
3298
|
+
const audioTranscript = await resolveFeishuAudioTranscript({
|
|
3339
3299
|
cfg: effectiveCfg,
|
|
3340
3300
|
mediaList,
|
|
3341
3301
|
content: ctx.content,
|
|
@@ -3343,8 +3303,8 @@ async function handleFeishuMessage(params) {
|
|
|
3343
3303
|
chatType: isGroup ? "group" : "direct",
|
|
3344
3304
|
log
|
|
3345
3305
|
});
|
|
3346
|
-
const
|
|
3347
|
-
const inboundMedia = await toInboundMediaFactsWithMetadata(mediaList, { transcribed: (_media, index) => index ===
|
|
3306
|
+
const transcribedAudioIndex = audioTranscript === void 0 ? -1 : mediaList.findIndex((media) => media.kind === "audio" || media.contentType?.startsWith("audio/"));
|
|
3307
|
+
const inboundMedia = await toInboundMediaFactsWithMetadata(mediaList, { transcribed: (_media, index) => index === transcribedAudioIndex });
|
|
3348
3308
|
const requiredMentionTargets = isGroup && ctx.senderType === "bot" && ctx.senderOpenId ? [{
|
|
3349
3309
|
openId: ctx.senderOpenId,
|
|
3350
3310
|
name: ctx.senderName ?? ctx.senderOpenId,
|
|
@@ -4018,8 +3978,8 @@ function createApprovalCard(params) {
|
|
|
4018
3978
|
}
|
|
4019
3979
|
//#endregion
|
|
4020
3980
|
//#region extensions/feishu/src/card-action.ts
|
|
4021
|
-
const FEISHU_APPROVAL_CARD_TTL_MS =
|
|
4022
|
-
const FEISHU_CARD_ACTION_TOKEN_TTL_MS =
|
|
3981
|
+
const FEISHU_APPROVAL_CARD_TTL_MS = 3e5;
|
|
3982
|
+
const FEISHU_CARD_ACTION_TOKEN_TTL_MS = 9e5;
|
|
4023
3983
|
function pruneProcessedCardActionTokens(now) {
|
|
4024
3984
|
const validNow = asDateTimestampMs(now);
|
|
4025
3985
|
if (validNow === void 0) {
|
|
@@ -4111,7 +4071,7 @@ async function dispatchSyntheticCommand(params) {
|
|
|
4111
4071
|
});
|
|
4112
4072
|
}
|
|
4113
4073
|
const resolvedChatTypeCache = resolvedCardActionChatTypes;
|
|
4114
|
-
const CHAT_TYPE_CACHE_TTL_MS =
|
|
4074
|
+
const CHAT_TYPE_CACHE_TTL_MS = 18e5;
|
|
4115
4075
|
const CHAT_TYPE_CACHE_MAX_SIZE = 5e3;
|
|
4116
4076
|
function pruneChatTypeCache(now) {
|
|
4117
4077
|
const validNow = asDateTimestampMs(now);
|
|
@@ -4366,7 +4326,8 @@ function inspectFeishuIngressEnvelope(rawEnvelope, encryptKey, allowInvalidLane
|
|
|
4366
4326
|
if (!eventId) throw new FeishuIngressPermanentError("invalid-event", `Feishu ${eventType} envelope is missing header.event_id.`);
|
|
4367
4327
|
const event = nestedEvent ?? envelope;
|
|
4368
4328
|
if (eventType === "im.message.receive_v1") {
|
|
4369
|
-
const
|
|
4329
|
+
const message = isRecord$1(event.message) ? event.message : null;
|
|
4330
|
+
const chatId = normalizeNullableString(message?.chat_id);
|
|
4370
4331
|
if (!chatId) {
|
|
4371
4332
|
if (allowInvalidLane) return {
|
|
4372
4333
|
eventId,
|
|
@@ -4490,6 +4451,7 @@ function buildFeishuFlushIngressLifecycle(sources, options) {
|
|
|
4490
4451
|
handedOff = true;
|
|
4491
4452
|
transportLifecycle.onDeferred();
|
|
4492
4453
|
},
|
|
4454
|
+
onDeferredHeartbeat: () => transportLifecycle.onDeferredHeartbeat?.(),
|
|
4493
4455
|
onAdoptionFinalizing: () => {
|
|
4494
4456
|
transportLifecycle.onAdoptionFinalizing();
|
|
4495
4457
|
},
|
|
@@ -4642,7 +4604,7 @@ const FEISHU_WEBHOOK_RATE_LIMIT_FALLBACK_DEFAULTS = {
|
|
|
4642
4604
|
};
|
|
4643
4605
|
const FEISHU_WEBHOOK_ANOMALY_FALLBACK_DEFAULTS = {
|
|
4644
4606
|
maxTrackedKeys: 4096,
|
|
4645
|
-
ttlMs:
|
|
4607
|
+
ttlMs: 216e5,
|
|
4646
4608
|
logEvery: 25
|
|
4647
4609
|
};
|
|
4648
4610
|
function coercePositiveInt(value, fallback) {
|
|
@@ -4673,7 +4635,7 @@ const httpServers = /* @__PURE__ */ new Map();
|
|
|
4673
4635
|
const botOpenIds = /* @__PURE__ */ new Map();
|
|
4674
4636
|
const botNames = /* @__PURE__ */ new Map();
|
|
4675
4637
|
const botIdentityRevisions = /* @__PURE__ */ new Map();
|
|
4676
|
-
const FEISHU_WEBHOOK_MAX_BODY_BYTES =
|
|
4638
|
+
const FEISHU_WEBHOOK_MAX_BODY_BYTES = 65536;
|
|
4677
4639
|
const FEISHU_WEBHOOK_BODY_TIMEOUT_MS = 5e3;
|
|
4678
4640
|
const FEISHU_HTTP_SERVER_CLOSE_TIMEOUT_MS = 5e3;
|
|
4679
4641
|
const feishuWebhookRateLimitDefaults = resolveFeishuWebhookRateLimitDefaults(WEBHOOK_RATE_LIMIT_DEFAULTS);
|
|
@@ -4821,7 +4783,7 @@ function startBotIdentityRecovery(params) {
|
|
|
4821
4783
|
}
|
|
4822
4784
|
//#endregion
|
|
4823
4785
|
//#region extensions/feishu/src/card-ux-launcher.ts
|
|
4824
|
-
const FEISHU_QUICK_ACTION_CARD_TTL_MS =
|
|
4786
|
+
const FEISHU_QUICK_ACTION_CARD_TTL_MS = 6e5;
|
|
4825
4787
|
const QUICK_ACTION_MENU_KEYS = /* @__PURE__ */ new Set([
|
|
4826
4788
|
"quick-actions",
|
|
4827
4789
|
"quick_actions",
|
|
@@ -5043,10 +5005,11 @@ function createFeishuCommentReplyDispatcher(params) {
|
|
|
5043
5005
|
channel: "feishu",
|
|
5044
5006
|
accountId: params.accountId
|
|
5045
5007
|
});
|
|
5046
|
-
const
|
|
5008
|
+
const account = resolveFeishuRuntimeAccount({
|
|
5047
5009
|
cfg: params.cfg,
|
|
5048
5010
|
accountId: params.accountId
|
|
5049
|
-
})
|
|
5011
|
+
});
|
|
5012
|
+
const client = createFeishuClient(account);
|
|
5050
5013
|
const textChunkLimit = core.channel.text.resolveTextChunkLimit(params.cfg, "feishu", params.accountId, { fallbackLimit: 4e3 });
|
|
5051
5014
|
const chunkMode = core.channel.text.resolveChunkMode(params.cfg, "feishu", params.accountId);
|
|
5052
5015
|
const typingReaction = createCommentTypingReactionLifecycle({
|
|
@@ -5075,7 +5038,7 @@ function createFeishuCommentReplyDispatcher(params) {
|
|
|
5075
5038
|
if (info.kind !== "final") return noVisibleFeishuReplyDelivery;
|
|
5076
5039
|
const reply = resolveSendableOutboundReplyParts(payload);
|
|
5077
5040
|
const { presentation } = resolveFeishuRichReply(payload);
|
|
5078
|
-
let { text } =
|
|
5041
|
+
let { commentText: text } = buildFeishuPresentationFallback({
|
|
5079
5042
|
text: reply.text,
|
|
5080
5043
|
presentation
|
|
5081
5044
|
});
|
|
@@ -5994,7 +5957,8 @@ async function handleFeishuCommentEvent(params) {
|
|
|
5994
5957
|
})
|
|
5995
5958
|
}
|
|
5996
5959
|
});
|
|
5997
|
-
const
|
|
5960
|
+
const dispatchResult = turnResult.dispatched ? turnResult.dispatchResult : void 0;
|
|
5961
|
+
const counts = resolveInboundReplyDispatchCounts(dispatchResult);
|
|
5998
5962
|
log(`feishu[${account.accountId}]: drive comment dispatch complete (replies=${counts.final}, session=${commentSessionKey})`);
|
|
5999
5963
|
} finally {
|
|
6000
5964
|
cleanupTypingReaction();
|
|
@@ -6019,7 +5983,7 @@ async function handleFeishuCommentEvent(params) {
|
|
|
6019
5983
|
* resolves) keeps later same-chat messages in `queued` state until the
|
|
6020
5984
|
* gateway is restarted. See #70133.
|
|
6021
5985
|
*/
|
|
6022
|
-
const DEFAULT_TASK_TIMEOUT_MS =
|
|
5986
|
+
const DEFAULT_TASK_TIMEOUT_MS = 3e5;
|
|
6023
5987
|
function createSequentialQueue(options = {}) {
|
|
6024
5988
|
const queues = /* @__PURE__ */ new Map();
|
|
6025
5989
|
const taskTimeoutMs = options.taskTimeoutMs ?? DEFAULT_TASK_TIMEOUT_MS;
|
|
@@ -6230,8 +6194,8 @@ function createFeishuMessageReceiveHandler({ cfg, channelRuntime, accountId, run
|
|
|
6230
6194
|
const chatId = event.message.chat_id?.trim();
|
|
6231
6195
|
const senderId = resolveSenderDebounceId(event);
|
|
6232
6196
|
if (!chatId || !senderId) return null;
|
|
6233
|
-
const
|
|
6234
|
-
return `feishu:${accountId}:${chatId}:${
|
|
6197
|
+
const threadId = event.message.root_id?.trim() || event.message.thread_id?.trim();
|
|
6198
|
+
return `feishu:${accountId}:${chatId}:${threadId ? `thread:${threadId}` : "chat"}:${senderId}`;
|
|
6235
6199
|
},
|
|
6236
6200
|
shouldDebounce: ({ event }) => {
|
|
6237
6201
|
if (event.message.message_type !== "text") return false;
|
|
@@ -6427,7 +6391,8 @@ function isFeishuWebhookSignatureValid(params) {
|
|
|
6427
6391
|
const nonce = Array.isArray(nonceHeader) ? nonceHeader[0] : nonceHeader;
|
|
6428
6392
|
const signature = Array.isArray(signatureHeader) ? signatureHeader[0] : signatureHeader;
|
|
6429
6393
|
if (!timestamp || !nonce || !signature) return false;
|
|
6430
|
-
|
|
6394
|
+
const computedSignature = crypto.createHash("sha256").update(timestamp + nonce + encryptKey + params.rawBody).digest("hex");
|
|
6395
|
+
return safeEqualSecret(computedSignature, signature);
|
|
6431
6396
|
}
|
|
6432
6397
|
function respondText(res, statusCode, body) {
|
|
6433
6398
|
res.statusCode = statusCode;
|
|
@@ -6617,8 +6582,10 @@ async function monitorWebhook({ account, accountId, runtime, abortSignal, eventD
|
|
|
6617
6582
|
respondText(res, 404, "Not Found");
|
|
6618
6583
|
return;
|
|
6619
6584
|
}
|
|
6620
|
-
res.
|
|
6585
|
+
res.once("close", () => {
|
|
6621
6586
|
recordWebhookStatus(runtime, accountId, path, res.statusCode);
|
|
6587
|
+
});
|
|
6588
|
+
res.once("finish", () => {
|
|
6622
6589
|
if (res.statusCode >= 200 && res.statusCode < 300) {
|
|
6623
6590
|
const inboundAt = Date.now();
|
|
6624
6591
|
statusSink?.({
|
|
@@ -6627,16 +6594,17 @@ async function monitorWebhook({ account, accountId, runtime, abortSignal, eventD
|
|
|
6627
6594
|
});
|
|
6628
6595
|
}
|
|
6629
6596
|
});
|
|
6597
|
+
const rateLimitKey = buildFeishuWebhookRateLimitKey({
|
|
6598
|
+
accountId,
|
|
6599
|
+
path,
|
|
6600
|
+
clientIp: resolveRequestClientIp(req)
|
|
6601
|
+
});
|
|
6630
6602
|
if (!applyBasicWebhookRequestGuards({
|
|
6631
6603
|
req,
|
|
6632
6604
|
res,
|
|
6633
6605
|
allowMethods: ["POST"],
|
|
6634
6606
|
rateLimiter: feishuWebhookRateLimiter,
|
|
6635
|
-
rateLimitKey
|
|
6636
|
-
accountId,
|
|
6637
|
-
path,
|
|
6638
|
-
clientIp: resolveRequestClientIp(req)
|
|
6639
|
-
}),
|
|
6607
|
+
rateLimitKey,
|
|
6640
6608
|
nowMs: Date.now(),
|
|
6641
6609
|
requireJsonContentType: true
|
|
6642
6610
|
})) return;
|