@openclaw/feishu 2026.6.6-beta.2 → 2026.6.8-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.
- package/dist/api.js +1 -1
- package/dist/{channel-BYCCZN1h.js → channel-DwygSth-.js} +2 -2
- package/dist/channel-plugin-api.js +1 -1
- package/dist/{channel.runtime-DACJbxhN.js → channel.runtime-CrqKcXaU.js} +1 -1
- package/dist/{monitor-D6ePNz_R.js → monitor-_1eNsUVp.js} +2 -2
- package/dist/{monitor.account-Cl-QnhPV.js → monitor.account-BhoLA1Y0.js} +319 -198
- package/dist/{monitor.state-r4OLFBfg.js → monitor.state-QV66eUNA.js} +73 -1
- package/dist/setup-api.js +1 -1
- package/npm-shrinkwrap.json +3 -3
- package/package.json +4 -4
package/dist/api.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { r as listEnabledFeishuAccounts } from "./accounts-Cfzht2Hc.js";
|
|
2
|
-
import { a as setFeishuNamedAccountEnabled, i as feishuSetupAdapter, n as feishuSetupWizard, r as runFeishuLogin, t as feishuPlugin } from "./channel-
|
|
2
|
+
import { a as setFeishuNamedAccountEnabled, i as feishuSetupAdapter, n as feishuSetupWizard, r as runFeishuLogin, t as feishuPlugin } from "./channel-DwygSth-.js";
|
|
3
3
|
import { a as parseFeishuTargetId, i as parseFeishuDirectConversationId, n as buildFeishuModelOverrideParentCandidates, r as parseFeishuConversationId, t as buildFeishuConversationId } from "./conversation-id-DuL575sn.js";
|
|
4
4
|
import { t as getFeishuRuntime } from "./runtime-C5JxBWZp.js";
|
|
5
5
|
import { r as createFeishuClient } from "./client-BhMNZBJD.js";
|
|
@@ -1735,7 +1735,7 @@ const meta = {
|
|
|
1735
1735
|
order: 70,
|
|
1736
1736
|
preferSessionLookupForAnnounceTarget: true
|
|
1737
1737
|
};
|
|
1738
|
-
const loadFeishuChannelRuntime = createLazyRuntimeNamedExport(() => import("./channel.runtime-
|
|
1738
|
+
const loadFeishuChannelRuntime = createLazyRuntimeNamedExport(() => import("./channel.runtime-CrqKcXaU.js"), "feishuChannelRuntime");
|
|
1739
1739
|
function toFeishuMessageSendResult(result, kind) {
|
|
1740
1740
|
const receipt = result.receipt ?? createFeishuSendReceipt({
|
|
1741
1741
|
messageId: result.messageId,
|
|
@@ -2551,7 +2551,7 @@ const feishuPlugin = createChatChannelPlugin({
|
|
|
2551
2551
|
})
|
|
2552
2552
|
}),
|
|
2553
2553
|
gateway: { startAccount: async (ctx) => {
|
|
2554
|
-
const { monitorFeishuProvider } = await import("./monitor-
|
|
2554
|
+
const { monitorFeishuProvider } = await import("./monitor-_1eNsUVp.js");
|
|
2555
2555
|
const account = resolveFeishuRuntimeAccount({
|
|
2556
2556
|
cfg: ctx.cfg,
|
|
2557
2557
|
accountId: ctx.accountId
|
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
import { t as feishuPlugin } from "./channel-
|
|
1
|
+
import { t as feishuPlugin } from "./channel-DwygSth-.js";
|
|
2
2
|
export { feishuPlugin };
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { o as resolveFeishuAccount, s as resolveFeishuRuntimeAccount, v as parseFeishuCommentTarget } from "./accounts-Cfzht2Hc.js";
|
|
2
|
-
import { h as listFeishuDirectoryPeers, m as listFeishuDirectoryGroups, o as buildFeishuPresentationCardElements } from "./channel-
|
|
2
|
+
import { h as listFeishuDirectoryPeers, m as listFeishuDirectoryGroups, o as buildFeishuPresentationCardElements } from "./channel-DwygSth-.js";
|
|
3
3
|
import { r as createFeishuClient } from "./client-BhMNZBJD.js";
|
|
4
4
|
import { c as getChatInfo, l as getChatMembers, r as cleanupAmbientCommentTypingReaction, t as deliverCommentThreadText, u as getFeishuMemberInfo } from "./drive-8o3Omlnd.js";
|
|
5
5
|
import { chunkTextForOutbound } from "./runtime-api.js";
|
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
import { r as listEnabledFeishuAccounts, s as resolveFeishuRuntimeAccount } from "./accounts-Cfzht2Hc.js";
|
|
2
|
-
import {
|
|
2
|
+
import { f as fetchBotIdentityForMonitor } from "./monitor.state-QV66eUNA.js";
|
|
3
3
|
//#region extensions/feishu/src/monitor.ts
|
|
4
4
|
let monitorAccountRuntimePromise;
|
|
5
5
|
async function loadMonitorAccountRuntime() {
|
|
6
|
-
monitorAccountRuntimePromise ??= import("./monitor.account-
|
|
6
|
+
monitorAccountRuntimePromise ??= import("./monitor.account-BhoLA1Y0.js");
|
|
7
7
|
return await monitorAccountRuntimePromise;
|
|
8
8
|
}
|
|
9
9
|
async function monitorFeishuProvider(opts = {}) {
|
|
@@ -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-
|
|
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
|
|
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
|
-
*
|
|
659
|
-
*
|
|
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,
|
|
663
|
-
|
|
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:
|
|
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
|
-
|
|
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:
|
|
731
|
+
updatedCfg: currentCfg
|
|
674
732
|
};
|
|
675
|
-
|
|
676
|
-
|
|
677
|
-
|
|
678
|
-
|
|
679
|
-
|
|
680
|
-
|
|
681
|
-
|
|
682
|
-
|
|
683
|
-
|
|
684
|
-
|
|
685
|
-
|
|
686
|
-
|
|
687
|
-
|
|
688
|
-
|
|
689
|
-
|
|
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
|
-
|
|
701
|
-
|
|
702
|
-
|
|
703
|
-
}
|
|
704
|
-
|
|
705
|
-
|
|
706
|
-
|
|
707
|
-
|
|
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:
|
|
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 (!
|
|
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:
|
|
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
|
|
2273
|
-
|
|
2274
|
-
|
|
2275
|
-
|
|
2276
|
-
|
|
2277
|
-
|
|
2278
|
-
|
|
2279
|
-
|
|
2280
|
-
|
|
2281
|
-
|
|
2282
|
-
|
|
2283
|
-
|
|
2284
|
-
|
|
2285
|
-
|
|
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
|
-
|
|
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
|
|
2332
|
-
|
|
2333
|
-
|
|
2334
|
-
|
|
2335
|
-
|
|
2336
|
-
|
|
2337
|
-
|
|
2338
|
-
|
|
2339
|
-
|
|
2340
|
-
|
|
2341
|
-
|
|
2342
|
-
|
|
2343
|
-
|
|
2344
|
-
|
|
2345
|
-
|
|
2346
|
-
|
|
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 ?
|
|
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:
|
|
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:
|
|
2715
|
-
allowFrom:
|
|
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(
|
|
2908
|
-
const storePath = core.channel.session.resolveStorePath(
|
|
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
|
-
|
|
3434
|
-
|
|
3435
|
-
|
|
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
|
|
4416
|
-
|
|
4417
|
-
|
|
4418
|
-
|
|
4419
|
-
|
|
4420
|
-
|
|
4421
|
-
|
|
4422
|
-
|
|
4423
|
-
|
|
4424
|
-
|
|
4425
|
-
|
|
4426
|
-
|
|
4427
|
-
|
|
4428
|
-
|
|
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:
|
|
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
|
|
4464
|
-
|
|
4465
|
-
|
|
4466
|
-
|
|
4467
|
-
|
|
4468
|
-
|
|
4469
|
-
|
|
4470
|
-
|
|
4471
|
-
|
|
4472
|
-
|
|
4473
|
-
|
|
4474
|
-
|
|
4475
|
-
|
|
4476
|
-
|
|
4477
|
-
|
|
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
|
-
|
|
5160
|
-
|
|
5161
|
-
|
|
5162
|
-
|
|
5163
|
-
|
|
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",
|
|
@@ -44,8 +44,10 @@ const wsClients = /* @__PURE__ */ new Map();
|
|
|
44
44
|
const httpServers = /* @__PURE__ */ new Map();
|
|
45
45
|
const botOpenIds = /* @__PURE__ */ new Map();
|
|
46
46
|
const botNames = /* @__PURE__ */ new Map();
|
|
47
|
+
const botIdentityRevisions = /* @__PURE__ */ new Map();
|
|
47
48
|
const FEISHU_WEBHOOK_MAX_BODY_BYTES = 64 * 1024;
|
|
48
49
|
const FEISHU_WEBHOOK_BODY_TIMEOUT_MS = 5e3;
|
|
50
|
+
const FEISHU_HTTP_SERVER_CLOSE_TIMEOUT_MS = 5e3;
|
|
49
51
|
const FEISHU_WEBHOOK_RATE_LIMIT_FALLBACK_DEFAULTS = {
|
|
50
52
|
windowMs: 6e4,
|
|
51
53
|
maxRequests: 120,
|
|
@@ -89,6 +91,76 @@ const feishuWebhookAnomalyTracker = createWebhookAnomalyTracker({
|
|
|
89
91
|
ttlMs: feishuWebhookAnomalyDefaults.ttlMs,
|
|
90
92
|
logEvery: feishuWebhookAnomalyDefaults.logEvery
|
|
91
93
|
});
|
|
94
|
+
function readBotIdentityRevision(accountId) {
|
|
95
|
+
return botIdentityRevisions.get(accountId) ?? 0;
|
|
96
|
+
}
|
|
97
|
+
function bumpBotIdentityRevision(accountId) {
|
|
98
|
+
botIdentityRevisions.set(accountId, readBotIdentityRevision(accountId) + 1);
|
|
99
|
+
}
|
|
100
|
+
function captureBotIdentitySnapshot(accountId) {
|
|
101
|
+
return { revision: readBotIdentityRevision(accountId) };
|
|
102
|
+
}
|
|
103
|
+
function clearFeishuBotIdentityStateIfUnchanged(accountId, snapshot) {
|
|
104
|
+
if (readBotIdentityRevision(accountId) !== snapshot.revision) return;
|
|
105
|
+
botOpenIds.delete(accountId);
|
|
106
|
+
botNames.delete(accountId);
|
|
107
|
+
bumpBotIdentityRevision(accountId);
|
|
108
|
+
}
|
|
109
|
+
function setFeishuBotIdentityState(accountId, identity) {
|
|
110
|
+
botOpenIds.set(accountId, identity.botOpenId);
|
|
111
|
+
if (identity.botName) botNames.set(accountId, identity.botName);
|
|
112
|
+
else botNames.delete(accountId);
|
|
113
|
+
bumpBotIdentityRevision(accountId);
|
|
114
|
+
}
|
|
115
|
+
function clearFeishuBotIdentityState(accountId) {
|
|
116
|
+
botOpenIds.delete(accountId);
|
|
117
|
+
botNames.delete(accountId);
|
|
118
|
+
bumpBotIdentityRevision(accountId);
|
|
119
|
+
}
|
|
120
|
+
function isServerNotRunningError(error) {
|
|
121
|
+
return error.code === "ERR_SERVER_NOT_RUNNING";
|
|
122
|
+
}
|
|
123
|
+
async function closeFeishuHttpServer(server) {
|
|
124
|
+
await new Promise((resolve, reject) => {
|
|
125
|
+
let settled = false;
|
|
126
|
+
const settle = (err) => {
|
|
127
|
+
if (settled) return;
|
|
128
|
+
settled = true;
|
|
129
|
+
clearTimeout(fallbackTimer);
|
|
130
|
+
if (!err || isServerNotRunningError(err)) {
|
|
131
|
+
resolve();
|
|
132
|
+
return;
|
|
133
|
+
}
|
|
134
|
+
reject(err);
|
|
135
|
+
};
|
|
136
|
+
const fallbackTimer = setTimeout(() => {
|
|
137
|
+
try {
|
|
138
|
+
server.closeAllConnections();
|
|
139
|
+
settle();
|
|
140
|
+
} catch (err) {
|
|
141
|
+
settle(err instanceof Error ? err : new Error(String(err)));
|
|
142
|
+
}
|
|
143
|
+
}, FEISHU_HTTP_SERVER_CLOSE_TIMEOUT_MS);
|
|
144
|
+
try {
|
|
145
|
+
server.close((err) => {
|
|
146
|
+
settle(err);
|
|
147
|
+
});
|
|
148
|
+
} catch (err) {
|
|
149
|
+
settle(err instanceof Error ? err : new Error(String(err)));
|
|
150
|
+
}
|
|
151
|
+
});
|
|
152
|
+
}
|
|
153
|
+
async function closeTrackedFeishuHttpServer(accountId, server) {
|
|
154
|
+
const identitySnapshot = captureBotIdentitySnapshot(accountId);
|
|
155
|
+
try {
|
|
156
|
+
await closeFeishuHttpServer(server);
|
|
157
|
+
} finally {
|
|
158
|
+
if (httpServers.get(accountId) === server) {
|
|
159
|
+
httpServers.delete(accountId);
|
|
160
|
+
clearFeishuBotIdentityStateIfUnchanged(accountId, identitySnapshot);
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
}
|
|
92
164
|
function recordWebhookStatus(runtime, accountId, path, statusCode) {
|
|
93
165
|
feishuWebhookAnomalyTracker.record({
|
|
94
166
|
key: `${accountId}:${path}:${statusCode}`,
|
|
@@ -98,4 +170,4 @@ function recordWebhookStatus(runtime, accountId, path, statusCode) {
|
|
|
98
170
|
});
|
|
99
171
|
}
|
|
100
172
|
//#endregion
|
|
101
|
-
export {
|
|
173
|
+
export { clearFeishuBotIdentityState as a, httpServers as c, wsClients as d, fetchBotIdentityForMonitor as f, botOpenIds as i, recordWebhookStatus as l, FEISHU_WEBHOOK_MAX_BODY_BYTES as n, closeTrackedFeishuHttpServer as o, botNames as r, feishuWebhookRateLimiter as s, FEISHU_WEBHOOK_BODY_TIMEOUT_MS as t, setFeishuBotIdentityState as u };
|
package/dist/setup-api.js
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
import { i as feishuSetupAdapter, n as feishuSetupWizard, t as feishuPlugin } from "./channel-
|
|
1
|
+
import { i as feishuSetupAdapter, n as feishuSetupWizard, t as feishuPlugin } from "./channel-DwygSth-.js";
|
|
2
2
|
export { feishuPlugin, feishuSetupAdapter, feishuSetupWizard };
|
package/npm-shrinkwrap.json
CHANGED
|
@@ -1,19 +1,19 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@openclaw/feishu",
|
|
3
|
-
"version": "2026.6.
|
|
3
|
+
"version": "2026.6.8-beta.1",
|
|
4
4
|
"lockfileVersion": 3,
|
|
5
5
|
"requires": true,
|
|
6
6
|
"packages": {
|
|
7
7
|
"": {
|
|
8
8
|
"name": "@openclaw/feishu",
|
|
9
|
-
"version": "2026.6.
|
|
9
|
+
"version": "2026.6.8-beta.1",
|
|
10
10
|
"dependencies": {
|
|
11
11
|
"@larksuiteoapi/node-sdk": "1.66.0",
|
|
12
12
|
"typebox": "1.1.39",
|
|
13
13
|
"zod": "4.4.3"
|
|
14
14
|
},
|
|
15
15
|
"peerDependencies": {
|
|
16
|
-
"openclaw": ">=2026.6.
|
|
16
|
+
"openclaw": ">=2026.6.8-beta.1"
|
|
17
17
|
},
|
|
18
18
|
"peerDependenciesMeta": {
|
|
19
19
|
"openclaw": {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@openclaw/feishu",
|
|
3
|
-
"version": "2026.6.
|
|
3
|
+
"version": "2026.6.8-beta.1",
|
|
4
4
|
"description": "OpenClaw Feishu/Lark channel plugin for chats and workplace tools (community maintained by @m1heng).",
|
|
5
5
|
"repository": {
|
|
6
6
|
"type": "git",
|
|
@@ -13,7 +13,7 @@
|
|
|
13
13
|
"zod": "4.4.3"
|
|
14
14
|
},
|
|
15
15
|
"peerDependencies": {
|
|
16
|
-
"openclaw": ">=2026.6.
|
|
16
|
+
"openclaw": ">=2026.6.8-beta.1"
|
|
17
17
|
},
|
|
18
18
|
"peerDependenciesMeta": {
|
|
19
19
|
"openclaw": {
|
|
@@ -47,10 +47,10 @@
|
|
|
47
47
|
"minHostVersion": ">=2026.5.29"
|
|
48
48
|
},
|
|
49
49
|
"compat": {
|
|
50
|
-
"pluginApi": ">=2026.6.
|
|
50
|
+
"pluginApi": ">=2026.6.8-beta.1"
|
|
51
51
|
},
|
|
52
52
|
"build": {
|
|
53
|
-
"openclawVersion": "2026.6.
|
|
53
|
+
"openclawVersion": "2026.6.8-beta.1"
|
|
54
54
|
},
|
|
55
55
|
"release": {
|
|
56
56
|
"publishToClawHub": true,
|