openclaw-lark-multi-agent 1.0.12 → 1.0.14
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/config.d.ts +4 -0
- package/dist/config.js +5 -0
- package/dist/discussion-manager.d.ts +16 -0
- package/dist/discussion-manager.js +68 -32
- package/dist/feishu-bot.d.ts +12 -0
- package/dist/feishu-bot.js +266 -54
- package/dist/i18n.d.ts +60 -0
- package/dist/i18n.js +140 -0
- package/dist/message-store.d.ts +8 -0
- package/dist/message-store.js +59 -9
- package/dist/openclaw-client.d.ts +11 -0
- package/dist/openclaw-client.js +34 -1
- package/package.json +1 -1
package/dist/config.d.ts
CHANGED
|
@@ -1,8 +1,10 @@
|
|
|
1
|
+
import { type Locale } from "./i18n.js";
|
|
1
2
|
export interface BotConfig {
|
|
2
3
|
name: string;
|
|
3
4
|
appId: string;
|
|
4
5
|
appSecret: string;
|
|
5
6
|
model: string;
|
|
7
|
+
locale?: Locale;
|
|
6
8
|
}
|
|
7
9
|
export interface OpenClawConfig {
|
|
8
10
|
baseUrl: string;
|
|
@@ -13,5 +15,7 @@ export interface AppConfig {
|
|
|
13
15
|
bots: BotConfig[];
|
|
14
16
|
/** Optional Feishu/Lark open_id for model-drift notifications */
|
|
15
17
|
adminOpenId?: string;
|
|
18
|
+
/** Default UI/prompt language. Bot-level locale overrides this. */
|
|
19
|
+
locale?: Locale;
|
|
16
20
|
}
|
|
17
21
|
export declare function loadConfig(path?: string): AppConfig;
|
package/dist/config.js
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { readFileSync } from "fs";
|
|
2
2
|
import { resolve } from "path";
|
|
3
|
+
import { normalizeLocale } from "./i18n.js";
|
|
3
4
|
export function loadConfig(path) {
|
|
4
5
|
const configPath = path || resolve(process.cwd(), "config.json");
|
|
5
6
|
const raw = readFileSync(configPath, "utf-8");
|
|
@@ -15,6 +16,10 @@ export function loadConfig(path) {
|
|
|
15
16
|
throw new Error(`Bot "${bot.name}" missing appId, appSecret, or model`);
|
|
16
17
|
}
|
|
17
18
|
}
|
|
19
|
+
config.locale = normalizeLocale(config.locale);
|
|
20
|
+
for (const bot of config.bots) {
|
|
21
|
+
bot.locale = normalizeLocale(bot.locale || config.locale);
|
|
22
|
+
}
|
|
18
23
|
// Validate uniqueness
|
|
19
24
|
const names = config.bots.map((b) => b.name);
|
|
20
25
|
const appIds = config.bots.map((b) => b.appId);
|
|
@@ -1,15 +1,24 @@
|
|
|
1
|
+
import { type Locale } from "./i18n.js";
|
|
1
2
|
export type ReplyResult = {
|
|
2
3
|
botName: string;
|
|
3
4
|
text: string;
|
|
4
5
|
visible: boolean;
|
|
5
6
|
error?: string;
|
|
6
7
|
};
|
|
8
|
+
export type DiscussionCompleteReason = "chairman_final" | "all_no_reply" | "max_rounds";
|
|
9
|
+
export type DiscussionCompleteEvent = {
|
|
10
|
+
chatId: string;
|
|
11
|
+
reason: DiscussionCompleteReason;
|
|
12
|
+
chairmanName?: string;
|
|
13
|
+
};
|
|
7
14
|
type DiscussionSession = {
|
|
8
15
|
id: string;
|
|
9
16
|
chatId: string;
|
|
10
17
|
rootMessageId: string;
|
|
11
18
|
topic: string;
|
|
12
19
|
participants: string[];
|
|
20
|
+
chairmanName?: string;
|
|
21
|
+
locale: Locale;
|
|
13
22
|
currentRound: number;
|
|
14
23
|
maxRounds: number;
|
|
15
24
|
completedRounds: Array<{
|
|
@@ -26,9 +35,11 @@ export type DiscussionParticipant = {
|
|
|
26
35
|
}): Promise<ReplyResult>;
|
|
27
36
|
};
|
|
28
37
|
export declare class DiscussionManager {
|
|
38
|
+
private defaultLocale;
|
|
29
39
|
private sessions;
|
|
30
40
|
private seenRoots;
|
|
31
41
|
private readonly seenRootTtlMs;
|
|
42
|
+
constructor(defaultLocale?: Locale);
|
|
32
43
|
isActive(chatId: string): boolean;
|
|
33
44
|
stop(chatId: string): boolean;
|
|
34
45
|
status(chatId: string): DiscussionSession | null;
|
|
@@ -38,10 +49,15 @@ export declare class DiscussionManager {
|
|
|
38
49
|
topic: string;
|
|
39
50
|
maxRounds: number;
|
|
40
51
|
participants: DiscussionParticipant[];
|
|
52
|
+
chairman?: DiscussionParticipant;
|
|
41
53
|
sendSystemMessage?: (text: string) => Promise<void>;
|
|
54
|
+
onComplete?: (event: DiscussionCompleteEvent) => Promise<void>;
|
|
55
|
+
locale?: Locale;
|
|
42
56
|
}): boolean;
|
|
43
57
|
private pruneSeenRoots;
|
|
44
58
|
private runLoop;
|
|
59
|
+
private hasFinalSummaryMarker;
|
|
60
|
+
private buildChairmanPrompt;
|
|
45
61
|
private buildPrompt;
|
|
46
62
|
}
|
|
47
63
|
export declare const discussionManager: DiscussionManager;
|
|
@@ -1,8 +1,13 @@
|
|
|
1
1
|
import { randomUUID } from "crypto";
|
|
2
|
+
import { getI18n } from "./i18n.js";
|
|
2
3
|
export class DiscussionManager {
|
|
4
|
+
defaultLocale;
|
|
3
5
|
sessions = new Map();
|
|
4
6
|
seenRoots = new Map();
|
|
5
7
|
seenRootTtlMs = 6 * 60 * 60 * 1000;
|
|
8
|
+
constructor(defaultLocale = "zh") {
|
|
9
|
+
this.defaultLocale = defaultLocale;
|
|
10
|
+
}
|
|
6
11
|
isActive(chatId) {
|
|
7
12
|
return this.sessions.get(chatId)?.status === "running";
|
|
8
13
|
}
|
|
@@ -26,8 +31,11 @@ export class DiscussionManager {
|
|
|
26
31
|
this.seenRoots.set(key, Date.now());
|
|
27
32
|
if (this.isActive(params.chatId))
|
|
28
33
|
this.stop(params.chatId);
|
|
29
|
-
const
|
|
30
|
-
|
|
34
|
+
const chairman = params.chairman;
|
|
35
|
+
const participants = params.participants
|
|
36
|
+
.filter((p) => p.name !== chairman?.name)
|
|
37
|
+
.filter((p, index, arr) => arr.findIndex((x) => x.name === p.name) === index);
|
|
38
|
+
if (participants.length === 0 && !chairman) {
|
|
31
39
|
this.seenRoots.delete(key);
|
|
32
40
|
return false;
|
|
33
41
|
}
|
|
@@ -36,14 +44,16 @@ export class DiscussionManager {
|
|
|
36
44
|
chatId: params.chatId,
|
|
37
45
|
rootMessageId: params.rootMessageId,
|
|
38
46
|
topic: params.topic,
|
|
39
|
-
participants: participants.map((p) => p.name),
|
|
47
|
+
participants: [...participants.map((p) => p.name), ...(chairman ? [chairman.name] : [])],
|
|
48
|
+
chairmanName: chairman?.name,
|
|
49
|
+
locale: params.locale || this.defaultLocale,
|
|
40
50
|
currentRound: 1,
|
|
41
51
|
maxRounds: params.maxRounds,
|
|
42
52
|
completedRounds: [],
|
|
43
53
|
status: "running",
|
|
44
54
|
};
|
|
45
55
|
this.sessions.set(params.chatId, session);
|
|
46
|
-
void this.runLoop(session.id, participants, params.sendSystemMessage).catch((err) => {
|
|
56
|
+
void this.runLoop(session.id, participants, chairman, params.sendSystemMessage, params.onComplete).catch((err) => {
|
|
47
57
|
console.warn(`[Discussion] loop failed for ${params.chatId}:`, err instanceof Error ? err.message : String(err));
|
|
48
58
|
const current = this.sessions.get(params.chatId);
|
|
49
59
|
if (current?.id === session.id)
|
|
@@ -57,7 +67,7 @@ export class DiscussionManager {
|
|
|
57
67
|
this.seenRoots.delete(key);
|
|
58
68
|
}
|
|
59
69
|
}
|
|
60
|
-
async runLoop(sessionId, participants, sendSystemMessage) {
|
|
70
|
+
async runLoop(sessionId, participants, chairman, sendSystemMessage, onComplete) {
|
|
61
71
|
while (true) {
|
|
62
72
|
const session = Array.from(this.sessions.values()).find((s) => s.id === sessionId);
|
|
63
73
|
if (!session || session.status !== "running")
|
|
@@ -101,62 +111,88 @@ export class DiscussionManager {
|
|
|
101
111
|
const errorNames = participants
|
|
102
112
|
.map((participant) => participant.name)
|
|
103
113
|
.filter((name) => (replies[name] || "").trim().startsWith("[ERROR]"));
|
|
104
|
-
const allNoReply = participants.every((participant) => {
|
|
114
|
+
const allNoReply = participants.length > 0 && participants.every((participant) => {
|
|
105
115
|
const text = (replies[participant.name] || "").trim();
|
|
106
116
|
return !text || text.toUpperCase() === "NO_REPLY" || text.startsWith("[ERROR]");
|
|
107
117
|
});
|
|
108
118
|
if (sendSystemMessage && !allNoReply && (noReplyNames.length > 0 || errorNames.length > 0)) {
|
|
109
119
|
const parts = [];
|
|
120
|
+
const t = getI18n(current.locale).labels;
|
|
110
121
|
if (noReplyNames.length > 0)
|
|
111
|
-
parts.push(`${noReplyNames.join("、")}
|
|
122
|
+
parts.push(`${noReplyNames.join("、")} ${t.noNewReply}`);
|
|
112
123
|
if (errorNames.length > 0)
|
|
113
|
-
parts.push(`${errorNames.join("、")}
|
|
114
|
-
await sendSystemMessage(
|
|
124
|
+
parts.push(`${errorNames.join("、")} ${t.error}`);
|
|
125
|
+
await sendSystemMessage(t.discussionRoundNotice(current.currentRound, current.maxRounds, parts.join(current.locale === "zh" ? ";" : "; "))).catch(() => { });
|
|
126
|
+
}
|
|
127
|
+
const mustFinish = allNoReply || current.currentRound >= current.maxRounds;
|
|
128
|
+
if (chairman) {
|
|
129
|
+
const chairmanPrompt = this.buildChairmanPrompt(current, replies, mustFinish);
|
|
130
|
+
const chairResult = await chairman.runDiscussionTurn(current.chatId, chairmanPrompt, { round: current.currentRound, maxRounds: current.maxRounds });
|
|
131
|
+
const chairText = (chairResult.text || "").trim();
|
|
132
|
+
replies[chairman.name] = chairText;
|
|
133
|
+
const latest = current.completedRounds[current.completedRounds.length - 1];
|
|
134
|
+
if (latest)
|
|
135
|
+
latest.replies[chairman.name] = chairText;
|
|
136
|
+
const wantsFinal = this.hasFinalSummaryMarker(chairText);
|
|
137
|
+
if (mustFinish || wantsFinal) {
|
|
138
|
+
current.status = "completed";
|
|
139
|
+
this.sessions.delete(current.chatId);
|
|
140
|
+
if (onComplete)
|
|
141
|
+
await onComplete({ chatId: current.chatId, reason: "chairman_final", chairmanName: chairman.name }).catch(() => { });
|
|
142
|
+
else if (sendSystemMessage)
|
|
143
|
+
await sendSystemMessage(getI18n(current.locale).labels.discussEndedChairman(chairman.name)).catch(() => { });
|
|
144
|
+
return;
|
|
145
|
+
}
|
|
115
146
|
}
|
|
116
|
-
if (allNoReply) {
|
|
147
|
+
else if (allNoReply) {
|
|
117
148
|
current.status = "completed";
|
|
118
149
|
this.sessions.delete(current.chatId);
|
|
150
|
+
if (onComplete)
|
|
151
|
+
await onComplete({ chatId: current.chatId, reason: "all_no_reply" }).catch(() => { });
|
|
119
152
|
if (sendSystemMessage)
|
|
120
|
-
await sendSystemMessage(
|
|
153
|
+
await sendSystemMessage(getI18n(current.locale).labels.discussEndedNoNew(current.currentRound)).catch(() => { });
|
|
121
154
|
return;
|
|
122
155
|
}
|
|
123
|
-
if (current.currentRound >= current.maxRounds) {
|
|
156
|
+
else if (current.currentRound >= current.maxRounds) {
|
|
124
157
|
current.status = "completed";
|
|
125
158
|
this.sessions.delete(current.chatId);
|
|
159
|
+
if (onComplete)
|
|
160
|
+
await onComplete({ chatId: current.chatId, reason: "max_rounds" }).catch(() => { });
|
|
126
161
|
if (sendSystemMessage)
|
|
127
|
-
await sendSystemMessage(
|
|
162
|
+
await sendSystemMessage(getI18n(current.locale).labels.discussMaxRounds(current.maxRounds)).catch(() => { });
|
|
128
163
|
return;
|
|
129
164
|
}
|
|
130
165
|
current.currentRound += 1;
|
|
131
166
|
}
|
|
132
167
|
}
|
|
168
|
+
hasFinalSummaryMarker(text) {
|
|
169
|
+
return /(^|\n)\s*FINAL_SUMMARY\s*[::]/i.test(text) || /(^|\n)\s*最终总结\s*[::]/.test(text);
|
|
170
|
+
}
|
|
171
|
+
buildChairmanPrompt(session, replies, mustFinish) {
|
|
172
|
+
const t = getI18n(session.locale);
|
|
173
|
+
const lines = Object.entries(replies).map(([bot, text]) => `- ${bot}: ${text || "NO_REPLY"}`);
|
|
174
|
+
return t.chairmanPrompt({
|
|
175
|
+
topic: session.topic,
|
|
176
|
+
round: session.currentRound,
|
|
177
|
+
maxRounds: session.maxRounds,
|
|
178
|
+
replies: lines.length ? lines.join("\n") : t.labels.noRegularReplies,
|
|
179
|
+
mustFinish,
|
|
180
|
+
});
|
|
181
|
+
}
|
|
133
182
|
buildPrompt(session) {
|
|
183
|
+
const t = getI18n(session.locale);
|
|
134
184
|
const previous = session.completedRounds.length === 0
|
|
135
|
-
?
|
|
185
|
+
? t.labels.noPreviousRounds
|
|
136
186
|
: (() => {
|
|
137
187
|
const round = session.completedRounds[session.completedRounds.length - 1];
|
|
138
188
|
const lines = Object.entries(round.replies).map(([bot, text]) => `- ${bot}: ${text || "NO_REPLY"}`);
|
|
139
189
|
return `Round ${round.round}:\n${lines.join("\n")}`;
|
|
140
190
|
})();
|
|
141
|
-
return
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
"话题:",
|
|
145
|
-
session.topic,
|
|
146
|
-
"",
|
|
147
|
-
`当前轮次:${session.currentRound}`,
|
|
148
|
-
"",
|
|
149
|
-
"已完成的轮次:",
|
|
191
|
+
return t.discussParticipantPrompt({
|
|
192
|
+
topic: session.topic,
|
|
193
|
+
round: session.currentRound,
|
|
150
194
|
previous,
|
|
151
|
-
|
|
152
|
-
"本轮其他 bot 的回复你暂时看不到,请基于同一份上下文独立给出观点。",
|
|
153
|
-
"",
|
|
154
|
-
"要求:",
|
|
155
|
-
"1. 不要重复前几轮已经说过的观点。",
|
|
156
|
-
"2. 只补充新的、有价值的信息。",
|
|
157
|
-
"3. 如果没有新东西,回复 NO_REPLY。",
|
|
158
|
-
"4. 简洁作答。",
|
|
159
|
-
].join("\n");
|
|
195
|
+
});
|
|
160
196
|
}
|
|
161
197
|
}
|
|
162
198
|
export const discussionManager = new DiscussionManager();
|
package/dist/feishu-bot.d.ts
CHANGED
|
@@ -36,6 +36,7 @@ export declare class FeishuBot {
|
|
|
36
36
|
/** Active chatSend trigger target so final replies and proactive session.message share one delivery key. */
|
|
37
37
|
private activeDeliveryTargets;
|
|
38
38
|
private adminOpenId;
|
|
39
|
+
private locale;
|
|
39
40
|
private static allBots;
|
|
40
41
|
constructor(config: BotConfig, openclawClient: OpenClawClient, store: MessageStore, adminOpenId?: string);
|
|
41
42
|
private handleMessageRecalled;
|
|
@@ -45,6 +46,10 @@ export declare class FeishuBot {
|
|
|
45
46
|
* Format: lma-<botname>-<chatId>
|
|
46
47
|
*/
|
|
47
48
|
getSessionKey(chatId: string): string;
|
|
49
|
+
private chatLocale;
|
|
50
|
+
private isEn;
|
|
51
|
+
private lmaBridgePolicy;
|
|
52
|
+
private injectBridgePolicy;
|
|
48
53
|
/**
|
|
49
54
|
* Ensure the session for a given chatId exists with the correct model.
|
|
50
55
|
* Lazy: only creates on first message in that chat.
|
|
@@ -74,6 +79,7 @@ export declare class FeishuBot {
|
|
|
74
79
|
private processQueueInner;
|
|
75
80
|
private shouldHandleBridgeCommand;
|
|
76
81
|
private shouldRespond;
|
|
82
|
+
private getRoutingIntent;
|
|
77
83
|
private isMentioned;
|
|
78
84
|
private isAllMention;
|
|
79
85
|
private isAllMentionItem;
|
|
@@ -93,8 +99,12 @@ export declare class FeishuBot {
|
|
|
93
99
|
private deliverySourceId;
|
|
94
100
|
private enqueueAndDispatchDelivery;
|
|
95
101
|
private dispatchPendingDeliveries;
|
|
102
|
+
private hasFreeModeBot;
|
|
103
|
+
private mentionedBotNames;
|
|
96
104
|
private isDiscussionCoordinator;
|
|
97
105
|
private getDiscussionParticipants;
|
|
106
|
+
private getChairmanParticipant;
|
|
107
|
+
private asDiscussionParticipant;
|
|
98
108
|
private runDiscussionTurn;
|
|
99
109
|
private replyMessage;
|
|
100
110
|
private extractBridgeAttachments;
|
|
@@ -125,7 +135,9 @@ export declare class FeishuBot {
|
|
|
125
135
|
* Finds the bot's own reaction of that type and deletes it.
|
|
126
136
|
*/
|
|
127
137
|
private removeReaction;
|
|
138
|
+
private handleLocaleCommand;
|
|
128
139
|
private handleDiscussCommand;
|
|
140
|
+
private handleChairmanCommand;
|
|
129
141
|
/**
|
|
130
142
|
* Handle /status command: show current session info.
|
|
131
143
|
*/
|