@openclaw/feishu 2026.7.2-beta.1 → 2026.7.2-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/api.js +322 -176
- package/dist/{app-registration-BwQ-aGTe.js → app-registration-BzkyrVZu.js} +11 -11
- package/dist/{channel-DdPQ2HGh.js → channel-BdnXYU6g.js} +36 -50
- package/dist/channel-plugin-api.js +1 -1
- package/dist/{channel.runtime-DJ_B-bpG.js → channel.runtime-CVIzo2dV.js} +102 -17
- package/dist/{client-BUC-R2Wi.js → client-87BmeMpj.js} +51 -16
- package/dist/{drive-Bpe8ONK_.js → drive-BHv8WW9i.js} +2 -2
- package/dist/{monitor-M3iYtoh4.js → monitor-BVQf7-Bl.js} +2 -2
- package/dist/{monitor.account-F99J862T.js → monitor.account-Blbf2T6J.js} +752 -423
- package/dist/{monitor.startup-DpZzunQn.js → monitor.startup-Cy8NPGAX.js} +1 -1
- package/dist/{probe-CAloGqgG.js → probe-BgboTHIn.js} +1 -1
- package/dist/{secret-contract-DnlMcrn5.js → secret-contract-BCpDLdg9.js} +17 -26
- package/dist/secret-contract-api.js +1 -1
- package/dist/{send-CpnLYueT.js → send-DcrXbqqA.js} +87 -8
- package/dist/{send-result-CPS7AozW.js → send-result-DfE5Fhz6.js} +31 -16
- package/dist/setup-api.js +1 -1
- package/npm-shrinkwrap.json +3 -3
- package/openclaw.plugin.json +34 -8
- package/package.json +4 -4
|
@@ -1,16 +1,16 @@
|
|
|
1
1
|
import { _ as normalizeCommentFileType, c as encodeQuery, d as isRecord$1, f as normalizeString, g as buildFeishuCommentTarget, h as requestFeishuApi, l as extractReplyText, m as readString, o as resolveFeishuAccount, p as parseCommentContentElements, s as resolveFeishuRuntimeAccount } from "./accounts-BDoGHJDk.js";
|
|
2
2
|
import { i as resolveReceiveIdType } from "./targets-BUjQ1TcA.js";
|
|
3
|
-
import { C as
|
|
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-87BmeMpj.js";
|
|
5
6
|
import { t as getFeishuRuntime } from "./runtime-C5JxBWZp.js";
|
|
6
|
-
import {
|
|
7
|
-
import { i as createCommentTypingReactionLifecycle, l as getChatInfo, t as deliverCommentThreadText } from "./drive-Bpe8ONK_.js";
|
|
7
|
+
import { i as createCommentTypingReactionLifecycle, l as getChatInfo, t as deliverCommentThreadText } from "./drive-BHv8WW9i.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
|
|
12
|
-
import { i as waitForAbortableDelay, r as raceWithTimeoutAndAbort } from "./probe-
|
|
13
|
-
import { t as fetchBotIdentityForMonitor } from "./monitor.startup-
|
|
11
|
+
import { _ as buildFeishuMediaFallbackText, c as parsePostContent, d as isMentionForwardRequest, f as isFeishuGroupChatType, g as normalizeFeishuExternalKey, h as shouldSuppressFeishuTextForVoiceMedia, i as sendCardFeishu, l as extractMentionTargets, m as sendMediaFeishu, n as getMessageFeishu, o as sendMessageFeishu, p as saveMessageResourceFeishu, r as listFeishuThreadMessages, s as sendStructuredCardFeishu, u as isFeishuBroadcastMention, v as resolveFeishuIdentityEmoji } from "./send-DcrXbqqA.js";
|
|
12
|
+
import { i as waitForAbortableDelay, r as raceWithTimeoutAndAbort } from "./probe-BgboTHIn.js";
|
|
13
|
+
import { t as fetchBotIdentityForMonitor } from "./monitor.startup-Cy8NPGAX.js";
|
|
14
14
|
import { createChannelMessageReplyPipeline, 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";
|
|
@@ -22,20 +22,22 @@ import fs from "node:fs";
|
|
|
22
22
|
import os from "node:os";
|
|
23
23
|
import path from "node:path";
|
|
24
24
|
import { normalizeAccountId, resolveAgentRoute, resolveInboundLastRouteSessionKey } from "openclaw/plugin-sdk/routing";
|
|
25
|
+
import { resolveStorePath } from "openclaw/plugin-sdk/session-store-runtime";
|
|
25
26
|
import { formatErrorMessage } from "openclaw/plugin-sdk/error-runtime";
|
|
26
27
|
import * as Lark from "@larksuiteoapi/node-sdk";
|
|
27
28
|
import { WEBHOOK_ANOMALY_COUNTER_DEFAULTS, WEBHOOK_RATE_LIMIT_DEFAULTS, applyBasicWebhookRequestGuards, createFixedWindowRateLimiter, createWebhookAnomalyTracker, resolveRequestClientIp } from "openclaw/plugin-sdk/webhook-ingress";
|
|
28
29
|
import { fetchWithSsrFGuard } from "openclaw/plugin-sdk/ssrf-runtime";
|
|
29
|
-
import { resolveSendableOutboundReplyParts, resolveTextChunksWithFallback, sendMediaWithLeadingCaption } from "openclaw/plugin-sdk/reply-payload";
|
|
30
|
+
import { getReplyPayloadTtsSupplement, resolveSendableOutboundReplyParts, resolveTextChunksWithFallback, sendMediaWithLeadingCaption } from "openclaw/plugin-sdk/reply-payload";
|
|
30
31
|
import { resolvePinnedMainDmOwnerFromAllowlist, safeEqualSecret } from "openclaw/plugin-sdk/security-runtime";
|
|
31
32
|
import * as crypto$1 from "node:crypto";
|
|
32
33
|
import crypto, { createHash } from "node:crypto";
|
|
33
|
-
import { buildChannelInboundEventContext, formatInboundMediaUnavailableText, toInboundMediaFacts } from "openclaw/plugin-sdk/channel-inbound";
|
|
34
|
+
import { buildChannelInboundEventContext, formatAgentEnvelope, formatInboundMediaUnavailableText, recordChannelBotPairLoopAndCheckSuppression, resolveEnvelopeFormatOptions, toInboundMediaFacts } from "openclaw/plugin-sdk/channel-inbound";
|
|
34
35
|
import { DEFAULT_GROUP_HISTORY_LIMIT, createChannelHistoryWindow } from "openclaw/plugin-sdk/reply-history";
|
|
35
36
|
import { sliceUtf16Safe, truncateUtf16Safe } from "openclaw/plugin-sdk/text-utility-runtime";
|
|
36
|
-
import {
|
|
37
|
+
import { pruneMapToMaxSize } from "openclaw/plugin-sdk/collection-runtime";
|
|
38
|
+
import { createChannelReplayGuard } from "openclaw/plugin-sdk/persistent-dedupe";
|
|
37
39
|
import { resolveChannelConfigWrites } from "openclaw/plugin-sdk/channel-config-writes";
|
|
38
|
-
import { formatReasoningMessage } from "openclaw/plugin-sdk/agent-runtime";
|
|
40
|
+
import { formatReasoningMessage, resolveHumanDelayConfig } from "openclaw/plugin-sdk/agent-runtime";
|
|
39
41
|
import { logTypingFailure } from "openclaw/plugin-sdk/channel-feedback";
|
|
40
42
|
import * as http from "node:http";
|
|
41
43
|
import { installRequestBodyLimitGuard, readWebhookBodyOrReject } from "openclaw/plugin-sdk/webhook-request-guards";
|
|
@@ -478,10 +480,150 @@ async function resolveGroupName(params) {
|
|
|
478
480
|
return resolvedName;
|
|
479
481
|
}
|
|
480
482
|
//#endregion
|
|
483
|
+
//#region extensions/feishu/src/bot-name.ts
|
|
484
|
+
const POSITIVE_TTL_MS = 10 * 6e4;
|
|
485
|
+
const NEGATIVE_TTL_MS = 6e4;
|
|
486
|
+
const MAX_CACHE_ENTRIES = 5e3;
|
|
487
|
+
const BREAKER_FAILURE_THRESHOLD = 10;
|
|
488
|
+
const BREAKER_OPEN_MS = 60 * 6e4;
|
|
489
|
+
const PERMISSION_BACKOFF_MS = 6e4;
|
|
490
|
+
const REQUEST_TIMEOUT_MS = 1500;
|
|
491
|
+
const cache = /* @__PURE__ */ new Map();
|
|
492
|
+
const breakerByAccount = /* @__PURE__ */ new Map();
|
|
493
|
+
const permissionBackoffUntilByAccount = /* @__PURE__ */ new Map();
|
|
494
|
+
const inflight = /* @__PURE__ */ new Map();
|
|
495
|
+
function resolveCacheKey(accountId, openId) {
|
|
496
|
+
return `${accountId}::${openId}`;
|
|
497
|
+
}
|
|
498
|
+
function readCache(key) {
|
|
499
|
+
const entry = cache.get(key);
|
|
500
|
+
if (!entry) return;
|
|
501
|
+
if (entry.expiresAt <= Date.now()) {
|
|
502
|
+
cache.delete(key);
|
|
503
|
+
return;
|
|
504
|
+
}
|
|
505
|
+
cache.delete(key);
|
|
506
|
+
cache.set(key, entry);
|
|
507
|
+
return entry;
|
|
508
|
+
}
|
|
509
|
+
function writeCache(key, entry) {
|
|
510
|
+
cache.delete(key);
|
|
511
|
+
cache.set(key, entry);
|
|
512
|
+
pruneMapToMaxSize(cache, MAX_CACHE_ENTRIES);
|
|
513
|
+
}
|
|
514
|
+
function isBreakerOpen(accountId) {
|
|
515
|
+
const state = breakerByAccount.get(accountId);
|
|
516
|
+
if (!state) return false;
|
|
517
|
+
if (state.openUntil > Date.now()) return true;
|
|
518
|
+
if (state.openUntil > 0) breakerByAccount.delete(accountId);
|
|
519
|
+
return false;
|
|
520
|
+
}
|
|
521
|
+
function recordFailure(accountId) {
|
|
522
|
+
const state = breakerByAccount.get(accountId) ?? {
|
|
523
|
+
failures: 0,
|
|
524
|
+
openUntil: 0
|
|
525
|
+
};
|
|
526
|
+
state.failures += 1;
|
|
527
|
+
if (state.failures >= BREAKER_FAILURE_THRESHOLD) {
|
|
528
|
+
state.failures = 0;
|
|
529
|
+
state.openUntil = Date.now() + BREAKER_OPEN_MS;
|
|
530
|
+
}
|
|
531
|
+
breakerByAccount.set(accountId, state);
|
|
532
|
+
}
|
|
533
|
+
function recordSuccess(accountId) {
|
|
534
|
+
breakerByAccount.delete(accountId);
|
|
535
|
+
}
|
|
536
|
+
function readFeishuErrorCode(error) {
|
|
537
|
+
if (!error || typeof error !== "object") return;
|
|
538
|
+
const data = error.response?.data;
|
|
539
|
+
if (!data || typeof data !== "object") return;
|
|
540
|
+
const code = data.code;
|
|
541
|
+
return typeof code === "number" ? code : void 0;
|
|
542
|
+
}
|
|
543
|
+
async function requestBotName(params) {
|
|
544
|
+
const { account, openId, log } = params;
|
|
545
|
+
const query = new URLSearchParams({ bot_ids: openId });
|
|
546
|
+
try {
|
|
547
|
+
const response = await createFeishuClient(account).request({
|
|
548
|
+
method: "GET",
|
|
549
|
+
url: `/open-apis/bot/v3/bots/basic_batch?${query.toString()}`,
|
|
550
|
+
timeout: REQUEST_TIMEOUT_MS
|
|
551
|
+
});
|
|
552
|
+
const code = response.code ?? 0;
|
|
553
|
+
if (code === 0) return response;
|
|
554
|
+
if (code === 99991672) {
|
|
555
|
+
log(`feishu[${account.accountId}]: bot.basic_info scope not granted`);
|
|
556
|
+
return "permission";
|
|
557
|
+
}
|
|
558
|
+
log(`feishu[${account.accountId}]: bot name lookup failed (code=${code})`);
|
|
559
|
+
return "failure";
|
|
560
|
+
} catch (error) {
|
|
561
|
+
if (readFeishuErrorCode(error) === 99991672) {
|
|
562
|
+
log(`feishu[${account.accountId}]: bot.basic_info scope not granted`);
|
|
563
|
+
return "permission";
|
|
564
|
+
}
|
|
565
|
+
log(`feishu[${account.accountId}]: bot name lookup failed: ${String(error)}`);
|
|
566
|
+
return "failure";
|
|
567
|
+
}
|
|
568
|
+
}
|
|
569
|
+
async function resolveUncachedBotName(params) {
|
|
570
|
+
const { account, openId, cacheKey, log } = params;
|
|
571
|
+
if (isBreakerOpen(account.accountId)) {
|
|
572
|
+
log(`feishu[${account.accountId}]: bot name lookup skipped (breaker open)`);
|
|
573
|
+
return;
|
|
574
|
+
}
|
|
575
|
+
const result = await requestBotName({
|
|
576
|
+
account,
|
|
577
|
+
openId,
|
|
578
|
+
log
|
|
579
|
+
});
|
|
580
|
+
if (result === "permission") {
|
|
581
|
+
permissionBackoffUntilByAccount.set(account.accountId, Date.now() + PERMISSION_BACKOFF_MS);
|
|
582
|
+
return;
|
|
583
|
+
}
|
|
584
|
+
if (result === "failure") {
|
|
585
|
+
recordFailure(account.accountId);
|
|
586
|
+
return;
|
|
587
|
+
}
|
|
588
|
+
recordSuccess(account.accountId);
|
|
589
|
+
const name = result.data?.bots?.[openId]?.name?.trim();
|
|
590
|
+
writeCache(cacheKey, {
|
|
591
|
+
...name ? { name } : {},
|
|
592
|
+
expiresAt: Date.now() + (name ? POSITIVE_TTL_MS : NEGATIVE_TTL_MS)
|
|
593
|
+
});
|
|
594
|
+
return name;
|
|
595
|
+
}
|
|
596
|
+
async function resolveFeishuBotName(params) {
|
|
597
|
+
const openId = params.openId.trim();
|
|
598
|
+
if (!params.account.configured || !openId) return;
|
|
599
|
+
const permissionBackoffUntil = permissionBackoffUntilByAccount.get(params.account.accountId);
|
|
600
|
+
if (permissionBackoffUntil !== void 0) {
|
|
601
|
+
if (permissionBackoffUntil > Date.now()) return;
|
|
602
|
+
permissionBackoffUntilByAccount.delete(params.account.accountId);
|
|
603
|
+
}
|
|
604
|
+
const key = resolveCacheKey(params.account.accountId, openId);
|
|
605
|
+
const cached = readCache(key);
|
|
606
|
+
if (cached) return cached.name;
|
|
607
|
+
const pending = inflight.get(key);
|
|
608
|
+
if (pending) return pending;
|
|
609
|
+
const lookup = resolveUncachedBotName({
|
|
610
|
+
...params,
|
|
611
|
+
openId,
|
|
612
|
+
cacheKey: key
|
|
613
|
+
});
|
|
614
|
+
inflight.set(key, lookup);
|
|
615
|
+
try {
|
|
616
|
+
return await lookup;
|
|
617
|
+
} finally {
|
|
618
|
+
inflight.delete(key);
|
|
619
|
+
}
|
|
620
|
+
}
|
|
621
|
+
//#endregion
|
|
481
622
|
//#region extensions/feishu/src/bot-sender-name.ts
|
|
482
623
|
const IGNORED_PERMISSION_SCOPE_TOKENS = ["contact:contact.base:readonly"];
|
|
483
624
|
const FEISHU_SCOPE_CORRECTIONS = { "contact:contact.base:readonly": "contact:user.base:readonly" };
|
|
484
625
|
const SENDER_NAME_TTL_MS = 600 * 1e3;
|
|
626
|
+
const SENDER_NAME_CACHE_MAX_SIZE = 500;
|
|
485
627
|
const senderNameCache = /* @__PURE__ */ new Map();
|
|
486
628
|
function correctFeishuScopeInUrl(url) {
|
|
487
629
|
let corrected = url;
|
|
@@ -535,10 +677,13 @@ async function resolveFeishuSenderName(params) {
|
|
|
535
677
|
const name = user?.name ?? user?.nickname ?? user?.en_name;
|
|
536
678
|
if (name) {
|
|
537
679
|
const expireAt = resolveExpiresAtMsFromDurationMs(SENDER_NAME_TTL_MS);
|
|
538
|
-
if (expireAt !== void 0)
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
|
|
680
|
+
if (expireAt !== void 0) {
|
|
681
|
+
senderNameCache.set(normalizedSenderId, {
|
|
682
|
+
name,
|
|
683
|
+
expireAt
|
|
684
|
+
});
|
|
685
|
+
pruneMapToMaxSize(senderNameCache, SENDER_NAME_CACHE_MAX_SIZE);
|
|
686
|
+
}
|
|
542
687
|
return { name };
|
|
543
688
|
}
|
|
544
689
|
return {};
|
|
@@ -563,12 +708,15 @@ const DEDUP_TTL_MS = 1440 * 60 * 1e3;
|
|
|
563
708
|
const MEMORY_MAX_SIZE = 1e3;
|
|
564
709
|
const STORE_MAX_ENTRIES = 1e4;
|
|
565
710
|
function createFeishuDedupeGuard() {
|
|
566
|
-
return
|
|
567
|
-
|
|
568
|
-
|
|
569
|
-
|
|
570
|
-
|
|
571
|
-
|
|
711
|
+
return createChannelReplayGuard({
|
|
712
|
+
dedupe: {
|
|
713
|
+
pluginId: "feishu",
|
|
714
|
+
namespacePrefix: DEDUPE_NAMESPACE_PREFIX,
|
|
715
|
+
ttlMs: DEDUP_TTL_MS,
|
|
716
|
+
memoryMaxSize: MEMORY_MAX_SIZE,
|
|
717
|
+
stateMaxEntries: STORE_MAX_ENTRIES
|
|
718
|
+
},
|
|
719
|
+
buildReplayKey: (messageId) => messageId
|
|
572
720
|
});
|
|
573
721
|
}
|
|
574
722
|
const feishuDedupeState = {
|
|
@@ -590,18 +738,13 @@ function dedupeOptions(namespace, log) {
|
|
|
590
738
|
}
|
|
591
739
|
/**
|
|
592
740
|
* Claims a dedupe key for exclusive handling. Duplicate (already committed)
|
|
593
|
-
* and in-flight keys are reported; blank keys fail open as
|
|
741
|
+
* and in-flight keys are reported; blank keys fail open as invalid so an
|
|
594
742
|
* unidentifiable event is never suppressed.
|
|
595
743
|
*/
|
|
596
744
|
async function claimUnprocessedFeishuMessage(params) {
|
|
597
|
-
const
|
|
598
|
-
if (
|
|
599
|
-
return
|
|
600
|
-
}
|
|
601
|
-
/** Drops an uncommitted claim so a failed handler can retry the message. */
|
|
602
|
-
function releaseFeishuMessageProcessing(messageId, namespace = "global") {
|
|
603
|
-
const key = dedupeKey(messageId);
|
|
604
|
-
if (key) feishuDedupeState.guard.release(key, { namespace });
|
|
745
|
+
const claim = await feishuDedupeState.guard.claim(params.messageId, dedupeOptions(params.namespace, params.log));
|
|
746
|
+
if (claim.kind === "inflight") return { kind: "inflight" };
|
|
747
|
+
return claim;
|
|
605
748
|
}
|
|
606
749
|
/**
|
|
607
750
|
* Claims (unless the caller already holds the claim) and commits a message.
|
|
@@ -612,26 +755,22 @@ async function finalizeFeishuMessageProcessing(params) {
|
|
|
612
755
|
const key = dedupeKey(params.messageId);
|
|
613
756
|
if (!key) return false;
|
|
614
757
|
const options = dedupeOptions(params.namespace, params.log);
|
|
615
|
-
|
|
616
|
-
|
|
758
|
+
const claim = params.processingClaim ?? await feishuDedupeState.guard.claim(key, options);
|
|
759
|
+
if ("kind" in claim && claim.kind !== "claimed") return false;
|
|
760
|
+
return await ("kind" in claim ? claim.handle : claim).commit();
|
|
617
761
|
}
|
|
618
762
|
/** Records a handled message so restart/replay cannot dispatch it again; false when already recorded. */
|
|
619
763
|
async function recordProcessedFeishuMessage(messageId, namespace = "global", log) {
|
|
620
|
-
const
|
|
621
|
-
|
|
622
|
-
return await feishuDedupeState.guard.commit(key, dedupeOptions(namespace, log));
|
|
764
|
+
const claim = await feishuDedupeState.guard.claim(messageId, dedupeOptions(namespace, log));
|
|
765
|
+
return claim.kind === "claimed" ? await claim.handle.commit() : false;
|
|
623
766
|
}
|
|
624
767
|
/** Forgets a recorded message so a retryable synthetic event can be handled on redelivery. */
|
|
625
768
|
async function forgetProcessedFeishuMessage(messageId, namespace = "global", log) {
|
|
626
|
-
|
|
627
|
-
if (!key) return false;
|
|
628
|
-
return await feishuDedupeState.guard.forget(key, dedupeOptions(namespace, log));
|
|
769
|
+
return await feishuDedupeState.guard.forget(messageId, dedupeOptions(namespace, log));
|
|
629
770
|
}
|
|
630
771
|
/** Checks recency without claiming or recording. */
|
|
631
772
|
async function hasProcessedFeishuMessage(messageId, namespace = "global", log) {
|
|
632
|
-
|
|
633
|
-
if (!key) return false;
|
|
634
|
-
return await feishuDedupeState.guard.hasRecent(key, dedupeOptions(namespace, log));
|
|
773
|
+
return await feishuDedupeState.guard.hasRecent(messageId, dedupeOptions(namespace, log));
|
|
635
774
|
}
|
|
636
775
|
/** Loads recent persisted entries into memory at account start; returns the loaded count. */
|
|
637
776
|
async function warmupDedupFromPluginState(namespace, log) {
|
|
@@ -1495,11 +1634,6 @@ function rememberStreamingStartFailure(accountId, now = Date.now()) {
|
|
|
1495
1634
|
streamingStartBackoffUntilByAccount.set(accountId, backoffUntil);
|
|
1496
1635
|
return backoffUntil;
|
|
1497
1636
|
}
|
|
1498
|
-
function formatMediaFallbackText(text, mediaUrl) {
|
|
1499
|
-
const trimmedText = text?.trim() ?? "";
|
|
1500
|
-
const attachmentText = `📎 ${mediaUrl}`;
|
|
1501
|
-
return trimmedText ? `${trimmedText}\n\n${attachmentText}` : attachmentText;
|
|
1502
|
-
}
|
|
1503
1637
|
function normalizeEpochMs(timestamp) {
|
|
1504
1638
|
if (!Number.isFinite(timestamp) || timestamp === void 0 || timestamp <= 0) return;
|
|
1505
1639
|
return timestamp < MS_EPOCH_MIN ? timestamp * 1e3 : timestamp;
|
|
@@ -1524,7 +1658,7 @@ function resolveCardNote(agentId, identity, prefixCtx) {
|
|
|
1524
1658
|
}
|
|
1525
1659
|
function createFeishuReplyDispatcher(params) {
|
|
1526
1660
|
const core = getFeishuRuntime();
|
|
1527
|
-
const { cfg, agentId, chatId, sendTarget, replyToMessageId, typingTargetMessageId: explicitTypingTargetMessageId, skipReplyToInMessages, replyInThread, threadReply, rootId, accountId, identity, mentionTargets } = params;
|
|
1661
|
+
const { cfg, agentId, chatId, sendTarget, replyToMessageId, typingTargetMessageId: explicitTypingTargetMessageId, skipReplyToInMessages, replyInThread, threadReply, rootId, accountId, identity, mentionTargets, requiredMentionTargets } = params;
|
|
1528
1662
|
const sendReplyToMessageId = skipReplyToInMessages ? void 0 : replyToMessageId;
|
|
1529
1663
|
const typingTargetMessageId = explicitTypingTargetMessageId?.trim() || replyToMessageId;
|
|
1530
1664
|
const threadReplyMode = threadReply === true;
|
|
@@ -1589,7 +1723,7 @@ function createFeishuReplyDispatcher(params) {
|
|
|
1589
1723
|
channel: "feishu"
|
|
1590
1724
|
});
|
|
1591
1725
|
const renderMode = account.config?.renderMode ?? "auto";
|
|
1592
|
-
const streamingEnabled = resolveChannelPreviewStreamMode(account.config, "partial") !== "off" && renderMode !== "raw";
|
|
1726
|
+
const streamingEnabled = !requiredMentionTargets?.length && resolveChannelPreviewStreamMode(account.config, "partial") !== "off" && renderMode !== "raw";
|
|
1593
1727
|
const blockStreamingEnabled = resolveChannelStreamingBlockEnabled(account.config);
|
|
1594
1728
|
const coreBlockStreamingEnabled = blockStreamingEnabled === true;
|
|
1595
1729
|
const reasoningPreviewEnabled = streamingEnabled && params.allowReasoningPreview === true;
|
|
@@ -1737,12 +1871,15 @@ function createFeishuReplyDispatcher(params) {
|
|
|
1737
1871
|
limit: textChunkLimit,
|
|
1738
1872
|
mode: chunkMode,
|
|
1739
1873
|
firstChunkMentions: paramsLocal.firstChunkMentions,
|
|
1874
|
+
chunkMentions: paramsLocal.chunkMentions,
|
|
1740
1875
|
initialChunks
|
|
1741
1876
|
}));
|
|
1742
1877
|
for (const [index, chunk] of chunks.entries()) {
|
|
1878
|
+
const mentions = [...paramsLocal.chunkMentions ?? [], ...index === 0 ? paramsLocal.firstChunkMentions ?? [] : []];
|
|
1743
1879
|
await paramsLocal.sendChunk({
|
|
1744
1880
|
chunk,
|
|
1745
|
-
isFirst: index === 0
|
|
1881
|
+
isFirst: index === 0,
|
|
1882
|
+
mentions: mentions.length > 0 ? mentions : void 0
|
|
1746
1883
|
});
|
|
1747
1884
|
markVisibleReplySent();
|
|
1748
1885
|
}
|
|
@@ -1771,7 +1908,8 @@ function createFeishuReplyDispatcher(params) {
|
|
|
1771
1908
|
text: options.fallbackText,
|
|
1772
1909
|
useCard: false,
|
|
1773
1910
|
infoKind: "final",
|
|
1774
|
-
|
|
1911
|
+
chunkMentions: requiredMentionTargets,
|
|
1912
|
+
sendChunk: async ({ chunk, mentions }) => {
|
|
1775
1913
|
await sendMessageFeishu({
|
|
1776
1914
|
cfg,
|
|
1777
1915
|
to: sendTarget,
|
|
@@ -1779,20 +1917,25 @@ function createFeishuReplyDispatcher(params) {
|
|
|
1779
1917
|
replyToMessageId: sendReplyToMessageId,
|
|
1780
1918
|
replyInThread: effectiveReplyInThread,
|
|
1781
1919
|
allowTopLevelReplyFallback,
|
|
1782
|
-
accountId
|
|
1920
|
+
accountId,
|
|
1921
|
+
...mentions ? { mentions } : {}
|
|
1783
1922
|
});
|
|
1784
1923
|
}
|
|
1785
1924
|
});
|
|
1786
1925
|
}
|
|
1787
1926
|
},
|
|
1788
1927
|
onError: options?.fallbackText === void 0 ? void 0 : async ({ mediaUrl }) => {
|
|
1789
|
-
const fallbackText =
|
|
1928
|
+
const fallbackText = await buildFeishuMediaFallbackText({
|
|
1929
|
+
text: sentFallbackText ? void 0 : options.fallbackText,
|
|
1930
|
+
mediaUrl
|
|
1931
|
+
});
|
|
1790
1932
|
sentFallbackText = true;
|
|
1791
1933
|
await sendChunkedTextReply({
|
|
1792
1934
|
text: fallbackText,
|
|
1793
1935
|
useCard: false,
|
|
1794
1936
|
infoKind: "final",
|
|
1795
|
-
|
|
1937
|
+
chunkMentions: requiredMentionTargets,
|
|
1938
|
+
sendChunk: async ({ chunk, mentions }) => {
|
|
1796
1939
|
await sendMessageFeishu({
|
|
1797
1940
|
cfg,
|
|
1798
1941
|
to: sendTarget,
|
|
@@ -1800,7 +1943,8 @@ function createFeishuReplyDispatcher(params) {
|
|
|
1800
1943
|
replyToMessageId: sendReplyToMessageId,
|
|
1801
1944
|
replyInThread: effectiveReplyInThread,
|
|
1802
1945
|
allowTopLevelReplyFallback,
|
|
1803
|
-
accountId
|
|
1946
|
+
accountId,
|
|
1947
|
+
...mentions ? { mentions } : {}
|
|
1804
1948
|
});
|
|
1805
1949
|
}
|
|
1806
1950
|
});
|
|
@@ -1821,7 +1965,8 @@ function createFeishuReplyDispatcher(params) {
|
|
|
1821
1965
|
replyToMessageId: sendReplyToMessageId,
|
|
1822
1966
|
replyInThread: effectiveReplyInThread,
|
|
1823
1967
|
allowTopLevelReplyFallback,
|
|
1824
|
-
accountId
|
|
1968
|
+
accountId,
|
|
1969
|
+
...requiredMentionTargets?.length ? { mentions: requiredMentionTargets } : {}
|
|
1825
1970
|
});
|
|
1826
1971
|
markVisibleReplySent();
|
|
1827
1972
|
params.runtime.error?.(`feishu[${account.accountId}]: sent no-visible-reply fallback (${reason})`);
|
|
@@ -1835,10 +1980,10 @@ function createFeishuReplyDispatcher(params) {
|
|
|
1835
1980
|
idleSideEffectsPromise = nextIdleSideEffects.catch(() => {});
|
|
1836
1981
|
return nextIdleSideEffects;
|
|
1837
1982
|
};
|
|
1838
|
-
const
|
|
1983
|
+
const dispatcherOptions = {
|
|
1839
1984
|
responsePrefix: prefixContext.responsePrefix,
|
|
1840
1985
|
responsePrefixContextProvider: prefixContext.responsePrefixContextProvider,
|
|
1841
|
-
humanDelay:
|
|
1986
|
+
humanDelay: resolveHumanDelayConfig(cfg, agentId),
|
|
1842
1987
|
silentReplyContext: {
|
|
1843
1988
|
cfg,
|
|
1844
1989
|
sessionKey: params.sessionKey,
|
|
@@ -1861,142 +2006,151 @@ function createFeishuReplyDispatcher(params) {
|
|
|
1861
2006
|
if (streamingEnabled && renderMode === "card") startStreaming();
|
|
1862
2007
|
await Promise.resolve(typingCallbacks?.onReplyStart?.());
|
|
1863
2008
|
},
|
|
1864
|
-
|
|
1865
|
-
|
|
1866
|
-
|
|
1867
|
-
|
|
1868
|
-
|
|
1869
|
-
|
|
1870
|
-
|
|
1871
|
-
|
|
1872
|
-
|
|
1873
|
-
|
|
1874
|
-
|
|
1875
|
-
|
|
1876
|
-
|
|
1877
|
-
|
|
1878
|
-
|
|
1879
|
-
|
|
1880
|
-
|
|
1881
|
-
|
|
1882
|
-
|
|
1883
|
-
|
|
1884
|
-
|
|
1885
|
-
|
|
1886
|
-
|
|
1887
|
-
|
|
1888
|
-
|
|
1889
|
-
|
|
1890
|
-
|
|
1891
|
-
|
|
1892
|
-
|
|
1893
|
-
|
|
1894
|
-
|
|
1895
|
-
|
|
1896
|
-
|
|
1897
|
-
|
|
1898
|
-
|
|
1899
|
-
|
|
1900
|
-
|
|
1901
|
-
|
|
1902
|
-
|
|
1903
|
-
|
|
1904
|
-
|
|
1905
|
-
|
|
1906
|
-
|
|
1907
|
-
|
|
1908
|
-
|
|
1909
|
-
|
|
1910
|
-
|
|
1911
|
-
|
|
1912
|
-
|
|
1913
|
-
|
|
2009
|
+
onIdle: () => queueIdleSideEffects(),
|
|
2010
|
+
onCleanup: () => {
|
|
2011
|
+
typingCallbacks?.onCleanup?.();
|
|
2012
|
+
}
|
|
2013
|
+
};
|
|
2014
|
+
const handleDeliveryError = async (error, info) => {
|
|
2015
|
+
streamingCloseErroredForReply = true;
|
|
2016
|
+
streamingClosedForReply = false;
|
|
2017
|
+
params.runtime.error?.(`feishu[${account.accountId}] ${info.kind} reply failed: ${String(error)}`);
|
|
2018
|
+
await queueIdleSideEffects({ markClosedForReply: false }).catch((cleanupError) => params.runtime.error?.(`feishu[${account.accountId}] reply error cleanup failed: ${String(cleanupError)}`));
|
|
2019
|
+
};
|
|
2020
|
+
return {
|
|
2021
|
+
dispatcherOptions,
|
|
2022
|
+
delivery: {
|
|
2023
|
+
deliver: async (payload, info) => {
|
|
2024
|
+
if (info?.kind === "final") skippedFinalReason = null;
|
|
2025
|
+
const payloadText = payload.isReasoning && payload.text ? formatReasoningMessage(payload.text) : payload.text;
|
|
2026
|
+
const reply = resolveSendableOutboundReplyParts({
|
|
2027
|
+
...payload,
|
|
2028
|
+
text: payloadText
|
|
2029
|
+
});
|
|
2030
|
+
const text = info?.kind === "final" ? mergeStreamingFinalText(streamText, reply.text, payload.isError === true && hasStreamingFinalText) : reply.text;
|
|
2031
|
+
const hasText = reply.hasText;
|
|
2032
|
+
const hasMedia = reply.hasMedia;
|
|
2033
|
+
const ttsSupplement = getReplyPayloadTtsSupplement(payload);
|
|
2034
|
+
const ttsTextAlreadyVisible = ttsSupplement?.visibleTextAlreadyDelivered === true;
|
|
2035
|
+
const hasVoiceMedia = hasMedia && reply.mediaUrls.some((mediaUrl) => shouldSuppressFeishuTextForVoiceMedia({
|
|
2036
|
+
mediaUrl,
|
|
2037
|
+
...payload.audioAsVoice === true ? { audioAsVoice: true } : {},
|
|
2038
|
+
ttsSupplement
|
|
2039
|
+
}));
|
|
2040
|
+
const finalTextExceedsStreamingLimit = info?.kind === "final" && hasText && text.length > textChunkLimit;
|
|
2041
|
+
const useStaticCard = hasText && (renderMode === "card" || info?.kind === "block" && coreBlockStreamingEnabled && renderMode !== "raw" || renderMode === "auto" && shouldUseCard(text));
|
|
2042
|
+
const useStreamingCard = hasText && streamingEnabled && !finalTextExceedsStreamingLimit && (info?.kind === "final" || useStaticCard);
|
|
2043
|
+
const finalTextWouldUseStreamingCard = info?.kind === "final" && hasText && streamingEnabled;
|
|
2044
|
+
const useCard = useStaticCard || useStreamingCard;
|
|
2045
|
+
const skipTextForDuplicateFinal = info?.kind === "final" && hasText && deliveredFinalTexts.has(text);
|
|
2046
|
+
const skipTextForClosedStreamingFinal = info?.kind === "final" && hasText && streamingClosedForReply && !streamingCloseErroredForReply && finalTextWouldUseStreamingCard;
|
|
2047
|
+
const shouldDeliverText = hasText && !hasVoiceMedia && !skipTextForDuplicateFinal && !skipTextForClosedStreamingFinal;
|
|
2048
|
+
const shouldDiscardStreamingPreview = info?.kind === "final" && (finalTextExceedsStreamingLimit || hasMedia && (hasVoiceMedia && !shouldDeliverText && !ttsTextAlreadyVisible || skipTextForDuplicateFinal));
|
|
2049
|
+
if (!shouldDeliverText && !hasMedia) return;
|
|
2050
|
+
if (shouldDiscardStreamingPreview) await discardStreamingPreview();
|
|
2051
|
+
if (shouldDeliverText) {
|
|
2052
|
+
if (info?.kind === "block") {
|
|
2053
|
+
if (!useStreamingCard) {
|
|
2054
|
+
if (coreBlockStreamingEnabled) {
|
|
2055
|
+
const firstChunkMentions = !sentIndependentBlockText && mentionTargets?.length ? mentionTargets : void 0;
|
|
2056
|
+
await sendChunkedTextReply({
|
|
2057
|
+
text,
|
|
2058
|
+
useCard: false,
|
|
2059
|
+
infoKind: "block",
|
|
2060
|
+
firstChunkMentions,
|
|
2061
|
+
chunkMentions: requiredMentionTargets,
|
|
2062
|
+
sendChunk: async ({ chunk, mentions }) => {
|
|
2063
|
+
await sendMessageFeishu({
|
|
2064
|
+
cfg,
|
|
2065
|
+
to: sendTarget,
|
|
2066
|
+
text: chunk,
|
|
2067
|
+
replyToMessageId: sendReplyToMessageId,
|
|
2068
|
+
replyInThread: effectiveReplyInThread,
|
|
2069
|
+
allowTopLevelReplyFallback,
|
|
2070
|
+
accountId,
|
|
2071
|
+
...mentions ? { mentions } : {}
|
|
2072
|
+
});
|
|
2073
|
+
}
|
|
2074
|
+
});
|
|
2075
|
+
sentIndependentBlockText = true;
|
|
2076
|
+
if (hasMedia) await sendMediaReplies(payload);
|
|
2077
|
+
}
|
|
2078
|
+
return;
|
|
1914
2079
|
}
|
|
1915
|
-
|
|
2080
|
+
startStreaming();
|
|
2081
|
+
if (streamingStartPromise) await streamingStartPromise;
|
|
1916
2082
|
}
|
|
1917
|
-
|
|
1918
|
-
|
|
1919
|
-
|
|
1920
|
-
if (info?.kind === "final" && useStreamingCard) {
|
|
1921
|
-
startStreaming();
|
|
1922
|
-
if (streamingStartPromise) await streamingStartPromise;
|
|
1923
|
-
}
|
|
1924
|
-
const shouldStreamText = info?.kind === "block" || info?.kind === "final";
|
|
1925
|
-
if (streaming?.isActive() && shouldStreamText) {
|
|
1926
|
-
if (info?.kind === "block") queueStreamingUpdate(text, {
|
|
1927
|
-
mode: "delta",
|
|
1928
|
-
dedupeWithLastPartial: true
|
|
1929
|
-
});
|
|
1930
|
-
if (info?.kind === "final") {
|
|
1931
|
-
streamText = text;
|
|
1932
|
-
hasStreamingFinalText = true;
|
|
1933
|
-
snapshotBaseText = "";
|
|
1934
|
-
lastSnapshotTextLength = text.length;
|
|
1935
|
-
flushStreamingCardUpdate(buildCombinedStreamText(reasoningText, streamText));
|
|
2083
|
+
if (info?.kind === "final" && useStreamingCard) {
|
|
2084
|
+
startStreaming();
|
|
2085
|
+
if (streamingStartPromise) await streamingStartPromise;
|
|
1936
2086
|
}
|
|
1937
|
-
|
|
1938
|
-
|
|
1939
|
-
|
|
1940
|
-
|
|
1941
|
-
|
|
1942
|
-
|
|
1943
|
-
|
|
1944
|
-
|
|
1945
|
-
|
|
1946
|
-
|
|
1947
|
-
|
|
1948
|
-
|
|
1949
|
-
cfg,
|
|
1950
|
-
to: sendTarget,
|
|
1951
|
-
text: chunk,
|
|
1952
|
-
replyToMessageId: sendReplyToMessageId,
|
|
1953
|
-
replyInThread: effectiveReplyInThread,
|
|
1954
|
-
allowTopLevelReplyFallback,
|
|
1955
|
-
accountId,
|
|
1956
|
-
header: cardHeader,
|
|
1957
|
-
note: cardNote
|
|
1958
|
-
});
|
|
1959
|
-
}
|
|
1960
|
-
});
|
|
1961
|
-
} else {
|
|
1962
|
-
const firstChunkMentions = info?.kind === "final" && mentionTargets?.length ? mentionTargets : void 0;
|
|
1963
|
-
await sendChunkedTextReply({
|
|
1964
|
-
text,
|
|
1965
|
-
useCard: false,
|
|
1966
|
-
infoKind: info?.kind,
|
|
1967
|
-
firstChunkMentions,
|
|
1968
|
-
sendChunk: async ({ chunk, isFirst }) => {
|
|
1969
|
-
await sendMessageFeishu({
|
|
1970
|
-
cfg,
|
|
1971
|
-
to: sendTarget,
|
|
1972
|
-
text: chunk,
|
|
1973
|
-
replyToMessageId: sendReplyToMessageId,
|
|
1974
|
-
replyInThread: effectiveReplyInThread,
|
|
1975
|
-
allowTopLevelReplyFallback,
|
|
1976
|
-
accountId,
|
|
1977
|
-
...isFirst && firstChunkMentions ? { mentions: firstChunkMentions } : {}
|
|
1978
|
-
});
|
|
2087
|
+
const shouldStreamText = info?.kind === "block" || info?.kind === "final";
|
|
2088
|
+
if (streaming?.isActive() && shouldStreamText) {
|
|
2089
|
+
if (info?.kind === "block") queueStreamingUpdate(text, {
|
|
2090
|
+
mode: "delta",
|
|
2091
|
+
dedupeWithLastPartial: true
|
|
2092
|
+
});
|
|
2093
|
+
if (info?.kind === "final") {
|
|
2094
|
+
streamText = text;
|
|
2095
|
+
hasStreamingFinalText = true;
|
|
2096
|
+
snapshotBaseText = "";
|
|
2097
|
+
lastSnapshotTextLength = text.length;
|
|
2098
|
+
flushStreamingCardUpdate(buildCombinedStreamText(reasoningText, streamText));
|
|
1979
2099
|
}
|
|
1980
|
-
|
|
2100
|
+
if (hasMedia) await sendMediaReplies(payload);
|
|
2101
|
+
return;
|
|
2102
|
+
}
|
|
2103
|
+
if (useCard) {
|
|
2104
|
+
const cardHeader = resolveCardHeader(agentId, identity);
|
|
2105
|
+
const cardNote = resolveCardNote(agentId, identity, prefixContext.prefixContext);
|
|
2106
|
+
await sendChunkedTextReply({
|
|
2107
|
+
text,
|
|
2108
|
+
useCard: true,
|
|
2109
|
+
infoKind: info?.kind,
|
|
2110
|
+
chunkMentions: requiredMentionTargets,
|
|
2111
|
+
sendChunk: async ({ chunk, mentions }) => {
|
|
2112
|
+
await sendStructuredCardFeishu({
|
|
2113
|
+
cfg,
|
|
2114
|
+
to: sendTarget,
|
|
2115
|
+
text: chunk,
|
|
2116
|
+
replyToMessageId: sendReplyToMessageId,
|
|
2117
|
+
replyInThread: effectiveReplyInThread,
|
|
2118
|
+
allowTopLevelReplyFallback,
|
|
2119
|
+
accountId,
|
|
2120
|
+
header: cardHeader,
|
|
2121
|
+
note: cardNote,
|
|
2122
|
+
...mentions ? { mentions } : {}
|
|
2123
|
+
});
|
|
2124
|
+
}
|
|
2125
|
+
});
|
|
2126
|
+
} else {
|
|
2127
|
+
const firstChunkMentions = info?.kind === "final" && mentionTargets?.length ? mentionTargets : void 0;
|
|
2128
|
+
await sendChunkedTextReply({
|
|
2129
|
+
text,
|
|
2130
|
+
useCard: false,
|
|
2131
|
+
infoKind: info?.kind,
|
|
2132
|
+
firstChunkMentions,
|
|
2133
|
+
chunkMentions: requiredMentionTargets,
|
|
2134
|
+
sendChunk: async ({ chunk, mentions }) => {
|
|
2135
|
+
await sendMessageFeishu({
|
|
2136
|
+
cfg,
|
|
2137
|
+
to: sendTarget,
|
|
2138
|
+
text: chunk,
|
|
2139
|
+
replyToMessageId: sendReplyToMessageId,
|
|
2140
|
+
replyInThread: effectiveReplyInThread,
|
|
2141
|
+
allowTopLevelReplyFallback,
|
|
2142
|
+
accountId,
|
|
2143
|
+
...mentions ? { mentions } : {}
|
|
2144
|
+
});
|
|
2145
|
+
}
|
|
2146
|
+
});
|
|
2147
|
+
}
|
|
1981
2148
|
}
|
|
1982
|
-
|
|
1983
|
-
|
|
1984
|
-
|
|
1985
|
-
onError: async (error, info) => {
|
|
1986
|
-
streamingCloseErroredForReply = true;
|
|
1987
|
-
streamingClosedForReply = false;
|
|
1988
|
-
params.runtime.error?.(`feishu[${account.accountId}] ${info.kind} reply failed: ${String(error)}`);
|
|
1989
|
-
await queueIdleSideEffects({ markClosedForReply: false });
|
|
2149
|
+
if (hasMedia) await sendMediaReplies(payload, hasVoiceMedia && hasText ? { fallbackText: text } : void 0);
|
|
2150
|
+
},
|
|
2151
|
+
onError: handleDeliveryError
|
|
1990
2152
|
},
|
|
1991
|
-
onIdle: () => queueIdleSideEffects(),
|
|
1992
|
-
onCleanup: () => {
|
|
1993
|
-
typingCallbacks?.onCleanup?.();
|
|
1994
|
-
}
|
|
1995
|
-
});
|
|
1996
|
-
return {
|
|
1997
|
-
dispatcher,
|
|
1998
2153
|
replyOptions: {
|
|
1999
|
-
...replyOptions,
|
|
2000
2154
|
onModelSelected: prefixContext.onModelSelected,
|
|
2001
2155
|
disableBlockStreaming: typeof blockStreamingEnabled === "boolean" ? !blockStreamingEnabled : true,
|
|
2002
2156
|
onPartialReply: streamingEnabled ? (payload) => {
|
|
@@ -2038,7 +2192,6 @@ function createFeishuReplyDispatcher(params) {
|
|
|
2038
2192
|
updateStreamingStatusLine("");
|
|
2039
2193
|
} : void 0
|
|
2040
2194
|
},
|
|
2041
|
-
markDispatchIdle,
|
|
2042
2195
|
ensureNoVisibleReplyFallback,
|
|
2043
2196
|
getVisibleReplyState: () => ({
|
|
2044
2197
|
visibleReplySent,
|
|
@@ -2047,6 +2200,17 @@ function createFeishuReplyDispatcher(params) {
|
|
|
2047
2200
|
};
|
|
2048
2201
|
}
|
|
2049
2202
|
//#endregion
|
|
2203
|
+
//#region extensions/feishu/src/synthetic-event-target.ts
|
|
2204
|
+
const directPreDispatchTargets = /* @__PURE__ */ new WeakMap();
|
|
2205
|
+
/** Keep synthetic-only routing metadata outside the public Feishu event shape. */
|
|
2206
|
+
function setFeishuSyntheticDirectPreDispatchTarget(event, target) {
|
|
2207
|
+
directPreDispatchTargets.set(event, target);
|
|
2208
|
+
return event;
|
|
2209
|
+
}
|
|
2210
|
+
function getFeishuSyntheticDirectPreDispatchTarget(event) {
|
|
2211
|
+
return directPreDispatchTargets.get(event);
|
|
2212
|
+
}
|
|
2213
|
+
//#endregion
|
|
2050
2214
|
//#region extensions/feishu/src/bot.ts
|
|
2051
2215
|
const permissionErrorNotifiedAt = /* @__PURE__ */ new Map();
|
|
2052
2216
|
const PERMISSION_ERROR_COOLDOWN_MS = 300 * 1e3;
|
|
@@ -2099,6 +2263,7 @@ function parseFeishuMessageEvent(event, botOpenId, _botName) {
|
|
|
2099
2263
|
suppressReplyTarget: event.message.suppress_reply_target === true,
|
|
2100
2264
|
senderId: senderUserId || senderOpenId || "",
|
|
2101
2265
|
senderOpenId: senderFallbackId,
|
|
2266
|
+
senderType: event.sender.sender_type === "bot" ? "bot" : "user",
|
|
2102
2267
|
chatType: event.message.chat_type,
|
|
2103
2268
|
mentionedBot,
|
|
2104
2269
|
hasAnyMention,
|
|
@@ -2148,7 +2313,7 @@ async function filterFetchedGroupContextMessages(messages, params) {
|
|
|
2148
2313
|
}) ? message : void 0))).filter((message) => message !== void 0);
|
|
2149
2314
|
}
|
|
2150
2315
|
async function handleFeishuMessage(params) {
|
|
2151
|
-
const { cfg, event, botOpenId, botName, runtime, channelRuntime, chatHistories, accountId,
|
|
2316
|
+
const { cfg, event, botOpenId, botName, runtime, channelRuntime, chatHistories, accountId, processingClaim, messageDedupeKey: messageDedupeKeyOverride } = params;
|
|
2152
2317
|
const account = resolveFeishuRuntimeAccount({
|
|
2153
2318
|
cfg,
|
|
2154
2319
|
accountId
|
|
@@ -2162,7 +2327,7 @@ async function handleFeishuMessage(params) {
|
|
|
2162
2327
|
messageId: messageDedupeKey,
|
|
2163
2328
|
namespace: account.accountId,
|
|
2164
2329
|
log,
|
|
2165
|
-
|
|
2330
|
+
processingClaim
|
|
2166
2331
|
})) {
|
|
2167
2332
|
log(`feishu: skipping duplicate message ${messageId}`);
|
|
2168
2333
|
return;
|
|
@@ -2170,7 +2335,62 @@ async function handleFeishuMessage(params) {
|
|
|
2170
2335
|
let ctx = parseFeishuMessageEvent(event, botOpenId, botName);
|
|
2171
2336
|
const isGroup = isFeishuGroupChatType(ctx.chatType);
|
|
2172
2337
|
const isDirect = !isGroup;
|
|
2338
|
+
const directPreDispatchTarget = isDirect ? getFeishuSyntheticDirectPreDispatchTarget(event) : void 0;
|
|
2173
2339
|
const senderUserId = normalizeOptionalString(event.sender.sender_id.user_id);
|
|
2340
|
+
const localBotOpenId = botOpenId?.trim();
|
|
2341
|
+
if (ctx.senderType === "bot") {
|
|
2342
|
+
if (!localBotOpenId) {
|
|
2343
|
+
log(`feishu[${account.accountId}]: dropping bot message ${ctx.messageId} (local bot identity unavailable)`);
|
|
2344
|
+
return;
|
|
2345
|
+
}
|
|
2346
|
+
if (ctx.senderOpenId === localBotOpenId) {
|
|
2347
|
+
log(`feishu[${account.accountId}]: dropping self-authored bot message ${ctx.messageId}`);
|
|
2348
|
+
return;
|
|
2349
|
+
}
|
|
2350
|
+
if (feishuCfg?.allowBots !== true) {
|
|
2351
|
+
log(`feishu[${account.accountId}]: dropping bot message ${ctx.messageId} (allowBots=false)`);
|
|
2352
|
+
return;
|
|
2353
|
+
}
|
|
2354
|
+
if (isGroup && !ctx.mentionedBot) {
|
|
2355
|
+
let verifiedEvent;
|
|
2356
|
+
try {
|
|
2357
|
+
const response = await createFeishuClient(account).im.message.get({
|
|
2358
|
+
params: { user_id_type: "open_id" },
|
|
2359
|
+
path: { message_id: ctx.messageId }
|
|
2360
|
+
});
|
|
2361
|
+
const verifiedKey = (response.data?.items?.[0]?.mentions?.find((mention) => mention.id_type === "open_id" && mention.id === localBotOpenId && Boolean(mention.key?.trim())))?.key?.trim();
|
|
2362
|
+
if (response.code === 0 && verifiedKey) {
|
|
2363
|
+
const eventMention = event.message.mentions?.find((mention) => mention.key === verifiedKey && !isFeishuBroadcastMention(mention));
|
|
2364
|
+
if (eventMention) verifiedEvent = {
|
|
2365
|
+
...event,
|
|
2366
|
+
message: {
|
|
2367
|
+
...event.message,
|
|
2368
|
+
mentions: event.message.mentions?.map((mention) => mention === eventMention ? {
|
|
2369
|
+
...mention,
|
|
2370
|
+
id: {
|
|
2371
|
+
...mention.id,
|
|
2372
|
+
open_id: localBotOpenId
|
|
2373
|
+
}
|
|
2374
|
+
} : mention)
|
|
2375
|
+
}
|
|
2376
|
+
};
|
|
2377
|
+
}
|
|
2378
|
+
} catch (err) {
|
|
2379
|
+
log(`feishu[${account.accountId}]: failed to verify bot mention for ${ctx.messageId}: ${String(err)}`);
|
|
2380
|
+
}
|
|
2381
|
+
if (!verifiedEvent) {
|
|
2382
|
+
log(`feishu[${account.accountId}]: dropping bot message ${ctx.messageId} (local mention not verifiable)`);
|
|
2383
|
+
return;
|
|
2384
|
+
}
|
|
2385
|
+
const deliveredCtx = parseFeishuMessageEvent(verifiedEvent, localBotOpenId, botName);
|
|
2386
|
+
ctx = {
|
|
2387
|
+
...deliveredCtx,
|
|
2388
|
+
mentionedBot: true,
|
|
2389
|
+
content: normalizeFeishuCommandProbeBody(deliveredCtx.content),
|
|
2390
|
+
mentionTargets: void 0
|
|
2391
|
+
};
|
|
2392
|
+
}
|
|
2393
|
+
}
|
|
2174
2394
|
if (event.message.message_type === "merge_forward") {
|
|
2175
2395
|
log(`feishu[${account.accountId}]: processing merge_forward message, fetching full content via API`);
|
|
2176
2396
|
try {
|
|
@@ -2201,7 +2421,17 @@ async function handleFeishuMessage(params) {
|
|
|
2201
2421
|
}
|
|
2202
2422
|
}
|
|
2203
2423
|
let permissionErrorForAgent;
|
|
2204
|
-
if (feishuCfg?.resolveSenderNames ?? true) {
|
|
2424
|
+
if (feishuCfg?.resolveSenderNames ?? true) if (ctx.senderType === "bot") {
|
|
2425
|
+
const senderName = await resolveFeishuBotName({
|
|
2426
|
+
account,
|
|
2427
|
+
openId: ctx.senderOpenId,
|
|
2428
|
+
log
|
|
2429
|
+
});
|
|
2430
|
+
if (senderName) ctx = {
|
|
2431
|
+
...ctx,
|
|
2432
|
+
senderName
|
|
2433
|
+
};
|
|
2434
|
+
} else {
|
|
2205
2435
|
const senderResult = await resolveFeishuSenderName({
|
|
2206
2436
|
account,
|
|
2207
2437
|
senderId: ctx.senderOpenId,
|
|
@@ -2338,6 +2568,23 @@ async function handleFeishuMessage(params) {
|
|
|
2338
2568
|
});
|
|
2339
2569
|
return;
|
|
2340
2570
|
}
|
|
2571
|
+
if (ctx.senderType === "bot") {
|
|
2572
|
+
if (!localBotOpenId || !ctx.senderOpenId) {
|
|
2573
|
+
log(`feishu[${account.accountId}]: dropping bot message ${ctx.messageId} (loop identity unavailable)`);
|
|
2574
|
+
return;
|
|
2575
|
+
}
|
|
2576
|
+
if (recordChannelBotPairLoopAndCheckSuppression({
|
|
2577
|
+
scopeId: account.accountId,
|
|
2578
|
+
conversationId: ctx.chatId,
|
|
2579
|
+
senderId: ctx.senderOpenId,
|
|
2580
|
+
receiverId: localBotOpenId,
|
|
2581
|
+
defaultsConfig: cfg.channels?.defaults?.botLoopProtection,
|
|
2582
|
+
defaultEnabled: true
|
|
2583
|
+
}).suppressed) {
|
|
2584
|
+
log(`feishu[${account.accountId}]: bot-pair loop suppressed for ${ctx.senderOpenId} in ${ctx.chatId}`);
|
|
2585
|
+
return;
|
|
2586
|
+
}
|
|
2587
|
+
}
|
|
2341
2588
|
}
|
|
2342
2589
|
try {
|
|
2343
2590
|
const core = { channel: channelRuntime?.inbound ? channelRuntime : getFeishuRuntime().channel };
|
|
@@ -2385,7 +2632,7 @@ async function handleFeishuMessage(params) {
|
|
|
2385
2632
|
sendPairingReply: async (text) => {
|
|
2386
2633
|
await sendMessageFeishu({
|
|
2387
2634
|
cfg: authorization.cfg,
|
|
2388
|
-
to: `chat:${ctx.chatId}`,
|
|
2635
|
+
to: directPreDispatchTarget ?? `chat:${ctx.chatId}`,
|
|
2389
2636
|
text,
|
|
2390
2637
|
accountId: account.accountId
|
|
2391
2638
|
});
|
|
@@ -2520,7 +2767,7 @@ async function handleFeishuMessage(params) {
|
|
|
2520
2767
|
const replyTargetMessageId = directThreadReply ? directThreadReplyTargetMessageId : acpTopicReply ? ctx.rootId ?? ctx.messageId : ctx.messageId;
|
|
2521
2768
|
await sendMessageFeishu({
|
|
2522
2769
|
cfg: effectiveCfg,
|
|
2523
|
-
to: `chat:${ctx.chatId}`,
|
|
2770
|
+
to: directPreDispatchTarget ?? `chat:${ctx.chatId}`,
|
|
2524
2771
|
text: `⚠️ Failed to initialize the configured ACP session for this Feishu conversation: ${ensured.error}`,
|
|
2525
2772
|
replyToMessageId: replyTargetMessageId,
|
|
2526
2773
|
replyInThread,
|
|
@@ -2594,6 +2841,11 @@ async function handleFeishuMessage(params) {
|
|
|
2594
2841
|
});
|
|
2595
2842
|
const preflightAudioIndex = audioTranscript === void 0 ? -1 : mediaList.findIndex((media) => media.contentType?.startsWith("audio/"));
|
|
2596
2843
|
const inboundMedia = toInboundMediaFacts(mediaList, { transcribed: (_media, index) => index === preflightAudioIndex });
|
|
2844
|
+
const requiredMentionTargets = isGroup && ctx.senderType === "bot" && ctx.senderOpenId ? [{
|
|
2845
|
+
openId: ctx.senderOpenId,
|
|
2846
|
+
name: ctx.senderName ?? ctx.senderOpenId,
|
|
2847
|
+
key: ""
|
|
2848
|
+
}] : void 0;
|
|
2597
2849
|
const agentFacingContent = audioTranscript ?? mediaFailureContent;
|
|
2598
2850
|
const commandFacingContent = audioTranscript ?? ctx.content;
|
|
2599
2851
|
const agentFacingCtx = agentFacingContent === ctx.content ? ctx : {
|
|
@@ -2624,7 +2876,7 @@ async function handleFeishuMessage(params) {
|
|
|
2624
2876
|
command: { hasControlCommand: true }
|
|
2625
2877
|
})).commandAccess.authorized : void 0;
|
|
2626
2878
|
const isTopicSessionForThread = isGroup && (groupSession?.groupSessionScope === "group_topic" || groupSession?.groupSessionScope === "group_topic_sender");
|
|
2627
|
-
const envelopeOptions =
|
|
2879
|
+
const envelopeOptions = resolveEnvelopeFormatOptions(cfg);
|
|
2628
2880
|
const messageBody = buildFeishuAgentBody({
|
|
2629
2881
|
ctx: agentFacingCtx,
|
|
2630
2882
|
quotedContent,
|
|
@@ -2633,7 +2885,7 @@ async function handleFeishuMessage(params) {
|
|
|
2633
2885
|
});
|
|
2634
2886
|
const envelopeFrom = isGroup ? `${ctx.chatId}:${ctx.senderOpenId}` : ctx.senderOpenId;
|
|
2635
2887
|
if (permissionErrorForAgent) log(`feishu[${account.accountId}]: appending permission error notice to message body`);
|
|
2636
|
-
let combinedBody =
|
|
2888
|
+
let combinedBody = formatAgentEnvelope({
|
|
2637
2889
|
channel: "Feishu",
|
|
2638
2890
|
from: envelopeFrom,
|
|
2639
2891
|
timestamp: /* @__PURE__ */ new Date(),
|
|
@@ -2645,7 +2897,7 @@ async function handleFeishuMessage(params) {
|
|
|
2645
2897
|
historyKey,
|
|
2646
2898
|
limit: historyLimit,
|
|
2647
2899
|
currentMessage: combinedBody,
|
|
2648
|
-
formatEntry: (entry) =>
|
|
2900
|
+
formatEntry: (entry) => formatAgentEnvelope({
|
|
2649
2901
|
channel: "Feishu",
|
|
2650
2902
|
from: `${ctx.chatId}:${entry.sender}`,
|
|
2651
2903
|
timestamp: entry.timestamp,
|
|
@@ -2712,7 +2964,7 @@ async function handleFeishuMessage(params) {
|
|
|
2712
2964
|
threadContextBySessionKey.set(agentSessionKey, threadContext);
|
|
2713
2965
|
return threadContext;
|
|
2714
2966
|
}
|
|
2715
|
-
const storePath =
|
|
2967
|
+
const storePath = resolveStorePath(cfg.session?.store, { agentId });
|
|
2716
2968
|
if (core.channel.session.readSessionUpdatedAt({
|
|
2717
2969
|
storePath,
|
|
2718
2970
|
sessionKey: agentSessionKey
|
|
@@ -2754,7 +3006,7 @@ async function handleFeishuMessage(params) {
|
|
|
2754
3006
|
const historyMessages = Boolean(rootMsg?.content || ctx.rootId) ? relevantMessages : relevantMessages.slice(1);
|
|
2755
3007
|
const historyParts = historyMessages.map((msg) => {
|
|
2756
3008
|
const role = msg.senderType === "app" ? "assistant" : "user";
|
|
2757
|
-
return
|
|
3009
|
+
return formatAgentEnvelope({
|
|
2758
3010
|
channel: "Feishu",
|
|
2759
3011
|
from: `${msg.senderId ?? "Unknown"} (${role})`,
|
|
2760
3012
|
timestamp: msg.createTime,
|
|
@@ -2776,7 +3028,6 @@ async function handleFeishuMessage(params) {
|
|
|
2776
3028
|
const threadContext = await resolveThreadContextForAgent(agentId, agentSessionKey, groupName);
|
|
2777
3029
|
return buildChannelInboundEventContext({
|
|
2778
3030
|
channel: "feishu",
|
|
2779
|
-
finalize: core.channel.reply.finalizeInboundContext,
|
|
2780
3031
|
supplemental: {
|
|
2781
3032
|
quote: quotedContent ? {
|
|
2782
3033
|
id: ctx.parentId,
|
|
@@ -2795,7 +3046,8 @@ async function handleFeishuMessage(params) {
|
|
|
2795
3046
|
from: feishuFrom,
|
|
2796
3047
|
sender: {
|
|
2797
3048
|
id: ctx.senderOpenId,
|
|
2798
|
-
name: ctx.senderName ?? ctx.senderOpenId
|
|
3049
|
+
name: ctx.senderName ?? ctx.senderOpenId,
|
|
3050
|
+
isBot: ctx.senderType === "bot"
|
|
2799
3051
|
},
|
|
2800
3052
|
conversation: {
|
|
2801
3053
|
kind: isGroup ? "group" : "direct",
|
|
@@ -2806,6 +3058,7 @@ async function handleFeishuMessage(params) {
|
|
|
2806
3058
|
},
|
|
2807
3059
|
route: {
|
|
2808
3060
|
agentId,
|
|
3061
|
+
dmScope: route.dmScope,
|
|
2809
3062
|
accountId: agentAccountId,
|
|
2810
3063
|
routeSessionKey: agentSessionKey
|
|
2811
3064
|
},
|
|
@@ -2827,7 +3080,7 @@ async function handleFeishuMessage(params) {
|
|
|
2827
3080
|
wasMentioned,
|
|
2828
3081
|
requireMention
|
|
2829
3082
|
},
|
|
2830
|
-
commands: { authorized: commandAuthorized }
|
|
3083
|
+
commands: { authorized: commandAuthorized === true }
|
|
2831
3084
|
},
|
|
2832
3085
|
extra: {
|
|
2833
3086
|
RootMessageId: ctx.rootId,
|
|
@@ -2885,7 +3138,7 @@ async function handleFeishuMessage(params) {
|
|
|
2885
3138
|
return;
|
|
2886
3139
|
}
|
|
2887
3140
|
const agentSessionKey = buildBroadcastSessionKey(route.sessionKey, route.agentId, agentId);
|
|
2888
|
-
const agentStorePath =
|
|
3141
|
+
const agentStorePath = resolveStorePath(cfg.session?.store, { agentId });
|
|
2889
3142
|
const agentRecord = {
|
|
2890
3143
|
updateLastRoute: buildFeishuInboundLastRouteUpdate({
|
|
2891
3144
|
sessionKey: agentSessionKey,
|
|
@@ -2904,7 +3157,7 @@ async function handleFeishuMessage(params) {
|
|
|
2904
3157
|
const agentCtx = await buildCtxPayloadForAgent(agentId, agentSessionKey, route.accountId, ctx.mentionedBot && agentId === activeAgentId);
|
|
2905
3158
|
if (agentId === activeAgentId) {
|
|
2906
3159
|
const identity = resolveAgentOutboundIdentity(cfg, agentId);
|
|
2907
|
-
const {
|
|
3160
|
+
const { dispatcherOptions, delivery, replyOptions, ensureNoVisibleReplyFallback } = createFeishuReplyDispatcher({
|
|
2908
3161
|
cfg,
|
|
2909
3162
|
agentId,
|
|
2910
3163
|
runtime,
|
|
@@ -2920,6 +3173,7 @@ async function handleFeishuMessage(params) {
|
|
|
2920
3173
|
accountId: account.accountId,
|
|
2921
3174
|
identity,
|
|
2922
3175
|
mentionTargets: ctx.mentionTargets,
|
|
3176
|
+
requiredMentionTargets,
|
|
2923
3177
|
messageCreateTimeMs,
|
|
2924
3178
|
sessionKey: agentSessionKey
|
|
2925
3179
|
});
|
|
@@ -2938,57 +3192,24 @@ async function handleFeishuMessage(params) {
|
|
|
2938
3192
|
raw: ctx
|
|
2939
3193
|
}),
|
|
2940
3194
|
resolveTurn: () => ({
|
|
3195
|
+
cfg,
|
|
2941
3196
|
channel: "feishu",
|
|
2942
3197
|
accountId: route.accountId,
|
|
2943
|
-
|
|
2944
|
-
|
|
3198
|
+
route: {
|
|
3199
|
+
agentId,
|
|
3200
|
+
sessionKey: agentSessionKey
|
|
3201
|
+
},
|
|
2945
3202
|
ctxPayload: agentCtx,
|
|
2946
|
-
recordInboundSession: core.channel.session.recordInboundSession,
|
|
2947
3203
|
record: agentRecord,
|
|
2948
|
-
|
|
2949
|
-
|
|
2950
|
-
|
|
2951
|
-
}),
|
|
2952
|
-
runDispatch: () => core.channel.reply.withReplyDispatcher({
|
|
2953
|
-
dispatcher,
|
|
2954
|
-
onSettled: () => markDispatchIdle(),
|
|
2955
|
-
run: () => core.channel.reply.dispatchReplyFromConfig({
|
|
2956
|
-
ctx: agentCtx,
|
|
2957
|
-
cfg,
|
|
2958
|
-
dispatcher,
|
|
2959
|
-
replyOptions
|
|
2960
|
-
})
|
|
2961
|
-
})
|
|
3204
|
+
dispatcherOptions,
|
|
3205
|
+
delivery,
|
|
3206
|
+
replyOptions
|
|
2962
3207
|
})
|
|
2963
3208
|
}
|
|
2964
3209
|
});
|
|
2965
|
-
if (turnResult.dispatched && shouldSendNoVisibleReplyFallback(
|
|
2966
|
-
...turnResult.dispatchResult,
|
|
2967
|
-
failedCounts: dispatcher.getFailedCounts?.() ?? {
|
|
2968
|
-
tool: 0,
|
|
2969
|
-
block: 0,
|
|
2970
|
-
final: 0
|
|
2971
|
-
}
|
|
2972
|
-
})) await ensureNoVisibleReplyFallback("broadcast-dispatch-complete-no-visible-reply");
|
|
3210
|
+
if (turnResult.dispatched && shouldSendNoVisibleReplyFallback(turnResult.dispatchResult)) await ensureNoVisibleReplyFallback("broadcast-dispatch-complete-no-visible-reply");
|
|
2973
3211
|
} else {
|
|
2974
3212
|
delete agentCtx.CommandAuthorized;
|
|
2975
|
-
const noopDispatcher = {
|
|
2976
|
-
sendToolResult: () => false,
|
|
2977
|
-
sendBlockReply: () => false,
|
|
2978
|
-
sendFinalReply: () => false,
|
|
2979
|
-
waitForIdle: async () => {},
|
|
2980
|
-
getQueuedCounts: () => ({
|
|
2981
|
-
tool: 0,
|
|
2982
|
-
block: 0,
|
|
2983
|
-
final: 0
|
|
2984
|
-
}),
|
|
2985
|
-
getFailedCounts: () => ({
|
|
2986
|
-
tool: 0,
|
|
2987
|
-
block: 0,
|
|
2988
|
-
final: 0
|
|
2989
|
-
}),
|
|
2990
|
-
markComplete: () => {}
|
|
2991
|
-
};
|
|
2992
3213
|
log(`feishu[${account.accountId}]: broadcast observer dispatch agent=${agentId} (session=${agentSessionKey})`);
|
|
2993
3214
|
await core.channel.inbound.run({
|
|
2994
3215
|
channel: "feishu",
|
|
@@ -3004,21 +3225,20 @@ async function handleFeishuMessage(params) {
|
|
|
3004
3225
|
raw: ctx
|
|
3005
3226
|
}),
|
|
3006
3227
|
resolveTurn: () => ({
|
|
3228
|
+
cfg,
|
|
3007
3229
|
channel: "feishu",
|
|
3008
3230
|
accountId: route.accountId,
|
|
3009
|
-
|
|
3010
|
-
|
|
3231
|
+
route: {
|
|
3232
|
+
agentId,
|
|
3233
|
+
sessionKey: agentSessionKey
|
|
3234
|
+
},
|
|
3011
3235
|
ctxPayload: agentCtx,
|
|
3012
|
-
recordInboundSession: core.channel.session.recordInboundSession,
|
|
3013
3236
|
record: agentRecord,
|
|
3014
|
-
|
|
3015
|
-
|
|
3016
|
-
|
|
3017
|
-
|
|
3018
|
-
|
|
3019
|
-
dispatcher: noopDispatcher
|
|
3020
|
-
})
|
|
3021
|
-
})
|
|
3237
|
+
admission: {
|
|
3238
|
+
kind: "observeOnly",
|
|
3239
|
+
reason: "broadcast-observer"
|
|
3240
|
+
},
|
|
3241
|
+
delivery: { deliver: async () => ({ visibleReplySent: false }) }
|
|
3022
3242
|
})
|
|
3023
3243
|
}
|
|
3024
3244
|
});
|
|
@@ -3045,14 +3265,14 @@ async function handleFeishuMessage(params) {
|
|
|
3045
3265
|
} else {
|
|
3046
3266
|
const ctxPayload = await buildCtxPayloadForAgent(route.agentId, route.sessionKey, route.accountId, ctx.mentionedBot);
|
|
3047
3267
|
const identity = resolveAgentOutboundIdentity(effectiveCfg, route.agentId);
|
|
3048
|
-
const storePath =
|
|
3268
|
+
const storePath = resolveStorePath(effectiveCfg.session?.store, { agentId: route.agentId });
|
|
3049
3269
|
const allowReasoningPreview = resolveFeishuReasoningPreviewEnabled({
|
|
3050
3270
|
cfg: effectiveCfg,
|
|
3051
3271
|
agentId: route.agentId,
|
|
3052
3272
|
storePath,
|
|
3053
3273
|
sessionKey: route.sessionKey
|
|
3054
3274
|
});
|
|
3055
|
-
const {
|
|
3275
|
+
const { dispatcherOptions, delivery, replyOptions, ensureNoVisibleReplyFallback } = createFeishuReplyDispatcher({
|
|
3056
3276
|
cfg: effectiveCfg,
|
|
3057
3277
|
agentId: route.agentId,
|
|
3058
3278
|
runtime,
|
|
@@ -3068,6 +3288,7 @@ async function handleFeishuMessage(params) {
|
|
|
3068
3288
|
accountId: account.accountId,
|
|
3069
3289
|
identity,
|
|
3070
3290
|
mentionTargets: ctx.mentionTargets,
|
|
3291
|
+
requiredMentionTargets,
|
|
3071
3292
|
messageCreateTimeMs,
|
|
3072
3293
|
sessionKey: route.sessionKey
|
|
3073
3294
|
});
|
|
@@ -3086,12 +3307,14 @@ async function handleFeishuMessage(params) {
|
|
|
3086
3307
|
raw: ctx
|
|
3087
3308
|
}),
|
|
3088
3309
|
resolveTurn: () => ({
|
|
3310
|
+
cfg: effectiveCfg,
|
|
3089
3311
|
channel: "feishu",
|
|
3090
3312
|
accountId: route.accountId,
|
|
3091
|
-
|
|
3092
|
-
|
|
3313
|
+
route: {
|
|
3314
|
+
agentId: route.agentId,
|
|
3315
|
+
sessionKey: route.sessionKey
|
|
3316
|
+
},
|
|
3093
3317
|
ctxPayload,
|
|
3094
|
-
recordInboundSession: core.channel.session.recordInboundSession,
|
|
3095
3318
|
record: {
|
|
3096
3319
|
updateLastRoute: buildFeishuInboundLastRouteUpdate({
|
|
3097
3320
|
sessionKey: route.sessionKey,
|
|
@@ -3107,36 +3330,16 @@ async function handleFeishuMessage(params) {
|
|
|
3107
3330
|
historyMap: chatHistories,
|
|
3108
3331
|
limit: historyLimit
|
|
3109
3332
|
},
|
|
3110
|
-
|
|
3111
|
-
|
|
3112
|
-
|
|
3113
|
-
}),
|
|
3114
|
-
runDispatch: () => core.channel.reply.withReplyDispatcher({
|
|
3115
|
-
dispatcher,
|
|
3116
|
-
onSettled: () => {
|
|
3117
|
-
markDispatchIdle();
|
|
3118
|
-
},
|
|
3119
|
-
run: () => core.channel.reply.dispatchReplyFromConfig({
|
|
3120
|
-
ctx: ctxPayload,
|
|
3121
|
-
cfg: effectiveCfg,
|
|
3122
|
-
dispatcher,
|
|
3123
|
-
replyOptions
|
|
3124
|
-
})
|
|
3125
|
-
})
|
|
3333
|
+
dispatcherOptions,
|
|
3334
|
+
delivery,
|
|
3335
|
+
replyOptions
|
|
3126
3336
|
})
|
|
3127
3337
|
}
|
|
3128
3338
|
});
|
|
3129
3339
|
if (!turnResult.dispatched) return;
|
|
3130
3340
|
const { dispatchResult } = turnResult;
|
|
3131
3341
|
const { queuedFinal, counts } = dispatchResult;
|
|
3132
|
-
if (shouldSendNoVisibleReplyFallback(
|
|
3133
|
-
...dispatchResult,
|
|
3134
|
-
failedCounts: dispatcher.getFailedCounts?.() ?? {
|
|
3135
|
-
tool: 0,
|
|
3136
|
-
block: 0,
|
|
3137
|
-
final: 0
|
|
3138
|
-
}
|
|
3139
|
-
})) await ensureNoVisibleReplyFallback("dispatch-complete-no-visible-reply");
|
|
3342
|
+
if (shouldSendNoVisibleReplyFallback(dispatchResult)) await ensureNoVisibleReplyFallback("dispatch-complete-no-visible-reply");
|
|
3140
3343
|
log(`feishu[${account.accountId}]: dispatch complete (queuedFinal=${queuedFinal}, replies=${counts.final})`);
|
|
3141
3344
|
}
|
|
3142
3345
|
} catch (err) {
|
|
@@ -3256,7 +3459,7 @@ function completeFeishuCardAction(actionId, accountId, now = Date.now()) {
|
|
|
3256
3459
|
expiresAt
|
|
3257
3460
|
});
|
|
3258
3461
|
}
|
|
3259
|
-
function buildSyntheticMessageEvent(event, content, chatType) {
|
|
3462
|
+
function buildSyntheticMessageEvent$1(event, content, chatType) {
|
|
3260
3463
|
const replyTargetMessageId = event.context.open_message_id ?? event.open_message_id;
|
|
3261
3464
|
const isTemporaryCardActionId = replyTargetMessageId?.startsWith("card-action-c-");
|
|
3262
3465
|
const validReplyTargetId = replyTargetMessageId && !isTemporaryCardActionId ? replyTargetMessageId : void 0;
|
|
@@ -3292,7 +3495,7 @@ async function dispatchSyntheticCommand(params) {
|
|
|
3292
3495
|
});
|
|
3293
3496
|
await handleFeishuMessage({
|
|
3294
3497
|
cfg: params.cfg,
|
|
3295
|
-
event: buildSyntheticMessageEvent(params.event, params.command, resolvedChatType),
|
|
3498
|
+
event: buildSyntheticMessageEvent$1(params.event, params.command, resolvedChatType),
|
|
3296
3499
|
botOpenId: params.botOpenId,
|
|
3297
3500
|
runtime: params.runtime,
|
|
3298
3501
|
channelRuntime: params.channelRuntime,
|
|
@@ -3863,11 +4066,11 @@ function createFeishuBotMenuHandler(params) {
|
|
|
3863
4066
|
namespace: accountId,
|
|
3864
4067
|
log
|
|
3865
4068
|
});
|
|
3866
|
-
if (claim === "duplicate") {
|
|
4069
|
+
if (claim.kind === "duplicate") {
|
|
3867
4070
|
log(`feishu[${accountId}]: dropping duplicate bot-menu event for ${syntheticMessageId}`);
|
|
3868
4071
|
return;
|
|
3869
4072
|
}
|
|
3870
|
-
if (claim === "inflight") {
|
|
4073
|
+
if (claim.kind === "inflight") {
|
|
3871
4074
|
log(`feishu[${accountId}]: dropping in-flight bot-menu event for ${syntheticMessageId}`);
|
|
3872
4075
|
return;
|
|
3873
4076
|
}
|
|
@@ -3880,7 +4083,7 @@ function createFeishuBotMenuHandler(params) {
|
|
|
3880
4083
|
channelRuntime: params.channelRuntime,
|
|
3881
4084
|
chatHistories,
|
|
3882
4085
|
accountId,
|
|
3883
|
-
|
|
4086
|
+
processingClaim: claim.kind === "claimed" ? claim.handle : void 0
|
|
3884
4087
|
});
|
|
3885
4088
|
const promise = maybeHandleFeishuQuickActionMenu({
|
|
3886
4089
|
cfg,
|
|
@@ -3890,16 +4093,16 @@ function createFeishuBotMenuHandler(params) {
|
|
|
3890
4093
|
accountId
|
|
3891
4094
|
}).then(async (handledMenu) => {
|
|
3892
4095
|
if (handledMenu) {
|
|
3893
|
-
|
|
4096
|
+
if (claim.kind === "claimed") await claim.handle.commit();
|
|
3894
4097
|
return;
|
|
3895
4098
|
}
|
|
3896
4099
|
return await handleLegacyMenu();
|
|
3897
4100
|
}).catch(async (err) => {
|
|
3898
|
-
if (isFeishuRetryableSyntheticEventError(err))
|
|
3899
|
-
|
|
4101
|
+
if (isFeishuRetryableSyntheticEventError(err)) {
|
|
4102
|
+
await forgetProcessedFeishuMessage(syntheticMessageId, accountId, log);
|
|
4103
|
+
if (claim.kind === "claimed") claim.handle.release({ error: err });
|
|
4104
|
+
} else if (claim.kind === "claimed") await claim.handle.commit();
|
|
3900
4105
|
throw err;
|
|
3901
|
-
}).finally(() => {
|
|
3902
|
-
releaseFeishuMessageProcessing(syntheticMessageId, accountId);
|
|
3903
4106
|
});
|
|
3904
4107
|
if (fireAndForget) {
|
|
3905
4108
|
promise.catch((err) => {
|
|
@@ -3937,41 +4140,39 @@ function createFeishuCommentReplyDispatcher(params) {
|
|
|
3937
4140
|
accountId: params.accountId,
|
|
3938
4141
|
runtime: params.runtime
|
|
3939
4142
|
});
|
|
3940
|
-
|
|
3941
|
-
|
|
3942
|
-
|
|
3943
|
-
|
|
3944
|
-
|
|
3945
|
-
|
|
3946
|
-
|
|
3947
|
-
|
|
3948
|
-
|
|
3949
|
-
|
|
3950
|
-
if (!reply.hasText) {
|
|
3951
|
-
if (reply.hasMedia) params.runtime.log?.(`feishu[${params.accountId ?? "default"}]: comment reply ignored media-only payload for comment=${params.commentId}`);
|
|
3952
|
-
return;
|
|
4143
|
+
return {
|
|
4144
|
+
dispatcherOptions: {
|
|
4145
|
+
responsePrefix: prefixContext.responsePrefix,
|
|
4146
|
+
responsePrefixContextProvider: prefixContext.responsePrefixContextProvider,
|
|
4147
|
+
humanDelay: resolveHumanDelayConfig(params.cfg, params.agentId),
|
|
4148
|
+
onReplyStart: async () => {
|
|
4149
|
+
await typingReaction.start();
|
|
4150
|
+
},
|
|
4151
|
+
onCleanup: () => {
|
|
4152
|
+
typingReaction.cleanup();
|
|
3953
4153
|
}
|
|
3954
|
-
const chunks = core.channel.text.chunkTextWithMode(reply.text, textChunkLimit, chunkMode);
|
|
3955
|
-
for (const chunk of chunks) await deliverCommentThreadText(client, {
|
|
3956
|
-
file_token: params.fileToken,
|
|
3957
|
-
file_type: params.fileType,
|
|
3958
|
-
comment_id: params.commentId,
|
|
3959
|
-
content: chunk,
|
|
3960
|
-
is_whole_comment: params.isWholeComment
|
|
3961
|
-
});
|
|
3962
4154
|
},
|
|
3963
|
-
|
|
3964
|
-
|
|
4155
|
+
delivery: {
|
|
4156
|
+
deliver: async (payload, info) => {
|
|
4157
|
+
if (info.kind !== "final") return;
|
|
4158
|
+
const reply = resolveSendableOutboundReplyParts(payload);
|
|
4159
|
+
if (!reply.hasText) {
|
|
4160
|
+
if (reply.hasMedia) params.runtime.log?.(`feishu[${params.accountId ?? "default"}]: comment reply ignored media-only payload for comment=${params.commentId}`);
|
|
4161
|
+
return;
|
|
4162
|
+
}
|
|
4163
|
+
const chunks = core.channel.text.chunkTextWithMode(reply.text, textChunkLimit, chunkMode);
|
|
4164
|
+
for (const chunk of chunks) await deliverCommentThreadText(client, {
|
|
4165
|
+
file_token: params.fileToken,
|
|
4166
|
+
file_type: params.fileType,
|
|
4167
|
+
comment_id: params.commentId,
|
|
4168
|
+
content: chunk,
|
|
4169
|
+
is_whole_comment: params.isWholeComment
|
|
4170
|
+
});
|
|
4171
|
+
},
|
|
4172
|
+
onError: (err, info) => {
|
|
4173
|
+
params.runtime.error?.(`feishu[${params.accountId ?? "default"}]: comment dispatcher failed kind=${info.kind} comment=${params.commentId}: ${String(err)}`);
|
|
4174
|
+
}
|
|
3965
4175
|
},
|
|
3966
|
-
onCleanup: () => {
|
|
3967
|
-
typingReaction.cleanup();
|
|
3968
|
-
}
|
|
3969
|
-
});
|
|
3970
|
-
return {
|
|
3971
|
-
dispatcher,
|
|
3972
|
-
replyOptions,
|
|
3973
|
-
markDispatchIdle,
|
|
3974
|
-
markRunComplete,
|
|
3975
4176
|
startTypingReaction: typingReaction.start,
|
|
3976
4177
|
cleanupTypingReaction: typingReaction.cleanup
|
|
3977
4178
|
};
|
|
@@ -4088,11 +4289,6 @@ async function resolveParsedCommentContent(params) {
|
|
|
4088
4289
|
linkedDocuments: resolvedLinkedDocuments
|
|
4089
4290
|
};
|
|
4090
4291
|
}
|
|
4091
|
-
async function delayMs(ms) {
|
|
4092
|
-
await new Promise((resolve) => {
|
|
4093
|
-
setTimeout(resolve, ms);
|
|
4094
|
-
});
|
|
4095
|
-
}
|
|
4096
4292
|
function buildDriveCommentTargetUrl(params) {
|
|
4097
4293
|
return `/open-apis/drive/v1/files/${encodeURIComponent(params.fileToken)}/comments/batch_query` + encodeQuery({
|
|
4098
4294
|
file_type: params.fileType,
|
|
@@ -4315,8 +4511,9 @@ async function fetchDriveCommentContext(params) {
|
|
|
4315
4511
|
fetchedMatchedReply = params.replyId ? replies.find((reply) => reply.reply_id?.trim() === params.replyId?.trim()) : void 0;
|
|
4316
4512
|
}
|
|
4317
4513
|
if (params.replyId && !embeddedTargetReply && !fetchedMatchedReply) for (let attempt = 1; attempt <= FEISHU_COMMENT_REPLY_MISS_RETRY_LIMIT; attempt += 1) {
|
|
4514
|
+
if (params.abortSignal?.aborted) break;
|
|
4318
4515
|
params.logger?.(`feishu[${params.accountId}]: retrying comment reply lookup comment=${params.commentId} requested_reply=${params.replyId} attempt=${attempt}/${FEISHU_COMMENT_REPLY_MISS_RETRY_LIMIT} delay_ms=${FEISHU_COMMENT_REPLY_MISS_RETRY_DELAY_MS}`);
|
|
4319
|
-
await params.
|
|
4516
|
+
if (!await waitForAbortableDelay(FEISHU_COMMENT_REPLY_MISS_RETRY_DELAY_MS, params.abortSignal)) break;
|
|
4320
4517
|
const retried = await fetchDriveCommentReplies(params);
|
|
4321
4518
|
if (retried.replies.length > 0) {
|
|
4322
4519
|
params.logger?.(`feishu[${params.accountId}]: fetched retried comment replies comment=${params.commentId} attempt=${attempt} count=${retried.replies.length} log_ids=${safeJsonStringify(retried.logIds)} summary=${summarizeCommentRepliesForLog(retried.replies)}`);
|
|
@@ -4464,7 +4661,7 @@ function buildDriveCommentSurfacePrompt(params) {
|
|
|
4464
4661
|
return lines.join("\n");
|
|
4465
4662
|
}
|
|
4466
4663
|
async function resolveDriveCommentEventCore(params) {
|
|
4467
|
-
const { cfg, accountId, event, account, botOpenId, createClient, verificationTimeoutMs = FEISHU_COMMENT_VERIFY_TIMEOUT_MS, logger,
|
|
4664
|
+
const { cfg, accountId, event, account, botOpenId, createClient, verificationTimeoutMs = FEISHU_COMMENT_VERIFY_TIMEOUT_MS, logger, abortSignal } = params;
|
|
4468
4665
|
const eventId = event.event_id?.trim();
|
|
4469
4666
|
const commentId = event.comment_id?.trim();
|
|
4470
4667
|
const replyId = event.reply_id?.trim();
|
|
@@ -4502,7 +4699,7 @@ async function resolveDriveCommentEventCore(params) {
|
|
|
4502
4699
|
timeoutMs: verificationTimeoutMs,
|
|
4503
4700
|
logger,
|
|
4504
4701
|
accountId,
|
|
4505
|
-
|
|
4702
|
+
abortSignal
|
|
4506
4703
|
});
|
|
4507
4704
|
return {
|
|
4508
4705
|
eventId,
|
|
@@ -4628,7 +4825,8 @@ async function handleFeishuCommentEvent(params) {
|
|
|
4628
4825
|
accountId: account.accountId,
|
|
4629
4826
|
event: params.event,
|
|
4630
4827
|
botOpenId: params.botOpenId,
|
|
4631
|
-
logger: log
|
|
4828
|
+
logger: log,
|
|
4829
|
+
abortSignal: params.abortSignal
|
|
4632
4830
|
});
|
|
4633
4831
|
if (!turn) {
|
|
4634
4832
|
log(`feishu[${account.accountId}]: drive comment notice skipped event=${params.event.event_id ?? "unknown"} comment=${params.event.comment_id ?? "unknown"}`);
|
|
@@ -4753,31 +4951,51 @@ async function handleFeishuCommentEvent(params) {
|
|
|
4753
4951
|
fileToken: turn.fileToken
|
|
4754
4952
|
});
|
|
4755
4953
|
const bodyForAgent = `[message_id: ${turn.messageId}]\n${turn.prompt}`;
|
|
4756
|
-
const
|
|
4757
|
-
|
|
4758
|
-
|
|
4759
|
-
|
|
4760
|
-
|
|
4761
|
-
|
|
4762
|
-
|
|
4763
|
-
|
|
4764
|
-
|
|
4765
|
-
|
|
4766
|
-
|
|
4767
|
-
|
|
4768
|
-
|
|
4769
|
-
|
|
4770
|
-
|
|
4771
|
-
|
|
4772
|
-
|
|
4773
|
-
|
|
4774
|
-
|
|
4775
|
-
|
|
4776
|
-
|
|
4777
|
-
|
|
4954
|
+
const rawBody = turn.targetReplyText ?? turn.rootCommentText ?? turn.prompt;
|
|
4955
|
+
const conversationLabel = turn.documentTitle ? `Feishu comment · ${turn.documentTitle}` : "Feishu comment";
|
|
4956
|
+
const ctxPayload = buildChannelInboundEventContext({
|
|
4957
|
+
channel: "feishu",
|
|
4958
|
+
accountId: route.accountId,
|
|
4959
|
+
surface: "feishu-comment",
|
|
4960
|
+
messageId: turn.messageId,
|
|
4961
|
+
timestamp: parseTimestampMs(turn.timestamp),
|
|
4962
|
+
from: `feishu:${turn.senderId}`,
|
|
4963
|
+
sender: {
|
|
4964
|
+
id: turn.senderId,
|
|
4965
|
+
name: turn.senderId
|
|
4966
|
+
},
|
|
4967
|
+
conversation: {
|
|
4968
|
+
kind: "direct",
|
|
4969
|
+
id: commentTarget,
|
|
4970
|
+
label: conversationLabel
|
|
4971
|
+
},
|
|
4972
|
+
route: {
|
|
4973
|
+
agentId: route.agentId,
|
|
4974
|
+
dmScope: route.dmScope,
|
|
4975
|
+
accountId: route.accountId,
|
|
4976
|
+
routeSessionKey: commentSessionKey,
|
|
4977
|
+
dispatchSessionKey: commentSessionKey
|
|
4978
|
+
},
|
|
4979
|
+
reply: {
|
|
4980
|
+
to: commentTarget,
|
|
4981
|
+
originatingTo: commentTarget,
|
|
4982
|
+
messageThreadId: turn.replyId
|
|
4983
|
+
},
|
|
4984
|
+
message: {
|
|
4985
|
+
body: bodyForAgent,
|
|
4986
|
+
bodyForAgent,
|
|
4987
|
+
rawBody,
|
|
4988
|
+
commandBody: rawBody
|
|
4989
|
+
},
|
|
4990
|
+
access: {
|
|
4991
|
+
commands: { authorized: false },
|
|
4992
|
+
mentions: {
|
|
4993
|
+
canDetectMention: true,
|
|
4994
|
+
wasMentioned: turn.isMentioned ?? false
|
|
4995
|
+
}
|
|
4996
|
+
}
|
|
4778
4997
|
});
|
|
4779
|
-
const
|
|
4780
|
-
const { dispatcher, replyOptions, markDispatchIdle, markRunComplete, cleanupTypingReaction } = createFeishuCommentReplyDispatcher({
|
|
4998
|
+
const { dispatcherOptions, delivery, cleanupTypingReaction } = createFeishuCommentReplyDispatcher({
|
|
4781
4999
|
cfg: effectiveCfg,
|
|
4782
5000
|
agentId: route.agentId,
|
|
4783
5001
|
runtime,
|
|
@@ -4788,7 +5006,6 @@ async function handleFeishuCommentEvent(params) {
|
|
|
4788
5006
|
replyId: turn.replyId,
|
|
4789
5007
|
isWholeComment: turn.isWholeComment
|
|
4790
5008
|
});
|
|
4791
|
-
let dispatchSettledBeforeStart = false;
|
|
4792
5009
|
try {
|
|
4793
5010
|
log(`feishu[${account.accountId}]: dispatching drive comment to agent (session=${commentSessionKey} comment=${turn.commentId} type=${turn.noticeType})`);
|
|
4794
5011
|
const turnResult = await core.channel.inbound.run({
|
|
@@ -4805,34 +5022,19 @@ async function handleFeishuCommentEvent(params) {
|
|
|
4805
5022
|
raw: turn
|
|
4806
5023
|
}),
|
|
4807
5024
|
resolveTurn: () => ({
|
|
5025
|
+
cfg: effectiveCfg,
|
|
4808
5026
|
channel: "feishu",
|
|
4809
5027
|
accountId: route.accountId,
|
|
4810
|
-
|
|
4811
|
-
|
|
5028
|
+
route: {
|
|
5029
|
+
agentId: route.agentId,
|
|
5030
|
+
sessionKey: commentSessionKey
|
|
5031
|
+
},
|
|
4812
5032
|
ctxPayload,
|
|
4813
|
-
recordInboundSession: core.channel.session.recordInboundSession,
|
|
4814
5033
|
record: { onRecordError: (err) => {
|
|
4815
5034
|
error(`feishu[${account.accountId}]: failed to record comment inbound session ${commentSessionKey}: ${String(err)}`);
|
|
4816
5035
|
} },
|
|
4817
|
-
|
|
4818
|
-
|
|
4819
|
-
await core.channel.reply.settleReplyDispatcher({
|
|
4820
|
-
dispatcher,
|
|
4821
|
-
onSettled: () => {
|
|
4822
|
-
markRunComplete();
|
|
4823
|
-
markDispatchIdle();
|
|
4824
|
-
}
|
|
4825
|
-
});
|
|
4826
|
-
},
|
|
4827
|
-
runDispatch: () => core.channel.reply.withReplyDispatcher({
|
|
4828
|
-
dispatcher,
|
|
4829
|
-
run: () => core.channel.reply.dispatchReplyFromConfig({
|
|
4830
|
-
ctx: ctxPayload,
|
|
4831
|
-
cfg: effectiveCfg,
|
|
4832
|
-
dispatcher,
|
|
4833
|
-
replyOptions
|
|
4834
|
-
})
|
|
4835
|
-
})
|
|
5036
|
+
dispatcherOptions,
|
|
5037
|
+
delivery
|
|
4836
5038
|
})
|
|
4837
5039
|
}
|
|
4838
5040
|
});
|
|
@@ -4845,10 +5047,6 @@ async function handleFeishuCommentEvent(params) {
|
|
|
4845
5047
|
};
|
|
4846
5048
|
log(`feishu[${account.accountId}]: drive comment dispatch complete (queuedFinal=${queuedFinal}, replies=${counts.final}, session=${commentSessionKey})`);
|
|
4847
5049
|
} finally {
|
|
4848
|
-
if (!dispatchSettledBeforeStart) {
|
|
4849
|
-
markRunComplete();
|
|
4850
|
-
markDispatchIdle();
|
|
4851
|
-
}
|
|
4852
5050
|
cleanupTypingReaction();
|
|
4853
5051
|
}
|
|
4854
5052
|
}
|
|
@@ -4912,7 +5110,7 @@ function buildCommentNoticeQueueKey(event) {
|
|
|
4912
5110
|
return `comment-doc:${event.notice_meta?.file_type?.trim() || "unknown"}:${event.notice_meta?.file_token?.trim() || "unknown"}`;
|
|
4913
5111
|
}
|
|
4914
5112
|
function createFeishuDriveCommentNoticeHandler(params) {
|
|
4915
|
-
const { cfg, accountId, runtime, fireAndForget } = params;
|
|
5113
|
+
const { cfg, accountId, runtime, fireAndForget, abortSignal } = params;
|
|
4916
5114
|
const log = runtime?.log ?? console.log;
|
|
4917
5115
|
const error = runtime?.error ?? console.error;
|
|
4918
5116
|
const enqueue = createSequentialQueue();
|
|
@@ -4932,20 +5130,22 @@ function createFeishuDriveCommentNoticeHandler(params) {
|
|
|
4932
5130
|
}
|
|
4933
5131
|
const eventId = event.event_id?.trim();
|
|
4934
5132
|
const syntheticMessageId = eventId ? `drive-comment:${eventId}` : void 0;
|
|
5133
|
+
let processingClaim;
|
|
4935
5134
|
if (syntheticMessageId) {
|
|
4936
5135
|
const claim = await claimUnprocessedFeishuMessage({
|
|
4937
5136
|
messageId: syntheticMessageId,
|
|
4938
5137
|
namespace: accountId,
|
|
4939
5138
|
log
|
|
4940
5139
|
});
|
|
4941
|
-
if (claim === "duplicate") {
|
|
5140
|
+
if (claim.kind === "duplicate") {
|
|
4942
5141
|
log(`feishu[${accountId}]: dropping duplicate comment event ${syntheticMessageId}`);
|
|
4943
5142
|
return;
|
|
4944
5143
|
}
|
|
4945
|
-
if (claim === "inflight") {
|
|
5144
|
+
if (claim.kind === "inflight") {
|
|
4946
5145
|
log(`feishu[${accountId}]: dropping in-flight comment event ${syntheticMessageId}`);
|
|
4947
5146
|
return;
|
|
4948
5147
|
}
|
|
5148
|
+
processingClaim = claim.kind === "claimed" ? claim.handle : void 0;
|
|
4949
5149
|
}
|
|
4950
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"}`);
|
|
4951
5151
|
try {
|
|
@@ -4955,15 +5155,15 @@ function createFeishuDriveCommentNoticeHandler(params) {
|
|
|
4955
5155
|
accountId,
|
|
4956
5156
|
event,
|
|
4957
5157
|
botOpenId: getBotOpenId(accountId),
|
|
4958
|
-
runtime
|
|
5158
|
+
runtime,
|
|
5159
|
+
abortSignal
|
|
4959
5160
|
});
|
|
4960
5161
|
});
|
|
4961
|
-
|
|
5162
|
+
await processingClaim?.commit();
|
|
4962
5163
|
} catch (err) {
|
|
4963
|
-
if (
|
|
5164
|
+
if (isFeishuRetryableSyntheticEventError(err)) processingClaim?.release({ error: err });
|
|
5165
|
+
else await processingClaim?.commit();
|
|
4964
5166
|
throw err;
|
|
4965
|
-
} finally {
|
|
4966
|
-
if (syntheticMessageId) releaseFeishuMessageProcessing(syntheticMessageId, accountId);
|
|
4967
5167
|
}
|
|
4968
5168
|
});
|
|
4969
5169
|
};
|
|
@@ -5005,7 +5205,7 @@ function dedupeFeishuDebounceEntriesByDedupeKey(entries) {
|
|
|
5005
5205
|
const seen = /* @__PURE__ */ new Set();
|
|
5006
5206
|
const deduped = [];
|
|
5007
5207
|
for (const entry of entries) {
|
|
5008
|
-
const dedupeKey = resolveFeishuMessageDedupeKey(entry);
|
|
5208
|
+
const dedupeKey = resolveFeishuMessageDedupeKey(entry.event);
|
|
5009
5209
|
if (!dedupeKey) {
|
|
5010
5210
|
deduped.push(entry);
|
|
5011
5211
|
continue;
|
|
@@ -5027,7 +5227,7 @@ function resolveFeishuDebounceMentions(params) {
|
|
|
5027
5227
|
const botMentions = merged.filter((mention) => mention.id.open_id?.trim() === normalizedBotOpenId);
|
|
5028
5228
|
return botMentions.length > 0 ? botMentions : void 0;
|
|
5029
5229
|
}
|
|
5030
|
-
function createFeishuMessageReceiveHandler({ cfg, channelRuntime, accountId, runtime, chatHistories, fireAndForget, handleMessage, resolveDebounceText: resolveText, hasProcessedMessage,
|
|
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 }) {
|
|
5031
5231
|
const inboundDebounceMs = channelRuntime.debounce.resolveInboundDebounceMs({
|
|
5032
5232
|
cfg,
|
|
5033
5233
|
channel: "feishu"
|
|
@@ -5037,7 +5237,7 @@ function createFeishuMessageReceiveHandler({ cfg, channelRuntime, accountId, run
|
|
|
5037
5237
|
const enqueue = createSequentialQueue({ onTaskTimeout: (key, timeoutMs) => {
|
|
5038
5238
|
log(`feishu[${accountId}]: per-chat task exceeded ${timeoutMs}ms cap (key=${key}); evicting from queue so later same-key messages can proceed (#70133)`);
|
|
5039
5239
|
} });
|
|
5040
|
-
const dispatchFeishuMessage = async (event, messageDedupeKey) => {
|
|
5240
|
+
const dispatchFeishuMessage = async (event, messageDedupeKey, processingClaim) => {
|
|
5041
5241
|
const sequentialKey = resolveSequentialKey({
|
|
5042
5242
|
accountId,
|
|
5043
5243
|
event,
|
|
@@ -5053,7 +5253,7 @@ function createFeishuMessageReceiveHandler({ cfg, channelRuntime, accountId, run
|
|
|
5053
5253
|
channelRuntime,
|
|
5054
5254
|
chatHistories,
|
|
5055
5255
|
accountId,
|
|
5056
|
-
|
|
5256
|
+
processingClaim,
|
|
5057
5257
|
messageDedupeKey
|
|
5058
5258
|
});
|
|
5059
5259
|
await enqueue(sequentialKey, task);
|
|
@@ -5070,23 +5270,26 @@ function createFeishuMessageReceiveHandler({ cfg, channelRuntime, accountId, run
|
|
|
5070
5270
|
};
|
|
5071
5271
|
const recordSuppressedMessageIds = async (entries, dispatchDedupeKey) => {
|
|
5072
5272
|
const keepDedupeKey = dispatchDedupeKey?.trim();
|
|
5073
|
-
const suppressedIds = new Set(entries.map((entry) =>
|
|
5074
|
-
|
|
5075
|
-
|
|
5273
|
+
const suppressedIds = new Set(entries.map((entry) => ({
|
|
5274
|
+
id: resolveFeishuMessageDedupeKey(entry.event),
|
|
5275
|
+
claim: entry.processingClaim
|
|
5276
|
+
})).filter(({ id }) => Boolean(id) && (!keepDedupeKey || id !== keepDedupeKey)));
|
|
5277
|
+
for (const suppressed of suppressedIds) try {
|
|
5278
|
+
await suppressed.claim?.commit();
|
|
5076
5279
|
} catch (err) {
|
|
5077
|
-
error(`feishu[${accountId}]: failed to record merged dedupe id ${
|
|
5280
|
+
error(`feishu[${accountId}]: failed to record merged dedupe id ${suppressed.id}: ${String(err)}`);
|
|
5078
5281
|
}
|
|
5079
5282
|
};
|
|
5080
5283
|
const inboundDebouncer = channelRuntime.debounce.createInboundDebouncer({
|
|
5081
5284
|
debounceMs: inboundDebounceMs,
|
|
5082
|
-
buildKey: (event) => {
|
|
5285
|
+
buildKey: ({ event }) => {
|
|
5083
5286
|
const chatId = event.message.chat_id?.trim();
|
|
5084
5287
|
const senderId = resolveSenderDebounceId(event);
|
|
5085
5288
|
if (!chatId || !senderId) return null;
|
|
5086
5289
|
const rootId = event.message.root_id?.trim();
|
|
5087
5290
|
return `feishu:${accountId}:${chatId}:${rootId ? `thread:${rootId}` : "chat"}:${senderId}`;
|
|
5088
5291
|
},
|
|
5089
|
-
shouldDebounce: (event) => {
|
|
5292
|
+
shouldDebounce: ({ event }) => {
|
|
5090
5293
|
if (event.message.message_type !== "text") return false;
|
|
5091
5294
|
const text = resolveDebounceText(event);
|
|
5092
5295
|
return Boolean(text) && !channelRuntime.commands.isControlCommandMessage(text, cfg);
|
|
@@ -5095,35 +5298,35 @@ function createFeishuMessageReceiveHandler({ cfg, channelRuntime, accountId, run
|
|
|
5095
5298
|
const last = entries.at(-1);
|
|
5096
5299
|
if (!last) return;
|
|
5097
5300
|
if (entries.length === 1) {
|
|
5098
|
-
await dispatchFeishuMessage(last, resolveFeishuMessageDedupeKey(last));
|
|
5301
|
+
await dispatchFeishuMessage(last.event, resolveFeishuMessageDedupeKey(last.event), last.processingClaim);
|
|
5099
5302
|
return;
|
|
5100
5303
|
}
|
|
5101
5304
|
const dedupedEntries = dedupeFeishuDebounceEntriesByDedupeKey(entries);
|
|
5102
5305
|
const freshEntries = [];
|
|
5103
|
-
for (const entry of dedupedEntries) if (!await hasProcessedMessage(resolveFeishuMessageDedupeKey(entry), accountId, log)) freshEntries.push(entry);
|
|
5306
|
+
for (const entry of dedupedEntries) if (!await hasProcessedMessage(resolveFeishuMessageDedupeKey(entry.event), accountId, log)) freshEntries.push(entry);
|
|
5104
5307
|
const dispatchEntry = freshEntries.at(-1);
|
|
5105
5308
|
if (!dispatchEntry) return;
|
|
5106
|
-
const dispatchDedupeKey = resolveFeishuMessageDedupeKey(dispatchEntry);
|
|
5309
|
+
const dispatchDedupeKey = resolveFeishuMessageDedupeKey(dispatchEntry.event);
|
|
5107
5310
|
await recordSuppressedMessageIds(dedupedEntries, dispatchDedupeKey);
|
|
5108
|
-
const combinedText = freshEntries.map((entry) => resolveDebounceText(entry)).filter(Boolean).join("\n");
|
|
5311
|
+
const combinedText = freshEntries.map((entry) => resolveDebounceText(entry.event)).filter(Boolean).join("\n");
|
|
5109
5312
|
const mergedMentions = resolveFeishuDebounceMentions({
|
|
5110
|
-
entries: freshEntries,
|
|
5313
|
+
entries: freshEntries.map((entry) => entry.event),
|
|
5111
5314
|
botOpenId: getBotOpenId(accountId)
|
|
5112
5315
|
});
|
|
5113
5316
|
await dispatchFeishuMessage({
|
|
5114
|
-
...dispatchEntry,
|
|
5317
|
+
...dispatchEntry.event,
|
|
5115
5318
|
message: {
|
|
5116
|
-
...dispatchEntry.message,
|
|
5319
|
+
...dispatchEntry.event.message,
|
|
5117
5320
|
...combinedText.trim() ? {
|
|
5118
5321
|
message_type: "text",
|
|
5119
5322
|
content: JSON.stringify({ text: combinedText })
|
|
5120
5323
|
} : {},
|
|
5121
|
-
mentions: mergedMentions ?? dispatchEntry.message.mentions
|
|
5324
|
+
mentions: mergedMentions ?? dispatchEntry.event.message.mentions
|
|
5122
5325
|
}
|
|
5123
|
-
}, dispatchDedupeKey);
|
|
5326
|
+
}, dispatchDedupeKey, dispatchEntry.processingClaim);
|
|
5124
5327
|
},
|
|
5125
5328
|
onError: (err, entries) => {
|
|
5126
|
-
for (const entry of entries)
|
|
5329
|
+
for (const entry of entries) entry.processingClaim?.release({ error: err });
|
|
5127
5330
|
error(`feishu[${accountId}]: inbound debounce flush failed: ${String(err)}`);
|
|
5128
5331
|
}
|
|
5129
5332
|
});
|
|
@@ -5141,22 +5344,24 @@ function createFeishuMessageReceiveHandler({ cfg, channelRuntime, accountId, run
|
|
|
5141
5344
|
log(`feishu[${accountId}]: dropping self-authored message ${messageId ?? "unknown"}`);
|
|
5142
5345
|
return;
|
|
5143
5346
|
}
|
|
5144
|
-
const messageDedupeKey = resolveFeishuMessageDedupeKey(event);
|
|
5145
5347
|
const claim = await claimUnprocessedFeishuMessage({
|
|
5146
|
-
messageId:
|
|
5348
|
+
messageId: resolveFeishuMessageDedupeKey(event),
|
|
5147
5349
|
namespace: accountId,
|
|
5148
5350
|
log
|
|
5149
5351
|
});
|
|
5150
|
-
if (claim
|
|
5151
|
-
log(`feishu[${accountId}]: dropping ${claim} event for message ${messageId}`);
|
|
5352
|
+
if (claim.kind === "duplicate" || claim.kind === "inflight") {
|
|
5353
|
+
log(`feishu[${accountId}]: dropping ${claim.kind} event for message ${messageId}`);
|
|
5152
5354
|
return;
|
|
5153
5355
|
}
|
|
5154
5356
|
const processMessage = async () => {
|
|
5155
|
-
await inboundDebouncer.enqueue(
|
|
5357
|
+
await inboundDebouncer.enqueue({
|
|
5358
|
+
event,
|
|
5359
|
+
...claim.kind === "claimed" ? { processingClaim: claim.handle } : {}
|
|
5360
|
+
});
|
|
5156
5361
|
};
|
|
5157
5362
|
if (fireAndForget) {
|
|
5158
5363
|
processMessage().catch((err) => {
|
|
5159
|
-
|
|
5364
|
+
if (claim.kind === "claimed") claim.handle.release({ error: err });
|
|
5160
5365
|
error(`feishu[${accountId}]: error handling message: ${String(err)}`);
|
|
5161
5366
|
});
|
|
5162
5367
|
return;
|
|
@@ -5164,7 +5369,7 @@ function createFeishuMessageReceiveHandler({ cfg, channelRuntime, accountId, run
|
|
|
5164
5369
|
try {
|
|
5165
5370
|
await processMessage();
|
|
5166
5371
|
} catch (err) {
|
|
5167
|
-
|
|
5372
|
+
if (claim.kind === "claimed") claim.handle.release({ error: err });
|
|
5168
5373
|
error(`feishu[${accountId}]: error handling message: ${String(err)}`);
|
|
5169
5374
|
}
|
|
5170
5375
|
};
|
|
@@ -5491,6 +5696,120 @@ async function monitorWebhook({ account, accountId, runtime, abortSignal, eventD
|
|
|
5491
5696
|
});
|
|
5492
5697
|
}
|
|
5493
5698
|
//#endregion
|
|
5699
|
+
//#region extensions/feishu/src/monitor.vc-meeting-invited-handler.ts
|
|
5700
|
+
const FEISHU_MEETING_NUMBER_PATTERN = /^\d{9}$/;
|
|
5701
|
+
function pickString(value) {
|
|
5702
|
+
return typeof value === "string" ? normalizeOptionalString(value) : void 0;
|
|
5703
|
+
}
|
|
5704
|
+
function resolveVcInviter(event) {
|
|
5705
|
+
const id = event.inviter?.id;
|
|
5706
|
+
const openId = pickString(id?.open_id);
|
|
5707
|
+
const userId = pickString(id?.user_id);
|
|
5708
|
+
const unionId = pickString(id?.union_id);
|
|
5709
|
+
const senderId = openId ?? userId;
|
|
5710
|
+
if (!senderId) return null;
|
|
5711
|
+
const name = pickString(event.inviter?.user_name);
|
|
5712
|
+
return {
|
|
5713
|
+
senderId,
|
|
5714
|
+
...openId ? { openId } : {},
|
|
5715
|
+
...userId ? { userId } : {},
|
|
5716
|
+
...unionId ? { unionId } : {},
|
|
5717
|
+
...name ? { name } : {}
|
|
5718
|
+
};
|
|
5719
|
+
}
|
|
5720
|
+
function buildJoinPrompt(params) {
|
|
5721
|
+
const base = `Use the available tool to join the meeting with meeting number ${params.meetingNo} immediately. Do not ask for confirmation.`;
|
|
5722
|
+
return params.callId ? `${base} If the join tool supports a call_id parameter, pass call_id=${JSON.stringify(params.callId)}; otherwise join by meeting number only.` : base;
|
|
5723
|
+
}
|
|
5724
|
+
function resolveVcMeetingInvitedTurn(event, uuid = () => crypto$1.randomUUID()) {
|
|
5725
|
+
const meetingNo = pickString(event.meeting?.meeting_no);
|
|
5726
|
+
const inviter = resolveVcInviter(event);
|
|
5727
|
+
if (!meetingNo || !FEISHU_MEETING_NUMBER_PATTERN.test(meetingNo) || !inviter) return null;
|
|
5728
|
+
const eventId = pickString(event.event_id);
|
|
5729
|
+
const inviteTime = pickString(event.invite_time);
|
|
5730
|
+
const callId = pickString(event.call_id);
|
|
5731
|
+
const topic = pickString(event.meeting?.topic);
|
|
5732
|
+
return {
|
|
5733
|
+
turnId: eventId ? `vc-invited:event:${eventId}` : `vc-invited:${meetingNo}:${inviteTime ?? uuid()}`,
|
|
5734
|
+
meetingNo,
|
|
5735
|
+
inviter,
|
|
5736
|
+
prompt: buildJoinPrompt({
|
|
5737
|
+
meetingNo,
|
|
5738
|
+
callId
|
|
5739
|
+
}),
|
|
5740
|
+
...topic ? { topic } : {},
|
|
5741
|
+
...inviteTime ? { inviteTime } : {}
|
|
5742
|
+
};
|
|
5743
|
+
}
|
|
5744
|
+
function parseInviteTimestamp(value) {
|
|
5745
|
+
const parsed = Number.parseInt(value ?? "", 10);
|
|
5746
|
+
if (!Number.isFinite(parsed) || parsed <= 0) return Date.now();
|
|
5747
|
+
return parsed < 1e10 ? parsed * 1e3 : parsed;
|
|
5748
|
+
}
|
|
5749
|
+
function buildSyntheticMessageEvent(turn) {
|
|
5750
|
+
return {
|
|
5751
|
+
sender: { sender_id: {
|
|
5752
|
+
...turn.inviter.openId ? { open_id: turn.inviter.openId } : {},
|
|
5753
|
+
...turn.inviter.userId ? { user_id: turn.inviter.userId } : {},
|
|
5754
|
+
...turn.inviter.unionId ? { union_id: turn.inviter.unionId } : {}
|
|
5755
|
+
} },
|
|
5756
|
+
message: {
|
|
5757
|
+
message_id: turn.turnId,
|
|
5758
|
+
chat_id: turn.inviter.senderId,
|
|
5759
|
+
chat_type: "p2p",
|
|
5760
|
+
message_type: "text",
|
|
5761
|
+
content: JSON.stringify({ text: turn.prompt }),
|
|
5762
|
+
create_time: String(parseInviteTimestamp(turn.inviteTime)),
|
|
5763
|
+
suppress_reply_target: true
|
|
5764
|
+
}
|
|
5765
|
+
};
|
|
5766
|
+
}
|
|
5767
|
+
async function dispatchVcMeetingInvitedTurn(params) {
|
|
5768
|
+
params.runtime?.log?.(`feishu[${params.accountId}]: vc meeting invited, dispatching synthetic p2p message sender=${params.turn.inviter.senderId} meeting_no=${params.turn.meetingNo}`);
|
|
5769
|
+
const event = setFeishuSyntheticDirectPreDispatchTarget(buildSyntheticMessageEvent(params.turn), `user:${params.turn.inviter.senderId}`);
|
|
5770
|
+
await handleFeishuMessage({
|
|
5771
|
+
cfg: params.cfg,
|
|
5772
|
+
accountId: params.accountId,
|
|
5773
|
+
event,
|
|
5774
|
+
runtime: params.runtime,
|
|
5775
|
+
channelRuntime: params.channelRuntime
|
|
5776
|
+
});
|
|
5777
|
+
}
|
|
5778
|
+
function createFeishuVcMeetingInvitedHandler(params) {
|
|
5779
|
+
const { cfg, accountId, runtime, fireAndForget, autoJoin } = params;
|
|
5780
|
+
const log = runtime?.log ?? console.log;
|
|
5781
|
+
const error = runtime?.error ?? console.error;
|
|
5782
|
+
return async (data) => {
|
|
5783
|
+
if (!autoJoin) {
|
|
5784
|
+
log(`feishu[${accountId}]: ignoring vc meeting invite (vcAutoJoin=false)`);
|
|
5785
|
+
return;
|
|
5786
|
+
}
|
|
5787
|
+
try {
|
|
5788
|
+
const turn = resolveVcMeetingInvitedTurn(data);
|
|
5789
|
+
if (!turn) {
|
|
5790
|
+
log(`feishu[${accountId}]: vc meeting invited event has invalid meeting_no or inviter identity, skipping`);
|
|
5791
|
+
return;
|
|
5792
|
+
}
|
|
5793
|
+
const promise = dispatchVcMeetingInvitedTurn({
|
|
5794
|
+
cfg,
|
|
5795
|
+
accountId,
|
|
5796
|
+
runtime,
|
|
5797
|
+
channelRuntime: params.channelRuntime,
|
|
5798
|
+
turn
|
|
5799
|
+
});
|
|
5800
|
+
if (fireAndForget) {
|
|
5801
|
+
promise.catch((err) => {
|
|
5802
|
+
error(`feishu[${accountId}]: error handling vc meeting invited event: ${String(err)}`);
|
|
5803
|
+
});
|
|
5804
|
+
return;
|
|
5805
|
+
}
|
|
5806
|
+
await promise;
|
|
5807
|
+
} catch (err) {
|
|
5808
|
+
error(`feishu[${accountId}]: error handling vc meeting invited event: ${String(err)}`);
|
|
5809
|
+
}
|
|
5810
|
+
};
|
|
5811
|
+
}
|
|
5812
|
+
//#endregion
|
|
5494
5813
|
//#region extensions/feishu/src/sequential-key.ts
|
|
5495
5814
|
function getFeishuSequentialKey(params) {
|
|
5496
5815
|
const { accountId, event, botOpenId, botName } = params;
|
|
@@ -5619,7 +5938,7 @@ function parseFeishuCardActionEventPayload(value) {
|
|
|
5619
5938
|
};
|
|
5620
5939
|
}
|
|
5621
5940
|
function registerEventHandlers(eventDispatcher, context) {
|
|
5622
|
-
const { cfg, accountId, channelRuntime, runtime, chatHistories, fireAndForget } = context;
|
|
5941
|
+
const { cfg, accountId, channelRuntime, runtime, chatHistories, fireAndForget, abortSignal } = context;
|
|
5623
5942
|
const log = runtime?.log ?? console.log;
|
|
5624
5943
|
const error = runtime?.error ?? console.error;
|
|
5625
5944
|
const runFeishuHandler = async (params) => {
|
|
@@ -5646,7 +5965,6 @@ function registerEventHandlers(eventDispatcher, context) {
|
|
|
5646
5965
|
handleMessage: handleFeishuMessage,
|
|
5647
5966
|
resolveDebounceText: ({ event, botOpenId, botName }) => parseFeishuMessageEvent(event, botOpenId, botName).content,
|
|
5648
5967
|
hasProcessedMessage: hasProcessedFeishuMessage,
|
|
5649
|
-
recordProcessedMessage: recordProcessedFeishuMessage,
|
|
5650
5968
|
getBotOpenId: (id) => botOpenIds.get(id),
|
|
5651
5969
|
getBotName: (id) => botNames.get(id),
|
|
5652
5970
|
resolveSequentialKey: getFeishuSequentialKey,
|
|
@@ -5676,7 +5994,16 @@ function registerEventHandlers(eventDispatcher, context) {
|
|
|
5676
5994
|
cfg,
|
|
5677
5995
|
accountId,
|
|
5678
5996
|
runtime,
|
|
5679
|
-
fireAndForget
|
|
5997
|
+
fireAndForget,
|
|
5998
|
+
abortSignal
|
|
5999
|
+
}),
|
|
6000
|
+
"vc.bot.meeting_invited_v1": createFeishuVcMeetingInvitedHandler({
|
|
6001
|
+
cfg,
|
|
6002
|
+
accountId,
|
|
6003
|
+
runtime,
|
|
6004
|
+
fireAndForget,
|
|
6005
|
+
channelRuntime,
|
|
6006
|
+
autoJoin: context.vcAutoJoin
|
|
5680
6007
|
}),
|
|
5681
6008
|
"im.message.reaction.created_v1": async (data) => {
|
|
5682
6009
|
await runFeishuHandler({
|
|
@@ -5805,6 +6132,8 @@ async function monitorSingleAccount(params) {
|
|
|
5805
6132
|
runtime,
|
|
5806
6133
|
chatHistories,
|
|
5807
6134
|
fireAndForget: params.fireAndForget ?? true,
|
|
6135
|
+
vcAutoJoin: account.config.vcAutoJoin === true,
|
|
6136
|
+
abortSignal,
|
|
5808
6137
|
...params.statusSink ? { statusSink: params.statusSink } : {}
|
|
5809
6138
|
});
|
|
5810
6139
|
if (connectionMode === "webhook") return await monitorWebhook({
|