openclaw-lark-multi-agent 0.1.11 → 0.1.13
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/discussion-manager.d.ts +2 -0
- package/dist/discussion-manager.js +13 -3
- package/dist/feishu-bot.js +3 -3
- package/dist/openclaw-client.js +42 -22
- package/package.json +1 -1
|
@@ -25,6 +25,7 @@ export type DiscussionParticipant = {
|
|
|
25
25
|
export declare class DiscussionManager {
|
|
26
26
|
private sessions;
|
|
27
27
|
private seenRoots;
|
|
28
|
+
private readonly seenRootTtlMs;
|
|
28
29
|
isActive(chatId: string): boolean;
|
|
29
30
|
stop(chatId: string): boolean;
|
|
30
31
|
status(chatId: string): DiscussionSession | null;
|
|
@@ -36,6 +37,7 @@ export declare class DiscussionManager {
|
|
|
36
37
|
participants: DiscussionParticipant[];
|
|
37
38
|
sendSystemMessage?: (text: string) => Promise<void>;
|
|
38
39
|
}): boolean;
|
|
40
|
+
private pruneSeenRoots;
|
|
39
41
|
private runLoop;
|
|
40
42
|
private buildPrompt;
|
|
41
43
|
}
|
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
import { randomUUID } from "crypto";
|
|
2
2
|
export class DiscussionManager {
|
|
3
3
|
sessions = new Map();
|
|
4
|
-
seenRoots = new
|
|
4
|
+
seenRoots = new Map();
|
|
5
|
+
seenRootTtlMs = 6 * 60 * 60 * 1000;
|
|
5
6
|
isActive(chatId) {
|
|
6
7
|
return this.sessions.get(chatId)?.status === "running";
|
|
7
8
|
}
|
|
@@ -18,15 +19,18 @@ export class DiscussionManager {
|
|
|
18
19
|
return session ? { ...session, completedRounds: [...session.completedRounds] } : null;
|
|
19
20
|
}
|
|
20
21
|
startIfAbsent(params) {
|
|
22
|
+
this.pruneSeenRoots();
|
|
21
23
|
const key = `${params.chatId}:${params.rootMessageId}`;
|
|
22
24
|
if (this.seenRoots.has(key))
|
|
23
25
|
return false;
|
|
24
|
-
this.seenRoots.
|
|
26
|
+
this.seenRoots.set(key, Date.now());
|
|
25
27
|
if (this.isActive(params.chatId))
|
|
26
28
|
this.stop(params.chatId);
|
|
27
29
|
const participants = params.participants.filter((p, index, arr) => arr.findIndex((x) => x.name === p.name) === index);
|
|
28
|
-
if (participants.length === 0)
|
|
30
|
+
if (participants.length === 0) {
|
|
31
|
+
this.seenRoots.delete(key);
|
|
29
32
|
return false;
|
|
33
|
+
}
|
|
30
34
|
const session = {
|
|
31
35
|
id: randomUUID(),
|
|
32
36
|
chatId: params.chatId,
|
|
@@ -47,6 +51,12 @@ export class DiscussionManager {
|
|
|
47
51
|
});
|
|
48
52
|
return true;
|
|
49
53
|
}
|
|
54
|
+
pruneSeenRoots(now = Date.now()) {
|
|
55
|
+
for (const [key, ts] of this.seenRoots) {
|
|
56
|
+
if (now - ts > this.seenRootTtlMs)
|
|
57
|
+
this.seenRoots.delete(key);
|
|
58
|
+
}
|
|
59
|
+
}
|
|
50
60
|
async runLoop(sessionId, participants, sendSystemMessage) {
|
|
51
61
|
while (true) {
|
|
52
62
|
const session = Array.from(this.sessions.values()).find((s) => s.id === sessionId);
|
package/dist/feishu-bot.js
CHANGED
|
@@ -457,7 +457,7 @@ export class FeishuBot {
|
|
|
457
457
|
const next = current === "free" ? "normal" : "free";
|
|
458
458
|
this.store.setBotMode(this.config.name, chatId, next);
|
|
459
459
|
if (next === "free") {
|
|
460
|
-
await this.replyMessage(messageId, `🔓 ${this.config.name} 已切换到 free 模式\n不需要 @
|
|
460
|
+
await this.replyMessage(messageId, `🔓 ${this.config.name} 已切换到 free 模式\n不需要 @ 也可以回复普通人类消息;如果消息明确 @ 了其他 bot 或普通人,我不会抢答。\n如需多轮自动讨论,请使用群级命令 /discuss on。`);
|
|
461
461
|
}
|
|
462
462
|
else {
|
|
463
463
|
await this.replyMessage(messageId, `🔒 ${this.config.name} 已切换到 normal 模式\n只有明确 @ 我才会回复`);
|
|
@@ -635,7 +635,7 @@ export class FeishuBot {
|
|
|
635
635
|
deliver: false,
|
|
636
636
|
// Keep bridge UX responsive; long agent/tool loops should surface a clear failure
|
|
637
637
|
// instead of leaving reactions stuck forever.
|
|
638
|
-
timeoutMs:
|
|
638
|
+
timeoutMs: 1_800_000,
|
|
639
639
|
});
|
|
640
640
|
console.log(`[${this.config.name}] OpenClaw reply collected for ${chatId.slice(-8)} in ${Date.now() - queueStartedAt}ms`);
|
|
641
641
|
const parsedReply = this.extractBridgeAttachments(reply);
|
|
@@ -921,7 +921,7 @@ export class FeishuBot {
|
|
|
921
921
|
currentMessage: prompt,
|
|
922
922
|
currentSenderName: "Discussion Scheduler",
|
|
923
923
|
deliver: false,
|
|
924
|
-
timeoutMs:
|
|
924
|
+
timeoutMs: 1_800_000,
|
|
925
925
|
});
|
|
926
926
|
const parsedReply = this.extractBridgeAttachments(reply);
|
|
927
927
|
const visibleReply = parsedReply.text.trim();
|
package/dist/openclaw-client.js
CHANGED
|
@@ -266,22 +266,28 @@ export class OpenClawClient {
|
|
|
266
266
|
let replayInvalidTimer = null;
|
|
267
267
|
const collectStartedAt = Date.now();
|
|
268
268
|
let lifecycleStartedLogged = false;
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
if (
|
|
272
|
-
clearTimeout(
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
269
|
+
let idleTimer;
|
|
270
|
+
const resetIdleTimer = () => {
|
|
271
|
+
if (idleTimer)
|
|
272
|
+
clearTimeout(idleTimer);
|
|
273
|
+
idleTimer = setTimeout(() => {
|
|
274
|
+
clearInterval(poller);
|
|
275
|
+
if (chatFinalTimer)
|
|
276
|
+
clearTimeout(chatFinalTimer);
|
|
277
|
+
if (lifecycleEndTimer)
|
|
278
|
+
clearTimeout(lifecycleEndTimer);
|
|
279
|
+
if (replayInvalidTimer)
|
|
280
|
+
clearTimeout(replayInvalidTimer);
|
|
281
|
+
console.warn(`[OpenClaw] collectReply idle timeout for runId=${runId} sessionKey=${sessionKey}`);
|
|
282
|
+
this.abortChat(targetSessionKey || sessionKey, runId).catch((err) => {
|
|
283
|
+
console.warn(`[OpenClaw] abort after collectReply idle timeout failed:`, err.message);
|
|
284
|
+
});
|
|
285
|
+
resolve(text || chatFinalText || "(timeout: no reply received)");
|
|
286
|
+
}, timeoutMs);
|
|
287
|
+
};
|
|
288
|
+
resetIdleTimer();
|
|
283
289
|
const finish = (finalText) => {
|
|
284
|
-
clearTimeout(
|
|
290
|
+
clearTimeout(idleTimer);
|
|
285
291
|
clearInterval(poller);
|
|
286
292
|
if (chatFinalTimer)
|
|
287
293
|
clearTimeout(chatFinalTimer);
|
|
@@ -318,6 +324,10 @@ export class OpenClawClient {
|
|
|
318
324
|
continue;
|
|
319
325
|
}
|
|
320
326
|
bucket.splice(i, 1);
|
|
327
|
+
// Any matching event — including toolCall/toolResult/item/lifecycle —
|
|
328
|
+
// means the agent is still alive. Use an idle timeout, not an absolute
|
|
329
|
+
// wall-clock timeout, so long tool-heavy tasks are not killed while active.
|
|
330
|
+
resetIdleTimer();
|
|
321
331
|
// If more events arrive after a replay-invalid lifecycle end, that lifecycle
|
|
322
332
|
// was not terminal for the user-visible run. Keep waiting for the real final.
|
|
323
333
|
if (replayInvalidTimer) {
|
|
@@ -342,7 +352,14 @@ export class OpenClawClient {
|
|
|
342
352
|
});
|
|
343
353
|
// Prefer final chat message over accumulated deltas: some providers may
|
|
344
354
|
// emit only partial deltas (e.g. "N") while final contains "NO_REPLY".
|
|
345
|
-
|
|
355
|
+
const latestFinalText = chatFinalText || text;
|
|
356
|
+
if (latestFinalText) {
|
|
357
|
+
finish(latestFinalText);
|
|
358
|
+
}
|
|
359
|
+
else {
|
|
360
|
+
console.warn(`[OpenClaw] collectReply: empty chatFinal fallback ignored; waiting for real text or idle timeout`);
|
|
361
|
+
chatFinalTimer = null;
|
|
362
|
+
}
|
|
346
363
|
}, 5000);
|
|
347
364
|
}
|
|
348
365
|
}
|
|
@@ -359,7 +376,7 @@ export class OpenClawClient {
|
|
|
359
376
|
finish("NO_REPLY");
|
|
360
377
|
return;
|
|
361
378
|
}
|
|
362
|
-
if (!latestFinalText
|
|
379
|
+
if (!latestFinalText) {
|
|
363
380
|
const state = ev.data?.livenessState || "unknown";
|
|
364
381
|
const reason = ev.data?.stopReason || "";
|
|
365
382
|
const replayInvalid = ev.data?.replayInvalid ? ", replayInvalid" : "";
|
|
@@ -369,11 +386,14 @@ export class OpenClawClient {
|
|
|
369
386
|
replayInvalidTimer = setTimeout(() => finish(failureText), 120000);
|
|
370
387
|
return;
|
|
371
388
|
}
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
389
|
+
if (ev.data?.livenessState !== "working") {
|
|
390
|
+
finish(failureText);
|
|
391
|
+
return;
|
|
392
|
+
}
|
|
393
|
+
console.warn(`[OpenClaw] empty lifecycle end ignored for runId=${evRunId || runId}; waiting for real text or idle timeout`);
|
|
394
|
+
return;
|
|
376
395
|
}
|
|
396
|
+
finish(latestFinalText);
|
|
377
397
|
};
|
|
378
398
|
// If lifecycle end beats chat final, a short delta like "N" can be a truncated
|
|
379
399
|
// final reply. Wait for chatFinal before resolving; otherwise suppress lone "N".
|
|
@@ -386,7 +406,7 @@ export class OpenClawClient {
|
|
|
386
406
|
return;
|
|
387
407
|
}
|
|
388
408
|
if (ev.stream === "lifecycle" && ev.data?.phase === "error") {
|
|
389
|
-
clearTimeout(
|
|
409
|
+
clearTimeout(idleTimer);
|
|
390
410
|
clearInterval(poller);
|
|
391
411
|
if (chatFinalTimer)
|
|
392
412
|
clearTimeout(chatFinalTimer);
|