@openclaw/feishu 2026.7.2-beta.7 → 2026.8.1-beta.3
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/accounts-CCCdMen2.js +203 -0
- package/dist/api.js +61 -60
- package/dist/{channel-CScE82zY.js → channel-Ceq80_zP.js} +71 -42
- package/dist/channel-plugin-api.js +1 -1
- package/dist/{channel.runtime-B21e1E0r.js → channel.runtime-DnRyzGoU.js} +201 -152
- package/dist/{client-Dcbs6vml.js → client-Hp7uo_cl.js} +27 -16
- package/dist/contract-api.js +2 -2
- package/dist/{conversation-id-BeJL-wq7.js → conversation-id-VYgGQ-GX.js} +11 -15
- package/dist/doctor-contract-api.js +1 -1
- package/dist/doctor-contract-bEQXIXyP.js +158 -0
- package/dist/{drive-hSi_Utp0.js → drive-BVYM_Y4k.js} +325 -44
- package/dist/{media-CEUraFOR.js → media-BDouVaaM.js} +298 -182
- package/dist/{monitor-DngbaA6a.js → monitor-CZ8cDXrq.js} +3 -3
- package/dist/{monitor.account-BnTWCw55.js → monitor.account-Dq_voVMB.js} +263 -179
- package/dist/{monitor.startup-CqH2tiJq.js → monitor.startup-BGErejNH.js} +1 -1
- package/dist/{probe-p3POS2RN.js → probe-DVpy58s0.js} +2 -2
- package/dist/security-audit-D6Fz2h6p.js +23 -0
- package/dist/{send-result-B9_BpUPx.js → send-result-DEAycmOk.js} +18 -9
- package/dist/session-binding-contract-api.js +2 -2
- package/dist/{session-conversation-BksWrfzm.js → session-conversation-DFCIvQK-.js} +1 -1
- package/dist/session-key-api.js +1 -1
- package/dist/setup-api.js +1 -1
- package/dist/{subagent-hooks-Cx1cX7rW.js → subagent-hooks-KUi-7vUy.js} +4 -64
- package/dist/subagent-hooks-api.js +1 -1
- package/dist/{thread-bindings-N3wkgkIN.js → thread-bindings-CptM_YU6.js} +7 -9
- package/openclaw.plugin.json +143 -1
- package/package.json +4 -4
- package/skills/feishu-doc/SKILL.md +20 -195
- package/skills/feishu-doc/references/block-types.md +9 -14
- package/skills/feishu-drive/SKILL.md +16 -102
- package/skills/feishu-perm/SKILL.md +10 -110
- package/skills/feishu-wiki/SKILL.md +13 -109
- package/dist/accounts-u9X5Wsan.js +0 -469
- package/dist/doctor-contract-BiD9tyIv.js +0 -102
- package/dist/security-audit-D7WK_BHh.js +0 -11
|
@@ -1,21 +1,24 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { o as resolveFeishuAccount, s as resolveFeishuRuntimeAccount } from "./accounts-CCCdMen2.js";
|
|
2
2
|
import { i as resolveReceiveIdType } from "./targets-BUjQ1TcA.js";
|
|
3
|
-
import { A as resolveFeishuChatType, C as resolveFeishuDmIngressAccess, E as resolveFeishuGroupSenderActivationIngressAccess, M as buildFeishuCardActionTextFallback, N as createFeishuCardInteractionEnvelope, O as resolveFeishuReplyPolicy, P as decodeFeishuCardAction, S as normalizeFeishuAllowEntry, T as resolveFeishuGroupConversationIngressAccess, d as chunkFeishuPostMarkdown, f as materializeFeishuPostMarkdownSoftBreaks, k as normalizeFeishuChatType$2, o as resolveFeishuCardTemplate, w as resolveFeishuGroupConfig, x as hasExplicitFeishuGroupConfig } from "./send-result-
|
|
4
|
-
import {
|
|
5
|
-
import {
|
|
3
|
+
import { A as resolveFeishuChatType, C as resolveFeishuDmIngressAccess, E as resolveFeishuGroupSenderActivationIngressAccess, M as buildFeishuCardActionTextFallback, N as createFeishuCardInteractionEnvelope, O as resolveFeishuReplyPolicy, P as decodeFeishuCardAction, S as normalizeFeishuAllowEntry, T as resolveFeishuGroupConversationIngressAccess, d as chunkFeishuPostMarkdown, f as materializeFeishuPostMarkdownSoftBreaks, k as normalizeFeishuChatType$2, o as resolveFeishuCardTemplate, w as resolveFeishuGroupConfig, x as hasExplicitFeishuGroupConfig } from "./send-result-DEAycmOk.js";
|
|
4
|
+
import { i as normalizeFeishuWebhookPath } from "./doctor-contract-bEQXIXyP.js";
|
|
5
|
+
import { o as resolveConfiguredFeishuGroupSessionScope, t as buildFeishuConversationId } from "./conversation-id-VYgGQ-GX.js";
|
|
6
|
+
import { a as getFeishuUserAgent, i as createFeishuWSClient, n as createEventDispatcher, r as createFeishuClient, s as resolveConfiguredHttpTimeoutMs } from "./client-Hp7uo_cl.js";
|
|
6
7
|
import { t as getFeishuRuntime } from "./runtime-C5JxBWZp.js";
|
|
7
|
-
import { i as createCommentTypingReactionLifecycle,
|
|
8
|
-
import { t as createFeishuThreadBindingManager } from "./thread-bindings-
|
|
9
|
-
import { createReplyPrefixContext, evaluateSupplementalContextVisibility, getSessionEntry, normalizeAgentId as normalizeAgentId$
|
|
8
|
+
import { d as encodeQuery, f as extractReplyText, g as normalizeCommentFileType, h as buildFeishuCommentTarget, i as createCommentTypingReactionLifecycle, m as requestFeishuApi, p as parseCommentContentElements, s as getChatInfo, t as deliverCommentThreadText } from "./drive-BVYM_Y4k.js";
|
|
9
|
+
import { t as createFeishuThreadBindingManager } from "./thread-bindings-CptM_YU6.js";
|
|
10
|
+
import { createReplyPrefixContext, evaluateSupplementalContextVisibility, getSessionEntry, normalizeAgentId as normalizeAgentId$1, resolveChannelContextVisibilityMode } from "./runtime-api.js";
|
|
10
11
|
import { t as readFeishuJsonResponse } from "./json-response-CheljwGr.js";
|
|
11
|
-
import { _ as
|
|
12
|
-
import { a as waitForAbortableDelay, i as raceWithTimeoutAndAbort } from "./probe-
|
|
13
|
-
import { t as fetchBotIdentityForMonitor } from "./monitor.startup-
|
|
12
|
+
import { _ as normalizeFeishuExternalKey, a as getMessageFeishu, d as extractMentionTargets, f as isFeishuBroadcastMention, g as parsePostContent, h as parseInteractiveCardContent, l as sendMessageFeishu, m as isFeishuGroupChatType, n as sendMediaFeishu, o as listFeishuThreadMessages, p as isMentionForwardRequest, r as shouldSuppressFeishuTextForVoiceMedia, s as sendCardFeishu, t as saveMessageResourceFeishu, u as sendStructuredCardFeishu, v as buildFeishuMediaFallbackText, y as resolveFeishuIdentityEmoji } from "./media-BDouVaaM.js";
|
|
13
|
+
import { a as waitForAbortableDelay, i as raceWithTimeoutAndAbort } from "./probe-DVpy58s0.js";
|
|
14
|
+
import { t as fetchBotIdentityForMonitor } from "./monitor.startup-BGErejNH.js";
|
|
15
|
+
import { resolveAgentConfig } from "openclaw/plugin-sdk/agent-scope-runtime";
|
|
14
16
|
import { DEFAULT_INGRESS_ADOPTION_STALL_MS, bindIngressLifecycleToReplyOptions, createChannelIngressError, createChannelIngressMonitor, createChannelMessageReplyPipeline, createMessageReceiptFromOutboundResults, formatChannelProgressDraftLineForEntry, isChannelProgressDraftWorkToolName, resolveAgentOutboundIdentity, resolveChannelPreviewStreamMode, resolveChannelStreamingBlockEnabled } from "openclaw/plugin-sdk/channel-outbound";
|
|
15
17
|
import { createChannelPairingController, createChannelPairingController as createChannelPairingController$1 } from "openclaw/plugin-sdk/channel-pairing";
|
|
16
18
|
import { ensureConfiguredBindingRouteReady, resolveConfiguredBindingRoute, resolveRuntimeConversationBindingRoute } from "openclaw/plugin-sdk/conversation-runtime";
|
|
19
|
+
import { collectErrorGraphCandidates, formatErrorMessage, toStringifiedError } from "openclaw/plugin-sdk/error-runtime";
|
|
17
20
|
import { asDateTimestampMs, isFutureDateTimestampMs, parseStrictNonNegativeInteger, resolveDateTimestampMs, resolveExpiresAtMsFromDurationMs, resolveExpiresAtMsFromDurationSeconds, resolveTimerTimeoutMs } from "openclaw/plugin-sdk/number-runtime";
|
|
18
|
-
import { asBoolean, asNullableRecord, isRecord, normalizeLowercaseStringOrEmpty, normalizeOptionalLowercaseString, normalizeOptionalString, readStringValue, uniqueStrings } from "openclaw/plugin-sdk/string-coerce-runtime";
|
|
21
|
+
import { asBoolean, asNullableRecord, isRecord, normalizeLowercaseStringOrEmpty, normalizeNullableString, normalizeOptionalLowercaseString, normalizeOptionalString, readStringValue, uniqueStrings } from "openclaw/plugin-sdk/string-coerce-runtime";
|
|
19
22
|
import { stripReasoningTagsFromText } from "openclaw/plugin-sdk/text-chunking";
|
|
20
23
|
import { resolveDefaultGroupPolicy, resolveOpenProviderRuntimeGroupPolicy, warnMissingProviderGroupPolicyFallbackOnce } from "openclaw/plugin-sdk/runtime-group-policy";
|
|
21
24
|
import { fanInChannelIngressLifecycles } from "openclaw/plugin-sdk/channel-ingress-runtime";
|
|
@@ -25,24 +28,24 @@ import path from "node:path";
|
|
|
25
28
|
import { normalizeAccountId, resolveAgentRoute, resolveInboundLastRouteSessionKey } from "openclaw/plugin-sdk/routing";
|
|
26
29
|
import { resolveStorePath } from "openclaw/plugin-sdk/session-store-runtime";
|
|
27
30
|
import { isRecord as isRecord$1 } from "openclaw/plugin-sdk/channel-secret-basic-runtime";
|
|
28
|
-
import {
|
|
31
|
+
import { createChannelPartialDeliveryError, formatAgentEnvelope, formatInboundMediaUnavailableText, isChannelPartialDeliveryError, recordChannelBotPairLoopAndCheckSuppression, resolveEnvelopeFormatOptions, resolveInboundReplyDispatchCounts, toInboundMediaFactsWithMetadata } from "openclaw/plugin-sdk/channel-inbound";
|
|
29
32
|
import * as Lark from "@larksuiteoapi/node-sdk";
|
|
30
|
-
import {
|
|
33
|
+
import { resolvePinnedMainDmOwnerFromAllowlist, safeEqualSecret } from "openclaw/plugin-sdk/security-runtime";
|
|
31
34
|
import { WEBHOOK_ANOMALY_COUNTER_DEFAULTS, WEBHOOK_RATE_LIMIT_DEFAULTS, applyBasicWebhookRequestGuards, createFixedWindowRateLimiter, createWebhookAnomalyTracker, resolveRequestClientIp } from "openclaw/plugin-sdk/webhook-ingress";
|
|
32
35
|
import { fetchWithSsrFGuard } from "openclaw/plugin-sdk/ssrf-runtime";
|
|
33
36
|
import { getReplyPayloadTtsSupplement, resolveSendableOutboundReplyParts, resolveTextChunksWithFallback, sendMediaWithLeadingCaption } from "openclaw/plugin-sdk/reply-payload";
|
|
34
|
-
import { resolvePinnedMainDmOwnerFromAllowlist, safeEqualSecret } from "openclaw/plugin-sdk/security-runtime";
|
|
35
37
|
import * as crypto$1 from "node:crypto";
|
|
36
38
|
import crypto, { createHash } from "node:crypto";
|
|
37
|
-
import { buildChannelInboundEventContext, createChannelPartialDeliveryError, formatAgentEnvelope, formatInboundMediaUnavailableText, isChannelPartialDeliveryError, recordChannelBotPairLoopAndCheckSuppression, resolveEnvelopeFormatOptions, toInboundMediaFactsWithMetadata } from "openclaw/plugin-sdk/channel-inbound";
|
|
38
39
|
import { DEFAULT_GROUP_HISTORY_LIMIT, createChannelHistoryWindow } from "openclaw/plugin-sdk/reply-history";
|
|
39
40
|
import { sliceUtf16Safe, truncateUtf16Safe } from "openclaw/plugin-sdk/text-utility-runtime";
|
|
40
41
|
import { pruneMapToMaxSize } from "openclaw/plugin-sdk/collection-runtime";
|
|
41
42
|
import { createChannelReplayGuard } from "openclaw/plugin-sdk/persistent-dedupe";
|
|
42
43
|
import { resolveChannelConfigWrites } from "openclaw/plugin-sdk/channel-config-writes";
|
|
44
|
+
import { formatReasoningMessage, resolveHumanDelayConfig } from "openclaw/plugin-sdk/agent-runtime";
|
|
43
45
|
import { logTypingFailure } from "openclaw/plugin-sdk/channel-feedback";
|
|
44
46
|
import { getGlobalHookRunner } from "openclaw/plugin-sdk/plugin-runtime";
|
|
45
47
|
import * as http from "node:http";
|
|
48
|
+
import { channelBlockedPatch, channelReadyPatch } from "openclaw/plugin-sdk/gateway-runtime";
|
|
46
49
|
import { installRequestBodyLimitGuard, readWebhookBodyOrReject } from "openclaw/plugin-sdk/webhook-request-guards";
|
|
47
50
|
import { isAbortRequestText, isBtwRequestText } from "openclaw/plugin-sdk/command-primitives-runtime";
|
|
48
51
|
//#region extensions/feishu/src/bot-agent-body.ts
|
|
@@ -321,6 +324,7 @@ function parseMessageContent(content, messageType) {
|
|
|
321
324
|
return "[Forwarded message]";
|
|
322
325
|
}
|
|
323
326
|
if (messageType === "merge_forward") return "[Merged and Forwarded Message - loading...]";
|
|
327
|
+
if (messageType === "interactive") return parseInteractiveCardContent(parsed);
|
|
324
328
|
return content;
|
|
325
329
|
} catch {
|
|
326
330
|
return FEISHU_MEDIA_MESSAGE_TYPES.has(messageType) ? "" : content;
|
|
@@ -345,6 +349,7 @@ function formatSubMessageContent(content, contentType) {
|
|
|
345
349
|
switch (contentType) {
|
|
346
350
|
case "text": return parsed.text || content;
|
|
347
351
|
case "post": return parsePostContent(content).textContent;
|
|
352
|
+
case "interactive": return parseInteractiveCardContent(parsed);
|
|
348
353
|
case "image": return "[Image]";
|
|
349
354
|
case "file": return `[File: ${parsed.file_name || "unknown"}]`;
|
|
350
355
|
case "audio": return "[Audio]";
|
|
@@ -369,7 +374,8 @@ function parseMergeForwardContent(params) {
|
|
|
369
374
|
return "[Merged and Forwarded Message - parse error]";
|
|
370
375
|
}
|
|
371
376
|
if (!Array.isArray(items) || items.length === 0) return "[Merged and Forwarded Message - no sub-messages]";
|
|
372
|
-
const
|
|
377
|
+
const container = items.find((item) => item.msg_type === "merge_forward" && !item.upper_message_id);
|
|
378
|
+
const subMessages = container ? items.filter((item) => item !== container) : items.filter((item) => item.upper_message_id);
|
|
373
379
|
if (subMessages.length === 0) return "[Merged and Forwarded Message - no sub-messages found]";
|
|
374
380
|
log?.(`feishu: merge_forward contains ${subMessages.length} sub-messages`);
|
|
375
381
|
subMessages.sort((a, b) => (parseStrictNonNegativeInteger(a.create_time) ?? 0) - (parseStrictNonNegativeInteger(b.create_time) ?? 0));
|
|
@@ -1133,13 +1139,8 @@ function resolveUserPath(p) {
|
|
|
1133
1139
|
}
|
|
1134
1140
|
//#endregion
|
|
1135
1141
|
//#region extensions/feishu/src/agent-config.ts
|
|
1136
|
-
const DEFAULT_AGENT_ID = "main";
|
|
1137
|
-
function normalizeAgentId$1(value) {
|
|
1138
|
-
return (value ?? "").trim().toLowerCase() || DEFAULT_AGENT_ID;
|
|
1139
|
-
}
|
|
1140
1142
|
function resolveFeishuConfigReasoningDefault(cfg, agentId) {
|
|
1141
|
-
|
|
1142
|
-
return cfg.agents?.list?.find((entry) => normalizeAgentId$1(entry?.id) === id)?.reasoningDefault ?? cfg.agents?.defaults?.reasoningDefault ?? "off";
|
|
1143
|
+
return resolveAgentConfig(cfg, agentId)?.reasoningDefault ?? cfg.agents?.defaults?.reasoningDefault ?? "off";
|
|
1143
1144
|
}
|
|
1144
1145
|
//#endregion
|
|
1145
1146
|
//#region extensions/feishu/src/reasoning-preview.ts
|
|
@@ -1185,10 +1186,11 @@ function createFeishuReplyDeliveryResult(params) {
|
|
|
1185
1186
|
/** Preserves the first result's provider identity while retaining supplemental ids. */
|
|
1186
1187
|
function mergeFeishuReplyDeliveryResults(results, content) {
|
|
1187
1188
|
const visible = results.filter((result) => result.visibleReplySent === true);
|
|
1189
|
+
const acceptedContent = visible.flatMap((result) => result.content === void 0 ? [] : [result.content]);
|
|
1188
1190
|
return createFeishuReplyDeliveryResult({
|
|
1189
1191
|
results: visible,
|
|
1190
1192
|
visibleReplySent: visible.length > 0,
|
|
1191
|
-
content: content === void 0 ?
|
|
1193
|
+
content: content === void 0 ? acceptedContent.length > 0 ? acceptedContent.join("\n\n") : void 0 : content
|
|
1192
1194
|
});
|
|
1193
1195
|
}
|
|
1194
1196
|
/** Keeps a failed outcome while exposing the provider-visible subset to core lifecycle hooks. */
|
|
@@ -1242,8 +1244,14 @@ function resolveAllowedHostnames(domain) {
|
|
|
1242
1244
|
}
|
|
1243
1245
|
return ["open.feishu.cn"];
|
|
1244
1246
|
}
|
|
1247
|
+
function cancelUnreadResponseBody(response) {
|
|
1248
|
+
if (!response.bodyUsed) response.body?.cancel().catch(() => void 0);
|
|
1249
|
+
}
|
|
1245
1250
|
async function assertSuccessfulCardKitResponse(response, auditContext, action) {
|
|
1246
|
-
if (!response.ok)
|
|
1251
|
+
if (!response.ok) {
|
|
1252
|
+
cancelUnreadResponseBody(response);
|
|
1253
|
+
throw new Error(`${action} failed with HTTP ${response.status}`);
|
|
1254
|
+
}
|
|
1247
1255
|
const data = await readFeishuJsonResponse(response, auditContext);
|
|
1248
1256
|
if (data.code !== 0) throw new Error(`${action} failed: ${data.msg ?? "unknown error"} (code=${String(data.code)})`);
|
|
1249
1257
|
}
|
|
@@ -1276,7 +1284,10 @@ async function getToken(creds, deps) {
|
|
|
1276
1284
|
});
|
|
1277
1285
|
let data;
|
|
1278
1286
|
try {
|
|
1279
|
-
if (!response.ok)
|
|
1287
|
+
if (!response.ok) {
|
|
1288
|
+
cancelUnreadResponseBody(response);
|
|
1289
|
+
throw new Error(`Token request failed with HTTP ${response.status}`);
|
|
1290
|
+
}
|
|
1280
1291
|
data = await readFeishuJsonResponse(response, "feishu.streaming-card.token");
|
|
1281
1292
|
} finally {
|
|
1282
1293
|
await release();
|
|
@@ -1384,7 +1395,10 @@ var FeishuStreamingSession = class {
|
|
|
1384
1395
|
});
|
|
1385
1396
|
let createData;
|
|
1386
1397
|
try {
|
|
1387
|
-
if (!createRes.ok)
|
|
1398
|
+
if (!createRes.ok) {
|
|
1399
|
+
cancelUnreadResponseBody(createRes);
|
|
1400
|
+
throw new Error(`Create card request failed with HTTP ${createRes.status}`);
|
|
1401
|
+
}
|
|
1388
1402
|
createData = await readFeishuJsonResponse(createRes, "feishu.streaming-card.create");
|
|
1389
1403
|
} finally {
|
|
1390
1404
|
await releaseCreate();
|
|
@@ -1821,9 +1835,6 @@ function mergeStreamingFinalText(previousText, nextText, appendError) {
|
|
|
1821
1835
|
if (previousText.endsWith(`\n\n${nextText}`)) return previousText;
|
|
1822
1836
|
return `${previousText}\n\n${nextText}`;
|
|
1823
1837
|
}
|
|
1824
|
-
function toError(error) {
|
|
1825
|
-
return error instanceof Error ? error : new Error(String(error));
|
|
1826
|
-
}
|
|
1827
1838
|
/** Maximum age (ms) for a message to receive a typing indicator reaction.
|
|
1828
1839
|
* Messages older than this are likely replays after context compaction (#30418). */
|
|
1829
1840
|
const TYPING_INDICATOR_MAX_AGE_MS = 2 * 6e4;
|
|
@@ -2191,9 +2202,10 @@ function createFeishuReplyDispatcher(params) {
|
|
|
2191
2202
|
};
|
|
2192
2203
|
const updateStreamingStatusLine = (nextStatusLine, options) => {
|
|
2193
2204
|
statusLine = nextStatusLine;
|
|
2194
|
-
if (!Boolean(streaming?.isActive() || streamingStartPromise) && (options?.startIfNeeded === false || renderMode !== "card")) return;
|
|
2205
|
+
if (!Boolean(streaming?.isActive() || streamingStartPromise) && (options?.startIfNeeded === false || renderMode !== "card")) return false;
|
|
2195
2206
|
startStreaming();
|
|
2196
2207
|
flushStreamingCardUpdate(buildCombinedStreamText(reasoningText, streamText));
|
|
2208
|
+
return false;
|
|
2197
2209
|
};
|
|
2198
2210
|
const sendChunkedTextReply = async (paramsLocal) => {
|
|
2199
2211
|
const chunkSource = paramsLocal.useCard ? paramsLocal.text : materializeFeishuPostMarkdownSoftBreaks(core.channel.text.convertMarkdownTables(paramsLocal.text, tableMode));
|
|
@@ -2220,12 +2232,20 @@ function createFeishuReplyDispatcher(params) {
|
|
|
2220
2232
|
acceptedChunks.push(chunk);
|
|
2221
2233
|
markVisibleReplySent();
|
|
2222
2234
|
} catch (error) {
|
|
2223
|
-
|
|
2224
|
-
|
|
2225
|
-
|
|
2226
|
-
|
|
2227
|
-
|
|
2228
|
-
|
|
2235
|
+
const acceptedChunk = isChannelPartialDeliveryError(error) ? error.deliveryResult : void 0;
|
|
2236
|
+
if (acceptedChunk) {
|
|
2237
|
+
acceptedChunks.push(acceptedChunk.content ?? chunk);
|
|
2238
|
+
markVisibleReplySent();
|
|
2239
|
+
}
|
|
2240
|
+
throw createFeishuPartialReplyDeliveryError(error, {
|
|
2241
|
+
...acceptedChunk,
|
|
2242
|
+
...createFeishuReplyDeliveryResult({
|
|
2243
|
+
results,
|
|
2244
|
+
visibleReplySent: results.length > 0 || acceptedChunk !== void 0,
|
|
2245
|
+
content: acceptedChunks.join(""),
|
|
2246
|
+
kind: paramsLocal.useCard ? "card" : "text"
|
|
2247
|
+
})
|
|
2248
|
+
});
|
|
2229
2249
|
}
|
|
2230
2250
|
}
|
|
2231
2251
|
if (paramsLocal.infoKind === "final") deliveredFinalTexts.add(paramsLocal.text);
|
|
@@ -2280,7 +2300,11 @@ function createFeishuReplyDispatcher(params) {
|
|
|
2280
2300
|
markVisibleReplySent();
|
|
2281
2301
|
if (result?.voiceIntentDegradedToFile && options?.fallbackText && !sentFallbackText) degradedVoiceFallbackText = options.fallbackText;
|
|
2282
2302
|
},
|
|
2283
|
-
onError: options?.fallbackText === void 0 ? void 0 : async ({ mediaUrl }) => {
|
|
2303
|
+
onError: options?.fallbackText === void 0 ? void 0 : async ({ error, mediaUrl }) => {
|
|
2304
|
+
if (isChannelPartialDeliveryError(error)) {
|
|
2305
|
+
markVisibleReplySent();
|
|
2306
|
+
throw toStringifiedError(error);
|
|
2307
|
+
}
|
|
2284
2308
|
const fallbackText = await buildFeishuMediaFallbackText({
|
|
2285
2309
|
text: sentFallbackText ? void 0 : options.fallbackText,
|
|
2286
2310
|
mediaUrl
|
|
@@ -2295,6 +2319,7 @@ function createFeishuReplyDispatcher(params) {
|
|
|
2295
2319
|
}
|
|
2296
2320
|
} catch (error) {
|
|
2297
2321
|
const partial = isChannelPartialDeliveryError(error) ? error.deliveryResult : void 0;
|
|
2322
|
+
if (partial) markVisibleReplySent();
|
|
2298
2323
|
throw createFeishuPartialReplyDeliveryError(error, mergeFeishuReplyDeliveryResults([...results, ...partial ? [partial] : []]));
|
|
2299
2324
|
}
|
|
2300
2325
|
return mergeFeishuReplyDeliveryResults(results);
|
|
@@ -2396,7 +2421,7 @@ function createFeishuReplyDispatcher(params) {
|
|
|
2396
2421
|
if (deliveryError !== void 0) completion.reject(createFeishuPartialReplyDeliveryError(isChannelPartialDeliveryError(deliveryError) && deliveryError instanceof Error ? deliveryError.cause ?? deliveryError : deliveryError instanceof FeishuStreamingFinalizationError ? deliveryError.cause ?? deliveryError : deliveryError, settledResult));
|
|
2397
2422
|
else completion.resolve(settledResult);
|
|
2398
2423
|
}
|
|
2399
|
-
if (closeOutcome.error !== void 0) throw
|
|
2424
|
+
if (closeOutcome.error !== void 0) throw toStringifiedError(closeOutcome.error);
|
|
2400
2425
|
} while (pendingStreamingDeliveries.length > 0);
|
|
2401
2426
|
} finally {
|
|
2402
2427
|
typingCallbacks?.onIdle?.();
|
|
@@ -2486,6 +2511,7 @@ function createFeishuReplyDispatcher(params) {
|
|
|
2486
2511
|
}
|
|
2487
2512
|
};
|
|
2488
2513
|
const handleDeliveryError = async (error, info) => {
|
|
2514
|
+
if (isChannelPartialDeliveryError(error)) markVisibleReplySent();
|
|
2489
2515
|
params.runtime.error?.(`feishu[${account.accountId}] ${info.kind} reply failed: ${String(error)}`);
|
|
2490
2516
|
await queueIdleSideEffects().catch((cleanupError) => params.runtime.error?.(`feishu[${account.accountId}] reply error cleanup failed: ${String(cleanupError)}`));
|
|
2491
2517
|
};
|
|
@@ -2524,7 +2550,7 @@ function createFeishuReplyDispatcher(params) {
|
|
|
2524
2550
|
const shouldDiscardStreamingPreview = info?.kind === "final" && (finalTextExceedsStreamingLimit || hasMedia && (hasVoiceMedia && !shouldDeliverText && !ttsTextAlreadyVisible || skipTextForDuplicateFinal));
|
|
2525
2551
|
const priorClosedStreamingSettlement = info?.kind === "final" && hasText && skipTextForDuplicateFinal ? claimClosedStreamingResult(void 0, text) : void 0;
|
|
2526
2552
|
if (!shouldDeliverText && !hasMedia) {
|
|
2527
|
-
if (priorClosedStreamingSettlement?.error !== void 0) throw
|
|
2553
|
+
if (priorClosedStreamingSettlement?.error !== void 0) throw toStringifiedError(priorClosedStreamingSettlement.error);
|
|
2528
2554
|
return priorClosedStreamingSettlement?.result ?? noVisibleFeishuReplyDelivery;
|
|
2529
2555
|
}
|
|
2530
2556
|
const deliveredResults = priorClosedStreamingSettlement ? [priorClosedStreamingSettlement.result] : [];
|
|
@@ -2644,7 +2670,7 @@ function createFeishuReplyDispatcher(params) {
|
|
|
2644
2670
|
}
|
|
2645
2671
|
}
|
|
2646
2672
|
if (hasMedia) await collectMediaDelivery(payload, hasVoiceMedia && hasText ? { fallbackText: text } : void 0);
|
|
2647
|
-
const result = mergeFeishuReplyDeliveryResults(deliveredResults, text);
|
|
2673
|
+
const result = mergeFeishuReplyDeliveryResults(deliveredResults, hasVoiceMedia ? deliveredResults.at(-1)?.content ?? text : text);
|
|
2648
2674
|
if (priorClosedStreamingSettlement?.error !== void 0) throw createFeishuPartialReplyDeliveryError(isChannelPartialDeliveryError(priorClosedStreamingSettlement.error) && priorClosedStreamingSettlement.error instanceof Error ? priorClosedStreamingSettlement.error.cause ?? priorClosedStreamingSettlement.error : priorClosedStreamingSettlement.error, result);
|
|
2649
2675
|
return result;
|
|
2650
2676
|
},
|
|
@@ -2654,43 +2680,40 @@ function createFeishuReplyDispatcher(params) {
|
|
|
2654
2680
|
onModelSelected: prefixContext.onModelSelected,
|
|
2655
2681
|
disableBlockStreaming: typeof blockStreamingEnabled === "boolean" ? !blockStreamingEnabled : true,
|
|
2656
2682
|
onPartialReply: previewStreamingEnabled ? (payload) => {
|
|
2657
|
-
if (!payload.text) return;
|
|
2683
|
+
if (!payload.text) return false;
|
|
2658
2684
|
const cleaned = stripReasoningTagsFromText(payload.text, {
|
|
2659
2685
|
mode: "strict",
|
|
2660
2686
|
trim: "both"
|
|
2661
2687
|
});
|
|
2662
|
-
if (!cleaned) return;
|
|
2688
|
+
if (!cleaned) return false;
|
|
2663
2689
|
startStreaming();
|
|
2664
2690
|
queueStreamingUpdate(cleaned, {
|
|
2665
2691
|
dedupeWithLastPartial: true,
|
|
2666
2692
|
mode: "snapshot"
|
|
2667
2693
|
});
|
|
2694
|
+
return false;
|
|
2668
2695
|
} : void 0,
|
|
2669
2696
|
onReasoningStream: reasoningPreviewEnabled ? (payload) => {
|
|
2670
|
-
if (!payload.text) return;
|
|
2697
|
+
if (!payload.text) return false;
|
|
2671
2698
|
startStreaming();
|
|
2672
2699
|
queueReasoningUpdate(formatReasoningMessage(payload.text));
|
|
2700
|
+
return false;
|
|
2673
2701
|
} : void 0,
|
|
2674
|
-
onReasoningEnd: reasoningPreviewEnabled ? () =>
|
|
2702
|
+
onReasoningEnd: reasoningPreviewEnabled ? () => false : void 0,
|
|
2675
2703
|
onToolStart: previewStreamingEnabled ? (payload) => {
|
|
2676
|
-
if (!isChannelProgressDraftWorkToolName(payload.name)) return;
|
|
2704
|
+
if (!isChannelProgressDraftWorkToolName(payload.name)) return false;
|
|
2677
2705
|
const statusLineLocal = formatChannelProgressDraftLineForEntry(account.config, {
|
|
2678
2706
|
event: "tool",
|
|
2679
2707
|
name: payload.name,
|
|
2680
2708
|
phase: payload.phase,
|
|
2681
2709
|
args: payload.args
|
|
2682
2710
|
}, { detailMode: payload.detailMode });
|
|
2683
|
-
if (statusLineLocal) updateStreamingStatusLine(statusLineLocal);
|
|
2684
|
-
|
|
2685
|
-
onAssistantMessageStart: previewStreamingEnabled ? () => {
|
|
2686
|
-
updateStreamingStatusLine("", { startIfNeeded: false });
|
|
2711
|
+
if (statusLineLocal) return updateStreamingStatusLine(statusLineLocal);
|
|
2712
|
+
return false;
|
|
2687
2713
|
} : void 0,
|
|
2688
|
-
|
|
2689
|
-
|
|
2690
|
-
|
|
2691
|
-
onCompactionEnd: previewStreamingEnabled ? () => {
|
|
2692
|
-
updateStreamingStatusLine("");
|
|
2693
|
-
} : void 0
|
|
2714
|
+
onAssistantMessageStart: previewStreamingEnabled ? () => updateStreamingStatusLine("", { startIfNeeded: false }) : void 0,
|
|
2715
|
+
onCompactionStart: previewStreamingEnabled ? () => updateStreamingStatusLine("📦 **Compacting context...**") : void 0,
|
|
2716
|
+
onCompactionEnd: previewStreamingEnabled ? () => updateStreamingStatusLine("") : void 0
|
|
2694
2717
|
},
|
|
2695
2718
|
ensureNoVisibleReplyFallback,
|
|
2696
2719
|
getVisibleReplyState: () => ({
|
|
@@ -2715,11 +2738,9 @@ function getFeishuSyntheticDirectPreDispatchTarget(event) {
|
|
|
2715
2738
|
const permissionErrorNotifiedAt = /* @__PURE__ */ new Map();
|
|
2716
2739
|
const PERMISSION_ERROR_COOLDOWN_MS = 300 * 1e3;
|
|
2717
2740
|
function shouldSendNoVisibleReplyFallback(dispatchResult) {
|
|
2718
|
-
const
|
|
2719
|
-
const
|
|
2720
|
-
|
|
2721
|
-
const queuedFinalFailed = dispatchResult.queuedFinal === true && failedFinalCount > 0;
|
|
2722
|
-
return dispatchResult.sendPolicyDenied !== true && dispatchResult.sourceReplyDeliveryMode !== "message_tool_only" && (emptyEligibleDispatch || queuedFinalFailed);
|
|
2741
|
+
const emptyEligibleDispatch = dispatchResult.noVisibleReplyFallbackEligible === true && dispatchResult.settledReceipt?.anyVisibleDelivered !== true;
|
|
2742
|
+
const finalFailedBeforeSend = (dispatchResult.settledReceipt?.counts.final.failedBeforeSend ?? 0) > 0;
|
|
2743
|
+
return dispatchResult.sendPolicyDenied !== true && dispatchResult.sourceReplyDeliveryMode !== "message_tool_only" && (emptyEligibleDispatch || finalFailedBeforeSend);
|
|
2723
2744
|
}
|
|
2724
2745
|
function isFeishuTopicSessionScope(scope) {
|
|
2725
2746
|
return scope === "group_topic" || scope === "group_topic_sender";
|
|
@@ -2892,7 +2913,10 @@ async function handleFeishuMessage(params) {
|
|
|
2892
2913
|
if (event.message.message_type === "merge_forward") {
|
|
2893
2914
|
log(`feishu[${account.accountId}]: processing merge_forward message, fetching full content via API`);
|
|
2894
2915
|
try {
|
|
2895
|
-
const response = await createFeishuClient(account).im.message.get({
|
|
2916
|
+
const response = await createFeishuClient(account).im.message.get({
|
|
2917
|
+
params: { card_msg_content_type: "user_card_content" },
|
|
2918
|
+
path: { message_id: event.message.message_id }
|
|
2919
|
+
});
|
|
2896
2920
|
if (response.code === 0 && response.data?.items && response.data.items.length > 0) {
|
|
2897
2921
|
log(`feishu[${account.accountId}]: merge_forward API returned ${response.data.items.length} items`);
|
|
2898
2922
|
const expandedContent = parseMergeForwardContent({
|
|
@@ -2963,22 +2987,25 @@ async function handleFeishuMessage(params) {
|
|
|
2963
2987
|
feishuCfg
|
|
2964
2988
|
}) : null;
|
|
2965
2989
|
let effectiveThreadId = ctx.threadId;
|
|
2966
|
-
if (isGroup && ctx.chatType === "topic_group" && !effectiveThreadId && isFeishuTopicSessionScope(groupSessionScope ?? "group"))
|
|
2967
|
-
const
|
|
2968
|
-
|
|
2969
|
-
|
|
2970
|
-
|
|
2971
|
-
|
|
2972
|
-
|
|
2973
|
-
|
|
2974
|
-
|
|
2975
|
-
|
|
2976
|
-
|
|
2977
|
-
|
|
2978
|
-
|
|
2990
|
+
if (isGroup && ctx.chatType === "topic_group" && !effectiveThreadId && isFeishuTopicSessionScope(groupSessionScope ?? "group")) {
|
|
2991
|
+
const topicHydrationMessageId = ctx.replyTargetMessageId ?? ctx.messageId;
|
|
2992
|
+
try {
|
|
2993
|
+
const hydratedThreadId = (await getMessageFeishu({
|
|
2994
|
+
cfg,
|
|
2995
|
+
accountId: account.accountId,
|
|
2996
|
+
messageId: topicHydrationMessageId
|
|
2997
|
+
}))?.threadId?.trim();
|
|
2998
|
+
if (hydratedThreadId) {
|
|
2999
|
+
ctx = {
|
|
3000
|
+
...ctx,
|
|
3001
|
+
threadId: hydratedThreadId
|
|
3002
|
+
};
|
|
3003
|
+
effectiveThreadId = hydratedThreadId;
|
|
3004
|
+
log(`feishu[${account.accountId}]: hydrated topic thread_id=${hydratedThreadId} for message=${topicHydrationMessageId}`);
|
|
3005
|
+
}
|
|
3006
|
+
} catch (err) {
|
|
3007
|
+
log(`feishu[${account.accountId}]: failed to hydrate topic thread_id for message=${topicHydrationMessageId}: ${String(err)}`);
|
|
2979
3008
|
}
|
|
2980
|
-
} catch (err) {
|
|
2981
|
-
log(`feishu[${account.accountId}]: failed to hydrate topic thread_id for message=${ctx.messageId}: ${String(err)}`);
|
|
2982
3009
|
}
|
|
2983
3010
|
const effectiveGroupSenderAllowFrom = isGroup ? (groupConfig?.allowFrom?.length ?? 0) > 0 ? groupConfig?.allowFrom ?? [] : feishuCfg?.groupSenderAllowFrom ?? [] : [];
|
|
2984
3011
|
const groupSession = isGroup ? resolveFeishuGroupSession({
|
|
@@ -2995,7 +3022,7 @@ async function handleFeishuMessage(params) {
|
|
|
2995
3022
|
const dmPolicy = feishuCfg?.dmPolicy ?? "pairing";
|
|
2996
3023
|
const configAllowFrom = feishuCfg?.allowFrom ?? [];
|
|
2997
3024
|
const rawBroadcastAgents = isGroup ? resolveBroadcastAgents(cfg, ctx.chatId) : null;
|
|
2998
|
-
const broadcastAgents = rawBroadcastAgents ? uniqueStrings(rawBroadcastAgents.map((id) => normalizeAgentId$
|
|
3025
|
+
const broadcastAgents = rawBroadcastAgents ? uniqueStrings(rawBroadcastAgents.map((id) => normalizeAgentId$1(id))) : null;
|
|
2999
3026
|
const messageCreateTimeMs = parseStrictNonNegativeInteger(event.message.create_time) ?? Date.now();
|
|
3000
3027
|
let requireMention = false;
|
|
3001
3028
|
if (isGroup) {
|
|
@@ -3524,7 +3551,37 @@ async function handleFeishuMessage(params) {
|
|
|
3524
3551
|
const buildCtxPayloadForAgent = async (agentId, agentSessionKey, agentAccountId, wasMentioned) => {
|
|
3525
3552
|
const groupName = await resolveGroupNameForLabel();
|
|
3526
3553
|
const threadContext = await resolveThreadContextForAgent(agentId, agentSessionKey, groupName);
|
|
3527
|
-
|
|
3554
|
+
const contextBinding = {
|
|
3555
|
+
agentId,
|
|
3556
|
+
sessionKey: agentSessionKey,
|
|
3557
|
+
messageId: ctx.messageId,
|
|
3558
|
+
inboundEventKind: "user_request"
|
|
3559
|
+
};
|
|
3560
|
+
const boundChannelIngress = isGroup ? await resolveFeishuGroupSenderActivationIngressAccess({
|
|
3561
|
+
cfg: effectiveCfg,
|
|
3562
|
+
accountId: account.accountId,
|
|
3563
|
+
chatId: ctx.chatId,
|
|
3564
|
+
allowFrom: effectiveGroupSenderAllowFrom,
|
|
3565
|
+
senderOpenId: ctx.senderOpenId,
|
|
3566
|
+
senderUserId,
|
|
3567
|
+
requireMention,
|
|
3568
|
+
mentionedBot: ctx.mentionedBot,
|
|
3569
|
+
contextBinding,
|
|
3570
|
+
threadId: ctx.rootId && isTopicSessionForThread ? ctx.rootId : void 0
|
|
3571
|
+
}) : await resolveFeishuDmIngressAccess({
|
|
3572
|
+
cfg: effectiveCfg,
|
|
3573
|
+
accountId: account.accountId,
|
|
3574
|
+
dmPolicy: effectiveDmPolicy,
|
|
3575
|
+
allowFrom: effectiveConfigAllowFrom,
|
|
3576
|
+
readAllowFromStore: pairing.readAllowFromStore,
|
|
3577
|
+
senderOpenId: ctx.senderOpenId,
|
|
3578
|
+
senderUserId,
|
|
3579
|
+
conversationId: ctx.senderOpenId,
|
|
3580
|
+
mayPair: false,
|
|
3581
|
+
contextBinding
|
|
3582
|
+
});
|
|
3583
|
+
return core.channel.inbound.buildContext({
|
|
3584
|
+
channelIngress: boundChannelIngress,
|
|
3528
3585
|
channel: "feishu",
|
|
3529
3586
|
supplemental: {
|
|
3530
3587
|
quote: quotedContent ? {
|
|
@@ -3550,7 +3607,12 @@ async function handleFeishuMessage(params) {
|
|
|
3550
3607
|
conversation: {
|
|
3551
3608
|
kind: isGroup ? "group" : "direct",
|
|
3552
3609
|
id: ctx.chatId,
|
|
3610
|
+
routePeer: {
|
|
3611
|
+
kind: isGroup ? "group" : "direct",
|
|
3612
|
+
id: peerId
|
|
3613
|
+
},
|
|
3553
3614
|
nativeChannelId: ctx.chatId,
|
|
3615
|
+
parentId: parentPeer?.id,
|
|
3554
3616
|
label: isGroup && groupName && !isTopicSessionForThread ? groupName : void 0,
|
|
3555
3617
|
threadId: ctx.rootId && isTopicSessionForThread ? ctx.rootId : void 0
|
|
3556
3618
|
},
|
|
@@ -3651,8 +3713,8 @@ async function handleFeishuMessage(params) {
|
|
|
3651
3713
|
}
|
|
3652
3714
|
};
|
|
3653
3715
|
const strategy = cfg.broadcast?.strategy === "sequential" ? "sequential" : "parallel";
|
|
3654
|
-
const activeAgentId = ctx.mentionedBot || !requireMention ? normalizeAgentId$
|
|
3655
|
-
const agentIds = (cfg.agents?.list ?? []).map((a) => normalizeAgentId$
|
|
3716
|
+
const activeAgentId = ctx.mentionedBot || !requireMention ? normalizeAgentId$1(route.agentId) : null;
|
|
3717
|
+
const agentIds = (cfg.agents?.list ?? []).map((a) => normalizeAgentId$1(a.id));
|
|
3656
3718
|
const hasKnownAgents = agentIds.length > 0;
|
|
3657
3719
|
log(`feishu[${account.accountId}]: broadcasting to ${broadcastAgents.length} agents (strategy=${strategy}, active=${activeAgentId ?? "none"})`);
|
|
3658
3720
|
const createBroadcastInboundAdapter = (paramsLocal) => ({
|
|
@@ -3692,7 +3754,7 @@ async function handleFeishuMessage(params) {
|
|
|
3692
3754
|
})
|
|
3693
3755
|
});
|
|
3694
3756
|
const dispatchForAgent = async (agentId) => {
|
|
3695
|
-
const normalizedAgentId = normalizeAgentId$
|
|
3757
|
+
const normalizedAgentId = normalizeAgentId$1(agentId);
|
|
3696
3758
|
if (hasKnownAgents && !agentIds.includes(normalizedAgentId)) {
|
|
3697
3759
|
log(`feishu[${account.accountId}]: broadcast agent ${agentId} not found in agents.list; skipping`);
|
|
3698
3760
|
return;
|
|
@@ -4293,9 +4355,6 @@ const FEISHU_INGRESS_PAYLOAD_VERSION = 1;
|
|
|
4293
4355
|
const FEISHU_INGRESS_POLL_INTERVAL_MS = 500;
|
|
4294
4356
|
const FEISHU_DURABLE_EVENT_TYPES = /* @__PURE__ */ new Set(["drive.notice.comment_add_v1", "im.message.receive_v1"]);
|
|
4295
4357
|
const FeishuIngressPermanentError = createChannelIngressError("FeishuIngressPermanentError", { withReason: true });
|
|
4296
|
-
function readString(value) {
|
|
4297
|
-
return typeof value === "string" && value.trim() ? value.trim() : null;
|
|
4298
|
-
}
|
|
4299
4358
|
function parseRawEnvelope(rawEnvelope) {
|
|
4300
4359
|
let parsed;
|
|
4301
4360
|
try {
|
|
@@ -4307,7 +4366,7 @@ function parseRawEnvelope(rawEnvelope) {
|
|
|
4307
4366
|
return parsed;
|
|
4308
4367
|
}
|
|
4309
4368
|
function decryptEnvelope(envelope, encryptKey) {
|
|
4310
|
-
const encrypted =
|
|
4369
|
+
const encrypted = normalizeNullableString(envelope.encrypt);
|
|
4311
4370
|
if (!encrypted) return envelope;
|
|
4312
4371
|
const key = encryptKey?.trim();
|
|
4313
4372
|
if (!key) throw new FeishuIngressPermanentError("authentication-failed", "Feishu encrypted ingress envelope has no configured encrypt key.");
|
|
@@ -4322,13 +4381,13 @@ function inspectFeishuIngressEnvelope(rawEnvelope, encryptKey, allowInvalidLane
|
|
|
4322
4381
|
const envelope = decryptEnvelope(parseRawEnvelope(rawEnvelope), encryptKey);
|
|
4323
4382
|
const nestedHeader = isRecord$1(envelope.header) ? envelope.header : null;
|
|
4324
4383
|
const nestedEvent = isRecord$1(envelope.event) ? envelope.event : null;
|
|
4325
|
-
const eventType =
|
|
4384
|
+
const eventType = normalizeNullableString(nestedHeader?.event_type) ?? normalizeNullableString(envelope.event_type);
|
|
4326
4385
|
if (!eventType || !FEISHU_DURABLE_EVENT_TYPES.has(eventType)) return null;
|
|
4327
|
-
const eventId =
|
|
4386
|
+
const eventId = normalizeNullableString(nestedHeader?.event_id) ?? normalizeNullableString(envelope.event_id);
|
|
4328
4387
|
if (!eventId) throw new FeishuIngressPermanentError("invalid-event", `Feishu ${eventType} envelope is missing header.event_id.`);
|
|
4329
4388
|
const event = nestedEvent ?? envelope;
|
|
4330
4389
|
if (eventType === "im.message.receive_v1") {
|
|
4331
|
-
const chatId =
|
|
4390
|
+
const chatId = normalizeNullableString((isRecord$1(event.message) ? event.message : null)?.chat_id);
|
|
4332
4391
|
if (!chatId) {
|
|
4333
4392
|
if (allowInvalidLane) return {
|
|
4334
4393
|
eventId,
|
|
@@ -4344,8 +4403,8 @@ function inspectFeishuIngressEnvelope(rawEnvelope, encryptKey, allowInvalidLane
|
|
|
4344
4403
|
};
|
|
4345
4404
|
}
|
|
4346
4405
|
const noticeMeta = isRecord$1(event.notice_meta) ? event.notice_meta : null;
|
|
4347
|
-
const fileType =
|
|
4348
|
-
const documentId =
|
|
4406
|
+
const fileType = normalizeNullableString(noticeMeta?.file_type);
|
|
4407
|
+
const documentId = normalizeNullableString(noticeMeta?.file_token);
|
|
4349
4408
|
if (!fileType || !documentId) {
|
|
4350
4409
|
if (allowInvalidLane) return {
|
|
4351
4410
|
eventId,
|
|
@@ -4580,7 +4639,7 @@ function createFeishuDurableIngress(options) {
|
|
|
4580
4639
|
invoke,
|
|
4581
4640
|
invokeWebhook,
|
|
4582
4641
|
resolveLifecycle: (data) => {
|
|
4583
|
-
const eventId = isRecord$1(data) ?
|
|
4642
|
+
const eventId = isRecord$1(data) ? normalizeNullableString(data.event_id) : null;
|
|
4584
4643
|
return eventId ? activeLifecycles.get(eventId) : void 0;
|
|
4585
4644
|
},
|
|
4586
4645
|
setSocketTerminator: (terminate) => {
|
|
@@ -4777,7 +4836,9 @@ function startBotIdentityRecovery(params) {
|
|
|
4777
4836
|
const log = runtime?.log ?? console.log;
|
|
4778
4837
|
log(`feishu[${accountId}]: bot open_id ${currentSource === "cache" ? "loaded from cache" : "unknown"}; starting background provider refresh (delays: ${BOT_IDENTITY_RETRY_DELAYS_MS.map((delay) => `${delay / 1e3}s`).join(", ")})`);
|
|
4779
4838
|
if (currentSource !== "cache") log(`feishu[${accountId}]: requireMention group messages stay gated until bot identity recovery succeeds`);
|
|
4780
|
-
retryBotIdentityProbe(account, accountId, runtime, abortSignal)
|
|
4839
|
+
retryBotIdentityProbe(account, accountId, runtime, abortSignal).catch((err) => {
|
|
4840
|
+
(runtime?.error ?? console.error)(`feishu[${accountId}]: bot identity background retry failed unexpectedly: ${String(err)}`);
|
|
4841
|
+
});
|
|
4781
4842
|
}
|
|
4782
4843
|
//#endregion
|
|
4783
4844
|
//#region extensions/feishu/src/card-ux-launcher.ts
|
|
@@ -5094,7 +5155,7 @@ function safeJsonStringify(value) {
|
|
|
5094
5155
|
}
|
|
5095
5156
|
}
|
|
5096
5157
|
function truncatePromptText(text, maxLength = FEISHU_PROMPT_TEXT_LIMIT) {
|
|
5097
|
-
const normalized =
|
|
5158
|
+
const normalized = normalizeOptionalString(text);
|
|
5098
5159
|
if (!normalized) return "";
|
|
5099
5160
|
return normalized.length > maxLength ? `${sliceUtf16Safe(normalized, 0, maxLength - 1)}…` : normalized;
|
|
5100
5161
|
}
|
|
@@ -5160,7 +5221,7 @@ async function resolveParsedCommentContent(params) {
|
|
|
5160
5221
|
return null;
|
|
5161
5222
|
}
|
|
5162
5223
|
const objType = normalizeCommentFileType(response.data?.node?.obj_type);
|
|
5163
|
-
const objToken =
|
|
5224
|
+
const objToken = normalizeOptionalString(response.data?.node?.obj_token);
|
|
5164
5225
|
if (!objType || !objToken) return null;
|
|
5165
5226
|
return {
|
|
5166
5227
|
resolvedObjType: objType,
|
|
@@ -5229,18 +5290,18 @@ async function fetchDriveComments(params) {
|
|
|
5229
5290
|
}
|
|
5230
5291
|
async function requestFeishuOpenApi(params) {
|
|
5231
5292
|
const formatErrorDetails = (error) => {
|
|
5232
|
-
if (!isRecord
|
|
5233
|
-
const response = isRecord
|
|
5234
|
-
const responseData = isRecord
|
|
5293
|
+
if (!isRecord(error)) return typeof error === "string" ? error : JSON.stringify(error);
|
|
5294
|
+
const response = isRecord(error.response) ? error.response : void 0;
|
|
5295
|
+
const responseData = isRecord(response?.data) ? response?.data : void 0;
|
|
5235
5296
|
return safeJsonStringify({
|
|
5236
5297
|
message: typeof error.message === "string" ? error.message : typeof error === "string" ? error : JSON.stringify(error),
|
|
5237
|
-
code:
|
|
5238
|
-
method:
|
|
5239
|
-
url:
|
|
5298
|
+
code: readStringValue(error.code),
|
|
5299
|
+
method: readStringValue(isRecord(error.config) ? error.config.method : void 0),
|
|
5300
|
+
url: readStringValue(isRecord(error.config) ? error.config.url : void 0),
|
|
5240
5301
|
http_status: typeof response?.status === "number" ? response.status : void 0,
|
|
5241
|
-
feishu_code: typeof responseData?.code === "number" ? responseData.code :
|
|
5242
|
-
feishu_msg:
|
|
5243
|
-
feishu_log_id:
|
|
5302
|
+
feishu_code: typeof responseData?.code === "number" ? responseData.code : readStringValue(responseData?.code),
|
|
5303
|
+
feishu_msg: readStringValue(responseData?.msg),
|
|
5304
|
+
feishu_log_id: readStringValue(responseData?.log_id)
|
|
5244
5305
|
});
|
|
5245
5306
|
};
|
|
5246
5307
|
const result = await raceWithTimeoutAndAbort(params.client.request({
|
|
@@ -5288,15 +5349,15 @@ async function fetchDriveCommentReplies(params) {
|
|
|
5288
5349
|
};
|
|
5289
5350
|
}
|
|
5290
5351
|
async function resolveCommentReplyContext(params) {
|
|
5291
|
-
const userId =
|
|
5292
|
-
const normalizedBotOpenIds = new Set(Array.from(params.botOpenIds ?? []).map((botId) =>
|
|
5352
|
+
const userId = normalizeOptionalString(params.reply.user_id);
|
|
5353
|
+
const normalizedBotOpenIds = new Set(Array.from(params.botOpenIds ?? []).map((botId) => normalizeOptionalString(botId)).filter((botId) => Boolean(botId)));
|
|
5293
5354
|
return {
|
|
5294
|
-
replyId:
|
|
5355
|
+
replyId: normalizeOptionalString(params.reply.reply_id),
|
|
5295
5356
|
userId,
|
|
5296
5357
|
createTime: typeof params.reply.create_time === "number" ? params.reply.create_time : void 0,
|
|
5297
5358
|
isBotAuthored: typeof userId === "string" && normalizedBotOpenIds.has(userId),
|
|
5298
5359
|
content: await resolveParsedCommentContent({
|
|
5299
|
-
elements: isRecord
|
|
5360
|
+
elements: isRecord(params.reply.content) ? params.reply.content.elements : void 0,
|
|
5300
5361
|
botOpenIds: params.botOpenIds,
|
|
5301
5362
|
currentDocument: params.currentDocument,
|
|
5302
5363
|
client: params.client,
|
|
@@ -5451,8 +5512,8 @@ async function fetchDriveCommentContext(params) {
|
|
|
5451
5512
|
createTime: right.createTime,
|
|
5452
5513
|
stableId: right.replyId
|
|
5453
5514
|
}));
|
|
5454
|
-
const rootReplyContext = resolvedReplies.find((reply) => reply.replyId ===
|
|
5455
|
-
const targetReplyContext = resolvedReplies.find((reply) => reply.replyId ===
|
|
5515
|
+
const rootReplyContext = resolvedReplies.find((reply) => reply.replyId === normalizeOptionalString(rootReply?.reply_id)) ?? resolvedReplies[0];
|
|
5516
|
+
const targetReplyContext = resolvedReplies.find((reply) => reply.replyId === normalizeOptionalString(targetReply?.reply_id)) ?? (params.replyId ? void 0 : resolvedReplies.at(-1) ?? rootReplyContext);
|
|
5456
5517
|
let wholeCommentTimeline = [];
|
|
5457
5518
|
if (commentCard?.is_whole === true) {
|
|
5458
5519
|
const wholeComments = (await fetchDriveComments({
|
|
@@ -5466,9 +5527,9 @@ async function fetchDriveCommentContext(params) {
|
|
|
5466
5527
|
})).filter((comment) => comment.is_whole === true);
|
|
5467
5528
|
wholeCommentTimeline = await Promise.all(wholeComments.map(async (comment) => {
|
|
5468
5529
|
const rootWholeReply = comment.reply_list?.replies?.[0];
|
|
5469
|
-
const normalizedBotOpenIds = new Set(Array.from(params.botOpenIds ?? []).map((botId) =>
|
|
5530
|
+
const normalizedBotOpenIds = new Set(Array.from(params.botOpenIds ?? []).map((botId) => normalizeOptionalString(botId)).filter((botId) => Boolean(botId)));
|
|
5470
5531
|
const content = await resolveParsedCommentContent({
|
|
5471
|
-
elements: isRecord
|
|
5532
|
+
elements: isRecord(rootWholeReply?.content) ? rootWholeReply.content.elements : void 0,
|
|
5472
5533
|
botOpenIds: params.botOpenIds,
|
|
5473
5534
|
currentDocument,
|
|
5474
5535
|
client: params.client,
|
|
@@ -5476,12 +5537,12 @@ async function fetchDriveCommentContext(params) {
|
|
|
5476
5537
|
logger: params.logger,
|
|
5477
5538
|
accountId: params.accountId
|
|
5478
5539
|
});
|
|
5479
|
-
const commentUserId =
|
|
5540
|
+
const commentUserId = normalizeOptionalString(rootWholeReply?.user_id) || normalizeOptionalString(comment.user_id);
|
|
5480
5541
|
return {
|
|
5481
|
-
commentId:
|
|
5542
|
+
commentId: normalizeOptionalString(comment.comment_id) ?? "",
|
|
5482
5543
|
userId: commentUserId,
|
|
5483
5544
|
createTime: typeof comment.create_time === "number" ? comment.create_time : typeof rootWholeReply?.create_time === "number" ? rootWholeReply.create_time : void 0,
|
|
5484
|
-
isCurrentComment:
|
|
5545
|
+
isCurrentComment: normalizeOptionalString(comment.comment_id) === params.commentId,
|
|
5485
5546
|
isBotAuthored: typeof commentUserId === "string" && normalizedBotOpenIds.has(commentUserId),
|
|
5486
5547
|
content
|
|
5487
5548
|
};
|
|
@@ -5496,10 +5557,10 @@ async function fetchDriveCommentContext(params) {
|
|
|
5496
5557
|
}
|
|
5497
5558
|
const currentWholeCommentIndex = wholeCommentTimeline.findIndex((entry) => entry.commentId === params.commentId);
|
|
5498
5559
|
return {
|
|
5499
|
-
documentTitle:
|
|
5500
|
-
documentUrl:
|
|
5560
|
+
documentTitle: normalizeOptionalString(meta?.title),
|
|
5561
|
+
documentUrl: normalizeOptionalString(meta?.url),
|
|
5501
5562
|
isWholeComment: commentCard?.is_whole,
|
|
5502
|
-
quoteText:
|
|
5563
|
+
quoteText: normalizeOptionalString(commentCard?.quote),
|
|
5503
5564
|
rootCommentText: rootReplyContext?.content.semanticText ?? rootReplyContext?.content.plainText,
|
|
5504
5565
|
targetReplyText: targetReplyContext?.content.semanticText ?? targetReplyContext?.content.plainText,
|
|
5505
5566
|
rootCommentContent: rootReplyContext?.content,
|
|
@@ -5553,7 +5614,7 @@ function buildDriveCommentSurfacePrompt(params) {
|
|
|
5553
5614
|
if (params.nearestBotWholeCommentBefore) lines.push(`Nearest bot-authored whole-comment before the current comment: comment_id=${params.nearestBotWholeCommentBefore.commentId} text=${formatPromptTextValue(params.nearestBotWholeCommentBefore.content.semanticText ?? params.nearestBotWholeCommentBefore.content.plainText)}`);
|
|
5554
5615
|
lines.push("For this whole-document comment, use the whole-comment timeline above as the primary source for phrases like 'just now', 'previous result', 'that summary', or 'write it back'.", "Document-level session history is auxiliary background only. Do not resolve whole-comment follow-ups by blindly using the most recent document-session output.");
|
|
5555
5616
|
}
|
|
5556
|
-
lines.push("This is a Feishu document comment thread.", "It is not a Feishu IM chat.", "Your final text reply will be posted to the current comment thread automatically.", "Use the thread timeline above as the main context for follow-up requests.", "Do not use another comment card or document-session output as the main reference.", "If you need comment thread context, use
|
|
5617
|
+
lines.push("This is a Feishu document comment thread.", "It is not a Feishu IM chat.", "Your final text reply will be posted to the current comment thread automatically.", "Use the thread timeline above as the main context for follow-up requests.", "Do not use another comment card or document-session output as the main reference.", "If you need comment thread context, use the available Feishu drive comment-list actions.", "If you modify the document, post a user-visible follow-up in the comment thread.", "Use an available Feishu drive reply-comment or add-comment action for that follow-up.", "Whole-document comments do not support direct replies.", "For whole-document comments, use the Feishu drive add-comment action when available.", "Only treat URLs listed under \"Referenced documents from current user comment\" as structured Feishu document references.", "URLs that appear only in comment text are plain links unless you verify them.", "If the user asks about a linked Feishu document or wiki page, treat that linked document as the read target.", "If the user asks you to use a linked document as guidance, treat the linked document as the reference source and the current commented document as the edit target.", "If a referenced document resolves to the same file_token and file_type as the current commented document, treat it as the current document.", "If the user asks you to modify document content, you must use an available Feishu document tool to make the change.", "Do not reply with only \"done\", \"I'll handle it\", or a restated plan without calling tools.", "If the comment quotes document content, treat the quoted content as the main anchor.", "For requests like \"insert xxx below this content\", locate the quoted content first, then edit the document.", "For requests like \"summarize the content below\", \"explain this section\", or \"continue writing from here\", use the quoted content as the main target.", "If the quote is not enough, use available Feishu document read or list-blocks actions to read nearby context.", "Do not guess document content from the comment alone.", "Do not give a vague answer before reading enough context.", "Unless the user asks for the whole document, handle only the local content around the quoted anchor.", "If document edits are involved, read the anchor first, then edit.", "If the edit fails or the anchor cannot be found, say so clearly.", "If this is a reading task, such as summarization, explanation, or extraction, you may output the final answer directly after confirming the context.", "Use the same language as the user's comment or reply, unless the user asks for another language.", "Use plain text only.", "Do not use Markdown.", "Do not use headings.", "Do not use bullet lists.", "Do not use numbered lists.", "Do not use tables.", "Do not use blockquotes.", "Do not use code blocks.", "Do not show reasoning.", "Do not show analysis.", "Do not show chain-of-thought.", "Do not show scratch work.", "Do not describe your plan.", "Do not describe your steps.", "Do not describe tool use.", "Do not start with phrases like \"I will\", \"I’ll first\", \"I need to\", \"The user wants\", or \"I have updated\".", "Output only the final user-facing reply.", "If you already sent the user-visible reply with a Feishu drive comment action, output exactly NO_REPLY.", "If no user-visible reply is needed, output exactly NO_REPLY.", "Be concise.", "Do not omit requested content.");
|
|
5557
5618
|
lines.push("Choose one outcome: output the final plain-text reply, edit the document and then post a user-visible follow-up in the comment thread, or output exactly NO_REPLY.");
|
|
5558
5619
|
return lines.join("\n");
|
|
5559
5620
|
}
|
|
@@ -5566,7 +5627,7 @@ async function resolveDriveCommentEventCore(params) {
|
|
|
5566
5627
|
const fileToken = event.notice_meta?.file_token?.trim();
|
|
5567
5628
|
const fileType = normalizeCommentFileType(event.notice_meta?.file_type);
|
|
5568
5629
|
const senderId = event.notice_meta?.from_user_id?.open_id?.trim();
|
|
5569
|
-
const senderUserId =
|
|
5630
|
+
const senderUserId = normalizeOptionalString(event.notice_meta?.from_user_id?.user_id);
|
|
5570
5631
|
if (!eventId || !commentId || !noticeType || !fileToken || !fileType || !senderId) {
|
|
5571
5632
|
logger?.(`feishu[${accountId}]: drive comment notice missing required fields event=${eventId ?? "unknown"} comment=${commentId ?? "unknown"}`);
|
|
5572
5633
|
return null;
|
|
@@ -5587,7 +5648,7 @@ async function resolveDriveCommentEventCore(params) {
|
|
|
5587
5648
|
return null;
|
|
5588
5649
|
}
|
|
5589
5650
|
const context = await fetchDriveCommentContext({
|
|
5590
|
-
client: createClient ? createClient(account ?? { accountId }) : createFeishuClient((await import("./accounts-
|
|
5651
|
+
client: createClient ? createClient(account ?? { accountId }) : createFeishuClient((await import("./accounts-CCCdMen2.js").then((n) => n.t)).resolveFeishuAccount({
|
|
5591
5652
|
cfg,
|
|
5592
5653
|
accountId
|
|
5593
5654
|
})),
|
|
@@ -5617,32 +5678,32 @@ async function resolveDriveCommentEventCore(params) {
|
|
|
5617
5678
|
};
|
|
5618
5679
|
}
|
|
5619
5680
|
function parseFeishuDriveCommentNoticeEventPayload(value) {
|
|
5620
|
-
if (!isRecord
|
|
5681
|
+
if (!isRecord(value) || !isRecord(value.notice_meta)) return null;
|
|
5621
5682
|
const noticeMeta = value.notice_meta;
|
|
5622
|
-
const fromUserId = isRecord
|
|
5623
|
-
const toUserId = isRecord
|
|
5683
|
+
const fromUserId = isRecord(noticeMeta.from_user_id) ? noticeMeta.from_user_id : void 0;
|
|
5684
|
+
const toUserId = isRecord(noticeMeta.to_user_id) ? noticeMeta.to_user_id : void 0;
|
|
5624
5685
|
return {
|
|
5625
|
-
comment_id:
|
|
5626
|
-
event_id:
|
|
5686
|
+
comment_id: readStringValue(value.comment_id),
|
|
5687
|
+
event_id: readStringValue(value.event_id),
|
|
5627
5688
|
is_mentioned: asBoolean(value.is_mentioned),
|
|
5628
5689
|
notice_meta: {
|
|
5629
|
-
file_token:
|
|
5630
|
-
file_type:
|
|
5690
|
+
file_token: readStringValue(noticeMeta.file_token),
|
|
5691
|
+
file_type: readStringValue(noticeMeta.file_type),
|
|
5631
5692
|
from_user_id: fromUserId ? {
|
|
5632
|
-
open_id:
|
|
5633
|
-
user_id:
|
|
5634
|
-
union_id:
|
|
5693
|
+
open_id: readStringValue(fromUserId.open_id),
|
|
5694
|
+
user_id: readStringValue(fromUserId.user_id),
|
|
5695
|
+
union_id: readStringValue(fromUserId.union_id)
|
|
5635
5696
|
} : void 0,
|
|
5636
|
-
notice_type:
|
|
5697
|
+
notice_type: readStringValue(noticeMeta.notice_type),
|
|
5637
5698
|
to_user_id: toUserId ? {
|
|
5638
|
-
open_id:
|
|
5639
|
-
user_id:
|
|
5640
|
-
union_id:
|
|
5699
|
+
open_id: readStringValue(toUserId.open_id),
|
|
5700
|
+
user_id: readStringValue(toUserId.user_id),
|
|
5701
|
+
union_id: readStringValue(toUserId.union_id)
|
|
5641
5702
|
} : void 0
|
|
5642
5703
|
},
|
|
5643
|
-
reply_id:
|
|
5644
|
-
timestamp:
|
|
5645
|
-
type:
|
|
5704
|
+
reply_id: readStringValue(value.reply_id),
|
|
5705
|
+
timestamp: readStringValue(value.timestamp),
|
|
5706
|
+
type: readStringValue(value.type)
|
|
5646
5707
|
};
|
|
5647
5708
|
}
|
|
5648
5709
|
async function resolveDriveCommentEventTurn(params) {
|
|
@@ -5742,7 +5803,7 @@ async function handleFeishuCommentEvent(params) {
|
|
|
5742
5803
|
channel: "feishu",
|
|
5743
5804
|
accountId: account.accountId
|
|
5744
5805
|
});
|
|
5745
|
-
const resolveCommentAuthorization = async (candidateCfg, mayPair) => {
|
|
5806
|
+
const resolveCommentAuthorization = async (candidateCfg, mayPair, contextBinding) => {
|
|
5746
5807
|
const candidateAccount = resolveFeishuRuntimeAccount({
|
|
5747
5808
|
cfg: candidateCfg,
|
|
5748
5809
|
accountId: account.accountId
|
|
@@ -5761,7 +5822,8 @@ async function handleFeishuCommentEvent(params) {
|
|
|
5761
5822
|
senderOpenId: turn.senderId,
|
|
5762
5823
|
senderUserId: turn.senderUserId,
|
|
5763
5824
|
conversationId: turn.senderId,
|
|
5764
|
-
mayPair
|
|
5825
|
+
mayPair,
|
|
5826
|
+
...contextBinding ? { contextBinding } : {}
|
|
5765
5827
|
})
|
|
5766
5828
|
};
|
|
5767
5829
|
};
|
|
@@ -5853,7 +5915,14 @@ async function handleFeishuCommentEvent(params) {
|
|
|
5853
5915
|
const bodyForAgent = `[message_id: ${turn.messageId}]\n${turn.prompt}`;
|
|
5854
5916
|
const rawBody = turn.targetReplyText ?? turn.rootCommentText ?? turn.prompt;
|
|
5855
5917
|
const conversationLabel = turn.documentTitle ? `Feishu comment · ${turn.documentTitle}` : "Feishu comment";
|
|
5856
|
-
const
|
|
5918
|
+
const boundAuthorization = await resolveCommentAuthorization(effectiveCfg, false, {
|
|
5919
|
+
agentId: route.agentId,
|
|
5920
|
+
sessionKey: commentSessionKey,
|
|
5921
|
+
messageId: turn.messageId,
|
|
5922
|
+
inboundEventKind: "user_request"
|
|
5923
|
+
});
|
|
5924
|
+
const ctxPayload = core.channel.inbound.buildContext({
|
|
5925
|
+
channelIngress: boundAuthorization.ingress,
|
|
5857
5926
|
channel: "feishu",
|
|
5858
5927
|
accountId: route.accountId,
|
|
5859
5928
|
surface: "feishu-comment",
|
|
@@ -5939,14 +6008,8 @@ async function handleFeishuCommentEvent(params) {
|
|
|
5939
6008
|
})
|
|
5940
6009
|
}
|
|
5941
6010
|
});
|
|
5942
|
-
const
|
|
5943
|
-
|
|
5944
|
-
const counts = dispatchResult?.counts ?? {
|
|
5945
|
-
tool: 0,
|
|
5946
|
-
block: 0,
|
|
5947
|
-
final: 0
|
|
5948
|
-
};
|
|
5949
|
-
log(`feishu[${account.accountId}]: drive comment dispatch complete (queuedFinal=${queuedFinal}, replies=${counts.final}, session=${commentSessionKey})`);
|
|
6011
|
+
const counts = resolveInboundReplyDispatchCounts(turnResult.dispatched ? turnResult.dispatchResult : void 0);
|
|
6012
|
+
log(`feishu[${account.accountId}]: drive comment dispatch complete (replies=${counts.final}, session=${commentSessionKey})`);
|
|
5950
6013
|
} finally {
|
|
5951
6014
|
cleanupTypingReaction();
|
|
5952
6015
|
}
|
|
@@ -6347,7 +6410,7 @@ const FEISHU_WS_LOG_ERROR_MAX_LENGTH = 500;
|
|
|
6347
6410
|
const FEISHU_WS_RECONNECT_EXHAUSTED_RE = /^WebSocket reconnect exhausted after \d+ attempts?/;
|
|
6348
6411
|
const FEISHU_WS_AUTORECONNECT_DISABLED_ERROR = "WebSocket connect failed and autoReconnect is disabled";
|
|
6349
6412
|
function isFeishuWebhookPayload(value) {
|
|
6350
|
-
return
|
|
6413
|
+
return isRecord(value);
|
|
6351
6414
|
}
|
|
6352
6415
|
const BLOCKED_FEISHU_WEBHOOK_PAYLOAD_KEYS = /* @__PURE__ */ new Set([
|
|
6353
6416
|
"__proto__",
|
|
@@ -6452,16 +6515,15 @@ async function monitorWebSocket({ account, accountId, runtime, abortSignal, even
|
|
|
6452
6515
|
};
|
|
6453
6516
|
const publishWsConnected = () => {
|
|
6454
6517
|
const connectedAt = Date.now();
|
|
6455
|
-
statusSink?.({
|
|
6456
|
-
connected: true,
|
|
6518
|
+
statusSink?.(channelReadyPatch({
|
|
6457
6519
|
lastConnectedAt: connectedAt,
|
|
6458
|
-
lastEventAt: connectedAt
|
|
6459
|
-
|
|
6460
|
-
});
|
|
6520
|
+
lastEventAt: connectedAt
|
|
6521
|
+
}));
|
|
6461
6522
|
};
|
|
6462
6523
|
const publishWsReconnecting = () => {
|
|
6463
6524
|
statusSink?.({
|
|
6464
6525
|
connected: false,
|
|
6526
|
+
lifecycle: "recovering",
|
|
6465
6527
|
lastEventAt: Date.now()
|
|
6466
6528
|
});
|
|
6467
6529
|
};
|
|
@@ -6509,10 +6571,11 @@ async function monitorWebSocket({ account, accountId, runtime, abortSignal, even
|
|
|
6509
6571
|
});
|
|
6510
6572
|
setSocketTerminator?.(void 0);
|
|
6511
6573
|
if (abortSignal?.aborted) break;
|
|
6512
|
-
|
|
6574
|
+
const disconnectedAt = Date.now();
|
|
6575
|
+
statusSink?.(channelBlockedPatch(formatFeishuWsErrorForLog(cycleEnd), {
|
|
6513
6576
|
connected: false,
|
|
6514
|
-
lastEventAt:
|
|
6515
|
-
});
|
|
6577
|
+
lastEventAt: disconnectedAt
|
|
6578
|
+
}));
|
|
6516
6579
|
attempt += 1;
|
|
6517
6580
|
const delayMs = getFeishuWsReconnectDelayMs(attempt);
|
|
6518
6581
|
error(`feishu[${accountId}]: WebSocket connection ended, recreating client in ${delayMs}ms: ${formatFeishuWsErrorForLog(cycleEnd)}`);
|
|
@@ -6526,9 +6589,14 @@ async function monitorWebSocket({ account, accountId, runtime, abortSignal, even
|
|
|
6526
6589
|
});
|
|
6527
6590
|
setSocketTerminator?.(void 0);
|
|
6528
6591
|
if (abortSignal?.aborted) break;
|
|
6592
|
+
const failedAt = Date.now();
|
|
6593
|
+
const terminal = err instanceof Error && isFeishuWsTerminalError(err);
|
|
6529
6594
|
statusSink?.({
|
|
6530
6595
|
connected: false,
|
|
6531
|
-
|
|
6596
|
+
lifecycle: terminal ? "blocked" : "recovering",
|
|
6597
|
+
...terminal ? { terminalDisconnect: true } : {},
|
|
6598
|
+
lastEventAt: failedAt,
|
|
6599
|
+
lastError: formatFeishuWsErrorForLog(err)
|
|
6532
6600
|
});
|
|
6533
6601
|
attempt += 1;
|
|
6534
6602
|
const delayMs = getFeishuWsReconnectDelayMs(attempt);
|
|
@@ -6551,10 +6619,18 @@ async function monitorWebhook({ account, accountId, runtime, abortSignal, eventD
|
|
|
6551
6619
|
if (!encryptKey) throw new Error(`Feishu account "${accountId}" webhook mode requires encryptKey`);
|
|
6552
6620
|
const port = account.config.webhookPort ?? 3e3;
|
|
6553
6621
|
const path = account.config.webhookPath ?? "/feishu/events";
|
|
6622
|
+
if (normalizeFeishuWebhookPath(path) !== path) throw new Error(`Feishu account "${accountId}" webhookPath must be a canonical HTTP request path; run "openclaw doctor --fix" to repair it`);
|
|
6554
6623
|
const host = account.config.webhookHost ?? "127.0.0.1";
|
|
6555
6624
|
log(`feishu[${accountId}]: starting Webhook server on ${host}:${port}, path ${path}...`);
|
|
6556
6625
|
const server = http.createServer();
|
|
6557
6626
|
server.on("request", (req, res) => {
|
|
6627
|
+
const requestUrl = req.url ?? "/";
|
|
6628
|
+
const requestPath = requestUrl.split("?", 1)[0];
|
|
6629
|
+
const requestRoute = path.includes("?") ? requestUrl : requestPath;
|
|
6630
|
+
if (!requestPath?.startsWith("/") || requestUrl.includes("#") || requestRoute !== path) {
|
|
6631
|
+
respondText(res, 404, "Not Found");
|
|
6632
|
+
return;
|
|
6633
|
+
}
|
|
6558
6634
|
res.on("finish", () => {
|
|
6559
6635
|
recordWebhookStatus(runtime, accountId, path, res.statusCode);
|
|
6560
6636
|
if (res.statusCode >= 200 && res.statusCode < 300) {
|
|
@@ -6568,6 +6644,7 @@ async function monitorWebhook({ account, accountId, runtime, abortSignal, eventD
|
|
|
6568
6644
|
if (!applyBasicWebhookRequestGuards({
|
|
6569
6645
|
req,
|
|
6570
6646
|
res,
|
|
6647
|
+
allowMethods: ["POST"],
|
|
6571
6648
|
rateLimiter: feishuWebhookRateLimiter,
|
|
6572
6649
|
rateLimitKey: buildFeishuWebhookRateLimitKey({
|
|
6573
6650
|
accountId,
|
|
@@ -6654,15 +6731,18 @@ async function monitorWebhook({ account, accountId, runtime, abortSignal, eventD
|
|
|
6654
6731
|
server.listen(port, host, () => {
|
|
6655
6732
|
log(`feishu[${accountId}]: Webhook server listening on ${host}:${port}`);
|
|
6656
6733
|
const webhookConnectedAt = Date.now();
|
|
6657
|
-
statusSink?.({
|
|
6658
|
-
connected: true,
|
|
6734
|
+
statusSink?.(channelReadyPatch({
|
|
6659
6735
|
lastConnectedAt: webhookConnectedAt,
|
|
6660
|
-
lastEventAt: webhookConnectedAt
|
|
6661
|
-
|
|
6662
|
-
});
|
|
6736
|
+
lastEventAt: webhookConnectedAt
|
|
6737
|
+
}));
|
|
6663
6738
|
});
|
|
6664
6739
|
server.on("error", (err) => {
|
|
6665
6740
|
error(`feishu[${accountId}]: Webhook server error: ${err}`);
|
|
6741
|
+
statusSink?.({
|
|
6742
|
+
connected: false,
|
|
6743
|
+
lifecycle: "recovering",
|
|
6744
|
+
lastError: formatFeishuWsErrorForLog(err)
|
|
6745
|
+
});
|
|
6666
6746
|
abortSignal?.removeEventListener("abort", handleAbort);
|
|
6667
6747
|
reject(err);
|
|
6668
6748
|
});
|
|
@@ -6799,10 +6879,11 @@ async function resolveReactionSyntheticEvent(params) {
|
|
|
6799
6879
|
const { cfg, accountId, event, botOpenId, fetchMessage = getMessageFeishu, verificationTimeoutMs = FEISHU_REACTION_VERIFY_TIMEOUT_MS, logger, uuid = () => crypto$1.randomUUID(), action = "created" } = params;
|
|
6800
6880
|
const emoji = event.reaction_type?.emoji_type;
|
|
6801
6881
|
const messageId = event.message_id;
|
|
6802
|
-
const
|
|
6803
|
-
const senderUserId = event.user_id?.user_id;
|
|
6882
|
+
const senderOpenId = event.user_id?.open_id?.trim();
|
|
6883
|
+
const senderUserId = event.user_id?.user_id?.trim();
|
|
6884
|
+
const senderId = senderOpenId || senderUserId;
|
|
6804
6885
|
if (!emoji || !messageId || !senderId) return null;
|
|
6805
|
-
const { resolveFeishuAccount } = await import("./accounts-
|
|
6886
|
+
const { resolveFeishuAccount } = await import("./accounts-CCCdMen2.js").then((n) => n.t);
|
|
6806
6887
|
const reactionNotifications = resolveFeishuAccount({
|
|
6807
6888
|
cfg,
|
|
6808
6889
|
accountId
|
|
@@ -6836,14 +6917,17 @@ async function resolveReactionSyntheticEvent(params) {
|
|
|
6836
6917
|
return {
|
|
6837
6918
|
sender: {
|
|
6838
6919
|
sender_id: {
|
|
6839
|
-
open_id:
|
|
6920
|
+
...senderOpenId ? { open_id: senderOpenId } : {},
|
|
6840
6921
|
...senderUserId ? { user_id: senderUserId } : {}
|
|
6841
6922
|
},
|
|
6842
6923
|
sender_type: "user"
|
|
6843
6924
|
},
|
|
6844
6925
|
message: {
|
|
6845
6926
|
message_id: `${messageId}:reaction:${emoji}:${uuid()}`,
|
|
6927
|
+
reply_target_message_id: messageId,
|
|
6846
6928
|
typing_target_message_id: messageId,
|
|
6929
|
+
...reactedMsg.rootId ? { root_id: reactedMsg.rootId } : {},
|
|
6930
|
+
...reactedMsg.threadId ? { thread_id: reactedMsg.threadId } : {},
|
|
6847
6931
|
chat_id: syntheticChatId,
|
|
6848
6932
|
chat_type: syntheticChatType,
|
|
6849
6933
|
message_type: "text",
|
|
@@ -6855,41 +6939,41 @@ function normalizeFeishuChatType(value) {
|
|
|
6855
6939
|
return value === "group" || value === "topic_group" || value === "private" || value === "p2p" ? value : void 0;
|
|
6856
6940
|
}
|
|
6857
6941
|
function parseFeishuBotAddedEventPayload(value) {
|
|
6858
|
-
if (!isRecord
|
|
6942
|
+
if (!isRecord(value) || !readStringValue(value.chat_id) || !isRecord(value.operator_id)) return null;
|
|
6859
6943
|
return value;
|
|
6860
6944
|
}
|
|
6861
6945
|
function parseFeishuBotRemovedChatId(value) {
|
|
6862
|
-
if (!isRecord
|
|
6863
|
-
return
|
|
6946
|
+
if (!isRecord(value)) return null;
|
|
6947
|
+
return readStringValue(value.chat_id) ?? null;
|
|
6864
6948
|
}
|
|
6865
6949
|
function firstString(...values) {
|
|
6866
6950
|
for (const value of values) {
|
|
6867
|
-
const trimmed =
|
|
6951
|
+
const trimmed = readStringValue(value)?.trim();
|
|
6868
6952
|
if (trimmed) return trimmed;
|
|
6869
6953
|
}
|
|
6870
6954
|
}
|
|
6871
6955
|
function readFeishuIdentityField(value, field) {
|
|
6872
|
-
if (!isRecord
|
|
6956
|
+
if (!isRecord(value)) return;
|
|
6873
6957
|
return firstString(value[field]);
|
|
6874
6958
|
}
|
|
6875
6959
|
function parseFeishuCardActionEventPayload(value) {
|
|
6876
|
-
if (!isRecord
|
|
6877
|
-
const operator = isRecord
|
|
6960
|
+
if (!isRecord(value)) return null;
|
|
6961
|
+
const operator = isRecord(value.operator) ? value.operator : {};
|
|
6878
6962
|
const action = value.action;
|
|
6879
|
-
const context = isRecord
|
|
6880
|
-
if (!isRecord
|
|
6963
|
+
const context = isRecord(value.context) ? value.context : {};
|
|
6964
|
+
if (!isRecord(action)) return null;
|
|
6881
6965
|
const operatorUserId = operator.user_id;
|
|
6882
|
-
const token =
|
|
6966
|
+
const token = readStringValue(value.token);
|
|
6883
6967
|
const openId = firstString(operator.open_id, readFeishuIdentityField(operatorUserId, "open_id"), value.open_id, context.open_id);
|
|
6884
6968
|
const userId = firstString(operator.user_id, readFeishuIdentityField(operatorUserId, "user_id"), value.user_id, context.user_id);
|
|
6885
6969
|
const unionId = firstString(operator.union_id, readFeishuIdentityField(operatorUserId, "union_id"));
|
|
6886
|
-
const tag =
|
|
6970
|
+
const tag = readStringValue(action.tag);
|
|
6887
6971
|
const actionValue = action.value;
|
|
6888
6972
|
const openMessageId = firstString(context.open_message_id, value.open_message_id);
|
|
6889
6973
|
const contextOpenId = firstString(context.open_id, openId);
|
|
6890
6974
|
const contextUserId = firstString(context.user_id, userId);
|
|
6891
6975
|
const chatId = firstString(context.chat_id, context.open_chat_id);
|
|
6892
|
-
if (!token || !openId || !tag || !isRecord
|
|
6976
|
+
if (!token || !openId || !tag || !isRecord(actionValue)) return null;
|
|
6893
6977
|
return {
|
|
6894
6978
|
operator: {
|
|
6895
6979
|
open_id: openId,
|