@openclaw/feishu 2026.7.2-beta.3 → 2026.7.2-beta.5
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/README.md +1 -1
- package/dist/{accounts-BDoGHJDk.js → accounts-CKhCa76b.js} +2 -19
- package/dist/api.js +65 -43
- package/dist/{app-registration-BzkyrVZu.js → app-registration-cN1bVV6s.js} +5 -5
- package/dist/{channel-BdnXYU6g.js → channel-CBNICmK9.js} +37 -75
- package/dist/channel-plugin-api.js +1 -1
- package/dist/{channel.runtime-CVIzo2dV.js → channel.runtime-CSlJVqy4.js} +7 -7
- package/dist/{client-87BmeMpj.js → client-Dee7cKsS.js} +62 -1
- package/dist/contract-api.js +2 -2
- package/dist/doctor-contract-BiD9tyIv.js +102 -0
- package/dist/doctor-contract-api.js +1 -1
- package/dist/{drive-BHv8WW9i.js → drive-DG6pKTPE.js} +23 -20
- package/dist/{send-DcrXbqqA.js → media-DOwcLJ1j.js} +1050 -1021
- package/dist/{monitor-BVQf7-Bl.js → monitor-DEHJlyWW.js} +6 -5
- package/dist/{monitor.account-Blbf2T6J.js → monitor.account-DEHl8Non.js} +1556 -592
- package/dist/monitor.startup-CSUPT_U1.js +143 -0
- package/dist/{probe-BgboTHIn.js → probe-Za1kgUvx.js} +70 -10
- package/dist/runtime-api.js +1 -2
- package/dist/{security-audit-BIeA3W3Q.js → security-audit-D7WK_BHh.js} +1 -1
- package/dist/{security-audit-shared-BIHeF-S_.js → security-audit-shared-BgpY7AiJ.js} +6 -11
- package/dist/security-contract-api.js +1 -1
- package/dist/{send-result-DfE5Fhz6.js → send-result-Bcofg0Vv.js} +1 -1
- package/dist/session-binding-contract-api.js +1 -1
- package/dist/setup-api.js +1 -1
- package/dist/{subagent-hooks-BKTOxB-T.js → subagent-hooks-DL2SC5zX.js} +1 -1
- package/dist/subagent-hooks-api.js +1 -1
- package/dist/{thread-bindings-V0bwk0A1.js → thread-bindings-Dqs_A0du.js} +2 -1
- package/node_modules/@larksuiteoapi/node-sdk/README.md +13 -0
- package/node_modules/@larksuiteoapi/node-sdk/README.zh.md +12 -0
- package/node_modules/@larksuiteoapi/node-sdk/es/index.js +8950 -3084
- package/node_modules/@larksuiteoapi/node-sdk/lib/index.js +8951 -3083
- package/node_modules/@larksuiteoapi/node-sdk/package.json +1 -1
- package/node_modules/@larksuiteoapi/node-sdk/types/index.d.ts +12522 -698
- package/node_modules/@protobufjs/utf8/CHANGELOG.md +8 -0
- package/node_modules/@protobufjs/utf8/index.js +29 -18
- package/node_modules/@protobufjs/utf8/package.json +2 -2
- package/node_modules/@protobufjs/utf8/tests/index.js +5 -0
- package/node_modules/@types/node/README.md +1 -1
- package/node_modules/@types/node/fs/promises.d.ts +16 -6
- package/node_modules/@types/node/package.json +2 -2
- package/node_modules/agent-base/README.md +145 -0
- package/node_modules/agent-base/dist/src/index.d.ts +78 -0
- package/node_modules/agent-base/dist/src/index.js +203 -0
- package/node_modules/agent-base/dist/src/index.js.map +1 -0
- package/node_modules/agent-base/dist/src/promisify.d.ts +4 -0
- package/node_modules/agent-base/dist/src/promisify.js +18 -0
- package/node_modules/agent-base/dist/src/promisify.js.map +1 -0
- package/node_modules/agent-base/package.json +64 -0
- package/node_modules/agent-base/src/index.ts +345 -0
- package/node_modules/agent-base/src/promisify.ts +33 -0
- package/node_modules/axios/CHANGELOG.md +197 -1
- package/node_modules/axios/README.md +424 -256
- package/node_modules/axios/dist/axios.js +589 -204
- package/node_modules/axios/dist/axios.min.js +3 -3
- package/node_modules/axios/dist/axios.min.js.map +1 -1
- package/node_modules/axios/dist/browser/axios.cjs +630 -185
- package/node_modules/axios/dist/esm/axios.js +630 -185
- package/node_modules/axios/dist/esm/axios.min.js +2 -2
- package/node_modules/axios/dist/esm/axios.min.js.map +1 -1
- package/node_modules/axios/dist/node/axios.cjs +949 -301
- package/node_modules/axios/index.d.cts +28 -5
- package/node_modules/axios/index.d.ts +24 -3
- package/node_modules/axios/lib/adapters/adapters.js +1 -1
- package/node_modules/axios/lib/adapters/fetch.js +223 -49
- package/node_modules/axios/lib/adapters/http.js +408 -193
- package/node_modules/axios/lib/adapters/xhr.js +3 -1
- package/node_modules/axios/lib/core/Axios.js +4 -2
- package/node_modules/axios/lib/core/AxiosError.js +13 -1
- package/node_modules/axios/lib/core/AxiosHeaders.js +13 -42
- package/node_modules/axios/lib/core/buildFullPath.js +29 -1
- package/node_modules/axios/lib/core/mergeConfig.js +35 -0
- package/node_modules/axios/lib/defaults/transitional.js +2 -0
- package/node_modules/axios/lib/env/data.js +1 -1
- package/node_modules/axios/lib/helpers/AxiosURLSearchParams.js +1 -3
- package/node_modules/axios/lib/helpers/Http2Sessions.js +119 -0
- package/node_modules/axios/lib/helpers/buildURL.js +7 -4
- package/node_modules/axios/lib/helpers/composeSignals.js +48 -47
- package/node_modules/axios/lib/helpers/cookies.js +5 -1
- package/node_modules/axios/lib/helpers/estimateDataURLDecodedBytes.js +16 -11
- package/node_modules/axios/lib/helpers/formDataToJSON.js +26 -4
- package/node_modules/axios/lib/helpers/formDataToStream.js +2 -2
- package/node_modules/axios/lib/helpers/fromDataURI.js +20 -5
- package/node_modules/axios/lib/helpers/progressEventReducer.js +3 -0
- package/node_modules/axios/lib/helpers/resolveConfig.js +26 -13
- package/node_modules/axios/lib/helpers/sanitizeHeaderValue.js +60 -0
- package/node_modules/axios/lib/helpers/shouldBypassProxy.js +33 -1
- package/node_modules/axios/lib/helpers/toFormData.js +48 -12
- package/node_modules/axios/lib/helpers/validator.js +1 -1
- package/node_modules/axios/lib/utils.js +105 -19
- package/node_modules/axios/package.json +31 -13
- package/node_modules/https-proxy-agent/README.md +137 -0
- package/node_modules/https-proxy-agent/dist/agent.d.ts +30 -0
- package/node_modules/https-proxy-agent/dist/agent.js +177 -0
- package/node_modules/https-proxy-agent/dist/agent.js.map +1 -0
- package/node_modules/https-proxy-agent/dist/index.d.ts +23 -0
- package/node_modules/https-proxy-agent/dist/index.js +14 -0
- package/node_modules/https-proxy-agent/dist/index.js.map +1 -0
- package/node_modules/https-proxy-agent/dist/parse-proxy-response.d.ts +7 -0
- package/node_modules/https-proxy-agent/dist/parse-proxy-response.js +66 -0
- package/node_modules/https-proxy-agent/dist/parse-proxy-response.js.map +1 -0
- package/node_modules/https-proxy-agent/package.json +56 -0
- package/node_modules/protobufjs/dist/light/protobuf.js +145 -188
- package/node_modules/protobufjs/dist/light/protobuf.js.map +1 -1
- package/node_modules/protobufjs/dist/light/protobuf.min.js +3 -3
- package/node_modules/protobufjs/dist/light/protobuf.min.js.map +1 -1
- package/node_modules/protobufjs/dist/minimal/protobuf.js +33 -76
- package/node_modules/protobufjs/dist/minimal/protobuf.js.map +1 -1
- package/node_modules/protobufjs/dist/minimal/protobuf.min.js +3 -3
- package/node_modules/protobufjs/dist/minimal/protobuf.min.js.map +1 -1
- package/node_modules/protobufjs/dist/protobuf.js +168 -208
- package/node_modules/protobufjs/dist/protobuf.js.map +1 -1
- package/node_modules/protobufjs/dist/protobuf.min.js +3 -3
- package/node_modules/protobufjs/dist/protobuf.min.js.map +1 -1
- package/node_modules/protobufjs/index.d.ts +0 -8
- package/node_modules/protobufjs/package.json +2 -3
- package/node_modules/protobufjs/src/parse.js +3 -0
- package/node_modules/protobufjs/src/util/minimal.js +0 -3
- package/node_modules/qs/CHANGELOG.md +11 -0
- package/node_modules/qs/dist/qs.js +17 -17
- package/node_modules/qs/lib/parse.js +19 -8
- package/node_modules/qs/lib/utils.js +47 -8
- package/node_modules/qs/package.json +6 -5
- package/node_modules/qs/test/parse.js +230 -0
- package/node_modules/qs/test/utils.js +194 -0
- package/node_modules/typebox/build/compile/validator.d.mts +1 -4
- package/node_modules/typebox/build/guard/guard.d.mts +1 -1
- package/node_modules/typebox/build/guard/guard.mjs +5 -2
- package/node_modules/typebox/build/guard/string.mjs +16 -4
- package/node_modules/typebox/build/schema/engine/_functions.mjs +34 -15
- package/node_modules/typebox/build/type/action/readonly_object.d.mts +5 -0
- package/node_modules/typebox/build/type/action/readonly_object.mjs +8 -0
- package/node_modules/typebox/build/typebox.d.mts +1 -1
- package/node_modules/typebox/build/typebox.mjs +1 -1
- package/node_modules/typebox/package.json +1 -1
- package/node_modules/typebox/readme.md +29 -32
- package/node_modules/ws/lib/receiver.js +15 -32
- package/node_modules/ws/lib/websocket-server.js +4 -4
- package/node_modules/ws/lib/websocket.js +4 -4
- package/node_modules/ws/package.json +5 -1
- package/openclaw.plugin.json +2 -16
- package/package.json +20 -8
- package/dist/doctor-contract-DXH5hux1.js +0 -97
- package/dist/monitor.startup-Cy8NPGAX.js +0 -43
- package/node_modules/@protobufjs/inquire/CHANGELOG.md +0 -8
- package/node_modules/@protobufjs/inquire/LICENSE +0 -26
- package/node_modules/@protobufjs/inquire/README.md +0 -13
- package/node_modules/@protobufjs/inquire/index.d.ts +0 -10
- package/node_modules/@protobufjs/inquire/index.js +0 -38
- package/node_modules/@protobufjs/inquire/package.json +0 -21
- package/node_modules/@protobufjs/inquire/tests/data/array.js +0 -1
- package/node_modules/@protobufjs/inquire/tests/data/emptyArray.js +0 -1
- package/node_modules/@protobufjs/inquire/tests/data/emptyObject.js +0 -1
- package/node_modules/@protobufjs/inquire/tests/data/object.js +0 -1
- package/node_modules/@protobufjs/inquire/tests/index.js +0 -20
- package/node_modules/axios/dist/axios.js.map +0 -1
- package/node_modules/axios/dist/browser/axios.cjs.map +0 -1
- package/node_modules/axios/dist/esm/axios.js.map +0 -1
- package/node_modules/axios/dist/node/axios.cjs.map +0 -1
- package/npm-shrinkwrap.json +0 -1354
|
@@ -1,44 +1,46 @@
|
|
|
1
|
-
import { _ as normalizeCommentFileType, c as encodeQuery, d as isRecord$
|
|
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-CKhCa76b.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-
|
|
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-Bcofg0Vv.js";
|
|
4
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-
|
|
5
|
+
import { a as getFeishuUserAgent, i as createFeishuWSClient, n as createEventDispatcher, r as createFeishuClient, s as resolveConfiguredHttpTimeoutMs } from "./client-Dee7cKsS.js";
|
|
6
6
|
import { t as getFeishuRuntime } from "./runtime-C5JxBWZp.js";
|
|
7
|
-
import { i as createCommentTypingReactionLifecycle, l as getChatInfo, t as deliverCommentThreadText } from "./drive-
|
|
8
|
-
import { t as createFeishuThreadBindingManager } from "./thread-bindings-
|
|
7
|
+
import { i as createCommentTypingReactionLifecycle, l as getChatInfo, t as deliverCommentThreadText } from "./drive-DG6pKTPE.js";
|
|
8
|
+
import { t as createFeishuThreadBindingManager } from "./thread-bindings-Dqs_A0du.js";
|
|
9
9
|
import { createReplyPrefixContext, evaluateSupplementalContextVisibility, getSessionEntry, normalizeAgentId as normalizeAgentId$2, resolveChannelContextVisibilityMode } from "./runtime-api.js";
|
|
10
10
|
import { t as readFeishuJsonResponse } from "./json-response-CheljwGr.js";
|
|
11
|
-
import { _ as buildFeishuMediaFallbackText,
|
|
12
|
-
import {
|
|
13
|
-
import { t as fetchBotIdentityForMonitor } from "./monitor.startup-
|
|
14
|
-
import { createChannelMessageReplyPipeline, formatChannelProgressDraftLineForEntry, isChannelProgressDraftWorkToolName, resolveAgentOutboundIdentity, resolveChannelPreviewStreamMode, resolveChannelStreamingBlockEnabled } from "openclaw/plugin-sdk/channel-outbound";
|
|
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-DOwcLJ1j.js";
|
|
12
|
+
import { a as waitForAbortableDelay, i as raceWithTimeoutAndAbort } from "./probe-Za1kgUvx.js";
|
|
13
|
+
import { t as fetchBotIdentityForMonitor } from "./monitor.startup-CSUPT_U1.js";
|
|
14
|
+
import { DEFAULT_INGRESS_ADOPTION_STALL_MS, bindIngressLifecycleToReplyOptions, createChannelIngressMonitor, createChannelMessageReplyPipeline, createMessageReceiptFromOutboundResults, formatChannelProgressDraftLineForEntry, isChannelProgressDraftWorkToolName, resolveAgentOutboundIdentity, resolveChannelPreviewStreamMode, resolveChannelStreamingBlockEnabled } from "openclaw/plugin-sdk/channel-outbound";
|
|
15
15
|
import { createChannelPairingController, createChannelPairingController as createChannelPairingController$1 } from "openclaw/plugin-sdk/channel-pairing";
|
|
16
16
|
import { ensureConfiguredBindingRouteReady, resolveConfiguredBindingRoute, resolveRuntimeConversationBindingRoute } from "openclaw/plugin-sdk/conversation-runtime";
|
|
17
17
|
import { asDateTimestampMs, isFutureDateTimestampMs, parseStrictNonNegativeInteger, resolveDateTimestampMs, resolveExpiresAtMsFromDurationMs, resolveExpiresAtMsFromDurationSeconds, resolveTimerTimeoutMs } from "openclaw/plugin-sdk/number-runtime";
|
|
18
18
|
import { asBoolean, asNullableRecord, isRecord, normalizeLowercaseStringOrEmpty, normalizeOptionalLowercaseString, normalizeOptionalString, readStringValue, uniqueStrings } from "openclaw/plugin-sdk/string-coerce-runtime";
|
|
19
19
|
import { stripReasoningTagsFromText } from "openclaw/plugin-sdk/text-chunking";
|
|
20
20
|
import { resolveDefaultGroupPolicy, resolveOpenProviderRuntimeGroupPolicy, warnMissingProviderGroupPolicyFallbackOnce } from "openclaw/plugin-sdk/runtime-group-policy";
|
|
21
|
+
import { fanInChannelIngressLifecycles } from "openclaw/plugin-sdk/channel-ingress-runtime";
|
|
21
22
|
import fs from "node:fs";
|
|
22
23
|
import os from "node:os";
|
|
23
24
|
import path from "node:path";
|
|
24
25
|
import { normalizeAccountId, resolveAgentRoute, resolveInboundLastRouteSessionKey } from "openclaw/plugin-sdk/routing";
|
|
25
26
|
import { resolveStorePath } from "openclaw/plugin-sdk/session-store-runtime";
|
|
26
|
-
import { formatErrorMessage } from "openclaw/plugin-sdk/error-runtime";
|
|
27
|
+
import { collectErrorGraphCandidates, formatErrorMessage } from "openclaw/plugin-sdk/error-runtime";
|
|
27
28
|
import * as Lark from "@larksuiteoapi/node-sdk";
|
|
29
|
+
import { formatReasoningMessage, resolveHumanDelayConfig } from "openclaw/plugin-sdk/agent-runtime";
|
|
28
30
|
import { WEBHOOK_ANOMALY_COUNTER_DEFAULTS, WEBHOOK_RATE_LIMIT_DEFAULTS, applyBasicWebhookRequestGuards, createFixedWindowRateLimiter, createWebhookAnomalyTracker, resolveRequestClientIp } from "openclaw/plugin-sdk/webhook-ingress";
|
|
29
31
|
import { fetchWithSsrFGuard } from "openclaw/plugin-sdk/ssrf-runtime";
|
|
30
32
|
import { getReplyPayloadTtsSupplement, resolveSendableOutboundReplyParts, resolveTextChunksWithFallback, sendMediaWithLeadingCaption } from "openclaw/plugin-sdk/reply-payload";
|
|
31
33
|
import { resolvePinnedMainDmOwnerFromAllowlist, safeEqualSecret } from "openclaw/plugin-sdk/security-runtime";
|
|
32
34
|
import * as crypto$1 from "node:crypto";
|
|
33
35
|
import crypto, { createHash } from "node:crypto";
|
|
34
|
-
import { buildChannelInboundEventContext, formatAgentEnvelope, formatInboundMediaUnavailableText, recordChannelBotPairLoopAndCheckSuppression, resolveEnvelopeFormatOptions, toInboundMediaFacts } from "openclaw/plugin-sdk/channel-inbound";
|
|
36
|
+
import { buildChannelInboundEventContext, createChannelPartialDeliveryError, formatAgentEnvelope, formatInboundMediaUnavailableText, isChannelPartialDeliveryError, recordChannelBotPairLoopAndCheckSuppression, resolveEnvelopeFormatOptions, toInboundMediaFacts } from "openclaw/plugin-sdk/channel-inbound";
|
|
35
37
|
import { DEFAULT_GROUP_HISTORY_LIMIT, createChannelHistoryWindow } from "openclaw/plugin-sdk/reply-history";
|
|
36
38
|
import { sliceUtf16Safe, truncateUtf16Safe } from "openclaw/plugin-sdk/text-utility-runtime";
|
|
37
39
|
import { pruneMapToMaxSize } from "openclaw/plugin-sdk/collection-runtime";
|
|
38
40
|
import { createChannelReplayGuard } from "openclaw/plugin-sdk/persistent-dedupe";
|
|
39
41
|
import { resolveChannelConfigWrites } from "openclaw/plugin-sdk/channel-config-writes";
|
|
40
|
-
import { formatReasoningMessage, resolveHumanDelayConfig } from "openclaw/plugin-sdk/agent-runtime";
|
|
41
42
|
import { logTypingFailure } from "openclaw/plugin-sdk/channel-feedback";
|
|
43
|
+
import { getGlobalHookRunner } from "openclaw/plugin-sdk/plugin-runtime";
|
|
42
44
|
import * as http from "node:http";
|
|
43
45
|
import { installRequestBodyLimitGuard, readWebhookBodyOrReject } from "openclaw/plugin-sdk/webhook-request-guards";
|
|
44
46
|
import { isAbortRequestText, isBtwRequestText } from "openclaw/plugin-sdk/command-primitives-runtime";
|
|
@@ -74,6 +76,167 @@ function buildFeishuAgentBody(params) {
|
|
|
74
76
|
}
|
|
75
77
|
//#endregion
|
|
76
78
|
//#region extensions/feishu/src/bot-broadcast.ts
|
|
79
|
+
function createFeishuBroadcastIngressSettlement(params) {
|
|
80
|
+
const lanes = /* @__PURE__ */ new Set();
|
|
81
|
+
const failures = [];
|
|
82
|
+
const fallbackAbortSignal = new AbortController().signal;
|
|
83
|
+
let fanoutSettled = false;
|
|
84
|
+
let terminal;
|
|
85
|
+
let adoption;
|
|
86
|
+
let abandonment;
|
|
87
|
+
let finalizing = false;
|
|
88
|
+
let deferred = false;
|
|
89
|
+
let replayReleased = false;
|
|
90
|
+
const beginFinalizing = () => {
|
|
91
|
+
if (finalizing) return;
|
|
92
|
+
finalizing = true;
|
|
93
|
+
params.lifecycle?.onAdoptionFinalizing();
|
|
94
|
+
};
|
|
95
|
+
const defer = () => {
|
|
96
|
+
if (deferred) return;
|
|
97
|
+
deferred = true;
|
|
98
|
+
params.lifecycle?.onDeferred();
|
|
99
|
+
};
|
|
100
|
+
const reportReplayCommitError = (error) => {
|
|
101
|
+
try {
|
|
102
|
+
params.onReplayCommitError?.(error);
|
|
103
|
+
} catch {}
|
|
104
|
+
};
|
|
105
|
+
const releaseReplayClaim = (error) => {
|
|
106
|
+
if (replayReleased || terminal === "adopted") return;
|
|
107
|
+
replayReleased = true;
|
|
108
|
+
params.replayClaim?.release({ error });
|
|
109
|
+
};
|
|
110
|
+
const runAbandonment = async (error) => {
|
|
111
|
+
if (terminal) return;
|
|
112
|
+
releaseReplayClaim(error);
|
|
113
|
+
try {
|
|
114
|
+
await params.lifecycle?.onAbandoned();
|
|
115
|
+
} finally {
|
|
116
|
+
terminal = "abandoned";
|
|
117
|
+
}
|
|
118
|
+
};
|
|
119
|
+
const abandon = async (error) => {
|
|
120
|
+
if (terminal) return;
|
|
121
|
+
if (adoption) {
|
|
122
|
+
await adoption.catch(() => void 0);
|
|
123
|
+
if (terminal) return;
|
|
124
|
+
}
|
|
125
|
+
const activeAbandonment = abandonment ?? runAbandonment(error);
|
|
126
|
+
abandonment = activeAbandonment;
|
|
127
|
+
await activeAbandonment;
|
|
128
|
+
};
|
|
129
|
+
const runAdoption = async () => {
|
|
130
|
+
beginFinalizing();
|
|
131
|
+
try {
|
|
132
|
+
await params.lifecycle?.onAdopted();
|
|
133
|
+
terminal = "adopted";
|
|
134
|
+
try {
|
|
135
|
+
params.onAdopted?.();
|
|
136
|
+
} catch {}
|
|
137
|
+
try {
|
|
138
|
+
await params.replayClaim?.commit();
|
|
139
|
+
} catch (error) {
|
|
140
|
+
reportReplayCommitError(error);
|
|
141
|
+
}
|
|
142
|
+
} catch (error) {
|
|
143
|
+
await runAbandonment(error).catch(() => void 0);
|
|
144
|
+
throw error;
|
|
145
|
+
}
|
|
146
|
+
};
|
|
147
|
+
const adopt = async () => {
|
|
148
|
+
if (terminal) return;
|
|
149
|
+
if (abandonment) {
|
|
150
|
+
await abandonment.catch(() => void 0);
|
|
151
|
+
if (terminal) return;
|
|
152
|
+
}
|
|
153
|
+
const activeAdoption = adoption ?? runAdoption();
|
|
154
|
+
adoption = activeAdoption;
|
|
155
|
+
await activeAdoption;
|
|
156
|
+
};
|
|
157
|
+
const maybeSettle = async () => {
|
|
158
|
+
if (!fanoutSettled || terminal) return;
|
|
159
|
+
if (failures.length > 0 || [...lanes].some((lane) => lane.status === "failed" || lane.status === "abandoned")) {
|
|
160
|
+
await abandon(failures.length === 1 ? failures[0] : new AggregateError(failures, "Feishu broadcast dispatch failed"));
|
|
161
|
+
return;
|
|
162
|
+
}
|
|
163
|
+
if ([...lanes].some((lane) => lane.status === "pending" || lane.status === "deferred" || lane.status === "adopted")) return;
|
|
164
|
+
await adopt();
|
|
165
|
+
};
|
|
166
|
+
return {
|
|
167
|
+
createLane: (replayClaim) => {
|
|
168
|
+
const lane = {
|
|
169
|
+
replayClaim,
|
|
170
|
+
status: "pending"
|
|
171
|
+
};
|
|
172
|
+
lanes.add(lane);
|
|
173
|
+
const releaseLane = (error) => {
|
|
174
|
+
lane.replayClaim?.release({ error });
|
|
175
|
+
};
|
|
176
|
+
return {
|
|
177
|
+
lifecycle: {
|
|
178
|
+
abortSignal: params.lifecycle?.abortSignal ?? fallbackAbortSignal,
|
|
179
|
+
onAdopted: async () => {
|
|
180
|
+
if (lane.status === "adopted" || lane.status === "completed" || lane.status === "failed" || lane.status === "abandoned") return;
|
|
181
|
+
lane.status = "adopted";
|
|
182
|
+
beginFinalizing();
|
|
183
|
+
try {
|
|
184
|
+
await lane.replayClaim?.commit();
|
|
185
|
+
} catch (error) {
|
|
186
|
+
reportReplayCommitError(error);
|
|
187
|
+
}
|
|
188
|
+
lane.status = "completed";
|
|
189
|
+
await maybeSettle();
|
|
190
|
+
},
|
|
191
|
+
onDeferred: () => {
|
|
192
|
+
if (lane.status !== "pending") return;
|
|
193
|
+
lane.status = "deferred";
|
|
194
|
+
defer();
|
|
195
|
+
},
|
|
196
|
+
onAdoptionFinalizing: beginFinalizing,
|
|
197
|
+
onAbandoned: async () => {
|
|
198
|
+
if (lane.status === "completed" || lane.status === "failed" || lane.status === "abandoned") return;
|
|
199
|
+
lane.status = "abandoned";
|
|
200
|
+
releaseLane(/* @__PURE__ */ new Error("feishu-broadcast-turn-abandoned"));
|
|
201
|
+
await maybeSettle();
|
|
202
|
+
}
|
|
203
|
+
},
|
|
204
|
+
onDispatchComplete: async (dispatched) => {
|
|
205
|
+
if (!dispatched && lane.status === "pending") {
|
|
206
|
+
const error = /* @__PURE__ */ new Error("feishu broadcast lane was not dispatched");
|
|
207
|
+
lane.status = "failed";
|
|
208
|
+
failures.push(error);
|
|
209
|
+
releaseLane(error);
|
|
210
|
+
return;
|
|
211
|
+
}
|
|
212
|
+
if (lane.status !== "pending") return;
|
|
213
|
+
const error = /* @__PURE__ */ new Error("feishu broadcast dispatch returned before turn adoption");
|
|
214
|
+
lane.status = "failed";
|
|
215
|
+
failures.push(error);
|
|
216
|
+
releaseLane(error);
|
|
217
|
+
},
|
|
218
|
+
onDispatchFailed: async (error) => {
|
|
219
|
+
failures.push(error);
|
|
220
|
+
if (lane.status !== "completed") {
|
|
221
|
+
lane.status = "failed";
|
|
222
|
+
releaseLane(error);
|
|
223
|
+
}
|
|
224
|
+
await maybeSettle();
|
|
225
|
+
}
|
|
226
|
+
};
|
|
227
|
+
},
|
|
228
|
+
onLanePending: defer,
|
|
229
|
+
onDispatchComplete: async () => {
|
|
230
|
+
fanoutSettled = true;
|
|
231
|
+
await maybeSettle();
|
|
232
|
+
},
|
|
233
|
+
onDispatchFailed: async (error) => {
|
|
234
|
+
failures.push(error);
|
|
235
|
+
fanoutSettled = true;
|
|
236
|
+
await maybeSettle();
|
|
237
|
+
}
|
|
238
|
+
};
|
|
239
|
+
}
|
|
77
240
|
function resolveBroadcastAgents(cfg, peerId) {
|
|
78
241
|
const broadcast = cfg.broadcast;
|
|
79
242
|
if (!broadcast || typeof broadcast !== "object") return null;
|
|
@@ -139,11 +302,14 @@ function resolveFeishuGroupSession(params) {
|
|
|
139
302
|
};
|
|
140
303
|
}
|
|
141
304
|
function parseMessageContent(content, messageType) {
|
|
142
|
-
if (messageType === "post") return parsePostContent(content
|
|
305
|
+
if (messageType === "post") return parsePostContent(content, {
|
|
306
|
+
renderMediaPlaceholders: false,
|
|
307
|
+
emptyTextFallback: ""
|
|
308
|
+
}).textContent;
|
|
143
309
|
try {
|
|
144
310
|
const parsed = JSON.parse(content);
|
|
145
311
|
if (messageType === "text") return parsed.text || "";
|
|
146
|
-
if (FEISHU_MEDIA_MESSAGE_TYPES.has(messageType)) return formatFeishuMediaContent(parsed, messageType)
|
|
312
|
+
if (FEISHU_MEDIA_MESSAGE_TYPES.has(messageType)) return formatFeishuMediaContent(parsed, messageType);
|
|
147
313
|
if (messageType === "share_chat") {
|
|
148
314
|
if (parsed && typeof parsed === "object") {
|
|
149
315
|
const share = parsed;
|
|
@@ -156,7 +322,7 @@ function parseMessageContent(content, messageType) {
|
|
|
156
322
|
if (messageType === "merge_forward") return "[Merged and Forwarded Message - loading...]";
|
|
157
323
|
return content;
|
|
158
324
|
} catch {
|
|
159
|
-
return content;
|
|
325
|
+
return FEISHU_MEDIA_MESSAGE_TYPES.has(messageType) ? "" : content;
|
|
160
326
|
}
|
|
161
327
|
}
|
|
162
328
|
const FEISHU_MEDIA_MESSAGE_TYPES = /* @__PURE__ */ new Set([
|
|
@@ -169,32 +335,8 @@ const FEISHU_MEDIA_MESSAGE_TYPES = /* @__PURE__ */ new Set([
|
|
|
169
335
|
]);
|
|
170
336
|
function formatFeishuMediaContent(parsed, messageType) {
|
|
171
337
|
const speechToText = messageType === "audio" && typeof parsed.speech_to_text === "string" ? parsed.speech_to_text.trim() : "";
|
|
172
|
-
if (speechToText) return
|
|
173
|
-
|
|
174
|
-
const fileName = typeof parsed.file_name === "string" ? parsed.file_name.trim() : "";
|
|
175
|
-
return {
|
|
176
|
-
body: fileName ? `${placeholder} (${fileName})` : placeholder,
|
|
177
|
-
mediaPlaceholder: placeholder,
|
|
178
|
-
unavailableBody: fileName || void 0
|
|
179
|
-
};
|
|
180
|
-
}
|
|
181
|
-
function resolveFeishuMediaFailurePresentation(content, messageType) {
|
|
182
|
-
if (messageType === "post") return { unavailableBody: parsePostContent(content, {
|
|
183
|
-
renderMediaPlaceholders: false,
|
|
184
|
-
emptyTextFallback: ""
|
|
185
|
-
}).textContent };
|
|
186
|
-
if (!FEISHU_MEDIA_MESSAGE_TYPES.has(messageType)) return {};
|
|
187
|
-
try {
|
|
188
|
-
const parsed = JSON.parse(content);
|
|
189
|
-
if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) return {};
|
|
190
|
-
const presentation = formatFeishuMediaContent(parsed, messageType);
|
|
191
|
-
return {
|
|
192
|
-
mediaPlaceholder: presentation.mediaPlaceholder,
|
|
193
|
-
unavailableBody: presentation.unavailableBody
|
|
194
|
-
};
|
|
195
|
-
} catch {
|
|
196
|
-
return {};
|
|
197
|
-
}
|
|
338
|
+
if (speechToText) return speechToText;
|
|
339
|
+
return "";
|
|
198
340
|
}
|
|
199
341
|
function formatSubMessageContent(content, contentType) {
|
|
200
342
|
try {
|
|
@@ -295,15 +437,15 @@ async function resolveSavedFeishuMedia(params) {
|
|
|
295
437
|
const contentType = params.result.contentType ?? await core.media.detectMime({ buffer: params.result.buffer });
|
|
296
438
|
return await core.channel.media.saveMediaBuffer(params.result.buffer, contentType, "inbound", params.maxBytes, params.result.fileName ?? params.originalFilename);
|
|
297
439
|
}
|
|
298
|
-
function
|
|
440
|
+
function resolveFeishuMediaKind(messageType) {
|
|
299
441
|
switch (messageType) {
|
|
300
|
-
case "image": return "
|
|
301
|
-
case "file": return "
|
|
302
|
-
case "audio": return "
|
|
442
|
+
case "image": return "image";
|
|
443
|
+
case "file": return "document";
|
|
444
|
+
case "audio": return "audio";
|
|
303
445
|
case "video":
|
|
304
|
-
case "media": return "
|
|
305
|
-
case "sticker": return "
|
|
306
|
-
default: return "
|
|
446
|
+
case "media": return "video";
|
|
447
|
+
case "sticker": return "sticker";
|
|
448
|
+
default: return "document";
|
|
307
449
|
}
|
|
308
450
|
}
|
|
309
451
|
async function resolveFeishuMediaList(params) {
|
|
@@ -316,18 +458,11 @@ async function resolveFeishuMediaList(params) {
|
|
|
316
458
|
"media",
|
|
317
459
|
"sticker",
|
|
318
460
|
"post"
|
|
319
|
-
].includes(messageType)) return
|
|
320
|
-
media: [],
|
|
321
|
-
unavailableCount: 0
|
|
322
|
-
};
|
|
461
|
+
].includes(messageType)) return [];
|
|
323
462
|
const out = [];
|
|
324
|
-
let unavailableCount = 0;
|
|
325
463
|
if (messageType === "post") {
|
|
326
464
|
const { imageKeys, mediaKeys } = parsePostContent(content);
|
|
327
|
-
if (imageKeys.length === 0 && mediaKeys.length === 0) return
|
|
328
|
-
media: [],
|
|
329
|
-
unavailableCount: 0
|
|
330
|
-
};
|
|
465
|
+
if (imageKeys.length === 0 && mediaKeys.length === 0) return [];
|
|
331
466
|
if (imageKeys.length > 0) log?.(`feishu: post message contains ${imageKeys.length} embedded image(s)`);
|
|
332
467
|
if (mediaKeys.length > 0) log?.(`feishu: post message contains ${mediaKeys.length} embedded media file(s)`);
|
|
333
468
|
for (const imageKey of imageKeys) try {
|
|
@@ -345,11 +480,11 @@ async function resolveFeishuMediaList(params) {
|
|
|
345
480
|
out.push({
|
|
346
481
|
path: saved.path,
|
|
347
482
|
contentType: saved.contentType,
|
|
348
|
-
|
|
483
|
+
kind: "image"
|
|
349
484
|
});
|
|
350
485
|
log?.(`feishu: downloaded embedded image ${imageKey}, saved to ${saved.path}`);
|
|
351
486
|
} catch (err) {
|
|
352
|
-
|
|
487
|
+
out.push({ kind: "image" });
|
|
353
488
|
log?.(`feishu: failed to download embedded image ${imageKey}: ${String(err)}`);
|
|
354
489
|
}
|
|
355
490
|
for (const media of mediaKeys) try {
|
|
@@ -369,29 +504,20 @@ async function resolveFeishuMediaList(params) {
|
|
|
369
504
|
out.push({
|
|
370
505
|
path: saved.path,
|
|
371
506
|
contentType: saved.contentType,
|
|
372
|
-
|
|
507
|
+
kind: "video"
|
|
373
508
|
});
|
|
374
509
|
log?.(`feishu: downloaded embedded media ${media.fileKey}, saved to ${saved.path}`);
|
|
375
510
|
} catch (err) {
|
|
376
|
-
|
|
511
|
+
out.push({ kind: "video" });
|
|
377
512
|
log?.(`feishu: failed to download embedded media ${media.fileKey}: ${String(err)}`);
|
|
378
513
|
}
|
|
379
|
-
return
|
|
380
|
-
media: out,
|
|
381
|
-
unavailableCount
|
|
382
|
-
};
|
|
514
|
+
return out;
|
|
383
515
|
}
|
|
384
516
|
const mediaKeys = parseMediaKeys(content, messageType);
|
|
385
|
-
if (!mediaKeys.imageKey && !mediaKeys.fileKey) return {
|
|
386
|
-
media: [],
|
|
387
|
-
unavailableCount: 1
|
|
388
|
-
};
|
|
517
|
+
if (!mediaKeys.imageKey && !mediaKeys.fileKey) return [{ kind: resolveFeishuMediaKind(messageType) }];
|
|
389
518
|
try {
|
|
390
519
|
const fileKey = mediaKeys.fileKey || mediaKeys.imageKey;
|
|
391
|
-
if (!fileKey) return {
|
|
392
|
-
media: [],
|
|
393
|
-
unavailableCount: 1
|
|
394
|
-
};
|
|
520
|
+
if (!fileKey) return [{ kind: resolveFeishuMediaKind(messageType) }];
|
|
395
521
|
const saved = await resolveSavedFeishuMedia({
|
|
396
522
|
result: await saveMessageResourceFeishu({
|
|
397
523
|
cfg,
|
|
@@ -408,17 +534,14 @@ async function resolveFeishuMediaList(params) {
|
|
|
408
534
|
out.push({
|
|
409
535
|
path: saved.path,
|
|
410
536
|
contentType: saved.contentType,
|
|
411
|
-
|
|
537
|
+
kind: resolveFeishuMediaKind(messageType)
|
|
412
538
|
});
|
|
413
539
|
log?.(`feishu: downloaded ${messageType} media, saved to ${saved.path}`);
|
|
414
540
|
} catch (err) {
|
|
415
|
-
|
|
541
|
+
out.push({ kind: resolveFeishuMediaKind(messageType) });
|
|
416
542
|
log?.(`feishu: failed to download ${messageType} media: ${String(err)}`);
|
|
417
543
|
}
|
|
418
|
-
return
|
|
419
|
-
media: out,
|
|
420
|
-
unavailableCount
|
|
421
|
-
};
|
|
544
|
+
return out;
|
|
422
545
|
}
|
|
423
546
|
//#endregion
|
|
424
547
|
//#region extensions/feishu/src/bot-group-name-state.ts
|
|
@@ -742,9 +865,7 @@ function dedupeOptions(namespace, log) {
|
|
|
742
865
|
* unidentifiable event is never suppressed.
|
|
743
866
|
*/
|
|
744
867
|
async function claimUnprocessedFeishuMessage(params) {
|
|
745
|
-
|
|
746
|
-
if (claim.kind === "inflight") return { kind: "inflight" };
|
|
747
|
-
return claim;
|
|
868
|
+
return await feishuDedupeState.guard.claim(params.messageId, dedupeOptions(params.namespace, params.log));
|
|
748
869
|
}
|
|
749
870
|
/**
|
|
750
871
|
* Claims (unless the caller already holds the claim) and commits a message.
|
|
@@ -759,11 +880,6 @@ async function finalizeFeishuMessageProcessing(params) {
|
|
|
759
880
|
if ("kind" in claim && claim.kind !== "claimed") return false;
|
|
760
881
|
return await ("kind" in claim ? claim.handle : claim).commit();
|
|
761
882
|
}
|
|
762
|
-
/** Records a handled message so restart/replay cannot dispatch it again; false when already recorded. */
|
|
763
|
-
async function recordProcessedFeishuMessage(messageId, namespace = "global", log) {
|
|
764
|
-
const claim = await feishuDedupeState.guard.claim(messageId, dedupeOptions(namespace, log));
|
|
765
|
-
return claim.kind === "claimed" ? await claim.handle.commit() : false;
|
|
766
|
-
}
|
|
767
883
|
/** Forgets a recorded message so a retryable synthetic event can be handled on redelivery. */
|
|
768
884
|
async function forgetProcessedFeishuMessage(messageId, namespace = "global", log) {
|
|
769
885
|
return await feishuDedupeState.guard.forget(messageId, dedupeOptions(namespace, log));
|
|
@@ -1021,6 +1137,47 @@ function resolveFeishuReasoningPreviewEnabled(params) {
|
|
|
1021
1137
|
return configDefault === "stream";
|
|
1022
1138
|
}
|
|
1023
1139
|
//#endregion
|
|
1140
|
+
//#region extensions/feishu/src/reply-delivery-result.ts
|
|
1141
|
+
const noVisibleFeishuReplyDelivery = { visibleReplySent: false };
|
|
1142
|
+
function hasProviderIdentity(result) {
|
|
1143
|
+
return Boolean(result && (result.messageId?.trim() || result.receipt?.primaryPlatformMessageId?.trim() || result.receipt?.platformMessageIds.length));
|
|
1144
|
+
}
|
|
1145
|
+
/** Normalizes every physical Lark send behind one logical reply payload. */
|
|
1146
|
+
function createFeishuReplyDeliveryResult(params) {
|
|
1147
|
+
const results = params.visibleReplySent ? (params.results ?? []).filter(hasProviderIdentity) : [];
|
|
1148
|
+
const receipt = results.length > 0 ? createMessageReceiptFromOutboundResults({
|
|
1149
|
+
results,
|
|
1150
|
+
...params.kind ? { kind: params.kind } : {}
|
|
1151
|
+
}) : void 0;
|
|
1152
|
+
if (!receipt) return {
|
|
1153
|
+
visibleReplySent: params.visibleReplySent,
|
|
1154
|
+
...params.content === void 0 ? {} : { content: params.content }
|
|
1155
|
+
};
|
|
1156
|
+
return {
|
|
1157
|
+
messageIds: [...receipt.platformMessageIds],
|
|
1158
|
+
receipt,
|
|
1159
|
+
visibleReplySent: params.visibleReplySent,
|
|
1160
|
+
...params.content === void 0 ? {} : { content: params.content }
|
|
1161
|
+
};
|
|
1162
|
+
}
|
|
1163
|
+
/** Preserves the first result's provider identity while retaining supplemental ids. */
|
|
1164
|
+
function mergeFeishuReplyDeliveryResults(results, content) {
|
|
1165
|
+
const visible = results.filter((result) => result.visibleReplySent === true);
|
|
1166
|
+
return createFeishuReplyDeliveryResult({
|
|
1167
|
+
results: visible,
|
|
1168
|
+
visibleReplySent: visible.length > 0,
|
|
1169
|
+
content: content === void 0 ? results.find((result) => result.content !== void 0)?.content : content
|
|
1170
|
+
});
|
|
1171
|
+
}
|
|
1172
|
+
/** Keeps a failed outcome while exposing the provider-visible subset to core lifecycle hooks. */
|
|
1173
|
+
function createFeishuPartialReplyDeliveryError(cause, result) {
|
|
1174
|
+
if (result.visibleReplySent !== true) return cause instanceof Error ? cause : new Error(formatErrorMessage(cause), { cause });
|
|
1175
|
+
return createChannelPartialDeliveryError(cause, {
|
|
1176
|
+
...result,
|
|
1177
|
+
visibleReplySent: true
|
|
1178
|
+
});
|
|
1179
|
+
}
|
|
1180
|
+
//#endregion
|
|
1024
1181
|
//#region extensions/feishu/src/reply-dispatcher-state.ts
|
|
1025
1182
|
const streamingStartBackoffUntilByAccount = /* @__PURE__ */ new Map();
|
|
1026
1183
|
//#endregion
|
|
@@ -1032,6 +1189,14 @@ function resolveStreamingCardSendMode(options) {
|
|
|
1032
1189
|
}
|
|
1033
1190
|
//#endregion
|
|
1034
1191
|
//#region extensions/feishu/src/streaming-card.ts
|
|
1192
|
+
/** Provider finalization failed after a streaming card may already be visible. */
|
|
1193
|
+
var FeishuStreamingFinalizationError = class extends Error {
|
|
1194
|
+
constructor(cause, result) {
|
|
1195
|
+
super(cause instanceof Error ? cause.message : String(cause), { cause });
|
|
1196
|
+
this.name = "FeishuStreamingFinalizationError";
|
|
1197
|
+
this.result = result;
|
|
1198
|
+
}
|
|
1199
|
+
};
|
|
1035
1200
|
const STREAMING_UPDATE_THROTTLE_MS = 160;
|
|
1036
1201
|
const STREAMING_SIGNIFICANT_DELTA_CHARS = 18;
|
|
1037
1202
|
const FEISHU_STREAMING_TOKEN_DEFAULT_LIFETIME_SECONDS = 7200;
|
|
@@ -1106,23 +1271,17 @@ function truncateSummary(text, max = 50) {
|
|
|
1106
1271
|
const clean = text.replace(/\n/g, " ").trim();
|
|
1107
1272
|
return clean.length <= max ? clean : sliceUtf16Safe(clean, 0, max - 3) + "...";
|
|
1108
1273
|
}
|
|
1109
|
-
function hasNaturalStreamingBoundary(text) {
|
|
1110
|
-
return /[\n。!?!?;;::]$/.test(text);
|
|
1111
|
-
}
|
|
1112
1274
|
function shouldPushStreamingUpdate(previousText, nextText) {
|
|
1113
|
-
|
|
1114
|
-
if (hasNaturalStreamingBoundary(nextText)) return true;
|
|
1115
|
-
return nextText.length - previousText.length >= STREAMING_SIGNIFICANT_DELTA_CHARS;
|
|
1275
|
+
return !previousText || /[\n。!?!?;;::]$/.test(nextText) || nextText.length - previousText.length >= STREAMING_SIGNIFICANT_DELTA_CHARS;
|
|
1116
1276
|
}
|
|
1277
|
+
/** Merges cumulative or overlapping streaming snapshots without duplicating content. */
|
|
1117
1278
|
function mergeStreamingText(previousText, nextText) {
|
|
1118
1279
|
const previous = typeof previousText === "string" ? previousText : "";
|
|
1119
1280
|
const next = typeof nextText === "string" ? nextText : "";
|
|
1120
1281
|
if (!next) return previous;
|
|
1121
1282
|
if (!previous || next === previous) return next;
|
|
1122
|
-
if (next.startsWith(previous)) return next;
|
|
1123
|
-
if (previous.startsWith(next)) return previous;
|
|
1124
|
-
if (next.includes(previous)) return next;
|
|
1125
|
-
if (previous.includes(next)) return previous;
|
|
1283
|
+
if (next.startsWith(previous) || next.includes(previous)) return next;
|
|
1284
|
+
if (previous.startsWith(next) || previous.includes(next)) return previous;
|
|
1126
1285
|
const maxOverlap = Math.min(previous.length, next.length);
|
|
1127
1286
|
for (let overlap = maxOverlap; overlap > 0; overlap -= 1) if (previous.slice(-overlap) === next.slice(0, overlap)) return `${previous}${next.slice(overlap)}`;
|
|
1128
1287
|
return `${previous}${next}`;
|
|
@@ -1412,16 +1571,23 @@ var FeishuStreamingSession = class {
|
|
|
1412
1571
|
}
|
|
1413
1572
|
}).catch((e) => this.log?.(`Note update failed: ${String(e)}`));
|
|
1414
1573
|
}
|
|
1415
|
-
async
|
|
1416
|
-
if (!this.state || this.closed) return false;
|
|
1574
|
+
async closeWithResult(finalText, options) {
|
|
1575
|
+
if (!this.state || this.closed) return { visibleReplySent: false };
|
|
1417
1576
|
this.closed = true;
|
|
1418
1577
|
this.clearFlushTimer();
|
|
1419
1578
|
await this.queue;
|
|
1420
1579
|
const text = finalText ?? this.pendingText ?? this.state.currentText;
|
|
1421
1580
|
const apiBase = resolveApiBase(this.creds.domain);
|
|
1422
1581
|
let visibleContentSent = Boolean(this.state.sentText.trim());
|
|
1582
|
+
let finalWriteError;
|
|
1423
1583
|
if ((text || finalText !== void 0) && text !== this.state.sentText) {
|
|
1424
|
-
const sent = text.startsWith(this.state.sentText) ? await this.updateCardContent(text, (e) =>
|
|
1584
|
+
const sent = text.startsWith(this.state.sentText) ? await this.updateCardContent(text, (e) => {
|
|
1585
|
+
finalWriteError = e;
|
|
1586
|
+
this.log?.(`Final update failed: ${String(e)}`);
|
|
1587
|
+
}) : await this.replaceCardContent(text, (e) => {
|
|
1588
|
+
finalWriteError = e;
|
|
1589
|
+
this.log?.(`Final replace failed: ${String(e)}`);
|
|
1590
|
+
});
|
|
1425
1591
|
this.state.currentText = text;
|
|
1426
1592
|
if (sent) {
|
|
1427
1593
|
this.state.sentText = text;
|
|
@@ -1431,44 +1597,63 @@ var FeishuStreamingSession = class {
|
|
|
1431
1597
|
if (options?.note) await this.updateNoteContent(options.note);
|
|
1432
1598
|
const acceptedText = this.state.sentText;
|
|
1433
1599
|
this.state.sequence += 1;
|
|
1434
|
-
|
|
1435
|
-
|
|
1436
|
-
|
|
1437
|
-
|
|
1438
|
-
|
|
1439
|
-
|
|
1440
|
-
|
|
1441
|
-
|
|
1442
|
-
|
|
1443
|
-
|
|
1444
|
-
|
|
1600
|
+
let closeError;
|
|
1601
|
+
try {
|
|
1602
|
+
const { response, release } = await fetchWithSsrFGuard({
|
|
1603
|
+
url: `${apiBase}/cardkit/v1/cards/${this.state.cardId}/settings`,
|
|
1604
|
+
init: {
|
|
1605
|
+
method: "PATCH",
|
|
1606
|
+
headers: {
|
|
1607
|
+
Authorization: `Bearer ${await getToken(this.creds, {
|
|
1608
|
+
fetchImpl: this.fetchImpl,
|
|
1609
|
+
lookupFn: this.lookupFn
|
|
1610
|
+
})}`,
|
|
1611
|
+
"Content-Type": "application/json; charset=utf-8",
|
|
1612
|
+
"User-Agent": getFeishuUserAgent()
|
|
1613
|
+
},
|
|
1614
|
+
body: JSON.stringify({
|
|
1615
|
+
settings: JSON.stringify({ config: {
|
|
1616
|
+
streaming_mode: false,
|
|
1617
|
+
summary: { content: truncateSummary(acceptedText) }
|
|
1618
|
+
} }),
|
|
1619
|
+
sequence: this.state.sequence,
|
|
1620
|
+
uuid: `c_${this.state.cardId}_${this.state.sequence}`
|
|
1621
|
+
})
|
|
1445
1622
|
},
|
|
1446
|
-
|
|
1447
|
-
|
|
1448
|
-
|
|
1449
|
-
|
|
1450
|
-
|
|
1451
|
-
|
|
1452
|
-
uuid: `c_${this.state.cardId}_${this.state.sequence}`
|
|
1453
|
-
})
|
|
1454
|
-
},
|
|
1455
|
-
fetchImpl: this.fetchImpl,
|
|
1456
|
-
lookupFn: this.lookupFn,
|
|
1457
|
-
policy: { allowedHostnames: resolveAllowedHostnames(this.creds.domain) },
|
|
1458
|
-
auditContext: "feishu.streaming-card.close",
|
|
1459
|
-
timeoutMs: this.creds.httpTimeoutMs ?? 3e4
|
|
1460
|
-
}).then(async ({ response, release }) => {
|
|
1623
|
+
fetchImpl: this.fetchImpl,
|
|
1624
|
+
lookupFn: this.lookupFn,
|
|
1625
|
+
policy: { allowedHostnames: resolveAllowedHostnames(this.creds.domain) },
|
|
1626
|
+
auditContext: "feishu.streaming-card.close",
|
|
1627
|
+
timeoutMs: this.creds.httpTimeoutMs ?? 3e4
|
|
1628
|
+
});
|
|
1461
1629
|
try {
|
|
1462
1630
|
await assertSuccessfulCardKitResponse(response, "feishu.streaming-card.close", "Close streaming card");
|
|
1463
1631
|
} finally {
|
|
1464
1632
|
await release();
|
|
1465
1633
|
}
|
|
1466
|
-
}
|
|
1634
|
+
} catch (error) {
|
|
1635
|
+
closeError = error;
|
|
1636
|
+
this.log?.(`Close failed: ${String(error)}`);
|
|
1637
|
+
}
|
|
1467
1638
|
const finalState = this.state;
|
|
1468
1639
|
this.state = null;
|
|
1469
1640
|
this.pendingText = null;
|
|
1470
1641
|
this.log?.(`Closed streaming: cardId=${finalState.cardId}`);
|
|
1471
|
-
|
|
1642
|
+
const result = {
|
|
1643
|
+
visibleReplySent: visibleContentSent,
|
|
1644
|
+
...visibleContentSent ? { content: finalState.sentText } : {},
|
|
1645
|
+
messageId: finalState.messageId
|
|
1646
|
+
};
|
|
1647
|
+
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);
|
|
1648
|
+
return result;
|
|
1649
|
+
}
|
|
1650
|
+
async close(finalText, options) {
|
|
1651
|
+
try {
|
|
1652
|
+
return (await this.closeWithResult(finalText, options)).visibleReplySent;
|
|
1653
|
+
} catch (error) {
|
|
1654
|
+
if (error instanceof FeishuStreamingFinalizationError) return error.result.visibleReplySent;
|
|
1655
|
+
throw error;
|
|
1656
|
+
}
|
|
1472
1657
|
}
|
|
1473
1658
|
async discard() {
|
|
1474
1659
|
if (!this.state || this.closed) return;
|
|
@@ -1614,6 +1799,9 @@ function mergeStreamingFinalText(previousText, nextText, appendError) {
|
|
|
1614
1799
|
if (previousText.endsWith(`\n\n${nextText}`)) return previousText;
|
|
1615
1800
|
return `${previousText}\n\n${nextText}`;
|
|
1616
1801
|
}
|
|
1802
|
+
function toError(error) {
|
|
1803
|
+
return error instanceof Error ? error : new Error(String(error));
|
|
1804
|
+
}
|
|
1617
1805
|
/** Maximum age (ms) for a message to receive a typing indicator reaction.
|
|
1618
1806
|
* Messages older than this are likely replays after context compaction (#30418). */
|
|
1619
1807
|
const TYPING_INDICATOR_MAX_AGE_MS = 2 * 6e4;
|
|
@@ -1724,9 +1912,12 @@ function createFeishuReplyDispatcher(params) {
|
|
|
1724
1912
|
});
|
|
1725
1913
|
const renderMode = account.config?.renderMode ?? "auto";
|
|
1726
1914
|
const streamingEnabled = !requiredMentionTargets?.length && resolveChannelPreviewStreamMode(account.config, "partial") !== "off" && renderMode !== "raw";
|
|
1915
|
+
const hookRunner = getGlobalHookRunner();
|
|
1916
|
+
const modifyingHooksRegistered = (hookRunner?.hasHooks("reply_payload_sending") ?? false) || (hookRunner?.hasHooks("message_sending") ?? false);
|
|
1917
|
+
const previewStreamingEnabled = streamingEnabled && !modifyingHooksRegistered;
|
|
1727
1918
|
const blockStreamingEnabled = resolveChannelStreamingBlockEnabled(account.config);
|
|
1728
1919
|
const coreBlockStreamingEnabled = blockStreamingEnabled === true;
|
|
1729
|
-
const reasoningPreviewEnabled =
|
|
1920
|
+
const reasoningPreviewEnabled = previewStreamingEnabled && params.allowReasoningPreview === true;
|
|
1730
1921
|
let streaming = null;
|
|
1731
1922
|
let streamText = "";
|
|
1732
1923
|
let lastPartial = "";
|
|
@@ -1736,15 +1927,20 @@ function createFeishuReplyDispatcher(params) {
|
|
|
1736
1927
|
let lastSnapshotTextLength = 0;
|
|
1737
1928
|
let hasStreamingFinalText = false;
|
|
1738
1929
|
const deliveredFinalTexts = /* @__PURE__ */ new Set();
|
|
1930
|
+
const closedStreamingSettlements = /* @__PURE__ */ new Map();
|
|
1739
1931
|
let sentIndependentBlockText = false;
|
|
1740
1932
|
let partialUpdateQueue = Promise.resolve();
|
|
1741
1933
|
let streamingStartPromise = null;
|
|
1742
|
-
let
|
|
1743
|
-
let
|
|
1934
|
+
let streamingGeneration = 0;
|
|
1935
|
+
let activeStreamingGeneration;
|
|
1936
|
+
let inFlightStreamingClose;
|
|
1744
1937
|
let visibleReplySent = false;
|
|
1745
1938
|
let skippedFinalReason = null;
|
|
1746
1939
|
let idleSideEffectsPromise = Promise.resolve();
|
|
1940
|
+
let activeIdleSideEffectsPromise = null;
|
|
1941
|
+
let idleRequestedForReply = false;
|
|
1747
1942
|
let replyLifecycleStateInitialized = false;
|
|
1943
|
+
const pendingStreamingDeliveries = [];
|
|
1748
1944
|
const markVisibleReplySent = () => {
|
|
1749
1945
|
visibleReplySent = true;
|
|
1750
1946
|
};
|
|
@@ -1761,9 +1957,12 @@ function createFeishuReplyDispatcher(params) {
|
|
|
1761
1957
|
return parts.join("");
|
|
1762
1958
|
};
|
|
1763
1959
|
const flushStreamingCardUpdate = (combined) => {
|
|
1960
|
+
const session = streaming;
|
|
1961
|
+
const generation = activeStreamingGeneration;
|
|
1962
|
+
const startPromise = streamingStartPromise;
|
|
1764
1963
|
partialUpdateQueue = partialUpdateQueue.then(async () => {
|
|
1765
|
-
if (
|
|
1766
|
-
if (
|
|
1964
|
+
if (startPromise) await startPromise;
|
|
1965
|
+
if (generation !== void 0 && session?.isActive()) await session.update(combined);
|
|
1767
1966
|
});
|
|
1768
1967
|
};
|
|
1769
1968
|
const queueStreamingUpdate = (nextText, options) => {
|
|
@@ -1796,12 +1995,15 @@ function createFeishuReplyDispatcher(params) {
|
|
|
1796
1995
|
httpTimeoutMs: resolveConfiguredHttpTimeoutMs(account)
|
|
1797
1996
|
} : null;
|
|
1798
1997
|
if (!creds) return;
|
|
1799
|
-
|
|
1998
|
+
const session = new FeishuStreamingSession(createFeishuClient(account), creds, (message) => params.runtime.log?.(`feishu[${account.accountId}] ${message}`));
|
|
1999
|
+
const generation = ++streamingGeneration;
|
|
2000
|
+
streaming = session;
|
|
2001
|
+
activeStreamingGeneration = generation;
|
|
1800
2002
|
try {
|
|
1801
2003
|
const cardHeader = resolveCardHeader(agentId, identity);
|
|
1802
2004
|
const cardNote = resolveCardNote(agentId, identity, prefixContext.prefixContext);
|
|
1803
2005
|
const streamingTarget = sendTarget.replace(/^(feishu|lark):/i, "").replace(/^(chat|user|group|dm|open_id):/i, "").trim();
|
|
1804
|
-
await
|
|
2006
|
+
await session.start(streamingTarget, resolveReceiveIdType(sendTarget), {
|
|
1805
2007
|
replyToMessageId: sendReplyToMessageId,
|
|
1806
2008
|
replyInThread: effectiveReplyInThread,
|
|
1807
2009
|
rootId,
|
|
@@ -1812,14 +2014,18 @@ function createFeishuReplyDispatcher(params) {
|
|
|
1812
2014
|
} catch (error) {
|
|
1813
2015
|
rememberStreamingStartFailure(account.accountId);
|
|
1814
2016
|
params.runtime.error?.(`feishu[${account.accountId}]: streaming start failed; using non-streaming card fallback for ${STREAMING_START_FAILURE_BACKOFF_MS / 1e3}s: ${String(error)}`);
|
|
1815
|
-
streaming
|
|
1816
|
-
|
|
2017
|
+
if (streaming === session) {
|
|
2018
|
+
streaming = null;
|
|
2019
|
+
streamingStartPromise = null;
|
|
2020
|
+
activeStreamingGeneration = void 0;
|
|
2021
|
+
}
|
|
1817
2022
|
}
|
|
1818
2023
|
})();
|
|
1819
2024
|
};
|
|
1820
2025
|
const resetStreamingState = () => {
|
|
1821
2026
|
streaming = null;
|
|
1822
2027
|
streamingStartPromise = null;
|
|
2028
|
+
activeStreamingGeneration = void 0;
|
|
1823
2029
|
partialUpdateQueue = Promise.resolve();
|
|
1824
2030
|
streamText = "";
|
|
1825
2031
|
lastPartial = "";
|
|
@@ -1829,24 +2035,126 @@ function createFeishuReplyDispatcher(params) {
|
|
|
1829
2035
|
lastSnapshotTextLength = 0;
|
|
1830
2036
|
hasStreamingFinalText = false;
|
|
1831
2037
|
};
|
|
1832
|
-
const
|
|
2038
|
+
const rememberClosedStreamingSettlement = (generation, content, result, error) => {
|
|
2039
|
+
if (generation === void 0 || !content) return;
|
|
2040
|
+
closedStreamingSettlements.set(generation, {
|
|
2041
|
+
result,
|
|
2042
|
+
content,
|
|
2043
|
+
...error === void 0 ? {} : { error }
|
|
2044
|
+
});
|
|
2045
|
+
};
|
|
2046
|
+
const performStreamingClose = async () => {
|
|
2047
|
+
const streamingToClose = streaming;
|
|
2048
|
+
const generationToClose = activeStreamingGeneration;
|
|
2049
|
+
const startPromiseToClose = streamingStartPromise;
|
|
2050
|
+
const updateQueueToClose = partialUpdateQueue;
|
|
2051
|
+
const finalizedAnswerText = streamText;
|
|
2052
|
+
const finalizedReasoningText = reasoningText;
|
|
2053
|
+
if (generationToClose !== void 0 && activeStreamingGeneration === generationToClose) activeStreamingGeneration = void 0;
|
|
1833
2054
|
try {
|
|
1834
|
-
if (
|
|
1835
|
-
await
|
|
1836
|
-
if (
|
|
2055
|
+
if (startPromiseToClose) await startPromiseToClose;
|
|
2056
|
+
await updateQueueToClose;
|
|
2057
|
+
if (streamingToClose?.isActive()) {
|
|
1837
2058
|
statusLine = "";
|
|
1838
|
-
const text = buildCombinedStreamText(
|
|
2059
|
+
const text = buildCombinedStreamText(finalizedReasoningText, finalizedAnswerText);
|
|
1839
2060
|
const finalNote = resolveCardNote(agentId, identity, prefixContext.prefixContext);
|
|
1840
|
-
|
|
1841
|
-
|
|
1842
|
-
|
|
1843
|
-
|
|
1844
|
-
if (
|
|
2061
|
+
let closed;
|
|
2062
|
+
try {
|
|
2063
|
+
closed = await streamingToClose.closeWithResult(text, { note: finalNote });
|
|
2064
|
+
} catch (error) {
|
|
2065
|
+
if (!(error instanceof FeishuStreamingFinalizationError)) throw error;
|
|
2066
|
+
const failedResult = createFeishuReplyDeliveryResult({
|
|
2067
|
+
results: [error.result],
|
|
2068
|
+
visibleReplySent: error.result.visibleReplySent,
|
|
2069
|
+
content: error.result.content,
|
|
2070
|
+
kind: "card"
|
|
2071
|
+
});
|
|
2072
|
+
if (failedResult.visibleReplySent) markVisibleReplySent();
|
|
2073
|
+
if (failedResult.visibleReplySent && finalizedAnswerText) {
|
|
2074
|
+
const partialError = createFeishuPartialReplyDeliveryError(error.cause ?? error, failedResult);
|
|
2075
|
+
if (failedResult.content === text) deliveredFinalTexts.add(finalizedAnswerText);
|
|
2076
|
+
rememberClosedStreamingSettlement(generationToClose, finalizedAnswerText, failedResult, partialError);
|
|
2077
|
+
return {
|
|
2078
|
+
result: failedResult,
|
|
2079
|
+
...generationToClose === void 0 ? {} : { generation: generationToClose },
|
|
2080
|
+
error: partialError
|
|
2081
|
+
};
|
|
2082
|
+
}
|
|
2083
|
+
rememberClosedStreamingSettlement(generationToClose, finalizedAnswerText, failedResult, error);
|
|
2084
|
+
return {
|
|
2085
|
+
result: failedResult,
|
|
2086
|
+
...generationToClose === void 0 ? {} : { generation: generationToClose },
|
|
2087
|
+
error
|
|
2088
|
+
};
|
|
2089
|
+
}
|
|
2090
|
+
const result = createFeishuReplyDeliveryResult({
|
|
2091
|
+
results: [closed],
|
|
2092
|
+
visibleReplySent: closed.visibleReplySent,
|
|
2093
|
+
content: closed.content,
|
|
2094
|
+
kind: "card"
|
|
2095
|
+
});
|
|
2096
|
+
if (result.visibleReplySent) markVisibleReplySent();
|
|
2097
|
+
if (result.visibleReplySent && finalizedAnswerText) {
|
|
2098
|
+
deliveredFinalTexts.add(finalizedAnswerText);
|
|
2099
|
+
rememberClosedStreamingSettlement(generationToClose, finalizedAnswerText, result);
|
|
1845
2100
|
}
|
|
2101
|
+
return {
|
|
2102
|
+
result,
|
|
2103
|
+
...generationToClose === void 0 ? {} : { generation: generationToClose }
|
|
2104
|
+
};
|
|
1846
2105
|
}
|
|
2106
|
+
return {
|
|
2107
|
+
result: noVisibleFeishuReplyDelivery,
|
|
2108
|
+
...generationToClose === void 0 ? {} : { generation: generationToClose }
|
|
2109
|
+
};
|
|
2110
|
+
} catch (error) {
|
|
2111
|
+
return {
|
|
2112
|
+
result: noVisibleFeishuReplyDelivery,
|
|
2113
|
+
...generationToClose === void 0 ? {} : { generation: generationToClose },
|
|
2114
|
+
error
|
|
2115
|
+
};
|
|
1847
2116
|
} finally {
|
|
1848
|
-
resetStreamingState();
|
|
2117
|
+
if (streaming === streamingToClose) resetStreamingState();
|
|
2118
|
+
}
|
|
2119
|
+
};
|
|
2120
|
+
const closeStreaming = () => {
|
|
2121
|
+
const generation = activeStreamingGeneration;
|
|
2122
|
+
if (generation !== void 0 && inFlightStreamingClose?.generation === generation) return inFlightStreamingClose.promise;
|
|
2123
|
+
const content = streamText;
|
|
2124
|
+
const closePromise = performStreamingClose();
|
|
2125
|
+
if (generation !== void 0) {
|
|
2126
|
+
const closing = {
|
|
2127
|
+
generation,
|
|
2128
|
+
content,
|
|
2129
|
+
promise: closePromise
|
|
2130
|
+
};
|
|
2131
|
+
inFlightStreamingClose = closing;
|
|
2132
|
+
const clear = () => {
|
|
2133
|
+
if (inFlightStreamingClose === closing) inFlightStreamingClose = void 0;
|
|
2134
|
+
};
|
|
2135
|
+
closePromise.then(clear, clear);
|
|
1849
2136
|
}
|
|
2137
|
+
return closePromise;
|
|
2138
|
+
};
|
|
2139
|
+
const deferStreamingDelivery = (result, infoKind, ownerGeneration) => {
|
|
2140
|
+
let resolveFinalization;
|
|
2141
|
+
let rejectFinalization;
|
|
2142
|
+
const finalization = new Promise((resolve, reject) => {
|
|
2143
|
+
resolveFinalization = resolve;
|
|
2144
|
+
rejectFinalization = reject;
|
|
2145
|
+
});
|
|
2146
|
+
pendingStreamingDeliveries.push({
|
|
2147
|
+
result,
|
|
2148
|
+
...infoKind ? { infoKind } : {},
|
|
2149
|
+
...ownerGeneration === void 0 ? {} : { streamingGeneration: ownerGeneration },
|
|
2150
|
+
resolve: resolveFinalization,
|
|
2151
|
+
reject: rejectFinalization
|
|
2152
|
+
});
|
|
2153
|
+
if (idleRequestedForReply) queueIdleSideEffects().catch((error) => params.runtime.error?.(`feishu[${account.accountId}] late reply finalization failed: ${String(error)}`));
|
|
2154
|
+
return {
|
|
2155
|
+
...noVisibleFeishuReplyDelivery,
|
|
2156
|
+
finalization
|
|
2157
|
+
};
|
|
1850
2158
|
};
|
|
1851
2159
|
const discardStreamingPreview = async () => {
|
|
1852
2160
|
try {
|
|
@@ -1874,82 +2182,98 @@ function createFeishuReplyDispatcher(params) {
|
|
|
1874
2182
|
chunkMentions: paramsLocal.chunkMentions,
|
|
1875
2183
|
initialChunks
|
|
1876
2184
|
}));
|
|
2185
|
+
const results = [];
|
|
2186
|
+
const acceptedChunks = [];
|
|
1877
2187
|
for (const [index, chunk] of chunks.entries()) {
|
|
1878
2188
|
const mentions = [...paramsLocal.chunkMentions ?? [], ...index === 0 ? paramsLocal.firstChunkMentions ?? [] : []];
|
|
1879
|
-
|
|
1880
|
-
|
|
1881
|
-
|
|
1882
|
-
|
|
1883
|
-
|
|
1884
|
-
|
|
2189
|
+
try {
|
|
2190
|
+
const result = await paramsLocal.sendChunk({
|
|
2191
|
+
chunk,
|
|
2192
|
+
isFirst: index === 0,
|
|
2193
|
+
mentions: mentions.length > 0 ? mentions : void 0
|
|
2194
|
+
});
|
|
2195
|
+
results.push(result);
|
|
2196
|
+
acceptedChunks.push(chunk);
|
|
2197
|
+
markVisibleReplySent();
|
|
2198
|
+
} catch (error) {
|
|
2199
|
+
throw createFeishuPartialReplyDeliveryError(error, createFeishuReplyDeliveryResult({
|
|
2200
|
+
results,
|
|
2201
|
+
visibleReplySent: results.length > 0,
|
|
2202
|
+
content: acceptedChunks.join(""),
|
|
2203
|
+
kind: paramsLocal.useCard ? "card" : "text"
|
|
2204
|
+
}));
|
|
2205
|
+
}
|
|
1885
2206
|
}
|
|
1886
2207
|
if (paramsLocal.infoKind === "final") deliveredFinalTexts.add(paramsLocal.text);
|
|
2208
|
+
return createFeishuReplyDeliveryResult({
|
|
2209
|
+
results,
|
|
2210
|
+
visibleReplySent: results.length > 0,
|
|
2211
|
+
content: paramsLocal.text,
|
|
2212
|
+
kind: paramsLocal.useCard ? "card" : "text"
|
|
2213
|
+
});
|
|
1887
2214
|
};
|
|
1888
2215
|
const sendMediaReplies = async (payload, options) => {
|
|
1889
2216
|
const mediaUrls = resolveSendableOutboundReplyParts(payload).mediaUrls;
|
|
1890
2217
|
let sentFallbackText = false;
|
|
1891
|
-
|
|
1892
|
-
|
|
1893
|
-
|
|
1894
|
-
|
|
1895
|
-
|
|
1896
|
-
|
|
1897
|
-
|
|
1898
|
-
|
|
1899
|
-
|
|
1900
|
-
|
|
1901
|
-
|
|
1902
|
-
|
|
1903
|
-
|
|
1904
|
-
|
|
1905
|
-
|
|
1906
|
-
|
|
1907
|
-
|
|
1908
|
-
|
|
1909
|
-
|
|
1910
|
-
|
|
1911
|
-
|
|
1912
|
-
|
|
1913
|
-
|
|
1914
|
-
|
|
1915
|
-
|
|
1916
|
-
|
|
1917
|
-
|
|
1918
|
-
|
|
1919
|
-
|
|
1920
|
-
|
|
1921
|
-
|
|
1922
|
-
|
|
1923
|
-
|
|
2218
|
+
let degradedVoiceFallbackText;
|
|
2219
|
+
const results = [];
|
|
2220
|
+
const sendFallbackText = async (text) => await sendChunkedTextReply({
|
|
2221
|
+
text,
|
|
2222
|
+
useCard: false,
|
|
2223
|
+
infoKind: "final",
|
|
2224
|
+
chunkMentions: requiredMentionTargets,
|
|
2225
|
+
sendChunk: async ({ chunk, mentions }) => await sendMessageFeishu({
|
|
2226
|
+
cfg,
|
|
2227
|
+
to: sendTarget,
|
|
2228
|
+
text: chunk,
|
|
2229
|
+
replyToMessageId: sendReplyToMessageId,
|
|
2230
|
+
replyInThread: effectiveReplyInThread,
|
|
2231
|
+
allowTopLevelReplyFallback,
|
|
2232
|
+
accountId,
|
|
2233
|
+
...mentions ? { mentions } : {}
|
|
2234
|
+
})
|
|
2235
|
+
});
|
|
2236
|
+
try {
|
|
2237
|
+
await sendMediaWithLeadingCaption({
|
|
2238
|
+
mediaUrls,
|
|
2239
|
+
caption: "",
|
|
2240
|
+
send: async ({ mediaUrl }) => {
|
|
2241
|
+
const result = await sendMediaFeishu({
|
|
2242
|
+
cfg,
|
|
2243
|
+
to: sendTarget,
|
|
2244
|
+
mediaUrl,
|
|
2245
|
+
replyToMessageId: sendReplyToMessageId,
|
|
2246
|
+
replyInThread: effectiveReplyInThread,
|
|
2247
|
+
allowTopLevelReplyFallback,
|
|
2248
|
+
accountId,
|
|
2249
|
+
...payload.audioAsVoice === true ? { audioAsVoice: true } : {}
|
|
2250
|
+
});
|
|
2251
|
+
results.push(createFeishuReplyDeliveryResult({
|
|
2252
|
+
results: [result],
|
|
2253
|
+
visibleReplySent: true,
|
|
2254
|
+
kind: result?.voiceIntentDegradedToFile ? "media" : void 0
|
|
2255
|
+
}));
|
|
2256
|
+
markVisibleReplySent();
|
|
2257
|
+
if (result?.voiceIntentDegradedToFile && options?.fallbackText && !sentFallbackText) degradedVoiceFallbackText = options.fallbackText;
|
|
2258
|
+
},
|
|
2259
|
+
onError: options?.fallbackText === void 0 ? void 0 : async ({ mediaUrl }) => {
|
|
2260
|
+
const fallbackText = await buildFeishuMediaFallbackText({
|
|
2261
|
+
text: sentFallbackText ? void 0 : options.fallbackText,
|
|
2262
|
+
mediaUrl
|
|
1924
2263
|
});
|
|
2264
|
+
sentFallbackText = true;
|
|
2265
|
+
results.push(await sendFallbackText(fallbackText));
|
|
1925
2266
|
}
|
|
1926
|
-
}
|
|
1927
|
-
|
|
1928
|
-
const fallbackText = await buildFeishuMediaFallbackText({
|
|
1929
|
-
text: sentFallbackText ? void 0 : options.fallbackText,
|
|
1930
|
-
mediaUrl
|
|
1931
|
-
});
|
|
2267
|
+
});
|
|
2268
|
+
if (degradedVoiceFallbackText && !sentFallbackText) {
|
|
1932
2269
|
sentFallbackText = true;
|
|
1933
|
-
await
|
|
1934
|
-
text: fallbackText,
|
|
1935
|
-
useCard: false,
|
|
1936
|
-
infoKind: "final",
|
|
1937
|
-
chunkMentions: requiredMentionTargets,
|
|
1938
|
-
sendChunk: async ({ chunk, mentions }) => {
|
|
1939
|
-
await sendMessageFeishu({
|
|
1940
|
-
cfg,
|
|
1941
|
-
to: sendTarget,
|
|
1942
|
-
text: chunk,
|
|
1943
|
-
replyToMessageId: sendReplyToMessageId,
|
|
1944
|
-
replyInThread: effectiveReplyInThread,
|
|
1945
|
-
allowTopLevelReplyFallback,
|
|
1946
|
-
accountId,
|
|
1947
|
-
...mentions ? { mentions } : {}
|
|
1948
|
-
});
|
|
1949
|
-
}
|
|
1950
|
-
});
|
|
2270
|
+
results.push(await sendFallbackText(degradedVoiceFallbackText));
|
|
1951
2271
|
}
|
|
1952
|
-
})
|
|
2272
|
+
} catch (error) {
|
|
2273
|
+
const partial = isChannelPartialDeliveryError(error) ? error.deliveryResult : void 0;
|
|
2274
|
+
throw createFeishuPartialReplyDeliveryError(error, mergeFeishuReplyDeliveryResults([...results, ...partial ? [partial] : []]));
|
|
2275
|
+
}
|
|
2276
|
+
return mergeFeishuReplyDeliveryResults(results);
|
|
1953
2277
|
};
|
|
1954
2278
|
const ensureNoVisibleReplyFallback = async (reason) => {
|
|
1955
2279
|
await idleSideEffectsPromise;
|
|
@@ -1972,13 +2296,130 @@ function createFeishuReplyDispatcher(params) {
|
|
|
1972
2296
|
params.runtime.error?.(`feishu[${account.accountId}]: sent no-visible-reply fallback (${reason})`);
|
|
1973
2297
|
return true;
|
|
1974
2298
|
};
|
|
1975
|
-
const
|
|
2299
|
+
const claimClosedStreamingResult = (generation, content) => {
|
|
2300
|
+
if (generation !== void 0) {
|
|
2301
|
+
const settlement = closedStreamingSettlements.get(generation);
|
|
2302
|
+
if (settlement) settlement.contentClaimed = true;
|
|
2303
|
+
return settlement;
|
|
2304
|
+
}
|
|
2305
|
+
let latestKey;
|
|
2306
|
+
for (const [key, settlement] of closedStreamingSettlements) if (settlement.contentClaimed !== true && (content === void 0 || settlement.content === content)) latestKey = key;
|
|
2307
|
+
if (latestKey === void 0) return;
|
|
2308
|
+
const result = closedStreamingSettlements.get(latestKey);
|
|
2309
|
+
if (result) result.contentClaimed = true;
|
|
2310
|
+
return result;
|
|
2311
|
+
};
|
|
2312
|
+
const markClosedStreamingContentClaimed = (generation) => {
|
|
2313
|
+
if (generation !== void 0) {
|
|
2314
|
+
const settlement = closedStreamingSettlements.get(generation);
|
|
2315
|
+
if (settlement) settlement.contentClaimed = true;
|
|
2316
|
+
}
|
|
2317
|
+
};
|
|
2318
|
+
const ensureVisibleStreamingDelivery = async (result, content, infoKind) => {
|
|
2319
|
+
if (result?.visibleReplySent === true || !content?.trim()) return result;
|
|
2320
|
+
const cardHeader = resolveCardHeader(agentId, identity);
|
|
2321
|
+
const cardNote = resolveCardNote(agentId, identity, prefixContext.prefixContext);
|
|
2322
|
+
return await sendChunkedTextReply({
|
|
2323
|
+
text: content,
|
|
2324
|
+
useCard: true,
|
|
2325
|
+
infoKind,
|
|
2326
|
+
chunkMentions: requiredMentionTargets,
|
|
2327
|
+
sendChunk: async ({ chunk, mentions }) => await sendStructuredCardFeishu({
|
|
2328
|
+
cfg,
|
|
2329
|
+
to: sendTarget,
|
|
2330
|
+
text: chunk,
|
|
2331
|
+
replyToMessageId: sendReplyToMessageId,
|
|
2332
|
+
replyInThread: effectiveReplyInThread,
|
|
2333
|
+
allowTopLevelReplyFallback,
|
|
2334
|
+
accountId,
|
|
2335
|
+
header: cardHeader,
|
|
2336
|
+
note: cardNote,
|
|
2337
|
+
...mentions ? { mentions } : {}
|
|
2338
|
+
})
|
|
2339
|
+
});
|
|
2340
|
+
};
|
|
2341
|
+
function queueIdleSideEffects() {
|
|
2342
|
+
idleRequestedForReply = true;
|
|
2343
|
+
if (activeIdleSideEffectsPromise) return activeIdleSideEffectsPromise;
|
|
1976
2344
|
const nextIdleSideEffects = idleSideEffectsPromise.then(async () => {
|
|
1977
|
-
|
|
1978
|
-
|
|
2345
|
+
try {
|
|
2346
|
+
do {
|
|
2347
|
+
const completions = pendingStreamingDeliveries.splice(0);
|
|
2348
|
+
const closeOutcome = await closeStreaming();
|
|
2349
|
+
const finalized = closeOutcome.result;
|
|
2350
|
+
const ownsCurrentClose = (completion) => closeOutcome.generation !== void 0 && completion.streamingGeneration === closeOutcome.generation;
|
|
2351
|
+
if (completions.some((completion) => ownsCurrentClose(completion))) markClosedStreamingContentClaimed(closeOutcome.generation);
|
|
2352
|
+
for (const completion of completions) {
|
|
2353
|
+
const claimedSettlement = ownsCurrentClose(completion) ? {
|
|
2354
|
+
result: finalized,
|
|
2355
|
+
...closeOutcome.error === void 0 ? {} : { error: closeOutcome.error }
|
|
2356
|
+
} : claimClosedStreamingResult(completion.streamingGeneration, completion.result.content);
|
|
2357
|
+
const deliveryError = claimedSettlement?.error;
|
|
2358
|
+
let providerFinalized = claimedSettlement?.result;
|
|
2359
|
+
try {
|
|
2360
|
+
providerFinalized = await ensureVisibleStreamingDelivery(providerFinalized, completion.result.content, completion.infoKind);
|
|
2361
|
+
} catch (fallbackError) {
|
|
2362
|
+
const fallbackPartial = isChannelPartialDeliveryError(fallbackError) ? fallbackError.deliveryResult : void 0;
|
|
2363
|
+
const fallbackCause = fallbackPartial && fallbackError instanceof Error ? fallbackError.cause ?? fallbackError : fallbackError;
|
|
2364
|
+
completion.reject(createFeishuPartialReplyDeliveryError(deliveryError === void 0 ? fallbackCause : new AggregateError([deliveryError, fallbackCause], "Feishu streaming finalization and static fallback failed"), mergeFeishuReplyDeliveryResults([
|
|
2365
|
+
...providerFinalized ? [providerFinalized] : [],
|
|
2366
|
+
...fallbackPartial ? [fallbackPartial] : [],
|
|
2367
|
+
completion.result
|
|
2368
|
+
], fallbackPartial?.content ?? providerFinalized?.content ?? completion.result.content)));
|
|
2369
|
+
continue;
|
|
2370
|
+
}
|
|
2371
|
+
const settledResult = mergeFeishuReplyDeliveryResults([...providerFinalized ? [providerFinalized] : [], completion.result], deliveryError === void 0 ? completion.result.content ?? providerFinalized?.content : providerFinalized?.content ?? completion.result.content);
|
|
2372
|
+
if (deliveryError !== void 0) completion.reject(createFeishuPartialReplyDeliveryError(isChannelPartialDeliveryError(deliveryError) && deliveryError instanceof Error ? deliveryError.cause ?? deliveryError : deliveryError instanceof FeishuStreamingFinalizationError ? deliveryError.cause ?? deliveryError : deliveryError, settledResult));
|
|
2373
|
+
else completion.resolve(settledResult);
|
|
2374
|
+
}
|
|
2375
|
+
if (closeOutcome.error !== void 0) throw toError(closeOutcome.error);
|
|
2376
|
+
} while (pendingStreamingDeliveries.length > 0);
|
|
2377
|
+
} finally {
|
|
2378
|
+
typingCallbacks?.onIdle?.();
|
|
2379
|
+
}
|
|
1979
2380
|
});
|
|
2381
|
+
activeIdleSideEffectsPromise = nextIdleSideEffects;
|
|
1980
2382
|
idleSideEffectsPromise = nextIdleSideEffects.catch(() => {});
|
|
2383
|
+
const finishIdleSideEffects = () => {
|
|
2384
|
+
if (activeIdleSideEffectsPromise === nextIdleSideEffects) activeIdleSideEffectsPromise = null;
|
|
2385
|
+
if (pendingStreamingDeliveries.length > 0) queueIdleSideEffects().catch((error) => params.runtime.error?.(`feishu[${account.accountId}] queued reply finalization failed: ${String(error)}`));
|
|
2386
|
+
};
|
|
2387
|
+
nextIdleSideEffects.then(finishIdleSideEffects, finishIdleSideEffects);
|
|
1981
2388
|
return nextIdleSideEffects;
|
|
2389
|
+
}
|
|
2390
|
+
const throwStreamingDeliveryFailure = async (paramsLocal) => {
|
|
2391
|
+
let finalized = noVisibleFeishuReplyDelivery;
|
|
2392
|
+
let finalizationError;
|
|
2393
|
+
let fallbackPartial;
|
|
2394
|
+
const claimedSettlement = claimClosedStreamingResult(paramsLocal.ownerGeneration, paramsLocal.content);
|
|
2395
|
+
if (claimedSettlement) {
|
|
2396
|
+
finalized = claimedSettlement.result;
|
|
2397
|
+
finalizationError = claimedSettlement.error;
|
|
2398
|
+
} else if (paramsLocal.ownerGeneration !== void 0 && inFlightStreamingClose?.generation === paramsLocal.ownerGeneration) {
|
|
2399
|
+
const closeOutcome = await inFlightStreamingClose.promise;
|
|
2400
|
+
const completedSettlement = claimClosedStreamingResult(paramsLocal.ownerGeneration, paramsLocal.content);
|
|
2401
|
+
finalized = completedSettlement?.result ?? closeOutcome?.result ?? finalized;
|
|
2402
|
+
finalizationError = completedSettlement?.error ?? closeOutcome?.error;
|
|
2403
|
+
} else if (paramsLocal.ownerGeneration !== void 0 && activeStreamingGeneration === paramsLocal.ownerGeneration) {
|
|
2404
|
+
const closeOutcome = await closeStreaming();
|
|
2405
|
+
finalized = closeOutcome.result;
|
|
2406
|
+
finalizationError = closeOutcome.error;
|
|
2407
|
+
}
|
|
2408
|
+
try {
|
|
2409
|
+
finalized = await ensureVisibleStreamingDelivery(finalized, paramsLocal.content, paramsLocal.infoKind) ?? finalized;
|
|
2410
|
+
} catch (fallbackError) {
|
|
2411
|
+
fallbackPartial = isChannelPartialDeliveryError(fallbackError) ? fallbackError.deliveryResult : void 0;
|
|
2412
|
+
const fallbackCause = fallbackPartial && fallbackError instanceof Error ? fallbackError.cause ?? fallbackError : fallbackError;
|
|
2413
|
+
finalizationError = finalizationError ? new AggregateError([finalizationError, fallbackCause], "Feishu streaming finalization and static fallback failed") : fallbackCause;
|
|
2414
|
+
}
|
|
2415
|
+
const mediaPartial = isChannelPartialDeliveryError(paramsLocal.error) ? paramsLocal.error.deliveryResult : void 0;
|
|
2416
|
+
const accepted = mergeFeishuReplyDeliveryResults([
|
|
2417
|
+
finalized,
|
|
2418
|
+
...fallbackPartial ? [fallbackPartial] : [],
|
|
2419
|
+
...mediaPartial ? [mediaPartial] : []
|
|
2420
|
+
], fallbackPartial?.visibleReplySent === true ? fallbackPartial.content : finalized.visibleReplySent === true ? finalized.content : paramsLocal.content);
|
|
2421
|
+
const mediaCause = mediaPartial && paramsLocal.error instanceof Error ? paramsLocal.error.cause ?? paramsLocal.error : paramsLocal.error;
|
|
2422
|
+
throw createFeishuPartialReplyDeliveryError(finalizationError ? new AggregateError([mediaCause, finalizationError instanceof Error ? finalizationError.cause ?? finalizationError : finalizationError], "Feishu reply delivery and streaming finalization failed") : mediaCause, accepted);
|
|
1982
2423
|
};
|
|
1983
2424
|
const dispatcherOptions = {
|
|
1984
2425
|
responsePrefix: prefixContext.responsePrefix,
|
|
@@ -1997,13 +2438,13 @@ function createFeishuReplyDispatcher(params) {
|
|
|
1997
2438
|
if (!replyLifecycleStateInitialized) {
|
|
1998
2439
|
replyLifecycleStateInitialized = true;
|
|
1999
2440
|
deliveredFinalTexts.clear();
|
|
2441
|
+
closedStreamingSettlements.clear();
|
|
2000
2442
|
sentIndependentBlockText = false;
|
|
2001
|
-
|
|
2002
|
-
streamingCloseErroredForReply = false;
|
|
2443
|
+
idleRequestedForReply = false;
|
|
2003
2444
|
visibleReplySent = false;
|
|
2004
2445
|
skippedFinalReason = null;
|
|
2005
2446
|
}
|
|
2006
|
-
if (
|
|
2447
|
+
if (previewStreamingEnabled && renderMode === "card") startStreaming();
|
|
2007
2448
|
await Promise.resolve(typingCallbacks?.onReplyStart?.());
|
|
2008
2449
|
},
|
|
2009
2450
|
onIdle: () => queueIdleSideEffects(),
|
|
@@ -2012,14 +2453,13 @@ function createFeishuReplyDispatcher(params) {
|
|
|
2012
2453
|
}
|
|
2013
2454
|
};
|
|
2014
2455
|
const handleDeliveryError = async (error, info) => {
|
|
2015
|
-
streamingCloseErroredForReply = true;
|
|
2016
|
-
streamingClosedForReply = false;
|
|
2017
2456
|
params.runtime.error?.(`feishu[${account.accountId}] ${info.kind} reply failed: ${String(error)}`);
|
|
2018
|
-
await queueIdleSideEffects(
|
|
2457
|
+
await queueIdleSideEffects().catch((cleanupError) => params.runtime.error?.(`feishu[${account.accountId}] reply error cleanup failed: ${String(cleanupError)}`));
|
|
2019
2458
|
};
|
|
2020
2459
|
return {
|
|
2021
2460
|
dispatcherOptions,
|
|
2022
2461
|
delivery: {
|
|
2462
|
+
observeMessageSent: true,
|
|
2023
2463
|
deliver: async (payload, info) => {
|
|
2024
2464
|
if (info?.kind === "final") skippedFinalReason = null;
|
|
2025
2465
|
const payloadText = payload.isReasoning && payload.text ? formatReasoningMessage(payload.text) : payload.text;
|
|
@@ -2040,42 +2480,52 @@ function createFeishuReplyDispatcher(params) {
|
|
|
2040
2480
|
const finalTextExceedsStreamingLimit = info?.kind === "final" && hasText && text.length > textChunkLimit;
|
|
2041
2481
|
const useStaticCard = hasText && (renderMode === "card" || info?.kind === "block" && coreBlockStreamingEnabled && renderMode !== "raw" || renderMode === "auto" && shouldUseCard(text));
|
|
2042
2482
|
const useStreamingCard = hasText && streamingEnabled && !finalTextExceedsStreamingLimit && (info?.kind === "final" || useStaticCard);
|
|
2043
|
-
const finalTextWouldUseStreamingCard = info?.kind === "final" && hasText && streamingEnabled;
|
|
2044
2483
|
const useCard = useStaticCard || useStreamingCard;
|
|
2045
2484
|
const skipTextForDuplicateFinal = info?.kind === "final" && hasText && deliveredFinalTexts.has(text);
|
|
2046
|
-
const
|
|
2047
|
-
const shouldDeliverText = hasText && !hasVoiceMedia && !skipTextForDuplicateFinal && !skipTextForClosedStreamingFinal;
|
|
2485
|
+
const shouldDeliverText = hasText && !hasVoiceMedia && !skipTextForDuplicateFinal;
|
|
2048
2486
|
const shouldDiscardStreamingPreview = info?.kind === "final" && (finalTextExceedsStreamingLimit || hasMedia && (hasVoiceMedia && !shouldDeliverText && !ttsTextAlreadyVisible || skipTextForDuplicateFinal));
|
|
2049
|
-
|
|
2487
|
+
const priorClosedStreamingSettlement = info?.kind === "final" && hasText && skipTextForDuplicateFinal ? claimClosedStreamingResult(void 0, text) : void 0;
|
|
2488
|
+
if (!shouldDeliverText && !hasMedia) {
|
|
2489
|
+
if (priorClosedStreamingSettlement?.error !== void 0) throw toError(priorClosedStreamingSettlement.error);
|
|
2490
|
+
return priorClosedStreamingSettlement?.result ?? noVisibleFeishuReplyDelivery;
|
|
2491
|
+
}
|
|
2492
|
+
const deliveredResults = priorClosedStreamingSettlement ? [priorClosedStreamingSettlement.result] : [];
|
|
2493
|
+
const collectMediaDelivery = async (mediaPayload, mediaOptions) => {
|
|
2494
|
+
try {
|
|
2495
|
+
deliveredResults.push(await sendMediaReplies(mediaPayload, mediaOptions));
|
|
2496
|
+
} catch (error) {
|
|
2497
|
+
const partial = isChannelPartialDeliveryError(error) ? error.deliveryResult : void 0;
|
|
2498
|
+
const accumulated = mergeFeishuReplyDeliveryResults([...deliveredResults, ...partial ? [partial] : []]);
|
|
2499
|
+
throw createFeishuPartialReplyDeliveryError(partial && error instanceof Error ? error.cause ?? error : error, accumulated);
|
|
2500
|
+
}
|
|
2501
|
+
};
|
|
2050
2502
|
if (shouldDiscardStreamingPreview) await discardStreamingPreview();
|
|
2051
2503
|
if (shouldDeliverText) {
|
|
2052
2504
|
if (info?.kind === "block") {
|
|
2053
2505
|
if (!useStreamingCard) {
|
|
2054
2506
|
if (coreBlockStreamingEnabled) {
|
|
2055
2507
|
const firstChunkMentions = !sentIndependentBlockText && mentionTargets?.length ? mentionTargets : void 0;
|
|
2056
|
-
await sendChunkedTextReply({
|
|
2508
|
+
deliveredResults.push(await sendChunkedTextReply({
|
|
2057
2509
|
text,
|
|
2058
2510
|
useCard: false,
|
|
2059
2511
|
infoKind: "block",
|
|
2060
2512
|
firstChunkMentions,
|
|
2061
2513
|
chunkMentions: requiredMentionTargets,
|
|
2062
|
-
sendChunk: async ({ chunk, mentions }) => {
|
|
2063
|
-
|
|
2064
|
-
|
|
2065
|
-
|
|
2066
|
-
|
|
2067
|
-
|
|
2068
|
-
|
|
2069
|
-
|
|
2070
|
-
|
|
2071
|
-
|
|
2072
|
-
|
|
2073
|
-
}
|
|
2074
|
-
});
|
|
2514
|
+
sendChunk: async ({ chunk, mentions }) => await sendMessageFeishu({
|
|
2515
|
+
cfg,
|
|
2516
|
+
to: sendTarget,
|
|
2517
|
+
text: chunk,
|
|
2518
|
+
replyToMessageId: sendReplyToMessageId,
|
|
2519
|
+
replyInThread: effectiveReplyInThread,
|
|
2520
|
+
allowTopLevelReplyFallback,
|
|
2521
|
+
accountId,
|
|
2522
|
+
...mentions ? { mentions } : {}
|
|
2523
|
+
})
|
|
2524
|
+
}));
|
|
2075
2525
|
sentIndependentBlockText = true;
|
|
2076
|
-
if (hasMedia) await
|
|
2526
|
+
if (hasMedia) await collectMediaDelivery(payload);
|
|
2077
2527
|
}
|
|
2078
|
-
return;
|
|
2528
|
+
return mergeFeishuReplyDeliveryResults(deliveredResults, text);
|
|
2079
2529
|
}
|
|
2080
2530
|
startStreaming();
|
|
2081
2531
|
if (streamingStartPromise) await streamingStartPromise;
|
|
@@ -2085,75 +2535,87 @@ function createFeishuReplyDispatcher(params) {
|
|
|
2085
2535
|
if (streamingStartPromise) await streamingStartPromise;
|
|
2086
2536
|
}
|
|
2087
2537
|
const shouldStreamText = info?.kind === "block" || info?.kind === "final";
|
|
2088
|
-
|
|
2089
|
-
|
|
2090
|
-
|
|
2091
|
-
|
|
2092
|
-
|
|
2093
|
-
|
|
2094
|
-
|
|
2095
|
-
|
|
2096
|
-
|
|
2097
|
-
|
|
2098
|
-
|
|
2538
|
+
const matchingInFlightClose = info?.kind === "final" && inFlightStreamingClose?.content === text ? inFlightStreamingClose : void 0;
|
|
2539
|
+
const ownerGeneration = activeStreamingGeneration ?? matchingInFlightClose?.generation;
|
|
2540
|
+
if (shouldStreamText && ownerGeneration !== void 0 && (streaming?.isActive() || matchingInFlightClose !== void 0)) {
|
|
2541
|
+
if (activeStreamingGeneration !== void 0) {
|
|
2542
|
+
if (info?.kind === "block") queueStreamingUpdate(text, {
|
|
2543
|
+
mode: "delta",
|
|
2544
|
+
dedupeWithLastPartial: true
|
|
2545
|
+
});
|
|
2546
|
+
if (info?.kind === "final") {
|
|
2547
|
+
streamText = text;
|
|
2548
|
+
hasStreamingFinalText = true;
|
|
2549
|
+
snapshotBaseText = "";
|
|
2550
|
+
lastSnapshotTextLength = text.length;
|
|
2551
|
+
flushStreamingCardUpdate(buildCombinedStreamText(reasoningText, streamText));
|
|
2552
|
+
}
|
|
2099
2553
|
}
|
|
2100
|
-
if (hasMedia)
|
|
2101
|
-
|
|
2554
|
+
if (hasMedia) try {
|
|
2555
|
+
await collectMediaDelivery(payload);
|
|
2556
|
+
} catch (error) {
|
|
2557
|
+
await throwStreamingDeliveryFailure({
|
|
2558
|
+
error,
|
|
2559
|
+
content: text,
|
|
2560
|
+
infoKind: info?.kind,
|
|
2561
|
+
ownerGeneration
|
|
2562
|
+
});
|
|
2563
|
+
}
|
|
2564
|
+
return deferStreamingDelivery(mergeFeishuReplyDeliveryResults(deliveredResults, text), info?.kind, ownerGeneration);
|
|
2102
2565
|
}
|
|
2103
2566
|
if (useCard) {
|
|
2104
2567
|
const cardHeader = resolveCardHeader(agentId, identity);
|
|
2105
2568
|
const cardNote = resolveCardNote(agentId, identity, prefixContext.prefixContext);
|
|
2106
|
-
await sendChunkedTextReply({
|
|
2569
|
+
deliveredResults.push(await sendChunkedTextReply({
|
|
2107
2570
|
text,
|
|
2108
2571
|
useCard: true,
|
|
2109
2572
|
infoKind: info?.kind,
|
|
2110
2573
|
chunkMentions: requiredMentionTargets,
|
|
2111
|
-
sendChunk: async ({ chunk, mentions }) => {
|
|
2112
|
-
|
|
2113
|
-
|
|
2114
|
-
|
|
2115
|
-
|
|
2116
|
-
|
|
2117
|
-
|
|
2118
|
-
|
|
2119
|
-
|
|
2120
|
-
|
|
2121
|
-
|
|
2122
|
-
|
|
2123
|
-
|
|
2124
|
-
}
|
|
2125
|
-
});
|
|
2574
|
+
sendChunk: async ({ chunk, mentions }) => await sendStructuredCardFeishu({
|
|
2575
|
+
cfg,
|
|
2576
|
+
to: sendTarget,
|
|
2577
|
+
text: chunk,
|
|
2578
|
+
replyToMessageId: sendReplyToMessageId,
|
|
2579
|
+
replyInThread: effectiveReplyInThread,
|
|
2580
|
+
allowTopLevelReplyFallback,
|
|
2581
|
+
accountId,
|
|
2582
|
+
header: cardHeader,
|
|
2583
|
+
note: cardNote,
|
|
2584
|
+
...mentions ? { mentions } : {}
|
|
2585
|
+
})
|
|
2586
|
+
}));
|
|
2126
2587
|
} else {
|
|
2127
2588
|
const firstChunkMentions = info?.kind === "final" && mentionTargets?.length ? mentionTargets : void 0;
|
|
2128
|
-
await sendChunkedTextReply({
|
|
2589
|
+
deliveredResults.push(await sendChunkedTextReply({
|
|
2129
2590
|
text,
|
|
2130
2591
|
useCard: false,
|
|
2131
2592
|
infoKind: info?.kind,
|
|
2132
2593
|
firstChunkMentions,
|
|
2133
2594
|
chunkMentions: requiredMentionTargets,
|
|
2134
|
-
sendChunk: async ({ chunk, mentions }) => {
|
|
2135
|
-
|
|
2136
|
-
|
|
2137
|
-
|
|
2138
|
-
|
|
2139
|
-
|
|
2140
|
-
|
|
2141
|
-
|
|
2142
|
-
|
|
2143
|
-
|
|
2144
|
-
|
|
2145
|
-
}
|
|
2146
|
-
});
|
|
2595
|
+
sendChunk: async ({ chunk, mentions }) => await sendMessageFeishu({
|
|
2596
|
+
cfg,
|
|
2597
|
+
to: sendTarget,
|
|
2598
|
+
text: chunk,
|
|
2599
|
+
replyToMessageId: sendReplyToMessageId,
|
|
2600
|
+
replyInThread: effectiveReplyInThread,
|
|
2601
|
+
allowTopLevelReplyFallback,
|
|
2602
|
+
accountId,
|
|
2603
|
+
...mentions ? { mentions } : {}
|
|
2604
|
+
})
|
|
2605
|
+
}));
|
|
2147
2606
|
}
|
|
2148
2607
|
}
|
|
2149
|
-
if (hasMedia) await
|
|
2608
|
+
if (hasMedia) await collectMediaDelivery(payload, hasVoiceMedia && hasText ? { fallbackText: text } : void 0);
|
|
2609
|
+
const result = mergeFeishuReplyDeliveryResults(deliveredResults, text);
|
|
2610
|
+
if (priorClosedStreamingSettlement?.error !== void 0) throw createFeishuPartialReplyDeliveryError(isChannelPartialDeliveryError(priorClosedStreamingSettlement.error) && priorClosedStreamingSettlement.error instanceof Error ? priorClosedStreamingSettlement.error.cause ?? priorClosedStreamingSettlement.error : priorClosedStreamingSettlement.error, result);
|
|
2611
|
+
return result;
|
|
2150
2612
|
},
|
|
2151
2613
|
onError: handleDeliveryError
|
|
2152
2614
|
},
|
|
2153
2615
|
replyOptions: {
|
|
2154
2616
|
onModelSelected: prefixContext.onModelSelected,
|
|
2155
2617
|
disableBlockStreaming: typeof blockStreamingEnabled === "boolean" ? !blockStreamingEnabled : true,
|
|
2156
|
-
onPartialReply:
|
|
2618
|
+
onPartialReply: previewStreamingEnabled ? (payload) => {
|
|
2157
2619
|
if (!payload.text) return;
|
|
2158
2620
|
const cleaned = stripReasoningTagsFromText(payload.text, {
|
|
2159
2621
|
mode: "strict",
|
|
@@ -2172,7 +2634,7 @@ function createFeishuReplyDispatcher(params) {
|
|
|
2172
2634
|
queueReasoningUpdate(formatReasoningMessage(payload.text));
|
|
2173
2635
|
} : void 0,
|
|
2174
2636
|
onReasoningEnd: reasoningPreviewEnabled ? () => {} : void 0,
|
|
2175
|
-
onToolStart:
|
|
2637
|
+
onToolStart: previewStreamingEnabled ? (payload) => {
|
|
2176
2638
|
if (!isChannelProgressDraftWorkToolName(payload.name)) return;
|
|
2177
2639
|
const statusLineLocal = formatChannelProgressDraftLineForEntry(account.config, {
|
|
2178
2640
|
event: "tool",
|
|
@@ -2182,13 +2644,13 @@ function createFeishuReplyDispatcher(params) {
|
|
|
2182
2644
|
}, { detailMode: payload.detailMode });
|
|
2183
2645
|
if (statusLineLocal) updateStreamingStatusLine(statusLineLocal);
|
|
2184
2646
|
} : void 0,
|
|
2185
|
-
onAssistantMessageStart:
|
|
2647
|
+
onAssistantMessageStart: previewStreamingEnabled ? () => {
|
|
2186
2648
|
updateStreamingStatusLine("", { startIfNeeded: false });
|
|
2187
2649
|
} : void 0,
|
|
2188
|
-
onCompactionStart:
|
|
2650
|
+
onCompactionStart: previewStreamingEnabled ? () => {
|
|
2189
2651
|
updateStreamingStatusLine("📦 **Compacting context...**");
|
|
2190
2652
|
} : void 0,
|
|
2191
|
-
onCompactionEnd:
|
|
2653
|
+
onCompactionEnd: previewStreamingEnabled ? () => {
|
|
2192
2654
|
updateStreamingStatusLine("");
|
|
2193
2655
|
} : void 0
|
|
2194
2656
|
},
|
|
@@ -2225,15 +2687,14 @@ function isFeishuTopicSessionScope(scope) {
|
|
|
2225
2687
|
return scope === "group_topic" || scope === "group_topic_sender";
|
|
2226
2688
|
}
|
|
2227
2689
|
async function resolveFeishuAudioPreflightTranscript(params) {
|
|
2228
|
-
if (params.
|
|
2229
|
-
const audioMedia = params.mediaList.filter((media) => media.contentType?.startsWith("audio/"));
|
|
2690
|
+
if (params.messageType !== "audio" || params.content.trim()) return;
|
|
2691
|
+
const audioMedia = params.mediaList.filter((media) => Boolean(media.path) && (media.kind === "audio" || media.contentType?.startsWith("audio/")));
|
|
2230
2692
|
if (audioMedia.length === 0) return;
|
|
2231
2693
|
try {
|
|
2232
2694
|
const { transcribeFirstAudio } = await import("./audio-preflight.runtime-BBfC9elv.js");
|
|
2233
2695
|
return await transcribeFirstAudio({
|
|
2234
2696
|
ctx: {
|
|
2235
|
-
|
|
2236
|
-
MediaTypes: audioMedia.map((media) => media.contentType).filter(Boolean),
|
|
2697
|
+
media: audioMedia,
|
|
2237
2698
|
ChatType: params.chatType
|
|
2238
2699
|
},
|
|
2239
2700
|
cfg: params.cfg
|
|
@@ -2244,8 +2705,7 @@ async function resolveFeishuAudioPreflightTranscript(params) {
|
|
|
2244
2705
|
}
|
|
2245
2706
|
}
|
|
2246
2707
|
/**
|
|
2247
|
-
*
|
|
2248
|
-
* Similar to Discord's buildDiscordMediaPayload().
|
|
2708
|
+
* Parse an inbound Feishu event into its caption and routing metadata.
|
|
2249
2709
|
*/
|
|
2250
2710
|
function parseFeishuMessageEvent(event, botOpenId, _botName) {
|
|
2251
2711
|
const rawContent = parseMessageContent(event.message.content, event.message.message_type);
|
|
@@ -2313,7 +2773,7 @@ async function filterFetchedGroupContextMessages(messages, params) {
|
|
|
2313
2773
|
}) ? message : void 0))).filter((message) => message !== void 0);
|
|
2314
2774
|
}
|
|
2315
2775
|
async function handleFeishuMessage(params) {
|
|
2316
|
-
const { cfg, event, botOpenId, botName, runtime, channelRuntime, chatHistories, accountId, processingClaim, messageDedupeKey: messageDedupeKeyOverride } = params;
|
|
2776
|
+
const { cfg, event, botOpenId, botName, runtime, channelRuntime, chatHistories, accountId, processingClaim, messageDedupeKey: messageDedupeKeyOverride, turnAdoptionLifecycle } = params;
|
|
2317
2777
|
const account = resolveFeishuRuntimeAccount({
|
|
2318
2778
|
cfg,
|
|
2319
2779
|
accountId
|
|
@@ -2323,7 +2783,7 @@ async function handleFeishuMessage(params) {
|
|
|
2323
2783
|
const error = runtime?.error ?? console.error;
|
|
2324
2784
|
const messageId = event.message.message_id;
|
|
2325
2785
|
const messageDedupeKey = messageDedupeKeyOverride ?? resolveFeishuMessageDedupeKey(event);
|
|
2326
|
-
if (!await finalizeFeishuMessageProcessing({
|
|
2786
|
+
if (!turnAdoptionLifecycle && !await finalizeFeishuMessageProcessing({
|
|
2327
2787
|
messageId: messageDedupeKey,
|
|
2328
2788
|
namespace: account.accountId,
|
|
2329
2789
|
log,
|
|
@@ -2787,7 +3247,7 @@ async function handleFeishuMessage(params) {
|
|
|
2787
3247
|
});
|
|
2788
3248
|
log(`feishu[${account.accountId}]: ${inboundLabel}: ${preview}`);
|
|
2789
3249
|
const mediaMaxBytes = (feishuCfg?.mediaMaxMb ?? 30) * 1024 * 1024;
|
|
2790
|
-
const
|
|
3250
|
+
const mediaList = await resolveFeishuMediaList({
|
|
2791
3251
|
cfg,
|
|
2792
3252
|
messageId: ctx.messageId,
|
|
2793
3253
|
messageType: event.message.message_type,
|
|
@@ -2796,12 +3256,10 @@ async function handleFeishuMessage(params) {
|
|
|
2796
3256
|
log,
|
|
2797
3257
|
accountId: account.accountId
|
|
2798
3258
|
});
|
|
2799
|
-
const
|
|
2800
|
-
const
|
|
2801
|
-
|
|
2802
|
-
|
|
2803
|
-
mediaPlaceholder: mediaFailurePresentation.mediaPlaceholder,
|
|
2804
|
-
notice: `[feishu ${mediaResolution.unavailableCount > 1 ? `${mediaResolution.unavailableCount} attachments` : "attachment"} unavailable]`
|
|
3259
|
+
const unavailableMediaCount = mediaList.filter((media) => !media.path).length;
|
|
3260
|
+
const mediaFailureContent = unavailableMediaCount > 0 ? formatInboundMediaUnavailableText({
|
|
3261
|
+
body: ctx.content,
|
|
3262
|
+
notice: `[feishu ${unavailableMediaCount > 1 ? `${unavailableMediaCount} attachments` : "attachment"} unavailable]`
|
|
2805
3263
|
}) : ctx.content;
|
|
2806
3264
|
let quotedMessageInfo = null;
|
|
2807
3265
|
let quotedContent;
|
|
@@ -2836,10 +3294,11 @@ async function handleFeishuMessage(params) {
|
|
|
2836
3294
|
cfg: effectiveCfg,
|
|
2837
3295
|
mediaList,
|
|
2838
3296
|
content: ctx.content,
|
|
3297
|
+
messageType: event.message.message_type,
|
|
2839
3298
|
chatType: isGroup ? "group" : "direct",
|
|
2840
3299
|
log
|
|
2841
3300
|
});
|
|
2842
|
-
const preflightAudioIndex = audioTranscript === void 0 ? -1 : mediaList.findIndex((media) => media.contentType?.startsWith("audio/"));
|
|
3301
|
+
const preflightAudioIndex = audioTranscript === void 0 ? -1 : mediaList.findIndex((media) => media.kind === "audio" || media.contentType?.startsWith("audio/"));
|
|
2843
3302
|
const inboundMedia = toInboundMediaFacts(mediaList, { transcribed: (_media, index) => index === preflightAudioIndex });
|
|
2844
3303
|
const requiredMentionTargets = isGroup && ctx.senderType === "bot" && ctx.senderOpenId ? [{
|
|
2845
3304
|
openId: ctx.senderOpenId,
|
|
@@ -3074,6 +3533,7 @@ async function handleFeishuMessage(params) {
|
|
|
3074
3533
|
rawBody: commandFacingContent,
|
|
3075
3534
|
commandBody: commandFacingContent
|
|
3076
3535
|
},
|
|
3536
|
+
sessionTranscript: { historyLimit: isGroup ? historyLimit : 0 },
|
|
3077
3537
|
access: {
|
|
3078
3538
|
mentions: {
|
|
3079
3539
|
canDetectMention: isGroup,
|
|
@@ -3123,144 +3583,202 @@ async function handleFeishuMessage(params) {
|
|
|
3123
3583
|
};
|
|
3124
3584
|
};
|
|
3125
3585
|
if (broadcastAgents) {
|
|
3126
|
-
|
|
3586
|
+
const broadcastDedupeKey = messageDedupeKey ?? ctx.messageId;
|
|
3587
|
+
const broadcastClaim = await claimUnprocessedFeishuMessage({
|
|
3588
|
+
messageId: broadcastDedupeKey,
|
|
3589
|
+
namespace: "broadcast",
|
|
3590
|
+
log
|
|
3591
|
+
});
|
|
3592
|
+
if (broadcastClaim.kind === "duplicate" || broadcastClaim.kind === "inflight") {
|
|
3127
3593
|
log(`feishu[${account.accountId}]: broadcast already claimed by another account for message ${ctx.messageId}; skipping`);
|
|
3128
3594
|
return;
|
|
3129
3595
|
}
|
|
3130
|
-
const
|
|
3131
|
-
|
|
3132
|
-
|
|
3133
|
-
|
|
3596
|
+
const broadcastSettlement = createFeishuBroadcastIngressSettlement({
|
|
3597
|
+
lifecycle: turnAdoptionLifecycle,
|
|
3598
|
+
replayClaim: broadcastClaim.kind === "claimed" ? broadcastClaim.handle : void 0,
|
|
3599
|
+
onReplayCommitError: (err) => error(`feishu[${account.accountId}]: failed to commit broadcast replay guard: ${String(err)}`),
|
|
3600
|
+
onAdopted: () => {
|
|
3601
|
+
if (isGroup && historyKey && chatHistories) createChannelHistoryWindow({ historyMap: chatHistories }).clear({
|
|
3602
|
+
historyKey,
|
|
3603
|
+
limit: historyLimit
|
|
3604
|
+
});
|
|
3605
|
+
}
|
|
3606
|
+
});
|
|
3607
|
+
const abandonBroadcast = async (err) => {
|
|
3608
|
+
try {
|
|
3609
|
+
await broadcastSettlement.onDispatchFailed(err);
|
|
3610
|
+
} catch (abandonError) {
|
|
3611
|
+
error(`feishu[${account.accountId}]: failed to abandon broadcast ingress: ${String(abandonError)}`);
|
|
3612
|
+
}
|
|
3613
|
+
};
|
|
3614
|
+
const strategy = cfg.broadcast?.strategy === "sequential" ? "sequential" : "parallel";
|
|
3615
|
+
const activeAgentId = ctx.mentionedBot || !requireMention ? normalizeAgentId$2(route.agentId) : null;
|
|
3616
|
+
const agentIds = (cfg.agents?.list ?? []).map((a) => normalizeAgentId$2(a.id));
|
|
3617
|
+
const hasKnownAgents = agentIds.length > 0;
|
|
3134
3618
|
log(`feishu[${account.accountId}]: broadcasting to ${broadcastAgents.length} agents (strategy=${strategy}, active=${activeAgentId ?? "none"})`);
|
|
3619
|
+
const createBroadcastInboundAdapter = (paramsLocal) => ({
|
|
3620
|
+
ingest: () => ({
|
|
3621
|
+
id: ctx.messageId,
|
|
3622
|
+
timestamp: messageCreateTimeMs,
|
|
3623
|
+
rawText: ctx.content,
|
|
3624
|
+
textForAgent: paramsLocal.ctxPayload.BodyForAgent,
|
|
3625
|
+
textForCommands: paramsLocal.ctxPayload.CommandBody,
|
|
3626
|
+
raw: ctx
|
|
3627
|
+
}),
|
|
3628
|
+
resolveTurn: () => ({
|
|
3629
|
+
cfg,
|
|
3630
|
+
channel: "feishu",
|
|
3631
|
+
accountId: route.accountId,
|
|
3632
|
+
route: {
|
|
3633
|
+
agentId: paramsLocal.agentId,
|
|
3634
|
+
sessionKey: paramsLocal.sessionKey
|
|
3635
|
+
},
|
|
3636
|
+
ctxPayload: paramsLocal.ctxPayload,
|
|
3637
|
+
record: paramsLocal.record,
|
|
3638
|
+
...paramsLocal.variant.kind === "observeOnly" ? {
|
|
3639
|
+
admission: {
|
|
3640
|
+
kind: "observeOnly",
|
|
3641
|
+
reason: "broadcast-observer"
|
|
3642
|
+
},
|
|
3643
|
+
delivery: { deliver: async () => ({ visibleReplySent: false }) },
|
|
3644
|
+
replyOptions: bindIngressLifecycleToReplyOptions(paramsLocal.lifecycle)
|
|
3645
|
+
} : {
|
|
3646
|
+
dispatcherOptions: paramsLocal.variant.dispatcher.dispatcherOptions,
|
|
3647
|
+
delivery: paramsLocal.variant.dispatcher.delivery,
|
|
3648
|
+
replyOptions: {
|
|
3649
|
+
...paramsLocal.variant.dispatcher.replyOptions,
|
|
3650
|
+
...bindIngressLifecycleToReplyOptions(paramsLocal.lifecycle)
|
|
3651
|
+
}
|
|
3652
|
+
}
|
|
3653
|
+
})
|
|
3654
|
+
});
|
|
3135
3655
|
const dispatchForAgent = async (agentId) => {
|
|
3136
|
-
|
|
3656
|
+
const normalizedAgentId = normalizeAgentId$2(agentId);
|
|
3657
|
+
if (hasKnownAgents && !agentIds.includes(normalizedAgentId)) {
|
|
3137
3658
|
log(`feishu[${account.accountId}]: broadcast agent ${agentId} not found in agents.list; skipping`);
|
|
3138
3659
|
return;
|
|
3139
3660
|
}
|
|
3140
|
-
|
|
3141
|
-
|
|
3142
|
-
|
|
3143
|
-
|
|
3144
|
-
|
|
3145
|
-
|
|
3146
|
-
})
|
|
3147
|
-
|
|
3148
|
-
|
|
3661
|
+
let agentClaim;
|
|
3662
|
+
for (let attempt = 0; attempt < 2; attempt += 1) {
|
|
3663
|
+
agentClaim = await claimUnprocessedFeishuMessage({
|
|
3664
|
+
messageId: broadcastDedupeKey,
|
|
3665
|
+
namespace: `broadcast:${normalizedAgentId}`,
|
|
3666
|
+
log
|
|
3667
|
+
});
|
|
3668
|
+
if (agentClaim.kind === "duplicate") return;
|
|
3669
|
+
if (agentClaim.kind !== "inflight") break;
|
|
3670
|
+
broadcastSettlement.onLanePending();
|
|
3671
|
+
try {
|
|
3672
|
+
await agentClaim.pending;
|
|
3673
|
+
return;
|
|
3674
|
+
} catch (err) {
|
|
3675
|
+
if (attempt === 1) throw err;
|
|
3149
3676
|
}
|
|
3150
|
-
}
|
|
3151
|
-
const
|
|
3152
|
-
|
|
3153
|
-
agentId,
|
|
3154
|
-
|
|
3155
|
-
|
|
3156
|
-
|
|
3157
|
-
|
|
3158
|
-
|
|
3159
|
-
|
|
3160
|
-
|
|
3677
|
+
}
|
|
3678
|
+
const lane = broadcastSettlement.createLane(agentClaim?.kind === "claimed" ? agentClaim.handle : void 0);
|
|
3679
|
+
try {
|
|
3680
|
+
const agentSessionKey = buildBroadcastSessionKey(route.sessionKey, route.agentId, agentId);
|
|
3681
|
+
const agentStorePath = resolveStorePath(cfg.session?.store, { agentId });
|
|
3682
|
+
const agentRecord = {
|
|
3683
|
+
updateLastRoute: buildFeishuInboundLastRouteUpdate({
|
|
3684
|
+
sessionKey: agentSessionKey,
|
|
3685
|
+
accountId: route.accountId
|
|
3686
|
+
}),
|
|
3687
|
+
onRecordError: (err) => {
|
|
3688
|
+
log(`feishu[${account.accountId}]: failed to record broadcast inbound session ${agentSessionKey}: ${String(err)}`);
|
|
3689
|
+
}
|
|
3690
|
+
};
|
|
3691
|
+
const allowReasoningPreview = resolveFeishuReasoningPreviewEnabled({
|
|
3161
3692
|
cfg,
|
|
3162
3693
|
agentId,
|
|
3163
|
-
|
|
3164
|
-
chatId: ctx.chatId,
|
|
3165
|
-
sendTarget: feishuTo,
|
|
3166
|
-
allowReasoningPreview,
|
|
3167
|
-
replyToMessageId: replyTargetMessageId,
|
|
3168
|
-
typingTargetMessageId,
|
|
3169
|
-
skipReplyToInMessages: !isGroup && !directThreadReply,
|
|
3170
|
-
replyInThread,
|
|
3171
|
-
rootId: ctx.rootId,
|
|
3172
|
-
threadReply,
|
|
3173
|
-
accountId: account.accountId,
|
|
3174
|
-
identity,
|
|
3175
|
-
mentionTargets: ctx.mentionTargets,
|
|
3176
|
-
requiredMentionTargets,
|
|
3177
|
-
messageCreateTimeMs,
|
|
3694
|
+
storePath: agentStorePath,
|
|
3178
3695
|
sessionKey: agentSessionKey
|
|
3179
3696
|
});
|
|
3180
|
-
|
|
3181
|
-
|
|
3182
|
-
|
|
3183
|
-
|
|
3184
|
-
|
|
3185
|
-
|
|
3186
|
-
|
|
3187
|
-
id: ctx.messageId,
|
|
3188
|
-
timestamp: messageCreateTimeMs,
|
|
3189
|
-
rawText: ctx.content,
|
|
3190
|
-
textForAgent: agentCtx.BodyForAgent,
|
|
3191
|
-
textForCommands: agentCtx.CommandBody,
|
|
3192
|
-
raw: ctx
|
|
3193
|
-
}),
|
|
3194
|
-
resolveTurn: () => ({
|
|
3697
|
+
const agentCtx = await buildCtxPayloadForAgent(agentId, agentSessionKey, route.accountId, ctx.mentionedBot && agentId === activeAgentId);
|
|
3698
|
+
let variant;
|
|
3699
|
+
if (agentId === activeAgentId) {
|
|
3700
|
+
const identity = resolveAgentOutboundIdentity(cfg, agentId);
|
|
3701
|
+
variant = {
|
|
3702
|
+
kind: "active",
|
|
3703
|
+
dispatcher: createFeishuReplyDispatcher({
|
|
3195
3704
|
cfg,
|
|
3196
|
-
|
|
3197
|
-
|
|
3198
|
-
|
|
3199
|
-
|
|
3200
|
-
|
|
3201
|
-
|
|
3202
|
-
|
|
3203
|
-
|
|
3204
|
-
|
|
3205
|
-
|
|
3206
|
-
|
|
3705
|
+
agentId,
|
|
3706
|
+
runtime,
|
|
3707
|
+
chatId: ctx.chatId,
|
|
3708
|
+
sendTarget: feishuTo,
|
|
3709
|
+
allowReasoningPreview,
|
|
3710
|
+
replyToMessageId: replyTargetMessageId,
|
|
3711
|
+
typingTargetMessageId,
|
|
3712
|
+
skipReplyToInMessages: !isGroup && !directThreadReply,
|
|
3713
|
+
replyInThread,
|
|
3714
|
+
rootId: ctx.rootId,
|
|
3715
|
+
threadReply,
|
|
3716
|
+
accountId: account.accountId,
|
|
3717
|
+
identity,
|
|
3718
|
+
mentionTargets: ctx.mentionTargets,
|
|
3719
|
+
requiredMentionTargets,
|
|
3720
|
+
messageCreateTimeMs,
|
|
3721
|
+
sessionKey: agentSessionKey
|
|
3207
3722
|
})
|
|
3208
|
-
}
|
|
3209
|
-
|
|
3210
|
-
|
|
3211
|
-
|
|
3212
|
-
|
|
3213
|
-
|
|
3214
|
-
|
|
3723
|
+
};
|
|
3724
|
+
log(`feishu[${account.accountId}]: broadcast active dispatch agent=${agentId} (session=${agentSessionKey})`);
|
|
3725
|
+
} else {
|
|
3726
|
+
delete agentCtx.CommandAuthorized;
|
|
3727
|
+
variant = { kind: "observeOnly" };
|
|
3728
|
+
log(`feishu[${account.accountId}]: broadcast observer dispatch agent=${agentId} (session=${agentSessionKey})`);
|
|
3729
|
+
}
|
|
3730
|
+
const turnResult = await core.channel.inbound.run({
|
|
3215
3731
|
channel: "feishu",
|
|
3216
3732
|
accountId: route.accountId,
|
|
3217
3733
|
raw: ctx,
|
|
3218
|
-
adapter: {
|
|
3219
|
-
|
|
3220
|
-
|
|
3221
|
-
|
|
3222
|
-
|
|
3223
|
-
|
|
3224
|
-
|
|
3225
|
-
|
|
3226
|
-
}),
|
|
3227
|
-
resolveTurn: () => ({
|
|
3228
|
-
cfg,
|
|
3229
|
-
channel: "feishu",
|
|
3230
|
-
accountId: route.accountId,
|
|
3231
|
-
route: {
|
|
3232
|
-
agentId,
|
|
3233
|
-
sessionKey: agentSessionKey
|
|
3234
|
-
},
|
|
3235
|
-
ctxPayload: agentCtx,
|
|
3236
|
-
record: agentRecord,
|
|
3237
|
-
admission: {
|
|
3238
|
-
kind: "observeOnly",
|
|
3239
|
-
reason: "broadcast-observer"
|
|
3240
|
-
},
|
|
3241
|
-
delivery: { deliver: async () => ({ visibleReplySent: false }) }
|
|
3242
|
-
})
|
|
3243
|
-
}
|
|
3734
|
+
adapter: createBroadcastInboundAdapter({
|
|
3735
|
+
agentId,
|
|
3736
|
+
sessionKey: agentSessionKey,
|
|
3737
|
+
ctxPayload: agentCtx,
|
|
3738
|
+
record: agentRecord,
|
|
3739
|
+
lifecycle: lane.lifecycle,
|
|
3740
|
+
variant
|
|
3741
|
+
})
|
|
3244
3742
|
});
|
|
3743
|
+
if (variant.kind === "active" && turnResult.dispatched && shouldSendNoVisibleReplyFallback(turnResult.dispatchResult)) await variant.dispatcher.ensureNoVisibleReplyFallback("broadcast-dispatch-complete-no-visible-reply");
|
|
3744
|
+
await lane.onDispatchComplete(turnResult.dispatched);
|
|
3745
|
+
} catch (err) {
|
|
3746
|
+
await lane.onDispatchFailed(err);
|
|
3747
|
+
throw err;
|
|
3245
3748
|
}
|
|
3246
3749
|
};
|
|
3750
|
+
const results = [];
|
|
3247
3751
|
if (strategy === "sequential") for (const agentId of broadcastAgents) try {
|
|
3248
3752
|
await dispatchForAgent(agentId);
|
|
3249
|
-
|
|
3250
|
-
|
|
3753
|
+
results.push({
|
|
3754
|
+
status: "fulfilled",
|
|
3755
|
+
value: void 0
|
|
3756
|
+
});
|
|
3757
|
+
} catch (reason) {
|
|
3758
|
+
results.push({
|
|
3759
|
+
status: "rejected",
|
|
3760
|
+
reason
|
|
3761
|
+
});
|
|
3251
3762
|
}
|
|
3252
|
-
else
|
|
3253
|
-
|
|
3254
|
-
|
|
3255
|
-
|
|
3256
|
-
|
|
3257
|
-
|
|
3258
|
-
|
|
3763
|
+
else results.push(...await Promise.allSettled(broadcastAgents.map(dispatchForAgent)));
|
|
3764
|
+
const failures = [];
|
|
3765
|
+
for (const [i, result] of results.entries()) if (result.status === "rejected") {
|
|
3766
|
+
const agentId = broadcastAgents.at(i);
|
|
3767
|
+
if (agentId === void 0) continue;
|
|
3768
|
+
log(`feishu[${account.accountId}]: broadcast dispatch failed for agent=${agentId}: ${String(result.reason)}`);
|
|
3769
|
+
failures.push(result.reason);
|
|
3770
|
+
}
|
|
3771
|
+
if (failures.length > 0) {
|
|
3772
|
+
const failure = failures.length === 1 ? failures[0] : new AggregateError(failures, "Feishu broadcast dispatch failed");
|
|
3773
|
+
await abandonBroadcast(failure);
|
|
3774
|
+
throw failure;
|
|
3775
|
+
}
|
|
3776
|
+
try {
|
|
3777
|
+
await broadcastSettlement.onDispatchComplete();
|
|
3778
|
+
} catch (err) {
|
|
3779
|
+
await abandonBroadcast(err);
|
|
3780
|
+
throw err;
|
|
3259
3781
|
}
|
|
3260
|
-
if (isGroup && historyKey && chatHistories) createChannelHistoryWindow({ historyMap: chatHistories }).clear({
|
|
3261
|
-
historyKey,
|
|
3262
|
-
limit: historyLimit
|
|
3263
|
-
});
|
|
3264
3782
|
log(`feishu[${account.accountId}]: broadcast dispatch complete for ${broadcastAgents.length} agents`);
|
|
3265
3783
|
} else {
|
|
3266
3784
|
const ctxPayload = await buildCtxPayloadForAgent(route.agentId, route.sessionKey, route.accountId, ctx.mentionedBot);
|
|
@@ -3332,7 +3850,10 @@ async function handleFeishuMessage(params) {
|
|
|
3332
3850
|
},
|
|
3333
3851
|
dispatcherOptions,
|
|
3334
3852
|
delivery,
|
|
3335
|
-
replyOptions
|
|
3853
|
+
replyOptions: {
|
|
3854
|
+
...replyOptions,
|
|
3855
|
+
...turnAdoptionLifecycle ? bindIngressLifecycleToReplyOptions(turnAdoptionLifecycle) : {}
|
|
3856
|
+
}
|
|
3336
3857
|
})
|
|
3337
3858
|
}
|
|
3338
3859
|
});
|
|
@@ -3344,6 +3865,7 @@ async function handleFeishuMessage(params) {
|
|
|
3344
3865
|
}
|
|
3345
3866
|
} catch (err) {
|
|
3346
3867
|
error(`feishu[${account.accountId}]: failed to dispatch message: ${String(err)}`);
|
|
3868
|
+
if (turnAdoptionLifecycle) throw err;
|
|
3347
3869
|
}
|
|
3348
3870
|
}
|
|
3349
3871
|
//#endregion
|
|
@@ -3721,6 +4243,350 @@ async function handleFeishuCardAction(params) {
|
|
|
3721
4243
|
}
|
|
3722
4244
|
}
|
|
3723
4245
|
//#endregion
|
|
4246
|
+
//#region extensions/feishu/src/feishu-ingress.ts
|
|
4247
|
+
const FEISHU_INGRESS_PAYLOAD_VERSION = 1;
|
|
4248
|
+
const FEISHU_INGRESS_POLL_INTERVAL_MS = 500;
|
|
4249
|
+
const FEISHU_INGRESS_PRUNE_INTERVAL_MS = 3600 * 1e3;
|
|
4250
|
+
const FEISHU_INGRESS_COMPLETED_TTL_MS = 720 * 60 * 60 * 1e3;
|
|
4251
|
+
const FEISHU_INGRESS_COMPLETED_MAX_ENTRIES = 2e4;
|
|
4252
|
+
const FEISHU_INGRESS_FAILED_TTL_MS = 720 * 60 * 60 * 1e3;
|
|
4253
|
+
const FEISHU_INGRESS_FAILED_MAX_ENTRIES = 2e4;
|
|
4254
|
+
const FEISHU_DURABLE_EVENT_TYPES = /* @__PURE__ */ new Set(["drive.notice.comment_add_v1", "im.message.receive_v1"]);
|
|
4255
|
+
var FeishuIngressPermanentError = class extends Error {
|
|
4256
|
+
constructor(reason, message, options) {
|
|
4257
|
+
super(message, options);
|
|
4258
|
+
this.reason = reason;
|
|
4259
|
+
this.name = "FeishuIngressPermanentError";
|
|
4260
|
+
}
|
|
4261
|
+
};
|
|
4262
|
+
function isRecord$1(value) {
|
|
4263
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
4264
|
+
}
|
|
4265
|
+
function readString(value) {
|
|
4266
|
+
return typeof value === "string" && value.trim() ? value.trim() : null;
|
|
4267
|
+
}
|
|
4268
|
+
function parseRawEnvelope(rawEnvelope) {
|
|
4269
|
+
let parsed;
|
|
4270
|
+
try {
|
|
4271
|
+
parsed = JSON.parse(rawEnvelope);
|
|
4272
|
+
} catch (error) {
|
|
4273
|
+
throw new FeishuIngressPermanentError("invalid-event", "Feishu ingress envelope contains invalid JSON.", { cause: error });
|
|
4274
|
+
}
|
|
4275
|
+
if (!isRecord$1(parsed)) throw new FeishuIngressPermanentError("invalid-event", "Feishu ingress envelope must be a JSON object.");
|
|
4276
|
+
return parsed;
|
|
4277
|
+
}
|
|
4278
|
+
function decryptEnvelope(envelope, encryptKey) {
|
|
4279
|
+
const encrypted = readString(envelope.encrypt);
|
|
4280
|
+
if (!encrypted) return envelope;
|
|
4281
|
+
const key = encryptKey?.trim();
|
|
4282
|
+
if (!key) throw new FeishuIngressPermanentError("authentication-failed", "Feishu encrypted ingress envelope has no configured encrypt key.");
|
|
4283
|
+
try {
|
|
4284
|
+
return parseRawEnvelope(new Lark.AESCipher(key).decrypt(encrypted));
|
|
4285
|
+
} catch (error) {
|
|
4286
|
+
if (error instanceof FeishuIngressPermanentError) throw error;
|
|
4287
|
+
throw new FeishuIngressPermanentError("authentication-failed", "Feishu ingress envelope decryption failed.", { cause: error });
|
|
4288
|
+
}
|
|
4289
|
+
}
|
|
4290
|
+
function inspectFeishuIngressEnvelope(rawEnvelope, encryptKey, allowInvalidLane = false) {
|
|
4291
|
+
const envelope = decryptEnvelope(parseRawEnvelope(rawEnvelope), encryptKey);
|
|
4292
|
+
const nestedHeader = isRecord$1(envelope.header) ? envelope.header : null;
|
|
4293
|
+
const nestedEvent = isRecord$1(envelope.event) ? envelope.event : null;
|
|
4294
|
+
const eventType = readString(nestedHeader?.event_type) ?? readString(envelope.event_type);
|
|
4295
|
+
if (!eventType || !FEISHU_DURABLE_EVENT_TYPES.has(eventType)) return null;
|
|
4296
|
+
const eventId = readString(nestedHeader?.event_id) ?? readString(envelope.event_id);
|
|
4297
|
+
if (!eventId) throw new FeishuIngressPermanentError("invalid-event", `Feishu ${eventType} envelope is missing header.event_id.`);
|
|
4298
|
+
const event = nestedEvent ?? envelope;
|
|
4299
|
+
if (eventType === "im.message.receive_v1") {
|
|
4300
|
+
const chatId = readString((isRecord$1(event.message) ? event.message : null)?.chat_id);
|
|
4301
|
+
if (!chatId) {
|
|
4302
|
+
if (allowInvalidLane) return {
|
|
4303
|
+
eventId,
|
|
4304
|
+
eventType,
|
|
4305
|
+
laneKey: `invalid:${eventType}:${eventId}`
|
|
4306
|
+
};
|
|
4307
|
+
throw new FeishuIngressPermanentError("invalid-event", "Feishu message ingress envelope is missing event.message.chat_id.");
|
|
4308
|
+
}
|
|
4309
|
+
return {
|
|
4310
|
+
eventId,
|
|
4311
|
+
eventType,
|
|
4312
|
+
laneKey: `chat:${chatId}`
|
|
4313
|
+
};
|
|
4314
|
+
}
|
|
4315
|
+
const noticeMeta = isRecord$1(event.notice_meta) ? event.notice_meta : null;
|
|
4316
|
+
const fileType = readString(noticeMeta?.file_type);
|
|
4317
|
+
const documentId = readString(noticeMeta?.file_token);
|
|
4318
|
+
if (!fileType || !documentId) {
|
|
4319
|
+
if (allowInvalidLane) return {
|
|
4320
|
+
eventId,
|
|
4321
|
+
eventType,
|
|
4322
|
+
laneKey: `invalid:${eventType}:${eventId}`
|
|
4323
|
+
};
|
|
4324
|
+
throw new FeishuIngressPermanentError("invalid-event", "Feishu comment ingress envelope is missing its document identity.");
|
|
4325
|
+
}
|
|
4326
|
+
return {
|
|
4327
|
+
eventId,
|
|
4328
|
+
eventType,
|
|
4329
|
+
laneKey: `comment-doc:${fileType}:${documentId}`
|
|
4330
|
+
};
|
|
4331
|
+
}
|
|
4332
|
+
function resolveFeishuIngressNonRetryableFailure(error) {
|
|
4333
|
+
if (error instanceof FeishuIngressPermanentError) return {
|
|
4334
|
+
reason: error.reason,
|
|
4335
|
+
message: error.message
|
|
4336
|
+
};
|
|
4337
|
+
for (const candidate of collectErrorGraphCandidates(error, (current) => [current.cause, current.response])) {
|
|
4338
|
+
const status = isRecord$1(candidate) ? candidate.status : void 0;
|
|
4339
|
+
if (status === 401 || status === 403) return {
|
|
4340
|
+
reason: "authentication-failed",
|
|
4341
|
+
message: formatErrorMessage(error)
|
|
4342
|
+
};
|
|
4343
|
+
}
|
|
4344
|
+
return null;
|
|
4345
|
+
}
|
|
4346
|
+
/** Fan one merged Feishu turn's adoption across every transport and logical claim. */
|
|
4347
|
+
function buildFeishuFlushIngressLifecycle(sources, options) {
|
|
4348
|
+
const durableSources = sources.filter((source) => source.lifecycle !== void 0);
|
|
4349
|
+
const lifecycles = durableSources.map((source) => source.lifecycle);
|
|
4350
|
+
const replayClaims = durableSources.map((source) => source.replayClaim).filter((claim) => claim !== void 0);
|
|
4351
|
+
const transportLifecycle = fanInChannelIngressLifecycles(lifecycles).lifecycle;
|
|
4352
|
+
if (!transportLifecycle) return {
|
|
4353
|
+
lifecycle: void 0,
|
|
4354
|
+
settle: async () => {}
|
|
4355
|
+
};
|
|
4356
|
+
let handedOff = false;
|
|
4357
|
+
let terminal;
|
|
4358
|
+
let adopting;
|
|
4359
|
+
let abandoning;
|
|
4360
|
+
const releaseReplayClaims = () => {
|
|
4361
|
+
for (const claim of replayClaims) claim.release({ error: /* @__PURE__ */ new Error("feishu-ingress-not-adopted") });
|
|
4362
|
+
};
|
|
4363
|
+
const runAbandon = async () => {
|
|
4364
|
+
if (terminal) return;
|
|
4365
|
+
releaseReplayClaims();
|
|
4366
|
+
await transportLifecycle.onAbandoned();
|
|
4367
|
+
terminal = "abandoned";
|
|
4368
|
+
};
|
|
4369
|
+
const ensureAbandoned = async () => {
|
|
4370
|
+
if (terminal) return;
|
|
4371
|
+
const activeAbandonment = abandoning ?? runAbandon();
|
|
4372
|
+
abandoning = activeAbandonment;
|
|
4373
|
+
try {
|
|
4374
|
+
await activeAbandonment;
|
|
4375
|
+
} finally {
|
|
4376
|
+
if (abandoning === activeAbandonment && terminal !== "abandoned") abandoning = void 0;
|
|
4377
|
+
}
|
|
4378
|
+
};
|
|
4379
|
+
const abandonAll = async () => {
|
|
4380
|
+
if (terminal) return;
|
|
4381
|
+
if (adopting) {
|
|
4382
|
+
await adopting.catch(() => void 0);
|
|
4383
|
+
if (terminal) return;
|
|
4384
|
+
}
|
|
4385
|
+
await ensureAbandoned();
|
|
4386
|
+
};
|
|
4387
|
+
const adoptAll = async () => {
|
|
4388
|
+
if (terminal) return;
|
|
4389
|
+
if (abandoning) {
|
|
4390
|
+
await abandoning.catch(() => void 0);
|
|
4391
|
+
if (terminal) return;
|
|
4392
|
+
}
|
|
4393
|
+
const activeAdoption = adopting ?? (async () => {
|
|
4394
|
+
try {
|
|
4395
|
+
await transportLifecycle.onAdopted();
|
|
4396
|
+
terminal = "adopted";
|
|
4397
|
+
const results = await Promise.allSettled(replayClaims.map(async (claim) => claim.commit()));
|
|
4398
|
+
for (const result of results) if (result.status === "rejected") try {
|
|
4399
|
+
options?.onReplayCommitError?.(result.reason);
|
|
4400
|
+
} catch {}
|
|
4401
|
+
} catch (error) {
|
|
4402
|
+
await ensureAbandoned().catch(() => void 0);
|
|
4403
|
+
throw error;
|
|
4404
|
+
}
|
|
4405
|
+
})();
|
|
4406
|
+
adopting = activeAdoption;
|
|
4407
|
+
try {
|
|
4408
|
+
await activeAdoption;
|
|
4409
|
+
} finally {
|
|
4410
|
+
if (adopting === activeAdoption && terminal !== "adopted") adopting = void 0;
|
|
4411
|
+
}
|
|
4412
|
+
};
|
|
4413
|
+
return {
|
|
4414
|
+
lifecycle: {
|
|
4415
|
+
abortSignal: transportLifecycle.abortSignal,
|
|
4416
|
+
onAdopted: async () => {
|
|
4417
|
+
handedOff = true;
|
|
4418
|
+
await adoptAll();
|
|
4419
|
+
},
|
|
4420
|
+
onDeferred: () => {
|
|
4421
|
+
handedOff = true;
|
|
4422
|
+
transportLifecycle.onDeferred();
|
|
4423
|
+
},
|
|
4424
|
+
onAdoptionFinalizing: () => {
|
|
4425
|
+
transportLifecycle.onAdoptionFinalizing();
|
|
4426
|
+
},
|
|
4427
|
+
onAbandoned: async () => {
|
|
4428
|
+
handedOff = true;
|
|
4429
|
+
await abandonAll();
|
|
4430
|
+
}
|
|
4431
|
+
},
|
|
4432
|
+
settle: async () => {
|
|
4433
|
+
if (handedOff) return;
|
|
4434
|
+
handedOff = true;
|
|
4435
|
+
releaseReplayClaims();
|
|
4436
|
+
try {
|
|
4437
|
+
transportLifecycle.onAdoptionFinalizing();
|
|
4438
|
+
await transportLifecycle.onAdopted();
|
|
4439
|
+
terminal = "adopted";
|
|
4440
|
+
} catch (error) {
|
|
4441
|
+
await ensureAbandoned().catch(() => void 0);
|
|
4442
|
+
throw error;
|
|
4443
|
+
}
|
|
4444
|
+
}
|
|
4445
|
+
};
|
|
4446
|
+
}
|
|
4447
|
+
function createFeishuDurableIngress(options) {
|
|
4448
|
+
let socketTerminator;
|
|
4449
|
+
const activeLifecycles = /* @__PURE__ */ new Map();
|
|
4450
|
+
const deferredClaims = /* @__PURE__ */ new Map();
|
|
4451
|
+
const monitor = createChannelIngressMonitor({
|
|
4452
|
+
queue: options.queue ?? (() => getFeishuRuntime().state.openChannelIngressQueue({ accountId: options.accountId })),
|
|
4453
|
+
inspect: (rawEnvelope, context) => {
|
|
4454
|
+
const facts = inspectFeishuIngressEnvelope(rawEnvelope, options.encryptKey, context.phase === "admission");
|
|
4455
|
+
return facts ? {
|
|
4456
|
+
eventId: facts.eventId,
|
|
4457
|
+
laneKey: facts.laneKey
|
|
4458
|
+
} : null;
|
|
4459
|
+
},
|
|
4460
|
+
payload: {
|
|
4461
|
+
version: FEISHU_INGRESS_PAYLOAD_VERSION,
|
|
4462
|
+
serialize: (rawEnvelope, { receivedAt }) => ({
|
|
4463
|
+
receivedAt,
|
|
4464
|
+
rawEnvelope
|
|
4465
|
+
}),
|
|
4466
|
+
deserialize: (body) => body.rawEnvelope,
|
|
4467
|
+
encode: ({ body }) => ({
|
|
4468
|
+
version: FEISHU_INGRESS_PAYLOAD_VERSION,
|
|
4469
|
+
...body
|
|
4470
|
+
}),
|
|
4471
|
+
decode: (payload) => ({
|
|
4472
|
+
version: payload.version,
|
|
4473
|
+
body: {
|
|
4474
|
+
receivedAt: payload.receivedAt,
|
|
4475
|
+
rawEnvelope: payload.rawEnvelope
|
|
4476
|
+
}
|
|
4477
|
+
}),
|
|
4478
|
+
createClaimError: (kind, claim) => new FeishuIngressPermanentError("invalid-event", kind === "invalid-version" ? `Feishu ingress row ${claim.id} has an unsupported version.` : `Feishu ingress row ${claim.id} has invalid delivery identity.`)
|
|
4479
|
+
},
|
|
4480
|
+
deliver: async (rawEnvelope, lifecycle, claim) => {
|
|
4481
|
+
let resolveDeferred;
|
|
4482
|
+
const deferred = new Promise((resolve) => {
|
|
4483
|
+
resolveDeferred = resolve;
|
|
4484
|
+
});
|
|
4485
|
+
let deferredSettled = false;
|
|
4486
|
+
const settleDeferred = () => {
|
|
4487
|
+
if (deferredSettled) return;
|
|
4488
|
+
deferredSettled = true;
|
|
4489
|
+
if (deferredClaims.get(claim.id) === deferred) deferredClaims.delete(claim.id);
|
|
4490
|
+
resolveDeferred();
|
|
4491
|
+
};
|
|
4492
|
+
const abandonHandlers = /* @__PURE__ */ new Set();
|
|
4493
|
+
const wrappedLifecycle = {
|
|
4494
|
+
...lifecycle,
|
|
4495
|
+
onAdopted: async () => {
|
|
4496
|
+
try {
|
|
4497
|
+
await lifecycle.onAdopted();
|
|
4498
|
+
} finally {
|
|
4499
|
+
settleDeferred();
|
|
4500
|
+
}
|
|
4501
|
+
},
|
|
4502
|
+
onAbandoned: async () => {
|
|
4503
|
+
try {
|
|
4504
|
+
await Promise.allSettled([...abandonHandlers].map(async (handler) => await handler()));
|
|
4505
|
+
await lifecycle.onAbandoned();
|
|
4506
|
+
} finally {
|
|
4507
|
+
settleDeferred();
|
|
4508
|
+
}
|
|
4509
|
+
},
|
|
4510
|
+
registerAbandonHandler: (handler) => {
|
|
4511
|
+
abandonHandlers.add(handler);
|
|
4512
|
+
return () => abandonHandlers.delete(handler);
|
|
4513
|
+
}
|
|
4514
|
+
};
|
|
4515
|
+
activeLifecycles.set(claim.id, wrappedLifecycle);
|
|
4516
|
+
try {
|
|
4517
|
+
const result = await options.dispatcher.invoke(parseRawEnvelope(rawEnvelope), { needCheck: false });
|
|
4518
|
+
if (!isRecord$1(result)) return;
|
|
4519
|
+
if (result.kind === "deferred") {
|
|
4520
|
+
if (!deferredSettled) deferredClaims.set(claim.id, deferred);
|
|
4521
|
+
return { kind: "deferred" };
|
|
4522
|
+
}
|
|
4523
|
+
if (result.kind === "completed") return { kind: "completed" };
|
|
4524
|
+
if (result.kind === "failed-retryable") return {
|
|
4525
|
+
kind: "failed-retryable",
|
|
4526
|
+
error: result.error
|
|
4527
|
+
};
|
|
4528
|
+
return;
|
|
4529
|
+
} finally {
|
|
4530
|
+
if (activeLifecycles.get(claim.id) === wrappedLifecycle) activeLifecycles.delete(claim.id);
|
|
4531
|
+
}
|
|
4532
|
+
},
|
|
4533
|
+
pollIntervalMs: options.pollIntervalMs ?? FEISHU_INGRESS_POLL_INTERVAL_MS,
|
|
4534
|
+
retention: {
|
|
4535
|
+
pruneIntervalMs: FEISHU_INGRESS_PRUNE_INTERVAL_MS,
|
|
4536
|
+
completedTtlMs: FEISHU_INGRESS_COMPLETED_TTL_MS,
|
|
4537
|
+
completedMaxEntries: FEISHU_INGRESS_COMPLETED_MAX_ENTRIES,
|
|
4538
|
+
failedTtlMs: FEISHU_INGRESS_FAILED_TTL_MS,
|
|
4539
|
+
failedMaxEntries: FEISHU_INGRESS_FAILED_MAX_ENTRIES
|
|
4540
|
+
},
|
|
4541
|
+
drain: {
|
|
4542
|
+
adoptionStallTimeoutMs: options.adoptionStallTimeoutMs ?? DEFAULT_INGRESS_ADOPTION_STALL_MS,
|
|
4543
|
+
resolveNonRetryableFailure: resolveFeishuIngressNonRetryableFailure,
|
|
4544
|
+
onLog: (message) => options.runtime.error?.(`feishu ingress: ${message}`)
|
|
4545
|
+
},
|
|
4546
|
+
createStoppedError: () => /* @__PURE__ */ new Error("Feishu ingress is stopped."),
|
|
4547
|
+
onError: (error) => options.runtime.error?.(`feishu ingress drain failed: ${formatErrorMessage(error)}`)
|
|
4548
|
+
});
|
|
4549
|
+
const invoke = async (data, params) => {
|
|
4550
|
+
let rawEnvelope;
|
|
4551
|
+
try {
|
|
4552
|
+
const serialized = JSON.stringify(data);
|
|
4553
|
+
if (serialized === void 0) throw new TypeError("Feishu ingress envelope has no JSON representation.");
|
|
4554
|
+
rawEnvelope = serialized;
|
|
4555
|
+
} catch (error) {
|
|
4556
|
+
throw new FeishuIngressPermanentError("invalid-event", "Feishu ingress envelope is not serializable.", { cause: error });
|
|
4557
|
+
}
|
|
4558
|
+
const facts = inspectFeishuIngressEnvelope(rawEnvelope, options.encryptKey, true);
|
|
4559
|
+
if (!facts) return await options.dispatcher.invoke(data, params);
|
|
4560
|
+
try {
|
|
4561
|
+
await monitor.admit(rawEnvelope, { facts: {
|
|
4562
|
+
eventId: facts.eventId,
|
|
4563
|
+
laneKey: facts.laneKey
|
|
4564
|
+
} });
|
|
4565
|
+
} catch (error) {
|
|
4566
|
+
socketTerminator?.();
|
|
4567
|
+
throw error;
|
|
4568
|
+
}
|
|
4569
|
+
};
|
|
4570
|
+
return {
|
|
4571
|
+
invoke,
|
|
4572
|
+
resolveLifecycle: (data) => {
|
|
4573
|
+
const eventId = isRecord$1(data) ? readString(data.event_id) : null;
|
|
4574
|
+
return eventId ? activeLifecycles.get(eventId) : void 0;
|
|
4575
|
+
},
|
|
4576
|
+
setSocketTerminator: (terminate) => {
|
|
4577
|
+
socketTerminator = terminate;
|
|
4578
|
+
},
|
|
4579
|
+
start: monitor.start,
|
|
4580
|
+
stop: async () => {
|
|
4581
|
+
await monitor.stop();
|
|
4582
|
+
await Promise.allSettled(deferredClaims.values());
|
|
4583
|
+
activeLifecycles.clear();
|
|
4584
|
+
socketTerminator = void 0;
|
|
4585
|
+
},
|
|
4586
|
+
waitForIdle: monitor.waitForIdle
|
|
4587
|
+
};
|
|
4588
|
+
}
|
|
4589
|
+
//#endregion
|
|
3724
4590
|
//#region extensions/feishu/src/monitor-defaults.ts
|
|
3725
4591
|
const FEISHU_WEBHOOK_RATE_LIMIT_FALLBACK_DEFAULTS = {
|
|
3726
4592
|
windowMs: 6e4,
|
|
@@ -3871,7 +4737,8 @@ function applyBotIdentityState(accountId, identity) {
|
|
|
3871
4737
|
});
|
|
3872
4738
|
return {
|
|
3873
4739
|
botOpenId,
|
|
3874
|
-
botName
|
|
4740
|
+
botName,
|
|
4741
|
+
source: botOpenId ? identity.source : void 0
|
|
3875
4742
|
};
|
|
3876
4743
|
}
|
|
3877
4744
|
async function retryBotIdentityProbe(account, accountId, runtime, abortSignal) {
|
|
@@ -3881,12 +4748,13 @@ async function retryBotIdentityProbe(account, accountId, runtime, abortSignal) {
|
|
|
3881
4748
|
for (const [i, delayMs] of BOT_IDENTITY_RETRY_DELAYS_MS.entries()) {
|
|
3882
4749
|
if (abortSignal?.aborted) return;
|
|
3883
4750
|
if (!await waitForAbortableDelay(delayMs, abortSignal)) return;
|
|
3884
|
-
const
|
|
4751
|
+
const identity = await fetchBotIdentityForMonitor(account, {
|
|
3885
4752
|
runtime,
|
|
3886
|
-
abortSignal
|
|
3887
|
-
|
|
3888
|
-
|
|
3889
|
-
|
|
4753
|
+
abortSignal,
|
|
4754
|
+
allowCachedFallback: false
|
|
4755
|
+
});
|
|
4756
|
+
if (normalizeOptionalString(identity.botOpenId) && identity.source === "provider") {
|
|
4757
|
+
log(`feishu[${accountId}]: bot open_id recovered via background retry: ${applyBotIdentityState(accountId, identity).botOpenId}`);
|
|
3890
4758
|
return;
|
|
3891
4759
|
}
|
|
3892
4760
|
const nextDelayResult = nextDelays.next();
|
|
@@ -3896,10 +4764,10 @@ async function retryBotIdentityProbe(account, accountId, runtime, abortSignal) {
|
|
|
3896
4764
|
error(`feishu[${accountId}]: bot identity background retry exhausted; requireMention group messages may be skipped until restart`);
|
|
3897
4765
|
}
|
|
3898
4766
|
function startBotIdentityRecovery(params) {
|
|
3899
|
-
const { account, accountId, runtime, abortSignal } = params;
|
|
4767
|
+
const { account, accountId, runtime, abortSignal, currentSource } = params;
|
|
3900
4768
|
const log = runtime?.log ?? console.log;
|
|
3901
|
-
log(`feishu[${accountId}]: bot open_id unknown; starting background
|
|
3902
|
-
log(`feishu[${accountId}]: requireMention group messages stay gated until bot identity recovery succeeds`);
|
|
4769
|
+
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(", ")})`);
|
|
4770
|
+
if (currentSource !== "cache") log(`feishu[${accountId}]: requireMention group messages stay gated until bot identity recovery succeeds`);
|
|
3903
4771
|
retryBotIdentityProbe(account, accountId, runtime, abortSignal);
|
|
3904
4772
|
}
|
|
3905
4773
|
//#endregion
|
|
@@ -4153,20 +5021,40 @@ function createFeishuCommentReplyDispatcher(params) {
|
|
|
4153
5021
|
}
|
|
4154
5022
|
},
|
|
4155
5023
|
delivery: {
|
|
5024
|
+
observeMessageSent: true,
|
|
4156
5025
|
deliver: async (payload, info) => {
|
|
4157
|
-
if (info.kind !== "final") return;
|
|
5026
|
+
if (info.kind !== "final") return noVisibleFeishuReplyDelivery;
|
|
4158
5027
|
const reply = resolveSendableOutboundReplyParts(payload);
|
|
4159
5028
|
if (!reply.hasText) {
|
|
4160
5029
|
if (reply.hasMedia) params.runtime.log?.(`feishu[${params.accountId ?? "default"}]: comment reply ignored media-only payload for comment=${params.commentId}`);
|
|
4161
|
-
return;
|
|
5030
|
+
return noVisibleFeishuReplyDelivery;
|
|
4162
5031
|
}
|
|
4163
5032
|
const chunks = core.channel.text.chunkTextWithMode(reply.text, textChunkLimit, chunkMode);
|
|
4164
|
-
|
|
4165
|
-
|
|
4166
|
-
|
|
4167
|
-
|
|
4168
|
-
|
|
4169
|
-
|
|
5033
|
+
const results = [];
|
|
5034
|
+
const acceptedChunks = [];
|
|
5035
|
+
for (const chunk of chunks) try {
|
|
5036
|
+
const result = await deliverCommentThreadText(client, {
|
|
5037
|
+
file_token: params.fileToken,
|
|
5038
|
+
file_type: params.fileType,
|
|
5039
|
+
comment_id: params.commentId,
|
|
5040
|
+
content: chunk,
|
|
5041
|
+
is_whole_comment: params.isWholeComment
|
|
5042
|
+
});
|
|
5043
|
+
results.push({ messageId: result.delivery_mode === "reply_comment" ? result.reply_id : result.comment_id });
|
|
5044
|
+
acceptedChunks.push(chunk);
|
|
5045
|
+
} catch (error) {
|
|
5046
|
+
throw createFeishuPartialReplyDeliveryError(error, createFeishuReplyDeliveryResult({
|
|
5047
|
+
results,
|
|
5048
|
+
visibleReplySent: results.length > 0,
|
|
5049
|
+
content: acceptedChunks.join(""),
|
|
5050
|
+
kind: "text"
|
|
5051
|
+
}));
|
|
5052
|
+
}
|
|
5053
|
+
return createFeishuReplyDeliveryResult({
|
|
5054
|
+
results,
|
|
5055
|
+
visibleReplySent: results.length > 0,
|
|
5056
|
+
content: reply.text,
|
|
5057
|
+
kind: "text"
|
|
4170
5058
|
});
|
|
4171
5059
|
},
|
|
4172
5060
|
onError: (err, info) => {
|
|
@@ -4332,18 +5220,18 @@ async function fetchDriveComments(params) {
|
|
|
4332
5220
|
}
|
|
4333
5221
|
async function requestFeishuOpenApi(params) {
|
|
4334
5222
|
const formatErrorDetails = (error) => {
|
|
4335
|
-
if (!isRecord$
|
|
4336
|
-
const response = isRecord$
|
|
4337
|
-
const responseData = isRecord$
|
|
5223
|
+
if (!isRecord$2(error)) return typeof error === "string" ? error : JSON.stringify(error);
|
|
5224
|
+
const response = isRecord$2(error.response) ? error.response : void 0;
|
|
5225
|
+
const responseData = isRecord$2(response?.data) ? response?.data : void 0;
|
|
4338
5226
|
return safeJsonStringify({
|
|
4339
5227
|
message: typeof error.message === "string" ? error.message : typeof error === "string" ? error : JSON.stringify(error),
|
|
4340
|
-
code: readString(error.code),
|
|
4341
|
-
method: readString(isRecord$
|
|
4342
|
-
url: readString(isRecord$
|
|
5228
|
+
code: readString$1(error.code),
|
|
5229
|
+
method: readString$1(isRecord$2(error.config) ? error.config.method : void 0),
|
|
5230
|
+
url: readString$1(isRecord$2(error.config) ? error.config.url : void 0),
|
|
4343
5231
|
http_status: typeof response?.status === "number" ? response.status : void 0,
|
|
4344
|
-
feishu_code: typeof responseData?.code === "number" ? responseData.code : readString(responseData?.code),
|
|
4345
|
-
feishu_msg: readString(responseData?.msg),
|
|
4346
|
-
feishu_log_id: readString(responseData?.log_id)
|
|
5232
|
+
feishu_code: typeof responseData?.code === "number" ? responseData.code : readString$1(responseData?.code),
|
|
5233
|
+
feishu_msg: readString$1(responseData?.msg),
|
|
5234
|
+
feishu_log_id: readString$1(responseData?.log_id)
|
|
4347
5235
|
});
|
|
4348
5236
|
};
|
|
4349
5237
|
const result = await raceWithTimeoutAndAbort(params.client.request({
|
|
@@ -4399,7 +5287,7 @@ async function resolveCommentReplyContext(params) {
|
|
|
4399
5287
|
createTime: typeof params.reply.create_time === "number" ? params.reply.create_time : void 0,
|
|
4400
5288
|
isBotAuthored: typeof userId === "string" && normalizedBotOpenIds.has(userId),
|
|
4401
5289
|
content: await resolveParsedCommentContent({
|
|
4402
|
-
elements: isRecord$
|
|
5290
|
+
elements: isRecord$2(params.reply.content) ? params.reply.content.elements : void 0,
|
|
4403
5291
|
botOpenIds: params.botOpenIds,
|
|
4404
5292
|
currentDocument: params.currentDocument,
|
|
4405
5293
|
client: params.client,
|
|
@@ -4571,7 +5459,7 @@ async function fetchDriveCommentContext(params) {
|
|
|
4571
5459
|
const rootWholeReply = comment.reply_list?.replies?.[0];
|
|
4572
5460
|
const normalizedBotOpenIds = new Set(Array.from(params.botOpenIds ?? []).map((botId) => normalizeString(botId)).filter((botId) => Boolean(botId)));
|
|
4573
5461
|
const content = await resolveParsedCommentContent({
|
|
4574
|
-
elements: isRecord$
|
|
5462
|
+
elements: isRecord$2(rootWholeReply?.content) ? rootWholeReply.content.elements : void 0,
|
|
4575
5463
|
botOpenIds: params.botOpenIds,
|
|
4576
5464
|
currentDocument,
|
|
4577
5465
|
client: params.client,
|
|
@@ -4678,16 +5566,19 @@ async function resolveDriveCommentEventCore(params) {
|
|
|
4678
5566
|
logger?.(`feishu[${accountId}]: unsupported drive comment notice type ${noticeType}`);
|
|
4679
5567
|
return null;
|
|
4680
5568
|
}
|
|
4681
|
-
|
|
5569
|
+
const configuredBotOpenId = botOpenId?.trim();
|
|
5570
|
+
const eventRecipientBotOpenId = event.notice_meta?.to_user_id?.open_id?.trim();
|
|
5571
|
+
const effectiveBotOpenId = configuredBotOpenId || (event.is_mentioned === true ? eventRecipientBotOpenId : void 0);
|
|
5572
|
+
if (!effectiveBotOpenId) {
|
|
4682
5573
|
logger?.(`feishu[${accountId}]: skipping drive comment notice because bot open_id is unavailable event=${eventId}`);
|
|
4683
5574
|
return null;
|
|
4684
5575
|
}
|
|
4685
|
-
if (senderId ===
|
|
5576
|
+
if (senderId === effectiveBotOpenId) {
|
|
4686
5577
|
logger?.(`feishu[${accountId}]: ignoring self-authored drive comment notice event=${eventId} sender=${senderId}`);
|
|
4687
5578
|
return null;
|
|
4688
5579
|
}
|
|
4689
5580
|
const context = await fetchDriveCommentContext({
|
|
4690
|
-
client: createClient ? createClient(account ?? { accountId }) : createFeishuClient((await import("./accounts-
|
|
5581
|
+
client: createClient ? createClient(account ?? { accountId }) : createFeishuClient((await import("./accounts-CKhCa76b.js").then((n) => n.t)).resolveFeishuAccount({
|
|
4691
5582
|
cfg,
|
|
4692
5583
|
accountId
|
|
4693
5584
|
})),
|
|
@@ -4695,7 +5586,7 @@ async function resolveDriveCommentEventCore(params) {
|
|
|
4695
5586
|
fileType,
|
|
4696
5587
|
commentId,
|
|
4697
5588
|
replyId,
|
|
4698
|
-
botOpenIds: [
|
|
5589
|
+
botOpenIds: [effectiveBotOpenId, event.notice_meta?.to_user_id?.open_id],
|
|
4699
5590
|
timeoutMs: verificationTimeoutMs,
|
|
4700
5591
|
logger,
|
|
4701
5592
|
accountId,
|
|
@@ -4717,32 +5608,32 @@ async function resolveDriveCommentEventCore(params) {
|
|
|
4717
5608
|
};
|
|
4718
5609
|
}
|
|
4719
5610
|
function parseFeishuDriveCommentNoticeEventPayload(value) {
|
|
4720
|
-
if (!isRecord$
|
|
5611
|
+
if (!isRecord$2(value) || !isRecord$2(value.notice_meta)) return null;
|
|
4721
5612
|
const noticeMeta = value.notice_meta;
|
|
4722
|
-
const fromUserId = isRecord$
|
|
4723
|
-
const toUserId = isRecord$
|
|
5613
|
+
const fromUserId = isRecord$2(noticeMeta.from_user_id) ? noticeMeta.from_user_id : void 0;
|
|
5614
|
+
const toUserId = isRecord$2(noticeMeta.to_user_id) ? noticeMeta.to_user_id : void 0;
|
|
4724
5615
|
return {
|
|
4725
|
-
comment_id: readString(value.comment_id),
|
|
4726
|
-
event_id: readString(value.event_id),
|
|
5616
|
+
comment_id: readString$1(value.comment_id),
|
|
5617
|
+
event_id: readString$1(value.event_id),
|
|
4727
5618
|
is_mentioned: asBoolean(value.is_mentioned),
|
|
4728
5619
|
notice_meta: {
|
|
4729
|
-
file_token: readString(noticeMeta.file_token),
|
|
4730
|
-
file_type: readString(noticeMeta.file_type),
|
|
5620
|
+
file_token: readString$1(noticeMeta.file_token),
|
|
5621
|
+
file_type: readString$1(noticeMeta.file_type),
|
|
4731
5622
|
from_user_id: fromUserId ? {
|
|
4732
|
-
open_id: readString(fromUserId.open_id),
|
|
4733
|
-
user_id: readString(fromUserId.user_id),
|
|
4734
|
-
union_id: readString(fromUserId.union_id)
|
|
5623
|
+
open_id: readString$1(fromUserId.open_id),
|
|
5624
|
+
user_id: readString$1(fromUserId.user_id),
|
|
5625
|
+
union_id: readString$1(fromUserId.union_id)
|
|
4735
5626
|
} : void 0,
|
|
4736
|
-
notice_type: readString(noticeMeta.notice_type),
|
|
5627
|
+
notice_type: readString$1(noticeMeta.notice_type),
|
|
4737
5628
|
to_user_id: toUserId ? {
|
|
4738
|
-
open_id: readString(toUserId.open_id),
|
|
4739
|
-
user_id: readString(toUserId.user_id),
|
|
4740
|
-
union_id: readString(toUserId.union_id)
|
|
5629
|
+
open_id: readString$1(toUserId.open_id),
|
|
5630
|
+
user_id: readString$1(toUserId.user_id),
|
|
5631
|
+
union_id: readString$1(toUserId.union_id)
|
|
4741
5632
|
} : void 0
|
|
4742
5633
|
},
|
|
4743
|
-
reply_id: readString(value.reply_id),
|
|
4744
|
-
timestamp: readString(value.timestamp),
|
|
4745
|
-
type: readString(value.type)
|
|
5634
|
+
reply_id: readString$1(value.reply_id),
|
|
5635
|
+
timestamp: readString$1(value.timestamp),
|
|
5636
|
+
type: readString$1(value.type)
|
|
4746
5637
|
};
|
|
4747
5638
|
}
|
|
4748
5639
|
async function resolveDriveCommentEventTurn(params) {
|
|
@@ -5034,7 +5925,8 @@ async function handleFeishuCommentEvent(params) {
|
|
|
5034
5925
|
error(`feishu[${account.accountId}]: failed to record comment inbound session ${commentSessionKey}: ${String(err)}`);
|
|
5035
5926
|
} },
|
|
5036
5927
|
dispatcherOptions,
|
|
5037
|
-
delivery
|
|
5928
|
+
delivery,
|
|
5929
|
+
...params.turnAdoptionLifecycle ? { replyOptions: bindIngressLifecycleToReplyOptions(params.turnAdoptionLifecycle) } : {}
|
|
5038
5930
|
})
|
|
5039
5931
|
}
|
|
5040
5932
|
});
|
|
@@ -5121,52 +6013,40 @@ function createFeishuDriveCommentNoticeHandler(params) {
|
|
|
5121
6013
|
});
|
|
5122
6014
|
if (!fireAndForget) await promise;
|
|
5123
6015
|
};
|
|
5124
|
-
|
|
5125
|
-
|
|
5126
|
-
|
|
5127
|
-
if (
|
|
5128
|
-
|
|
6016
|
+
const handleNotice = async (data, turnAdoptionLifecycle) => {
|
|
6017
|
+
const event = parseFeishuDriveCommentNoticeEventPayload(data);
|
|
6018
|
+
if (!event) {
|
|
6019
|
+
if (turnAdoptionLifecycle) throw new FeishuIngressPermanentError("invalid-event", "Feishu durable comment event payload is malformed.");
|
|
6020
|
+
error(`feishu[${accountId}]: ignoring malformed drive comment notice payload`);
|
|
6021
|
+
return;
|
|
6022
|
+
}
|
|
6023
|
+
log(`feishu[${accountId}]: received drive comment notice event=${event.event_id ?? "unknown"} type=${event.notice_meta?.notice_type ?? "unknown"} file=${event.notice_meta?.file_type ?? "unknown"}:${event.notice_meta?.file_token ?? "unknown"} comment=${event.comment_id ?? "unknown"} reply=${event.reply_id ?? "none"} from=${event.notice_meta?.from_user_id?.open_id ?? "unknown"} mentioned=${event.is_mentioned === true ? "yes" : "no"}`);
|
|
6024
|
+
await enqueue(buildCommentNoticeQueueKey(event), async () => {
|
|
6025
|
+
if (turnAdoptionLifecycle?.abortSignal.aborted) {
|
|
6026
|
+
await turnAdoptionLifecycle.onAbandoned();
|
|
5129
6027
|
return;
|
|
5130
6028
|
}
|
|
5131
|
-
|
|
5132
|
-
|
|
5133
|
-
|
|
5134
|
-
|
|
5135
|
-
|
|
5136
|
-
|
|
5137
|
-
|
|
5138
|
-
|
|
5139
|
-
|
|
5140
|
-
if (claim.kind === "duplicate") {
|
|
5141
|
-
log(`feishu[${accountId}]: dropping duplicate comment event ${syntheticMessageId}`);
|
|
5142
|
-
return;
|
|
5143
|
-
}
|
|
5144
|
-
if (claim.kind === "inflight") {
|
|
5145
|
-
log(`feishu[${accountId}]: dropping in-flight comment event ${syntheticMessageId}`);
|
|
5146
|
-
return;
|
|
5147
|
-
}
|
|
5148
|
-
processingClaim = claim.kind === "claimed" ? claim.handle : void 0;
|
|
5149
|
-
}
|
|
5150
|
-
log(`feishu[${accountId}]: received drive comment notice event=${event.event_id ?? "unknown"} type=${event.notice_meta?.notice_type ?? "unknown"} file=${event.notice_meta?.file_type ?? "unknown"}:${event.notice_meta?.file_token ?? "unknown"} comment=${event.comment_id ?? "unknown"} reply=${event.reply_id ?? "none"} from=${event.notice_meta?.from_user_id?.open_id ?? "unknown"} mentioned=${event.is_mentioned === true ? "yes" : "no"}`);
|
|
5151
|
-
try {
|
|
5152
|
-
await enqueue(buildCommentNoticeQueueKey(event), async () => {
|
|
5153
|
-
await handleFeishuCommentEvent({
|
|
5154
|
-
cfg,
|
|
5155
|
-
accountId,
|
|
5156
|
-
event,
|
|
5157
|
-
botOpenId: getBotOpenId(accountId),
|
|
5158
|
-
runtime,
|
|
5159
|
-
abortSignal
|
|
5160
|
-
});
|
|
5161
|
-
});
|
|
5162
|
-
await processingClaim?.commit();
|
|
5163
|
-
} catch (err) {
|
|
5164
|
-
if (isFeishuRetryableSyntheticEventError(err)) processingClaim?.release({ error: err });
|
|
5165
|
-
else await processingClaim?.commit();
|
|
5166
|
-
throw err;
|
|
5167
|
-
}
|
|
6029
|
+
await handleFeishuCommentEvent({
|
|
6030
|
+
cfg,
|
|
6031
|
+
accountId,
|
|
6032
|
+
event,
|
|
6033
|
+
botOpenId: getBotOpenId(accountId),
|
|
6034
|
+
runtime,
|
|
6035
|
+
abortSignal,
|
|
6036
|
+
turnAdoptionLifecycle
|
|
6037
|
+
});
|
|
5168
6038
|
});
|
|
5169
6039
|
};
|
|
6040
|
+
return async (data) => {
|
|
6041
|
+
const ingressLifecycle = params.resolveIngressLifecycle?.(data);
|
|
6042
|
+
if (!ingressLifecycle) {
|
|
6043
|
+
await runFeishuHandler(async () => await handleNotice(data));
|
|
6044
|
+
return;
|
|
6045
|
+
}
|
|
6046
|
+
const { lifecycle, settle } = buildFeishuFlushIngressLifecycle([{ lifecycle: ingressLifecycle }]);
|
|
6047
|
+
await handleNotice(data, lifecycle);
|
|
6048
|
+
await settle();
|
|
6049
|
+
};
|
|
5170
6050
|
}
|
|
5171
6051
|
//#endregion
|
|
5172
6052
|
//#region extensions/feishu/src/monitor.message-handler.ts
|
|
@@ -5227,7 +6107,7 @@ function resolveFeishuDebounceMentions(params) {
|
|
|
5227
6107
|
const botMentions = merged.filter((mention) => mention.id.open_id?.trim() === normalizedBotOpenId);
|
|
5228
6108
|
return botMentions.length > 0 ? botMentions : void 0;
|
|
5229
6109
|
}
|
|
5230
|
-
function createFeishuMessageReceiveHandler({ cfg, channelRuntime, accountId, runtime, chatHistories, fireAndForget, handleMessage, resolveDebounceText: resolveText, hasProcessedMessage, getBotOpenId = () => void 0, getBotName = () => void 0, resolveSequentialKey = ({ accountId: accountIdLocal, event }) => `feishu:${accountIdLocal}:${event.message.chat_id?.trim() || "unknown"}`, statusSink }) {
|
|
6110
|
+
function createFeishuMessageReceiveHandler({ cfg, channelRuntime, accountId, runtime, chatHistories, fireAndForget, handleMessage, resolveDebounceText: resolveText, hasProcessedMessage, getBotOpenId = () => void 0, getBotName = () => void 0, resolveSequentialKey = ({ accountId: accountIdLocal, event }) => `feishu:${accountIdLocal}:${event.message.chat_id?.trim() || "unknown"}`, statusSink, resolveIngressLifecycle }) {
|
|
5231
6111
|
const inboundDebounceMs = channelRuntime.debounce.resolveInboundDebounceMs({
|
|
5232
6112
|
cfg,
|
|
5233
6113
|
channel: "feishu"
|
|
@@ -5237,25 +6117,32 @@ function createFeishuMessageReceiveHandler({ cfg, channelRuntime, accountId, run
|
|
|
5237
6117
|
const enqueue = createSequentialQueue({ onTaskTimeout: (key, timeoutMs) => {
|
|
5238
6118
|
log(`feishu[${accountId}]: per-chat task exceeded ${timeoutMs}ms cap (key=${key}); evicting from queue so later same-key messages can proceed (#70133)`);
|
|
5239
6119
|
} });
|
|
5240
|
-
const dispatchFeishuMessage = async (event, messageDedupeKey, processingClaim) => {
|
|
6120
|
+
const dispatchFeishuMessage = async (event, messageDedupeKey, processingClaim, turnAdoptionLifecycle) => {
|
|
5241
6121
|
const sequentialKey = resolveSequentialKey({
|
|
5242
6122
|
accountId,
|
|
5243
6123
|
event,
|
|
5244
6124
|
botOpenId: getBotOpenId(accountId),
|
|
5245
6125
|
botName: getBotName(accountId)
|
|
5246
6126
|
});
|
|
5247
|
-
const task = () =>
|
|
5248
|
-
|
|
5249
|
-
|
|
5250
|
-
|
|
5251
|
-
|
|
5252
|
-
|
|
5253
|
-
|
|
5254
|
-
|
|
5255
|
-
|
|
5256
|
-
|
|
5257
|
-
|
|
5258
|
-
|
|
6127
|
+
const task = async () => {
|
|
6128
|
+
if (turnAdoptionLifecycle?.abortSignal.aborted) {
|
|
6129
|
+
await turnAdoptionLifecycle.onAbandoned();
|
|
6130
|
+
return;
|
|
6131
|
+
}
|
|
6132
|
+
await handleMessage({
|
|
6133
|
+
cfg,
|
|
6134
|
+
event,
|
|
6135
|
+
botOpenId: getBotOpenId(accountId),
|
|
6136
|
+
botName: getBotName(accountId),
|
|
6137
|
+
runtime,
|
|
6138
|
+
channelRuntime,
|
|
6139
|
+
chatHistories,
|
|
6140
|
+
accountId,
|
|
6141
|
+
processingClaim,
|
|
6142
|
+
messageDedupeKey,
|
|
6143
|
+
turnAdoptionLifecycle
|
|
6144
|
+
});
|
|
6145
|
+
};
|
|
5259
6146
|
await enqueue(sequentialKey, task);
|
|
5260
6147
|
};
|
|
5261
6148
|
const resolveSenderDebounceId = (event) => {
|
|
@@ -5295,45 +6182,80 @@ function createFeishuMessageReceiveHandler({ cfg, channelRuntime, accountId, run
|
|
|
5295
6182
|
return Boolean(text) && !channelRuntime.commands.isControlCommandMessage(text, cfg);
|
|
5296
6183
|
},
|
|
5297
6184
|
onFlush: async (entries) => {
|
|
5298
|
-
const
|
|
6185
|
+
const activeEntries = entries.filter((entry) => !entry.abandoned);
|
|
6186
|
+
const last = activeEntries.at(-1);
|
|
5299
6187
|
if (!last) return;
|
|
5300
|
-
|
|
5301
|
-
|
|
6188
|
+
const { lifecycle, settle } = buildFeishuFlushIngressLifecycle(activeEntries.map((entry) => ({
|
|
6189
|
+
lifecycle: entry.turnAdoptionLifecycle,
|
|
6190
|
+
replayClaim: entry.processingClaim
|
|
6191
|
+
})), { onReplayCommitError: (err) => error(`feishu[${accountId}]: failed to commit logical replay guard: ${String(err)}`) });
|
|
6192
|
+
if (lifecycle?.abortSignal.aborted) {
|
|
6193
|
+
await lifecycle.onAbandoned();
|
|
5302
6194
|
return;
|
|
5303
6195
|
}
|
|
5304
|
-
|
|
5305
|
-
|
|
5306
|
-
|
|
5307
|
-
|
|
5308
|
-
|
|
5309
|
-
|
|
5310
|
-
|
|
5311
|
-
|
|
5312
|
-
|
|
5313
|
-
|
|
5314
|
-
|
|
5315
|
-
|
|
5316
|
-
|
|
5317
|
-
...dispatchEntry.event,
|
|
5318
|
-
message: {
|
|
5319
|
-
...dispatchEntry.event.message,
|
|
5320
|
-
...combinedText.trim() ? {
|
|
5321
|
-
message_type: "text",
|
|
5322
|
-
content: JSON.stringify({ text: combinedText })
|
|
5323
|
-
} : {},
|
|
5324
|
-
mentions: mergedMentions ?? dispatchEntry.event.message.mentions
|
|
6196
|
+
try {
|
|
6197
|
+
if (activeEntries.length === 1) {
|
|
6198
|
+
await dispatchFeishuMessage(last.event, resolveFeishuMessageDedupeKey(last.event), last.processingClaim, lifecycle);
|
|
6199
|
+
await settle();
|
|
6200
|
+
return;
|
|
6201
|
+
}
|
|
6202
|
+
const dedupedEntries = dedupeFeishuDebounceEntriesByDedupeKey(activeEntries);
|
|
6203
|
+
const freshEntries = [];
|
|
6204
|
+
for (const entry of dedupedEntries) if (!await hasProcessedMessage(resolveFeishuMessageDedupeKey(entry.event), accountId, log)) freshEntries.push(entry);
|
|
6205
|
+
const dispatchEntry = freshEntries.at(-1);
|
|
6206
|
+
if (!dispatchEntry) {
|
|
6207
|
+
await settle();
|
|
6208
|
+
return;
|
|
5325
6209
|
}
|
|
5326
|
-
|
|
6210
|
+
const dispatchDedupeKey = resolveFeishuMessageDedupeKey(dispatchEntry.event);
|
|
6211
|
+
if (!lifecycle) await recordSuppressedMessageIds(dedupedEntries, dispatchDedupeKey);
|
|
6212
|
+
const combinedText = freshEntries.map((entry) => resolveDebounceText(entry.event)).filter(Boolean).join("\n");
|
|
6213
|
+
const mergedMentions = resolveFeishuDebounceMentions({
|
|
6214
|
+
entries: freshEntries.map((entry) => entry.event),
|
|
6215
|
+
botOpenId: getBotOpenId(accountId)
|
|
6216
|
+
});
|
|
6217
|
+
await dispatchFeishuMessage({
|
|
6218
|
+
...dispatchEntry.event,
|
|
6219
|
+
message: {
|
|
6220
|
+
...dispatchEntry.event.message,
|
|
6221
|
+
...combinedText.trim() ? {
|
|
6222
|
+
message_type: "text",
|
|
6223
|
+
content: JSON.stringify({ text: combinedText })
|
|
6224
|
+
} : {},
|
|
6225
|
+
mentions: mergedMentions ?? dispatchEntry.event.message.mentions
|
|
6226
|
+
}
|
|
6227
|
+
}, dispatchDedupeKey, dispatchEntry.processingClaim, lifecycle);
|
|
6228
|
+
await settle();
|
|
6229
|
+
} catch (err) {
|
|
6230
|
+
await lifecycle?.onAbandoned();
|
|
6231
|
+
throw err;
|
|
6232
|
+
}
|
|
5327
6233
|
},
|
|
5328
6234
|
onError: (err, entries) => {
|
|
5329
|
-
for (const entry of entries)
|
|
6235
|
+
for (const entry of entries) {
|
|
6236
|
+
entry.processingClaim?.release({ error: err });
|
|
6237
|
+
try {
|
|
6238
|
+
Promise.resolve(entry.turnAdoptionLifecycle?.onAbandoned()).catch((abandonError) => {
|
|
6239
|
+
error(`feishu[${accountId}]: failed to abandon durable ingress after debounce error: ${String(abandonError)}`);
|
|
6240
|
+
});
|
|
6241
|
+
} catch (abandonError) {
|
|
6242
|
+
error(`feishu[${accountId}]: failed to abandon durable ingress after debounce error: ${String(abandonError)}`);
|
|
6243
|
+
}
|
|
6244
|
+
}
|
|
5330
6245
|
error(`feishu[${accountId}]: inbound debounce flush failed: ${String(err)}`);
|
|
5331
6246
|
}
|
|
5332
6247
|
});
|
|
5333
6248
|
return async (data) => {
|
|
6249
|
+
const turnAdoptionLifecycle = resolveIngressLifecycle?.(data);
|
|
6250
|
+
const completeSuppressedIngress = async () => {
|
|
6251
|
+
if (!turnAdoptionLifecycle) return;
|
|
6252
|
+
turnAdoptionLifecycle.onAdoptionFinalizing();
|
|
6253
|
+
await turnAdoptionLifecycle.onAdopted();
|
|
6254
|
+
};
|
|
5334
6255
|
statusSink?.({ lastEventAt: Date.now() });
|
|
5335
6256
|
const event = parseFeishuMessageEventPayload(data);
|
|
5336
6257
|
if (!event) {
|
|
6258
|
+
if (turnAdoptionLifecycle) throw new FeishuIngressPermanentError("invalid-event", "Feishu durable message event payload is malformed.");
|
|
5337
6259
|
error(`feishu[${accountId}]: ignoring malformed message event payload`);
|
|
5338
6260
|
return;
|
|
5339
6261
|
}
|
|
@@ -5342,6 +6264,7 @@ function createFeishuMessageReceiveHandler({ cfg, channelRuntime, accountId, run
|
|
|
5342
6264
|
const senderOpenId = event.sender.sender_id.open_id?.trim();
|
|
5343
6265
|
if (botOpenId && senderOpenId === botOpenId) {
|
|
5344
6266
|
log(`feishu[${accountId}]: dropping self-authored message ${messageId ?? "unknown"}`);
|
|
6267
|
+
await completeSuppressedIngress();
|
|
5345
6268
|
return;
|
|
5346
6269
|
}
|
|
5347
6270
|
const claim = await claimUnprocessedFeishuMessage({
|
|
@@ -5351,14 +6274,31 @@ function createFeishuMessageReceiveHandler({ cfg, channelRuntime, accountId, run
|
|
|
5351
6274
|
});
|
|
5352
6275
|
if (claim.kind === "duplicate" || claim.kind === "inflight") {
|
|
5353
6276
|
log(`feishu[${accountId}]: dropping ${claim.kind} event for message ${messageId}`);
|
|
6277
|
+
await completeSuppressedIngress();
|
|
5354
6278
|
return;
|
|
5355
6279
|
}
|
|
6280
|
+
const debounceEntry = {
|
|
6281
|
+
event,
|
|
6282
|
+
...claim.kind === "claimed" ? { processingClaim: claim.handle } : {},
|
|
6283
|
+
...turnAdoptionLifecycle ? { turnAdoptionLifecycle } : {}
|
|
6284
|
+
};
|
|
6285
|
+
if (claim.kind === "claimed" && turnAdoptionLifecycle) turnAdoptionLifecycle.registerAbandonHandler?.(() => {
|
|
6286
|
+
debounceEntry.abandoned = true;
|
|
6287
|
+
claim.handle.release({ error: /* @__PURE__ */ new Error("feishu-ingress-abandoned-before-flush") });
|
|
6288
|
+
});
|
|
5356
6289
|
const processMessage = async () => {
|
|
5357
|
-
await inboundDebouncer.enqueue(
|
|
5358
|
-
event,
|
|
5359
|
-
...claim.kind === "claimed" ? { processingClaim: claim.handle } : {}
|
|
5360
|
-
});
|
|
6290
|
+
await inboundDebouncer.enqueue(debounceEntry);
|
|
5361
6291
|
};
|
|
6292
|
+
if (turnAdoptionLifecycle) try {
|
|
6293
|
+
await processMessage();
|
|
6294
|
+
return { kind: "deferred" };
|
|
6295
|
+
} catch (err) {
|
|
6296
|
+
if (claim.kind === "claimed") claim.handle.release({ error: err });
|
|
6297
|
+
return {
|
|
6298
|
+
kind: "failed-retryable",
|
|
6299
|
+
error: err
|
|
6300
|
+
};
|
|
6301
|
+
}
|
|
5362
6302
|
if (fireAndForget) {
|
|
5363
6303
|
processMessage().catch((err) => {
|
|
5364
6304
|
if (claim.kind === "claimed") claim.handle.release({ error: err });
|
|
@@ -5468,7 +6408,7 @@ function waitForFeishuWsCycleEnd(params) {
|
|
|
5468
6408
|
params.terminalError.then(finish);
|
|
5469
6409
|
});
|
|
5470
6410
|
}
|
|
5471
|
-
async function monitorWebSocket({ account, accountId, runtime, abortSignal, eventDispatcher, statusSink }) {
|
|
6411
|
+
async function monitorWebSocket({ account, accountId, runtime, abortSignal, eventDispatcher, setSocketTerminator, statusSink }) {
|
|
5472
6412
|
const log = runtime?.log ?? console.log;
|
|
5473
6413
|
const error = runtime?.error ?? console.error;
|
|
5474
6414
|
let attempt = 0;
|
|
@@ -5509,6 +6449,7 @@ async function monitorWebSocket({ account, accountId, runtime, abortSignal, even
|
|
|
5509
6449
|
onReconnected: publishWsConnected,
|
|
5510
6450
|
onReconnecting: publishWsReconnecting
|
|
5511
6451
|
});
|
|
6452
|
+
setSocketTerminator?.(() => wsClient?.close({ force: true }));
|
|
5512
6453
|
if (abortSignal?.aborted) {
|
|
5513
6454
|
cleanupFeishuWsClient({
|
|
5514
6455
|
accountId,
|
|
@@ -5534,6 +6475,7 @@ async function monitorWebSocket({ account, accountId, runtime, abortSignal, even
|
|
|
5534
6475
|
error,
|
|
5535
6476
|
clearIdentity: true
|
|
5536
6477
|
});
|
|
6478
|
+
setSocketTerminator?.(void 0);
|
|
5537
6479
|
return;
|
|
5538
6480
|
}
|
|
5539
6481
|
cleanupFeishuWsClient({
|
|
@@ -5542,6 +6484,7 @@ async function monitorWebSocket({ account, accountId, runtime, abortSignal, even
|
|
|
5542
6484
|
error,
|
|
5543
6485
|
clearIdentity: false
|
|
5544
6486
|
});
|
|
6487
|
+
setSocketTerminator?.(void 0);
|
|
5545
6488
|
if (abortSignal?.aborted) break;
|
|
5546
6489
|
statusSink?.({
|
|
5547
6490
|
connected: false,
|
|
@@ -5558,6 +6501,7 @@ async function monitorWebSocket({ account, accountId, runtime, abortSignal, even
|
|
|
5558
6501
|
error,
|
|
5559
6502
|
clearIdentity: false
|
|
5560
6503
|
});
|
|
6504
|
+
setSocketTerminator?.(void 0);
|
|
5561
6505
|
if (abortSignal?.aborted) break;
|
|
5562
6506
|
statusSink?.({
|
|
5563
6507
|
connected: false,
|
|
@@ -5575,6 +6519,7 @@ async function monitorWebSocket({ account, accountId, runtime, abortSignal, even
|
|
|
5575
6519
|
error,
|
|
5576
6520
|
clearIdentity: true
|
|
5577
6521
|
});
|
|
6522
|
+
setSocketTerminator?.(void 0);
|
|
5578
6523
|
}
|
|
5579
6524
|
async function monitorWebhook({ account, accountId, runtime, abortSignal, eventDispatcher, statusSink }) {
|
|
5580
6525
|
const log = runtime?.log ?? console.log;
|
|
@@ -5829,7 +6774,7 @@ async function resolveReactionSyntheticEvent(params) {
|
|
|
5829
6774
|
const senderId = event.user_id?.open_id;
|
|
5830
6775
|
const senderUserId = event.user_id?.user_id;
|
|
5831
6776
|
if (!emoji || !messageId || !senderId) return null;
|
|
5832
|
-
const { resolveFeishuAccount } = await import("./accounts-
|
|
6777
|
+
const { resolveFeishuAccount } = await import("./accounts-CKhCa76b.js").then((n) => n.t);
|
|
5833
6778
|
const reactionNotifications = resolveFeishuAccount({
|
|
5834
6779
|
cfg,
|
|
5835
6780
|
accountId
|
|
@@ -5882,41 +6827,41 @@ function normalizeFeishuChatType(value) {
|
|
|
5882
6827
|
return value === "group" || value === "topic_group" || value === "private" || value === "p2p" ? value : void 0;
|
|
5883
6828
|
}
|
|
5884
6829
|
function parseFeishuBotAddedEventPayload(value) {
|
|
5885
|
-
if (!isRecord$
|
|
6830
|
+
if (!isRecord$2(value) || !readString$1(value.chat_id) || !isRecord$2(value.operator_id)) return null;
|
|
5886
6831
|
return value;
|
|
5887
6832
|
}
|
|
5888
6833
|
function parseFeishuBotRemovedChatId(value) {
|
|
5889
|
-
if (!isRecord$
|
|
5890
|
-
return readString(value.chat_id) ?? null;
|
|
6834
|
+
if (!isRecord$2(value)) return null;
|
|
6835
|
+
return readString$1(value.chat_id) ?? null;
|
|
5891
6836
|
}
|
|
5892
6837
|
function firstString(...values) {
|
|
5893
6838
|
for (const value of values) {
|
|
5894
|
-
const trimmed = readString(value)?.trim();
|
|
6839
|
+
const trimmed = readString$1(value)?.trim();
|
|
5895
6840
|
if (trimmed) return trimmed;
|
|
5896
6841
|
}
|
|
5897
6842
|
}
|
|
5898
6843
|
function readFeishuIdentityField(value, field) {
|
|
5899
|
-
if (!isRecord$
|
|
6844
|
+
if (!isRecord$2(value)) return;
|
|
5900
6845
|
return firstString(value[field]);
|
|
5901
6846
|
}
|
|
5902
6847
|
function parseFeishuCardActionEventPayload(value) {
|
|
5903
|
-
if (!isRecord$
|
|
5904
|
-
const operator = isRecord$
|
|
6848
|
+
if (!isRecord$2(value)) return null;
|
|
6849
|
+
const operator = isRecord$2(value.operator) ? value.operator : {};
|
|
5905
6850
|
const action = value.action;
|
|
5906
|
-
const context = isRecord$
|
|
5907
|
-
if (!isRecord$
|
|
6851
|
+
const context = isRecord$2(value.context) ? value.context : {};
|
|
6852
|
+
if (!isRecord$2(action)) return null;
|
|
5908
6853
|
const operatorUserId = operator.user_id;
|
|
5909
|
-
const token = readString(value.token);
|
|
6854
|
+
const token = readString$1(value.token);
|
|
5910
6855
|
const openId = firstString(operator.open_id, readFeishuIdentityField(operatorUserId, "open_id"), value.open_id, context.open_id);
|
|
5911
6856
|
const userId = firstString(operator.user_id, readFeishuIdentityField(operatorUserId, "user_id"), value.user_id, context.user_id);
|
|
5912
6857
|
const unionId = firstString(operator.union_id, readFeishuIdentityField(operatorUserId, "union_id"));
|
|
5913
|
-
const tag = readString(action.tag);
|
|
6858
|
+
const tag = readString$1(action.tag);
|
|
5914
6859
|
const actionValue = action.value;
|
|
5915
6860
|
const openMessageId = firstString(context.open_message_id, value.open_message_id);
|
|
5916
6861
|
const contextOpenId = firstString(context.open_id, openId);
|
|
5917
6862
|
const contextUserId = firstString(context.user_id, userId);
|
|
5918
6863
|
const chatId = firstString(context.chat_id, context.open_chat_id);
|
|
5919
|
-
if (!token || !openId || !tag || !isRecord$
|
|
6864
|
+
if (!token || !openId || !tag || !isRecord$2(actionValue)) return null;
|
|
5920
6865
|
return {
|
|
5921
6866
|
operator: {
|
|
5922
6867
|
open_id: openId,
|
|
@@ -5968,6 +6913,7 @@ function registerEventHandlers(eventDispatcher, context) {
|
|
|
5968
6913
|
getBotOpenId: (id) => botOpenIds.get(id),
|
|
5969
6914
|
getBotName: (id) => botNames.get(id),
|
|
5970
6915
|
resolveSequentialKey: getFeishuSequentialKey,
|
|
6916
|
+
resolveIngressLifecycle: context.resolveIngressLifecycle,
|
|
5971
6917
|
...context.statusSink ? { statusSink: context.statusSink } : {}
|
|
5972
6918
|
}),
|
|
5973
6919
|
"im.message.message_read_v1": async () => {},
|
|
@@ -5995,7 +6941,8 @@ function registerEventHandlers(eventDispatcher, context) {
|
|
|
5995
6941
|
accountId,
|
|
5996
6942
|
runtime,
|
|
5997
6943
|
fireAndForget,
|
|
5998
|
-
abortSignal
|
|
6944
|
+
abortSignal,
|
|
6945
|
+
resolveIngressLifecycle: context.resolveIngressLifecycle
|
|
5999
6946
|
}),
|
|
6000
6947
|
"vc.bot.meeting_invited_v1": createFeishuVcMeetingInvitedHandler({
|
|
6001
6948
|
cfg,
|
|
@@ -6098,19 +7045,22 @@ async function monitorSingleAccount(params) {
|
|
|
6098
7045
|
const { accountId } = account;
|
|
6099
7046
|
const log = runtime?.log ?? console.log;
|
|
6100
7047
|
const botOpenIdSource = params.botOpenIdSource ?? { kind: "fetch" };
|
|
6101
|
-
const
|
|
7048
|
+
const botIdentity = botOpenIdSource.kind === "prefetched" ? {
|
|
6102
7049
|
botOpenId: botOpenIdSource.botOpenId,
|
|
6103
|
-
botName: botOpenIdSource.botName
|
|
7050
|
+
botName: botOpenIdSource.botName,
|
|
7051
|
+
source: botOpenIdSource.source
|
|
6104
7052
|
} : await fetchBotIdentityForMonitor(account, {
|
|
6105
7053
|
runtime,
|
|
6106
7054
|
abortSignal
|
|
6107
|
-
})
|
|
7055
|
+
});
|
|
7056
|
+
const { botOpenId } = applyBotIdentityState(accountId, botIdentity);
|
|
6108
7057
|
log(`feishu[${accountId}]: bot open_id resolved: ${botOpenId ?? "unknown"}`);
|
|
6109
|
-
if (!botOpenId && !abortSignal?.aborted) startBotIdentityRecovery({
|
|
7058
|
+
if ((!botOpenId || botIdentity.source === "cache") && !abortSignal?.aborted) startBotIdentityRecovery({
|
|
6110
7059
|
account,
|
|
6111
7060
|
accountId,
|
|
6112
7061
|
runtime,
|
|
6113
|
-
abortSignal
|
|
7062
|
+
abortSignal,
|
|
7063
|
+
currentSource: botIdentity.source
|
|
6114
7064
|
});
|
|
6115
7065
|
const connectionMode = account.config.connectionMode ?? "websocket";
|
|
6116
7066
|
if (connectionMode === "webhook" && !account.verificationToken?.trim()) throw new Error(`Feishu account "${accountId}" webhook mode requires verificationToken`);
|
|
@@ -6120,6 +7070,13 @@ async function monitorSingleAccount(params) {
|
|
|
6120
7070
|
let threadBindingManager;
|
|
6121
7071
|
try {
|
|
6122
7072
|
const eventDispatcher = createEventDispatcher(account);
|
|
7073
|
+
const durableIngress = typeof eventDispatcher.invoke === "function" ? createFeishuDurableIngress({
|
|
7074
|
+
accountId,
|
|
7075
|
+
dispatcher: eventDispatcher,
|
|
7076
|
+
...account.encryptKey ? { encryptKey: account.encryptKey } : {},
|
|
7077
|
+
runtime: runtime ?? {}
|
|
7078
|
+
}) : void 0;
|
|
7079
|
+
const durableEventDispatcher = durableIngress ? Object.assign(Object.create(eventDispatcher), { invoke: durableIngress.invoke }) : eventDispatcher;
|
|
6123
7080
|
const chatHistories = /* @__PURE__ */ new Map();
|
|
6124
7081
|
threadBindingManager = createFeishuThreadBindingManager({
|
|
6125
7082
|
accountId,
|
|
@@ -6134,24 +7091,31 @@ async function monitorSingleAccount(params) {
|
|
|
6134
7091
|
fireAndForget: params.fireAndForget ?? true,
|
|
6135
7092
|
vcAutoJoin: account.config.vcAutoJoin === true,
|
|
6136
7093
|
abortSignal,
|
|
7094
|
+
...durableIngress ? { resolveIngressLifecycle: durableIngress.resolveLifecycle } : {},
|
|
6137
7095
|
...params.statusSink ? { statusSink: params.statusSink } : {}
|
|
6138
7096
|
});
|
|
6139
|
-
|
|
6140
|
-
|
|
6141
|
-
|
|
6142
|
-
|
|
6143
|
-
|
|
6144
|
-
|
|
6145
|
-
|
|
6146
|
-
|
|
6147
|
-
|
|
6148
|
-
|
|
6149
|
-
|
|
6150
|
-
|
|
6151
|
-
|
|
6152
|
-
|
|
6153
|
-
|
|
6154
|
-
|
|
7097
|
+
durableIngress?.start();
|
|
7098
|
+
try {
|
|
7099
|
+
if (connectionMode === "webhook") return await monitorWebhook({
|
|
7100
|
+
account,
|
|
7101
|
+
accountId,
|
|
7102
|
+
runtime,
|
|
7103
|
+
abortSignal,
|
|
7104
|
+
eventDispatcher: durableEventDispatcher,
|
|
7105
|
+
...params.statusSink ? { statusSink: params.statusSink } : {}
|
|
7106
|
+
});
|
|
7107
|
+
return await monitorWebSocket({
|
|
7108
|
+
account,
|
|
7109
|
+
accountId,
|
|
7110
|
+
runtime,
|
|
7111
|
+
abortSignal,
|
|
7112
|
+
eventDispatcher: durableEventDispatcher,
|
|
7113
|
+
...durableIngress ? { setSocketTerminator: durableIngress.setSocketTerminator } : {},
|
|
7114
|
+
...params.statusSink ? { statusSink: params.statusSink } : {}
|
|
7115
|
+
});
|
|
7116
|
+
} finally {
|
|
7117
|
+
await durableIngress?.stop();
|
|
7118
|
+
}
|
|
6155
7119
|
} finally {
|
|
6156
7120
|
threadBindingManager?.stop();
|
|
6157
7121
|
}
|