@openclaw/feishu 2026.9.2 → 2026.9.4
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 +110 -122
- package/dist/{channel-VytDDz_B.js → channel-HDnQAH4Z.js} +24 -48
- package/dist/channel-plugin-api.js +1 -1
- package/dist/{channel.runtime-CAD74kBO.js → channel.runtime-B-f6ajOp.js} +3 -3
- package/dist/{chat-DDhIcPQy.js → chat-zhIPXu4X.js} +88 -87
- package/dist/{monitor-rPJ_Mz4M.js → monitor-EuNsFn2T.js} +1 -1
- package/dist/{monitor.account-v0XIDyfs.js → monitor.account-CZ7EtqYS.js} +67 -118
- package/dist/{reply-delivery-result-CYaKkbUN.js → reply-delivery-result-C3smdVzb.js} +117 -53
- package/dist/setup-api.js +1 -1
- package/openclaw.plugin.json +4 -0
- package/package.json +4 -4
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { G as feishuExternalToolResult, X as resolveFeishuToolAccount, Y as resolveAnyEnabledFeishuToolsConfig, kt as resolveFeishuChatType, mt as authorizeFeishuChatMemberRead, pt as assertFeishuChatReadAllowed, tt as formatFeishuApiError, yt as resolveFeishuChatReadPreliminaryAuthorization } from "./reply-delivery-result-C3smdVzb.js";
|
|
2
2
|
import { r as createFeishuClient } from "./client-DMbIL3UH.js";
|
|
3
3
|
import { optionalPositiveIntegerSchema } from "openclaw/plugin-sdk/channel-actions";
|
|
4
4
|
import { readPositiveIntegerParam } from "openclaw/plugin-sdk/param-readers";
|
|
@@ -189,96 +189,97 @@ async function getFeishuMemberInfo(client, memberId, memberIdType = "open_id") {
|
|
|
189
189
|
};
|
|
190
190
|
}
|
|
191
191
|
function registerFeishuChatTools(api) {
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
}
|
|
212
|
-
});
|
|
213
|
-
const client = createFeishuClient(account);
|
|
214
|
-
switch (p.action) {
|
|
215
|
-
case "members":
|
|
216
|
-
if (!p.chat_id) return feishuExternalToolResult({ error: "chat_id is required for action members" });
|
|
217
|
-
{
|
|
218
|
-
const chat = await getAuthorizedFeishuChatInfo({
|
|
219
|
-
client,
|
|
220
|
-
cfg,
|
|
221
|
-
account,
|
|
222
|
-
chatId: p.chat_id,
|
|
223
|
-
ctx: toolContext
|
|
224
|
-
});
|
|
225
|
-
const authorization = authorizeFeishuChatMemberRead({
|
|
226
|
-
cfg,
|
|
227
|
-
account,
|
|
228
|
-
chatId: p.chat_id,
|
|
229
|
-
chatType: resolveFeishuChatType(chat),
|
|
230
|
-
ctx: toolContext,
|
|
231
|
-
memberIdType: p.member_id_type
|
|
232
|
-
});
|
|
233
|
-
if (authorization.kind === "direct") return feishuExternalToolResult(buildFeishuDirectChatMembers(authorization));
|
|
234
|
-
}
|
|
235
|
-
return feishuExternalToolResult(await getChatMembers(client, p.chat_id, readChatPageSize(rawParams), p.page_token, p.member_id_type));
|
|
236
|
-
case "info":
|
|
237
|
-
if (!p.chat_id) return feishuExternalToolResult({ error: "chat_id is required for action info" });
|
|
238
|
-
{
|
|
239
|
-
const chat = await getAuthorizedFeishuChatInfo({
|
|
240
|
-
client,
|
|
241
|
-
cfg,
|
|
242
|
-
account,
|
|
243
|
-
chatId: p.chat_id,
|
|
244
|
-
ctx: toolContext
|
|
245
|
-
});
|
|
246
|
-
return feishuExternalToolResult(chat);
|
|
192
|
+
api.registerTool((toolContext) => {
|
|
193
|
+
const cfg = toolContext.runtimeConfig ?? toolContext.config ?? api.config;
|
|
194
|
+
if (!cfg || !resolveAnyEnabledFeishuToolsConfig(cfg).chat) return null;
|
|
195
|
+
return {
|
|
196
|
+
name: "feishu_chat",
|
|
197
|
+
resultContentSource: "network",
|
|
198
|
+
label: "Feishu Chat",
|
|
199
|
+
description: "Feishu chat operations. Actions: members, info, member_info",
|
|
200
|
+
parameters: FeishuChatSchema,
|
|
201
|
+
async execute(_toolCallId, params) {
|
|
202
|
+
const rawParams = params;
|
|
203
|
+
const p = params;
|
|
204
|
+
try {
|
|
205
|
+
const account = resolveFeishuToolAccount({
|
|
206
|
+
cfg,
|
|
207
|
+
defaultAccountId: toolContext.agentAccountId,
|
|
208
|
+
requiredTool: {
|
|
209
|
+
family: "chat",
|
|
210
|
+
label: "chat"
|
|
247
211
|
}
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
await assertFeishuChatMember(client, p.chat_id, p.member_id, memberIdType);
|
|
271
|
-
return feishuExternalToolResult(await getFeishuMemberInfo(client, p.member_id, memberIdType));
|
|
212
|
+
});
|
|
213
|
+
const client = createFeishuClient(account);
|
|
214
|
+
switch (p.action) {
|
|
215
|
+
case "members":
|
|
216
|
+
if (!p.chat_id) return feishuExternalToolResult({ error: "chat_id is required for action members" });
|
|
217
|
+
{
|
|
218
|
+
const chat = await getAuthorizedFeishuChatInfo({
|
|
219
|
+
client,
|
|
220
|
+
cfg,
|
|
221
|
+
account,
|
|
222
|
+
chatId: p.chat_id,
|
|
223
|
+
ctx: toolContext
|
|
224
|
+
});
|
|
225
|
+
const authorization = authorizeFeishuChatMemberRead({
|
|
226
|
+
cfg,
|
|
227
|
+
account,
|
|
228
|
+
chatId: p.chat_id,
|
|
229
|
+
chatType: resolveFeishuChatType(chat),
|
|
230
|
+
ctx: toolContext,
|
|
231
|
+
memberIdType: p.member_id_type
|
|
232
|
+
});
|
|
233
|
+
if (authorization.kind === "direct") return feishuExternalToolResult(buildFeishuDirectChatMembers(authorization));
|
|
272
234
|
}
|
|
273
|
-
return feishuExternalToolResult(await
|
|
274
|
-
|
|
275
|
-
|
|
235
|
+
return feishuExternalToolResult(await getChatMembers(client, p.chat_id, readChatPageSize(rawParams), p.page_token, p.member_id_type));
|
|
236
|
+
case "info":
|
|
237
|
+
if (!p.chat_id) return feishuExternalToolResult({ error: "chat_id is required for action info" });
|
|
238
|
+
{
|
|
239
|
+
const chat = await getAuthorizedFeishuChatInfo({
|
|
240
|
+
client,
|
|
241
|
+
cfg,
|
|
242
|
+
account,
|
|
243
|
+
chatId: p.chat_id,
|
|
244
|
+
ctx: toolContext
|
|
245
|
+
});
|
|
246
|
+
return feishuExternalToolResult(chat);
|
|
247
|
+
}
|
|
248
|
+
case "member_info":
|
|
249
|
+
if (!p.member_id) return feishuExternalToolResult({ error: "member_id is required for action member_info" });
|
|
250
|
+
if (!p.chat_id) return feishuExternalToolResult({ error: "chat_id is required for action member_info" });
|
|
251
|
+
{
|
|
252
|
+
const chat = await getAuthorizedFeishuChatInfo({
|
|
253
|
+
client,
|
|
254
|
+
cfg,
|
|
255
|
+
account,
|
|
256
|
+
chatId: p.chat_id,
|
|
257
|
+
ctx: toolContext
|
|
258
|
+
});
|
|
259
|
+
const authorization = authorizeFeishuChatMemberRead({
|
|
260
|
+
cfg,
|
|
261
|
+
account,
|
|
262
|
+
chatId: p.chat_id,
|
|
263
|
+
chatType: resolveFeishuChatType(chat),
|
|
264
|
+
ctx: toolContext,
|
|
265
|
+
memberId: p.member_id,
|
|
266
|
+
memberIdType: p.member_id_type
|
|
267
|
+
});
|
|
268
|
+
if (authorization.kind === "group") {
|
|
269
|
+
const memberIdType = p.member_id_type ?? "open_id";
|
|
270
|
+
await assertFeishuChatMember(client, p.chat_id, p.member_id, memberIdType);
|
|
271
|
+
return feishuExternalToolResult(await getFeishuMemberInfo(client, p.member_id, memberIdType));
|
|
272
|
+
}
|
|
273
|
+
return feishuExternalToolResult(await getFeishuMemberInfo(client, authorization.memberId, authorization.memberIdType));
|
|
274
|
+
}
|
|
275
|
+
default: return feishuExternalToolResult({ error: `Unknown action: ${String(p.action)}` });
|
|
276
|
+
}
|
|
277
|
+
} catch (err) {
|
|
278
|
+
return feishuExternalToolResult({ error: formatFeishuApiError(err, { includeNestedErrorLogId: true }) });
|
|
276
279
|
}
|
|
277
|
-
} catch (err) {
|
|
278
|
-
return feishuExternalToolResult({ error: formatFeishuApiError(err, { includeNestedErrorLogId: true }) });
|
|
279
280
|
}
|
|
280
|
-
}
|
|
281
|
-
}
|
|
281
|
+
};
|
|
282
|
+
}, { name: "feishu_chat" });
|
|
282
283
|
}
|
|
283
284
|
//#endregion
|
|
284
285
|
export { getFeishuMemberInfo as a, getChatMembers as i, buildFeishuDirectChatMembers as n, registerFeishuChatTools as o, getChatInfo as r, assertFeishuChatMember as t };
|
|
@@ -143,7 +143,7 @@ async function fetchBotIdentityForMonitor(account, options = {}) {
|
|
|
143
143
|
}
|
|
144
144
|
//#endregion
|
|
145
145
|
//#region extensions/feishu/src/monitor.ts
|
|
146
|
-
const loadMonitorAccountRuntime = createLazyRuntimeModule(() => import("./monitor.account-
|
|
146
|
+
const loadMonitorAccountRuntime = createLazyRuntimeModule(() => import("./monitor.account-CZ7EtqYS.js"));
|
|
147
147
|
async function monitorFeishuProvider(opts = {}) {
|
|
148
148
|
const cfg = opts.config;
|
|
149
149
|
if (!cfg) throw new Error("Config is required for Feishu monitor");
|
|
@@ -1,16 +1,16 @@
|
|
|
1
1
|
import { o as resolveFeishuAccount, s as resolveFeishuRuntimeAccount } from "./accounts-cCMNFBKg.js";
|
|
2
2
|
import { i as resolveReceiveIdType } from "./targets-BTQCYRZQ.js";
|
|
3
|
-
import { $ as
|
|
3
|
+
import { $ as encodeQuery, A as sendStructuredCardFeishu, B as buildFeishuMediaFallbackText, C as shouldSuppressFeishuTextForVoiceMedia, Ct as resolveFeishuGroupConfig, D as listFeishuThreadMessages, Dt as resolveFeishuReplyPolicy, E as getMessageFeishu, F as isFeishuGroupChatType, I as parseInteractiveCardContent, L as parsePostContent, M as extractMentionTargets, Mt as createFeishuCardInteractionEnvelope, N as isFeishuBroadcastMention, Nt as decodeFeishuCardAction, O as sendCardFeishu, Ot as normalizeFeishuChatType$2, P as isMentionForwardRequest, Q as createCommentTypingReactionLifecycle, St as resolveFeishuDmIngressAccess, Tt as resolveFeishuGroupSenderActivationIngressAccess, U as deliverCommentThreadText, V as resolveFeishuIdentityEmoji, _ as renderFeishuReplyPayload, at as normalizeCommentFileType, b as saveMessageResourceFeishu, bt as hasExplicitFeishuGroupConfig, ct as resolveFeishuCardTemplate, dt as materializeFeishuPostMarkdownSoftBreaks, et as extractReplyText, i as noVisibleFeishuReplyDelivery, it as buildFeishuCommentTarget, j as formatFeishuMediaContent, jt as buildFeishuCardActionTextFallback, k as sendMessageFeishu, kt as resolveFeishuChatType, l as buildFeishuPresentationFallback, n as createFeishuReplyDeliveryResult, nt as parseCommentContentElements, r as mergeFeishuReplyDeliveryResults, rt as requestFeishuApi, t as createFeishuPartialReplyDeliveryError, u as consumeFeishuPresentationFallbackMarker, ut as chunkFeishuPostMarkdown, v as resolveFeishuRichReply, w as chunkFeishuCardMarkdown, wt as resolveFeishuGroupConversationIngressAccess, x as sendMediaFeishu, xt as normalizeFeishuAllowEntry, y as withinCardTableLimit } from "./reply-delivery-result-C3smdVzb.js";
|
|
4
4
|
import { a as normalizeFeishuWebhookPath, o as normalizeFeishuExternalKey } from "./doctor-contract-w6FpMypv.js";
|
|
5
5
|
import { o as resolveConfiguredFeishuGroupSessionScope, t as buildFeishuConversationId } from "./conversation-id-VYgGQ-GX.js";
|
|
6
6
|
import { a as getFeishuUserAgent, i as createFeishuWSClient, n as createEventDispatcher, r as createFeishuClient, s as resolveConfiguredHttpTimeoutMs } from "./client-DMbIL3UH.js";
|
|
7
7
|
import { t as getFeishuRuntime } from "./runtime-C5JxBWZp.js";
|
|
8
|
-
import { r as getChatInfo } from "./chat-
|
|
8
|
+
import { r as getChatInfo } from "./chat-zhIPXu4X.js";
|
|
9
9
|
import { t as createFeishuThreadBindingManager } from "./thread-bindings-BiL1wGqK.js";
|
|
10
10
|
import { evaluateSupplementalContextVisibility, normalizeAgentId as normalizeAgentId$1, resolveChannelContextVisibilityMode } from "./runtime-api.js";
|
|
11
11
|
import { t as readFeishuJsonResponse } from "./json-response-DXajw_FU.js";
|
|
12
12
|
import { a as waitForAbortableDelay, i as raceWithTimeoutAndAbort } from "./probe-D_SatdZl.js";
|
|
13
|
-
import { t as fetchBotIdentityForMonitor } from "./monitor-
|
|
13
|
+
import { t as fetchBotIdentityForMonitor } from "./monitor-EuNsFn2T.js";
|
|
14
14
|
import { resolveAgentConfig } from "openclaw/plugin-sdk/agent-scope-runtime";
|
|
15
15
|
import { DEFAULT_INGRESS_ADOPTION_STALL_MS, bindIngressLifecycleToReplyOptions, createChannelIngressError, createChannelIngressMonitor, createChannelMessageReplyPipeline, createReplyPrefixContext, formatChannelProgressDraftLineForEntry, isChannelProgressDraftWorkToolName, resolveAgentOutboundIdentity, resolveChannelPreviewStreamMode, resolveChannelStreamingBlockEnabled } from "openclaw/plugin-sdk/channel-outbound";
|
|
16
16
|
import { createChannelPairingController } from "openclaw/plugin-sdk/channel-pairing";
|
|
@@ -32,11 +32,11 @@ import { resolvePinnedMainDmOwnerFromAllowlist, safeEqualSecret } from "openclaw
|
|
|
32
32
|
import * as Lark from "@larksuiteoapi/node-sdk";
|
|
33
33
|
import { isRecord as isRecord$1 } from "openclaw/plugin-sdk/channel-secret-basic-runtime";
|
|
34
34
|
import { fetchWithSsrFGuard } from "openclaw/plugin-sdk/ssrf-runtime";
|
|
35
|
+
import { sliceUtf16Safe, truncateUtf16Safe } from "openclaw/plugin-sdk/text-utility-runtime";
|
|
35
36
|
import { WEBHOOK_ANOMALY_COUNTER_DEFAULTS, WEBHOOK_RATE_LIMIT_DEFAULTS, applyBasicWebhookRequestGuards, createFixedWindowRateLimiter, createWebhookAnomalyTracker, resolveRequestClientIp } from "openclaw/plugin-sdk/webhook-ingress";
|
|
36
37
|
import * as crypto$1 from "node:crypto";
|
|
37
38
|
import crypto, { createHash } from "node:crypto";
|
|
38
39
|
import { DEFAULT_GROUP_HISTORY_LIMIT, createChannelHistoryWindow } from "openclaw/plugin-sdk/reply-history";
|
|
39
|
-
import { escapeHtml, sliceUtf16Safe, truncateUtf16Safe } from "openclaw/plugin-sdk/text-utility-runtime";
|
|
40
40
|
import { pruneMapToMaxSize } from "openclaw/plugin-sdk/collection-runtime";
|
|
41
41
|
import { createChannelReplayGuard } from "openclaw/plugin-sdk/persistent-dedupe";
|
|
42
42
|
import { resolveChannelConfigWrites } from "openclaw/plugin-sdk/channel-config-writes";
|
|
@@ -337,56 +337,6 @@ const FEISHU_MEDIA_MESSAGE_TYPES = /* @__PURE__ */ new Set([
|
|
|
337
337
|
"media",
|
|
338
338
|
"sticker"
|
|
339
339
|
]);
|
|
340
|
-
function formatFeishuMediaContent(parsed, messageType) {
|
|
341
|
-
if (messageType === "sticker") {
|
|
342
|
-
const fileKey = normalizeFeishuExternalKey(parsed?.file_key);
|
|
343
|
-
return fileKey ? `<sticker key="${escapeHtml(fileKey)}"/>` : "[Sticker]";
|
|
344
|
-
}
|
|
345
|
-
const speechToText = messageType === "audio" && typeof parsed.speech_to_text === "string" ? parsed.speech_to_text.trim() : "";
|
|
346
|
-
if (speechToText) return speechToText;
|
|
347
|
-
return "";
|
|
348
|
-
}
|
|
349
|
-
function formatSubMessageContent(content, contentType) {
|
|
350
|
-
try {
|
|
351
|
-
const parsed = JSON.parse(content);
|
|
352
|
-
switch (contentType) {
|
|
353
|
-
case "text": return parsed.text || content;
|
|
354
|
-
case "post": return parsePostContent(content).textContent;
|
|
355
|
-
case "interactive": return parseInteractiveCardContent(parsed);
|
|
356
|
-
case "image": return "[Image]";
|
|
357
|
-
case "file": return `[File: ${parsed.file_name || "unknown"}]`;
|
|
358
|
-
case "audio": return "[Audio]";
|
|
359
|
-
case "video": return "[Video]";
|
|
360
|
-
case "sticker": return formatFeishuMediaContent(parsed, contentType);
|
|
361
|
-
case "merge_forward": return "[Nested Merged Forward]";
|
|
362
|
-
default: return `[${contentType}]`;
|
|
363
|
-
}
|
|
364
|
-
} catch {
|
|
365
|
-
return content;
|
|
366
|
-
}
|
|
367
|
-
}
|
|
368
|
-
function parseMergeForwardContent(params) {
|
|
369
|
-
const { content, log } = params;
|
|
370
|
-
const maxMessages = 50;
|
|
371
|
-
log?.("feishu: parsing merge_forward sub-messages from API response");
|
|
372
|
-
let items;
|
|
373
|
-
try {
|
|
374
|
-
items = JSON.parse(content);
|
|
375
|
-
} catch {
|
|
376
|
-
log?.("feishu: merge_forward items parse failed");
|
|
377
|
-
return "[Merged and Forwarded Message - parse error]";
|
|
378
|
-
}
|
|
379
|
-
if (!Array.isArray(items) || items.length === 0) return "[Merged and Forwarded Message - no sub-messages]";
|
|
380
|
-
const container = items.find((item) => item.msg_type === "merge_forward" && !item.upper_message_id);
|
|
381
|
-
const subMessages = container ? items.filter((item) => item !== container) : items.filter((item) => item.upper_message_id);
|
|
382
|
-
if (subMessages.length === 0) return "[Merged and Forwarded Message - no sub-messages found]";
|
|
383
|
-
log?.(`feishu: merge_forward contains ${subMessages.length} sub-messages`);
|
|
384
|
-
subMessages.sort((a, b) => (parseStrictNonNegativeInteger(a.create_time) ?? 0) - (parseStrictNonNegativeInteger(b.create_time) ?? 0));
|
|
385
|
-
const lines = ["[Merged and Forwarded Messages]"];
|
|
386
|
-
for (const item of subMessages.slice(0, maxMessages)) lines.push(`- ${formatSubMessageContent(item.body?.content || "", item.msg_type || "text")}`);
|
|
387
|
-
if (subMessages.length > maxMessages) lines.push(`... and ${subMessages.length - maxMessages} more messages`);
|
|
388
|
-
return lines.join("\n");
|
|
389
|
-
}
|
|
390
340
|
function checkBotMentioned(event, botOpenId) {
|
|
391
341
|
if (!botOpenId) return false;
|
|
392
342
|
const mentions = event.message.mentions ?? [];
|
|
@@ -398,13 +348,14 @@ function normalizeMentions(text, mentions, botStripId) {
|
|
|
398
348
|
if (!mentions || mentions.length === 0) return text;
|
|
399
349
|
const escaped = (value) => value.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
400
350
|
const escapeName = (value) => value.replace(/</g, "<").replace(/>/g, ">");
|
|
401
|
-
|
|
351
|
+
const replacements = /* @__PURE__ */ new Map();
|
|
402
352
|
for (const mention of mentions) {
|
|
403
353
|
const mentionId = mention.id.open_id;
|
|
404
354
|
const replacement = botStripId && mentionId === botStripId ? "" : mentionId ? `<at user_id="${mentionId}">${escapeName(mention.name)}</at>` : `@${mention.name}`;
|
|
405
|
-
|
|
355
|
+
replacements.set(mention.key, replacement);
|
|
406
356
|
}
|
|
407
|
-
|
|
357
|
+
const keys = [...replacements.keys()].toSorted((a, b) => b.length - a.length).map(escaped);
|
|
358
|
+
return text.replace(new RegExp(keys.join("|"), "g"), (key) => replacements.get(key)).trim();
|
|
408
359
|
}
|
|
409
360
|
function normalizeFeishuCommandProbeBody(text) {
|
|
410
361
|
if (!text) return "";
|
|
@@ -440,12 +391,6 @@ function parseMediaKeys(content, messageType) {
|
|
|
440
391
|
function toMessageResourceType(messageType) {
|
|
441
392
|
return messageType === "image" ? "image" : "file";
|
|
442
393
|
}
|
|
443
|
-
async function resolveSavedFeishuMedia(params) {
|
|
444
|
-
if ("saved" in params.result) return params.result.saved;
|
|
445
|
-
const core = getFeishuRuntime();
|
|
446
|
-
const contentType = params.result.contentType ?? await core.media.detectMime({ buffer: params.result.buffer });
|
|
447
|
-
return await core.channel.media.saveMediaBuffer(params.result.buffer, contentType, "inbound", params.maxBytes, params.result.fileName ?? params.originalFilename);
|
|
448
|
-
}
|
|
449
394
|
function resolveFeishuMediaKind(messageType) {
|
|
450
395
|
switch (messageType) {
|
|
451
396
|
case "image": return "image";
|
|
@@ -479,16 +424,12 @@ async function resolveFeishuMediaList(params) {
|
|
|
479
424
|
const fileName = attachment.kind === "file" ? attachment.fileName : void 0;
|
|
480
425
|
const mediaKind = attachment.kind === "image" ? "image" : "video";
|
|
481
426
|
try {
|
|
482
|
-
const saved = await
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
accountId,
|
|
489
|
-
maxBytes,
|
|
490
|
-
...fileName ? { originalFilename: fileName } : {}
|
|
491
|
-
}),
|
|
427
|
+
const { saved } = await saveMessageResourceFeishu({
|
|
428
|
+
cfg,
|
|
429
|
+
messageId,
|
|
430
|
+
fileKey: attachment.key,
|
|
431
|
+
type: attachment.kind,
|
|
432
|
+
accountId,
|
|
492
433
|
maxBytes,
|
|
493
434
|
...fileName ? { originalFilename: fileName } : {}
|
|
494
435
|
});
|
|
@@ -510,16 +451,12 @@ async function resolveFeishuMediaList(params) {
|
|
|
510
451
|
try {
|
|
511
452
|
const fileKey = mediaKeys.fileKey || mediaKeys.imageKey;
|
|
512
453
|
if (!fileKey) return [{ kind: resolveFeishuMediaKind(messageType) }];
|
|
513
|
-
const saved = await
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
accountId,
|
|
520
|
-
maxBytes,
|
|
521
|
-
originalFilename: mediaKeys.fileName
|
|
522
|
-
}),
|
|
454
|
+
const { saved } = await saveMessageResourceFeishu({
|
|
455
|
+
cfg,
|
|
456
|
+
messageId,
|
|
457
|
+
fileKey,
|
|
458
|
+
type: toMessageResourceType(messageType),
|
|
459
|
+
accountId,
|
|
523
460
|
maxBytes,
|
|
524
461
|
originalFilename: mediaKeys.fileName
|
|
525
462
|
});
|
|
@@ -2288,14 +2225,15 @@ function createFeishuReplyDispatcher(params) {
|
|
|
2288
2225
|
if (result?.visibleReplySent === true || !content?.trim()) return result;
|
|
2289
2226
|
const cardHeader = resolveCardHeader(agentId, identity);
|
|
2290
2227
|
const cardNote = resolveCardNote(agentId, identity, responsePrefixContextProvider());
|
|
2228
|
+
const useRecoveryCard = withinCardTableLimit(content);
|
|
2291
2229
|
return await sendChunkedTextReply({
|
|
2292
2230
|
text: content,
|
|
2293
|
-
useCard:
|
|
2231
|
+
useCard: useRecoveryCard,
|
|
2294
2232
|
infoKind,
|
|
2295
2233
|
header: cardHeader,
|
|
2296
2234
|
note: cardNote,
|
|
2297
2235
|
chunkMentions: requiredMentionTargets,
|
|
2298
|
-
sendChunk: async ({ chunk, mentions }) => await sendStructuredCardFeishu({
|
|
2236
|
+
sendChunk: async ({ chunk, mentions }) => useRecoveryCard ? await sendStructuredCardFeishu({
|
|
2299
2237
|
cfg,
|
|
2300
2238
|
to: sendTarget,
|
|
2301
2239
|
text: chunk,
|
|
@@ -2306,6 +2244,16 @@ function createFeishuReplyDispatcher(params) {
|
|
|
2306
2244
|
header: cardHeader,
|
|
2307
2245
|
note: cardNote,
|
|
2308
2246
|
...mentions ? { mentions } : {}
|
|
2247
|
+
}) : await sendMessageFeishu({
|
|
2248
|
+
cfg,
|
|
2249
|
+
to: sendTarget,
|
|
2250
|
+
text: chunk,
|
|
2251
|
+
preparedPostText: true,
|
|
2252
|
+
replyToMessageId: sendReplyToMessageId,
|
|
2253
|
+
replyInThread: effectiveReplyInThread,
|
|
2254
|
+
allowTopLevelReplyFallback,
|
|
2255
|
+
accountId,
|
|
2256
|
+
...mentions ? { mentions } : {}
|
|
2309
2257
|
})
|
|
2310
2258
|
});
|
|
2311
2259
|
};
|
|
@@ -2483,9 +2431,9 @@ function createFeishuReplyDispatcher(params) {
|
|
|
2483
2431
|
ttsSupplement
|
|
2484
2432
|
}));
|
|
2485
2433
|
const finalTextExceedsStreamingLimit = info?.kind === "final" && hasText && text.length > textChunkLimit;
|
|
2486
|
-
const
|
|
2487
|
-
const
|
|
2488
|
-
const
|
|
2434
|
+
const cardRenderingRequested = renderMode === "card" || info?.kind === "block" && coreBlockStreamingEnabled && renderMode !== "raw" || renderMode === "auto" && shouldUseCard(text);
|
|
2435
|
+
const useStaticCard = hasText && cardRenderingRequested && withinCardTableLimit(text);
|
|
2436
|
+
const useStreamingCard = hasText && streamingEnabled && !finalTextExceedsStreamingLimit && (info?.kind === "final" || cardRenderingRequested);
|
|
2489
2437
|
const skipTextForDuplicateFinal = !hasIndependentPresentation && info?.kind === "final" && hasText && deliveredFinalTexts.has(text);
|
|
2490
2438
|
const shouldDeliverText = hasText && (!hasVoiceMedia || hasPresentationFallback) && !skipTextForDuplicateFinal;
|
|
2491
2439
|
const shouldDiscardStreamingPreview = info?.kind === "final" && !(hasIndependentPresentation && payload.isError === true && hasStreamingFinalText) && (hasIndependentPresentation || finalTextExceedsStreamingLimit || hasMedia && (hasVoiceMedia && !shouldDeliverText && !ttsTextAlreadyVisible || skipTextForDuplicateFinal));
|
|
@@ -2577,7 +2525,7 @@ function createFeishuReplyDispatcher(params) {
|
|
|
2577
2525
|
}
|
|
2578
2526
|
return deferStreamingDelivery(mergeFeishuReplyDeliveryResults(deliveredResults, text), info?.kind, ownerGeneration);
|
|
2579
2527
|
}
|
|
2580
|
-
if (
|
|
2528
|
+
if (useStaticCard || useStreamingCard && !isStreamingStartBackedOff(account.accountId) && withinCardTableLimit(text)) {
|
|
2581
2529
|
const cardHeader = resolveCardHeader(agentId, identity);
|
|
2582
2530
|
const cardNote = resolveCardNote(agentId, identity, responsePrefixContextProvider());
|
|
2583
2531
|
deliveredResults.push(await sendChunkedTextReply({
|
|
@@ -2704,11 +2652,10 @@ async function resolveFeishuAudioTranscript(params) {
|
|
|
2704
2652
|
/**
|
|
2705
2653
|
* Parse an inbound Feishu event into its caption and routing metadata.
|
|
2706
2654
|
*/
|
|
2707
|
-
function parseFeishuMessageEvent(event, botOpenId, _botName) {
|
|
2708
|
-
const rawContent = parseMessageContent(event.message.content, event.message.message_type);
|
|
2655
|
+
function parseFeishuMessageEvent(event, botOpenId, _botName, preparedContent) {
|
|
2709
2656
|
const mentionedBot = checkBotMentioned(event, botOpenId);
|
|
2710
2657
|
const hasAnyMention = (event.message.mentions?.length ?? 0) > 0;
|
|
2711
|
-
const content = normalizeMentions(
|
|
2658
|
+
const content = preparedContent ?? normalizeMentions(parseMessageContent(event.message.content, event.message.message_type), event.message.mentions, botOpenId);
|
|
2712
2659
|
const senderOpenId = event.sender.sender_id.open_id?.trim();
|
|
2713
2660
|
const senderUserId = event.sender.sender_id.user_id?.trim();
|
|
2714
2661
|
const senderFallbackId = senderOpenId || senderUserId || "";
|
|
@@ -2770,7 +2717,8 @@ async function filterFetchedGroupContextMessages(messages, params) {
|
|
|
2770
2717
|
}) ? message : void 0))).filter((message) => message !== void 0);
|
|
2771
2718
|
}
|
|
2772
2719
|
async function handleFeishuMessage(params) {
|
|
2773
|
-
const {
|
|
2720
|
+
const { event, preparedContent, botOpenId, botName, runtime, channelRuntime, chatHistories, accountId, processingClaim, messageDedupeKey: messageDedupeKeyOverride, turnAdoptionLifecycle } = params;
|
|
2721
|
+
const cfg = getFeishuRuntime().config.current();
|
|
2774
2722
|
const account = resolveFeishuRuntimeAccount({
|
|
2775
2723
|
cfg,
|
|
2776
2724
|
accountId
|
|
@@ -2789,7 +2737,7 @@ async function handleFeishuMessage(params) {
|
|
|
2789
2737
|
log(`feishu: skipping duplicate message ${messageId}`);
|
|
2790
2738
|
return;
|
|
2791
2739
|
}
|
|
2792
|
-
let ctx = parseFeishuMessageEvent(event, botOpenId, botName);
|
|
2740
|
+
let ctx = parseFeishuMessageEvent(event, botOpenId, botName, preparedContent);
|
|
2793
2741
|
const isGroup = isFeishuGroupChatType(ctx.chatType);
|
|
2794
2742
|
const isDirect = !isGroup;
|
|
2795
2743
|
const directPreDispatchTarget = isDirect ? getFeishuSyntheticDirectPreDispatchTarget(event) : void 0;
|
|
@@ -2839,7 +2787,7 @@ async function handleFeishuMessage(params) {
|
|
|
2839
2787
|
log(`feishu[${account.accountId}]: dropping bot message ${ctx.messageId} (local mention not verifiable)`);
|
|
2840
2788
|
return;
|
|
2841
2789
|
}
|
|
2842
|
-
const deliveredCtx = parseFeishuMessageEvent(verifiedEvent, localBotOpenId, botName);
|
|
2790
|
+
const deliveredCtx = parseFeishuMessageEvent(verifiedEvent, localBotOpenId, botName, preparedContent);
|
|
2843
2791
|
ctx = {
|
|
2844
2792
|
...deliveredCtx,
|
|
2845
2793
|
mentionedBot: true,
|
|
@@ -2851,22 +2799,17 @@ async function handleFeishuMessage(params) {
|
|
|
2851
2799
|
if (event.message.message_type === "merge_forward") {
|
|
2852
2800
|
log(`feishu[${account.accountId}]: processing merge_forward message, fetching full content via API`);
|
|
2853
2801
|
try {
|
|
2854
|
-
const
|
|
2855
|
-
|
|
2856
|
-
|
|
2802
|
+
const messageInfo = await getMessageFeishu({
|
|
2803
|
+
cfg,
|
|
2804
|
+
messageId: event.message.message_id,
|
|
2805
|
+
accountId: account.accountId
|
|
2857
2806
|
});
|
|
2858
|
-
if (
|
|
2859
|
-
|
|
2860
|
-
|
|
2861
|
-
|
|
2862
|
-
|
|
2863
|
-
});
|
|
2864
|
-
ctx = {
|
|
2865
|
-
...ctx,
|
|
2866
|
-
content: expandedContent
|
|
2867
|
-
};
|
|
2868
|
-
} else {
|
|
2869
|
-
log(`feishu[${account.accountId}]: merge_forward API returned no items`);
|
|
2807
|
+
if (messageInfo) ctx = {
|
|
2808
|
+
...ctx,
|
|
2809
|
+
content: messageInfo.content
|
|
2810
|
+
};
|
|
2811
|
+
else {
|
|
2812
|
+
log(`feishu[${account.accountId}]: merge_forward message retrieval returned no result`);
|
|
2870
2813
|
ctx = {
|
|
2871
2814
|
...ctx,
|
|
2872
2815
|
content: "[Merged and Forwarded Message - could not fetch]"
|
|
@@ -6140,10 +6083,11 @@ function createFeishuMessageReceiveHandler({ cfg, channelRuntime, accountId, run
|
|
|
6140
6083
|
const enqueue = createSequentialQueue({ onTaskTimeout: (key, timeoutMs) => {
|
|
6141
6084
|
log(`feishu[${accountId}]: per-chat task exceeded ${timeoutMs}ms cap (key=${key}); evicting from queue so later same-key messages can proceed (#70133)`);
|
|
6142
6085
|
} });
|
|
6143
|
-
const dispatchFeishuMessage = async (event, messageDedupeKey, processingClaim, turnAdoptionLifecycle) => {
|
|
6086
|
+
const dispatchFeishuMessage = async (event, messageDedupeKey, processingClaim, turnAdoptionLifecycle, preparedContent) => {
|
|
6144
6087
|
const sequentialKey = resolveSequentialKey({
|
|
6145
6088
|
accountId,
|
|
6146
6089
|
event,
|
|
6090
|
+
preparedContent,
|
|
6147
6091
|
botOpenId: getBotOpenId(accountId),
|
|
6148
6092
|
botName: getBotName(accountId)
|
|
6149
6093
|
});
|
|
@@ -6155,6 +6099,7 @@ function createFeishuMessageReceiveHandler({ cfg, channelRuntime, accountId, run
|
|
|
6155
6099
|
await handleMessage({
|
|
6156
6100
|
cfg,
|
|
6157
6101
|
event,
|
|
6102
|
+
preparedContent,
|
|
6158
6103
|
botOpenId: getBotOpenId(accountId),
|
|
6159
6104
|
botName: getBotName(accountId),
|
|
6160
6105
|
runtime,
|
|
@@ -6245,13 +6190,9 @@ function createFeishuMessageReceiveHandler({ cfg, channelRuntime, accountId, run
|
|
|
6245
6190
|
...dispatchEntry.event,
|
|
6246
6191
|
message: {
|
|
6247
6192
|
...dispatchEntry.event.message,
|
|
6248
|
-
...combinedText.trim() ? {
|
|
6249
|
-
message_type: "text",
|
|
6250
|
-
content: JSON.stringify({ text: combinedText })
|
|
6251
|
-
} : {},
|
|
6252
6193
|
mentions: mergedMentions ?? dispatchEntry.event.message.mentions
|
|
6253
6194
|
}
|
|
6254
|
-
}, dispatchDedupeKey, dispatchEntry.processingClaim, admissionLifecycle);
|
|
6195
|
+
}, dispatchDedupeKey, dispatchEntry.processingClaim, admissionLifecycle, combinedText);
|
|
6255
6196
|
await settle();
|
|
6256
6197
|
} catch (err) {
|
|
6257
6198
|
await admissionLifecycle.onAbandoned();
|
|
@@ -6357,6 +6298,7 @@ function buildFeishuWebhookRateLimitKey(params) {
|
|
|
6357
6298
|
//#region extensions/feishu/src/monitor.transport.ts
|
|
6358
6299
|
const FEISHU_WEBHOOK_ACCEPTED_HEADER = "x-openclaw-delivery-accepted";
|
|
6359
6300
|
const FEISHU_WEBHOOK_ACCEPTED_VALUE = "durable";
|
|
6301
|
+
const FEISHU_WEBHOOK_TIMESTAMP_MAX_SKEW_MS = 36e5;
|
|
6360
6302
|
const FEISHU_WS_RECONNECT_INITIAL_DELAY_MS = 1e3;
|
|
6361
6303
|
const FEISHU_WS_RECONNECT_MAX_DELAY_MS = 3e4;
|
|
6362
6304
|
const FEISHU_WS_LOG_ERROR_MAX_LENGTH = 500;
|
|
@@ -6384,6 +6326,12 @@ function parseFeishuWebhookPayload(rawBody) {
|
|
|
6384
6326
|
return null;
|
|
6385
6327
|
}
|
|
6386
6328
|
}
|
|
6329
|
+
function isFeishuWebhookTimestampFresh(timestamp) {
|
|
6330
|
+
const parsed = Number.parseInt(timestamp, 10);
|
|
6331
|
+
if (!Number.isFinite(parsed)) return false;
|
|
6332
|
+
const timestampMs = parsed < 0xe8d4a51000 ? parsed * 1e3 : parsed;
|
|
6333
|
+
return Math.abs(Date.now() - timestampMs) <= FEISHU_WEBHOOK_TIMESTAMP_MAX_SKEW_MS;
|
|
6334
|
+
}
|
|
6387
6335
|
function isFeishuWebhookSignatureValid(params) {
|
|
6388
6336
|
const encryptKey = params.encryptKey?.trim();
|
|
6389
6337
|
if (!encryptKey) return false;
|
|
@@ -6394,6 +6342,7 @@ function isFeishuWebhookSignatureValid(params) {
|
|
|
6394
6342
|
const nonce = Array.isArray(nonceHeader) ? nonceHeader[0] : nonceHeader;
|
|
6395
6343
|
const signature = Array.isArray(signatureHeader) ? signatureHeader[0] : signatureHeader;
|
|
6396
6344
|
if (!timestamp || !nonce || !signature) return false;
|
|
6345
|
+
if (!isFeishuWebhookTimestampFresh(timestamp)) return false;
|
|
6397
6346
|
const computedSignature = crypto.createHash("sha256").update(timestamp + nonce + encryptKey + params.rawBody).digest("hex");
|
|
6398
6347
|
return safeEqualSecret(computedSignature, signature);
|
|
6399
6348
|
}
|
|
@@ -6822,9 +6771,9 @@ function createFeishuVcMeetingInvitedHandler(params) {
|
|
|
6822
6771
|
//#endregion
|
|
6823
6772
|
//#region extensions/feishu/src/sequential-key.ts
|
|
6824
6773
|
function getFeishuSequentialKey(params) {
|
|
6825
|
-
const { accountId, event, botOpenId, botName } = params;
|
|
6774
|
+
const { accountId, event, botOpenId, botName, preparedContent } = params;
|
|
6826
6775
|
const baseKey = `feishu:${accountId}:${event.message.chat_id?.trim() || "unknown"}`;
|
|
6827
|
-
const text = parseFeishuMessageEvent(event, botOpenId, botName).content.trim();
|
|
6776
|
+
const text = (preparedContent ?? parseFeishuMessageEvent(event, botOpenId, botName).content).trim();
|
|
6828
6777
|
if (isAbortRequestText(text)) return `${baseKey}:control`;
|
|
6829
6778
|
if (isBtwRequestText(text)) return `${baseKey}:btw`;
|
|
6830
6779
|
return baseKey;
|