@openclaw/feishu 2026.6.6 → 2026.6.8-beta.2

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.
Files changed (55) hide show
  1. package/dist/api.js +1 -1
  2. package/dist/{channel-BYCCZN1h.js → channel-DwygSth-.js} +2 -2
  3. package/dist/channel-plugin-api.js +1 -1
  4. package/dist/{channel.runtime-DACJbxhN.js → channel.runtime-CrqKcXaU.js} +1 -1
  5. package/dist/{monitor-D6ePNz_R.js → monitor-_1eNsUVp.js} +2 -2
  6. package/dist/{monitor.account-Cl-QnhPV.js → monitor.account-BhoLA1Y0.js} +319 -198
  7. package/dist/{monitor.state-r4OLFBfg.js → monitor.state-QV66eUNA.js} +73 -1
  8. package/dist/setup-api.js +1 -1
  9. package/node_modules/form-data/CHANGELOG.md +26 -1
  10. package/node_modules/form-data/README.md +4 -4
  11. package/node_modules/form-data/lib/form_data.js +7 -2
  12. package/node_modules/form-data/package.json +11 -10
  13. package/node_modules/protobufjs/README.md +45 -31
  14. package/node_modules/protobufjs/dist/light/protobuf.js +98 -66
  15. package/node_modules/protobufjs/dist/light/protobuf.js.map +1 -1
  16. package/node_modules/protobufjs/dist/light/protobuf.min.js +3 -3
  17. package/node_modules/protobufjs/dist/light/protobuf.min.js.map +1 -1
  18. package/node_modules/protobufjs/dist/minimal/protobuf.js +30 -10
  19. package/node_modules/protobufjs/dist/minimal/protobuf.js.map +1 -1
  20. package/node_modules/protobufjs/dist/minimal/protobuf.min.js +3 -3
  21. package/node_modules/protobufjs/dist/minimal/protobuf.min.js.map +1 -1
  22. package/node_modules/protobufjs/dist/protobuf.js +99 -67
  23. package/node_modules/protobufjs/dist/protobuf.js.map +1 -1
  24. package/node_modules/protobufjs/dist/protobuf.min.js +3 -3
  25. package/node_modules/protobufjs/dist/protobuf.min.js.map +1 -1
  26. package/node_modules/protobufjs/index.d.ts +20 -8
  27. package/node_modules/protobufjs/package.json +1 -1
  28. package/node_modules/protobufjs/src/converter.js +9 -6
  29. package/node_modules/protobufjs/src/decoder.js +3 -1
  30. package/node_modules/protobufjs/src/encoder.js +8 -5
  31. package/node_modules/protobufjs/src/enum.js +2 -2
  32. package/node_modules/protobufjs/src/field.js +2 -5
  33. package/node_modules/protobufjs/src/index-light.js +1 -1
  34. package/node_modules/protobufjs/src/message.js +1 -1
  35. package/node_modules/protobufjs/src/object.js +6 -6
  36. package/node_modules/protobufjs/src/parse.js +1 -1
  37. package/node_modules/protobufjs/src/root.js +14 -8
  38. package/node_modules/protobufjs/src/type.js +8 -8
  39. package/node_modules/protobufjs/src/typescript.js +6 -0
  40. package/node_modules/protobufjs/src/util/eventemitter.js +4 -2
  41. package/node_modules/protobufjs/src/util/minimal.js +24 -6
  42. package/node_modules/protobufjs/src/util/patterns.js +0 -1
  43. package/node_modules/protobufjs/src/util.js +2 -3
  44. package/node_modules/protobufjs/src/wrappers.js +11 -8
  45. package/npm-shrinkwrap.json +10 -18
  46. package/package.json +5 -5
  47. package/node_modules/form-data/README.md.bak +0 -350
  48. package/node_modules/has-own/.travis.yml +0 -4
  49. package/node_modules/has-own/History.md +0 -5
  50. package/node_modules/has-own/LICENSE +0 -22
  51. package/node_modules/has-own/Makefile +0 -5
  52. package/node_modules/has-own/README.md +0 -19
  53. package/node_modules/has-own/index.js +0 -8
  54. package/node_modules/has-own/package.json +0 -19
  55. package/node_modules/has-own/test/index.js +0 -36
@@ -1,6 +1,6 @@
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, p as parseCommentContentElements, s as resolveFeishuRuntimeAccount } from "./accounts-Cfzht2Hc.js";
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-Cfzht2Hc.js";
2
2
  import { i as resolveReceiveIdType } from "./targets-BUjQ1TcA.js";
3
- import { c as normalizeFeishuAllowEntry, d as resolveFeishuGroupConversationIngressAccess, f as resolveFeishuGroupSenderActivationIngressAccess, l as resolveFeishuDmIngressAccess, p as resolveFeishuReplyPolicy, s as hasExplicitFeishuGroupConfig, u as resolveFeishuGroupConfig } from "./channel-BYCCZN1h.js";
3
+ import { c as normalizeFeishuAllowEntry, d as resolveFeishuGroupConversationIngressAccess, f as resolveFeishuGroupSenderActivationIngressAccess, l as resolveFeishuDmIngressAccess, p as resolveFeishuReplyPolicy, s as hasExplicitFeishuGroupConfig, u as resolveFeishuGroupConfig } from "./channel-DwygSth-.js";
4
4
  import { c as decodeFeishuCardAction, o as buildFeishuCardActionTextFallback, s as createFeishuCardInteractionEnvelope } from "./send-result-DSsIa4-p.js";
5
5
  import { t as buildFeishuConversationId } from "./conversation-id-DuL575sn.js";
6
6
  import { t as getFeishuRuntime } from "./runtime-C5JxBWZp.js";
@@ -10,7 +10,7 @@ import { t as createFeishuThreadBindingManager } from "./thread-bindings-V0bwk0A
10
10
  import { createReplyPrefixContext, evaluateSupplementalContextVisibility, loadSessionStore, normalizeAgentId as normalizeAgentId$2, resolveChannelContextVisibilityMode, resolveSessionStoreEntry } from "./runtime-api.js";
11
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 saveMessageResourceFeishu, n as getMessageFeishu, p as isFeishuGroupChatType, r as listFeishuThreadMessages, s as sendMessageFeishu, u as extractMentionTargets } from "./send-B3kteMF8.js";
12
12
  import { i as waitForAbortableDelay, r as raceWithTimeoutAndAbort } from "./probe-BjKRV7em.js";
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-r4OLFBfg.js";
13
+ import { a as clearFeishuBotIdentityState, c as httpServers, d as wsClients, f as fetchBotIdentityForMonitor, i as botOpenIds, l as recordWebhookStatus, n as FEISHU_WEBHOOK_MAX_BODY_BYTES, o as closeTrackedFeishuHttpServer, r as botNames, s as feishuWebhookRateLimiter, t as FEISHU_WEBHOOK_BODY_TIMEOUT_MS, u as setFeishuBotIdentityState } from "./monitor.state-QV66eUNA.js";
14
14
  import { createChannelMessageReplyPipeline, formatChannelProgressDraftLineForEntry, isChannelProgressDraftWorkToolName, resolveAgentOutboundIdentity } from "openclaw/plugin-sdk/channel-outbound";
15
15
  import { createChannelPairingController, createChannelPairingController as createChannelPairingController$1 } from "openclaw/plugin-sdk/channel-pairing";
16
16
  import { ensureConfiguredBindingRouteReady, resolveConfiguredBindingRoute, resolveRuntimeConversationBindingRoute } from "openclaw/plugin-sdk/conversation-runtime";
@@ -20,7 +20,7 @@ import { stripReasoningTagsFromText } from "openclaw/plugin-sdk/text-chunking";
20
20
  import fs from "node:fs";
21
21
  import os from "node:os";
22
22
  import path from "node:path";
23
- import { resolveInboundLastRouteSessionKey } from "openclaw/plugin-sdk/routing";
23
+ import { normalizeAccountId, resolveAgentRoute, resolveInboundLastRouteSessionKey } from "openclaw/plugin-sdk/routing";
24
24
  import { formatErrorMessage } from "openclaw/plugin-sdk/error-runtime";
25
25
  import * as Lark from "@larksuiteoapi/node-sdk";
26
26
  import * as crypto$1 from "node:crypto";
@@ -29,10 +29,10 @@ import { applyBasicWebhookRequestGuards, resolveRequestClientIp } from "openclaw
29
29
  import { fetchWithSsrFGuard } from "openclaw/plugin-sdk/ssrf-runtime";
30
30
  import { resolveSendableOutboundReplyParts, resolveTextChunksWithFallback, sendMediaWithLeadingCaption } from "openclaw/plugin-sdk/reply-payload";
31
31
  import { resolvePinnedMainDmOwnerFromAllowlist, safeEqualSecret } from "openclaw/plugin-sdk/security-runtime";
32
- import { resolveChannelConfigWrites } from "openclaw/plugin-sdk/channel-config-writes";
33
32
  import { buildChannelInboundEventContext, toInboundMediaFacts } from "openclaw/plugin-sdk/channel-inbound";
34
33
  import { DEFAULT_GROUP_HISTORY_LIMIT, createChannelHistoryWindow } from "openclaw/plugin-sdk/reply-history";
35
34
  import { resolveDefaultGroupPolicy, resolveOpenProviderRuntimeGroupPolicy, warnMissingProviderGroupPolicyFallbackOnce } from "openclaw/plugin-sdk/runtime-group-policy";
35
+ import { resolveChannelConfigWrites } from "openclaw/plugin-sdk/channel-config-writes";
36
36
  import { formatReasoningMessage } from "openclaw/plugin-sdk/agent-runtime";
37
37
  import { logTypingFailure } from "openclaw/plugin-sdk/channel-feedback";
38
38
  import * as http from "node:http";
@@ -654,97 +654,150 @@ function resolveFeishuMessageDedupeKey(event) {
654
654
  }
655
655
  //#endregion
656
656
  //#region extensions/feishu/src/dynamic-agent.ts
657
+ var DynamicAgentMutationSkipped = class extends Error {
658
+ constructor(cfg) {
659
+ super("dynamic agent mutation skipped");
660
+ this.cfg = cfg;
661
+ }
662
+ };
663
+ function hasDefaultDirectRoute(cfg, accountId, senderOpenId) {
664
+ return resolveAgentRoute({
665
+ cfg,
666
+ channel: "feishu",
667
+ accountId,
668
+ peer: {
669
+ kind: "direct",
670
+ id: senderOpenId
671
+ }
672
+ }).matchedBy === "default";
673
+ }
674
+ function resolveDynamicAgentConfig(cfg, accountId) {
675
+ return resolveFeishuAccount({
676
+ cfg,
677
+ accountId
678
+ }).config.dynamicAgentCreation;
679
+ }
680
+ function isAtDynamicAgentLimit(cfg, dynamicCfg) {
681
+ if (dynamicCfg.maxAgents === void 0) return false;
682
+ return (cfg.agents?.list ?? []).filter((agent) => agent.id.startsWith("feishu-")).length >= dynamicCfg.maxAgents;
683
+ }
684
+ function resolveDynamicAgentId(accountId, senderOpenId) {
685
+ if (accountId === "default") return `feishu-${senderOpenId}`;
686
+ const identityDigest = createHash("sha256").update(accountId).update("\0").update(senderOpenId).digest("hex").slice(0, 32);
687
+ return `feishu-${accountId.slice(0, 12)}-${identityDigest}`;
688
+ }
657
689
  /**
658
- * Check if a dynamic agent should be created for a DM user and create it if needed.
659
- * This creates a unique agent instance with its own workspace for each DM user.
690
+ * Refresh an existing DM binding or create its dynamic agent when current
691
+ * account policy permits config writes.
660
692
  */
661
693
  async function maybeCreateDynamicAgent(params) {
662
- const { cfg, runtime, senderOpenId, dynamicCfg, configWritesAllowed, log } = params;
663
- if (!configWritesAllowed) {
694
+ const { cfg, runtime, senderOpenId, canCreateForConfig, log } = params;
695
+ const accountId = normalizeAccountId(params.accountId);
696
+ if (!hasDefaultDirectRoute(cfg, accountId, senderOpenId)) return {
697
+ created: false,
698
+ updatedCfg: cfg
699
+ };
700
+ const currentCfg = runtime.config.current();
701
+ if (!hasDefaultDirectRoute(currentCfg, accountId, senderOpenId)) return {
702
+ created: false,
703
+ updatedCfg: currentCfg
704
+ };
705
+ const currentDynamicCfg = resolveDynamicAgentConfig(currentCfg, accountId);
706
+ if (!currentDynamicCfg?.enabled) return {
707
+ created: false,
708
+ updatedCfg: currentCfg
709
+ };
710
+ if (!resolveChannelConfigWrites({
711
+ cfg: currentCfg,
712
+ channelId: "feishu",
713
+ accountId
714
+ })) {
664
715
  log(`feishu: config writes disabled, not creating agent for ${senderOpenId}`);
665
716
  return {
666
717
  created: false,
667
- updatedCfg: cfg
718
+ updatedCfg: currentCfg
719
+ };
720
+ }
721
+ const agentId = resolveDynamicAgentId(accountId, senderOpenId);
722
+ if (!(currentCfg.agents?.list ?? []).some((agent) => agent.id === agentId) && isAtDynamicAgentLimit(currentCfg, currentDynamicCfg)) {
723
+ log(`feishu: maxAgents limit (${currentDynamicCfg.maxAgents}) reached, not creating agent for ${senderOpenId}`);
724
+ return {
725
+ created: false,
726
+ updatedCfg: currentCfg
668
727
  };
669
728
  }
670
- const existingBindings = cfg.bindings ?? [];
671
- if (existingBindings.some((b) => b.match?.channel === "feishu" && b.match?.peer?.kind === "direct" && b.match?.peer?.id === senderOpenId)) return {
729
+ if (!await canCreateForConfig(currentCfg)) return {
672
730
  created: false,
673
- updatedCfg: cfg
731
+ updatedCfg: currentCfg
674
732
  };
675
- if (dynamicCfg.maxAgents !== void 0) {
676
- if ((cfg.agents?.list ?? []).filter((a) => a.id.startsWith("feishu-")).length >= dynamicCfg.maxAgents) {
677
- log(`feishu: maxAgents limit (${dynamicCfg.maxAgents}) reached, not creating agent for ${senderOpenId}`);
678
- return {
679
- created: false,
680
- updatedCfg: cfg
681
- };
682
- }
683
- }
684
- const agentId = `feishu-${senderOpenId}`;
685
- if ((cfg.agents?.list ?? []).find((a) => a.id === agentId)) {
686
- log(`feishu: agent "${agentId}" exists, adding missing binding for ${senderOpenId}`);
687
- const updatedCfg = {
688
- ...cfg,
689
- bindings: [...existingBindings, {
733
+ let skippedCfg;
734
+ const committed = await runtime.config.mutateConfigFile({
735
+ base: "runtime",
736
+ afterWrite: { mode: "auto" },
737
+ mutate: async (draft) => {
738
+ if (!hasDefaultDirectRoute(draft, accountId, senderOpenId)) throw new DynamicAgentMutationSkipped(draft);
739
+ const dynamicCfg = resolveDynamicAgentConfig(draft, accountId);
740
+ if (!dynamicCfg?.enabled || !resolveChannelConfigWrites({
741
+ cfg: draft,
742
+ channelId: "feishu",
743
+ accountId
744
+ })) throw new DynamicAgentMutationSkipped(draft);
745
+ const agentExists = (draft.agents?.list ?? []).some((agent) => agent.id === agentId);
746
+ if (!agentExists && isAtDynamicAgentLimit(draft, dynamicCfg)) {
747
+ log(`feishu: maxAgents limit (${dynamicCfg.maxAgents}) reached, not creating agent for ${senderOpenId}`);
748
+ throw new DynamicAgentMutationSkipped(draft);
749
+ }
750
+ if (!await canCreateForConfig(draft)) throw new DynamicAgentMutationSkipped(draft);
751
+ if (!agentExists) {
752
+ const workspaceTemplate = dynamicCfg.workspaceTemplate ?? "~/.openclaw/workspace-{agentId}";
753
+ const agentDirTemplate = dynamicCfg.agentDirTemplate ?? "~/.openclaw/agents/{agentId}/agent";
754
+ const workspace = resolveUserPath(workspaceTemplate.replace("{userId}", senderOpenId).replace("{agentId}", agentId));
755
+ const agentDir = resolveUserPath(agentDirTemplate.replace("{userId}", senderOpenId).replace("{agentId}", agentId));
756
+ log(`feishu: creating dynamic agent "${agentId}" for user ${senderOpenId}`);
757
+ log(` workspace: ${workspace}`);
758
+ log(` agentDir: ${agentDir}`);
759
+ await fs.promises.mkdir(workspace, { recursive: true });
760
+ await fs.promises.mkdir(agentDir, { recursive: true });
761
+ draft.agents = {
762
+ ...draft.agents,
763
+ list: [...draft.agents?.list ?? [], {
764
+ id: agentId,
765
+ workspace,
766
+ agentDir
767
+ }]
768
+ };
769
+ } else log(`feishu: agent "${agentId}" exists, adding missing binding for ${senderOpenId}`);
770
+ draft.bindings = [...draft.bindings ?? [], {
690
771
  agentId,
691
772
  match: {
692
773
  channel: "feishu",
774
+ accountId,
693
775
  peer: {
694
776
  kind: "direct",
695
777
  id: senderOpenId
696
778
  }
697
779
  }
698
- }]
699
- };
700
- await runtime.config.replaceConfigFile({
701
- nextConfig: updatedCfg,
702
- afterWrite: { mode: "auto" }
703
- });
704
- return {
705
- created: true,
706
- updatedCfg,
707
- agentId
708
- };
709
- }
710
- const workspaceTemplate = dynamicCfg.workspaceTemplate ?? "~/.openclaw/workspace-{agentId}";
711
- const agentDirTemplate = dynamicCfg.agentDirTemplate ?? "~/.openclaw/agents/{agentId}/agent";
712
- const workspace = resolveUserPath(workspaceTemplate.replace("{userId}", senderOpenId).replace("{agentId}", agentId));
713
- const agentDir = resolveUserPath(agentDirTemplate.replace("{userId}", senderOpenId).replace("{agentId}", agentId));
714
- log(`feishu: creating dynamic agent "${agentId}" for user ${senderOpenId}`);
715
- log(` workspace: ${workspace}`);
716
- log(` agentDir: ${agentDir}`);
717
- await fs.promises.mkdir(workspace, { recursive: true });
718
- await fs.promises.mkdir(agentDir, { recursive: true });
719
- const updatedCfg = {
720
- ...cfg,
721
- agents: {
722
- ...cfg.agents,
723
- list: [...cfg.agents?.list ?? [], {
724
- id: agentId,
725
- workspace,
726
- agentDir
727
- }]
728
- },
729
- bindings: [...existingBindings, {
730
- agentId,
731
- match: {
732
- channel: "feishu",
733
- peer: {
734
- kind: "direct",
735
- id: senderOpenId
736
- }
737
- }
738
- }]
739
- };
740
- await runtime.config.replaceConfigFile({
741
- nextConfig: updatedCfg,
742
- afterWrite: { mode: "auto" }
780
+ }];
781
+ return {
782
+ created: true,
783
+ agentId
784
+ };
785
+ }
786
+ }).catch((error) => {
787
+ if (error instanceof DynamicAgentMutationSkipped) {
788
+ skippedCfg = error.cfg;
789
+ return null;
790
+ }
791
+ throw error;
743
792
  });
793
+ if (!committed) return {
794
+ created: false,
795
+ updatedCfg: skippedCfg ?? currentCfg
796
+ };
744
797
  return {
745
- created: true,
746
- updatedCfg,
747
- agentId
798
+ created: committed.result?.created ?? false,
799
+ updatedCfg: runtime.config.current(),
800
+ agentId: committed.result?.agentId
748
801
  };
749
802
  }
750
803
  /**
@@ -1392,8 +1445,9 @@ function resolveCardNote(agentId, identity, prefixCtx) {
1392
1445
  }
1393
1446
  function createFeishuReplyDispatcher(params) {
1394
1447
  const core = getFeishuRuntime();
1395
- const { cfg, agentId, chatId, replyToMessageId, skipReplyToInMessages, replyInThread, threadReply, rootId, accountId, identity } = params;
1448
+ const { cfg, agentId, chatId, replyToMessageId, typingTargetMessageId: explicitTypingTargetMessageId, skipReplyToInMessages, replyInThread, threadReply, rootId, accountId, identity } = params;
1396
1449
  const sendReplyToMessageId = skipReplyToInMessages ? void 0 : replyToMessageId;
1450
+ const typingTargetMessageId = explicitTypingTargetMessageId?.trim() || replyToMessageId;
1397
1451
  const threadReplyMode = threadReply === true;
1398
1452
  const effectiveReplyInThread = threadReplyMode ? true : replyInThread;
1399
1453
  const allowTopLevelReplyFallback = effectiveReplyInThread === true && threadReplyMode && rootId !== void 0 && sendReplyToMessageId !== void 0 && sendReplyToMessageId !== rootId;
@@ -1414,13 +1468,13 @@ function createFeishuReplyDispatcher(params) {
1414
1468
  typing: {
1415
1469
  start: async () => {
1416
1470
  if (!(account.config.typingIndicator ?? true)) return;
1417
- if (!replyToMessageId) return;
1471
+ if (!typingTargetMessageId) return;
1418
1472
  const messageCreateTimeMs = normalizeEpochMs(params.messageCreateTimeMs);
1419
1473
  if (messageCreateTimeMs !== void 0 && Date.now() - messageCreateTimeMs > TYPING_INDICATOR_MAX_AGE_MS) return;
1420
1474
  if (typingState?.reactionId) return;
1421
1475
  typingState = await addTypingIndicator({
1422
1476
  cfg,
1423
- messageId: replyToMessageId,
1477
+ messageId: typingTargetMessageId,
1424
1478
  accountId,
1425
1479
  runtime: params.runtime
1426
1480
  });
@@ -1989,6 +2043,7 @@ function parseFeishuMessageEvent(event, botOpenId, _botName) {
1989
2043
  chatId: event.message.chat_id,
1990
2044
  messageId: event.message.message_id,
1991
2045
  replyTargetMessageId: event.message.reply_target_message_id?.trim() || void 0,
2046
+ typingTargetMessageId: event.message.typing_target_message_id?.trim() || void 0,
1992
2047
  suppressReplyTarget: event.message.suppress_reply_target === true,
1993
2048
  senderId: senderUserId || senderOpenId || "",
1994
2049
  senderOpenId: senderFallbackId,
@@ -2269,20 +2324,34 @@ async function handleFeishuMessage(params) {
2269
2324
  });
2270
2325
  const commandProbeBody = isGroup ? normalizeFeishuCommandProbeBody(ctx.content) : ctx.content;
2271
2326
  const shouldComputeCommandAuthorized = core.channel.commands.shouldComputeCommandAuthorized(commandProbeBody, cfg);
2272
- const dmIngress = isDirect ? await resolveFeishuDmIngressAccess({
2273
- cfg,
2274
- accountId: account.accountId,
2275
- dmPolicy,
2276
- allowFrom: configAllowFrom,
2277
- readAllowFromStore: pairing.readAllowFromStore,
2278
- senderOpenId: ctx.senderOpenId,
2279
- senderUserId,
2280
- conversationId: ctx.senderOpenId,
2281
- mayPair: true,
2282
- ...shouldComputeCommandAuthorized ? { command: { hasControlCommand: true } } : {}
2283
- }) : null;
2284
- if (isDirect && dmIngress?.ingress.admission !== "dispatch") {
2285
- if (dmIngress?.ingress.admission === "pairing-required") await pairing.issueChallenge({
2327
+ const resolveDirectAuthorization = async (candidateCfg, mayPair, shouldComputeCommand = core.channel.commands.shouldComputeCommandAuthorized(commandProbeBody, candidateCfg)) => {
2328
+ const candidateAccount = resolveFeishuRuntimeAccount({
2329
+ cfg: candidateCfg,
2330
+ accountId: account.accountId
2331
+ });
2332
+ const candidateDmPolicy = candidateAccount.config.dmPolicy ?? "pairing";
2333
+ const candidateConfigAllowFrom = candidateAccount.config.allowFrom ?? [];
2334
+ return {
2335
+ cfg: candidateCfg,
2336
+ dmPolicy: candidateDmPolicy,
2337
+ configAllowFrom: candidateConfigAllowFrom,
2338
+ ingress: await resolveFeishuDmIngressAccess({
2339
+ cfg: candidateCfg,
2340
+ accountId: candidateAccount.accountId,
2341
+ dmPolicy: candidateDmPolicy,
2342
+ allowFrom: candidateConfigAllowFrom,
2343
+ readAllowFromStore: pairing.readAllowFromStore,
2344
+ senderOpenId: ctx.senderOpenId,
2345
+ senderUserId,
2346
+ conversationId: ctx.senderOpenId,
2347
+ mayPair,
2348
+ ...shouldComputeCommand ? { command: { hasControlCommand: true } } : {}
2349
+ }),
2350
+ shouldComputeCommandAuthorized: shouldComputeCommand
2351
+ };
2352
+ };
2353
+ const rejectDirectAuthorization = async (authorization) => {
2354
+ if (authorization.ingress.ingress.admission === "pairing-required") await pairing.issueChallenge({
2286
2355
  senderId: ctx.senderOpenId,
2287
2356
  senderIdLine: `Your Feishu user id: ${ctx.senderOpenId}`,
2288
2357
  meta: { name: ctx.senderName },
@@ -2291,7 +2360,7 @@ async function handleFeishuMessage(params) {
2291
2360
  },
2292
2361
  sendPairingReply: async (text) => {
2293
2362
  await sendMessageFeishu({
2294
- cfg,
2363
+ cfg: authorization.cfg,
2295
2364
  to: `chat:${ctx.chatId}`,
2296
2365
  text,
2297
2366
  accountId: account.accountId
@@ -2301,10 +2370,34 @@ async function handleFeishuMessage(params) {
2301
2370
  log(`feishu[${account.accountId}]: pairing reply failed for ${ctx.senderOpenId}: ${String(err)}`);
2302
2371
  }
2303
2372
  });
2304
- else log(`feishu[${account.accountId}]: blocked unauthorized sender ${ctx.senderOpenId} (dmPolicy=${dmPolicy})`);
2373
+ else log(`feishu[${account.accountId}]: blocked unauthorized sender ${ctx.senderOpenId} (dmPolicy=${authorization.dmPolicy})`);
2374
+ };
2375
+ const directAuthorization = isDirect ? await resolveDirectAuthorization(cfg, true, shouldComputeCommandAuthorized) : null;
2376
+ const dmIngress = directAuthorization?.ingress ?? null;
2377
+ if (isDirect && dmIngress?.ingress.admission !== "dispatch") {
2378
+ if (directAuthorization) await rejectDirectAuthorization(directAuthorization);
2305
2379
  return;
2306
2380
  }
2307
- const commandAllowFrom = isGroup ? groupConfig?.allowFrom ?? configAllowFrom : dmIngress?.senderAccess.effectiveAllowFrom ?? configAllowFrom;
2381
+ let effectiveDmPolicy = directAuthorization?.dmPolicy ?? dmPolicy;
2382
+ let effectiveConfigAllowFrom = directAuthorization?.configAllowFrom ?? configAllowFrom;
2383
+ let effectiveDmIngress = dmIngress;
2384
+ let effectiveShouldComputeCommandAuthorized = directAuthorization?.shouldComputeCommandAuthorized ?? shouldComputeCommandAuthorized;
2385
+ let effectiveCfg = cfg;
2386
+ if (isDirect) {
2387
+ const currentCfg = getFeishuRuntime().config.current();
2388
+ if (currentCfg !== effectiveCfg) {
2389
+ const currentAuthorization = await resolveDirectAuthorization(currentCfg, true);
2390
+ if (currentAuthorization.ingress.ingress.admission !== "dispatch") {
2391
+ await rejectDirectAuthorization(currentAuthorization);
2392
+ return;
2393
+ }
2394
+ effectiveCfg = currentCfg;
2395
+ effectiveDmPolicy = currentAuthorization.dmPolicy;
2396
+ effectiveConfigAllowFrom = currentAuthorization.configAllowFrom;
2397
+ effectiveDmIngress = currentAuthorization.ingress;
2398
+ effectiveShouldComputeCommandAuthorized = currentAuthorization.shouldComputeCommandAuthorized;
2399
+ }
2400
+ }
2308
2401
  const feishuFrom = `feishu:${ctx.senderOpenId}`;
2309
2402
  const feishuTo = isGroup ? `chat:${ctx.chatId}` : `user:${ctx.senderOpenId}`;
2310
2403
  const peerId = isGroup ? groupSession?.peerId ?? ctx.chatId : ctx.senderOpenId;
@@ -2317,7 +2410,7 @@ async function handleFeishuMessage(params) {
2317
2410
  const feishuAcpConversationSupported = !isGroup || groupSession?.groupSessionScope === "group_topic" || groupSession?.groupSessionScope === "group_topic_sender";
2318
2411
  if (isGroup && groupSession) log(`feishu[${account.accountId}]: group session scope=${groupSession.groupSessionScope}, peer=${peerId}`);
2319
2412
  let route = core.channel.routing.resolveAgentRoute({
2320
- cfg,
2413
+ cfg: effectiveCfg,
2321
2414
  channel: "feishu",
2322
2415
  accountId: account.accountId,
2323
2416
  peer: {
@@ -2326,37 +2419,42 @@ async function handleFeishuMessage(params) {
2326
2419
  },
2327
2420
  parentPeer
2328
2421
  });
2329
- let effectiveCfg = cfg;
2330
2422
  if (!isGroup && route.matchedBy === "default") {
2331
- const dynamicCfg = feishuCfg?.dynamicAgentCreation;
2332
- if (dynamicCfg?.enabled) {
2333
- const result = await maybeCreateDynamicAgent({
2334
- cfg,
2335
- runtime: getFeishuRuntime(),
2336
- senderOpenId: ctx.senderOpenId,
2337
- dynamicCfg,
2338
- configWritesAllowed: resolveChannelConfigWrites({
2339
- cfg,
2340
- channelId: "feishu",
2341
- accountId: account.accountId
2342
- }),
2343
- log: (msg) => log(msg)
2344
- });
2345
- if (result.created) {
2346
- effectiveCfg = result.updatedCfg;
2347
- route = core.channel.routing.resolveAgentRoute({
2348
- cfg: result.updatedCfg,
2349
- channel: "feishu",
2350
- accountId: account.accountId,
2351
- peer: {
2352
- kind: "direct",
2353
- id: ctx.senderOpenId
2354
- }
2355
- });
2356
- log(`feishu[${account.accountId}]: dynamic agent created, new route: ${route.sessionKey}`);
2423
+ const runtimeLocal = getFeishuRuntime();
2424
+ const result = await maybeCreateDynamicAgent({
2425
+ cfg: effectiveCfg,
2426
+ runtime: runtimeLocal,
2427
+ accountId: account.accountId,
2428
+ senderOpenId: ctx.senderOpenId,
2429
+ canCreateForConfig: async (candidateCfg) => {
2430
+ return (await resolveDirectAuthorization(candidateCfg, false)).ingress.ingress.admission === "dispatch";
2431
+ },
2432
+ log: (msg) => log(msg)
2433
+ });
2434
+ if (result.created || result.updatedCfg !== effectiveCfg) {
2435
+ const refreshedAuthorization = await resolveDirectAuthorization(result.updatedCfg, false);
2436
+ if (refreshedAuthorization.ingress.ingress.admission !== "dispatch") {
2437
+ log(`feishu[${account.accountId}]: current policy rejected stale DM from ${ctx.senderOpenId} before adopting refreshed dynamic route (dmPolicy=${refreshedAuthorization.dmPolicy})`);
2438
+ return;
2357
2439
  }
2440
+ effectiveCfg = result.updatedCfg;
2441
+ effectiveDmPolicy = refreshedAuthorization.dmPolicy;
2442
+ effectiveConfigAllowFrom = refreshedAuthorization.configAllowFrom;
2443
+ effectiveDmIngress = refreshedAuthorization.ingress;
2444
+ effectiveShouldComputeCommandAuthorized = refreshedAuthorization.shouldComputeCommandAuthorized;
2445
+ route = core.channel.routing.resolveAgentRoute({
2446
+ cfg: result.updatedCfg,
2447
+ channel: "feishu",
2448
+ accountId: account.accountId,
2449
+ peer: {
2450
+ kind: "direct",
2451
+ id: ctx.senderOpenId
2452
+ }
2453
+ });
2454
+ if (result.created) log(`feishu[${account.accountId}]: dynamic agent created, new route: ${route.sessionKey}`);
2358
2455
  }
2359
2456
  }
2457
+ const commandAllowFrom = isGroup ? groupConfig?.allowFrom ?? effectiveConfigAllowFrom : effectiveDmIngress?.senderAccess.effectiveAllowFrom ?? effectiveConfigAllowFrom;
2360
2458
  const currentConversationId = peerId;
2361
2459
  const parentConversationId = isGroup ? parentPeer?.id ?? ctx.chatId : void 0;
2362
2460
  let configuredBinding = null;
@@ -2446,8 +2544,8 @@ async function handleFeishuMessage(params) {
2446
2544
  content: audioTranscript
2447
2545
  };
2448
2546
  const effectiveCommandProbeBody = audioTranscript === void 0 ? commandProbeBody : isGroup ? normalizeFeishuCommandProbeBody(audioTranscript) : audioTranscript;
2449
- const commandAuthorized = (audioTranscript === void 0 ? shouldComputeCommandAuthorized : core.channel.commands.shouldComputeCommandAuthorized(effectiveCommandProbeBody, cfg)) ? isDirect && audioTranscript === void 0 && dmIngress ? dmIngress.commandAccess.authorized : isGroup ? (await resolveFeishuGroupSenderActivationIngressAccess({
2450
- cfg,
2547
+ const commandAuthorized = (audioTranscript === void 0 ? effectiveShouldComputeCommandAuthorized : core.channel.commands.shouldComputeCommandAuthorized(effectiveCommandProbeBody, effectiveCfg)) ? isDirect && audioTranscript === void 0 && effectiveDmIngress ? effectiveDmIngress.commandAccess.authorized : isGroup ? (await resolveFeishuGroupSenderActivationIngressAccess({
2548
+ cfg: effectiveCfg,
2451
2549
  accountId: account.accountId,
2452
2550
  chatId: ctx.chatId,
2453
2551
  allowFrom: commandAllowFrom,
@@ -2457,10 +2555,10 @@ async function handleFeishuMessage(params) {
2457
2555
  mentionedBot: true,
2458
2556
  command: { hasControlCommand: true }
2459
2557
  })).commandAccess.authorized : (await resolveFeishuDmIngressAccess({
2460
- cfg,
2558
+ cfg: effectiveCfg,
2461
2559
  accountId: account.accountId,
2462
- dmPolicy,
2463
- allowFrom: configAllowFrom,
2560
+ dmPolicy: effectiveDmPolicy,
2561
+ allowFrom: effectiveConfigAllowFrom,
2464
2562
  readAllowFromStore: pairing.readAllowFromStore,
2465
2563
  senderOpenId: ctx.senderOpenId,
2466
2564
  senderUserId,
@@ -2708,11 +2806,12 @@ async function handleFeishuMessage(params) {
2708
2806
  const configReplyInThread = isGroup && (groupConfig?.replyInThread ?? feishuCfg?.replyInThread ?? "disabled") === "enabled";
2709
2807
  const topicReplyTargetMessageId = ctx.rootId ?? defaultReplyTargetMessageId;
2710
2808
  const replyTargetMessageId = directThreadReply ? directThreadReplyTargetMessageId : isTopicSession || configReplyInThread ? topicReplyTargetMessageId : defaultReplyTargetMessageId;
2809
+ const typingTargetMessageId = ctx.typingTargetMessageId ?? (ctx.suppressReplyTarget ? void 0 : ctx.messageId);
2711
2810
  const threadReply = isGroup ? groupSession?.threadReply ?? false : directThreadReply;
2712
2811
  const lastRouteThreadId = isGroup && (isTopicSession || configReplyInThread || threadReply) ? replyTargetMessageId : void 0;
2713
2812
  const pinnedMainDmOwner = !isGroup ? resolvePinnedMainDmOwnerFromAllowlist({
2714
- dmScope: cfg.session?.dmScope,
2715
- allowFrom: configAllowFrom,
2813
+ dmScope: effectiveCfg.session?.dmScope,
2814
+ allowFrom: effectiveConfigAllowFrom,
2716
2815
  normalizeEntry: normalizeFeishuAllowEntry
2717
2816
  }) : null;
2718
2817
  const pinnedMainDmSenderRecipient = pinnedMainDmOwner ? [ctx.senderOpenId, senderUserId].map((id) => id ? normalizeFeishuAllowEntry(id) : "").find((recipient) => recipient === pinnedMainDmOwner) : void 0;
@@ -2778,6 +2877,7 @@ async function handleFeishuMessage(params) {
2778
2877
  chatId: ctx.chatId,
2779
2878
  allowReasoningPreview,
2780
2879
  replyToMessageId: replyTargetMessageId,
2880
+ typingTargetMessageId,
2781
2881
  skipReplyToInMessages: !isGroup && !directThreadReply,
2782
2882
  replyInThread,
2783
2883
  rootId: ctx.rootId,
@@ -2904,21 +3004,22 @@ async function handleFeishuMessage(params) {
2904
3004
  log(`feishu[${account.accountId}]: broadcast dispatch complete for ${broadcastAgents.length} agents`);
2905
3005
  } else {
2906
3006
  const ctxPayload = await buildCtxPayloadForAgent(route.agentId, route.sessionKey, route.accountId, ctx.mentionedBot);
2907
- const identity = resolveAgentOutboundIdentity(cfg, route.agentId);
2908
- const storePath = core.channel.session.resolveStorePath(cfg.session?.store, { agentId: route.agentId });
3007
+ const identity = resolveAgentOutboundIdentity(effectiveCfg, route.agentId);
3008
+ const storePath = core.channel.session.resolveStorePath(effectiveCfg.session?.store, { agentId: route.agentId });
2909
3009
  const allowReasoningPreview = resolveFeishuReasoningPreviewEnabled({
2910
- cfg,
3010
+ cfg: effectiveCfg,
2911
3011
  agentId: route.agentId,
2912
3012
  storePath,
2913
3013
  sessionKey: route.sessionKey
2914
3014
  });
2915
3015
  const { dispatcher, replyOptions, markDispatchIdle, ensureNoVisibleReplyFallback } = createFeishuReplyDispatcher({
2916
- cfg,
3016
+ cfg: effectiveCfg,
2917
3017
  agentId: route.agentId,
2918
3018
  runtime,
2919
3019
  chatId: ctx.chatId,
2920
3020
  allowReasoningPreview,
2921
3021
  replyToMessageId: replyTargetMessageId,
3022
+ typingTargetMessageId,
2922
3023
  skipReplyToInMessages: !isGroup && !directThreadReply,
2923
3024
  replyInThread,
2924
3025
  rootId: ctx.rootId,
@@ -2975,7 +3076,7 @@ async function handleFeishuMessage(params) {
2975
3076
  },
2976
3077
  run: () => core.channel.reply.dispatchReplyFromConfig({
2977
3078
  ctx: ctxPayload,
2978
- cfg,
3079
+ cfg: effectiveCfg,
2979
3080
  dispatcher,
2980
3081
  replyOptions
2981
3082
  })
@@ -3133,6 +3234,7 @@ function buildSyntheticMessageEvent(event, content, chatType) {
3133
3234
  message: {
3134
3235
  message_id: `card-action-${event.token}`,
3135
3236
  ...replyTargetMessageId ? { reply_target_message_id: replyTargetMessageId } : {},
3237
+ ...replyTargetMessageId ? { typing_target_message_id: replyTargetMessageId } : {},
3136
3238
  ...!replyTargetMessageId ? { suppress_reply_target: true } : {},
3137
3239
  chat_id: event.context.chat_id || event.operator.open_id,
3138
3240
  chat_type: chatType,
@@ -3430,9 +3532,10 @@ const BOT_IDENTITY_RETRY_DELAYS_MS = [
3430
3532
  function applyBotIdentityState(accountId, identity) {
3431
3533
  const botOpenId = normalizeOptionalString(identity.botOpenId);
3432
3534
  const botName = normalizeOptionalString(identity.botName);
3433
- botOpenIds.set(accountId, botOpenId ?? "");
3434
- if (botName) botNames.set(accountId, botName);
3435
- else botNames.delete(accountId);
3535
+ setFeishuBotIdentityState(accountId, {
3536
+ botOpenId: botOpenId ?? "",
3537
+ botName
3538
+ });
3436
3539
  return {
3437
3540
  botOpenId,
3438
3541
  botName
@@ -4381,7 +4484,6 @@ async function handleFeishuCommentEvent(params) {
4381
4484
  cfg: params.cfg,
4382
4485
  accountId: params.accountId
4383
4486
  });
4384
- const feishuCfg = account.config;
4385
4487
  const core = getFeishuRuntime();
4386
4488
  const log = params.runtime?.log ?? console.log;
4387
4489
  const error = params.runtime?.error ?? console.error;
@@ -4405,27 +4507,37 @@ async function handleFeishuCommentEvent(params) {
4405
4507
  fileToken: turn.fileToken,
4406
4508
  commentId: turn.commentId
4407
4509
  });
4408
- const dmPolicy = feishuCfg?.dmPolicy ?? "pairing";
4409
- const configAllowFrom = feishuCfg?.allowFrom ?? [];
4410
4510
  const pairing = createChannelPairingController$1({
4411
4511
  core,
4412
4512
  channel: "feishu",
4413
4513
  accountId: account.accountId
4414
4514
  });
4415
- const dmIngress = await resolveFeishuDmIngressAccess({
4416
- cfg: params.cfg,
4417
- accountId: account.accountId,
4418
- dmPolicy,
4419
- allowFrom: configAllowFrom,
4420
- readAllowFromStore: pairing.readAllowFromStore,
4421
- senderOpenId: turn.senderId,
4422
- senderUserId: turn.senderUserId,
4423
- conversationId: turn.senderId,
4424
- mayPair: true
4425
- });
4426
- if (dmIngress.ingress.admission !== "dispatch") {
4427
- if (dmIngress.ingress.admission === "pairing-required") {
4428
- const client = createFeishuClient(account);
4515
+ const resolveCommentAuthorization = async (candidateCfg, mayPair) => {
4516
+ const candidateAccount = resolveFeishuRuntimeAccount({
4517
+ cfg: candidateCfg,
4518
+ accountId: account.accountId
4519
+ });
4520
+ const candidateDmPolicy = candidateAccount.config.dmPolicy ?? "pairing";
4521
+ return {
4522
+ account: candidateAccount,
4523
+ cfg: candidateCfg,
4524
+ dmPolicy: candidateDmPolicy,
4525
+ ingress: await resolveFeishuDmIngressAccess({
4526
+ cfg: candidateCfg,
4527
+ accountId: candidateAccount.accountId,
4528
+ dmPolicy: candidateDmPolicy,
4529
+ allowFrom: candidateAccount.config.allowFrom ?? [],
4530
+ readAllowFromStore: pairing.readAllowFromStore,
4531
+ senderOpenId: turn.senderId,
4532
+ senderUserId: turn.senderUserId,
4533
+ conversationId: turn.senderId,
4534
+ mayPair
4535
+ })
4536
+ };
4537
+ };
4538
+ const rejectCommentAuthorization = async (authorization) => {
4539
+ if (authorization.ingress.ingress.admission === "pairing-required") {
4540
+ const client = createFeishuClient(authorization.account);
4429
4541
  await pairing.issueChallenge({
4430
4542
  senderId: turn.senderId,
4431
4543
  senderIdLine: `Your Feishu user id: ${turn.senderId}`,
@@ -4446,12 +4558,25 @@ async function handleFeishuCommentEvent(params) {
4446
4558
  log(`feishu[${account.accountId}]: comment pairing reply failed for ${turn.senderId}: ${String(err)}`);
4447
4559
  }
4448
4560
  });
4449
- } else log(`feishu[${account.accountId}]: blocked unauthorized comment sender ${turn.senderId} (dmPolicy=${dmPolicy}, comment=${turn.commentId})`);
4561
+ } else log(`feishu[${account.accountId}]: blocked unauthorized comment sender ${turn.senderId} (dmPolicy=${authorization.dmPolicy}, comment=${turn.commentId})`);
4562
+ };
4563
+ const commentAuthorization = await resolveCommentAuthorization(params.cfg, true);
4564
+ if (commentAuthorization.ingress.ingress.admission !== "dispatch") {
4565
+ await rejectCommentAuthorization(commentAuthorization);
4450
4566
  return;
4451
4567
  }
4452
4568
  let effectiveCfg = params.cfg;
4569
+ const currentCfg = core.config.current();
4570
+ if (currentCfg !== effectiveCfg) {
4571
+ const currentAuthorization = await resolveCommentAuthorization(currentCfg, true);
4572
+ if (currentAuthorization.ingress.ingress.admission !== "dispatch") {
4573
+ await rejectCommentAuthorization(currentAuthorization);
4574
+ return;
4575
+ }
4576
+ effectiveCfg = currentCfg;
4577
+ }
4453
4578
  let route = core.channel.routing.resolveAgentRoute({
4454
- cfg: params.cfg,
4579
+ cfg: effectiveCfg,
4455
4580
  channel: "feishu",
4456
4581
  accountId: account.accountId,
4457
4582
  peer: {
@@ -4460,33 +4585,33 @@ async function handleFeishuCommentEvent(params) {
4460
4585
  }
4461
4586
  });
4462
4587
  if (route.matchedBy === "default") {
4463
- const dynamicCfg = feishuCfg?.dynamicAgentCreation;
4464
- if (dynamicCfg?.enabled) {
4465
- const dynamicResult = await maybeCreateDynamicAgent({
4466
- cfg: params.cfg,
4467
- runtime: core,
4468
- senderOpenId: turn.senderId,
4469
- dynamicCfg,
4470
- configWritesAllowed: resolveChannelConfigWrites({
4471
- cfg: params.cfg,
4472
- channelId: "feishu",
4473
- accountId: account.accountId
4474
- }),
4475
- log: (message) => log(message)
4476
- });
4477
- if (dynamicResult.created) {
4478
- effectiveCfg = dynamicResult.updatedCfg;
4479
- route = core.channel.routing.resolveAgentRoute({
4480
- cfg: dynamicResult.updatedCfg,
4481
- channel: "feishu",
4482
- accountId: account.accountId,
4483
- peer: {
4484
- kind: "direct",
4485
- id: turn.senderId
4486
- }
4487
- });
4488
- log(`feishu[${account.accountId}]: dynamic agent created for comment flow, route=${route.sessionKey}`);
4588
+ const dynamicResult = await maybeCreateDynamicAgent({
4589
+ cfg: effectiveCfg,
4590
+ runtime: core,
4591
+ accountId: account.accountId,
4592
+ senderOpenId: turn.senderId,
4593
+ canCreateForConfig: async (candidateCfg) => {
4594
+ return (await resolveCommentAuthorization(candidateCfg, false)).ingress.ingress.admission === "dispatch";
4595
+ },
4596
+ log: (message) => log(message)
4597
+ });
4598
+ if (dynamicResult.created || dynamicResult.updatedCfg !== effectiveCfg) {
4599
+ const refreshedAuthorization = await resolveCommentAuthorization(dynamicResult.updatedCfg, false);
4600
+ if (refreshedAuthorization.ingress.ingress.admission !== "dispatch") {
4601
+ log(`feishu[${account.accountId}]: current policy rejected stale comment sender ${turn.senderId} before adopting refreshed dynamic route (dmPolicy=${refreshedAuthorization.dmPolicy}, comment=${turn.commentId})`);
4602
+ return;
4489
4603
  }
4604
+ effectiveCfg = dynamicResult.updatedCfg;
4605
+ route = core.channel.routing.resolveAgentRoute({
4606
+ cfg: dynamicResult.updatedCfg,
4607
+ channel: "feishu",
4608
+ accountId: account.accountId,
4609
+ peer: {
4610
+ kind: "direct",
4611
+ id: turn.senderId
4612
+ }
4613
+ });
4614
+ if (dynamicResult.created) log(`feishu[${account.accountId}]: dynamic agent created for comment flow, route=${route.sessionKey}`);
4490
4615
  }
4491
4616
  }
4492
4617
  const commentSessionKey = buildCommentSessionKey({
@@ -4972,10 +5097,7 @@ function cleanupFeishuWsClient(params) {
4972
5097
  error(`feishu[${accountId}]: error closing WebSocket client: ${formatFeishuWsErrorForLog(err)}`);
4973
5098
  }
4974
5099
  wsClients.delete(accountId);
4975
- if (clearIdentity) {
4976
- botOpenIds.delete(accountId);
4977
- botNames.delete(accountId);
4978
- }
5100
+ if (clearIdentity) clearFeishuBotIdentityState(accountId);
4979
5101
  }
4980
5102
  function waitForFeishuWsCycleEnd(params) {
4981
5103
  if (params.abortSignal?.aborted) return Promise.resolve("abort");
@@ -5155,21 +5277,19 @@ async function monitorWebhook({ account, accountId, runtime, abortSignal, eventD
5155
5277
  })();
5156
5278
  });
5157
5279
  httpServers.set(accountId, server);
5158
- return new Promise((resolve, reject) => {
5159
- const cleanup = () => {
5160
- server.close();
5161
- httpServers.delete(accountId);
5162
- botOpenIds.delete(accountId);
5163
- botNames.delete(accountId);
5280
+ return await new Promise((resolve, reject) => {
5281
+ let cleanupStarted = false;
5282
+ const cleanup = async () => {
5283
+ if (cleanupStarted) return;
5284
+ cleanupStarted = true;
5285
+ await closeTrackedFeishuHttpServer(accountId, server);
5164
5286
  };
5165
5287
  const handleAbort = () => {
5166
5288
  log(`feishu[${accountId}]: abort signal received, stopping Webhook server`);
5167
- cleanup();
5168
- resolve();
5289
+ cleanup().then(resolve, reject);
5169
5290
  };
5170
5291
  if (abortSignal?.aborted) {
5171
- cleanup();
5172
- resolve();
5292
+ cleanup().then(resolve, reject);
5173
5293
  return;
5174
5294
  }
5175
5295
  abortSignal?.addEventListener("abort", handleAbort, { once: true });
@@ -5244,6 +5364,7 @@ async function resolveReactionSyntheticEvent(params) {
5244
5364
  },
5245
5365
  message: {
5246
5366
  message_id: `${messageId}:reaction:${emoji}:${uuid()}`,
5367
+ typing_target_message_id: messageId,
5247
5368
  chat_id: syntheticChatId,
5248
5369
  chat_type: syntheticChatType,
5249
5370
  message_type: "text",