mioku-plugin-chat 3.0.5 → 3.1.0
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/configs/base.ts +1 -0
- package/context.ts +5 -5
- package/core/base.ts +20 -20
- package/core/chat-engine.ts +6 -6
- package/core/chat-turn.ts +30 -26
- package/core/media/history-media.ts +13 -13
- package/core/media/segment.ts +2 -2
- package/core/prompt.ts +1 -1
- package/core/tools/info.ts +9 -4
- package/core/tools/web.ts +6 -9
- package/db.ts +13 -13
- package/handlers/idle-debug.ts +7 -7
- package/handlers/message.ts +7 -8
- package/handlers/poke.ts +15 -14
- package/humanize/emotion-agent.ts +5 -5
- package/humanize/expression.ts +6 -6
- package/humanize/index.ts +1 -1
- package/humanize/memory.ts +1 -1
- package/index.ts +30 -22
- package/manage/cooldown.ts +8 -8
- package/manage/group-structured-history.ts +2 -2
- package/manage/idle-check.ts +12 -11
- package/manage/queue-processor.ts +9 -8
- package/manage/rate-limit-guard.ts +2 -2
- package/manage/rate-limiter.ts +21 -21
- package/manage/session.ts +1 -1
- package/manage/types.ts +16 -16
- package/package.json +1 -1
- package/runtime/chat-runtime.ts +4 -4
- package/types.ts +12 -11
- package/utils/config.ts +4 -4
- package/utils/group-config.ts +3 -4
- package/utils/message.ts +21 -21
- package/utils/queue.ts +12 -11
package/handlers/poke.ts
CHANGED
|
@@ -15,12 +15,13 @@ export function createPokeHandler(
|
|
|
15
15
|
const { getConfig, runtimeState, pokeCooldowns } = state;
|
|
16
16
|
|
|
17
17
|
return async (e: NoticeEvent) => {
|
|
18
|
-
const selfId =
|
|
19
|
-
const
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
18
|
+
const selfId = String(e.self_id ?? "").trim();
|
|
19
|
+
const raw = e.raw as { target_id?: unknown; user_id?: unknown } | undefined;
|
|
20
|
+
const targetId = String(
|
|
21
|
+
raw?.target_id ?? raw?.user_id ?? e.user_id ?? "",
|
|
22
|
+
).trim();
|
|
23
|
+
if (!targetId || targetId !== selfId) return;
|
|
24
|
+
const groupId = String(e.group_id ?? "").trim();
|
|
24
25
|
const cfg = groupId ? await getConfig(groupId) : await getConfig();
|
|
25
26
|
if (!cfg.model && !cfg.apiKey) return;
|
|
26
27
|
if (!groupId || !isGroupAllowed(groupId, cfg)) return;
|
|
@@ -42,10 +43,9 @@ export function createPokeHandler(
|
|
|
42
43
|
"group",
|
|
43
44
|
groupId,
|
|
44
45
|
);
|
|
45
|
-
const userId =
|
|
46
|
+
const userId = String(e.user_id ?? "").trim();
|
|
46
47
|
const botRole = await getBotRole(groupId, ctx, selfId);
|
|
47
|
-
const botNickname =
|
|
48
|
-
cfg.nicknames[0] || e.bot?.nickname || "Bot";
|
|
48
|
+
const botNickname = cfg.nicknames[0] || e.bot?.nickname || "Bot";
|
|
49
49
|
|
|
50
50
|
let senderName = String(userId);
|
|
51
51
|
try {
|
|
@@ -54,9 +54,7 @@ export function createPokeHandler(
|
|
|
54
54
|
? await bot.getMemberInfo(groupId, userId)
|
|
55
55
|
: undefined;
|
|
56
56
|
senderName =
|
|
57
|
-
memberInfo?.card ||
|
|
58
|
-
memberInfo?.nickname ||
|
|
59
|
-
String(userId);
|
|
57
|
+
memberInfo?.card || memberInfo?.nickname || String(userId);
|
|
60
58
|
} catch {}
|
|
61
59
|
|
|
62
60
|
const targetMessage: TargetMessage = {
|
|
@@ -76,8 +74,11 @@ export function createPokeHandler(
|
|
|
76
74
|
selfId,
|
|
77
75
|
pluginCtx.buildHistoryMediaOptions(pluginCtx.aiInstance, cfg),
|
|
78
76
|
);
|
|
79
|
-
const { groupName, memberCount } =
|
|
80
|
-
|
|
77
|
+
const { groupName, memberCount } = await pluginCtx.getGroupInfoData(
|
|
78
|
+
ctx,
|
|
79
|
+
groupId,
|
|
80
|
+
selfId,
|
|
81
|
+
);
|
|
81
82
|
|
|
82
83
|
const toolCtx = pluginCtx.buildToolContext({
|
|
83
84
|
ctx,
|
|
@@ -38,7 +38,7 @@ export class EmotionAgent {
|
|
|
38
38
|
return state;
|
|
39
39
|
}
|
|
40
40
|
|
|
41
|
-
getAvailableEmotions(groupId?:
|
|
41
|
+
getAvailableEmotions(groupId?: string): string[] {
|
|
42
42
|
const cfg = this.getConfig(groupId);
|
|
43
43
|
const emotions = Object.keys(cfg.emotion?.emotions || {})
|
|
44
44
|
.map((name) => name.trim().toLowerCase())
|
|
@@ -46,7 +46,7 @@ export class EmotionAgent {
|
|
|
46
46
|
return Array.from(new Set(["default", ...emotions]));
|
|
47
47
|
}
|
|
48
48
|
|
|
49
|
-
getReferenceExamples(emotion: string, groupId?:
|
|
49
|
+
getReferenceExamples(emotion: string, groupId?: string): string[] {
|
|
50
50
|
const cfg = this.getConfig(groupId);
|
|
51
51
|
const normalized = this.normalizeEmotionName(emotion);
|
|
52
52
|
const emotions = cfg.emotion?.emotions || {};
|
|
@@ -55,7 +55,7 @@ export class EmotionAgent {
|
|
|
55
55
|
return this.normalizeExamples(emotions[this.getDefaultEmotion(groupId)]?.examples);
|
|
56
56
|
}
|
|
57
57
|
|
|
58
|
-
setEmotion(sessionId: string, emotion: string, groupId?:
|
|
58
|
+
setEmotion(sessionId: string, emotion: string, groupId?: string): EmotionState {
|
|
59
59
|
const current = this.resolveEmotion(emotion, groupId);
|
|
60
60
|
const state = { current, updatedAt: Date.now() };
|
|
61
61
|
this.states.set(sessionId, state);
|
|
@@ -144,14 +144,14 @@ ${input.targetMessage.userName}: ${input.targetMessage.content}`;
|
|
|
144
144
|
return this.resolveEmotion(parsed?.emotion, extractGroupIdFromSession(input.sessionId));
|
|
145
145
|
}
|
|
146
146
|
|
|
147
|
-
private resolveEmotion(emotion: unknown, groupId?:
|
|
147
|
+
private resolveEmotion(emotion: unknown, groupId?: string): string {
|
|
148
148
|
const normalized = this.normalizeEmotionName(emotion);
|
|
149
149
|
const available = this.getAvailableEmotions(groupId);
|
|
150
150
|
if (available.includes(normalized)) return normalized;
|
|
151
151
|
return this.getDefaultEmotion(groupId);
|
|
152
152
|
}
|
|
153
153
|
|
|
154
|
-
private getDefaultEmotion(groupId?:
|
|
154
|
+
private getDefaultEmotion(groupId?: string): string {
|
|
155
155
|
const cfg = this.getConfig(groupId);
|
|
156
156
|
const configured = this.normalizeEmotionName(cfg.emotion?.defaultEmotion);
|
|
157
157
|
const available = this.getAvailableEmotions(groupId);
|
package/humanize/expression.ts
CHANGED
|
@@ -9,8 +9,8 @@ export class ExpressionLearner {
|
|
|
9
9
|
private ai: AIInstance;
|
|
10
10
|
private getConfig: ChatConfigProvider;
|
|
11
11
|
private db: ChatDatabase;
|
|
12
|
-
private pendingMessagesByUser: Map<
|
|
13
|
-
private learningUsers: Set<
|
|
12
|
+
private pendingMessagesByUser: Map<string, ChatMessage[]> = new Map();
|
|
13
|
+
private learningUsers: Set<string> = new Set();
|
|
14
14
|
|
|
15
15
|
constructor(ai: AIInstance, configProvider: ChatConfigProvider, db: ChatDatabase) {
|
|
16
16
|
this.ai = ai;
|
|
@@ -33,9 +33,9 @@ export class ExpressionLearner {
|
|
|
33
33
|
}
|
|
34
34
|
|
|
35
35
|
getExpressionContextForUser(
|
|
36
|
-
userId:
|
|
36
|
+
userId: string,
|
|
37
37
|
userName: string,
|
|
38
|
-
groupId?:
|
|
38
|
+
groupId?: string,
|
|
39
39
|
): string {
|
|
40
40
|
const cfg = this.getConfig(groupId);
|
|
41
41
|
if (!cfg.expression?.enabled) return "";
|
|
@@ -62,7 +62,7 @@ export class ExpressionLearner {
|
|
|
62
62
|
return context;
|
|
63
63
|
}
|
|
64
64
|
|
|
65
|
-
private async tryLearn(userId:
|
|
65
|
+
private async tryLearn(userId: string, cfg = this.getConfig()): Promise<void> {
|
|
66
66
|
if (this.learningUsers.has(userId)) return;
|
|
67
67
|
|
|
68
68
|
const threshold = Math.max(
|
|
@@ -95,7 +95,7 @@ export class ExpressionLearner {
|
|
|
95
95
|
}
|
|
96
96
|
|
|
97
97
|
private async learnForUser(
|
|
98
|
-
userId:
|
|
98
|
+
userId: string,
|
|
99
99
|
messages: ChatMessage[],
|
|
100
100
|
cfg = this.getConfig(),
|
|
101
101
|
): Promise<void> {
|
package/humanize/index.ts
CHANGED
|
@@ -16,7 +16,7 @@ import { TopicTracker } from "./topic";
|
|
|
16
16
|
import { ActionPlanner } from "./planner";
|
|
17
17
|
import { ExpressionLearner } from "./expression";
|
|
18
18
|
|
|
19
|
-
export type ChatConfigProvider = (groupId?:
|
|
19
|
+
export type ChatConfigProvider = (groupId?: string) => ChatConfig;
|
|
20
20
|
|
|
21
21
|
export class HumanizeEngine {
|
|
22
22
|
readonly memoryRetrieval: MemoryRetrieval;
|
package/humanize/memory.ts
CHANGED
package/index.ts
CHANGED
|
@@ -261,7 +261,7 @@ export default definePlugin({
|
|
|
261
261
|
});
|
|
262
262
|
}
|
|
263
263
|
|
|
264
|
-
const configProvider: ChatConfigProvider = (groupId?:
|
|
264
|
+
const configProvider: ChatConfigProvider = (groupId?: string) => {
|
|
265
265
|
const base = cachedBaseConfig;
|
|
266
266
|
if (!base) return PERSONALIZATION_CONFIG as unknown as ChatConfig;
|
|
267
267
|
if (groupId === undefined) return base;
|
|
@@ -269,7 +269,7 @@ export default definePlugin({
|
|
|
269
269
|
return mergeGroupOverrides(base, overrides);
|
|
270
270
|
};
|
|
271
271
|
|
|
272
|
-
const getConfig = async (groupId?:
|
|
272
|
+
const getConfig = async (groupId?: string): Promise<ChatConfig> => {
|
|
273
273
|
if (!cachedBaseConfig) await refreshBaseCache();
|
|
274
274
|
return configProvider(groupId);
|
|
275
275
|
};
|
|
@@ -380,14 +380,14 @@ roleModels = resolved.models;
|
|
|
380
380
|
rateLimiter.setConfigProvider(configProvider);
|
|
381
381
|
const skillManager = new SkillSessionManager();
|
|
382
382
|
|
|
383
|
-
rateLimiter.setQueueLengthGetter((groupId:
|
|
383
|
+
rateLimiter.setQueueLengthGetter((groupId: string) =>
|
|
384
384
|
queueManager.getQueueLength(`group:${groupId}`),
|
|
385
385
|
);
|
|
386
386
|
|
|
387
387
|
const humanize = new HumanizeEngine(workAIInstance, db, configProvider);
|
|
388
388
|
await humanize.init();
|
|
389
389
|
|
|
390
|
-
const pokeCooldowns = new Map<
|
|
390
|
+
const pokeCooldowns = new Map<string, number>();
|
|
391
391
|
const processingSet = new Set<string>();
|
|
392
392
|
const sessionTurnScheduler = new SessionTurnScheduler();
|
|
393
393
|
const groupStructuredHistory = new GroupStructuredHistoryManager();
|
|
@@ -442,8 +442,8 @@ roleModels = resolved.models;
|
|
|
442
442
|
audioService,
|
|
443
443
|
startCooldownTimer: (
|
|
444
444
|
groupSessionId: string,
|
|
445
|
-
groupId:
|
|
446
|
-
selfId:
|
|
445
|
+
groupId: string,
|
|
446
|
+
selfId: string,
|
|
447
447
|
) => cooldownManager.startCooldownTimer(groupSessionId, groupId, selfId),
|
|
448
448
|
async recordGroupMessageForLearning(
|
|
449
449
|
userMsg: ChatMessage,
|
|
@@ -511,20 +511,25 @@ roleModels = resolved.models;
|
|
|
511
511
|
aiService.registerChatRuntime(runtime);
|
|
512
512
|
|
|
513
513
|
ctx.command({
|
|
514
|
-
name: "
|
|
515
|
-
|
|
516
|
-
|
|
514
|
+
name: "重置会话",
|
|
515
|
+
prefixes: ["/", "."],
|
|
516
|
+
match: /^重置会话$/,
|
|
517
517
|
description: "重置自己的AI聊天记录",
|
|
518
|
+
usage: ".重置会话",
|
|
518
519
|
handler: async ({ event }) => {
|
|
519
|
-
if (event.user_id === event.self_id) return;
|
|
520
|
+
if (String(event.user_id ?? "") === String(event.self_id ?? "")) return;
|
|
520
521
|
const groupId =
|
|
521
|
-
event.message_type === "group"
|
|
522
|
+
event.message_type === "group"
|
|
523
|
+
? String(event.group_id ?? "").trim() || undefined
|
|
524
|
+
: undefined;
|
|
522
525
|
if (groupId) {
|
|
523
526
|
pluginCtx.sessionManager.resetBotMessages(`group:${groupId}`);
|
|
524
527
|
pluginCtx.groupStructuredHistory.clear(`group:${groupId}`);
|
|
525
528
|
await event.reply("已清除本群会话中 AI 发送的消息~");
|
|
526
529
|
} else {
|
|
527
|
-
const userId =
|
|
530
|
+
const userId = String(
|
|
531
|
+
event.user_id ?? event.sender?.user_id ?? "",
|
|
532
|
+
).trim();
|
|
528
533
|
pluginCtx.sessionManager.resetBotMessages(`personal:${userId}`);
|
|
529
534
|
pluginCtx.groupStructuredHistory.clear(`personal:${userId}`);
|
|
530
535
|
await event.reply("已清除你的个人会话中 AI 发送的消息~");
|
|
@@ -532,15 +537,18 @@ roleModels = resolved.models;
|
|
|
532
537
|
},
|
|
533
538
|
});
|
|
534
539
|
ctx.command({
|
|
535
|
-
name: "
|
|
536
|
-
|
|
537
|
-
|
|
540
|
+
name: "重置群会话",
|
|
541
|
+
prefixes: ["/", "."],
|
|
542
|
+
match: /^重置群会话$/,
|
|
538
543
|
permission: "admin",
|
|
539
544
|
description: "重置当前群的AI聊天记录",
|
|
545
|
+
usage: ".重置群会话",
|
|
540
546
|
handler: async ({ event }) => {
|
|
541
|
-
if (event.user_id === event.self_id) return;
|
|
547
|
+
if (String(event.user_id ?? "") === String(event.self_id ?? "")) return;
|
|
542
548
|
const groupId =
|
|
543
|
-
event.message_type === "group"
|
|
549
|
+
event.message_type === "group"
|
|
550
|
+
? String(event.group_id ?? "").trim() || undefined
|
|
551
|
+
: undefined;
|
|
544
552
|
if (!groupId) {
|
|
545
553
|
await event.reply("在群里使用试试看吧~", true);
|
|
546
554
|
return;
|
|
@@ -551,14 +559,14 @@ roleModels = resolved.models;
|
|
|
551
559
|
},
|
|
552
560
|
});
|
|
553
561
|
ctx.command({
|
|
554
|
-
name: "
|
|
555
|
-
|
|
556
|
-
|
|
562
|
+
name: "tts",
|
|
563
|
+
prefixes: ["/", "."],
|
|
564
|
+
match: /^tts\s*(.*)$/,
|
|
557
565
|
permission: "owner",
|
|
558
566
|
description: "TTS 推理",
|
|
559
|
-
usage: "
|
|
567
|
+
usage: ".tts <文本>",
|
|
560
568
|
handler: async ({ event, match }) => {
|
|
561
|
-
if (event.user_id === event.self_id) return;
|
|
569
|
+
if (String(event.user_id ?? "") === String(event.self_id ?? "")) return;
|
|
562
570
|
await handleTtsCommand(pluginCtx, event, String(match?.[1] ?? "").trim());
|
|
563
571
|
},
|
|
564
572
|
});
|
package/manage/cooldown.ts
CHANGED
|
@@ -26,7 +26,7 @@ interface CooldownMessage {
|
|
|
26
26
|
event: any;
|
|
27
27
|
content: string;
|
|
28
28
|
userName: string;
|
|
29
|
-
userId:
|
|
29
|
+
userId: string;
|
|
30
30
|
messageId: string;
|
|
31
31
|
timestamp: number;
|
|
32
32
|
isDirectAt: boolean;
|
|
@@ -86,8 +86,8 @@ export class CooldownManager {
|
|
|
86
86
|
|
|
87
87
|
startCooldownTimer(
|
|
88
88
|
groupSessionId: string,
|
|
89
|
-
groupId:
|
|
90
|
-
selfId:
|
|
89
|
+
groupId: string,
|
|
90
|
+
selfId: string,
|
|
91
91
|
): void {
|
|
92
92
|
const existingTimer = this.cooldownTimeoutIds.get(groupSessionId);
|
|
93
93
|
if (existingTimer) clearTimeout(existingTimer);
|
|
@@ -142,7 +142,7 @@ export class CooldownManager {
|
|
|
142
142
|
|
|
143
143
|
collectMessage(
|
|
144
144
|
groupSessionId: string,
|
|
145
|
-
groupId:
|
|
145
|
+
groupId: string,
|
|
146
146
|
event: any,
|
|
147
147
|
content: string,
|
|
148
148
|
isDirectAt: boolean,
|
|
@@ -175,9 +175,9 @@ export class CooldownManager {
|
|
|
175
175
|
|
|
176
176
|
private async processReviewMessages(
|
|
177
177
|
groupSessionId: string,
|
|
178
|
-
groupId:
|
|
178
|
+
groupId: string,
|
|
179
179
|
collected: CooldownMessage[],
|
|
180
|
-
selfId:
|
|
180
|
+
selfId: string,
|
|
181
181
|
): Promise<void> {
|
|
182
182
|
const mergedContents = collected.map((m) => m.content);
|
|
183
183
|
const userNames = collected.map((m) => m.userName);
|
|
@@ -297,9 +297,9 @@ export class CooldownManager {
|
|
|
297
297
|
|
|
298
298
|
private async processCooldownWithPlanner(
|
|
299
299
|
groupSessionId: string,
|
|
300
|
-
groupId:
|
|
300
|
+
groupId: string,
|
|
301
301
|
collected: CooldownMessage[],
|
|
302
|
-
selfId:
|
|
302
|
+
selfId: string,
|
|
303
303
|
): Promise<void> {
|
|
304
304
|
const mergedContent = collected.map((m) => m.content).join("\n");
|
|
305
305
|
const firstMsg = collected[0];
|
|
@@ -4,7 +4,7 @@ import type { TargetMessage } from "../types";
|
|
|
4
4
|
|
|
5
5
|
export interface StructuredUserInput {
|
|
6
6
|
userName: string;
|
|
7
|
-
userId:
|
|
7
|
+
userId: string;
|
|
8
8
|
userRole?: string;
|
|
9
9
|
userTitle?: string;
|
|
10
10
|
content: string;
|
|
@@ -192,7 +192,7 @@ function formatStructuredUserContent(input: StructuredUserInput): string {
|
|
|
192
192
|
const lines = [
|
|
193
193
|
"[Group Member Message]",
|
|
194
194
|
`Name: ${input.userName || "unknown"}`,
|
|
195
|
-
`
|
|
195
|
+
`user_id: ${input.userId || "unknown"}`,
|
|
196
196
|
`Time: ${formatTimestamp(input.timestamp)}`,
|
|
197
197
|
];
|
|
198
198
|
|
package/manage/idle-check.ts
CHANGED
|
@@ -25,7 +25,7 @@ export class IdleCheckManager {
|
|
|
25
25
|
private groupLastBotMessageTime = new Map<string, number>();
|
|
26
26
|
private groupMessageCountAfterBot = new Map<string, number>();
|
|
27
27
|
private groupLastIdleCheckTime = new Map<string, number>();
|
|
28
|
-
private groupBotsMapping = new Map<string, Set<
|
|
28
|
+
private groupBotsMapping = new Map<string, Set<string>>();
|
|
29
29
|
private intervalHandle: NodeJS.Timeout | null = null;
|
|
30
30
|
|
|
31
31
|
private ctx: MiokuContext;
|
|
@@ -71,11 +71,11 @@ export class IdleCheckManager {
|
|
|
71
71
|
this.groupMessageCount.set(groupSessionId, count + 1);
|
|
72
72
|
}
|
|
73
73
|
|
|
74
|
-
recordBotMessages(groupSessionId: string, count: number, selfId:
|
|
74
|
+
recordBotMessages(groupSessionId: string, count: number, selfId: string): void {
|
|
75
75
|
const current = this.groupMessageCountAfterBot.get(groupSessionId) ?? 0;
|
|
76
76
|
this.groupMessageCountAfterBot.set(groupSessionId, current + count);
|
|
77
77
|
let bots = this.groupBotsMapping.get(groupSessionId);
|
|
78
|
-
if (!bots) { bots = new Set<
|
|
78
|
+
if (!bots) { bots = new Set<string>(); this.groupBotsMapping.set(groupSessionId, bots); }
|
|
79
79
|
bots.add(selfId);
|
|
80
80
|
this.groupLastBotMessageTime.set(groupSessionId, Date.now());
|
|
81
81
|
}
|
|
@@ -106,14 +106,15 @@ export class IdleCheckManager {
|
|
|
106
106
|
|
|
107
107
|
const now = Date.now();
|
|
108
108
|
const checkInterval = 60_000;
|
|
109
|
-
const allBotIds = Array.from(this.ctx.bots).map((bot) =>
|
|
109
|
+
const allBotIds = Array.from(this.ctx.bots).map((bot) => String(bot.bot_id));
|
|
110
110
|
|
|
111
111
|
for (const [groupSessionId, lastTime] of this.groupLastActivityTime) {
|
|
112
112
|
const lastCheckTime =
|
|
113
113
|
this.groupLastIdleCheckTime.get(groupSessionId) ?? 0;
|
|
114
114
|
if (now - lastCheckTime < checkInterval) continue;
|
|
115
115
|
|
|
116
|
-
const groupId =
|
|
116
|
+
const groupId = String(groupSessionId.split(":")[1] ?? "").trim();
|
|
117
|
+
if (!groupId) continue;
|
|
117
118
|
const cfg = this.configProvider(groupId);
|
|
118
119
|
if (!cfg.planner?.enabled) continue;
|
|
119
120
|
if (!isGroupAllowed(groupId, cfg)) continue;
|
|
@@ -130,7 +131,7 @@ export class IdleCheckManager {
|
|
|
130
131
|
cfg.planner.idleCheckBotIds ??
|
|
131
132
|
baseCfg.planner?.idleCheckBotIds ??
|
|
132
133
|
allBotIds;
|
|
133
|
-
const enabledBotIds = idleCheckBotIds.filter((id:
|
|
134
|
+
const enabledBotIds = idleCheckBotIds.filter((id: string) =>
|
|
134
135
|
allBotIds.includes(id),
|
|
135
136
|
);
|
|
136
137
|
|
|
@@ -193,8 +194,8 @@ export class IdleCheckManager {
|
|
|
193
194
|
|
|
194
195
|
private async processIdleCheckTurn(
|
|
195
196
|
groupSessionId: string,
|
|
196
|
-
groupId:
|
|
197
|
-
selfId:
|
|
197
|
+
groupId: string,
|
|
198
|
+
selfId: string,
|
|
198
199
|
cfg: ChatConfig,
|
|
199
200
|
scheduledLastActivity: number,
|
|
200
201
|
scheduledAt: number,
|
|
@@ -234,7 +235,7 @@ export class IdleCheckManager {
|
|
|
234
235
|
|
|
235
236
|
const targetMessage: TargetMessage = {
|
|
236
237
|
userName: "system",
|
|
237
|
-
userId:
|
|
238
|
+
userId: "",
|
|
238
239
|
userRole: "member",
|
|
239
240
|
content: "[No one in the group is talking? I'll answer!]",
|
|
240
241
|
messageId: "",
|
|
@@ -246,7 +247,7 @@ export class IdleCheckManager {
|
|
|
246
247
|
event: null,
|
|
247
248
|
groupSessionId,
|
|
248
249
|
groupId,
|
|
249
|
-
userId:
|
|
250
|
+
userId: "",
|
|
250
251
|
config: cfg,
|
|
251
252
|
aiService: this.aiService,
|
|
252
253
|
db: this.db,
|
|
@@ -292,7 +293,7 @@ export class IdleCheckManager {
|
|
|
292
293
|
result,
|
|
293
294
|
groupId,
|
|
294
295
|
groupSessionId,
|
|
295
|
-
userId:
|
|
296
|
+
userId: "",
|
|
296
297
|
selfId,
|
|
297
298
|
toolCtx,
|
|
298
299
|
send: true,
|
|
@@ -25,7 +25,7 @@ import { finalizeChatTurn } from "../core/chat-turn";
|
|
|
25
25
|
import { extractContent, getBotRole } from "../utils";
|
|
26
26
|
|
|
27
27
|
interface DynamicDelayQueueData {
|
|
28
|
-
messages: Array<{ event: any; content: string; userName: string; userId:
|
|
28
|
+
messages: Array<{ event: any; content: string; userName: string; userId: string; messageId: string; timestamp: number }>;
|
|
29
29
|
timer: NodeJS.Timeout | null;
|
|
30
30
|
delayUntil: number;
|
|
31
31
|
}
|
|
@@ -91,7 +91,7 @@ export class QueueProcessor {
|
|
|
91
91
|
queueData.messages.push({ event, content, userName, userId: event.user_id, messageId: event.message_id, timestamp: Date.now() });
|
|
92
92
|
}
|
|
93
93
|
|
|
94
|
-
startDynamicDelayTimer(groupSessionId: string, groupId:
|
|
94
|
+
startDynamicDelayTimer(groupSessionId: string, groupId: string, delayMs: number, selfId: string): void {
|
|
95
95
|
let queueData = this.dynamicDelayQueues.get(groupSessionId);
|
|
96
96
|
if (!queueData) {
|
|
97
97
|
queueData = { messages: [], timer: null, delayUntil: Date.now() + delayMs };
|
|
@@ -132,7 +132,7 @@ export class QueueProcessor {
|
|
|
132
132
|
return queue != null && Date.now() < queue.delayUntil;
|
|
133
133
|
}
|
|
134
134
|
|
|
135
|
-
scheduleQueuedMessages(groupSessionId: string, selfId:
|
|
135
|
+
scheduleQueuedMessages(groupSessionId: string, selfId: string): void {
|
|
136
136
|
void this.sessionTurnScheduler
|
|
137
137
|
.run(
|
|
138
138
|
groupSessionId,
|
|
@@ -156,8 +156,8 @@ export class QueueProcessor {
|
|
|
156
156
|
|
|
157
157
|
private async processDynamicDelayMessages(
|
|
158
158
|
groupSessionId: string,
|
|
159
|
-
groupId:
|
|
160
|
-
selfId:
|
|
159
|
+
groupId: string,
|
|
160
|
+
selfId: string,
|
|
161
161
|
messages: DynamicDelayQueueData["messages"],
|
|
162
162
|
): Promise<void> {
|
|
163
163
|
this.ctx.logger.info(`[DynamicDelay] group ${groupId} processes ${messages.length} delayed messages`);
|
|
@@ -217,7 +217,7 @@ export class QueueProcessor {
|
|
|
217
217
|
});
|
|
218
218
|
}
|
|
219
219
|
|
|
220
|
-
async processQueuedMessages(groupSessionId: string, selfId:
|
|
220
|
+
async processQueuedMessages(groupSessionId: string, selfId: string): Promise<void> {
|
|
221
221
|
try {
|
|
222
222
|
const queue = this.queueManager.getQueue(groupSessionId);
|
|
223
223
|
if (!queue || queue.length === 0) {
|
|
@@ -225,7 +225,8 @@ export class QueueProcessor {
|
|
|
225
225
|
return;
|
|
226
226
|
}
|
|
227
227
|
|
|
228
|
-
const groupId =
|
|
228
|
+
const groupId = String(groupSessionId.split(":")[1] ?? "").trim();
|
|
229
|
+
if (!groupId) return;
|
|
229
230
|
this.ctx.logger.info(`[Queue] group ${groupSessionId} batch ${queue.length} messages`);
|
|
230
231
|
|
|
231
232
|
const queuedContents: string[] = [];
|
|
@@ -255,7 +256,7 @@ export class QueueProcessor {
|
|
|
255
256
|
const mergedContent = queuedContents.join("\n");
|
|
256
257
|
|
|
257
258
|
const targetMessage: TargetMessage = {
|
|
258
|
-
userName, userId: firstItem.event.user_id
|
|
259
|
+
userName, userId: String(firstItem.event.user_id ?? firstItem.event.sender?.user_id ?? "").trim(),
|
|
259
260
|
userRole: firstItem.event.sender?.role || "member", content: mergedContent,
|
|
260
261
|
messageId: firstItem.event.message_id, timestamp: Date.now(),
|
|
261
262
|
};
|
package/manage/rate-limiter.ts
CHANGED
|
@@ -13,16 +13,16 @@ const DEFAULT_AI_REQUEST_LIMITS: AIRequestLimitConfig = {
|
|
|
13
13
|
windowMs: 60_000,
|
|
14
14
|
};
|
|
15
15
|
|
|
16
|
-
export type ChatConfigProvider = (groupId?:
|
|
16
|
+
export type ChatConfigProvider = (groupId?: string) => ChatConfig;
|
|
17
17
|
|
|
18
18
|
export class RateLimiter {
|
|
19
|
-
private userTriggers: Map<
|
|
20
|
-
private userMessages: Map<
|
|
19
|
+
private userTriggers: Map<string, number[]> = new Map();
|
|
20
|
+
private userMessages: Map<string, { content: string; timestamp: number }[]> =
|
|
21
21
|
new Map();
|
|
22
|
-
private groupLastResponse: Map<
|
|
23
|
-
private groupInteractions: Map<
|
|
24
|
-
private userAiRequests: Map<
|
|
25
|
-
private groupAiRequests: Map<
|
|
22
|
+
private groupLastResponse: Map<string, number> = new Map();
|
|
23
|
+
private groupInteractions: Map<string, Map<string, number[]>> = new Map();
|
|
24
|
+
private userAiRequests: Map<string, number[]> = new Map();
|
|
25
|
+
private groupAiRequests: Map<string, number[]> = new Map();
|
|
26
26
|
|
|
27
27
|
private readonly maxTriggersPerWindow: number;
|
|
28
28
|
private readonly windowMs: number;
|
|
@@ -30,13 +30,13 @@ export class RateLimiter {
|
|
|
30
30
|
private readonly groupCooldownMs: number;
|
|
31
31
|
private readonly cleanupTimer: ReturnType<typeof setInterval>;
|
|
32
32
|
private getConfig: ChatConfigProvider;
|
|
33
|
-
private getQueueLengthFn: ((groupId:
|
|
33
|
+
private getQueueLengthFn: ((groupId: string) => number) | null = null;
|
|
34
34
|
|
|
35
35
|
setConfigProvider(provider: ChatConfigProvider): void {
|
|
36
36
|
this.getConfig = provider;
|
|
37
37
|
}
|
|
38
38
|
|
|
39
|
-
setQueueLengthGetter(fn: (groupId:
|
|
39
|
+
setQueueLengthGetter(fn: (groupId: string) => number): void {
|
|
40
40
|
this.getQueueLengthFn = fn;
|
|
41
41
|
}
|
|
42
42
|
|
|
@@ -58,17 +58,17 @@ export class RateLimiter {
|
|
|
58
58
|
this.cleanupTimer = setInterval(() => this.cleanup(), 300_000);
|
|
59
59
|
}
|
|
60
60
|
|
|
61
|
-
private getDynamicDelay(groupId?:
|
|
61
|
+
private getDynamicDelay(groupId?: string): DynamicDelayConfig {
|
|
62
62
|
return this.getConfig(groupId)?.dynamicDelay ?? DEFAULT_DYNAMIC_DELAY;
|
|
63
63
|
}
|
|
64
64
|
|
|
65
|
-
private getAiRequestLimits(groupId?:
|
|
65
|
+
private getAiRequestLimits(groupId?: string): AIRequestLimitConfig {
|
|
66
66
|
return this.getConfig(groupId)?.aiRequestLimits ?? DEFAULT_AI_REQUEST_LIMITS;
|
|
67
67
|
}
|
|
68
68
|
|
|
69
69
|
canProcess(
|
|
70
|
-
userId:
|
|
71
|
-
groupId:
|
|
70
|
+
userId: string,
|
|
71
|
+
groupId: string | undefined,
|
|
72
72
|
content: string,
|
|
73
73
|
): boolean {
|
|
74
74
|
const now = Date.now();
|
|
@@ -93,7 +93,7 @@ export class RateLimiter {
|
|
|
93
93
|
return !recentSame;
|
|
94
94
|
}
|
|
95
95
|
|
|
96
|
-
record(userId:
|
|
96
|
+
record(userId: string, groupId: string | undefined, content: string): void {
|
|
97
97
|
const now = Date.now();
|
|
98
98
|
|
|
99
99
|
const triggers = this.userTriggers.get(userId) ?? [];
|
|
@@ -110,7 +110,7 @@ export class RateLimiter {
|
|
|
110
110
|
}
|
|
111
111
|
}
|
|
112
112
|
|
|
113
|
-
recordInteraction(groupId:
|
|
113
|
+
recordInteraction(groupId: string, userId: string): void {
|
|
114
114
|
const dynamicDelay = this.getDynamicDelay(groupId);
|
|
115
115
|
if (!dynamicDelay.enabled) return;
|
|
116
116
|
|
|
@@ -129,7 +129,7 @@ export class RateLimiter {
|
|
|
129
129
|
groupUsers.set(userId, timestamps);
|
|
130
130
|
}
|
|
131
131
|
|
|
132
|
-
canRunAIRequest(userId?:
|
|
132
|
+
canRunAIRequest(userId?: string, groupId?: string): boolean {
|
|
133
133
|
const now = Date.now();
|
|
134
134
|
const { userRpm, groupRpm, windowMs } = this.getAiRequestLimits(groupId);
|
|
135
135
|
|
|
@@ -154,7 +154,7 @@ export class RateLimiter {
|
|
|
154
154
|
return true;
|
|
155
155
|
}
|
|
156
156
|
|
|
157
|
-
recordAIRequest(userId?:
|
|
157
|
+
recordAIRequest(userId?: string, groupId?: string): void {
|
|
158
158
|
const now = Date.now();
|
|
159
159
|
const { windowMs } = this.getAiRequestLimits(groupId);
|
|
160
160
|
|
|
@@ -175,7 +175,7 @@ export class RateLimiter {
|
|
|
175
175
|
}
|
|
176
176
|
}
|
|
177
177
|
|
|
178
|
-
getInteractionCount(groupId:
|
|
178
|
+
getInteractionCount(groupId: string): number {
|
|
179
179
|
const now = Date.now();
|
|
180
180
|
const { interactionWindowMs: windowMs } = this.getDynamicDelay(groupId);
|
|
181
181
|
|
|
@@ -199,7 +199,7 @@ export class RateLimiter {
|
|
|
199
199
|
return count;
|
|
200
200
|
}
|
|
201
201
|
|
|
202
|
-
calculateDelay(groupId:
|
|
202
|
+
calculateDelay(groupId: string): number {
|
|
203
203
|
const dynamicDelay = this.getDynamicDelay(groupId);
|
|
204
204
|
if (!dynamicDelay.enabled) return 0;
|
|
205
205
|
|
|
@@ -211,7 +211,7 @@ export class RateLimiter {
|
|
|
211
211
|
return Math.min(delay, maxDelayMs);
|
|
212
212
|
}
|
|
213
213
|
|
|
214
|
-
getDelayInfo(groupId:
|
|
214
|
+
getDelayInfo(groupId: string): {
|
|
215
215
|
delayMs: number;
|
|
216
216
|
interactionCount: number;
|
|
217
217
|
shouldDelay: boolean;
|
|
@@ -225,7 +225,7 @@ export class RateLimiter {
|
|
|
225
225
|
};
|
|
226
226
|
}
|
|
227
227
|
|
|
228
|
-
clearGroupInteractions(groupId:
|
|
228
|
+
clearGroupInteractions(groupId: string): void {
|
|
229
229
|
this.groupInteractions.delete(groupId);
|
|
230
230
|
}
|
|
231
231
|
|
package/manage/session.ts
CHANGED
|
@@ -17,7 +17,7 @@ export class SessionManager {
|
|
|
17
17
|
/**
|
|
18
18
|
* 获取或创建会话
|
|
19
19
|
*/
|
|
20
|
-
getOrCreate(id: string, type: SessionType, targetId:
|
|
20
|
+
getOrCreate(id: string, type: SessionType, targetId: string): SessionMeta {
|
|
21
21
|
// 先查缓存
|
|
22
22
|
if (this.cache.has(id)) {
|
|
23
23
|
return this.touch(id);
|