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