@openclaw/feishu 2026.5.7 → 2026.5.10-beta.1

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.
@@ -2,17 +2,18 @@ import { t as buildFeishuConversationId } from "./conversation-id-DWS3Ep2A.js";
2
2
  import { i as resolveReceiveIdType } from "./targets-JMFJRKSe.js";
3
3
  import { n as createFeishuThreadBindingManager } from "./thread-bindings-BmS6TLes.js";
4
4
  import { _ as buildFeishuCommentTarget, f as isRecord$3, h as readString$2, l as encodeQuery, m as parseCommentContentElements, p as normalizeString, s as resolveFeishuRuntimeAccount, u as extractReplyText, v as normalizeCommentFileType } from "./accounts-Ba3-WP1z.js";
5
- import { i as decodeFeishuCardAction, n as buildFeishuCardActionTextFallback, r as createFeishuCardInteractionEnvelope } from "./card-interaction-BfRLgvw_.js";
6
- import { i as resolveFeishuGroupConfig, n as isFeishuGroupAllowed, o as resolveFeishuReplyPolicy, r as resolveFeishuAllowlistMatch, t as hasExplicitFeishuGroupConfig } from "./policy-D6c-wMPl.js";
5
+ import { c as decodeFeishuCardAction, o as buildFeishuCardActionTextFallback, s as createFeishuCardInteractionEnvelope } from "./send-result-BVFTskB_.js";
6
+ import { a as resolveFeishuGroupSenderActivationIngressAccess, i as resolveFeishuGroupConversationIngressAccess, n as resolveFeishuDmIngressAccess, r as resolveFeishuGroupConfig, s as resolveFeishuReplyPolicy, t as hasExplicitFeishuGroupConfig } from "./policy-DrLgxWF_.js";
7
7
  import { t as getFeishuRuntime } from "./runtime-CG0DuRCy.js";
8
8
  import { a as getFeishuUserAgent, i as createFeishuWSClient, n as createEventDispatcher, r as createFeishuClient } from "./client-DBVoQL5w.js";
9
9
  import { c as getChatInfo, i as createCommentTypingReactionLifecycle, t as deliverCommentThreadText } from "./drive-C5eJLJr7.js";
10
- import { buildAgentMediaPayload, createReplyPrefixContext, evaluateSupplementalContextVisibility, filterSupplementalContextItems, loadSessionStore, normalizeAgentId, resolveChannelContextVisibilityMode, resolveSessionStoreEntry } from "./runtime-api.js";
11
- import { _ as shouldSuppressFeishuTextForVoiceMedia, a as sendCardFeishu, c as sendStructuredCardFeishu, d as extractMentionTargets, f as isFeishuBroadcastMention, g as sendMediaFeishu, h as downloadMessageResourceFeishu, i as resolveFeishuCardTemplate, l as parsePostContent, m as isFeishuGroupChatType, n as getMessageFeishu, p as isMentionForwardRequest, r as listFeishuThreadMessages, s as sendMessageFeishu, u as buildMentionedCardContent, v as normalizeFeishuExternalKey } from "./send-DowxxbpH.js";
10
+ import { buildAgentMediaPayload, createReplyPrefixContext, evaluateSupplementalContextVisibility, loadSessionStore, normalizeAgentId as normalizeAgentId$1, resolveChannelContextVisibilityMode, resolveSessionStoreEntry } from "./runtime-api.js";
11
+ import { _ as normalizeFeishuExternalKey, a as sendCardFeishu, c as sendStructuredCardFeishu, d as isFeishuBroadcastMention, f as isMentionForwardRequest, g as shouldSuppressFeishuTextForVoiceMedia, h as sendMediaFeishu, i as resolveFeishuCardTemplate, l as parsePostContent, m as downloadMessageResourceFeishu, n as getMessageFeishu, p as isFeishuGroupChatType, r as listFeishuThreadMessages, s as sendMessageFeishu, u as extractMentionTargets } from "./send-DKHEQrzH.js";
12
12
  import { i as waitForAbortableDelay, r as raceWithTimeoutAndAbort } from "./probe-BNzzU_uR.js";
13
13
  import { a as feishuWebhookRateLimiter, c as wsClients, i as botOpenIds, l as fetchBotIdentityForMonitor, n as FEISHU_WEBHOOK_MAX_BODY_BYTES, o as httpServers, r as botNames, s as recordWebhookStatus, t as FEISHU_WEBHOOK_BODY_TIMEOUT_MS } from "./monitor.state-DYM02ipp.js";
14
14
  import { normalizeLowercaseStringOrEmpty, normalizeOptionalLowercaseString, normalizeOptionalString, stripReasoningTagsFromText } from "openclaw/plugin-sdk/text-runtime";
15
15
  import { ensureConfiguredBindingRouteReady, resolveConfiguredBindingRoute, resolveRuntimeConversationBindingRoute } from "openclaw/plugin-sdk/conversation-runtime";
16
+ import { createChannelMessageReplyPipeline } from "openclaw/plugin-sdk/channel-message";
16
17
  import { createChannelPairingController, createChannelPairingController as createChannelPairingController$1 } from "openclaw/plugin-sdk/channel-pairing";
17
18
  import { resolveAgentOutboundIdentity } from "openclaw/plugin-sdk/outbound-runtime";
18
19
  import fs from "node:fs";
@@ -20,16 +21,17 @@ import os from "node:os";
20
21
  import path from "node:path";
21
22
  import { formatErrorMessage } from "openclaw/plugin-sdk/error-runtime";
22
23
  import * as Lark from "@larksuiteoapi/node-sdk";
23
- import { createChannelReplyPipeline } from "openclaw/plugin-sdk/channel-reply-pipeline";
24
24
  import { createPersistentDedupe } from "openclaw/plugin-sdk/persistent-dedupe";
25
25
  import { applyBasicWebhookRequestGuards } from "openclaw/plugin-sdk/webhook-ingress";
26
26
  import { fetchWithSsrFGuard } from "openclaw/plugin-sdk/ssrf-runtime";
27
27
  import { resolveSendableOutboundReplyParts, resolveTextChunksWithFallback, sendMediaWithLeadingCaption } from "openclaw/plugin-sdk/reply-payload";
28
+ import { safeEqualSecret } from "openclaw/plugin-sdk/security-runtime";
28
29
  import * as crypto$1 from "node:crypto";
29
30
  import crypto from "node:crypto";
31
+ import { resolveChannelConfigWrites } from "openclaw/plugin-sdk/channel-config-writes";
30
32
  import { DEFAULT_GROUP_HISTORY_LIMIT, buildPendingHistoryContextFromMap, clearHistoryEntriesIfEnabled, recordPendingHistoryEntryIfEnabled } from "openclaw/plugin-sdk/reply-history";
31
33
  import { resolveDefaultGroupPolicy, resolveOpenProviderRuntimeGroupPolicy, warnMissingProviderGroupPolicyFallbackOnce } from "openclaw/plugin-sdk/runtime-group-policy";
32
- import { resolveOpenDmAllowlistAccess, safeEqualSecret } from "openclaw/plugin-sdk/security-runtime";
34
+ import { formatReasoningMessage } from "openclaw/plugin-sdk/agent-runtime";
33
35
  import { logTypingFailure } from "openclaw/plugin-sdk/channel-feedback";
34
36
  import { formatChannelProgressDraftLineForEntry, isChannelProgressDraftWorkToolName } from "openclaw/plugin-sdk/channel-streaming";
35
37
  import * as http from "node:http";
@@ -550,7 +552,14 @@ function resolveFeishuMessageDedupeKey(event) {
550
552
  * This creates a unique agent instance with its own workspace for each DM user.
551
553
  */
552
554
  async function maybeCreateDynamicAgent(params) {
553
- const { cfg, runtime, senderOpenId, dynamicCfg, log } = params;
555
+ const { cfg, runtime, senderOpenId, dynamicCfg, configWritesAllowed, log } = params;
556
+ if (!configWritesAllowed) {
557
+ log(`feishu: config writes disabled, not creating agent for ${senderOpenId}`);
558
+ return {
559
+ created: false,
560
+ updatedCfg: cfg
561
+ };
562
+ }
554
563
  const existingBindings = cfg.bindings ?? [];
555
564
  if (existingBindings.some((b) => b.match?.channel === "feishu" && b.match?.peer?.kind === "direct" && b.match?.peer?.id === senderOpenId)) return {
556
565
  created: false,
@@ -639,17 +648,30 @@ function resolveUserPath(p) {
639
648
  return p;
640
649
  }
641
650
  //#endregion
651
+ //#region extensions/feishu/src/agent-config.ts
652
+ const DEFAULT_AGENT_ID = "main";
653
+ function normalizeAgentId(value) {
654
+ return (value ?? "").trim().toLowerCase() || DEFAULT_AGENT_ID;
655
+ }
656
+ function resolveFeishuConfigReasoningDefault(cfg, agentId) {
657
+ const id = normalizeAgentId(agentId);
658
+ return cfg.agents?.list?.find((entry) => normalizeAgentId(entry?.id) === id)?.reasoningDefault ?? cfg.agents?.defaults?.reasoningDefault ?? "off";
659
+ }
660
+ //#endregion
642
661
  //#region extensions/feishu/src/reasoning-preview.ts
643
662
  function resolveFeishuReasoningPreviewEnabled(params) {
644
- if (!params.sessionKey) return false;
663
+ const configDefault = resolveFeishuConfigReasoningDefault(params.cfg, params.agentId);
664
+ if (!params.sessionKey) return configDefault === "stream";
645
665
  try {
646
- return resolveSessionStoreEntry({
666
+ const level = resolveSessionStoreEntry({
647
667
  store: loadSessionStore(params.storePath, { skipCache: true }),
648
668
  sessionKey: params.sessionKey
649
- }).existing?.reasoningLevel === "stream";
669
+ }).existing?.reasoningLevel;
670
+ if (level === "on" || level === "stream" || level === "off") return level === "stream";
650
671
  } catch {
651
672
  return false;
652
673
  }
674
+ return configDefault === "stream";
653
675
  }
654
676
  //#endregion
655
677
  //#region extensions/feishu/src/streaming-card.ts
@@ -1179,7 +1201,7 @@ function resolveCardNote(agentId, identity, prefixCtx) {
1179
1201
  }
1180
1202
  function createFeishuReplyDispatcher(params) {
1181
1203
  const core = getFeishuRuntime();
1182
- const { cfg, agentId, chatId, replyToMessageId, skipReplyToInMessages, replyInThread, threadReply, rootId, mentionTargets, accountId, identity } = params;
1204
+ const { cfg, agentId, chatId, replyToMessageId, skipReplyToInMessages, replyInThread, threadReply, rootId, accountId, identity } = params;
1183
1205
  const sendReplyToMessageId = skipReplyToInMessages ? void 0 : replyToMessageId;
1184
1206
  const effectiveReplyInThread = threadReply === true ? true : replyInThread;
1185
1207
  const account = resolveFeishuRuntimeAccount({
@@ -1191,7 +1213,7 @@ function createFeishuReplyDispatcher(params) {
1191
1213
  agentId
1192
1214
  });
1193
1215
  let typingState = null;
1194
- const { typingCallbacks } = createChannelReplyPipeline({
1216
+ const { typingCallbacks } = createChannelMessageReplyPipeline({
1195
1217
  cfg,
1196
1218
  agentId,
1197
1219
  channel: "feishu",
@@ -1329,8 +1351,7 @@ function createFeishuReplyDispatcher(params) {
1329
1351
  await partialUpdateQueue;
1330
1352
  if (streaming?.isActive()) {
1331
1353
  statusLine = "";
1332
- let text = buildCombinedStreamText(reasoningText, streamText);
1333
- if (mentionTargets?.length) text = buildMentionedCardContent(mentionTargets, text);
1354
+ const text = buildCombinedStreamText(reasoningText, streamText);
1334
1355
  const finalNote = resolveCardNote(agentId, identity, prefixContext.prefixContext);
1335
1356
  await streaming.close(text, { note: finalNote });
1336
1357
  if (streamText) {
@@ -1386,14 +1407,13 @@ function createFeishuReplyDispatcher(params) {
1386
1407
  text: options.fallbackText,
1387
1408
  useCard: false,
1388
1409
  infoKind: "final",
1389
- sendChunk: async ({ chunk, isFirst }) => {
1410
+ sendChunk: async ({ chunk }) => {
1390
1411
  await sendMessageFeishu({
1391
1412
  cfg,
1392
1413
  to: chatId,
1393
1414
  text: chunk,
1394
1415
  replyToMessageId: sendReplyToMessageId,
1395
1416
  replyInThread: effectiveReplyInThread,
1396
- mentions: isFirst ? mentionTargets : void 0,
1397
1417
  accountId
1398
1418
  });
1399
1419
  }
@@ -1407,14 +1427,13 @@ function createFeishuReplyDispatcher(params) {
1407
1427
  text: fallbackText,
1408
1428
  useCard: false,
1409
1429
  infoKind: "final",
1410
- sendChunk: async ({ chunk, isFirst }) => {
1430
+ sendChunk: async ({ chunk }) => {
1411
1431
  await sendMessageFeishu({
1412
1432
  cfg,
1413
1433
  to: chatId,
1414
1434
  text: chunk,
1415
1435
  replyToMessageId: sendReplyToMessageId,
1416
1436
  replyInThread: effectiveReplyInThread,
1417
- mentions: isFirst ? mentionTargets : void 0,
1418
1437
  accountId
1419
1438
  });
1420
1439
  }
@@ -1434,7 +1453,11 @@ function createFeishuReplyDispatcher(params) {
1434
1453
  await typingCallbacks?.onReplyStart?.();
1435
1454
  },
1436
1455
  deliver: async (payload, info) => {
1437
- const reply = resolveSendableOutboundReplyParts(payload);
1456
+ const payloadText = payload.isReasoning && payload.text ? formatReasoningMessage(payload.text) : payload.text;
1457
+ const reply = resolveSendableOutboundReplyParts({
1458
+ ...payload,
1459
+ text: payloadText
1460
+ });
1438
1461
  const text = reply.text;
1439
1462
  const hasText = reply.hasText;
1440
1463
  const hasMedia = reply.hasMedia;
@@ -1478,14 +1501,13 @@ function createFeishuReplyDispatcher(params) {
1478
1501
  text,
1479
1502
  useCard: true,
1480
1503
  infoKind: info?.kind,
1481
- sendChunk: async ({ chunk, isFirst }) => {
1504
+ sendChunk: async ({ chunk }) => {
1482
1505
  await sendStructuredCardFeishu({
1483
1506
  cfg,
1484
1507
  to: chatId,
1485
1508
  text: chunk,
1486
1509
  replyToMessageId: sendReplyToMessageId,
1487
1510
  replyInThread: effectiveReplyInThread,
1488
- mentions: isFirst ? mentionTargets : void 0,
1489
1511
  accountId,
1490
1512
  header: cardHeader,
1491
1513
  note: cardNote
@@ -1496,14 +1518,13 @@ function createFeishuReplyDispatcher(params) {
1496
1518
  text,
1497
1519
  useCard: false,
1498
1520
  infoKind: info?.kind,
1499
- sendChunk: async ({ chunk, isFirst }) => {
1521
+ sendChunk: async ({ chunk }) => {
1500
1522
  await sendMessageFeishu({
1501
1523
  cfg,
1502
1524
  to: chatId,
1503
1525
  text: chunk,
1504
1526
  replyToMessageId: sendReplyToMessageId,
1505
1527
  replyInThread: effectiveReplyInThread,
1506
- mentions: isFirst ? mentionTargets : void 0,
1507
1528
  accountId
1508
1529
  });
1509
1530
  }
@@ -1547,7 +1568,7 @@ function createFeishuReplyDispatcher(params) {
1547
1568
  onReasoningStream: reasoningPreviewEnabled ? (payload) => {
1548
1569
  if (!payload.text) return;
1549
1570
  startStreaming();
1550
- queueReasoningUpdate(payload.text);
1571
+ queueReasoningUpdate(formatReasoningMessage(payload.text));
1551
1572
  } : void 0,
1552
1573
  onReasoningEnd: reasoningPreviewEnabled ? () => {} : void 0,
1553
1574
  onToolStart: streamingEnabled ? (payload) => {
@@ -1696,6 +1717,14 @@ function parseFeishuMessageEvent(event, botOpenId, _botName) {
1696
1717
  }
1697
1718
  return ctx;
1698
1719
  }
1720
+ const MAX_MENTION_CONTEXT_NAME_LENGTH = 80;
1721
+ function formatMentionNameForAgentContext(name) {
1722
+ const normalized = Array.from(name, (char) => {
1723
+ return char.charCodeAt(0) < 32 || char === "[" || char === "]" ? " " : char;
1724
+ }).join("").replace(/\s+/g, " ").trim();
1725
+ const bounded = normalized.length > MAX_MENTION_CONTEXT_NAME_LENGTH ? `${normalized.slice(0, MAX_MENTION_CONTEXT_NAME_LENGTH - 3)}...` : normalized;
1726
+ return JSON.stringify(bounded || "unknown");
1727
+ }
1699
1728
  function buildFeishuAgentBody(params) {
1700
1729
  const { ctx, quotedContent, permissionErrorForAgent, botOpenId } = params;
1701
1730
  let messageBody = ctx.content;
@@ -1707,8 +1736,8 @@ function buildFeishuAgentBody(params) {
1707
1736
  if (botIdHint) messageBody += `\n[System: If user_id is "${botIdHint}", that mention refers to you.]`;
1708
1737
  }
1709
1738
  if (ctx.mentionTargets && ctx.mentionTargets.length > 0) {
1710
- const targetNames = ctx.mentionTargets.map((t) => t.name).join(", ");
1711
- messageBody += `\n\n[System: Your reply will automatically @mention: ${targetNames}. Do not write @xxx yourself.]`;
1739
+ const targetNames = ctx.mentionTargets.map((t) => formatMentionNameForAgentContext(t.name)).join(", ");
1740
+ messageBody += `\n\n[System: Feishu users mentioned in the incoming message, for context only: ${targetNames}. Do not notify or mention these users solely because they are listed here.]`;
1712
1741
  }
1713
1742
  messageBody = `[message_id: ${ctx.messageId}]\n${messageBody}`;
1714
1743
  if (permissionErrorForAgent) {
@@ -1717,42 +1746,37 @@ function buildFeishuAgentBody(params) {
1717
1746
  }
1718
1747
  return messageBody;
1719
1748
  }
1720
- function isFetchedGroupContextSenderAllowed(params) {
1721
- if (!params.isGroup || params.allowFrom.length === 0) return true;
1722
- if (params.senderType === "app") return true;
1749
+ async function shouldIncludeFetchedGroupContextMessage(params) {
1750
+ let senderAllowed = !params.isGroup || params.allowFrom.length === 0 || params.senderType === "app";
1723
1751
  const senderId = params.senderId?.trim();
1724
- return !!senderId && isFeishuGroupAllowed({
1725
- groupPolicy: "allowlist",
1726
- allowFrom: params.allowFrom,
1727
- senderId,
1728
- senderName: void 0
1729
- });
1730
- }
1731
- function shouldIncludeFetchedGroupContextMessage(params) {
1732
- const senderAllowed = isFetchedGroupContextSenderAllowed({
1733
- isGroup: params.isGroup,
1752
+ if (!senderAllowed && senderId) senderAllowed = (await resolveFeishuGroupSenderActivationIngressAccess({
1753
+ cfg: params.cfg,
1754
+ accountId: params.accountId,
1755
+ chatId: params.chatId,
1734
1756
  allowFrom: params.allowFrom,
1735
- senderId: params.senderId,
1736
- senderType: params.senderType
1737
- });
1757
+ senderOpenId: senderId,
1758
+ senderUserId: senderId,
1759
+ requireMention: false,
1760
+ mentionedBot: true
1761
+ })).senderAccess.decision === "allow";
1738
1762
  return evaluateSupplementalContextVisibility({
1739
1763
  mode: params.mode,
1740
1764
  kind: params.kind,
1741
1765
  senderAllowed
1742
1766
  }).include;
1743
1767
  }
1744
- function filterFetchedGroupContextMessages(messages, params) {
1745
- return filterSupplementalContextItems({
1746
- items: messages,
1768
+ async function filterFetchedGroupContextMessages(messages, params) {
1769
+ return (await Promise.all(messages.map(async (message) => await shouldIncludeFetchedGroupContextMessage({
1770
+ cfg: params.cfg,
1771
+ accountId: params.accountId,
1772
+ chatId: params.chatId,
1773
+ isGroup: params.isGroup,
1774
+ allowFrom: params.allowFrom,
1747
1775
  mode: params.mode,
1748
1776
  kind: params.kind,
1749
- isSenderAllowed: (message) => isFetchedGroupContextSenderAllowed({
1750
- isGroup: params.isGroup,
1751
- allowFrom: params.allowFrom,
1752
- senderId: message.senderId,
1753
- senderType: message.senderType
1754
- })
1755
- }).items;
1777
+ senderId: message.senderId,
1778
+ senderType: message.senderType
1779
+ }) ? message : void 0))).filter((message) => message !== void 0);
1756
1780
  }
1757
1781
  async function handleFeishuMessage(params) {
1758
1782
  const { cfg, event, botOpenId, botName, runtime, chatHistories, accountId, processingClaimHeld = false } = params;
@@ -1873,9 +1897,8 @@ async function handleFeishuMessage(params) {
1873
1897
  const groupHistoryKey = isGroup ? groupSession?.peerId ?? ctx.chatId : void 0;
1874
1898
  const dmPolicy = feishuCfg?.dmPolicy ?? "pairing";
1875
1899
  const configAllowFrom = feishuCfg?.allowFrom ?? [];
1876
- const useAccessGroups = cfg.commands?.useAccessGroups !== false;
1877
1900
  const rawBroadcastAgents = isGroup ? resolveBroadcastAgents(cfg, ctx.chatId) : null;
1878
- const broadcastAgents = rawBroadcastAgents ? [...new Set(rawBroadcastAgents.map((id) => normalizeAgentId(id)))] : null;
1901
+ const broadcastAgents = rawBroadcastAgents ? [...new Set(rawBroadcastAgents.map((id) => normalizeAgentId$1(id)))] : null;
1879
1902
  const messageCreateTimeMs = event.message.create_time ? Number.parseInt(event.message.create_time, 10) : Date.now();
1880
1903
  let requireMention = false;
1881
1904
  if (isGroup) {
@@ -1900,27 +1923,17 @@ async function handleFeishuMessage(params) {
1900
1923
  cfg: feishuCfg,
1901
1924
  groupId: ctx.chatId
1902
1925
  });
1903
- if (!(groupPolicy !== "disabled" && (groupExplicitlyConfigured || isFeishuGroupAllowed({
1926
+ if ((await resolveFeishuGroupConversationIngressAccess({
1927
+ cfg,
1928
+ accountId: account.accountId,
1929
+ chatId: ctx.chatId,
1904
1930
  groupPolicy,
1905
- allowFrom: groupAllowFrom,
1906
- senderId: ctx.chatId,
1907
- senderName: void 0
1908
- })))) {
1931
+ groupAllowFrom,
1932
+ groupExplicitlyConfigured
1933
+ })).ingress.admission !== "dispatch") {
1909
1934
  log(`feishu[${account.accountId}]: group ${ctx.chatId} not in groupAllowFrom (groupPolicy=${groupPolicy})`);
1910
1935
  return;
1911
1936
  }
1912
- if (effectiveGroupSenderAllowFrom.length > 0) {
1913
- if (!isFeishuGroupAllowed({
1914
- groupPolicy: "allowlist",
1915
- allowFrom: effectiveGroupSenderAllowFrom,
1916
- senderId: ctx.senderOpenId,
1917
- senderIds: [senderUserId],
1918
- senderName: ctx.senderName
1919
- })) {
1920
- log(`feishu: sender ${ctx.senderOpenId} not in group ${ctx.chatId} sender allowlist`);
1921
- return;
1922
- }
1923
- }
1924
1937
  ({requireMention} = resolveFeishuReplyPolicy({
1925
1938
  isDirectMessage: false,
1926
1939
  cfg,
@@ -1928,7 +1941,21 @@ async function handleFeishuMessage(params) {
1928
1941
  groupId: ctx.chatId,
1929
1942
  groupPolicy
1930
1943
  }));
1931
- if (requireMention && !ctx.mentionedBot) {
1944
+ const groupSenderActivationIngress = await resolveFeishuGroupSenderActivationIngressAccess({
1945
+ cfg,
1946
+ accountId: account.accountId,
1947
+ chatId: ctx.chatId,
1948
+ allowFrom: effectiveGroupSenderAllowFrom,
1949
+ senderOpenId: ctx.senderOpenId,
1950
+ senderUserId,
1951
+ requireMention,
1952
+ mentionedBot: ctx.mentionedBot
1953
+ });
1954
+ if (groupSenderActivationIngress.senderAccess.decision !== "allow") {
1955
+ log(`feishu: sender ${ctx.senderOpenId} not in group ${ctx.chatId} sender allowlist`);
1956
+ return;
1957
+ }
1958
+ if (groupSenderActivationIngress.ingress.admission !== "dispatch") {
1932
1959
  log(`feishu[${account.accountId}]: message in group ${ctx.chatId} did not mention bot`);
1933
1960
  if (!broadcastAgents && chatHistories && groupHistoryKey) recordPendingHistoryEntryIfEnabled({
1934
1961
  historyMap: chatHistories,
@@ -1953,25 +1980,20 @@ async function handleFeishuMessage(params) {
1953
1980
  });
1954
1981
  const commandProbeBody = isGroup ? normalizeFeishuCommandProbeBody(ctx.content) : ctx.content;
1955
1982
  const shouldComputeCommandAuthorized = core.channel.commands.shouldComputeCommandAuthorized(commandProbeBody, cfg);
1956
- const storeAllowFrom = !isGroup && dmPolicy !== "allowlist" && dmPolicy !== "open" ? await pairing.readAllowFromStore().catch(() => []) : [];
1957
- const effectiveDmAllowFrom = [...configAllowFrom, ...storeAllowFrom];
1958
- const dmAllowed = resolveFeishuAllowlistMatch({
1959
- allowFrom: effectiveDmAllowFrom,
1960
- senderId: ctx.senderOpenId,
1961
- senderIds: [senderUserId],
1962
- senderName: ctx.senderName
1963
- }).allowed;
1964
- const dmAccessAllowed = dmPolicy === "open" ? resolveOpenDmAllowlistAccess({
1965
- effectiveAllowFrom: effectiveDmAllowFrom,
1966
- isSenderAllowed: (allowFrom) => resolveFeishuAllowlistMatch({
1967
- allowFrom,
1968
- senderId: ctx.senderOpenId,
1969
- senderIds: [senderUserId],
1970
- senderName: ctx.senderName
1971
- }).allowed
1972
- }).decision === "allow" : dmAllowed;
1973
- if (isDirect && !dmAccessAllowed) {
1974
- if (dmPolicy === "pairing") await pairing.issueChallenge({
1983
+ const dmIngress = isDirect ? await resolveFeishuDmIngressAccess({
1984
+ cfg,
1985
+ accountId: account.accountId,
1986
+ dmPolicy,
1987
+ allowFrom: configAllowFrom,
1988
+ readAllowFromStore: pairing.readAllowFromStore,
1989
+ senderOpenId: ctx.senderOpenId,
1990
+ senderUserId,
1991
+ conversationId: ctx.senderOpenId,
1992
+ mayPair: true,
1993
+ ...shouldComputeCommandAuthorized ? { command: { hasControlCommand: true } } : {}
1994
+ }) : null;
1995
+ if (isDirect && dmIngress?.ingress.admission !== "dispatch") {
1996
+ if (dmIngress?.ingress.admission === "pairing-required") await pairing.issueChallenge({
1975
1997
  senderId: ctx.senderOpenId,
1976
1998
  senderIdLine: `Your Feishu user id: ${ctx.senderOpenId}`,
1977
1999
  meta: { name: ctx.senderName },
@@ -1993,13 +2015,7 @@ async function handleFeishuMessage(params) {
1993
2015
  else log(`feishu[${account.accountId}]: blocked unauthorized sender ${ctx.senderOpenId} (dmPolicy=${dmPolicy})`);
1994
2016
  return;
1995
2017
  }
1996
- const commandAllowFrom = isGroup ? groupConfig?.allowFrom ?? configAllowFrom : effectiveDmAllowFrom;
1997
- const senderAllowedForCommands = resolveFeishuAllowlistMatch({
1998
- allowFrom: commandAllowFrom,
1999
- senderId: ctx.senderOpenId,
2000
- senderIds: [senderUserId],
2001
- senderName: ctx.senderName
2002
- }).allowed;
2018
+ const commandAllowFrom = isGroup ? groupConfig?.allowFrom ?? configAllowFrom : dmIngress?.senderAccess.effectiveAllowFrom ?? configAllowFrom;
2003
2019
  const feishuFrom = `feishu:${ctx.senderOpenId}`;
2004
2020
  const feishuTo = isGroup ? `chat:${ctx.chatId}` : `user:${ctx.senderOpenId}`;
2005
2021
  const peerId = isGroup ? groupSession?.peerId ?? ctx.chatId : ctx.senderOpenId;
@@ -2026,6 +2042,11 @@ async function handleFeishuMessage(params) {
2026
2042
  runtime: getFeishuRuntime(),
2027
2043
  senderOpenId: ctx.senderOpenId,
2028
2044
  dynamicCfg,
2045
+ configWritesAllowed: resolveChannelConfigWrites({
2046
+ cfg,
2047
+ channelId: "feishu",
2048
+ accountId: account.accountId
2049
+ }),
2029
2050
  log: (msg) => log(msg)
2030
2051
  });
2031
2052
  if (result.created) {
@@ -2131,13 +2152,28 @@ async function handleFeishuMessage(params) {
2131
2152
  content: audioTranscript
2132
2153
  };
2133
2154
  const effectiveCommandProbeBody = audioTranscript === void 0 ? commandProbeBody : isGroup ? normalizeFeishuCommandProbeBody(audioTranscript) : audioTranscript;
2134
- const commandAuthorized = (audioTranscript === void 0 ? shouldComputeCommandAuthorized : core.channel.commands.shouldComputeCommandAuthorized(effectiveCommandProbeBody, cfg)) ? core.channel.commands.resolveCommandAuthorizedFromAuthorizers({
2135
- useAccessGroups,
2136
- authorizers: [{
2137
- configured: commandAllowFrom.length > 0,
2138
- allowed: senderAllowedForCommands
2139
- }]
2140
- }) : void 0;
2155
+ const commandAuthorized = (audioTranscript === void 0 ? shouldComputeCommandAuthorized : core.channel.commands.shouldComputeCommandAuthorized(effectiveCommandProbeBody, cfg)) ? isDirect && audioTranscript === void 0 && dmIngress ? dmIngress.commandAccess.authorized : isGroup ? (await resolveFeishuGroupSenderActivationIngressAccess({
2156
+ cfg,
2157
+ accountId: account.accountId,
2158
+ chatId: ctx.chatId,
2159
+ allowFrom: commandAllowFrom,
2160
+ senderOpenId: ctx.senderOpenId,
2161
+ senderUserId,
2162
+ requireMention: false,
2163
+ mentionedBot: true,
2164
+ command: { hasControlCommand: true }
2165
+ })).commandAccess.authorized : (await resolveFeishuDmIngressAccess({
2166
+ cfg,
2167
+ accountId: account.accountId,
2168
+ dmPolicy,
2169
+ allowFrom: configAllowFrom,
2170
+ readAllowFromStore: pairing.readAllowFromStore,
2171
+ senderOpenId: ctx.senderOpenId,
2172
+ senderUserId,
2173
+ conversationId: ctx.senderOpenId,
2174
+ mayPair: false,
2175
+ command: { hasControlCommand: true }
2176
+ })).commandAccess.authorized : void 0;
2141
2177
  let quotedMessageInfo = null;
2142
2178
  let quotedContent;
2143
2179
  if (ctx.parentId) try {
@@ -2146,7 +2182,10 @@ async function handleFeishuMessage(params) {
2146
2182
  messageId: ctx.parentId,
2147
2183
  accountId: account.accountId
2148
2184
  });
2149
- if (quotedMessageInfo && shouldIncludeFetchedGroupContextMessage({
2185
+ if (quotedMessageInfo && await shouldIncludeFetchedGroupContextMessage({
2186
+ cfg,
2187
+ accountId: account.accountId,
2188
+ chatId: ctx.chatId,
2150
2189
  isGroup,
2151
2190
  allowFrom: effectiveGroupSenderAllowFrom,
2152
2191
  mode: contextVisibilityMode,
@@ -2216,7 +2255,10 @@ async function handleFeishuMessage(params) {
2216
2255
  rootMessageInfo = null;
2217
2256
  }
2218
2257
  rootMessageThreadId = rootMessageInfo?.threadId;
2219
- if (rootMessageInfo && !shouldIncludeFetchedGroupContextMessage({
2258
+ if (rootMessageInfo && !await shouldIncludeFetchedGroupContextMessage({
2259
+ cfg,
2260
+ accountId: account.accountId,
2261
+ chatId: ctx.chatId,
2220
2262
  isGroup,
2221
2263
  allowFrom: effectiveGroupSenderAllowFrom,
2222
2264
  mode: contextVisibilityMode,
@@ -2276,7 +2318,10 @@ async function handleFeishuMessage(params) {
2276
2318
  });
2277
2319
  const senderScoped = groupSession?.groupSessionScope === "group_topic_sender";
2278
2320
  const senderIds = new Set([ctx.senderOpenId, senderUserId].map((id) => id?.trim()).filter((id) => id !== void 0 && id.length > 0));
2279
- const allowlistedMessages = filterFetchedGroupContextMessages(threadMessages, {
2321
+ const allowlistedMessages = await filterFetchedGroupContextMessages(threadMessages, {
2322
+ cfg,
2323
+ accountId: account.accountId,
2324
+ chatId: ctx.chatId,
2280
2325
  isGroup,
2281
2326
  allowFrom: effectiveGroupSenderAllowFrom,
2282
2327
  mode: contextVisibilityMode,
@@ -2353,12 +2398,12 @@ async function handleFeishuMessage(params) {
2353
2398
  return;
2354
2399
  }
2355
2400
  const strategy = cfg.broadcast?.strategy === "sequential" ? "sequential" : "parallel";
2356
- const activeAgentId = ctx.mentionedBot || !requireMention ? normalizeAgentId(route.agentId) : null;
2357
- const agentIds = (cfg.agents?.list ?? []).map((a) => normalizeAgentId(a.id));
2401
+ const activeAgentId = ctx.mentionedBot || !requireMention ? normalizeAgentId$1(route.agentId) : null;
2402
+ const agentIds = (cfg.agents?.list ?? []).map((a) => normalizeAgentId$1(a.id));
2358
2403
  const hasKnownAgents = agentIds.length > 0;
2359
2404
  log(`feishu[${account.accountId}]: broadcasting to ${broadcastAgents.length} agents (strategy=${strategy}, active=${activeAgentId ?? "none"})`);
2360
2405
  const dispatchForAgent = async (agentId) => {
2361
- if (hasKnownAgents && !agentIds.includes(normalizeAgentId(agentId))) {
2406
+ if (hasKnownAgents && !agentIds.includes(normalizeAgentId$1(agentId))) {
2362
2407
  log(`feishu[${account.accountId}]: broadcast agent ${agentId} not found in agents.list; skipping`);
2363
2408
  return;
2364
2409
  }
@@ -2368,6 +2413,8 @@ async function handleFeishuMessage(params) {
2368
2413
  log(`feishu[${account.accountId}]: failed to record broadcast inbound session ${agentSessionKey}: ${String(err)}`);
2369
2414
  } };
2370
2415
  const allowReasoningPreview = resolveFeishuReasoningPreviewEnabled({
2416
+ cfg,
2417
+ agentId,
2371
2418
  storePath: agentStorePath,
2372
2419
  sessionKey: agentSessionKey
2373
2420
  });
@@ -2385,7 +2432,6 @@ async function handleFeishuMessage(params) {
2385
2432
  replyInThread,
2386
2433
  rootId: ctx.rootId,
2387
2434
  threadReply,
2388
- mentionTargets: ctx.mentionTargets,
2389
2435
  accountId: account.accountId,
2390
2436
  identity,
2391
2437
  messageCreateTimeMs
@@ -2503,6 +2549,8 @@ async function handleFeishuMessage(params) {
2503
2549
  const identity = resolveAgentOutboundIdentity(cfg, route.agentId);
2504
2550
  const storePath = core.channel.session.resolveStorePath(cfg.session?.store, { agentId: route.agentId });
2505
2551
  const allowReasoningPreview = resolveFeishuReasoningPreviewEnabled({
2552
+ cfg,
2553
+ agentId: route.agentId,
2506
2554
  storePath,
2507
2555
  sessionKey: route.sessionKey
2508
2556
  });
@@ -2517,7 +2565,6 @@ async function handleFeishuMessage(params) {
2517
2565
  replyInThread,
2518
2566
  rootId: ctx.rootId,
2519
2567
  threadReply,
2520
- mentionTargets: ctx.mentionTargets,
2521
2568
  accountId: account.accountId,
2522
2569
  identity,
2523
2570
  messageCreateTimeMs
@@ -3941,22 +3988,19 @@ async function handleFeishuCommentEvent(params) {
3941
3988
  channel: "feishu",
3942
3989
  accountId: account.accountId
3943
3990
  });
3944
- const storeAllowFrom = dmPolicy !== "allowlist" && dmPolicy !== "open" ? await pairing.readAllowFromStore().catch(() => []) : [];
3945
- const effectiveDmAllowFrom = [...configAllowFrom, ...storeAllowFrom];
3946
- const senderAllowed = resolveFeishuAllowlistMatch({
3947
- allowFrom: effectiveDmAllowFrom,
3948
- senderId: turn.senderId,
3949
- senderIds: [turn.senderUserId]
3950
- }).allowed;
3951
- if (!(dmPolicy === "open" ? resolveOpenDmAllowlistAccess({
3952
- effectiveAllowFrom: effectiveDmAllowFrom,
3953
- isSenderAllowed: (allowFrom) => resolveFeishuAllowlistMatch({
3954
- allowFrom,
3955
- senderId: turn.senderId,
3956
- senderIds: [turn.senderUserId]
3957
- }).allowed
3958
- }).decision === "allow" : senderAllowed)) {
3959
- if (dmPolicy === "pairing") {
3991
+ const dmIngress = await resolveFeishuDmIngressAccess({
3992
+ cfg: params.cfg,
3993
+ accountId: account.accountId,
3994
+ dmPolicy,
3995
+ allowFrom: configAllowFrom,
3996
+ readAllowFromStore: pairing.readAllowFromStore,
3997
+ senderOpenId: turn.senderId,
3998
+ senderUserId: turn.senderUserId,
3999
+ conversationId: turn.senderId,
4000
+ mayPair: true
4001
+ });
4002
+ if (dmIngress.ingress.admission !== "dispatch") {
4003
+ if (dmIngress.ingress.admission === "pairing-required") {
3960
4004
  const client = createFeishuClient(account);
3961
4005
  await pairing.issueChallenge({
3962
4006
  senderId: turn.senderId,
@@ -3999,6 +4043,11 @@ async function handleFeishuCommentEvent(params) {
3999
4043
  runtime: core,
4000
4044
  senderOpenId: turn.senderId,
4001
4045
  dynamicCfg,
4046
+ configWritesAllowed: resolveChannelConfigWrites({
4047
+ cfg: params.cfg,
4048
+ channelId: "feishu",
4049
+ accountId: account.accountId
4050
+ }),
4002
4051
  log: (message) => log(message)
4003
4052
  });
4004
4053
  if (dynamicResult.created) {