openclaw-lark-multi-agent 1.0.11 → 1.0.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/feishu-bot.d.ts +2 -0
- package/dist/feishu-bot.js +18 -0
- package/dist/openclaw-client.d.ts +12 -0
- package/dist/openclaw-client.js +62 -9
- package/package.json +1 -1
package/dist/feishu-bot.d.ts
CHANGED
|
@@ -45,6 +45,8 @@ export declare class FeishuBot {
|
|
|
45
45
|
* Format: lma-<botname>-<chatId>
|
|
46
46
|
*/
|
|
47
47
|
getSessionKey(chatId: string): string;
|
|
48
|
+
private lmaBridgePolicy;
|
|
49
|
+
private injectBridgePolicy;
|
|
48
50
|
/**
|
|
49
51
|
* Ensure the session for a given chatId exists with the correct model.
|
|
50
52
|
* Lazy: only creates on first message in that chat.
|
package/dist/feishu-bot.js
CHANGED
|
@@ -100,6 +100,23 @@ export class FeishuBot {
|
|
|
100
100
|
getSessionKey(chatId) {
|
|
101
101
|
return `lma-${this.config.name.toLowerCase()}-${chatId}`;
|
|
102
102
|
}
|
|
103
|
+
lmaBridgePolicy() {
|
|
104
|
+
return [
|
|
105
|
+
"[LMA bridge policy]",
|
|
106
|
+
"你正在 OpenClaw Lark Multi-Agent bridge 会话中。",
|
|
107
|
+
"不要调用 message、sessions_send、feishu_im_user_message 或任何主动向飞书/外部聊天发送消息的工具。",
|
|
108
|
+
"直接在当前回复中作答;LMA bridge 会负责把最终回复投递回原始飞书群。",
|
|
109
|
+
].join("\n");
|
|
110
|
+
}
|
|
111
|
+
async injectBridgePolicy(sessionKey) {
|
|
112
|
+
await this.openclawClient.injectAssistantMessage({
|
|
113
|
+
sessionKey,
|
|
114
|
+
message: this.lmaBridgePolicy(),
|
|
115
|
+
label: "LMA bridge policy",
|
|
116
|
+
}).catch((err) => {
|
|
117
|
+
console.warn(`[${this.config.name}] bridge policy inject failed:`, err.message);
|
|
118
|
+
});
|
|
119
|
+
}
|
|
103
120
|
/**
|
|
104
121
|
* Ensure the session for a given chatId exists with the correct model.
|
|
105
122
|
* Lazy: only creates on first message in that chat.
|
|
@@ -136,6 +153,7 @@ export class FeishuBot {
|
|
|
136
153
|
});
|
|
137
154
|
// Always patch model after create to ensure it takes effect
|
|
138
155
|
await this.openclawClient.patchSession({ key: sessionKey, model: this.config.model });
|
|
156
|
+
await this.injectBridgePolicy(sessionKey);
|
|
139
157
|
console.log(`[${this.config.name}] Session created: ${sessionKey} (model: ${this.config.model})`);
|
|
140
158
|
}
|
|
141
159
|
}
|
|
@@ -43,6 +43,10 @@ export declare class OpenClawClient {
|
|
|
43
43
|
private chatSendConcurrency;
|
|
44
44
|
private activeChatSends;
|
|
45
45
|
private chatSendWaiters;
|
|
46
|
+
/** Maintenance RPCs like sessions.compact are heavy and should not fan out. */
|
|
47
|
+
private maintenanceConcurrency;
|
|
48
|
+
private activeMaintenanceRpcs;
|
|
49
|
+
private maintenanceWaiters;
|
|
46
50
|
constructor(config: OpenClawConfig);
|
|
47
51
|
connect(): Promise<void>;
|
|
48
52
|
private _doConnect;
|
|
@@ -59,6 +63,7 @@ export declare class OpenClawClient {
|
|
|
59
63
|
* 30-minute safety net only for catastrophic WS disconnection.
|
|
60
64
|
*/
|
|
61
65
|
private collectReply;
|
|
66
|
+
private pickBestCollectedText;
|
|
62
67
|
createSession(params: {
|
|
63
68
|
key: string;
|
|
64
69
|
model: string;
|
|
@@ -70,6 +75,11 @@ export declare class OpenClawClient {
|
|
|
70
75
|
label?: string;
|
|
71
76
|
}): Promise<any>;
|
|
72
77
|
getSessionStatus(key: string): Promise<any>;
|
|
78
|
+
injectAssistantMessage(params: {
|
|
79
|
+
sessionKey: string;
|
|
80
|
+
message: string;
|
|
81
|
+
label?: string;
|
|
82
|
+
}): Promise<any>;
|
|
73
83
|
/**
|
|
74
84
|
* Get session info (model, tokens, etc.) for status display.
|
|
75
85
|
*/
|
|
@@ -101,6 +111,8 @@ export declare class OpenClawClient {
|
|
|
101
111
|
muteProactiveDelivery(sessionKey: string): (delayMs?: number) => void;
|
|
102
112
|
private acquireChatSendSlot;
|
|
103
113
|
private releaseChatSendSlot;
|
|
114
|
+
private acquireMaintenanceSlot;
|
|
115
|
+
private releaseMaintenanceSlot;
|
|
104
116
|
chatSend(params: {
|
|
105
117
|
sessionKey: string;
|
|
106
118
|
message: string;
|
package/dist/openclaw-client.js
CHANGED
|
@@ -44,6 +44,10 @@ export class OpenClawClient {
|
|
|
44
44
|
chatSendConcurrency = Number(process.env.OPENCLAW_LARK_MULTI_AGENT_CHAT_SEND_CONCURRENCY || 3);
|
|
45
45
|
activeChatSends = 0;
|
|
46
46
|
chatSendWaiters = [];
|
|
47
|
+
/** Maintenance RPCs like sessions.compact are heavy and should not fan out. */
|
|
48
|
+
maintenanceConcurrency = Number(process.env.OPENCLAW_LARK_MULTI_AGENT_MAINTENANCE_CONCURRENCY || 1);
|
|
49
|
+
activeMaintenanceRpcs = 0;
|
|
50
|
+
maintenanceWaiters = [];
|
|
47
51
|
constructor(config) {
|
|
48
52
|
this.config = config;
|
|
49
53
|
}
|
|
@@ -192,7 +196,7 @@ export class OpenClawClient {
|
|
|
192
196
|
this.agentEvents.get(rawKey).push({
|
|
193
197
|
runId: frame.payload.runId,
|
|
194
198
|
sessionKey: rawKey,
|
|
195
|
-
stream: "
|
|
199
|
+
stream: "transcriptAssistant",
|
|
196
200
|
data: { deltaText: visibleAssistantText, delta: visibleAssistantText, replace: true },
|
|
197
201
|
});
|
|
198
202
|
}
|
|
@@ -336,6 +340,7 @@ export class OpenClawClient {
|
|
|
336
340
|
let text = "";
|
|
337
341
|
let chatDeltaText = "";
|
|
338
342
|
let chatFinalText = "";
|
|
343
|
+
let transcriptAssistantText = "";
|
|
339
344
|
let sessionKey = targetSessionKey ? `agent:main:${targetSessionKey.replace(/^agent:[^:]+:/, "")}` : "";
|
|
340
345
|
let shortSessionKey = targetSessionKey ? targetSessionKey.replace(/^agent:[^:]+:/, "") : "";
|
|
341
346
|
let chatFinalTimer = null;
|
|
@@ -374,7 +379,7 @@ export class OpenClawClient {
|
|
|
374
379
|
this.abortChat(targetSessionKey || sessionKey, runId).catch((err) => {
|
|
375
380
|
console.warn(`[OpenClaw] abort after collectReply idle timeout failed:`, err.message);
|
|
376
381
|
});
|
|
377
|
-
const visibleText = text
|
|
382
|
+
const visibleText = this.pickBestCollectedText(chatFinalText, text, chatDeltaText, transcriptAssistantText);
|
|
378
383
|
if (visibleText) {
|
|
379
384
|
resolve(visibleText);
|
|
380
385
|
}
|
|
@@ -399,7 +404,7 @@ export class OpenClawClient {
|
|
|
399
404
|
}
|
|
400
405
|
return `运行事件 item${data.kind ? `/${data.kind}` : ""}`;
|
|
401
406
|
}
|
|
402
|
-
if (ev.stream === "assistant" || ev.stream === "chatDelta") {
|
|
407
|
+
if (ev.stream === "assistant" || ev.stream === "chatDelta" || ev.stream === "transcriptAssistant") {
|
|
403
408
|
const chunk = String(ev.data?.deltaText || ev.data?.delta || "").trim();
|
|
404
409
|
return chunk ? `模型输出片段: ${chunk.slice(0, 300)}` : "模型输出片段";
|
|
405
410
|
}
|
|
@@ -550,7 +555,7 @@ export class OpenClawClient {
|
|
|
550
555
|
lifecycleStartedLogged = true;
|
|
551
556
|
console.log(`[OpenClaw] lifecycle start for runId=${runId} after ${Date.now() - collectStartedAt}ms`);
|
|
552
557
|
}
|
|
553
|
-
if ((ev.stream === "assistant" || ev.stream === "chatDelta") && (ev.data?.deltaText || ev.data?.delta)) {
|
|
558
|
+
if ((ev.stream === "assistant" || ev.stream === "chatDelta" || ev.stream === "transcriptAssistant") && (ev.data?.deltaText || ev.data?.delta)) {
|
|
554
559
|
const chunk = ev.data.deltaText || ev.data.delta;
|
|
555
560
|
if (ev.stream === "assistant") {
|
|
556
561
|
if (ev.data?.replace) {
|
|
@@ -560,7 +565,7 @@ export class OpenClawClient {
|
|
|
560
565
|
text += chunk;
|
|
561
566
|
}
|
|
562
567
|
}
|
|
563
|
-
else {
|
|
568
|
+
else if (ev.stream === "chatDelta") {
|
|
564
569
|
if (ev.data?.replace) {
|
|
565
570
|
chatDeltaText = chunk;
|
|
566
571
|
}
|
|
@@ -568,6 +573,14 @@ export class OpenClawClient {
|
|
|
568
573
|
chatDeltaText += chunk;
|
|
569
574
|
}
|
|
570
575
|
}
|
|
576
|
+
else {
|
|
577
|
+
if (ev.data?.replace) {
|
|
578
|
+
transcriptAssistantText = chunk;
|
|
579
|
+
}
|
|
580
|
+
else {
|
|
581
|
+
transcriptAssistantText += chunk;
|
|
582
|
+
}
|
|
583
|
+
}
|
|
571
584
|
}
|
|
572
585
|
if (ev.stream === "chatFinal") {
|
|
573
586
|
chatFinalText = ev.data?.text || "";
|
|
@@ -580,7 +593,7 @@ export class OpenClawClient {
|
|
|
580
593
|
});
|
|
581
594
|
// Prefer final chat message over accumulated deltas: some providers may
|
|
582
595
|
// emit only partial deltas (e.g. "N") while final contains "NO_REPLY".
|
|
583
|
-
const latestFinalText = chatFinalText
|
|
596
|
+
const latestFinalText = this.pickBestCollectedText(chatFinalText, text, chatDeltaText, transcriptAssistantText);
|
|
584
597
|
if (latestFinalText) {
|
|
585
598
|
finish(latestFinalText);
|
|
586
599
|
}
|
|
@@ -597,9 +610,9 @@ export class OpenClawClient {
|
|
|
597
610
|
if (ev.stream === "lifecycle" && ev.data?.phase === "end") {
|
|
598
611
|
// Prefer final chat message over accumulated deltas: some providers may
|
|
599
612
|
// emit only partial deltas (e.g. "N") while final contains "NO_REPLY".
|
|
600
|
-
const finalText = chatFinalText
|
|
613
|
+
const finalText = this.pickBestCollectedText(chatFinalText, text, chatDeltaText, transcriptAssistantText);
|
|
601
614
|
const finishFromLifecycle = () => {
|
|
602
|
-
const latestFinalText = chatFinalText
|
|
615
|
+
const latestFinalText = this.pickBestCollectedText(chatFinalText, text, chatDeltaText, transcriptAssistantText);
|
|
603
616
|
if (!chatFinalText && latestFinalText.trim() === "N") {
|
|
604
617
|
// Some providers stream the first character of NO_REPLY ("N") but
|
|
605
618
|
// never deliver a final chat message in time. Never surface a lone
|
|
@@ -662,6 +675,17 @@ export class OpenClawClient {
|
|
|
662
675
|
}, 50);
|
|
663
676
|
});
|
|
664
677
|
}
|
|
678
|
+
pickBestCollectedText(chatFinalText, assistantText, chatDeltaText, transcriptAssistantText) {
|
|
679
|
+
if (chatFinalText)
|
|
680
|
+
return chatFinalText;
|
|
681
|
+
const candidates = [assistantText, chatDeltaText, transcriptAssistantText].filter((value) => value && value.trim());
|
|
682
|
+
if (candidates.length === 0)
|
|
683
|
+
return "";
|
|
684
|
+
// transcriptAssistant is a full transcript mirror. Keep it separate from
|
|
685
|
+
// streaming deltas to avoid concatenating the same response twice; choose
|
|
686
|
+
// the richest available non-final text as fallback.
|
|
687
|
+
return candidates.sort((a, b) => b.length - a.length)[0];
|
|
688
|
+
}
|
|
665
689
|
// --- Session management ---
|
|
666
690
|
async createSession(params) {
|
|
667
691
|
return this.rpc("sessions.create", params);
|
|
@@ -672,6 +696,9 @@ export class OpenClawClient {
|
|
|
672
696
|
async getSessionStatus(key) {
|
|
673
697
|
return this.rpc("sessions.describe", { key });
|
|
674
698
|
}
|
|
699
|
+
async injectAssistantMessage(params) {
|
|
700
|
+
return this.rpc("chat.inject", params, 10000);
|
|
701
|
+
}
|
|
675
702
|
/**
|
|
676
703
|
* Get session info (model, tokens, etc.) for status display.
|
|
677
704
|
*/
|
|
@@ -704,7 +731,15 @@ export class OpenClawClient {
|
|
|
704
731
|
return this.rpc("sessions.reset", { key }, 5000).catch(() => { });
|
|
705
732
|
}
|
|
706
733
|
async compactSession(key) {
|
|
707
|
-
|
|
734
|
+
const release = await this.acquireMaintenanceSlot("sessions.compact");
|
|
735
|
+
const startedAt = Date.now();
|
|
736
|
+
try {
|
|
737
|
+
return await this.rpc("sessions.compact", { key }, 10 * 60 * 1000);
|
|
738
|
+
}
|
|
739
|
+
finally {
|
|
740
|
+
console.log(`[OpenClaw] sessions.compact finished for ${key} in ${Date.now() - startedAt}ms`);
|
|
741
|
+
release();
|
|
742
|
+
}
|
|
708
743
|
}
|
|
709
744
|
// --- Chat ---
|
|
710
745
|
/**
|
|
@@ -862,6 +897,24 @@ export class OpenClawClient {
|
|
|
862
897
|
if (next)
|
|
863
898
|
next();
|
|
864
899
|
}
|
|
900
|
+
async acquireMaintenanceSlot(kind) {
|
|
901
|
+
const limit = Math.max(1, this.maintenanceConcurrency || 1);
|
|
902
|
+
if (this.activeMaintenanceRpcs < limit) {
|
|
903
|
+
this.activeMaintenanceRpcs++;
|
|
904
|
+
return () => this.releaseMaintenanceSlot();
|
|
905
|
+
}
|
|
906
|
+
const startedWaitingAt = Date.now();
|
|
907
|
+
await new Promise((resolve) => this.maintenanceWaiters.push(resolve));
|
|
908
|
+
this.activeMaintenanceRpcs++;
|
|
909
|
+
console.log(`[OpenClaw] ${kind} waited ${Date.now() - startedWaitingAt}ms for maintenance slot (active=${this.activeMaintenanceRpcs}/${limit})`);
|
|
910
|
+
return () => this.releaseMaintenanceSlot();
|
|
911
|
+
}
|
|
912
|
+
releaseMaintenanceSlot() {
|
|
913
|
+
this.activeMaintenanceRpcs = Math.max(0, this.activeMaintenanceRpcs - 1);
|
|
914
|
+
const next = this.maintenanceWaiters.shift();
|
|
915
|
+
if (next)
|
|
916
|
+
next();
|
|
917
|
+
}
|
|
865
918
|
async chatSend(params) {
|
|
866
919
|
const sk = params.sessionKey;
|
|
867
920
|
const fullSessionKey = `agent:main:${sk}`;
|