openclaw-lark-multi-agent 0.1.6 → 0.1.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/dist/feishu-bot.js +56 -22
- package/dist/message-store.d.ts +4 -1
- package/dist/message-store.js +30 -7
- package/package.json +1 -1
package/dist/feishu-bot.js
CHANGED
|
@@ -344,7 +344,7 @@ export class FeishuBot {
|
|
|
344
344
|
// Single slash commands are handled by the bridge. Double slash commands were
|
|
345
345
|
// already unescaped above and should pass through to OpenClaw instead.
|
|
346
346
|
const isBridgeCommand = !commandText.startsWith("//");
|
|
347
|
-
const isCommand = isBridgeCommand && /^\/(help|status|compact|reset|verbose|free)/.test(cleanText.trim());
|
|
347
|
+
const isCommand = isBridgeCommand && /^\/(help|status|compact|reset|verbose|free|mute|mode)/.test(cleanText.trim());
|
|
348
348
|
if (isCommand) {
|
|
349
349
|
// In group chats, bridge commands must be explicitly routed to this bot
|
|
350
350
|
// or @all. Do not let Free Discussion make a bot execute commands meant
|
|
@@ -366,7 +366,9 @@ export class FeishuBot {
|
|
|
366
366
|
`🧹 /compact — 压缩当前 bot 的 OpenClaw session`,
|
|
367
367
|
`🔄 /reset — 重置当前 bot 的 OpenClaw session`,
|
|
368
368
|
`🔊 /verbose — 开关当前聊天里的 Tool Call 显示`,
|
|
369
|
-
`🔓 /free
|
|
369
|
+
`🔓 /free — 切换当前 bot 的 free 模式(不 @ 也可回复)`,
|
|
370
|
+
`🤐 /mute — 切换当前 bot 的 mute 模式(禁言,不转发 OpenClaw)`,
|
|
371
|
+
`🎛️ /mode — 查看当前 bot 在当前群聊的模式`,
|
|
370
372
|
`❓ /help — 显示此帮助信息`,
|
|
371
373
|
``,
|
|
372
374
|
`OpenClaw 原生命令(双斜杠,会转成单斜杠发给 OpenClaw)`,
|
|
@@ -425,31 +427,63 @@ export class FeishuBot {
|
|
|
425
427
|
}
|
|
426
428
|
if (cleanText.trim().startsWith("/free")) {
|
|
427
429
|
if (chatType === "p2p") {
|
|
428
|
-
await this.replyMessage(messageId, "❌ Free
|
|
430
|
+
await this.replyMessage(messageId, "❌ Free 模式只在群聊中可用");
|
|
429
431
|
markCommandSynced();
|
|
430
432
|
return;
|
|
431
433
|
}
|
|
432
|
-
const
|
|
433
|
-
const
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
: arg === "off" || arg === "false" || arg === "0"
|
|
438
|
-
? false
|
|
439
|
-
: arg === "status"
|
|
440
|
-
? isOn
|
|
441
|
-
: !isOn;
|
|
442
|
-
if (arg !== "status")
|
|
443
|
-
this.store.setBotFreeDiscussion(this.config.name, chatId, next);
|
|
444
|
-
if (next) {
|
|
445
|
-
await this.replyMessage(messageId, `🔓 ${this.config.name} Free Discussion 已开启\n只影响当前 Bot 在当前群聊的自由发言(连续 Bot 回复超过 ${MAX_BOT_STREAK} 轮将暂停,等待人类发言)`);
|
|
434
|
+
const current = this.store.getBotMode(this.config.name, chatId);
|
|
435
|
+
const next = current === "free" ? "normal" : "free";
|
|
436
|
+
this.store.setBotMode(this.config.name, chatId, next);
|
|
437
|
+
if (next === "free") {
|
|
438
|
+
await this.replyMessage(messageId, `🔓 ${this.config.name} 已切换到 free 模式\n不需要 @ 也可以参与回复(连续 Bot 回复超过 ${MAX_BOT_STREAK} 轮将暂停,等待人类发言)`);
|
|
446
439
|
}
|
|
447
440
|
else {
|
|
448
|
-
await this.replyMessage(messageId, `🔒 ${this.config.name}
|
|
441
|
+
await this.replyMessage(messageId, `🔒 ${this.config.name} 已切换到 normal 模式\n只有明确 @ 我才会回复`);
|
|
449
442
|
}
|
|
450
443
|
markCommandSynced();
|
|
451
444
|
return;
|
|
452
445
|
}
|
|
446
|
+
if (cleanText.trim().startsWith("/mute")) {
|
|
447
|
+
if (chatType === "p2p") {
|
|
448
|
+
await this.replyMessage(messageId, "❌ Mute 模式只在群聊中可用");
|
|
449
|
+
markCommandSynced();
|
|
450
|
+
return;
|
|
451
|
+
}
|
|
452
|
+
const current = this.store.getBotMode(this.config.name, chatId);
|
|
453
|
+
const next = current === "mute" ? "normal" : "mute";
|
|
454
|
+
this.store.setBotMode(this.config.name, chatId, next);
|
|
455
|
+
if (next === "mute") {
|
|
456
|
+
await this.replyMessage(messageId, `🤐 ${this.config.name} 已切换到 mute 模式\n普通消息、@所有人 都不会回复;明确 @ 我时只提示禁言中`);
|
|
457
|
+
}
|
|
458
|
+
else {
|
|
459
|
+
await this.replyMessage(messageId, `🔒 ${this.config.name} 已解除 mute,回到 normal 模式\n只有明确 @ 我才会回复`);
|
|
460
|
+
}
|
|
461
|
+
markCommandSynced();
|
|
462
|
+
return;
|
|
463
|
+
}
|
|
464
|
+
if (cleanText.trim().startsWith("/mode")) {
|
|
465
|
+
if (chatType === "p2p") {
|
|
466
|
+
await this.replyMessage(messageId, `🎛️ ${this.config.name} 当前模式:normal(私聊总是响应)`);
|
|
467
|
+
}
|
|
468
|
+
else {
|
|
469
|
+
const mode = this.store.getBotMode(this.config.name, chatId);
|
|
470
|
+
const desc = mode === "free" ? "不需要 @ 也可以参与回复" : mode === "mute" ? "禁言中;明确 @ 我时只提示禁言中" : "只有明确 @ 我才会回复";
|
|
471
|
+
await this.replyMessage(messageId, `🎛️ ${this.config.name} 当前模式:${mode}\n${desc}`);
|
|
472
|
+
}
|
|
473
|
+
markCommandSynced();
|
|
474
|
+
return;
|
|
475
|
+
}
|
|
476
|
+
}
|
|
477
|
+
// --- Mute mode: do not forward anything to OpenClaw. Only direct mentions get a local notice. ---
|
|
478
|
+
if (chatType !== "p2p" && !isBot && this.store.getBotMode(this.config.name, chatId) === "mute") {
|
|
479
|
+
if (this.isMentioned(message.mentions || [])) {
|
|
480
|
+
await this.replyMessage(messageId, `🤐 ${this.config.name} 当前处于 mute 模式,发送 /mute 可解除`);
|
|
481
|
+
if (insertedId > 0) {
|
|
482
|
+
this.store.markSynced(this.config.name, chatId, insertedId);
|
|
483
|
+
this.store.clearPendingTriggers(this.config.name, chatId, insertedId);
|
|
484
|
+
}
|
|
485
|
+
}
|
|
486
|
+
return;
|
|
453
487
|
}
|
|
454
488
|
// --- Should this bot respond? ---
|
|
455
489
|
if (!this.shouldRespond(chatType, message, isBot, chatId, message.content))
|
|
@@ -663,9 +697,9 @@ export class FeishuBot {
|
|
|
663
697
|
const anyBotMentioned = mentions.some((m) => this.mentionedBotName(m) !== null);
|
|
664
698
|
if (anyBotMentioned && !this.isMentioned(mentions))
|
|
665
699
|
return false;
|
|
666
|
-
// No bot mentioned: check
|
|
700
|
+
// No bot mentioned: check current per-bot mode
|
|
667
701
|
if (chatId) {
|
|
668
|
-
if (this.store.
|
|
702
|
+
if (this.store.getBotMode(this.config.name, chatId) === "free")
|
|
669
703
|
return true;
|
|
670
704
|
}
|
|
671
705
|
// Default: don't respond without @
|
|
@@ -1000,7 +1034,7 @@ ${doc.url}`);
|
|
|
1000
1034
|
const sessionExists = session ? "✅ 活跃" : "⏳ 未初始化";
|
|
1001
1035
|
const status = session?.status || "unknown";
|
|
1002
1036
|
const verboseStatus = this.store.getBotVerbose(this.config.name, chatId) ? "🔊 开启" : "🔇 关闭";
|
|
1003
|
-
const
|
|
1037
|
+
const mode = chatType === "p2p" ? "normal" : this.store.getBotMode(this.config.name, chatId);
|
|
1004
1038
|
const statusText = [
|
|
1005
1039
|
`📊 ${this.config.name} Bot Status`,
|
|
1006
1040
|
`━━━━━━━━━━━━━━━━━━`,
|
|
@@ -1013,7 +1047,7 @@ ${doc.url}`);
|
|
|
1013
1047
|
`🧮 上下文: ${fmtK(totalTokens)} / ${fmtK(contextTokens)} (${usedPct}%)${tokenNote}`,
|
|
1014
1048
|
`📥 输入: ${fmtK(inputTokens)} | 📤 输出: ${fmtK(outputTokens)}`,
|
|
1015
1049
|
`🔧 Verbose: ${verboseStatus}`,
|
|
1016
|
-
|
|
1050
|
+
`🎛️ Mode: ${mode}`,
|
|
1017
1051
|
].join("\n");
|
|
1018
1052
|
await this.replyMessage(messageId, statusText);
|
|
1019
1053
|
}
|
package/dist/message-store.d.ts
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
export type BotChatMode = "normal" | "free" | "mute";
|
|
1
2
|
export interface ChatInfo {
|
|
2
3
|
chatId: string;
|
|
3
4
|
chatType: "p2p" | "group";
|
|
@@ -8,7 +9,7 @@ export interface ChatInfo {
|
|
|
8
9
|
memberNames: string;
|
|
9
10
|
/** Which bot owns this chat (for p2p isolation) */
|
|
10
11
|
ownerBot: string;
|
|
11
|
-
/**
|
|
12
|
+
/** Legacy chat-level free discussion flag; per-bot mode is authoritative. */
|
|
12
13
|
freeDiscussion: boolean;
|
|
13
14
|
verbose: boolean;
|
|
14
15
|
updatedAt: number;
|
|
@@ -58,6 +59,8 @@ export declare class MessageStore {
|
|
|
58
59
|
setVerbose(chatId: string, verbose: boolean): void;
|
|
59
60
|
setBotVerbose(botName: string, chatId: string, verbose: boolean): void;
|
|
60
61
|
getBotVerbose(botName: string, chatId: string): boolean;
|
|
62
|
+
setBotMode(botName: string, chatId: string, mode: BotChatMode): void;
|
|
63
|
+
getBotMode(botName: string, chatId: string): BotChatMode;
|
|
61
64
|
setBotFreeDiscussion(botName: string, chatId: string, on: boolean): void;
|
|
62
65
|
getBotFreeDiscussion(botName: string, chatId: string): boolean;
|
|
63
66
|
getChatInfo(chatId: string): ChatInfo | null;
|
package/dist/message-store.js
CHANGED
|
@@ -76,6 +76,7 @@ export class MessageStore {
|
|
|
76
76
|
chat_id TEXT NOT NULL,
|
|
77
77
|
verbose INTEGER NOT NULL DEFAULT 0,
|
|
78
78
|
free_discussion INTEGER NOT NULL DEFAULT 0,
|
|
79
|
+
mode TEXT NOT NULL DEFAULT 'normal',
|
|
79
80
|
updated_at INTEGER NOT NULL DEFAULT 0,
|
|
80
81
|
PRIMARY KEY (bot_name, chat_id)
|
|
81
82
|
);
|
|
@@ -108,6 +109,18 @@ export class MessageStore {
|
|
|
108
109
|
catch {
|
|
109
110
|
// Column already exists
|
|
110
111
|
}
|
|
112
|
+
// Migration: replace independent free/mute booleans with one mutually exclusive mode.
|
|
113
|
+
try {
|
|
114
|
+
this.db.exec(`ALTER TABLE bot_chat_settings ADD COLUMN mode TEXT NOT NULL DEFAULT 'normal'`);
|
|
115
|
+
}
|
|
116
|
+
catch {
|
|
117
|
+
// Column already exists
|
|
118
|
+
}
|
|
119
|
+
this.db.exec(`
|
|
120
|
+
UPDATE bot_chat_settings
|
|
121
|
+
SET mode = 'free'
|
|
122
|
+
WHERE free_discussion = 1 AND (mode IS NULL OR mode = '' OR mode = 'normal')
|
|
123
|
+
`);
|
|
111
124
|
}
|
|
112
125
|
/**
|
|
113
126
|
* Insert a message. Returns the auto-increment id, or -1 if duplicate.
|
|
@@ -278,21 +291,31 @@ export class MessageStore {
|
|
|
278
291
|
`).get(botName, chatId);
|
|
279
292
|
return !!row?.verbose;
|
|
280
293
|
}
|
|
281
|
-
|
|
294
|
+
setBotMode(botName, chatId, mode) {
|
|
295
|
+
const freeDiscussion = mode === "free" ? 1 : 0;
|
|
282
296
|
this.db.prepare(`
|
|
283
|
-
INSERT INTO bot_chat_settings (bot_name, chat_id, free_discussion, updated_at)
|
|
284
|
-
VALUES (?, ?, ?, ?)
|
|
297
|
+
INSERT INTO bot_chat_settings (bot_name, chat_id, mode, free_discussion, updated_at)
|
|
298
|
+
VALUES (?, ?, ?, ?, ?)
|
|
285
299
|
ON CONFLICT (bot_name, chat_id) DO UPDATE SET
|
|
300
|
+
mode = excluded.mode,
|
|
286
301
|
free_discussion = excluded.free_discussion,
|
|
287
302
|
updated_at = excluded.updated_at
|
|
288
|
-
`).run(botName, chatId,
|
|
303
|
+
`).run(botName, chatId, mode, freeDiscussion, Date.now());
|
|
289
304
|
}
|
|
290
|
-
|
|
305
|
+
getBotMode(botName, chatId) {
|
|
291
306
|
const row = this.db.prepare(`
|
|
292
|
-
SELECT free_discussion FROM bot_chat_settings
|
|
307
|
+
SELECT mode, free_discussion FROM bot_chat_settings
|
|
293
308
|
WHERE bot_name = ? AND chat_id = ?
|
|
294
309
|
`).get(botName, chatId);
|
|
295
|
-
|
|
310
|
+
if (row?.mode === "free" || row?.mode === "mute" || row?.mode === "normal")
|
|
311
|
+
return row.mode;
|
|
312
|
+
return row?.free_discussion ? "free" : "normal";
|
|
313
|
+
}
|
|
314
|
+
setBotFreeDiscussion(botName, chatId, on) {
|
|
315
|
+
this.setBotMode(botName, chatId, on ? "free" : "normal");
|
|
316
|
+
}
|
|
317
|
+
getBotFreeDiscussion(botName, chatId) {
|
|
318
|
+
return this.getBotMode(botName, chatId) === "free";
|
|
296
319
|
}
|
|
297
320
|
getChatInfo(chatId) {
|
|
298
321
|
const row = this.db.prepare(`SELECT * FROM chat_info WHERE chat_id = ?`).get(chatId);
|