mioku-plugin-chat 2.6.0 → 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/index.ts CHANGED
@@ -23,6 +23,7 @@ import {
23
23
  import { CooldownManager } from "./manage/cooldown";
24
24
  import { IdleCheckManager } from "./manage/idle-check";
25
25
  import { QueueProcessor } from "./manage/queue-processor";
26
+ import { SessionTurnScheduler } from "./manage/session-turn-scheduler";
26
27
  import { ChatDatabaseCleanup, DEFAULT_CLEANUP_CONFIG } from "./manage/cleanup";
27
28
  import {
28
29
  GroupStructuredHistoryManager,
@@ -41,31 +42,11 @@ import { buildHistoryMediaOptions } from "./core/media/segment";
41
42
  import type { ChatConfigProvider } from "./humanize";
42
43
  import type { ChatPluginContext, ChatHandlerState } from "./context";
43
44
  import { mergeGroupOverrides } from "./utils/group-config";
44
-
45
- function normalizeIdList(input: unknown): number[] {
46
- if (!Array.isArray(input)) return [];
47
- return Array.from(
48
- new Set(
49
- input
50
- .map((item) => Math.floor(Number(item)))
51
- .filter((id) => Number.isFinite(id) && id > 0),
52
- ),
53
- );
54
- }
55
-
56
- function shallowMergeTopLevel<T extends Record<string, any>>(
57
- defaults: T,
58
- overrides: Record<string, any>,
59
- ): T {
60
- const result: Record<string, any> = { ...defaults };
61
- for (const key of Object.keys(overrides)) {
62
- const value = overrides[key];
63
- if (value !== undefined && value !== null) {
64
- result[key] = value;
65
- }
66
- }
67
- return result as T;
68
- }
45
+ import {
46
+ mergeChatConfig,
47
+ normalizeIdList,
48
+ normalizeMediaAnalysisBlacklist,
49
+ } from "./utils/config";
69
50
 
70
51
  export default definePlugin({
71
52
  name: "chat",
@@ -91,9 +72,9 @@ export default definePlugin({
91
72
 
92
73
  const buildGlobalConfig = async (): Promise<ChatConfig> => {
93
74
  if (!configService) {
94
- return shallowMergeTopLevel(
95
- shallowMergeTopLevel(
96
- shallowMergeTopLevel({} as ChatConfig, BASE_CONFIG),
75
+ return mergeChatConfig(
76
+ mergeChatConfig(
77
+ mergeChatConfig({} as ChatConfig, BASE_CONFIG),
97
78
  SETTINGS_CONFIG,
98
79
  ),
99
80
  PERSONALIZATION_CONFIG,
@@ -103,15 +84,19 @@ export default definePlugin({
103
84
  const settings = (await configService.getConfig("chat", "settings")) ?? {};
104
85
  const personalization =
105
86
  (await configService.getConfig("chat", "personalization")) ?? {};
106
- const merged = shallowMergeTopLevel(
107
- shallowMergeTopLevel(
108
- shallowMergeTopLevel({} as ChatConfig, BASE_CONFIG),
87
+ const mergedDefaults = mergeChatConfig(
88
+ mergeChatConfig(
89
+ mergeChatConfig({} as ChatConfig, BASE_CONFIG),
109
90
  SETTINGS_CONFIG,
110
91
  ),
111
- shallowMergeTopLevel(
112
- shallowMergeTopLevel({} as ChatConfig, personalization),
113
- base,
92
+ PERSONALIZATION_CONFIG,
93
+ );
94
+ const merged = mergeChatConfig(
95
+ mergeChatConfig(
96
+ mergeChatConfig(mergedDefaults, settings),
97
+ personalization,
114
98
  ),
99
+ base,
115
100
  ) as ChatConfig;
116
101
 
117
102
  if (typeof merged.stream !== "boolean") merged.stream = true;
@@ -127,9 +112,8 @@ export default definePlugin({
127
112
  }
128
113
  merged.whitelistGroups = normalizeIdList(merged.whitelistGroups);
129
114
  merged.blacklistGroups = normalizeIdList(merged.blacklistGroups);
130
- merged.mediaAnalysisBlacklistUsers = normalizeIdList(
131
- (merged as any).mediaAnalysisBlacklistUsers ?? (merged as any).imageAnalysisBlacklistUsers,
132
- );
115
+ merged.mediaAnalysisBlacklistUsers =
116
+ normalizeMediaAnalysisBlacklist(merged as Record<string, any>);
133
117
  delete (merged as any).imageAnalysisBlacklistUsers;
134
118
  return merged;
135
119
  };
@@ -257,11 +241,12 @@ export default definePlugin({
257
241
  });
258
242
  }
259
243
 
260
- const humanize = new HumanizeEngine(mainAIInstance, workAIInstance, db, configProvider);
244
+ const humanize = new HumanizeEngine(workAIInstance, db, configProvider);
261
245
  await humanize.init();
262
246
 
263
247
  const pokeCooldowns = new Map<number, number>();
264
248
  const processingSet = new Set<string>();
249
+ const sessionTurnScheduler = new SessionTurnScheduler();
265
250
  const groupStructuredHistory = new GroupStructuredHistoryManager();
266
251
  const rateLimitGuard = new RateLimitGuard(rateLimiter, ctx.logger);
267
252
  const runWithRateLimitGuard = rateLimitGuard.run.bind(rateLimitGuard);
@@ -276,6 +261,7 @@ export default definePlugin({
276
261
  cooldownManager: undefined as unknown as CooldownManager,
277
262
  idleCheckManager: undefined as unknown as IdleCheckManager,
278
263
  queueProcessor: undefined as unknown as QueueProcessor,
264
+ sessionTurnScheduler,
279
265
  runWithRateLimitGuard,
280
266
  buildHistoryMediaOptions,
281
267
  getGroupHistoryMessages, getGroupInfoData, getHumanizeContexts,
@@ -342,16 +328,18 @@ export default definePlugin({
342
328
  ctx.logger.info("聊天插件加载成功");
343
329
 
344
330
  return () => {
345
- db.close();
346
- rateLimiter.dispose();
347
- clearInterval(cleanupInterval);
348
- dbCleanup.stop();
349
- cooldownManager.dispose();
350
331
  idleCheckManager.dispose();
332
+ cooldownManager.dispose();
351
333
  queueProcessor.dispose();
334
+ sessionTurnScheduler.dispose();
335
+ queueManager.dispose();
336
+ clearInterval(cleanupInterval);
337
+ dbCleanup.stop();
338
+ rateLimiter.dispose();
352
339
  processingSet.clear();
353
340
  pokeCooldowns.clear();
341
+ db.close();
354
342
  ctx.logger.info("聊天插件已卸载");
355
343
  };
356
344
  },
357
- });
345
+ });
@@ -6,23 +6,20 @@ 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 { buildStructuredUserInputFromEvent } from "./group-structured-history";
9
- import type { SessionManager } from "./session";
10
9
  import type { RateLimiter } from "./rate-limiter";
11
- import type { IdleCheckManager } from "./idle-check";
12
10
  import type {
13
11
  RunRateLimitGuard,
14
12
  HistoryMediaOptions,
15
13
  GetGroupHistoryMessages,
16
14
  GetGroupInfoData,
17
15
  GetHumanizeContexts,
18
- SendAIResponse,
19
- SaveBotMessages,
20
- SendEmoji,
21
16
  RunChat,
22
17
  BuildToolContext,
23
18
  BuildStructuredUserInput,
24
19
  } from "./types";
25
20
  import type { ChatPluginContext } from "../context";
21
+ import type { SessionTurnScheduler } from "./session-turn-scheduler";
22
+ import { finalizeChatTurn } from "../core/chat-turn";
26
23
  import { getBotRole } from "../utils";
27
24
 
28
25
  interface CooldownMessage {
@@ -39,6 +36,8 @@ export class CooldownManager {
39
36
  private groupCooldownUntil = new Map<string, number>();
40
37
  private groupCooldownMessages = new Map<string, CooldownMessage[]>();
41
38
  private cooldownTimeoutIds = new Map<string, NodeJS.Timeout>();
39
+ private pluginCtx: ChatPluginContext;
40
+ private sessionTurnScheduler: SessionTurnScheduler;
42
41
 
43
42
  private ctx: MiokiContext;
44
43
  private configProvider: ChatConfigProvider;
@@ -49,9 +48,7 @@ export class CooldownManager {
49
48
  private aiService: import("mioku").AIService;
50
49
  private skillManager: SkillSessionManager;
51
50
  private groupStructuredHistory: GroupStructuredHistoryManager;
52
- private sessionManager: SessionManager;
53
51
  private rateLimiter: RateLimiter;
54
- private idleCheckManager: IdleCheckManager;
55
52
  private runWithRateLimitGuard: RunRateLimitGuard;
56
53
  private buildHistoryMediaOptions: (
57
54
  ai: AIInstance,
@@ -60,14 +57,13 @@ export class CooldownManager {
60
57
  private getGroupHistoryMessages: GetGroupHistoryMessages;
61
58
  private getGroupInfoData: GetGroupInfoData;
62
59
  private getHumanizeContexts: GetHumanizeContexts;
63
- private sendAIResponse: SendAIResponse;
64
- private saveBotMessages: SaveBotMessages;
65
- private sendEmoji: SendEmoji;
66
60
  private runChat: RunChat;
67
61
  private buildToolContext: BuildToolContext;
68
62
  private buildStructuredUserInputFromEvent: BuildStructuredUserInput;
69
63
 
70
64
  constructor(pluginCtx: ChatPluginContext) {
65
+ this.pluginCtx = pluginCtx;
66
+ this.sessionTurnScheduler = pluginCtx.sessionTurnScheduler;
71
67
  this.ctx = pluginCtx.ctx;
72
68
  this.configProvider = pluginCtx.configProvider;
73
69
  this.defaultConfig = pluginCtx.defaultConfig;
@@ -77,17 +73,12 @@ export class CooldownManager {
77
73
  this.aiService = pluginCtx.aiService;
78
74
  this.skillManager = pluginCtx.skillManager;
79
75
  this.groupStructuredHistory = pluginCtx.groupStructuredHistory;
80
- this.sessionManager = pluginCtx.sessionManager;
81
76
  this.rateLimiter = pluginCtx.rateLimiter;
82
- this.idleCheckManager = pluginCtx.idleCheckManager;
83
77
  this.runWithRateLimitGuard = pluginCtx.runWithRateLimitGuard;
84
78
  this.buildHistoryMediaOptions = pluginCtx.buildHistoryMediaOptions;
85
79
  this.getGroupHistoryMessages = pluginCtx.getGroupHistoryMessages;
86
80
  this.getGroupInfoData = pluginCtx.getGroupInfoData;
87
81
  this.getHumanizeContexts = pluginCtx.getHumanizeContexts;
88
- this.sendAIResponse = pluginCtx.sendAIResponse;
89
- this.saveBotMessages = pluginCtx.saveBotMessages;
90
- this.sendEmoji = pluginCtx.sendEmoji;
91
82
  this.runChat = pluginCtx.runChat;
92
83
  this.buildToolContext = pluginCtx.buildToolContext;
93
84
  this.buildStructuredUserInputFromEvent = buildStructuredUserInputFromEvent;
@@ -102,47 +93,51 @@ export class CooldownManager {
102
93
  if (existingTimer) clearTimeout(existingTimer);
103
94
 
104
95
  const cfg = this.configProvider(groupId);
105
- const cooldownMs = cfg.cooldownAfterReplyMs ?? this.defaultConfig.cooldownAfterReplyMs ?? 20_000;
96
+ const cooldownMs =
97
+ cfg.cooldownAfterReplyMs ??
98
+ this.defaultConfig.cooldownAfterReplyMs ??
99
+ 20_000;
106
100
 
107
- const timer = setTimeout(async () => {
101
+ const timer = setTimeout(() => {
102
+ if (this.cooldownTimeoutIds.get(groupSessionId) !== timer) return;
108
103
  this.cooldownTimeoutIds.delete(groupSessionId);
104
+ this.groupCooldownUntil.delete(groupSessionId);
109
105
  const collected = this.groupCooldownMessages.get(groupSessionId) || [];
110
- if (collected.length === 0) {
111
- this.groupCooldownMessages.delete(groupSessionId);
112
- this.groupCooldownUntil.delete(groupSessionId);
113
- return;
114
- }
106
+ this.groupCooldownMessages.delete(groupSessionId);
107
+ if (collected.length === 0) return;
115
108
 
116
- const directAtMessages = collected.filter((m) => m.isDirectAt);
117
- try {
118
- if (directAtMessages.length > 0) {
119
- await this.processReviewMessages(
120
- groupSessionId,
121
- groupId,
122
- collected,
123
- selfId,
124
- );
125
- } else {
126
- await this.processCooldownWithPlanner(
127
- groupSessionId,
128
- groupId,
129
- collected,
130
- selfId,
131
- );
132
- }
133
- } catch (err) {
134
- this.ctx.logger.error(
135
- `[Cooldown] Group ${groupId} processing failed: ${err}`,
109
+ const hasDirectAt = collected.some((message) => message.isDirectAt);
110
+ void this.sessionTurnScheduler
111
+ .run(
112
+ groupSessionId,
113
+ hasDirectAt ? "cooldown" : "cooldown-planner",
114
+ () =>
115
+ hasDirectAt
116
+ ? this.processReviewMessages(
117
+ groupSessionId,
118
+ groupId,
119
+ collected,
120
+ selfId,
121
+ )
122
+ : this.processCooldownWithPlanner(
123
+ groupSessionId,
124
+ groupId,
125
+ collected,
126
+ selfId,
127
+ ),
128
+ )
129
+ .catch((err) =>
130
+ this.ctx.logger.error(
131
+ `[Cooldown] Group ${groupId} processing failed: ${err}`,
132
+ ),
136
133
  );
137
- } finally {
138
- this.groupCooldownMessages.delete(groupSessionId);
139
- this.groupCooldownUntil.delete(groupSessionId);
140
- }
141
134
  }, cooldownMs);
142
135
 
143
136
  this.cooldownTimeoutIds.set(groupSessionId, timer);
144
137
  this.groupCooldownUntil.set(groupSessionId, Date.now() + cooldownMs);
145
- this.groupCooldownMessages.set(groupSessionId, []);
138
+ if (!this.groupCooldownMessages.has(groupSessionId)) {
139
+ this.groupCooldownMessages.set(groupSessionId, []);
140
+ }
146
141
  }
147
142
 
148
143
  collectMessage(
@@ -285,36 +280,18 @@ export class CooldownManager {
285
280
  );
286
281
  if (!result) return;
287
282
 
288
- await this.sendAIResponse(
289
- {
290
- ctx: this.ctx,
291
- groupId,
292
- messages: result.messages,
293
- config: cfg,
294
- sentIndices: toolCtx.sentMessageIndices,
295
- },
296
- selfId,
297
- );
298
- await this.sendEmoji(this.ctx, groupId, result.emojiPath, selfId);
299
-
300
- const now = Date.now();
301
- this.saveBotMessages(
302
- groupId,
303
- groupSessionId,
304
- result.messages,
305
- now,
283
+ await finalizeChatTurn(this.pluginCtx, {
284
+ event: firstMsg.event,
306
285
  cfg,
307
- this.db,
308
- this.ctx,
309
- selfId,
310
- );
311
- this.idleCheckManager.recordBotMessages(
286
+ result,
287
+ groupId,
312
288
  groupSessionId,
313
- result.messages.length,
289
+ userId: targetMessage.userId,
314
290
  selfId,
315
- );
316
- this.sessionManager.touch(groupSessionId);
317
- this.startCooldownTimer(groupSessionId, groupId, selfId);
291
+ toolCtx,
292
+ send: true,
293
+ isLive: true,
294
+ });
318
295
  }
319
296
 
320
297
  private async processCooldownWithPlanner(
@@ -444,34 +421,17 @@ export class CooldownManager {
444
421
  return;
445
422
  }
446
423
 
447
- await this.sendAIResponse(
448
- {
449
- ctx: this.ctx,
450
- groupId,
451
- messages: result.messages,
452
- config: cfg,
453
- sentIndices: toolCtx.sentMessageIndices,
454
- },
455
- selfId,
456
- );
457
- await this.sendEmoji(this.ctx, groupId, result.emojiPath, selfId);
458
- const now = Date.now();
459
- this.saveBotMessages(
460
- groupId,
461
- groupSessionId,
462
- result.messages,
463
- now,
424
+ await finalizeChatTurn(this.pluginCtx, {
425
+ event: firstMsg.event,
464
426
  cfg,
465
- this.db,
466
- this.ctx,
467
- selfId,
468
- );
469
- this.idleCheckManager.recordBotMessages(
427
+ result,
428
+ groupId,
470
429
  groupSessionId,
471
- result.messages.length,
430
+ userId: targetMessage.userId,
472
431
  selfId,
473
- );
474
- this.sessionManager.touch(groupSessionId);
475
- this.startCooldownTimer(groupSessionId, groupId, selfId);
432
+ toolCtx,
433
+ send: true,
434
+ isLive: true,
435
+ });
476
436
  }
477
437
  }