mioku-plugin-chat 2.5.2 → 2.7.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/groups.ts +5 -0
- package/configs/personalization.ts +1 -1
- package/context.ts +7 -3
- package/core/base.ts +1 -1
- package/core/chat-engine.ts +18 -7
- package/core/chat-turn.ts +28 -10
- package/core/media/image-analyzer.ts +63 -397
- package/core/prompt.ts +11 -20
- package/core/tools/web.ts +1 -1
- package/db.ts +8 -12
- package/handlers/idle-debug.ts +96 -97
- package/handlers/message.ts +38 -37
- package/handlers/poke.ts +104 -126
- package/humanize/emoji-agent.ts +210 -321
- package/humanize/emotion-agent.ts +33 -26
- package/humanize/expression.ts +24 -16
- package/humanize/index.ts +9 -8
- package/humanize/memory.ts +10 -7
- package/humanize/planner.ts +11 -16
- package/humanize/topic.ts +22 -15
- package/index.ts +120 -45
- package/manage/cooldown.ts +83 -116
- package/manage/idle-check.ts +173 -75
- package/manage/queue-processor.ts +92 -44
- package/manage/rate-limiter.ts +56 -80
- package/manage/session-turn-scheduler.ts +130 -0
- package/package.json +1 -1
- package/runtime/chat-runtime.ts +15 -3
- package/types.ts +25 -19
- package/utils/config.ts +57 -0
- package/utils/group-config.ts +70 -0
- package/utils/message.ts +4 -7
- package/utils/queue.ts +5 -0
package/manage/idle-check.ts
CHANGED
|
@@ -2,7 +2,7 @@ import type { MiokiContext } from "mioki";
|
|
|
2
2
|
import type { AIInstance } from "mioku";
|
|
3
3
|
import type { ChatConfig, TargetMessage } from "../types";
|
|
4
4
|
import type { ChatDatabase } from "../db";
|
|
5
|
-
import type { HumanizeEngine } from "../humanize";
|
|
5
|
+
import type { HumanizeEngine, ChatConfigProvider } from "../humanize";
|
|
6
6
|
import type { SkillSessionManager } from "./skill-session";
|
|
7
7
|
import type { GroupStructuredHistoryManager } from "./group-structured-history";
|
|
8
8
|
import type { SessionManager } from "./session";
|
|
@@ -11,26 +11,26 @@ import type {
|
|
|
11
11
|
HistoryMediaOptions,
|
|
12
12
|
GetGroupHistoryMessages,
|
|
13
13
|
GetGroupInfoData,
|
|
14
|
-
SendAIResponse,
|
|
15
|
-
SaveBotMessages,
|
|
16
14
|
RunChat,
|
|
17
15
|
BuildToolContext,
|
|
18
16
|
} from "./types";
|
|
19
17
|
import type { ChatPluginContext } from "../context";
|
|
18
|
+
import { finalizeChatTurn } from "../core/chat-turn";
|
|
20
19
|
import { getBotRole, isGroupAllowed } from "../utils";
|
|
21
20
|
|
|
22
21
|
export class IdleCheckManager {
|
|
22
|
+
private pluginCtx: ChatPluginContext;
|
|
23
23
|
private groupLastActivityTime = new Map<string, number>();
|
|
24
24
|
private groupMessageCount = new Map<string, number>();
|
|
25
25
|
private groupLastBotMessageTime = new Map<string, number>();
|
|
26
26
|
private groupMessageCountAfterBot = new Map<string, number>();
|
|
27
|
-
private idleCheckProcessing = new Set<string>();
|
|
28
27
|
private groupLastIdleCheckTime = new Map<string, number>();
|
|
29
28
|
private groupBotsMapping = new Map<string, Set<number>>();
|
|
30
29
|
private intervalHandle: NodeJS.Timeout | null = null;
|
|
31
30
|
|
|
32
31
|
private ctx: MiokiContext;
|
|
33
|
-
private
|
|
32
|
+
private configProvider: ChatConfigProvider;
|
|
33
|
+
private defaultConfig: ChatConfig;
|
|
34
34
|
private db: ChatDatabase;
|
|
35
35
|
private humanize: HumanizeEngine;
|
|
36
36
|
private aiInstance: AIInstance;
|
|
@@ -42,15 +42,14 @@ export class IdleCheckManager {
|
|
|
42
42
|
private buildHistoryMediaOptions: (ai: AIInstance, cfg: ChatConfig) => HistoryMediaOptions;
|
|
43
43
|
private getGroupHistoryMessages: GetGroupHistoryMessages;
|
|
44
44
|
private getGroupInfoData: GetGroupInfoData;
|
|
45
|
-
private sendAIResponse: SendAIResponse;
|
|
46
|
-
private saveBotMessages: SaveBotMessages;
|
|
47
45
|
private runChat: RunChat;
|
|
48
46
|
private buildToolContext: BuildToolContext;
|
|
49
|
-
private startCooldownTimer: (groupSessionId: string, groupId: number, selfId: number) => void;
|
|
50
47
|
|
|
51
48
|
constructor(pluginCtx: ChatPluginContext) {
|
|
49
|
+
this.pluginCtx = pluginCtx;
|
|
52
50
|
this.ctx = pluginCtx.ctx;
|
|
53
|
-
this.
|
|
51
|
+
this.configProvider = pluginCtx.configProvider;
|
|
52
|
+
this.defaultConfig = pluginCtx.defaultConfig;
|
|
54
53
|
this.db = pluginCtx.db;
|
|
55
54
|
this.humanize = pluginCtx.humanize;
|
|
56
55
|
this.aiInstance = pluginCtx.aiInstance;
|
|
@@ -62,11 +61,8 @@ export class IdleCheckManager {
|
|
|
62
61
|
this.buildHistoryMediaOptions = pluginCtx.buildHistoryMediaOptions;
|
|
63
62
|
this.getGroupHistoryMessages = pluginCtx.getGroupHistoryMessages;
|
|
64
63
|
this.getGroupInfoData = pluginCtx.getGroupInfoData;
|
|
65
|
-
this.sendAIResponse = pluginCtx.sendAIResponse;
|
|
66
|
-
this.saveBotMessages = pluginCtx.saveBotMessages;
|
|
67
64
|
this.runChat = pluginCtx.runChat;
|
|
68
65
|
this.buildToolContext = pluginCtx.buildToolContext;
|
|
69
|
-
this.startCooldownTimer = pluginCtx.startCooldownTimer;
|
|
70
66
|
}
|
|
71
67
|
|
|
72
68
|
recordActivity(groupSessionId: string): void {
|
|
@@ -99,32 +95,47 @@ export class IdleCheckManager {
|
|
|
99
95
|
this.groupMessageCount.clear();
|
|
100
96
|
this.groupLastBotMessageTime.clear();
|
|
101
97
|
this.groupMessageCountAfterBot.clear();
|
|
102
|
-
this.idleCheckProcessing.clear();
|
|
103
98
|
this.groupLastIdleCheckTime.clear();
|
|
104
99
|
this.groupBotsMapping.clear();
|
|
105
100
|
}
|
|
106
101
|
|
|
107
102
|
private async tick(): Promise<void> {
|
|
108
103
|
try {
|
|
109
|
-
const
|
|
110
|
-
if (!
|
|
104
|
+
const baseCfg = this.defaultConfig;
|
|
105
|
+
if (!baseCfg.apiKey) return;
|
|
111
106
|
|
|
112
107
|
const now = Date.now();
|
|
113
|
-
const idleThreshold = cfg.planner.idleThresholdMs ?? 30 * 60_000;
|
|
114
|
-
const messageCountThreshold = cfg.planner.idleMessageCount ?? 100;
|
|
115
108
|
const checkInterval = 60_000;
|
|
116
109
|
const allBotIds = Array.from(this.ctx.bots).map((bot) => bot.uin);
|
|
117
|
-
const idleCheckBotIds = cfg.planner.idleCheckBotIds ?? allBotIds;
|
|
118
|
-
const enabledBotIds = idleCheckBotIds.filter((id) => allBotIds.includes(id));
|
|
119
110
|
|
|
120
111
|
for (const [groupSessionId, lastTime] of this.groupLastActivityTime) {
|
|
121
|
-
const lastCheckTime =
|
|
112
|
+
const lastCheckTime =
|
|
113
|
+
this.groupLastIdleCheckTime.get(groupSessionId) ?? 0;
|
|
122
114
|
if (now - lastCheckTime < checkInterval) continue;
|
|
123
115
|
|
|
124
116
|
const groupId = parseInt(groupSessionId.split(":")[1], 10);
|
|
117
|
+
const cfg = this.configProvider(groupId);
|
|
118
|
+
if (!cfg.planner?.enabled) continue;
|
|
125
119
|
if (!isGroupAllowed(groupId, cfg)) continue;
|
|
126
120
|
|
|
127
|
-
|
|
121
|
+
const idleThreshold =
|
|
122
|
+
cfg.planner.idleThresholdMs ??
|
|
123
|
+
baseCfg.planner?.idleThresholdMs ??
|
|
124
|
+
30 * 60_000;
|
|
125
|
+
const messageCountThreshold =
|
|
126
|
+
cfg.planner.idleMessageCount ??
|
|
127
|
+
baseCfg.planner?.idleMessageCount ??
|
|
128
|
+
100;
|
|
129
|
+
const idleCheckBotIds =
|
|
130
|
+
cfg.planner.idleCheckBotIds ??
|
|
131
|
+
baseCfg.planner?.idleCheckBotIds ??
|
|
132
|
+
allBotIds;
|
|
133
|
+
const enabledBotIds = idleCheckBotIds.filter((id: number) =>
|
|
134
|
+
allBotIds.includes(id),
|
|
135
|
+
);
|
|
136
|
+
|
|
137
|
+
let lastBotTime =
|
|
138
|
+
this.groupLastBotMessageTime.get(groupSessionId) ?? 0;
|
|
128
139
|
if (lastBotTime === 0) {
|
|
129
140
|
const botMsgs = this.db.getBotMessages(groupId, 1);
|
|
130
141
|
if (botMsgs.length > 0) {
|
|
@@ -136,71 +147,158 @@ export class IdleCheckManager {
|
|
|
136
147
|
const lastActivityTime = Math.max(lastTime, lastBotTime);
|
|
137
148
|
if (now - lastActivityTime < idleThreshold) continue;
|
|
138
149
|
|
|
139
|
-
const messageCountAfterBot =
|
|
140
|
-
|
|
150
|
+
const messageCountAfterBot =
|
|
151
|
+
this.groupMessageCountAfterBot.get(groupSessionId) ?? 0;
|
|
152
|
+
const messageCount =
|
|
153
|
+
lastBotTime > 0
|
|
154
|
+
? messageCountAfterBot
|
|
155
|
+
: (this.groupMessageCount.get(groupSessionId) ?? 0);
|
|
141
156
|
if (messageCount < messageCountThreshold) continue;
|
|
142
157
|
|
|
143
158
|
const botsInGroup = this.groupBotsMapping.get(groupSessionId);
|
|
144
159
|
if (!botsInGroup || botsInGroup.size === 0) continue;
|
|
145
|
-
const availableBots = Array.from(botsInGroup).filter((id) =>
|
|
160
|
+
const availableBots = Array.from(botsInGroup).filter((id) =>
|
|
161
|
+
enabledBotIds.includes(id),
|
|
162
|
+
);
|
|
146
163
|
if (availableBots.length === 0) continue;
|
|
147
164
|
|
|
148
|
-
const selfId =
|
|
149
|
-
|
|
165
|
+
const selfId =
|
|
166
|
+
availableBots[Math.floor(Math.random() * availableBots.length)];
|
|
167
|
+
this.groupLastIdleCheckTime.set(groupSessionId, now);
|
|
168
|
+
void this.pluginCtx.sessionTurnScheduler
|
|
169
|
+
.run(
|
|
170
|
+
groupSessionId,
|
|
171
|
+
"idle-check",
|
|
172
|
+
() =>
|
|
173
|
+
this.processIdleCheckTurn(
|
|
174
|
+
groupSessionId,
|
|
175
|
+
groupId,
|
|
176
|
+
selfId,
|
|
177
|
+
cfg,
|
|
178
|
+
lastActivityTime,
|
|
179
|
+
now,
|
|
180
|
+
),
|
|
181
|
+
{ dedupeKey: "idle-check" },
|
|
182
|
+
)
|
|
183
|
+
.catch((err) =>
|
|
184
|
+
this.ctx.logger.error(
|
|
185
|
+
`[IdleCheck] group ${groupId} idle detection failed: ${err}`,
|
|
186
|
+
),
|
|
187
|
+
);
|
|
188
|
+
}
|
|
189
|
+
} catch (err) {
|
|
190
|
+
this.ctx.logger.error(`[IdleCheck] tick failed: ${err}`);
|
|
191
|
+
}
|
|
192
|
+
}
|
|
150
193
|
|
|
151
|
-
|
|
152
|
-
|
|
194
|
+
private async processIdleCheckTurn(
|
|
195
|
+
groupSessionId: string,
|
|
196
|
+
groupId: number,
|
|
197
|
+
selfId: number,
|
|
198
|
+
cfg: ChatConfig,
|
|
199
|
+
scheduledLastActivity: number,
|
|
200
|
+
scheduledAt: number,
|
|
201
|
+
): Promise<void> {
|
|
202
|
+
const latestActivity = Math.max(
|
|
203
|
+
this.groupLastActivityTime.get(groupSessionId) ?? 0,
|
|
204
|
+
this.groupLastBotMessageTime.get(groupSessionId) ?? 0,
|
|
205
|
+
);
|
|
206
|
+
if (latestActivity > scheduledLastActivity) return;
|
|
153
207
|
|
|
154
|
-
|
|
155
|
-
|
|
208
|
+
this.ctx.logger.info(`[IdleCheck] group ${groupId} triggers idle detection`);
|
|
209
|
+
const { history } = await this.getGroupHistoryMessages(
|
|
210
|
+
groupId,
|
|
211
|
+
groupSessionId,
|
|
212
|
+
this.ctx,
|
|
213
|
+
cfg.historyCount,
|
|
214
|
+
this.db,
|
|
215
|
+
selfId,
|
|
216
|
+
this.buildHistoryMediaOptions(this.aiInstance, cfg),
|
|
217
|
+
);
|
|
218
|
+
const botNickname =
|
|
219
|
+
cfg.nicknames[0] || this.ctx.pickBot(selfId).nickname || "Bot";
|
|
220
|
+
const planResult = await this.humanize.actionPlanner.plan(
|
|
221
|
+
groupSessionId,
|
|
222
|
+
botNickname,
|
|
223
|
+
history,
|
|
224
|
+
"[Check if you want to answer the call]",
|
|
225
|
+
true,
|
|
226
|
+
);
|
|
156
227
|
|
|
157
|
-
|
|
228
|
+
if (planResult.action !== "reply") {
|
|
229
|
+
this.groupMessageCount.set(groupSessionId, 0);
|
|
230
|
+
this.groupMessageCountAfterBot.set(groupSessionId, 0);
|
|
231
|
+
this.groupLastIdleCheckTime.set(groupSessionId, scheduledAt);
|
|
232
|
+
return;
|
|
233
|
+
}
|
|
158
234
|
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
235
|
+
const targetMessage: TargetMessage = {
|
|
236
|
+
userName: "system",
|
|
237
|
+
userId: 0,
|
|
238
|
+
userRole: "member",
|
|
239
|
+
content: "[No one in the group is talking? I'll answer!]",
|
|
240
|
+
messageId: 0,
|
|
241
|
+
timestamp: scheduledAt,
|
|
242
|
+
};
|
|
243
|
+
const botRole = await getBotRole(groupId, this.ctx, selfId);
|
|
244
|
+
const toolCtx = this.buildToolContext({
|
|
245
|
+
ctx: this.ctx,
|
|
246
|
+
event: null,
|
|
247
|
+
groupSessionId,
|
|
248
|
+
groupId,
|
|
249
|
+
userId: 0,
|
|
250
|
+
config: cfg,
|
|
251
|
+
aiService: this.aiService,
|
|
252
|
+
db: this.db,
|
|
253
|
+
botRole,
|
|
254
|
+
humanize: this.humanize,
|
|
255
|
+
targetMessage,
|
|
256
|
+
selfId,
|
|
257
|
+
});
|
|
165
258
|
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
config: cfg,
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
259
|
+
const result = await this.runWithRateLimitGuard(
|
|
260
|
+
() =>
|
|
261
|
+
this.runChat(
|
|
262
|
+
this.aiInstance,
|
|
263
|
+
toolCtx,
|
|
264
|
+
history,
|
|
265
|
+
targetMessage,
|
|
266
|
+
{
|
|
267
|
+
config: cfg,
|
|
268
|
+
botNickname,
|
|
269
|
+
botRole: toolCtx.botRole,
|
|
270
|
+
aiService: this.aiService,
|
|
271
|
+
isGroup: true,
|
|
272
|
+
plannerThoughts:
|
|
273
|
+
'You stumbled upon some message in this group and decided to reply.\nQuote messages from group friends appropriately (using [reply:message ID] format).\nDon\'t mention your intentions like "I\'m here to answer".',
|
|
274
|
+
replyContext: { type: "idle" },
|
|
275
|
+
},
|
|
276
|
+
this.humanize,
|
|
277
|
+
this.skillManager,
|
|
278
|
+
),
|
|
279
|
+
{ groupId, label: "idle-check" },
|
|
280
|
+
);
|
|
281
|
+
if (!result) {
|
|
282
|
+
this.groupMessageCount.set(groupSessionId, 0);
|
|
283
|
+
this.groupMessageCountAfterBot.set(groupSessionId, 0);
|
|
284
|
+
this.groupLastIdleCheckTime.set(groupSessionId, scheduledAt);
|
|
285
|
+
return;
|
|
286
|
+
}
|
|
191
287
|
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
288
|
+
await finalizeChatTurn(this.pluginCtx, {
|
|
289
|
+
event: null,
|
|
290
|
+
cfg,
|
|
291
|
+
result,
|
|
292
|
+
groupId,
|
|
293
|
+
groupSessionId,
|
|
294
|
+
userId: 0,
|
|
295
|
+
selfId,
|
|
296
|
+
toolCtx,
|
|
297
|
+
send: true,
|
|
298
|
+
isLive: true,
|
|
299
|
+
});
|
|
300
|
+
this.groupLastBotMessageTime.set(groupSessionId, Date.now());
|
|
301
|
+
this.groupMessageCountAfterBot.set(groupSessionId, 0);
|
|
302
|
+
this.ctx.logger.info(`[IdleCheck] group ${groupId} idle reply completed`);
|
|
205
303
|
}
|
|
206
304
|
}
|
|
@@ -2,7 +2,7 @@ import type { MiokiContext } from "mioki";
|
|
|
2
2
|
import type { AIInstance } from "mioku";
|
|
3
3
|
import type { ChatConfig, TargetMessage } from "../types";
|
|
4
4
|
import type { ChatDatabase } from "../db";
|
|
5
|
-
import type { HumanizeEngine } from "../humanize";
|
|
5
|
+
import type { HumanizeEngine, ChatConfigProvider } from "../humanize";
|
|
6
6
|
import type { SkillSessionManager } from "./skill-session";
|
|
7
7
|
import type { GroupStructuredHistoryManager, StructuredUserInput } from "./group-structured-history";
|
|
8
8
|
import { buildStructuredUserInputFromEvent } from "./group-structured-history";
|
|
@@ -15,14 +15,13 @@ import type {
|
|
|
15
15
|
GetGroupHistoryMessages,
|
|
16
16
|
GetGroupInfoData,
|
|
17
17
|
GetHumanizeContexts,
|
|
18
|
-
SendAIResponse,
|
|
19
|
-
SaveBotMessages,
|
|
20
|
-
SendEmoji,
|
|
21
18
|
RunChat,
|
|
22
19
|
BuildToolContext,
|
|
23
20
|
BuildStructuredUserInput,
|
|
24
21
|
} from "./types";
|
|
25
22
|
import type { ChatPluginContext } from "../context";
|
|
23
|
+
import type { SessionTurnScheduler } from "./session-turn-scheduler";
|
|
24
|
+
import { finalizeChatTurn } from "../core/chat-turn";
|
|
26
25
|
import { extractContent, getBotRole } from "../utils";
|
|
27
26
|
|
|
28
27
|
interface DynamicDelayQueueData {
|
|
@@ -33,9 +32,11 @@ interface DynamicDelayQueueData {
|
|
|
33
32
|
|
|
34
33
|
export class QueueProcessor {
|
|
35
34
|
private dynamicDelayQueues = new Map<string, DynamicDelayQueueData>();
|
|
35
|
+
private pluginCtx: ChatPluginContext;
|
|
36
36
|
|
|
37
37
|
private ctx: MiokiContext;
|
|
38
|
-
private
|
|
38
|
+
private configProvider: ChatConfigProvider;
|
|
39
|
+
private defaultConfig: ChatConfig;
|
|
39
40
|
private db: ChatDatabase;
|
|
40
41
|
private humanize: HumanizeEngine;
|
|
41
42
|
private aiInstance: AIInstance;
|
|
@@ -50,17 +51,16 @@ export class QueueProcessor {
|
|
|
50
51
|
private getGroupHistoryMessages: GetGroupHistoryMessages;
|
|
51
52
|
private getGroupInfoData: GetGroupInfoData;
|
|
52
53
|
private getHumanizeContexts: GetHumanizeContexts;
|
|
53
|
-
private sendAIResponse: SendAIResponse;
|
|
54
|
-
private saveBotMessages: SaveBotMessages;
|
|
55
|
-
private sendEmoji: SendEmoji;
|
|
56
54
|
private runChat: RunChat;
|
|
57
55
|
private buildToolContext: BuildToolContext;
|
|
58
56
|
private buildStructuredUserInputFromEvent: BuildStructuredUserInput;
|
|
59
|
-
private
|
|
57
|
+
private sessionTurnScheduler: SessionTurnScheduler;
|
|
60
58
|
|
|
61
59
|
constructor(pluginCtx: ChatPluginContext) {
|
|
60
|
+
this.pluginCtx = pluginCtx;
|
|
62
61
|
this.ctx = pluginCtx.ctx;
|
|
63
|
-
this.
|
|
62
|
+
this.configProvider = pluginCtx.configProvider;
|
|
63
|
+
this.defaultConfig = pluginCtx.defaultConfig;
|
|
64
64
|
this.db = pluginCtx.db;
|
|
65
65
|
this.humanize = pluginCtx.humanize;
|
|
66
66
|
this.aiInstance = pluginCtx.aiInstance;
|
|
@@ -75,13 +75,10 @@ export class QueueProcessor {
|
|
|
75
75
|
this.getGroupHistoryMessages = pluginCtx.getGroupHistoryMessages;
|
|
76
76
|
this.getGroupInfoData = pluginCtx.getGroupInfoData;
|
|
77
77
|
this.getHumanizeContexts = pluginCtx.getHumanizeContexts;
|
|
78
|
-
this.sendAIResponse = pluginCtx.sendAIResponse;
|
|
79
|
-
this.saveBotMessages = pluginCtx.saveBotMessages;
|
|
80
|
-
this.sendEmoji = pluginCtx.sendEmoji;
|
|
81
78
|
this.runChat = pluginCtx.runChat;
|
|
82
79
|
this.buildToolContext = pluginCtx.buildToolContext;
|
|
83
80
|
this.buildStructuredUserInputFromEvent = buildStructuredUserInputFromEvent;
|
|
84
|
-
this.
|
|
81
|
+
this.sessionTurnScheduler = pluginCtx.sessionTurnScheduler;
|
|
85
82
|
}
|
|
86
83
|
|
|
87
84
|
collectDynamicDelayMessage(groupSessionId: string, event: any, content: string): void {
|
|
@@ -105,9 +102,29 @@ export class QueueProcessor {
|
|
|
105
102
|
|
|
106
103
|
this.ctx.logger.info(`[DynamicDelay] group ${groupId} start delay ${delayMs / 1000}s, interactions: ${this.rateLimiter.getInteractionCount(groupId)}`);
|
|
107
104
|
|
|
108
|
-
|
|
109
|
-
|
|
105
|
+
const timer = setTimeout(() => {
|
|
106
|
+
const current = this.dynamicDelayQueues.get(groupSessionId);
|
|
107
|
+
if (!current || current !== queueData || current.timer !== timer) return;
|
|
108
|
+
this.dynamicDelayQueues.delete(groupSessionId);
|
|
109
|
+
current.timer = null;
|
|
110
|
+
const messages = current.messages;
|
|
111
|
+
if (messages.length === 0) return;
|
|
112
|
+
void this.sessionTurnScheduler
|
|
113
|
+
.run(groupSessionId, "dynamic-delay", () =>
|
|
114
|
+
this.processDynamicDelayMessages(
|
|
115
|
+
groupSessionId,
|
|
116
|
+
groupId,
|
|
117
|
+
selfId,
|
|
118
|
+
messages,
|
|
119
|
+
),
|
|
120
|
+
)
|
|
121
|
+
.catch((err) =>
|
|
122
|
+
this.ctx.logger.error(
|
|
123
|
+
`[DynamicDelay] group ${groupId} processing failed: ${err}`,
|
|
124
|
+
),
|
|
125
|
+
);
|
|
110
126
|
}, delayMs);
|
|
127
|
+
queueData.timer = timer;
|
|
111
128
|
}
|
|
112
129
|
|
|
113
130
|
isInDynamicDelay(groupSessionId: string): boolean {
|
|
@@ -115,6 +132,21 @@ export class QueueProcessor {
|
|
|
115
132
|
return queue != null && Date.now() < queue.delayUntil;
|
|
116
133
|
}
|
|
117
134
|
|
|
135
|
+
scheduleQueuedMessages(groupSessionId: string, selfId: number): void {
|
|
136
|
+
void this.sessionTurnScheduler
|
|
137
|
+
.run(
|
|
138
|
+
groupSessionId,
|
|
139
|
+
"queued-message",
|
|
140
|
+
() => this.processQueuedMessages(groupSessionId, selfId),
|
|
141
|
+
{ dedupeKey: `queued-message:${selfId}` },
|
|
142
|
+
)
|
|
143
|
+
.catch((err) =>
|
|
144
|
+
this.ctx.logger.error(
|
|
145
|
+
`[Queue] group ${groupSessionId} scheduling failed: ${err}`,
|
|
146
|
+
),
|
|
147
|
+
);
|
|
148
|
+
}
|
|
149
|
+
|
|
118
150
|
dispose(): void {
|
|
119
151
|
for (const queue of this.dynamicDelayQueues.values()) {
|
|
120
152
|
if (queue.timer) clearTimeout(queue.timer);
|
|
@@ -122,16 +154,12 @@ export class QueueProcessor {
|
|
|
122
154
|
this.dynamicDelayQueues.clear();
|
|
123
155
|
}
|
|
124
156
|
|
|
125
|
-
async
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
const messages = queueData.messages;
|
|
133
|
-
this.dynamicDelayQueues.delete(groupSessionId);
|
|
134
|
-
|
|
157
|
+
private async processDynamicDelayMessages(
|
|
158
|
+
groupSessionId: string,
|
|
159
|
+
groupId: number,
|
|
160
|
+
selfId: number,
|
|
161
|
+
messages: DynamicDelayQueueData["messages"],
|
|
162
|
+
): Promise<void> {
|
|
135
163
|
this.ctx.logger.info(`[DynamicDelay] group ${groupId} processes ${messages.length} delayed messages`);
|
|
136
164
|
this.rateLimiter.clearGroupInteractions(groupId);
|
|
137
165
|
|
|
@@ -147,14 +175,15 @@ export class QueueProcessor {
|
|
|
147
175
|
content: mergedContent, messageId: firstMsg.messageId, timestamp: Date.now(),
|
|
148
176
|
};
|
|
149
177
|
|
|
178
|
+
const cfg = this.configProvider(groupId);
|
|
150
179
|
const botRole = await getBotRole(groupId, this.ctx, selfId);
|
|
151
|
-
const botNickname =
|
|
180
|
+
const botNickname = cfg.nicknames[0] || this.ctx.pickBot(selfId).nickname || "Bot";
|
|
152
181
|
const { groupName, memberCount } = await this.getGroupInfoData(this.ctx, groupId, selfId, String(groupId));
|
|
153
|
-
const { history } = await this.getGroupHistoryMessages(groupId, groupSessionId, this.ctx,
|
|
182
|
+
const { history } = await this.getGroupHistoryMessages(groupId, groupSessionId, this.ctx, cfg.historyCount, this.db, selfId, this.buildHistoryMediaOptions(this.aiInstance, cfg));
|
|
154
183
|
|
|
155
184
|
const toolCtx = this.buildToolContext({
|
|
156
185
|
ctx: this.ctx, event: firstMsg.event, groupSessionId, groupId, userId: firstMsg.userId,
|
|
157
|
-
config:
|
|
186
|
+
config: cfg, aiService: this.aiService, db: this.db, botRole, humanize: this.humanize, targetMessage, selfId,
|
|
158
187
|
});
|
|
159
188
|
|
|
160
189
|
this.sessionManager.getOrCreate(groupSessionId, "group", groupId);
|
|
@@ -162,19 +191,29 @@ export class QueueProcessor {
|
|
|
162
191
|
|
|
163
192
|
const result = await this.runWithRateLimitGuard(
|
|
164
193
|
() => this.runChat(this.aiInstance, toolCtx, history, targetMessage, {
|
|
165
|
-
config:
|
|
194
|
+
config: cfg, groupName, memberCount, botNickname, botRole, aiService: this.aiService, isGroup: true,
|
|
166
195
|
memoryContext: contexts.memoryContext, topicContext: contexts.topicContext, expressionContext: contexts.expressionContext,
|
|
167
196
|
replyContext: { type: "review", targetUser: targetMessage.userName, targetMessage: targetMessage.content },
|
|
168
197
|
reviewMessages: { contents: mergedContents, userNames, messageIds },
|
|
169
198
|
}, this.humanize, this.skillManager, {
|
|
170
|
-
manager: this.groupStructuredHistory, ttlMs:
|
|
199
|
+
manager: this.groupStructuredHistory, ttlMs: cfg.groupStructuredHistoryTtlMs, currentUserInputs: structuredUserInputs,
|
|
171
200
|
}),
|
|
172
201
|
{ userId: targetMessage.userId, groupId, label: "dynamic-delay" },
|
|
173
202
|
);
|
|
174
203
|
if (!result) return;
|
|
175
204
|
|
|
176
|
-
await
|
|
177
|
-
|
|
205
|
+
await finalizeChatTurn(this.pluginCtx, {
|
|
206
|
+
event: firstMsg.event,
|
|
207
|
+
cfg,
|
|
208
|
+
result,
|
|
209
|
+
groupId,
|
|
210
|
+
groupSessionId,
|
|
211
|
+
userId: targetMessage.userId,
|
|
212
|
+
selfId,
|
|
213
|
+
toolCtx,
|
|
214
|
+
send: true,
|
|
215
|
+
isLive: true,
|
|
216
|
+
});
|
|
178
217
|
}
|
|
179
218
|
|
|
180
219
|
async processQueuedMessages(groupSessionId: string, selfId: number): Promise<void> {
|
|
@@ -191,8 +230,10 @@ export class QueueProcessor {
|
|
|
191
230
|
const queuedContents: string[] = [];
|
|
192
231
|
const structuredUserInputs: StructuredUserInput[] = [];
|
|
193
232
|
|
|
233
|
+
const cfg = this.configProvider(groupId);
|
|
234
|
+
|
|
194
235
|
for (const item of queue) {
|
|
195
|
-
const { text: extractedText, multimodal } = extractContent(item.event,
|
|
236
|
+
const { text: extractedText, multimodal } = extractContent(item.event, cfg, this.ctx);
|
|
196
237
|
const content = multimodal ? JSON.stringify(multimodal) : extractedText;
|
|
197
238
|
if (content) {
|
|
198
239
|
queuedContents.push(content);
|
|
@@ -219,33 +260,40 @@ export class QueueProcessor {
|
|
|
219
260
|
};
|
|
220
261
|
|
|
221
262
|
const botRole = await getBotRole(groupId, this.ctx, selfId);
|
|
222
|
-
const botNickname =
|
|
263
|
+
const botNickname = cfg.nicknames[0] || this.ctx.pickBot(selfId).nickname || "Bot";
|
|
223
264
|
const toolCtx = this.buildToolContext({
|
|
224
265
|
ctx: this.ctx, event: null, groupSessionId, groupId, userId: targetMessage.userId,
|
|
225
|
-
config:
|
|
266
|
+
config: cfg, aiService: this.aiService, db: this.db, botRole, humanize: this.humanize, targetMessage, selfId,
|
|
226
267
|
});
|
|
227
268
|
|
|
228
|
-
const { history } = await this.getGroupHistoryMessages(groupId, groupSessionId, this.ctx,
|
|
269
|
+
const { history } = await this.getGroupHistoryMessages(groupId, groupSessionId, this.ctx, cfg.historyCount, this.db, selfId, this.buildHistoryMediaOptions(this.aiInstance, cfg));
|
|
229
270
|
const contexts = await this.getHumanizeContexts(this.humanize, groupSessionId, targetMessage.userName, history, targetMessage.userId);
|
|
230
271
|
const { groupName, memberCount } = await this.getGroupInfoData(this.ctx, groupId, selfId);
|
|
231
272
|
|
|
232
273
|
const result = await this.runWithRateLimitGuard(
|
|
233
274
|
() => this.runChat(this.aiInstance, toolCtx, history, targetMessage, {
|
|
234
|
-
config:
|
|
275
|
+
config: cfg, groupName, memberCount, botNickname, botRole: toolCtx.botRole, aiService: this.aiService, isGroup: true,
|
|
235
276
|
memoryContext: contexts.memoryContext, topicContext: contexts.topicContext, expressionContext: contexts.expressionContext,
|
|
236
277
|
replyContext: { type: "comment", targetUser: targetMessage.userName, targetMessage: targetMessage.content },
|
|
237
278
|
}, this.humanize, this.skillManager, {
|
|
238
|
-
manager: this.groupStructuredHistory, ttlMs:
|
|
279
|
+
manager: this.groupStructuredHistory, ttlMs: cfg.groupStructuredHistoryTtlMs, currentUserInputs: structuredUserInputs,
|
|
239
280
|
}),
|
|
240
281
|
{ userId: targetMessage.userId, groupId, label: "queue" },
|
|
241
282
|
);
|
|
242
283
|
if (!result) return;
|
|
243
284
|
|
|
244
|
-
await
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
285
|
+
await finalizeChatTurn(this.pluginCtx, {
|
|
286
|
+
event: firstItem.event,
|
|
287
|
+
cfg,
|
|
288
|
+
result,
|
|
289
|
+
groupId,
|
|
290
|
+
groupSessionId,
|
|
291
|
+
userId: targetMessage.userId,
|
|
292
|
+
selfId,
|
|
293
|
+
toolCtx,
|
|
294
|
+
send: true,
|
|
295
|
+
isLive: true,
|
|
296
|
+
});
|
|
249
297
|
this.ctx.logger.info(`[Queue] group ${groupSessionId} done`);
|
|
250
298
|
} catch (err) {
|
|
251
299
|
this.ctx.logger.error(err);
|