koishi-plugin-group-memory 2.0.0 → 2.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.
Files changed (2) hide show
  1. package/package.json +32 -12
  2. package/src/index.js +351 -170
package/package.json CHANGED
@@ -1,9 +1,16 @@
1
1
  {
2
2
  "name": "koishi-plugin-group-memory",
3
- "version": "2.0.0",
4
- "description": "智能群聊记忆中间件:自动记录上下文,在@或回复时注入前情提要,支持时间窗口过滤与内容清洗",
5
- "main": "./src/index.js",
3
+ "version": "2.1.0",
4
+ "description": "群聊和私聊上下文记忆插件,为 AI 插件提供历史记录查询接口。支持数据库持久化、自动清理、上下文注入,可与其他 AI 插件无缝协作",
5
+ "main": "lib/index.js",
6
+ "typings": "lib/index.d.ts",
7
+ "files": [
8
+ "lib",
9
+ "src"
10
+ ],
6
11
  "scripts": {
12
+ "build": "atsc -b",
13
+ "build:watch": "atsc -b -w",
7
14
  "test": "echo \"Error: no test specified\" && exit 1"
8
15
  },
9
16
  "keywords": [
@@ -11,32 +18,45 @@
11
18
  "plugin",
12
19
  "memory",
13
20
  "context",
14
- "group-chat",
21
+ "chat-history",
22
+ "group-memory",
23
+ "database",
24
+ "cache",
15
25
  "ai",
16
- "middleware",
17
- "deepseek",
18
- "openai"
26
+ "llm",
27
+ "conversation",
28
+ "history"
19
29
  ],
20
30
  "author": "YourName",
21
31
  "license": "MIT",
22
32
  "repository": {
23
33
  "type": "git",
24
- "url": ""
34
+ "url": "https://github.com/yourusername/koishi-plugin-group-memory"
35
+ },
36
+ "bugs": {
37
+ "url": "https://github.com/yourusername/koishi-plugin-group-memory/issues"
25
38
  },
39
+ "homepage": "https://github.com/yourusername/koishi-plugin-group-memory#readme",
26
40
  "koishi": {
27
41
  "description": {
28
- "en": "Smart group chat memory middleware. Automatically records context and injects summaries when @mentioned or replied. Supports time-window filtering, content cleaning (mentions/images), and custom templates. Perfect for making AI bots aware of ongoing conversations.",
29
- "zh": "智能群聊记忆中间件。自动记录群内闲聊上下文,当用户 @机器人 或回复机器人时,自动将“前情提要”注入给 AI 插件。支持按时间/条数过滤记忆、自动清洗 @符号和图片、自定义提示词模板。是让 AI 机器人拥有“群聊感知力”的必备插件。"
42
+ "en": "Group and private chat memory plugin. Provides history query interface for AI plugins. Supports database persistence, auto-cleanup, context injection, and seamless collaboration with other AI plugins.",
43
+ "zh": "群聊和私聊上下文记忆插件,为 AI 插件提供历史记录查询接口。支持数据库持久化、自动清理、上下文注入,可与其他 AI 插件无缝协作。"
30
44
  },
31
45
  "service": {
32
- "required": [],
46
+ "required": ["database"],
33
47
  "optional": []
34
48
  }
35
49
  },
36
50
  "peerDependencies": {
37
51
  "koishi": "^4.16.0"
38
52
  },
53
+ "devDependencies": {
54
+ "@types/node": "^18.0.0",
55
+ "atsc": "^1.2.0",
56
+ "typescript": "^5.0.0"
57
+ },
39
58
  "engines": {
40
59
  "node": ">=18.0.0"
41
- }
60
+ },
61
+ "dependencies": {}
42
62
  }
package/src/index.js CHANGED
@@ -17,16 +17,59 @@ const MessageFields = {
17
17
 
18
18
  // --- 配置架构 ---
19
19
  exports.Config = Schema.object({
20
- // ... 其他配置保持不变 ...
20
+ // --- 存储设置 ---
21
+ maxContext: Schema.number()
22
+ .default(20)
23
+ .min(1)
24
+ .max(100)
25
+ .description('每个会话保留最近多少条消息作为上下文。'),
26
+
27
+ timeWindow: Schema.number()
28
+ .default(3600000)
29
+ .min(0)
30
+ .step(60000)
31
+ .description('只保留最近 N 毫秒内的消息。设置为 0 表示不限制时间。'),
32
+
33
+ // --- 记录设置 ---
34
+ recordPrivateChat: Schema.boolean()
35
+ .default(true)
36
+ .description('是否记录私聊消息。'),
37
+
38
+ recordGroupChat: Schema.boolean()
39
+ .default(true)
40
+ .description('是否记录群聊消息。'),
41
+
42
+ // --- 触发条件(用于中间件模式) ---
43
+ triggerOnAt: Schema.boolean()
44
+ .default(true)
45
+ .description('当消息中包含 @机器人 时,触发上下文注入。'),
46
+
47
+ triggerOnReply: Schema.boolean()
48
+ .default(true)
49
+ .description('当消息是回复机器人的消息时,触发上下文注入。'),
50
+
51
+ // --- 内容处理 ---
52
+ cleanMentions: Schema.boolean()
53
+ .default(true)
54
+ .description('发送给 AI 前,是否移除消息中的 @ 符号。'),
55
+
56
+ cleanImages: Schema.boolean()
57
+ .default(true)
58
+ .description('是否移除图片/媒体占位符。'),
59
+
60
+ // --- 调试 ---
61
+ verbose: Schema.boolean()
62
+ .default(false)
63
+ .description('是否在控制台打印注入的上下文 (调试用)。'),
21
64
 
22
65
  // ============================================
23
- // 新增:日志显示功能
66
+ // 新增:日志显示功能(修复版)
24
67
  // ============================================
25
68
  logLevel: Schema.union([
26
69
  Schema.const('none').description('不输出日志'),
27
70
  Schema.const('simple').description('简易模式:输出易于理解的操作摘要'),
28
71
  Schema.const('detailed').description('详细模式:输出完整的 JSON 格式日志'),
29
- ]).default('simple').description('日志输出级别'),
72
+ ]).default('detailed').description('日志输出级别(默认详细,便于调试)'), // 改为默认 detailed
30
73
 
31
74
  logDatabaseOps: Schema.boolean()
32
75
  .default(true)
@@ -40,6 +83,10 @@ exports.Config = Schema.object({
40
83
  .default(true)
41
84
  .description('是否记录对外部插件的 API 调用'),
42
85
 
86
+ logMessageReceive: Schema.boolean()
87
+ .default(true)
88
+ .description('是否记录接收到的原始消息'),
89
+
43
90
  logContentPreview: Schema.boolean()
44
91
  .default(true)
45
92
  .description('是否在简易模式下预览消息内容(只显示前30个字符)'),
@@ -51,6 +98,10 @@ exports.Config = Schema.object({
51
98
  logTimestamp: Schema.boolean()
52
99
  .default(true)
53
100
  .description('是否在日志中显示时间戳'),
101
+
102
+ logMiddlewarePriority: Schema.boolean()
103
+ .default(true)
104
+ .description('是否记录中间件执行优先级信息'),
54
105
  });
55
106
 
56
107
  exports.schema = exports.Config;
@@ -61,19 +112,20 @@ exports.apply = (ctx, config) => {
61
112
  const logger = ctx.logger('group-memory');
62
113
 
63
114
  // ============================================
64
- // 日志增强模块
115
+ // 日志增强模块(修复版)
65
116
  // ============================================
66
117
 
67
- // 颜色代码(仅在启用时使用)
118
+ // 立即输出启动日志,确认插件已加载
119
+ logger.info('='.repeat(50));
120
+ logger.info('🧠 Group Memory 插件正在初始化...');
121
+ logger.info(`日志级别: ${config.logLevel}`);
122
+ logger.info(`记录私聊: ${config.recordPrivateChat}`);
123
+ logger.info(`记录群聊: ${config.recordGroupChat}`);
124
+ logger.info('='.repeat(50));
125
+
126
+ // 颜色代码
68
127
  const colors = {
69
128
  reset: '\x1b[0m',
70
- bright: '\x1b[1m',
71
- dim: '\x1b[2m',
72
- underscore: '\x1b[4m',
73
- blink: '\x1b[5m',
74
- reverse: '\x1b[7m',
75
- hidden: '\x1b[8m',
76
-
77
129
  fg: {
78
130
  black: '\x1b[30m',
79
131
  red: '\x1b[31m',
@@ -83,39 +135,25 @@ exports.apply = (ctx, config) => {
83
135
  magenta: '\x1b[35m',
84
136
  cyan: '\x1b[36m',
85
137
  white: '\x1b[37m',
86
- crimson: '\x1b[38m'
87
- },
88
- bg: {
89
- black: '\x1b[40m',
90
- red: '\x1b[41m',
91
- green: '\x1b[42m',
92
- yellow: '\x1b[43m',
93
- blue: '\x1b[44m',
94
- magenta: '\x1b[45m',
95
- cyan: '\x1b[46m',
96
- white: '\x1b[47m',
97
- crimson: '\x1b[48m'
98
138
  }
99
139
  };
100
140
 
101
- // 是否启用颜色
102
141
  const useColor = config.logColorOutput && process.stdout.isTTY;
103
142
 
104
- // 颜色辅助函数
105
143
  const color = (text, colorCode) => {
106
144
  if (!useColor) return text;
107
145
  return `${colorCode}${text}${colors.reset}`;
108
146
  };
109
147
 
110
- // 格式化时间戳
111
148
  const getTimestamp = () => {
112
149
  if (!config.logTimestamp) return '';
113
150
  const now = new Date();
114
151
  return `[${now.toLocaleTimeString()}.${now.getMilliseconds().toString().padStart(3, '0')}] `;
115
152
  };
116
153
 
117
- // 日志类型图标
154
+ // 日志图标
118
155
  const icons = {
156
+ receive: '📥',
119
157
  db: '🗄️',
120
158
  cache: '⚡',
121
159
  inject: '📎',
@@ -127,137 +165,210 @@ exports.apply = (ctx, config) => {
127
165
  info: 'ℹ️',
128
166
  debug: '🔍',
129
167
  memory: '🧠',
130
- cleanup: '🧹'
168
+ cleanup: '🧹',
169
+ middleware: '🔄'
131
170
  };
132
171
 
133
- // 简易日志函数
134
- const logSimple = (type, message, data = null) => {
135
- if (config.logLevel === 'none') return;
136
-
137
- const timestamp = getTimestamp();
138
- const icon = icons[type] || '📝';
139
- let colorFn;
140
-
141
- switch(type) {
142
- case 'error': colorFn = (t) => color(t, colors.fg.red); break;
143
- case 'warning': colorFn = (t) => color(t, colors.fg.yellow); break;
144
- case 'success': colorFn = (t) => color(t, colors.fg.green); break;
145
- case 'db': colorFn = (t) => color(t, colors.fg.cyan); break;
146
- case 'inject': colorFn = (t) => color(t, colors.fg.magenta); break;
147
- case 'api': colorFn = (t) => color(t, colors.fg.blue); break;
148
- default: colorFn = (t) => t;
149
- }
150
-
151
- const logMessage = `${timestamp}${icon} ${colorFn(message)}`;
152
-
153
- if (data && config.logContentPreview) {
154
- let preview = '';
155
- if (typeof data === 'string') {
156
- preview = data.length > 30 ? data.substring(0, 30) + '...' : data;
157
- } else if (data.content) {
158
- preview = data.content.length > 30 ? data.content.substring(0, 30) + '...' : data.content;
159
- }
160
- if (preview) {
161
- logger.info(`${logMessage} ${color(`"${preview}"`, colors.fg.yellow)}`);
172
+ // 增强的日志函数 - 确保立即输出
173
+ const log = {
174
+ // 接收消息日志
175
+ receive: (session, details, simpleMsg) => {
176
+ if (config.logLevel === 'none') return;
177
+ if (!config.logMessageReceive) return;
178
+
179
+ const timestamp = getTimestamp();
180
+ const icon = icons.receive;
181
+
182
+ if (config.logLevel === 'detailed') {
183
+ logger.info(`${timestamp}${icon} ${JSON.stringify({
184
+ type: 'receive',
185
+ ...details
186
+ }, null, 2)}`);
162
187
  } else {
163
- logger.info(logMessage);
188
+ const msg = simpleMsg || `收到消息: ${details.userName} ${details.contentPreview}`;
189
+ logger.info(`${timestamp}${icon} ${color(msg, colors.fg.cyan)}`);
164
190
  }
165
- } else {
166
- logger.info(logMessage);
167
- }
168
- };
169
-
170
- // 详细日志函数
171
- const logDetailed = (type, operation, details) => {
172
- if (config.logLevel !== 'detailed') return;
173
-
174
- const timestamp = getTimestamp();
175
- const icon = icons[type] || '📝';
176
-
177
- const logEntry = {
178
- timestamp: new Date().toISOString(),
179
- type,
180
- operation,
181
- ...details
182
- };
191
+ },
183
192
 
184
- logger.info(`${timestamp}${icon} ${JSON.stringify(logEntry, null, 2)}`);
185
- };
186
-
187
- // 统一日志接口
188
- const log = {
193
+ // 数据库操作日志
189
194
  db: (operation, details, simpleMsg) => {
195
+ if (config.logLevel === 'none') return;
190
196
  if (!config.logDatabaseOps) return;
197
+
198
+ const timestamp = getTimestamp();
199
+ const icon = icons.db;
200
+
191
201
  if (config.logLevel === 'detailed') {
192
- logDetailed('db', operation, details);
193
- } else if (config.logLevel === 'simple') {
194
- logSimple('db', simpleMsg || `${operation} 操作`, details);
202
+ logger.info(`${timestamp}${icon} ${JSON.stringify({
203
+ type: 'database',
204
+ operation,
205
+ ...details
206
+ }, null, 2)}`);
207
+ } else {
208
+ logger.info(`${timestamp}${icon} ${simpleMsg || `数据库 ${operation}`}`);
195
209
  }
196
210
  },
197
211
 
212
+ // 上下文注入日志
198
213
  inject: (operation, details, simpleMsg) => {
214
+ if (config.logLevel === 'none') return;
199
215
  if (!config.logContextInject) return;
216
+
217
+ const timestamp = getTimestamp();
218
+ const icon = icons.inject;
219
+
200
220
  if (config.logLevel === 'detailed') {
201
- logDetailed('inject', operation, details);
202
- } else if (config.logLevel === 'simple') {
203
- logSimple('inject', simpleMsg || `上下文注入: ${operation}`, details);
221
+ logger.info(`${timestamp}${icon} ${JSON.stringify({
222
+ type: 'inject',
223
+ operation,
224
+ ...details
225
+ }, null, 2)}`);
226
+ } else {
227
+ logger.info(`${timestamp}${icon} ${simpleMsg || `上下文注入: ${operation}`}`);
204
228
  }
205
229
  },
206
230
 
231
+ // API调用日志
207
232
  api: (operation, details, simpleMsg) => {
233
+ if (config.logLevel === 'none') return;
208
234
  if (!config.logApiCalls) return;
235
+
236
+ const timestamp = getTimestamp();
237
+ const icon = icons.api;
238
+
239
+ if (config.logLevel === 'detailed') {
240
+ logger.info(`${timestamp}${icon} ${JSON.stringify({
241
+ type: 'api',
242
+ operation,
243
+ ...details
244
+ }, null, 2)}`);
245
+ } else {
246
+ logger.info(`${timestamp}${icon} ${simpleMsg || `API: ${operation}`}`);
247
+ }
248
+ },
249
+
250
+ // 中间件日志
251
+ middleware: (msg, details) => {
252
+ if (config.logLevel === 'none') return;
253
+ if (!config.logMiddlewarePriority) return;
254
+
255
+ const timestamp = getTimestamp();
256
+ const icon = icons.middleware;
257
+
209
258
  if (config.logLevel === 'detailed') {
210
- logDetailed('api', operation, details);
211
- } else if (config.logLevel === 'simple') {
212
- logSimple('api', simpleMsg || `API调用: ${operation}`, details);
259
+ logger.info(`${timestamp}${icon} ${JSON.stringify({
260
+ type: 'middleware',
261
+ ...details
262
+ }, null, 2)}`);
263
+ } else {
264
+ logger.info(`${timestamp}${icon} ${msg}`);
213
265
  }
214
266
  },
215
267
 
268
+ // 命令日志
216
269
  command: (cmd, user, details) => {
217
270
  if (config.logLevel === 'none') return;
271
+
272
+ const timestamp = getTimestamp();
273
+ const icon = icons.command;
218
274
  const simpleMsg = `命令 ${cmd} 由 ${user} 执行`;
275
+
219
276
  if (config.logLevel === 'detailed') {
220
- logDetailed('command', cmd, { user, ...details });
277
+ logger.info(`${timestamp}${icon} ${JSON.stringify({
278
+ type: 'command',
279
+ command: cmd,
280
+ user,
281
+ ...details
282
+ }, null, 2)}`);
221
283
  } else {
222
- logSimple('command', simpleMsg, details);
284
+ logger.info(`${timestamp}${icon} ${simpleMsg}`);
223
285
  }
224
286
  },
225
287
 
288
+ // 错误日志
226
289
  error: (msg, error) => {
227
290
  if (config.logLevel === 'none') return;
228
- const errorDetails = error ? { message: error.message, stack: error.stack } : {};
229
- if (config.logLevel === 'detailed') {
230
- logDetailed('error', '错误', { message: msg, ...errorDetails });
231
- } else {
232
- logSimple('error', `❌ ${msg}`, error);
291
+
292
+ const timestamp = getTimestamp();
293
+ const icon = icons.error;
294
+ const errorMsg = error ? `${msg}: ${error.message}` : msg;
295
+
296
+ logger.error(`${timestamp}${icon} ${color(errorMsg, colors.fg.red)}`);
297
+
298
+ if (config.logLevel === 'detailed' && error?.stack) {
299
+ logger.error(error.stack);
233
300
  }
234
301
  },
235
302
 
303
+ // 成功日志
236
304
  success: (msg, details) => {
237
305
  if (config.logLevel === 'none') return;
306
+
307
+ const timestamp = getTimestamp();
308
+ const icon = icons.success;
309
+
238
310
  if (config.logLevel === 'detailed') {
239
- logDetailed('success', '成功', { message: msg, ...details });
311
+ logger.info(`${timestamp}${icon} ${JSON.stringify({
312
+ type: 'success',
313
+ message: msg,
314
+ ...details
315
+ }, null, 2)}`);
240
316
  } else {
241
- logSimple('success', `✅ ${msg}`, details);
317
+ logger.info(`${timestamp}${icon} ${color(msg, colors.fg.green)}`);
242
318
  }
243
319
  },
244
320
 
321
+ // 缓存日志
245
322
  cache: (operation, details) => {
246
323
  if (config.logLevel === 'none') return;
324
+
325
+ const timestamp = getTimestamp();
326
+ const icon = icons.cache;
327
+
247
328
  if (config.logLevel === 'detailed') {
248
- logDetailed('cache', operation, details);
329
+ logger.info(`${timestamp}${icon} ${JSON.stringify({
330
+ type: 'cache',
331
+ operation,
332
+ ...details
333
+ }, null, 2)}`);
249
334
  } else {
250
- logSimple('cache', `缓存 ${operation}`, details);
335
+ logger.info(`${timestamp}${icon} 缓存 ${operation}`);
251
336
  }
252
337
  },
253
338
 
339
+ // 清理日志
254
340
  cleanup: (count, details) => {
255
341
  if (config.logLevel === 'none') return;
342
+
343
+ const timestamp = getTimestamp();
344
+ const icon = icons.cleanup;
256
345
  const simpleMsg = `清理了 ${count} 条过期消息`;
346
+
257
347
  if (config.logLevel === 'detailed') {
258
- logDetailed('cleanup', '清理', { count, ...details });
348
+ logger.info(`${timestamp}${icon} ${JSON.stringify({
349
+ type: 'cleanup',
350
+ count,
351
+ ...details
352
+ }, null, 2)}`);
259
353
  } else {
260
- logSimple('cleanup', simpleMsg, details);
354
+ logger.info(`${timestamp}${icon} ${simpleMsg}`);
355
+ }
356
+ },
357
+
358
+ // 信息日志
359
+ info: (msg, details) => {
360
+ if (config.logLevel === 'none') return;
361
+
362
+ const timestamp = getTimestamp();
363
+ const icon = icons.info;
364
+
365
+ if (config.logLevel === 'detailed' && details) {
366
+ logger.info(`${timestamp}${icon} ${JSON.stringify({
367
+ message: msg,
368
+ ...details
369
+ }, null, 2)}`);
370
+ } else {
371
+ logger.info(`${timestamp}${icon} ${msg}`);
261
372
  }
262
373
  }
263
374
  };
@@ -280,10 +391,10 @@ exports.apply = (ctx, config) => {
280
391
  await new Promise(resolve => setTimeout(resolve, 100));
281
392
  }
282
393
  sessionLocks.set(sessionId, true);
283
- log.cache('锁定', { sessionId, action: 'lock' });
394
+ log.cache('锁定', { sessionId });
284
395
  return () => {
285
396
  sessionLocks.delete(sessionId);
286
- log.cache('解锁', { sessionId, action: 'unlock' });
397
+ log.cache('解锁', { sessionId });
287
398
  };
288
399
  };
289
400
 
@@ -294,30 +405,35 @@ exports.apply = (ctx, config) => {
294
405
  index: ['session_id', 'session_type', 'timestamp']
295
406
  });
296
407
 
297
- log.success('数据库表初始化完成', { table: 'group_memory_messages' });
408
+ log.success('数据库表初始化完成');
298
409
 
299
410
  // --- 辅助函数:生成会话ID ---
300
411
  const generateSessionId = (session) => {
301
412
  const result = !session.groupId
302
- ? { sessionId: `private:${session.userId}`, sessionType: 'private' }
413
+ ? {
414
+ sessionId: `private:${session.userId}`,
415
+ sessionType: 'private'
416
+ }
303
417
  : {
304
418
  sessionId: `group:${session.groupId}`,
305
- separateId: config.supportSeparateContext ? `${session.groupId}:user:${session.userId}` : undefined,
419
+ separateId: config.supportSeparateContext ? `group:${session.groupId}:user:${session.userId}` : undefined,
306
420
  sessionType: 'group'
307
421
  };
308
422
 
309
423
  log.db('生成会话ID', {
310
424
  input: { userId: session.userId, groupId: session.groupId },
311
425
  output: result
312
- }, `生成会话ID: ${result.sessionId}`);
426
+ }, `会话ID: ${result.sessionId}`);
313
427
 
314
428
  return result;
315
429
  };
316
430
 
317
431
  // --- 辅助函数:清理内容 ---
318
432
  const cleanContent = (content, session) => {
433
+ if (!content) return '';
434
+
319
435
  let text = content;
320
- const originalLength = text.length;
436
+ const originalPreview = text.substring(0, 30);
321
437
 
322
438
  if (config.cleanMentions && session) {
323
439
  text = text.replace(new RegExp(`<@${session.selfId}>`, 'g'), '')
@@ -335,10 +451,9 @@ exports.apply = (ctx, config) => {
335
451
  const result = text.trim() || '[空消息]';
336
452
 
337
453
  log.db('清理内容', {
338
- original: { length: originalLength, preview: content.substring(0, 30) },
339
- result: { length: result.length, preview: result.substring(0, 30) },
340
- operations: { cleanMentions: config.cleanMentions, cleanImages: config.cleanImages }
341
- }, `清理内容: ${originalLength} -> ${result.length} 字符`);
454
+ original: originalPreview,
455
+ result: result.substring(0, 30)
456
+ }, `内容清理: ${originalPreview} -> ${result.substring(0, 30)}`);
342
457
 
343
458
  return result;
344
459
  };
@@ -361,29 +476,28 @@ exports.apply = (ctx, config) => {
361
476
  }
362
477
  }
363
478
  if (cleaned > 0) {
364
- log.cache('清理过期缓存', { cleaned, ttl: CACHE_TTL });
479
+ log.cache('清理过期缓存', { cleaned });
365
480
  }
366
481
  };
367
482
  setInterval(cleanupCache, CACHE_TTL);
368
483
 
369
484
  // --- 核心 API:获取上下文 ---
370
485
  const getContext = async (sessionId, limit = null) => {
371
- log.api('getContext', { sessionId, limit }, `获取上下文: ${sessionId}`);
486
+ log.api('getContext', { sessionId, limit });
372
487
 
373
- // 尝试从缓存获取
374
488
  if (memoryCache.has(sessionId)) {
375
489
  const cached = memoryCache.get(sessionId);
376
490
  const age = Date.now() - cacheTimestamps.get(sessionId);
377
491
  if (age < CACHE_TTL) {
378
- log.cache('命中', { sessionId, age, count: cached.length }, `缓存命中: ${sessionId} (${cached.length}条)`);
492
+ log.cache('命中', { sessionId, count: cached.length });
379
493
  return cached;
380
494
  } else {
381
- log.cache('过期', { sessionId, age }, `缓存过期: ${sessionId}`);
495
+ log.cache('过期', { sessionId, age });
382
496
  }
383
497
  }
384
498
 
385
499
  const maxMessages = limit ? limit * 2 : config.maxContext * 2;
386
- log.db('查询', { sessionId, maxMessages }, `查询数据库: ${sessionId}`);
500
+ log.db('查询', { sessionId, maxMessages });
387
501
 
388
502
  try {
389
503
  let history = await ctx.database.get('group_memory_messages',
@@ -401,13 +515,12 @@ exports.apply = (ctx, config) => {
401
515
  const before = history.length;
402
516
  const cutoff = Date.now() - config.timeWindow;
403
517
  history = history.filter(msg => msg.timestamp >= cutoff);
404
- log.db('时间窗口过滤', { before, after: history.length, cutoff }, `过滤时间窗口: ${before} -> ${history.length}条`);
518
+ log.db('时间窗口过滤', { before, after: history.length });
405
519
  }
406
520
 
407
- // 更新缓存
408
521
  memoryCache.set(sessionId, history);
409
522
  cacheTimestamps.set(sessionId, Date.now());
410
- log.cache('更新', { sessionId, count: history.length }, `更新缓存: ${sessionId} (${history.length}条)`);
523
+ log.cache('更新', { sessionId, count: history.length });
411
524
 
412
525
  return history;
413
526
  } catch (error) {
@@ -418,7 +531,7 @@ exports.apply = (ctx, config) => {
418
531
 
419
532
  // --- 核心 API:保存消息 ---
420
533
  const saveMessage = async (sessionId, role, content, userId = 'system', userName = 'System') => {
421
- log.api('saveMessage', { sessionId, role, userId }, `保存消息: ${role} @ ${sessionId}`);
534
+ log.api('saveMessage', { sessionId, role, userId });
422
535
 
423
536
  const release = await lockSession(sessionId);
424
537
 
@@ -435,14 +548,18 @@ exports.apply = (ctx, config) => {
435
548
  timestamp: Date.now(),
436
549
  };
437
550
 
438
- log.db('创建', messageData, `插入消息: ${role} (${content.substring(0, 30)}...)`);
551
+ log.db('创建', {
552
+ sessionId,
553
+ role,
554
+ userName,
555
+ contentPreview: content.substring(0, 30)
556
+ });
439
557
 
440
558
  await ctx.database.create('group_memory_messages', messageData);
441
559
 
442
- // 使缓存失效
443
560
  memoryCache.delete(sessionId);
444
561
  cacheTimestamps.delete(sessionId);
445
- log.cache('失效', { sessionId }, `缓存失效: ${sessionId}`);
562
+ log.cache('失效', { sessionId });
446
563
 
447
564
  // 异步清理旧消息
448
565
  cleanupOldMessages(sessionId).catch(err => {
@@ -460,7 +577,7 @@ exports.apply = (ctx, config) => {
460
577
 
461
578
  // --- API:删除最后一条消息 ---
462
579
  const deleteLastMessage = async (sessionId, role) => {
463
- log.api('deleteLastMessage', { sessionId, role }, `删除最后一条 ${role} 消息: ${sessionId}`);
580
+ log.api('deleteLastMessage', { sessionId, role });
464
581
 
465
582
  const release = await lockSession(sessionId);
466
583
 
@@ -474,14 +591,13 @@ exports.apply = (ctx, config) => {
474
591
  );
475
592
 
476
593
  if (lastMessage && lastMessage.length > 0) {
477
- log.db('删除', { id: lastMessage[0].id }, `删除消息 ID: ${lastMessage[0].id}`);
594
+ log.db('删除', { id: lastMessage[0].id });
478
595
  await ctx.database.remove('group_memory_messages', { id: lastMessage[0].id });
479
596
  memoryCache.delete(sessionId);
480
597
  cacheTimestamps.delete(sessionId);
481
598
  return true;
482
599
  }
483
600
 
484
- log.db('删除', { sessionId, role }, `未找到要删除的消息`);
485
601
  return false;
486
602
  } catch (error) {
487
603
  log.error('删除消息失败', error);
@@ -493,17 +609,17 @@ exports.apply = (ctx, config) => {
493
609
 
494
610
  // --- API:清空上下文 ---
495
611
  const clearContext = async (sessionId) => {
496
- log.api('clearContext', { sessionId }, `清空上下文: ${sessionId}`);
612
+ log.api('clearContext', { sessionId });
497
613
 
498
614
  const release = await lockSession(sessionId);
499
615
 
500
616
  try {
501
617
  const count = await ctx.database.remove('group_memory_messages', { session_id: sessionId });
502
- log.db('清空', { sessionId, count }, `清空了 ${count} 条消息`);
618
+ log.db('清空', { sessionId, count });
503
619
 
504
620
  memoryCache.delete(sessionId);
505
621
  cacheTimestamps.delete(sessionId);
506
- log.cache('失效', { sessionId }, `缓存失效: ${sessionId}`);
622
+ log.cache('失效', { sessionId });
507
623
 
508
624
  return true;
509
625
  } catch (error) {
@@ -527,23 +643,18 @@ exports.apply = (ctx, config) => {
527
643
  const maxMessages = config.maxContext * 2;
528
644
  let toDelete = [];
529
645
 
530
- // 按数量限制
531
646
  if (allMessages.length > maxMessages) {
532
647
  allMessages.sort((a, b) => a.id - b.id);
533
648
  const excess = allMessages.slice(0, allMessages.length - maxMessages);
534
649
  toDelete.push(...excess);
535
- log.db('数量限制', { total: allMessages.length, max: maxMessages, excess: excess.length });
536
650
  }
537
651
 
538
- // 按时间窗口限制
539
652
  if (config.timeWindow > 0) {
540
653
  const cutoff = Date.now() - config.timeWindow;
541
654
  const expired = allMessages.filter(msg => msg.timestamp < cutoff);
542
655
  toDelete.push(...expired);
543
- log.db('时间窗口限制', { cutoff, expired: expired.length });
544
656
  }
545
657
 
546
- // 批量删除
547
658
  if (toDelete.length > 0) {
548
659
  const uniqueIds = [...new Set(toDelete.map(msg => msg.id))];
549
660
  await ctx.database.remove('group_memory_messages', { id: uniqueIds });
@@ -551,7 +662,7 @@ exports.apply = (ctx, config) => {
551
662
  memoryCache.delete(sessionId);
552
663
  cacheTimestamps.delete(sessionId);
553
664
 
554
- log.cleanup(uniqueIds.length, { sessionId, ids: uniqueIds });
665
+ log.cleanup(uniqueIds.length, { sessionId });
555
666
  }
556
667
  } catch (error) {
557
668
  log.error('清理旧消息失败', error);
@@ -568,69 +679,140 @@ exports.apply = (ctx, config) => {
568
679
  deleteLastMessage,
569
680
  };
570
681
 
571
- log.success('API 接口已暴露', { methods: ['getContext', 'saveMessage', 'clearContext', 'deleteLastMessage'] });
682
+ log.success('API 接口已暴露');
572
683
 
573
- // --- 中间件1: 记录所有消息 ---
684
+ // ============================================
685
+ // 修复:中间件 - 记录所有消息(提高优先级)
686
+ // ============================================
574
687
  ctx.middleware(async (session, next) => {
575
- if (session.selfId === session.userId) return next();
688
+ // 记录中间件执行
689
+ log.middleware('执行消息记录中间件', {
690
+ userId: session.userId,
691
+ groupId: session.groupId,
692
+ content: session.content?.substring(0, 30)
693
+ });
694
+
695
+ if (session.selfId === session.userId) {
696
+ log.middleware('忽略机器人自身消息');
697
+ return next();
698
+ }
576
699
 
577
700
  const trimmed = session.content?.trim() || '';
578
- if (!trimmed) return next();
701
+ if (!trimmed) {
702
+ log.middleware('忽略空消息');
703
+ return next();
704
+ }
579
705
 
580
- if (/^[./\\]/.test(trimmed)) return next();
706
+ // 记录原始消息接收
707
+ const contentPreview = trimmed.substring(0, 50) + (trimmed.length > 50 ? '...' : '');
708
+ const userName = session.author?.nickname || session.userId;
709
+ const chatType = !session.groupId ? '私聊' : '群聊';
710
+
711
+ log.receive(session, {
712
+ userId: session.userId,
713
+ userName,
714
+ groupId: session.groupId,
715
+ content: trimmed,
716
+ contentPreview,
717
+ chatType
718
+ }, `${chatType} ${userName}: ${contentPreview}`);
719
+
720
+ // 忽略命令消息
721
+ if (/^[./\\]/.test(trimmed)) {
722
+ log.middleware('忽略命令消息');
723
+ return next();
724
+ }
581
725
 
582
726
  const sessionInfo = generateSessionId(session);
583
727
  const sessionId = sessionInfo.separateId || sessionInfo.sessionId;
584
728
  const sessionType = sessionInfo.sessionType;
585
729
 
586
- if (sessionType === 'private' && !config.recordPrivateChat) return next();
587
- if (sessionType === 'group' && !config.recordGroupChat) return next();
730
+ // 根据配置决定是否记录
731
+ if (sessionType === 'private' && !config.recordPrivateChat) {
732
+ log.middleware('私聊记录已禁用');
733
+ return next();
734
+ }
735
+ if (sessionType === 'group' && !config.recordGroupChat) {
736
+ log.middleware('群聊记录已禁用');
737
+ return next();
738
+ }
588
739
 
589
740
  const cleanText = cleanContent(session.content, session);
590
- const userName = session.author?.nickname || session.userId;
591
-
592
- log.db('记录消息', {
741
+
742
+ log.db('准备保存消息', {
593
743
  sessionId,
594
744
  sessionType,
595
745
  userId: session.userId,
596
746
  userName,
597
- content: cleanText
598
- }, `📝 ${userName}: ${cleanText.substring(0, 30)}...`);
599
-
600
- saveMessage(sessionId, 'user', cleanText, session.userId, userName).catch(err => {
601
- log.error('异步保存消息失败', err);
747
+ contentPreview: cleanText.substring(0, 30)
602
748
  });
603
749
 
750
+ // 立即保存并等待完成,确保日志输出
751
+ try {
752
+ await saveMessage(sessionId, 'user', cleanText, session.userId, userName);
753
+ log.success(`消息已保存: ${sessionId}`);
754
+ } catch (err) {
755
+ log.error('保存消息失败', err);
756
+ }
757
+
604
758
  return next();
605
- }, -100);
759
+ }, 100); // 提高优先级到 100,确保先执行
606
760
 
607
- // --- 中间件2: 上下文注入 ---
761
+ // ============================================
762
+ // 中间件2: 上下文注入(保持优先级 0)
763
+ // ============================================
608
764
  ctx.middleware(async (session, next) => {
609
- if (!session.groupId && !config.recordPrivateChat) return next();
610
- if (session.groupId && !config.recordGroupChat) return next();
765
+ log.middleware('执行上下文注入中间件', {
766
+ userId: session.userId,
767
+ groupId: session.groupId
768
+ });
769
+
770
+ if (!session.groupId && !config.recordPrivateChat) {
771
+ log.middleware('私聊注入已禁用');
772
+ return next();
773
+ }
774
+ if (session.groupId && !config.recordGroupChat) {
775
+ log.middleware('群聊注入已禁用');
776
+ return next();
777
+ }
611
778
 
612
779
  let shouldInject = false;
780
+ let injectReason = '';
613
781
 
782
+ // 检查是否 @ 机器人
614
783
  if (config.triggerOnAt) {
615
784
  const isAtBot = session.parsed?.bots?.includes(session.selfId) ||
616
785
  (session.content && new RegExp(`<@${session.selfId}>|@${session.selfId}\\b`).test(session.content));
617
- if (isAtBot) shouldInject = true;
786
+ if (isAtBot) {
787
+ shouldInject = true;
788
+ injectReason = '@机器人';
789
+ }
618
790
  }
619
791
 
792
+ // 检查是否回复机器人
620
793
  if (!shouldInject && config.triggerOnReply && session.quote) {
621
794
  if (session.quote.userId === session.selfId) {
622
795
  shouldInject = true;
796
+ injectReason = '回复机器人';
623
797
  }
624
798
  }
625
799
 
626
- if (!shouldInject) return next();
800
+ if (!shouldInject) {
801
+ log.middleware('无需注入上下文');
802
+ return next();
803
+ }
804
+
805
+ log.info(`触发上下文注入: ${injectReason}`);
627
806
 
628
807
  const sessionInfo = generateSessionId(session);
629
808
  const sessionId = sessionInfo.separateId || sessionInfo.sessionId;
630
809
 
631
810
  const history = await getContext(sessionId);
632
811
 
633
- if (history.length === 0) return next();
812
+ if (history.length === 0) {
813
+ log.middleware('无历史记录可注入');
814
+ return next();
815
+ }
634
816
 
635
817
  // 构建上下文
636
818
  const contextLines = history.map(msg => {
@@ -818,16 +1000,15 @@ exports.apply = (ctx, config) => {
818
1000
  memoryCache.clear();
819
1001
  cacheTimestamps.clear();
820
1002
  sessionLocks.clear();
821
- log.success('插件卸载完成', {});
1003
+ log.success('插件卸载完成');
822
1004
  });
823
1005
 
824
- // 启动日志
825
- log.success('Group Memory 插件已加载', {
1006
+ // 最终确认日志
1007
+ log.success('Group Memory 插件已完全加载,等待消息...');
1008
+ log.info('当前配置:', {
1009
+ recordPrivateChat: config.recordPrivateChat,
1010
+ recordGroupChat: config.recordGroupChat,
826
1011
  logLevel: config.logLevel,
827
- databaseOps: config.logDatabaseOps,
828
- contextInject: config.logContextInject,
829
- apiCalls: config.logApiCalls
1012
+ maxContext: config.maxContext
830
1013
  });
831
-
832
- logger.info(`Group Memory 插件已加载,日志级别: ${config.logLevel}`);
833
1014
  };