@openclaw/feishu 2026.7.2-beta.2 → 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 +3 -3
- package/dist/{channel-X30GQVwc.js → channel-BdnXYU6g.js} +20 -18
- package/dist/channel-plugin-api.js +1 -1
- package/dist/{channel.runtime-By2uqa3Q.js → channel.runtime-CVIzo2dV.js} +100 -15
- package/dist/{drive-B4E8OXZ0.js → drive-BHv8WW9i.js} +1 -1
- package/dist/{monitor-CHl0bWzb.js → monitor-BVQf7-Bl.js} +1 -1
- package/dist/{monitor.account-BSRV2hIF.js → monitor.account-Blbf2T6J.js} +665 -335
- package/dist/{secret-contract-DnlMcrn5.js → secret-contract-BCpDLdg9.js} +17 -26
- package/dist/secret-contract-api.js +1 -1
- package/dist/{send-DrWM_Ilf.js → send-DcrXbqqA.js} +31 -2
- package/dist/{send-result-DsqTk27v.js → send-result-DfE5Fhz6.js} +22 -11
- package/dist/setup-api.js +1 -1
- package/npm-shrinkwrap.json +3 -3
- package/openclaw.plugin.json +24 -0
- package/package.json +4 -4
|
@@ -1,14 +1,14 @@
|
|
|
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 { A as resolveFeishuChatType, C as resolveFeishuDmIngressAccess, E as resolveFeishuGroupSenderActivationIngressAccess, M as buildFeishuCardActionTextFallback, N as createFeishuCardInteractionEnvelope, O as resolveFeishuReplyPolicy, P as decodeFeishuCardAction, S as normalizeFeishuAllowEntry, T as resolveFeishuGroupConversationIngressAccess, d as chunkFeishuPostMarkdown, f as materializeFeishuPostMarkdownSoftBreaks, k as normalizeFeishuChatType$2, o as resolveFeishuCardTemplate, w as resolveFeishuGroupConfig, x as hasExplicitFeishuGroupConfig } from "./send-result-
|
|
3
|
+
import { A as resolveFeishuChatType, C as resolveFeishuDmIngressAccess, E as resolveFeishuGroupSenderActivationIngressAccess, M as buildFeishuCardActionTextFallback, N as createFeishuCardInteractionEnvelope, O as resolveFeishuReplyPolicy, P as decodeFeishuCardAction, S as normalizeFeishuAllowEntry, T as resolveFeishuGroupConversationIngressAccess, d as chunkFeishuPostMarkdown, f as materializeFeishuPostMarkdownSoftBreaks, k as normalizeFeishuChatType$2, o as resolveFeishuCardTemplate, w as resolveFeishuGroupConfig, x as hasExplicitFeishuGroupConfig } from "./send-result-DfE5Fhz6.js";
|
|
4
4
|
import { o as resolveConfiguredFeishuGroupSessionScope, t as buildFeishuConversationId } from "./conversation-id-BeJL-wq7.js";
|
|
5
5
|
import { a as getFeishuUserAgent, i as createFeishuWSClient, n as createEventDispatcher, r as createFeishuClient, s as resolveConfiguredHttpTimeoutMs } from "./client-87BmeMpj.js";
|
|
6
6
|
import { t as getFeishuRuntime } from "./runtime-C5JxBWZp.js";
|
|
7
|
-
import { i as createCommentTypingReactionLifecycle, l as getChatInfo, t as deliverCommentThreadText } from "./drive-
|
|
7
|
+
import { i as createCommentTypingReactionLifecycle, l as getChatInfo, t as deliverCommentThreadText } from "./drive-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
|
|
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
12
|
import { i as waitForAbortableDelay, r as raceWithTimeoutAndAbort } from "./probe-BgboTHIn.js";
|
|
13
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";
|
|
@@ -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";
|
|
37
|
+
import { pruneMapToMaxSize } from "openclaw/plugin-sdk/collection-runtime";
|
|
36
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 {};
|
|
@@ -1489,11 +1634,6 @@ function rememberStreamingStartFailure(accountId, now = Date.now()) {
|
|
|
1489
1634
|
streamingStartBackoffUntilByAccount.set(accountId, backoffUntil);
|
|
1490
1635
|
return backoffUntil;
|
|
1491
1636
|
}
|
|
1492
|
-
function formatMediaFallbackText(text, mediaUrl) {
|
|
1493
|
-
const trimmedText = text?.trim() ?? "";
|
|
1494
|
-
const attachmentText = `📎 ${mediaUrl}`;
|
|
1495
|
-
return trimmedText ? `${trimmedText}\n\n${attachmentText}` : attachmentText;
|
|
1496
|
-
}
|
|
1497
1637
|
function normalizeEpochMs(timestamp) {
|
|
1498
1638
|
if (!Number.isFinite(timestamp) || timestamp === void 0 || timestamp <= 0) return;
|
|
1499
1639
|
return timestamp < MS_EPOCH_MIN ? timestamp * 1e3 : timestamp;
|
|
@@ -1518,7 +1658,7 @@ function resolveCardNote(agentId, identity, prefixCtx) {
|
|
|
1518
1658
|
}
|
|
1519
1659
|
function createFeishuReplyDispatcher(params) {
|
|
1520
1660
|
const core = getFeishuRuntime();
|
|
1521
|
-
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;
|
|
1522
1662
|
const sendReplyToMessageId = skipReplyToInMessages ? void 0 : replyToMessageId;
|
|
1523
1663
|
const typingTargetMessageId = explicitTypingTargetMessageId?.trim() || replyToMessageId;
|
|
1524
1664
|
const threadReplyMode = threadReply === true;
|
|
@@ -1583,7 +1723,7 @@ function createFeishuReplyDispatcher(params) {
|
|
|
1583
1723
|
channel: "feishu"
|
|
1584
1724
|
});
|
|
1585
1725
|
const renderMode = account.config?.renderMode ?? "auto";
|
|
1586
|
-
const streamingEnabled = resolveChannelPreviewStreamMode(account.config, "partial") !== "off" && renderMode !== "raw";
|
|
1726
|
+
const streamingEnabled = !requiredMentionTargets?.length && resolveChannelPreviewStreamMode(account.config, "partial") !== "off" && renderMode !== "raw";
|
|
1587
1727
|
const blockStreamingEnabled = resolveChannelStreamingBlockEnabled(account.config);
|
|
1588
1728
|
const coreBlockStreamingEnabled = blockStreamingEnabled === true;
|
|
1589
1729
|
const reasoningPreviewEnabled = streamingEnabled && params.allowReasoningPreview === true;
|
|
@@ -1731,12 +1871,15 @@ function createFeishuReplyDispatcher(params) {
|
|
|
1731
1871
|
limit: textChunkLimit,
|
|
1732
1872
|
mode: chunkMode,
|
|
1733
1873
|
firstChunkMentions: paramsLocal.firstChunkMentions,
|
|
1874
|
+
chunkMentions: paramsLocal.chunkMentions,
|
|
1734
1875
|
initialChunks
|
|
1735
1876
|
}));
|
|
1736
1877
|
for (const [index, chunk] of chunks.entries()) {
|
|
1878
|
+
const mentions = [...paramsLocal.chunkMentions ?? [], ...index === 0 ? paramsLocal.firstChunkMentions ?? [] : []];
|
|
1737
1879
|
await paramsLocal.sendChunk({
|
|
1738
1880
|
chunk,
|
|
1739
|
-
isFirst: index === 0
|
|
1881
|
+
isFirst: index === 0,
|
|
1882
|
+
mentions: mentions.length > 0 ? mentions : void 0
|
|
1740
1883
|
});
|
|
1741
1884
|
markVisibleReplySent();
|
|
1742
1885
|
}
|
|
@@ -1765,7 +1908,8 @@ function createFeishuReplyDispatcher(params) {
|
|
|
1765
1908
|
text: options.fallbackText,
|
|
1766
1909
|
useCard: false,
|
|
1767
1910
|
infoKind: "final",
|
|
1768
|
-
|
|
1911
|
+
chunkMentions: requiredMentionTargets,
|
|
1912
|
+
sendChunk: async ({ chunk, mentions }) => {
|
|
1769
1913
|
await sendMessageFeishu({
|
|
1770
1914
|
cfg,
|
|
1771
1915
|
to: sendTarget,
|
|
@@ -1773,20 +1917,25 @@ function createFeishuReplyDispatcher(params) {
|
|
|
1773
1917
|
replyToMessageId: sendReplyToMessageId,
|
|
1774
1918
|
replyInThread: effectiveReplyInThread,
|
|
1775
1919
|
allowTopLevelReplyFallback,
|
|
1776
|
-
accountId
|
|
1920
|
+
accountId,
|
|
1921
|
+
...mentions ? { mentions } : {}
|
|
1777
1922
|
});
|
|
1778
1923
|
}
|
|
1779
1924
|
});
|
|
1780
1925
|
}
|
|
1781
1926
|
},
|
|
1782
1927
|
onError: options?.fallbackText === void 0 ? void 0 : async ({ mediaUrl }) => {
|
|
1783
|
-
const fallbackText =
|
|
1928
|
+
const fallbackText = await buildFeishuMediaFallbackText({
|
|
1929
|
+
text: sentFallbackText ? void 0 : options.fallbackText,
|
|
1930
|
+
mediaUrl
|
|
1931
|
+
});
|
|
1784
1932
|
sentFallbackText = true;
|
|
1785
1933
|
await sendChunkedTextReply({
|
|
1786
1934
|
text: fallbackText,
|
|
1787
1935
|
useCard: false,
|
|
1788
1936
|
infoKind: "final",
|
|
1789
|
-
|
|
1937
|
+
chunkMentions: requiredMentionTargets,
|
|
1938
|
+
sendChunk: async ({ chunk, mentions }) => {
|
|
1790
1939
|
await sendMessageFeishu({
|
|
1791
1940
|
cfg,
|
|
1792
1941
|
to: sendTarget,
|
|
@@ -1794,7 +1943,8 @@ function createFeishuReplyDispatcher(params) {
|
|
|
1794
1943
|
replyToMessageId: sendReplyToMessageId,
|
|
1795
1944
|
replyInThread: effectiveReplyInThread,
|
|
1796
1945
|
allowTopLevelReplyFallback,
|
|
1797
|
-
accountId
|
|
1946
|
+
accountId,
|
|
1947
|
+
...mentions ? { mentions } : {}
|
|
1798
1948
|
});
|
|
1799
1949
|
}
|
|
1800
1950
|
});
|
|
@@ -1815,7 +1965,8 @@ function createFeishuReplyDispatcher(params) {
|
|
|
1815
1965
|
replyToMessageId: sendReplyToMessageId,
|
|
1816
1966
|
replyInThread: effectiveReplyInThread,
|
|
1817
1967
|
allowTopLevelReplyFallback,
|
|
1818
|
-
accountId
|
|
1968
|
+
accountId,
|
|
1969
|
+
...requiredMentionTargets?.length ? { mentions: requiredMentionTargets } : {}
|
|
1819
1970
|
});
|
|
1820
1971
|
markVisibleReplySent();
|
|
1821
1972
|
params.runtime.error?.(`feishu[${account.accountId}]: sent no-visible-reply fallback (${reason})`);
|
|
@@ -1829,10 +1980,10 @@ function createFeishuReplyDispatcher(params) {
|
|
|
1829
1980
|
idleSideEffectsPromise = nextIdleSideEffects.catch(() => {});
|
|
1830
1981
|
return nextIdleSideEffects;
|
|
1831
1982
|
};
|
|
1832
|
-
const
|
|
1983
|
+
const dispatcherOptions = {
|
|
1833
1984
|
responsePrefix: prefixContext.responsePrefix,
|
|
1834
1985
|
responsePrefixContextProvider: prefixContext.responsePrefixContextProvider,
|
|
1835
|
-
humanDelay:
|
|
1986
|
+
humanDelay: resolveHumanDelayConfig(cfg, agentId),
|
|
1836
1987
|
silentReplyContext: {
|
|
1837
1988
|
cfg,
|
|
1838
1989
|
sessionKey: params.sessionKey,
|
|
@@ -1855,142 +2006,151 @@ function createFeishuReplyDispatcher(params) {
|
|
|
1855
2006
|
if (streamingEnabled && renderMode === "card") startStreaming();
|
|
1856
2007
|
await Promise.resolve(typingCallbacks?.onReplyStart?.());
|
|
1857
2008
|
},
|
|
1858
|
-
|
|
1859
|
-
|
|
1860
|
-
|
|
1861
|
-
|
|
1862
|
-
|
|
1863
|
-
|
|
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
|
-
|
|
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;
|
|
1908
2079
|
}
|
|
1909
|
-
|
|
2080
|
+
startStreaming();
|
|
2081
|
+
if (streamingStartPromise) await streamingStartPromise;
|
|
1910
2082
|
}
|
|
1911
|
-
|
|
1912
|
-
|
|
1913
|
-
|
|
1914
|
-
if (info?.kind === "final" && useStreamingCard) {
|
|
1915
|
-
startStreaming();
|
|
1916
|
-
if (streamingStartPromise) await streamingStartPromise;
|
|
1917
|
-
}
|
|
1918
|
-
const shouldStreamText = info?.kind === "block" || info?.kind === "final";
|
|
1919
|
-
if (streaming?.isActive() && shouldStreamText) {
|
|
1920
|
-
if (info?.kind === "block") queueStreamingUpdate(text, {
|
|
1921
|
-
mode: "delta",
|
|
1922
|
-
dedupeWithLastPartial: true
|
|
1923
|
-
});
|
|
1924
|
-
if (info?.kind === "final") {
|
|
1925
|
-
streamText = text;
|
|
1926
|
-
hasStreamingFinalText = true;
|
|
1927
|
-
snapshotBaseText = "";
|
|
1928
|
-
lastSnapshotTextLength = text.length;
|
|
1929
|
-
flushStreamingCardUpdate(buildCombinedStreamText(reasoningText, streamText));
|
|
2083
|
+
if (info?.kind === "final" && useStreamingCard) {
|
|
2084
|
+
startStreaming();
|
|
2085
|
+
if (streamingStartPromise) await streamingStartPromise;
|
|
1930
2086
|
}
|
|
1931
|
-
|
|
1932
|
-
|
|
1933
|
-
|
|
1934
|
-
|
|
1935
|
-
|
|
1936
|
-
|
|
1937
|
-
|
|
1938
|
-
|
|
1939
|
-
|
|
1940
|
-
|
|
1941
|
-
|
|
1942
|
-
|
|
1943
|
-
cfg,
|
|
1944
|
-
to: sendTarget,
|
|
1945
|
-
text: chunk,
|
|
1946
|
-
replyToMessageId: sendReplyToMessageId,
|
|
1947
|
-
replyInThread: effectiveReplyInThread,
|
|
1948
|
-
allowTopLevelReplyFallback,
|
|
1949
|
-
accountId,
|
|
1950
|
-
header: cardHeader,
|
|
1951
|
-
note: cardNote
|
|
1952
|
-
});
|
|
1953
|
-
}
|
|
1954
|
-
});
|
|
1955
|
-
} else {
|
|
1956
|
-
const firstChunkMentions = info?.kind === "final" && mentionTargets?.length ? mentionTargets : void 0;
|
|
1957
|
-
await sendChunkedTextReply({
|
|
1958
|
-
text,
|
|
1959
|
-
useCard: false,
|
|
1960
|
-
infoKind: info?.kind,
|
|
1961
|
-
firstChunkMentions,
|
|
1962
|
-
sendChunk: async ({ chunk, isFirst }) => {
|
|
1963
|
-
await sendMessageFeishu({
|
|
1964
|
-
cfg,
|
|
1965
|
-
to: sendTarget,
|
|
1966
|
-
text: chunk,
|
|
1967
|
-
replyToMessageId: sendReplyToMessageId,
|
|
1968
|
-
replyInThread: effectiveReplyInThread,
|
|
1969
|
-
allowTopLevelReplyFallback,
|
|
1970
|
-
accountId,
|
|
1971
|
-
...isFirst && firstChunkMentions ? { mentions: firstChunkMentions } : {}
|
|
1972
|
-
});
|
|
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));
|
|
1973
2099
|
}
|
|
1974
|
-
|
|
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
|
+
}
|
|
1975
2148
|
}
|
|
1976
|
-
|
|
1977
|
-
|
|
1978
|
-
|
|
1979
|
-
onError: async (error, info) => {
|
|
1980
|
-
streamingCloseErroredForReply = true;
|
|
1981
|
-
streamingClosedForReply = false;
|
|
1982
|
-
params.runtime.error?.(`feishu[${account.accountId}] ${info.kind} reply failed: ${String(error)}`);
|
|
1983
|
-
await queueIdleSideEffects({ markClosedForReply: false });
|
|
2149
|
+
if (hasMedia) await sendMediaReplies(payload, hasVoiceMedia && hasText ? { fallbackText: text } : void 0);
|
|
2150
|
+
},
|
|
2151
|
+
onError: handleDeliveryError
|
|
1984
2152
|
},
|
|
1985
|
-
onIdle: () => queueIdleSideEffects(),
|
|
1986
|
-
onCleanup: () => {
|
|
1987
|
-
typingCallbacks?.onCleanup?.();
|
|
1988
|
-
}
|
|
1989
|
-
});
|
|
1990
|
-
return {
|
|
1991
|
-
dispatcher,
|
|
1992
2153
|
replyOptions: {
|
|
1993
|
-
...replyOptions,
|
|
1994
2154
|
onModelSelected: prefixContext.onModelSelected,
|
|
1995
2155
|
disableBlockStreaming: typeof blockStreamingEnabled === "boolean" ? !blockStreamingEnabled : true,
|
|
1996
2156
|
onPartialReply: streamingEnabled ? (payload) => {
|
|
@@ -2032,7 +2192,6 @@ function createFeishuReplyDispatcher(params) {
|
|
|
2032
2192
|
updateStreamingStatusLine("");
|
|
2033
2193
|
} : void 0
|
|
2034
2194
|
},
|
|
2035
|
-
markDispatchIdle,
|
|
2036
2195
|
ensureNoVisibleReplyFallback,
|
|
2037
2196
|
getVisibleReplyState: () => ({
|
|
2038
2197
|
visibleReplySent,
|
|
@@ -2041,6 +2200,17 @@ function createFeishuReplyDispatcher(params) {
|
|
|
2041
2200
|
};
|
|
2042
2201
|
}
|
|
2043
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
|
|
2044
2214
|
//#region extensions/feishu/src/bot.ts
|
|
2045
2215
|
const permissionErrorNotifiedAt = /* @__PURE__ */ new Map();
|
|
2046
2216
|
const PERMISSION_ERROR_COOLDOWN_MS = 300 * 1e3;
|
|
@@ -2093,6 +2263,7 @@ function parseFeishuMessageEvent(event, botOpenId, _botName) {
|
|
|
2093
2263
|
suppressReplyTarget: event.message.suppress_reply_target === true,
|
|
2094
2264
|
senderId: senderUserId || senderOpenId || "",
|
|
2095
2265
|
senderOpenId: senderFallbackId,
|
|
2266
|
+
senderType: event.sender.sender_type === "bot" ? "bot" : "user",
|
|
2096
2267
|
chatType: event.message.chat_type,
|
|
2097
2268
|
mentionedBot,
|
|
2098
2269
|
hasAnyMention,
|
|
@@ -2164,7 +2335,62 @@ async function handleFeishuMessage(params) {
|
|
|
2164
2335
|
let ctx = parseFeishuMessageEvent(event, botOpenId, botName);
|
|
2165
2336
|
const isGroup = isFeishuGroupChatType(ctx.chatType);
|
|
2166
2337
|
const isDirect = !isGroup;
|
|
2338
|
+
const directPreDispatchTarget = isDirect ? getFeishuSyntheticDirectPreDispatchTarget(event) : void 0;
|
|
2167
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
|
+
}
|
|
2168
2394
|
if (event.message.message_type === "merge_forward") {
|
|
2169
2395
|
log(`feishu[${account.accountId}]: processing merge_forward message, fetching full content via API`);
|
|
2170
2396
|
try {
|
|
@@ -2195,7 +2421,17 @@ async function handleFeishuMessage(params) {
|
|
|
2195
2421
|
}
|
|
2196
2422
|
}
|
|
2197
2423
|
let permissionErrorForAgent;
|
|
2198
|
-
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 {
|
|
2199
2435
|
const senderResult = await resolveFeishuSenderName({
|
|
2200
2436
|
account,
|
|
2201
2437
|
senderId: ctx.senderOpenId,
|
|
@@ -2332,6 +2568,23 @@ async function handleFeishuMessage(params) {
|
|
|
2332
2568
|
});
|
|
2333
2569
|
return;
|
|
2334
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
|
+
}
|
|
2335
2588
|
}
|
|
2336
2589
|
try {
|
|
2337
2590
|
const core = { channel: channelRuntime?.inbound ? channelRuntime : getFeishuRuntime().channel };
|
|
@@ -2379,7 +2632,7 @@ async function handleFeishuMessage(params) {
|
|
|
2379
2632
|
sendPairingReply: async (text) => {
|
|
2380
2633
|
await sendMessageFeishu({
|
|
2381
2634
|
cfg: authorization.cfg,
|
|
2382
|
-
to: `chat:${ctx.chatId}`,
|
|
2635
|
+
to: directPreDispatchTarget ?? `chat:${ctx.chatId}`,
|
|
2383
2636
|
text,
|
|
2384
2637
|
accountId: account.accountId
|
|
2385
2638
|
});
|
|
@@ -2514,7 +2767,7 @@ async function handleFeishuMessage(params) {
|
|
|
2514
2767
|
const replyTargetMessageId = directThreadReply ? directThreadReplyTargetMessageId : acpTopicReply ? ctx.rootId ?? ctx.messageId : ctx.messageId;
|
|
2515
2768
|
await sendMessageFeishu({
|
|
2516
2769
|
cfg: effectiveCfg,
|
|
2517
|
-
to: `chat:${ctx.chatId}`,
|
|
2770
|
+
to: directPreDispatchTarget ?? `chat:${ctx.chatId}`,
|
|
2518
2771
|
text: `⚠️ Failed to initialize the configured ACP session for this Feishu conversation: ${ensured.error}`,
|
|
2519
2772
|
replyToMessageId: replyTargetMessageId,
|
|
2520
2773
|
replyInThread,
|
|
@@ -2588,6 +2841,11 @@ async function handleFeishuMessage(params) {
|
|
|
2588
2841
|
});
|
|
2589
2842
|
const preflightAudioIndex = audioTranscript === void 0 ? -1 : mediaList.findIndex((media) => media.contentType?.startsWith("audio/"));
|
|
2590
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;
|
|
2591
2849
|
const agentFacingContent = audioTranscript ?? mediaFailureContent;
|
|
2592
2850
|
const commandFacingContent = audioTranscript ?? ctx.content;
|
|
2593
2851
|
const agentFacingCtx = agentFacingContent === ctx.content ? ctx : {
|
|
@@ -2618,7 +2876,7 @@ async function handleFeishuMessage(params) {
|
|
|
2618
2876
|
command: { hasControlCommand: true }
|
|
2619
2877
|
})).commandAccess.authorized : void 0;
|
|
2620
2878
|
const isTopicSessionForThread = isGroup && (groupSession?.groupSessionScope === "group_topic" || groupSession?.groupSessionScope === "group_topic_sender");
|
|
2621
|
-
const envelopeOptions =
|
|
2879
|
+
const envelopeOptions = resolveEnvelopeFormatOptions(cfg);
|
|
2622
2880
|
const messageBody = buildFeishuAgentBody({
|
|
2623
2881
|
ctx: agentFacingCtx,
|
|
2624
2882
|
quotedContent,
|
|
@@ -2627,7 +2885,7 @@ async function handleFeishuMessage(params) {
|
|
|
2627
2885
|
});
|
|
2628
2886
|
const envelopeFrom = isGroup ? `${ctx.chatId}:${ctx.senderOpenId}` : ctx.senderOpenId;
|
|
2629
2887
|
if (permissionErrorForAgent) log(`feishu[${account.accountId}]: appending permission error notice to message body`);
|
|
2630
|
-
let combinedBody =
|
|
2888
|
+
let combinedBody = formatAgentEnvelope({
|
|
2631
2889
|
channel: "Feishu",
|
|
2632
2890
|
from: envelopeFrom,
|
|
2633
2891
|
timestamp: /* @__PURE__ */ new Date(),
|
|
@@ -2639,7 +2897,7 @@ async function handleFeishuMessage(params) {
|
|
|
2639
2897
|
historyKey,
|
|
2640
2898
|
limit: historyLimit,
|
|
2641
2899
|
currentMessage: combinedBody,
|
|
2642
|
-
formatEntry: (entry) =>
|
|
2900
|
+
formatEntry: (entry) => formatAgentEnvelope({
|
|
2643
2901
|
channel: "Feishu",
|
|
2644
2902
|
from: `${ctx.chatId}:${entry.sender}`,
|
|
2645
2903
|
timestamp: entry.timestamp,
|
|
@@ -2706,7 +2964,7 @@ async function handleFeishuMessage(params) {
|
|
|
2706
2964
|
threadContextBySessionKey.set(agentSessionKey, threadContext);
|
|
2707
2965
|
return threadContext;
|
|
2708
2966
|
}
|
|
2709
|
-
const storePath =
|
|
2967
|
+
const storePath = resolveStorePath(cfg.session?.store, { agentId });
|
|
2710
2968
|
if (core.channel.session.readSessionUpdatedAt({
|
|
2711
2969
|
storePath,
|
|
2712
2970
|
sessionKey: agentSessionKey
|
|
@@ -2748,7 +3006,7 @@ async function handleFeishuMessage(params) {
|
|
|
2748
3006
|
const historyMessages = Boolean(rootMsg?.content || ctx.rootId) ? relevantMessages : relevantMessages.slice(1);
|
|
2749
3007
|
const historyParts = historyMessages.map((msg) => {
|
|
2750
3008
|
const role = msg.senderType === "app" ? "assistant" : "user";
|
|
2751
|
-
return
|
|
3009
|
+
return formatAgentEnvelope({
|
|
2752
3010
|
channel: "Feishu",
|
|
2753
3011
|
from: `${msg.senderId ?? "Unknown"} (${role})`,
|
|
2754
3012
|
timestamp: msg.createTime,
|
|
@@ -2770,7 +3028,6 @@ async function handleFeishuMessage(params) {
|
|
|
2770
3028
|
const threadContext = await resolveThreadContextForAgent(agentId, agentSessionKey, groupName);
|
|
2771
3029
|
return buildChannelInboundEventContext({
|
|
2772
3030
|
channel: "feishu",
|
|
2773
|
-
finalize: core.channel.reply.finalizeInboundContext,
|
|
2774
3031
|
supplemental: {
|
|
2775
3032
|
quote: quotedContent ? {
|
|
2776
3033
|
id: ctx.parentId,
|
|
@@ -2789,7 +3046,8 @@ async function handleFeishuMessage(params) {
|
|
|
2789
3046
|
from: feishuFrom,
|
|
2790
3047
|
sender: {
|
|
2791
3048
|
id: ctx.senderOpenId,
|
|
2792
|
-
name: ctx.senderName ?? ctx.senderOpenId
|
|
3049
|
+
name: ctx.senderName ?? ctx.senderOpenId,
|
|
3050
|
+
isBot: ctx.senderType === "bot"
|
|
2793
3051
|
},
|
|
2794
3052
|
conversation: {
|
|
2795
3053
|
kind: isGroup ? "group" : "direct",
|
|
@@ -2800,6 +3058,7 @@ async function handleFeishuMessage(params) {
|
|
|
2800
3058
|
},
|
|
2801
3059
|
route: {
|
|
2802
3060
|
agentId,
|
|
3061
|
+
dmScope: route.dmScope,
|
|
2803
3062
|
accountId: agentAccountId,
|
|
2804
3063
|
routeSessionKey: agentSessionKey
|
|
2805
3064
|
},
|
|
@@ -2879,7 +3138,7 @@ async function handleFeishuMessage(params) {
|
|
|
2879
3138
|
return;
|
|
2880
3139
|
}
|
|
2881
3140
|
const agentSessionKey = buildBroadcastSessionKey(route.sessionKey, route.agentId, agentId);
|
|
2882
|
-
const agentStorePath =
|
|
3141
|
+
const agentStorePath = resolveStorePath(cfg.session?.store, { agentId });
|
|
2883
3142
|
const agentRecord = {
|
|
2884
3143
|
updateLastRoute: buildFeishuInboundLastRouteUpdate({
|
|
2885
3144
|
sessionKey: agentSessionKey,
|
|
@@ -2898,7 +3157,7 @@ async function handleFeishuMessage(params) {
|
|
|
2898
3157
|
const agentCtx = await buildCtxPayloadForAgent(agentId, agentSessionKey, route.accountId, ctx.mentionedBot && agentId === activeAgentId);
|
|
2899
3158
|
if (agentId === activeAgentId) {
|
|
2900
3159
|
const identity = resolveAgentOutboundIdentity(cfg, agentId);
|
|
2901
|
-
const {
|
|
3160
|
+
const { dispatcherOptions, delivery, replyOptions, ensureNoVisibleReplyFallback } = createFeishuReplyDispatcher({
|
|
2902
3161
|
cfg,
|
|
2903
3162
|
agentId,
|
|
2904
3163
|
runtime,
|
|
@@ -2914,6 +3173,7 @@ async function handleFeishuMessage(params) {
|
|
|
2914
3173
|
accountId: account.accountId,
|
|
2915
3174
|
identity,
|
|
2916
3175
|
mentionTargets: ctx.mentionTargets,
|
|
3176
|
+
requiredMentionTargets,
|
|
2917
3177
|
messageCreateTimeMs,
|
|
2918
3178
|
sessionKey: agentSessionKey
|
|
2919
3179
|
});
|
|
@@ -2932,57 +3192,24 @@ async function handleFeishuMessage(params) {
|
|
|
2932
3192
|
raw: ctx
|
|
2933
3193
|
}),
|
|
2934
3194
|
resolveTurn: () => ({
|
|
3195
|
+
cfg,
|
|
2935
3196
|
channel: "feishu",
|
|
2936
3197
|
accountId: route.accountId,
|
|
2937
|
-
|
|
2938
|
-
|
|
3198
|
+
route: {
|
|
3199
|
+
agentId,
|
|
3200
|
+
sessionKey: agentSessionKey
|
|
3201
|
+
},
|
|
2939
3202
|
ctxPayload: agentCtx,
|
|
2940
|
-
recordInboundSession: core.channel.session.recordInboundSession,
|
|
2941
3203
|
record: agentRecord,
|
|
2942
|
-
|
|
2943
|
-
|
|
2944
|
-
|
|
2945
|
-
}),
|
|
2946
|
-
runDispatch: () => core.channel.reply.withReplyDispatcher({
|
|
2947
|
-
dispatcher,
|
|
2948
|
-
onSettled: () => markDispatchIdle(),
|
|
2949
|
-
run: () => core.channel.reply.dispatchReplyFromConfig({
|
|
2950
|
-
ctx: agentCtx,
|
|
2951
|
-
cfg,
|
|
2952
|
-
dispatcher,
|
|
2953
|
-
replyOptions
|
|
2954
|
-
})
|
|
2955
|
-
})
|
|
3204
|
+
dispatcherOptions,
|
|
3205
|
+
delivery,
|
|
3206
|
+
replyOptions
|
|
2956
3207
|
})
|
|
2957
3208
|
}
|
|
2958
3209
|
});
|
|
2959
|
-
if (turnResult.dispatched && shouldSendNoVisibleReplyFallback(
|
|
2960
|
-
...turnResult.dispatchResult,
|
|
2961
|
-
failedCounts: dispatcher.getFailedCounts?.() ?? {
|
|
2962
|
-
tool: 0,
|
|
2963
|
-
block: 0,
|
|
2964
|
-
final: 0
|
|
2965
|
-
}
|
|
2966
|
-
})) await ensureNoVisibleReplyFallback("broadcast-dispatch-complete-no-visible-reply");
|
|
3210
|
+
if (turnResult.dispatched && shouldSendNoVisibleReplyFallback(turnResult.dispatchResult)) await ensureNoVisibleReplyFallback("broadcast-dispatch-complete-no-visible-reply");
|
|
2967
3211
|
} else {
|
|
2968
3212
|
delete agentCtx.CommandAuthorized;
|
|
2969
|
-
const noopDispatcher = {
|
|
2970
|
-
sendToolResult: () => false,
|
|
2971
|
-
sendBlockReply: () => false,
|
|
2972
|
-
sendFinalReply: () => false,
|
|
2973
|
-
waitForIdle: async () => {},
|
|
2974
|
-
getQueuedCounts: () => ({
|
|
2975
|
-
tool: 0,
|
|
2976
|
-
block: 0,
|
|
2977
|
-
final: 0
|
|
2978
|
-
}),
|
|
2979
|
-
getFailedCounts: () => ({
|
|
2980
|
-
tool: 0,
|
|
2981
|
-
block: 0,
|
|
2982
|
-
final: 0
|
|
2983
|
-
}),
|
|
2984
|
-
markComplete: () => {}
|
|
2985
|
-
};
|
|
2986
3213
|
log(`feishu[${account.accountId}]: broadcast observer dispatch agent=${agentId} (session=${agentSessionKey})`);
|
|
2987
3214
|
await core.channel.inbound.run({
|
|
2988
3215
|
channel: "feishu",
|
|
@@ -2998,21 +3225,20 @@ async function handleFeishuMessage(params) {
|
|
|
2998
3225
|
raw: ctx
|
|
2999
3226
|
}),
|
|
3000
3227
|
resolveTurn: () => ({
|
|
3228
|
+
cfg,
|
|
3001
3229
|
channel: "feishu",
|
|
3002
3230
|
accountId: route.accountId,
|
|
3003
|
-
|
|
3004
|
-
|
|
3231
|
+
route: {
|
|
3232
|
+
agentId,
|
|
3233
|
+
sessionKey: agentSessionKey
|
|
3234
|
+
},
|
|
3005
3235
|
ctxPayload: agentCtx,
|
|
3006
|
-
recordInboundSession: core.channel.session.recordInboundSession,
|
|
3007
3236
|
record: agentRecord,
|
|
3008
|
-
|
|
3009
|
-
|
|
3010
|
-
|
|
3011
|
-
|
|
3012
|
-
|
|
3013
|
-
dispatcher: noopDispatcher
|
|
3014
|
-
})
|
|
3015
|
-
})
|
|
3237
|
+
admission: {
|
|
3238
|
+
kind: "observeOnly",
|
|
3239
|
+
reason: "broadcast-observer"
|
|
3240
|
+
},
|
|
3241
|
+
delivery: { deliver: async () => ({ visibleReplySent: false }) }
|
|
3016
3242
|
})
|
|
3017
3243
|
}
|
|
3018
3244
|
});
|
|
@@ -3039,14 +3265,14 @@ async function handleFeishuMessage(params) {
|
|
|
3039
3265
|
} else {
|
|
3040
3266
|
const ctxPayload = await buildCtxPayloadForAgent(route.agentId, route.sessionKey, route.accountId, ctx.mentionedBot);
|
|
3041
3267
|
const identity = resolveAgentOutboundIdentity(effectiveCfg, route.agentId);
|
|
3042
|
-
const storePath =
|
|
3268
|
+
const storePath = resolveStorePath(effectiveCfg.session?.store, { agentId: route.agentId });
|
|
3043
3269
|
const allowReasoningPreview = resolveFeishuReasoningPreviewEnabled({
|
|
3044
3270
|
cfg: effectiveCfg,
|
|
3045
3271
|
agentId: route.agentId,
|
|
3046
3272
|
storePath,
|
|
3047
3273
|
sessionKey: route.sessionKey
|
|
3048
3274
|
});
|
|
3049
|
-
const {
|
|
3275
|
+
const { dispatcherOptions, delivery, replyOptions, ensureNoVisibleReplyFallback } = createFeishuReplyDispatcher({
|
|
3050
3276
|
cfg: effectiveCfg,
|
|
3051
3277
|
agentId: route.agentId,
|
|
3052
3278
|
runtime,
|
|
@@ -3062,6 +3288,7 @@ async function handleFeishuMessage(params) {
|
|
|
3062
3288
|
accountId: account.accountId,
|
|
3063
3289
|
identity,
|
|
3064
3290
|
mentionTargets: ctx.mentionTargets,
|
|
3291
|
+
requiredMentionTargets,
|
|
3065
3292
|
messageCreateTimeMs,
|
|
3066
3293
|
sessionKey: route.sessionKey
|
|
3067
3294
|
});
|
|
@@ -3080,12 +3307,14 @@ async function handleFeishuMessage(params) {
|
|
|
3080
3307
|
raw: ctx
|
|
3081
3308
|
}),
|
|
3082
3309
|
resolveTurn: () => ({
|
|
3310
|
+
cfg: effectiveCfg,
|
|
3083
3311
|
channel: "feishu",
|
|
3084
3312
|
accountId: route.accountId,
|
|
3085
|
-
|
|
3086
|
-
|
|
3313
|
+
route: {
|
|
3314
|
+
agentId: route.agentId,
|
|
3315
|
+
sessionKey: route.sessionKey
|
|
3316
|
+
},
|
|
3087
3317
|
ctxPayload,
|
|
3088
|
-
recordInboundSession: core.channel.session.recordInboundSession,
|
|
3089
3318
|
record: {
|
|
3090
3319
|
updateLastRoute: buildFeishuInboundLastRouteUpdate({
|
|
3091
3320
|
sessionKey: route.sessionKey,
|
|
@@ -3101,36 +3330,16 @@ async function handleFeishuMessage(params) {
|
|
|
3101
3330
|
historyMap: chatHistories,
|
|
3102
3331
|
limit: historyLimit
|
|
3103
3332
|
},
|
|
3104
|
-
|
|
3105
|
-
|
|
3106
|
-
|
|
3107
|
-
}),
|
|
3108
|
-
runDispatch: () => core.channel.reply.withReplyDispatcher({
|
|
3109
|
-
dispatcher,
|
|
3110
|
-
onSettled: () => {
|
|
3111
|
-
markDispatchIdle();
|
|
3112
|
-
},
|
|
3113
|
-
run: () => core.channel.reply.dispatchReplyFromConfig({
|
|
3114
|
-
ctx: ctxPayload,
|
|
3115
|
-
cfg: effectiveCfg,
|
|
3116
|
-
dispatcher,
|
|
3117
|
-
replyOptions
|
|
3118
|
-
})
|
|
3119
|
-
})
|
|
3333
|
+
dispatcherOptions,
|
|
3334
|
+
delivery,
|
|
3335
|
+
replyOptions
|
|
3120
3336
|
})
|
|
3121
3337
|
}
|
|
3122
3338
|
});
|
|
3123
3339
|
if (!turnResult.dispatched) return;
|
|
3124
3340
|
const { dispatchResult } = turnResult;
|
|
3125
3341
|
const { queuedFinal, counts } = dispatchResult;
|
|
3126
|
-
if (shouldSendNoVisibleReplyFallback(
|
|
3127
|
-
...dispatchResult,
|
|
3128
|
-
failedCounts: dispatcher.getFailedCounts?.() ?? {
|
|
3129
|
-
tool: 0,
|
|
3130
|
-
block: 0,
|
|
3131
|
-
final: 0
|
|
3132
|
-
}
|
|
3133
|
-
})) await ensureNoVisibleReplyFallback("dispatch-complete-no-visible-reply");
|
|
3342
|
+
if (shouldSendNoVisibleReplyFallback(dispatchResult)) await ensureNoVisibleReplyFallback("dispatch-complete-no-visible-reply");
|
|
3134
3343
|
log(`feishu[${account.accountId}]: dispatch complete (queuedFinal=${queuedFinal}, replies=${counts.final})`);
|
|
3135
3344
|
}
|
|
3136
3345
|
} catch (err) {
|
|
@@ -3250,7 +3459,7 @@ function completeFeishuCardAction(actionId, accountId, now = Date.now()) {
|
|
|
3250
3459
|
expiresAt
|
|
3251
3460
|
});
|
|
3252
3461
|
}
|
|
3253
|
-
function buildSyntheticMessageEvent(event, content, chatType) {
|
|
3462
|
+
function buildSyntheticMessageEvent$1(event, content, chatType) {
|
|
3254
3463
|
const replyTargetMessageId = event.context.open_message_id ?? event.open_message_id;
|
|
3255
3464
|
const isTemporaryCardActionId = replyTargetMessageId?.startsWith("card-action-c-");
|
|
3256
3465
|
const validReplyTargetId = replyTargetMessageId && !isTemporaryCardActionId ? replyTargetMessageId : void 0;
|
|
@@ -3286,7 +3495,7 @@ async function dispatchSyntheticCommand(params) {
|
|
|
3286
3495
|
});
|
|
3287
3496
|
await handleFeishuMessage({
|
|
3288
3497
|
cfg: params.cfg,
|
|
3289
|
-
event: buildSyntheticMessageEvent(params.event, params.command, resolvedChatType),
|
|
3498
|
+
event: buildSyntheticMessageEvent$1(params.event, params.command, resolvedChatType),
|
|
3290
3499
|
botOpenId: params.botOpenId,
|
|
3291
3500
|
runtime: params.runtime,
|
|
3292
3501
|
channelRuntime: params.channelRuntime,
|
|
@@ -3931,41 +4140,39 @@ function createFeishuCommentReplyDispatcher(params) {
|
|
|
3931
4140
|
accountId: params.accountId,
|
|
3932
4141
|
runtime: params.runtime
|
|
3933
4142
|
});
|
|
3934
|
-
|
|
3935
|
-
|
|
3936
|
-
|
|
3937
|
-
|
|
3938
|
-
|
|
3939
|
-
|
|
3940
|
-
|
|
3941
|
-
|
|
3942
|
-
|
|
3943
|
-
|
|
3944
|
-
if (!reply.hasText) {
|
|
3945
|
-
if (reply.hasMedia) params.runtime.log?.(`feishu[${params.accountId ?? "default"}]: comment reply ignored media-only payload for comment=${params.commentId}`);
|
|
3946
|
-
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();
|
|
3947
4153
|
}
|
|
3948
|
-
const chunks = core.channel.text.chunkTextWithMode(reply.text, textChunkLimit, chunkMode);
|
|
3949
|
-
for (const chunk of chunks) await deliverCommentThreadText(client, {
|
|
3950
|
-
file_token: params.fileToken,
|
|
3951
|
-
file_type: params.fileType,
|
|
3952
|
-
comment_id: params.commentId,
|
|
3953
|
-
content: chunk,
|
|
3954
|
-
is_whole_comment: params.isWholeComment
|
|
3955
|
-
});
|
|
3956
4154
|
},
|
|
3957
|
-
|
|
3958
|
-
|
|
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
|
+
}
|
|
3959
4175
|
},
|
|
3960
|
-
onCleanup: () => {
|
|
3961
|
-
typingReaction.cleanup();
|
|
3962
|
-
}
|
|
3963
|
-
});
|
|
3964
|
-
return {
|
|
3965
|
-
dispatcher,
|
|
3966
|
-
replyOptions,
|
|
3967
|
-
markDispatchIdle,
|
|
3968
|
-
markRunComplete,
|
|
3969
4176
|
startTypingReaction: typingReaction.start,
|
|
3970
4177
|
cleanupTypingReaction: typingReaction.cleanup
|
|
3971
4178
|
};
|
|
@@ -4744,31 +4951,51 @@ async function handleFeishuCommentEvent(params) {
|
|
|
4744
4951
|
fileToken: turn.fileToken
|
|
4745
4952
|
});
|
|
4746
4953
|
const bodyForAgent = `[message_id: ${turn.messageId}]\n${turn.prompt}`;
|
|
4747
|
-
const
|
|
4748
|
-
|
|
4749
|
-
|
|
4750
|
-
|
|
4751
|
-
|
|
4752
|
-
|
|
4753
|
-
|
|
4754
|
-
|
|
4755
|
-
|
|
4756
|
-
|
|
4757
|
-
|
|
4758
|
-
|
|
4759
|
-
|
|
4760
|
-
|
|
4761
|
-
|
|
4762
|
-
|
|
4763
|
-
|
|
4764
|
-
|
|
4765
|
-
|
|
4766
|
-
|
|
4767
|
-
|
|
4768
|
-
|
|
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
|
+
}
|
|
4769
4997
|
});
|
|
4770
|
-
const
|
|
4771
|
-
const { dispatcher, replyOptions, markDispatchIdle, markRunComplete, cleanupTypingReaction } = createFeishuCommentReplyDispatcher({
|
|
4998
|
+
const { dispatcherOptions, delivery, cleanupTypingReaction } = createFeishuCommentReplyDispatcher({
|
|
4772
4999
|
cfg: effectiveCfg,
|
|
4773
5000
|
agentId: route.agentId,
|
|
4774
5001
|
runtime,
|
|
@@ -4779,7 +5006,6 @@ async function handleFeishuCommentEvent(params) {
|
|
|
4779
5006
|
replyId: turn.replyId,
|
|
4780
5007
|
isWholeComment: turn.isWholeComment
|
|
4781
5008
|
});
|
|
4782
|
-
let dispatchSettledBeforeStart = false;
|
|
4783
5009
|
try {
|
|
4784
5010
|
log(`feishu[${account.accountId}]: dispatching drive comment to agent (session=${commentSessionKey} comment=${turn.commentId} type=${turn.noticeType})`);
|
|
4785
5011
|
const turnResult = await core.channel.inbound.run({
|
|
@@ -4796,34 +5022,19 @@ async function handleFeishuCommentEvent(params) {
|
|
|
4796
5022
|
raw: turn
|
|
4797
5023
|
}),
|
|
4798
5024
|
resolveTurn: () => ({
|
|
5025
|
+
cfg: effectiveCfg,
|
|
4799
5026
|
channel: "feishu",
|
|
4800
5027
|
accountId: route.accountId,
|
|
4801
|
-
|
|
4802
|
-
|
|
5028
|
+
route: {
|
|
5029
|
+
agentId: route.agentId,
|
|
5030
|
+
sessionKey: commentSessionKey
|
|
5031
|
+
},
|
|
4803
5032
|
ctxPayload,
|
|
4804
|
-
recordInboundSession: core.channel.session.recordInboundSession,
|
|
4805
5033
|
record: { onRecordError: (err) => {
|
|
4806
5034
|
error(`feishu[${account.accountId}]: failed to record comment inbound session ${commentSessionKey}: ${String(err)}`);
|
|
4807
5035
|
} },
|
|
4808
|
-
|
|
4809
|
-
|
|
4810
|
-
await core.channel.reply.settleReplyDispatcher({
|
|
4811
|
-
dispatcher,
|
|
4812
|
-
onSettled: () => {
|
|
4813
|
-
markRunComplete();
|
|
4814
|
-
markDispatchIdle();
|
|
4815
|
-
}
|
|
4816
|
-
});
|
|
4817
|
-
},
|
|
4818
|
-
runDispatch: () => core.channel.reply.withReplyDispatcher({
|
|
4819
|
-
dispatcher,
|
|
4820
|
-
run: () => core.channel.reply.dispatchReplyFromConfig({
|
|
4821
|
-
ctx: ctxPayload,
|
|
4822
|
-
cfg: effectiveCfg,
|
|
4823
|
-
dispatcher,
|
|
4824
|
-
replyOptions
|
|
4825
|
-
})
|
|
4826
|
-
})
|
|
5036
|
+
dispatcherOptions,
|
|
5037
|
+
delivery
|
|
4827
5038
|
})
|
|
4828
5039
|
}
|
|
4829
5040
|
});
|
|
@@ -4836,10 +5047,6 @@ async function handleFeishuCommentEvent(params) {
|
|
|
4836
5047
|
};
|
|
4837
5048
|
log(`feishu[${account.accountId}]: drive comment dispatch complete (queuedFinal=${queuedFinal}, replies=${counts.final}, session=${commentSessionKey})`);
|
|
4838
5049
|
} finally {
|
|
4839
|
-
if (!dispatchSettledBeforeStart) {
|
|
4840
|
-
markRunComplete();
|
|
4841
|
-
markDispatchIdle();
|
|
4842
|
-
}
|
|
4843
5050
|
cleanupTypingReaction();
|
|
4844
5051
|
}
|
|
4845
5052
|
}
|
|
@@ -5489,6 +5696,120 @@ async function monitorWebhook({ account, accountId, runtime, abortSignal, eventD
|
|
|
5489
5696
|
});
|
|
5490
5697
|
}
|
|
5491
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
|
|
5492
5813
|
//#region extensions/feishu/src/sequential-key.ts
|
|
5493
5814
|
function getFeishuSequentialKey(params) {
|
|
5494
5815
|
const { accountId, event, botOpenId, botName } = params;
|
|
@@ -5676,6 +5997,14 @@ function registerEventHandlers(eventDispatcher, context) {
|
|
|
5676
5997
|
fireAndForget,
|
|
5677
5998
|
abortSignal
|
|
5678
5999
|
}),
|
|
6000
|
+
"vc.bot.meeting_invited_v1": createFeishuVcMeetingInvitedHandler({
|
|
6001
|
+
cfg,
|
|
6002
|
+
accountId,
|
|
6003
|
+
runtime,
|
|
6004
|
+
fireAndForget,
|
|
6005
|
+
channelRuntime,
|
|
6006
|
+
autoJoin: context.vcAutoJoin
|
|
6007
|
+
}),
|
|
5679
6008
|
"im.message.reaction.created_v1": async (data) => {
|
|
5680
6009
|
await runFeishuHandler({
|
|
5681
6010
|
errorMessage: `feishu[${accountId}]: error handling reaction event`,
|
|
@@ -5803,6 +6132,7 @@ async function monitorSingleAccount(params) {
|
|
|
5803
6132
|
runtime,
|
|
5804
6133
|
chatHistories,
|
|
5805
6134
|
fireAndForget: params.fireAndForget ?? true,
|
|
6135
|
+
vcAutoJoin: account.config.vcAutoJoin === true,
|
|
5806
6136
|
abortSignal,
|
|
5807
6137
|
...params.statusSink ? { statusSink: params.statusSink } : {}
|
|
5808
6138
|
});
|