@openclaw/feishu 2026.3.1 → 2026.3.7
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/index.ts +2 -2
- package/package.json +1 -1
- package/src/accounts.test.ts +268 -11
- package/src/accounts.ts +101 -14
- package/src/bitable.ts +40 -28
- package/src/bot.checkBotMentioned.test.ts +9 -1
- package/src/bot.stripBotMention.test.ts +118 -22
- package/src/bot.test.ts +945 -77
- package/src/bot.ts +492 -165
- package/src/card-action.ts +1 -1
- package/src/channel.test.ts +1 -1
- package/src/channel.ts +72 -68
- package/src/chat.test.ts +2 -2
- package/src/chat.ts +1 -1
- package/src/client.test.ts +221 -4
- package/src/client.ts +70 -5
- package/src/config-schema.test.ts +33 -6
- package/src/config-schema.ts +18 -10
- package/src/dedup.ts +47 -1
- package/src/directory.test.ts +40 -0
- package/src/directory.ts +29 -50
- package/src/doc-schema.ts +16 -22
- package/src/docx-batch-insert.test.ts +90 -0
- package/src/docx-batch-insert.ts +8 -11
- package/src/docx.account-selection.test.ts +10 -16
- package/src/docx.test.ts +41 -189
- package/src/docx.ts +1 -1
- package/src/drive.ts +13 -17
- package/src/dynamic-agent.ts +1 -1
- package/src/feishu-command-handler.ts +59 -0
- package/src/media.test.ts +164 -14
- package/src/media.ts +44 -10
- package/src/mention.ts +1 -1
- package/src/monitor.account.ts +284 -25
- package/src/monitor.reaction.test.ts +395 -46
- package/src/monitor.startup.test.ts +25 -8
- package/src/monitor.startup.ts +20 -7
- package/src/monitor.state.defaults.test.ts +46 -0
- package/src/monitor.state.ts +88 -9
- package/src/monitor.test-mocks.ts +45 -0
- package/src/monitor.transport.ts +4 -1
- package/src/monitor.ts +4 -4
- package/src/monitor.webhook-security.test.ts +13 -11
- package/src/onboarding.status.test.ts +25 -0
- package/src/onboarding.test.ts +143 -0
- package/src/onboarding.ts +213 -106
- package/src/outbound.test.ts +178 -0
- package/src/outbound.ts +39 -6
- package/src/perm.ts +11 -15
- package/src/policy.test.ts +40 -0
- package/src/policy.ts +9 -10
- package/src/probe.test.ts +54 -36
- package/src/probe.ts +57 -37
- package/src/reactions.ts +1 -1
- package/src/reply-dispatcher.test.ts +216 -0
- package/src/reply-dispatcher.ts +89 -22
- package/src/runtime.ts +1 -1
- package/src/secret-input.ts +13 -0
- package/src/send-message.ts +71 -0
- package/src/send-target.test.ts +74 -0
- package/src/send-target.ts +7 -3
- package/src/send.reply-fallback.test.ts +74 -0
- package/src/send.test.ts +1 -1
- package/src/send.ts +88 -49
- package/src/streaming-card.test.ts +54 -0
- package/src/streaming-card.ts +96 -28
- package/src/targets.test.ts +29 -0
- package/src/targets.ts +25 -1
- package/src/tool-account-routing.test.ts +3 -3
- package/src/tool-account.ts +1 -1
- package/src/tool-factory-test-harness.ts +1 -1
- package/src/tool-result.test.ts +32 -0
- package/src/tool-result.ts +14 -0
- package/src/types.ts +11 -4
- package/src/typing.ts +1 -1
- package/src/wiki.ts +15 -19
package/src/monitor.account.ts
CHANGED
|
@@ -1,14 +1,28 @@
|
|
|
1
1
|
import * as crypto from "crypto";
|
|
2
2
|
import * as Lark from "@larksuiteoapi/node-sdk";
|
|
3
|
-
import type { ClawdbotConfig, RuntimeEnv, HistoryEntry } from "openclaw/plugin-sdk";
|
|
3
|
+
import type { ClawdbotConfig, RuntimeEnv, HistoryEntry } from "openclaw/plugin-sdk/feishu";
|
|
4
4
|
import { resolveFeishuAccount } from "./accounts.js";
|
|
5
5
|
import { raceWithTimeoutAndAbort } from "./async.js";
|
|
6
|
-
import {
|
|
6
|
+
import {
|
|
7
|
+
handleFeishuMessage,
|
|
8
|
+
parseFeishuMessageEvent,
|
|
9
|
+
type FeishuMessageEvent,
|
|
10
|
+
type FeishuBotAddedEvent,
|
|
11
|
+
} from "./bot.js";
|
|
7
12
|
import { handleFeishuCardAction, type FeishuCardActionEvent } from "./card-action.js";
|
|
8
13
|
import { createEventDispatcher } from "./client.js";
|
|
9
|
-
import {
|
|
10
|
-
|
|
14
|
+
import {
|
|
15
|
+
hasRecordedMessage,
|
|
16
|
+
hasRecordedMessagePersistent,
|
|
17
|
+
tryRecordMessage,
|
|
18
|
+
tryRecordMessagePersistent,
|
|
19
|
+
warmupDedupFromDisk,
|
|
20
|
+
} from "./dedup.js";
|
|
21
|
+
import { isMentionForwardRequest } from "./mention.js";
|
|
22
|
+
import { fetchBotIdentityForMonitor } from "./monitor.startup.js";
|
|
23
|
+
import { botNames, botOpenIds } from "./monitor.state.js";
|
|
11
24
|
import { monitorWebhook, monitorWebSocket } from "./monitor.transport.js";
|
|
25
|
+
import { getFeishuRuntime } from "./runtime.js";
|
|
12
26
|
import { getMessageFeishu } from "./send.js";
|
|
13
27
|
import type { ResolvedFeishuAccount } from "./types.js";
|
|
14
28
|
|
|
@@ -17,7 +31,7 @@ const FEISHU_REACTION_VERIFY_TIMEOUT_MS = 1_500;
|
|
|
17
31
|
export type FeishuReactionCreatedEvent = {
|
|
18
32
|
message_id: string;
|
|
19
33
|
chat_id?: string;
|
|
20
|
-
chat_type?: "p2p" | "group";
|
|
34
|
+
chat_type?: "p2p" | "group" | "private";
|
|
21
35
|
reaction_type?: { emoji_type?: string };
|
|
22
36
|
operator_type?: string;
|
|
23
37
|
user_id?: { open_id?: string };
|
|
@@ -93,7 +107,8 @@ export async function resolveReactionSyntheticEvent(
|
|
|
93
107
|
|
|
94
108
|
const syntheticChatIdRaw = event.chat_id ?? reactedMsg.chatId;
|
|
95
109
|
const syntheticChatId = syntheticChatIdRaw?.trim() ? syntheticChatIdRaw : `p2p:${senderId}`;
|
|
96
|
-
const syntheticChatType: "p2p" | "group"
|
|
110
|
+
const syntheticChatType: "p2p" | "group" | "private" =
|
|
111
|
+
event.chat_type === "group" ? "group" : "p2p";
|
|
97
112
|
return {
|
|
98
113
|
sender: {
|
|
99
114
|
sender_id: { open_id: senderId },
|
|
@@ -119,33 +134,262 @@ type RegisterEventHandlersContext = {
|
|
|
119
134
|
fireAndForget?: boolean;
|
|
120
135
|
};
|
|
121
136
|
|
|
137
|
+
/**
|
|
138
|
+
* Per-chat serial queue that ensures messages from the same chat are processed
|
|
139
|
+
* in arrival order while allowing different chats to run concurrently.
|
|
140
|
+
*/
|
|
141
|
+
function createChatQueue() {
|
|
142
|
+
const queues = new Map<string, Promise<void>>();
|
|
143
|
+
return (chatId: string, task: () => Promise<void>): Promise<void> => {
|
|
144
|
+
const prev = queues.get(chatId) ?? Promise.resolve();
|
|
145
|
+
const next = prev.then(task, task);
|
|
146
|
+
queues.set(chatId, next);
|
|
147
|
+
void next.finally(() => {
|
|
148
|
+
if (queues.get(chatId) === next) {
|
|
149
|
+
queues.delete(chatId);
|
|
150
|
+
}
|
|
151
|
+
});
|
|
152
|
+
return next;
|
|
153
|
+
};
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
function mergeFeishuDebounceMentions(
|
|
157
|
+
entries: FeishuMessageEvent[],
|
|
158
|
+
): FeishuMessageEvent["message"]["mentions"] | undefined {
|
|
159
|
+
const merged = new Map<string, NonNullable<FeishuMessageEvent["message"]["mentions"]>[number]>();
|
|
160
|
+
for (const entry of entries) {
|
|
161
|
+
for (const mention of entry.message.mentions ?? []) {
|
|
162
|
+
const stableId =
|
|
163
|
+
mention.id.open_id?.trim() || mention.id.user_id?.trim() || mention.id.union_id?.trim();
|
|
164
|
+
const mentionName = mention.name?.trim();
|
|
165
|
+
const mentionKey = mention.key?.trim();
|
|
166
|
+
const fallback =
|
|
167
|
+
mentionName && mentionKey ? `${mentionName}|${mentionKey}` : mentionName || mentionKey;
|
|
168
|
+
const key = stableId || fallback;
|
|
169
|
+
if (!key || merged.has(key)) {
|
|
170
|
+
continue;
|
|
171
|
+
}
|
|
172
|
+
merged.set(key, mention);
|
|
173
|
+
}
|
|
174
|
+
}
|
|
175
|
+
if (merged.size === 0) {
|
|
176
|
+
return undefined;
|
|
177
|
+
}
|
|
178
|
+
return Array.from(merged.values());
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
function dedupeFeishuDebounceEntriesByMessageId(
|
|
182
|
+
entries: FeishuMessageEvent[],
|
|
183
|
+
): FeishuMessageEvent[] {
|
|
184
|
+
const seen = new Set<string>();
|
|
185
|
+
const deduped: FeishuMessageEvent[] = [];
|
|
186
|
+
for (const entry of entries) {
|
|
187
|
+
const messageId = entry.message.message_id?.trim();
|
|
188
|
+
if (!messageId) {
|
|
189
|
+
deduped.push(entry);
|
|
190
|
+
continue;
|
|
191
|
+
}
|
|
192
|
+
if (seen.has(messageId)) {
|
|
193
|
+
continue;
|
|
194
|
+
}
|
|
195
|
+
seen.add(messageId);
|
|
196
|
+
deduped.push(entry);
|
|
197
|
+
}
|
|
198
|
+
return deduped;
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
function resolveFeishuDebounceMentions(params: {
|
|
202
|
+
entries: FeishuMessageEvent[];
|
|
203
|
+
botOpenId?: string;
|
|
204
|
+
}): FeishuMessageEvent["message"]["mentions"] | undefined {
|
|
205
|
+
const { entries, botOpenId } = params;
|
|
206
|
+
if (entries.length === 0) {
|
|
207
|
+
return undefined;
|
|
208
|
+
}
|
|
209
|
+
for (let index = entries.length - 1; index >= 0; index -= 1) {
|
|
210
|
+
const entry = entries[index];
|
|
211
|
+
if (isMentionForwardRequest(entry, botOpenId)) {
|
|
212
|
+
// Keep mention-forward semantics scoped to a single source message.
|
|
213
|
+
return mergeFeishuDebounceMentions([entry]);
|
|
214
|
+
}
|
|
215
|
+
}
|
|
216
|
+
const merged = mergeFeishuDebounceMentions(entries);
|
|
217
|
+
if (!merged) {
|
|
218
|
+
return undefined;
|
|
219
|
+
}
|
|
220
|
+
const normalizedBotOpenId = botOpenId?.trim();
|
|
221
|
+
if (!normalizedBotOpenId) {
|
|
222
|
+
return undefined;
|
|
223
|
+
}
|
|
224
|
+
const botMentions = merged.filter(
|
|
225
|
+
(mention) => mention.id.open_id?.trim() === normalizedBotOpenId,
|
|
226
|
+
);
|
|
227
|
+
return botMentions.length > 0 ? botMentions : undefined;
|
|
228
|
+
}
|
|
229
|
+
|
|
122
230
|
function registerEventHandlers(
|
|
123
231
|
eventDispatcher: Lark.EventDispatcher,
|
|
124
232
|
context: RegisterEventHandlersContext,
|
|
125
233
|
): void {
|
|
126
234
|
const { cfg, accountId, runtime, chatHistories, fireAndForget } = context;
|
|
235
|
+
const core = getFeishuRuntime();
|
|
236
|
+
const inboundDebounceMs = core.channel.debounce.resolveInboundDebounceMs({
|
|
237
|
+
cfg,
|
|
238
|
+
channel: "feishu",
|
|
239
|
+
});
|
|
127
240
|
const log = runtime?.log ?? console.log;
|
|
128
241
|
const error = runtime?.error ?? console.error;
|
|
242
|
+
const enqueue = createChatQueue();
|
|
243
|
+
const dispatchFeishuMessage = async (event: FeishuMessageEvent) => {
|
|
244
|
+
const chatId = event.message.chat_id?.trim() || "unknown";
|
|
245
|
+
const task = () =>
|
|
246
|
+
handleFeishuMessage({
|
|
247
|
+
cfg,
|
|
248
|
+
event,
|
|
249
|
+
botOpenId: botOpenIds.get(accountId),
|
|
250
|
+
botName: botNames.get(accountId),
|
|
251
|
+
runtime,
|
|
252
|
+
chatHistories,
|
|
253
|
+
accountId,
|
|
254
|
+
});
|
|
255
|
+
await enqueue(chatId, task);
|
|
256
|
+
};
|
|
257
|
+
const resolveSenderDebounceId = (event: FeishuMessageEvent): string | undefined => {
|
|
258
|
+
const senderId =
|
|
259
|
+
event.sender.sender_id.open_id?.trim() || event.sender.sender_id.user_id?.trim();
|
|
260
|
+
return senderId || undefined;
|
|
261
|
+
};
|
|
262
|
+
const resolveDebounceText = (event: FeishuMessageEvent): string => {
|
|
263
|
+
const botOpenId = botOpenIds.get(accountId);
|
|
264
|
+
const parsed = parseFeishuMessageEvent(event, botOpenId, botNames.get(accountId));
|
|
265
|
+
return parsed.content.trim();
|
|
266
|
+
};
|
|
267
|
+
const recordSuppressedMessageIds = async (
|
|
268
|
+
entries: FeishuMessageEvent[],
|
|
269
|
+
dispatchMessageId?: string,
|
|
270
|
+
) => {
|
|
271
|
+
const keepMessageId = dispatchMessageId?.trim();
|
|
272
|
+
const suppressedIds = new Set(
|
|
273
|
+
entries
|
|
274
|
+
.map((entry) => entry.message.message_id?.trim())
|
|
275
|
+
.filter((id): id is string => Boolean(id) && (!keepMessageId || id !== keepMessageId)),
|
|
276
|
+
);
|
|
277
|
+
if (suppressedIds.size === 0) {
|
|
278
|
+
return;
|
|
279
|
+
}
|
|
280
|
+
for (const messageId of suppressedIds) {
|
|
281
|
+
// Keep in-memory dedupe in sync with handleFeishuMessage's keying.
|
|
282
|
+
tryRecordMessage(`${accountId}:${messageId}`);
|
|
283
|
+
try {
|
|
284
|
+
await tryRecordMessagePersistent(messageId, accountId, log);
|
|
285
|
+
} catch (err) {
|
|
286
|
+
error(
|
|
287
|
+
`feishu[${accountId}]: failed to record merged dedupe id ${messageId}: ${String(err)}`,
|
|
288
|
+
);
|
|
289
|
+
}
|
|
290
|
+
}
|
|
291
|
+
};
|
|
292
|
+
const isMessageAlreadyProcessed = async (entry: FeishuMessageEvent): Promise<boolean> => {
|
|
293
|
+
const messageId = entry.message.message_id?.trim();
|
|
294
|
+
if (!messageId) {
|
|
295
|
+
return false;
|
|
296
|
+
}
|
|
297
|
+
const memoryKey = `${accountId}:${messageId}`;
|
|
298
|
+
if (hasRecordedMessage(memoryKey)) {
|
|
299
|
+
return true;
|
|
300
|
+
}
|
|
301
|
+
return hasRecordedMessagePersistent(messageId, accountId, log);
|
|
302
|
+
};
|
|
303
|
+
const inboundDebouncer = core.channel.debounce.createInboundDebouncer<FeishuMessageEvent>({
|
|
304
|
+
debounceMs: inboundDebounceMs,
|
|
305
|
+
buildKey: (event) => {
|
|
306
|
+
const chatId = event.message.chat_id?.trim();
|
|
307
|
+
const senderId = resolveSenderDebounceId(event);
|
|
308
|
+
if (!chatId || !senderId) {
|
|
309
|
+
return null;
|
|
310
|
+
}
|
|
311
|
+
const rootId = event.message.root_id?.trim();
|
|
312
|
+
const threadKey = rootId ? `thread:${rootId}` : "chat";
|
|
313
|
+
return `feishu:${accountId}:${chatId}:${threadKey}:${senderId}`;
|
|
314
|
+
},
|
|
315
|
+
shouldDebounce: (event) => {
|
|
316
|
+
if (event.message.message_type !== "text") {
|
|
317
|
+
return false;
|
|
318
|
+
}
|
|
319
|
+
const text = resolveDebounceText(event);
|
|
320
|
+
if (!text) {
|
|
321
|
+
return false;
|
|
322
|
+
}
|
|
323
|
+
return !core.channel.text.hasControlCommand(text, cfg);
|
|
324
|
+
},
|
|
325
|
+
onFlush: async (entries) => {
|
|
326
|
+
const last = entries.at(-1);
|
|
327
|
+
if (!last) {
|
|
328
|
+
return;
|
|
329
|
+
}
|
|
330
|
+
if (entries.length === 1) {
|
|
331
|
+
await dispatchFeishuMessage(last);
|
|
332
|
+
return;
|
|
333
|
+
}
|
|
334
|
+
const dedupedEntries = dedupeFeishuDebounceEntriesByMessageId(entries);
|
|
335
|
+
const freshEntries: FeishuMessageEvent[] = [];
|
|
336
|
+
for (const entry of dedupedEntries) {
|
|
337
|
+
if (!(await isMessageAlreadyProcessed(entry))) {
|
|
338
|
+
freshEntries.push(entry);
|
|
339
|
+
}
|
|
340
|
+
}
|
|
341
|
+
const dispatchEntry = freshEntries.at(-1);
|
|
342
|
+
if (!dispatchEntry) {
|
|
343
|
+
return;
|
|
344
|
+
}
|
|
345
|
+
await recordSuppressedMessageIds(dedupedEntries, dispatchEntry.message.message_id);
|
|
346
|
+
const combinedText = freshEntries
|
|
347
|
+
.map((entry) => resolveDebounceText(entry))
|
|
348
|
+
.filter(Boolean)
|
|
349
|
+
.join("\n");
|
|
350
|
+
const mergedMentions = resolveFeishuDebounceMentions({
|
|
351
|
+
entries: freshEntries,
|
|
352
|
+
botOpenId: botOpenIds.get(accountId),
|
|
353
|
+
});
|
|
354
|
+
if (!combinedText.trim()) {
|
|
355
|
+
await dispatchFeishuMessage({
|
|
356
|
+
...dispatchEntry,
|
|
357
|
+
message: {
|
|
358
|
+
...dispatchEntry.message,
|
|
359
|
+
mentions: mergedMentions ?? dispatchEntry.message.mentions,
|
|
360
|
+
},
|
|
361
|
+
});
|
|
362
|
+
return;
|
|
363
|
+
}
|
|
364
|
+
await dispatchFeishuMessage({
|
|
365
|
+
...dispatchEntry,
|
|
366
|
+
message: {
|
|
367
|
+
...dispatchEntry.message,
|
|
368
|
+
message_type: "text",
|
|
369
|
+
content: JSON.stringify({ text: combinedText }),
|
|
370
|
+
mentions: mergedMentions ?? dispatchEntry.message.mentions,
|
|
371
|
+
},
|
|
372
|
+
});
|
|
373
|
+
},
|
|
374
|
+
onError: (err) => {
|
|
375
|
+
error(`feishu[${accountId}]: inbound debounce flush failed: ${String(err)}`);
|
|
376
|
+
},
|
|
377
|
+
});
|
|
129
378
|
|
|
130
379
|
eventDispatcher.register({
|
|
131
380
|
"im.message.receive_v1": async (data) => {
|
|
132
|
-
|
|
381
|
+
const processMessage = async () => {
|
|
133
382
|
const event = data as unknown as FeishuMessageEvent;
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
chatHistories,
|
|
140
|
-
accountId,
|
|
383
|
+
await inboundDebouncer.enqueue(event);
|
|
384
|
+
};
|
|
385
|
+
if (fireAndForget) {
|
|
386
|
+
void processMessage().catch((err) => {
|
|
387
|
+
error(`feishu[${accountId}]: error handling message: ${String(err)}`);
|
|
141
388
|
});
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
} else {
|
|
147
|
-
await promise;
|
|
148
|
-
}
|
|
389
|
+
return;
|
|
390
|
+
}
|
|
391
|
+
try {
|
|
392
|
+
await processMessage();
|
|
149
393
|
} catch (err) {
|
|
150
394
|
error(`feishu[${accountId}]: error handling message: ${String(err)}`);
|
|
151
395
|
}
|
|
@@ -187,6 +431,7 @@ function registerEventHandlers(
|
|
|
187
431
|
cfg,
|
|
188
432
|
event: syntheticEvent,
|
|
189
433
|
botOpenId: myBotId,
|
|
434
|
+
botName: botNames.get(accountId),
|
|
190
435
|
runtime,
|
|
191
436
|
chatHistories,
|
|
192
437
|
accountId,
|
|
@@ -240,7 +485,9 @@ function registerEventHandlers(
|
|
|
240
485
|
});
|
|
241
486
|
}
|
|
242
487
|
|
|
243
|
-
export type BotOpenIdSource =
|
|
488
|
+
export type BotOpenIdSource =
|
|
489
|
+
| { kind: "prefetched"; botOpenId?: string; botName?: string }
|
|
490
|
+
| { kind: "fetch" };
|
|
244
491
|
|
|
245
492
|
export type MonitorSingleAccountParams = {
|
|
246
493
|
cfg: ClawdbotConfig;
|
|
@@ -256,11 +503,18 @@ export async function monitorSingleAccount(params: MonitorSingleAccountParams):
|
|
|
256
503
|
const log = runtime?.log ?? console.log;
|
|
257
504
|
|
|
258
505
|
const botOpenIdSource = params.botOpenIdSource ?? { kind: "fetch" };
|
|
259
|
-
const
|
|
506
|
+
const botIdentity =
|
|
260
507
|
botOpenIdSource.kind === "prefetched"
|
|
261
|
-
? botOpenIdSource.botOpenId
|
|
262
|
-
: await
|
|
508
|
+
? { botOpenId: botOpenIdSource.botOpenId, botName: botOpenIdSource.botName }
|
|
509
|
+
: await fetchBotIdentityForMonitor(account, { runtime, abortSignal });
|
|
510
|
+
const botOpenId = botIdentity.botOpenId;
|
|
511
|
+
const botName = botIdentity.botName?.trim();
|
|
263
512
|
botOpenIds.set(accountId, botOpenId ?? "");
|
|
513
|
+
if (botName) {
|
|
514
|
+
botNames.set(accountId, botName);
|
|
515
|
+
} else {
|
|
516
|
+
botNames.delete(accountId);
|
|
517
|
+
}
|
|
264
518
|
log(`feishu[${accountId}]: bot open_id resolved: ${botOpenId ?? "unknown"}`);
|
|
265
519
|
|
|
266
520
|
const connectionMode = account.config.connectionMode ?? "websocket";
|
|
@@ -268,6 +522,11 @@ export async function monitorSingleAccount(params: MonitorSingleAccountParams):
|
|
|
268
522
|
throw new Error(`Feishu account "${accountId}" webhook mode requires verificationToken`);
|
|
269
523
|
}
|
|
270
524
|
|
|
525
|
+
const warmupCount = await warmupDedupFromDisk(accountId, log);
|
|
526
|
+
if (warmupCount > 0) {
|
|
527
|
+
log(`feishu[${accountId}]: dedup warmup loaded ${warmupCount} entries from disk`);
|
|
528
|
+
}
|
|
529
|
+
|
|
271
530
|
const eventDispatcher = createEventDispatcher(account);
|
|
272
531
|
const chatHistories = new Map<string, HistoryEntry[]>();
|
|
273
532
|
|