@yun-zero/claw-memory 0.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 (131) hide show
  1. package/.claude/settings.local.json +68 -0
  2. package/README.md +323 -0
  3. package/dist/config/llm.d.ts +13 -0
  4. package/dist/config/llm.d.ts.map +1 -0
  5. package/dist/config/llm.js +96 -0
  6. package/dist/config/llm.js.map +1 -0
  7. package/dist/config/plugin.d.ts +15 -0
  8. package/dist/config/plugin.d.ts.map +1 -0
  9. package/dist/config/plugin.js +32 -0
  10. package/dist/config/plugin.js.map +1 -0
  11. package/dist/db/entityRepository.d.ts +21 -0
  12. package/dist/db/entityRepository.d.ts.map +1 -0
  13. package/dist/db/entityRepository.js +55 -0
  14. package/dist/db/entityRepository.js.map +1 -0
  15. package/dist/db/repository.d.ts +22 -0
  16. package/dist/db/repository.d.ts.map +1 -0
  17. package/dist/db/repository.js +77 -0
  18. package/dist/db/repository.js.map +1 -0
  19. package/dist/db/schema.d.ts +5 -0
  20. package/dist/db/schema.d.ts.map +1 -0
  21. package/dist/db/schema.js +112 -0
  22. package/dist/db/schema.js.map +1 -0
  23. package/dist/db/todoRepository.d.ts +26 -0
  24. package/dist/db/todoRepository.d.ts.map +1 -0
  25. package/dist/db/todoRepository.js +54 -0
  26. package/dist/db/todoRepository.js.map +1 -0
  27. package/dist/hooks/bootstrap.d.ts +3 -0
  28. package/dist/hooks/bootstrap.d.ts.map +1 -0
  29. package/dist/hooks/bootstrap.js +28 -0
  30. package/dist/hooks/bootstrap.js.map +1 -0
  31. package/dist/hooks/message.d.ts +18 -0
  32. package/dist/hooks/message.d.ts.map +1 -0
  33. package/dist/hooks/message.js +52 -0
  34. package/dist/hooks/message.js.map +1 -0
  35. package/dist/index.d.ts +3 -0
  36. package/dist/index.d.ts.map +1 -0
  37. package/dist/index.js +46 -0
  38. package/dist/index.js.map +1 -0
  39. package/dist/mcp/tools.d.ts +26 -0
  40. package/dist/mcp/tools.d.ts.map +1 -0
  41. package/dist/mcp/tools.js +360 -0
  42. package/dist/mcp/tools.js.map +1 -0
  43. package/dist/plugin.d.ts +18 -0
  44. package/dist/plugin.d.ts.map +1 -0
  45. package/dist/plugin.js +62 -0
  46. package/dist/plugin.js.map +1 -0
  47. package/dist/services/entityGraphService.d.ts +87 -0
  48. package/dist/services/entityGraphService.d.ts.map +1 -0
  49. package/dist/services/entityGraphService.js +271 -0
  50. package/dist/services/entityGraphService.js.map +1 -0
  51. package/dist/services/memory.d.ts +26 -0
  52. package/dist/services/memory.d.ts.map +1 -0
  53. package/dist/services/memory.js +281 -0
  54. package/dist/services/memory.js.map +1 -0
  55. package/dist/services/memoryIndex.d.ts +34 -0
  56. package/dist/services/memoryIndex.d.ts.map +1 -0
  57. package/dist/services/memoryIndex.js +100 -0
  58. package/dist/services/memoryIndex.js.map +1 -0
  59. package/dist/services/metadataExtractor.d.ts +16 -0
  60. package/dist/services/metadataExtractor.d.ts.map +1 -0
  61. package/dist/services/metadataExtractor.js +75 -0
  62. package/dist/services/metadataExtractor.js.map +1 -0
  63. package/dist/services/retrieval.d.ts +24 -0
  64. package/dist/services/retrieval.d.ts.map +1 -0
  65. package/dist/services/retrieval.js +40 -0
  66. package/dist/services/retrieval.js.map +1 -0
  67. package/dist/services/scheduler.d.ts +122 -0
  68. package/dist/services/scheduler.d.ts.map +1 -0
  69. package/dist/services/scheduler.js +434 -0
  70. package/dist/services/scheduler.js.map +1 -0
  71. package/dist/services/summarizer.d.ts +43 -0
  72. package/dist/services/summarizer.d.ts.map +1 -0
  73. package/dist/services/summarizer.js +252 -0
  74. package/dist/services/summarizer.js.map +1 -0
  75. package/dist/services/tagService.d.ts +64 -0
  76. package/dist/services/tagService.d.ts.map +1 -0
  77. package/dist/services/tagService.js +281 -0
  78. package/dist/services/tagService.js.map +1 -0
  79. package/dist/tools/memory.d.ts +3 -0
  80. package/dist/tools/memory.d.ts.map +1 -0
  81. package/dist/tools/memory.js +114 -0
  82. package/dist/tools/memory.js.map +1 -0
  83. package/dist/types.d.ts +128 -0
  84. package/dist/types.d.ts.map +1 -0
  85. package/dist/types.js +6 -0
  86. package/dist/types.js.map +1 -0
  87. package/docs/plans/2026-03-02-claw-memory-design.md +445 -0
  88. package/docs/plans/2026-03-02-incremental-summary-design.md +157 -0
  89. package/docs/plans/2026-03-02-incremental-summary-implementation.md +468 -0
  90. package/docs/plans/2026-03-02-memory-index-design.md +163 -0
  91. package/docs/plans/2026-03-02-memory-index-implementation.md +836 -0
  92. package/docs/plans/2026-03-02-mvp-implementation.md +1703 -0
  93. package/docs/plans/2026-03-02-testing-implementation.md +395 -0
  94. package/docs/plans/2026-03-02-testing-plan.md +93 -0
  95. package/docs/plans/2026-03-03-claw-memory-openclaw-plugin-design.md +285 -0
  96. package/docs/plans/2026-03-03-claw-memory-plugin-implementation.md +642 -0
  97. package/docs/plans/2026-03-03-entity-graph-design.md +121 -0
  98. package/docs/plans/2026-03-03-entity-graph-implementation.md +687 -0
  99. package/docs/plans/2026-03-03-llm-generic-config-design.md +43 -0
  100. package/docs/plans/2026-03-03-llm-generic-config-implementation.md +186 -0
  101. package/docs/plans/2026-03-03-memory-e2e-stress-test-design.md +110 -0
  102. package/docs/plans/2026-03-03-memory-e2e-stress-test-implementation.md +464 -0
  103. package/docs/plans/2026-03-03-minimax-llm-fix.md +156 -0
  104. package/docs/plans/2026-03-03-scheduler-design.md +165 -0
  105. package/docs/plans/2026-03-03-scheduler-implementation.md +777 -0
  106. package/docs/plans/2026-03-03-tags-visualization-design.md +73 -0
  107. package/docs/plans/2026-03-03-tags-visualization-implementation.md +539 -0
  108. package/openclaw.plugin.json +11 -0
  109. package/package.json +41 -0
  110. package/src/config/llm.ts +129 -0
  111. package/src/config/plugin.ts +47 -0
  112. package/src/db/entityRepository.ts +80 -0
  113. package/src/db/repository.ts +106 -0
  114. package/src/db/schema.ts +121 -0
  115. package/src/db/todoRepository.ts +76 -0
  116. package/src/hooks/bootstrap.ts +36 -0
  117. package/src/hooks/message.ts +84 -0
  118. package/src/index.ts +50 -0
  119. package/src/plugin.ts +85 -0
  120. package/src/services/entityGraphService.ts +367 -0
  121. package/src/services/memory.ts +338 -0
  122. package/src/services/memoryIndex.ts +140 -0
  123. package/src/services/metadataExtractor.ts +89 -0
  124. package/src/services/retrieval.ts +71 -0
  125. package/src/services/scheduler.ts +529 -0
  126. package/src/services/summarizer.ts +318 -0
  127. package/src/services/tagService.ts +335 -0
  128. package/src/tools/memory.ts +137 -0
  129. package/src/types.ts +139 -0
  130. package/tsconfig.json +20 -0
  131. package/vitest.config.ts +16 -0
@@ -0,0 +1,777 @@
1
+ # Scheduler 定时任务系统实现计划
2
+
3
+ > **For Claude:** REQUIRED SUB-SKILL: Use superpowers:executing-plans to implement this plan task-by-task.
4
+
5
+ **Goal:** 实现定时任务系统,支持每日/每周/每月自动生成总结,并处理重复记忆的去重
6
+
7
+ **Architecture:** 使用 node-cron 库实现定时任务,内置执行锁防止并发,任务失败记录日志并继续执行下一个
8
+
9
+ **Tech Stack:** TypeScript, node-cron, SQLite
10
+
11
+ ---
12
+
13
+ ## 准备工作
14
+
15
+ ### Task 1: 创建开发分支
16
+
17
+ **Step 1: 创建并切换到新分支**
18
+
19
+ ```bash
20
+ cd /home/ubuntu/openclaw/claw-memory
21
+ git checkout -b feature/scheduler
22
+ ```
23
+
24
+ **Step 2: 验证分支**
25
+
26
+ ```bash
27
+ git branch --show-current
28
+ ```
29
+
30
+ Expected: `feature/scheduler`
31
+
32
+ ---
33
+
34
+ ## Task 2: 安装 node-cron 依赖
35
+
36
+ **Files:**
37
+ - Modify: `package.json`
38
+
39
+ **Step 1: 添加依赖**
40
+
41
+ ```bash
42
+ cd /home/ubuntu/openclaw/claw-memory
43
+ npm install node-cron
44
+ npm install -D @types/node-cron
45
+ ```
46
+
47
+ **Step 2: 验证安装**
48
+
49
+ ```bash
50
+ cat package.json | grep node-cron
51
+ ```
52
+
53
+ Expected: 看到 node-cron 版本号
54
+
55
+ **Step 3: 提交**
56
+
57
+ ```bash
58
+ git add package.json package-lock.json
59
+ git commit -m "chore: add node-cron dependency"
60
+ ```
61
+
62
+ ---
63
+
64
+ ## Task 3: 创建 Scheduler 服务类
65
+
66
+ **Files:**
67
+ - Create: `src/services/scheduler.ts`
68
+
69
+ **Step 1: 编写测试**
70
+
71
+ ```typescript
72
+ // tests/unit/scheduler.test.ts
73
+ import { describe, it, expect, beforeEach, vi } from 'vitest';
74
+
75
+ describe('Scheduler', () => {
76
+ it('should have isRunning flag initially false', () => {
77
+ // 测试执行锁初始状态
78
+ expect(true).toBe(true); // 占位
79
+ });
80
+ });
81
+ ```
82
+
83
+ **Step 2: 运行测试确认失败**
84
+
85
+ ```bash
86
+ npm test -- tests/unit/scheduler.test.ts
87
+ ```
88
+
89
+ Expected: 测试文件不存在报错
90
+
91
+ **Step 3: 创建 Scheduler 类**
92
+
93
+ ```typescript
94
+ // src/services/scheduler.ts
95
+ import cron, { ScheduledTask } from 'node-cron';
96
+ import { getDatabase } from '../db/schema.js';
97
+
98
+ export interface SchedulerConfig {
99
+ deduplicateTime?: string; // HH:mm 格式
100
+ dailyTime?: string;
101
+ weeklyTime?: string;
102
+ monthlyTime?: string;
103
+ enabled?: boolean;
104
+ }
105
+
106
+ const DEFAULT_CONFIG: SchedulerConfig = {
107
+ deduplicateTime: '01:00',
108
+ dailyTime: '02:00',
109
+ weeklyTime: '03:00',
110
+ monthlyTime: '04:00',
111
+ enabled: true
112
+ };
113
+
114
+ export class Scheduler {
115
+ private config: SchedulerConfig;
116
+ private isRunning = false;
117
+ private taskQueue: Array<() => Promise<void>> = [];
118
+ private tasks: ScheduledTask[] = [];
119
+ private db: ReturnType<typeof getDatabase>;
120
+
121
+ constructor(config?: Partial<SchedulerConfig>) {
122
+ this.config = { ...DEFAULT_CONFIG, ...config };
123
+ this.db = getDatabase();
124
+ }
125
+
126
+ start(): void {
127
+ if (!this.config.enabled) {
128
+ console.log('[Scheduler] Disabled, not starting');
129
+ return;
130
+ }
131
+
132
+ console.log('[Scheduler] Starting...');
133
+ this.scheduleDeduplicate();
134
+ this.scheduleDailySummary();
135
+ this.scheduleWeeklySummary();
136
+ this.scheduleMonthlySummary();
137
+ console.log('[Scheduler] All tasks scheduled');
138
+ }
139
+
140
+ stop(): void {
141
+ this.tasks.forEach(task => task.stop());
142
+ this.tasks = [];
143
+ console.log('[Scheduler] Stopped');
144
+ }
145
+
146
+ private timeToCron(time: string): string {
147
+ const [hour, minute] = time.split(':');
148
+ return `${minute} ${hour} * * *`;
149
+ }
150
+
151
+ private scheduleDeduplicate(): void {
152
+ const task = cron.schedule(
153
+ this.timeToCron(this.config.deduplicateTime!),
154
+ async () => {
155
+ await this.executeWithLock(() => this.deduplicate());
156
+ }
157
+ );
158
+ this.tasks.push(task);
159
+ }
160
+
161
+ private scheduleDailySummary(): void {
162
+ const task = cron.schedule(
163
+ this.timeToCron(this.config.dailyTime!),
164
+ async () => {
165
+ await this.executeWithLock(() => this.dailySummary());
166
+ }
167
+ );
168
+ this.tasks.push(task);
169
+ }
170
+
171
+ private scheduleWeeklySummary(): void {
172
+ const task = cron.schedule(
173
+ this.timeToCron(this.config.weeklyTime!),
174
+ async () => {
175
+ await this.executeWithLock(() => this.weeklySummary());
176
+ }
177
+ );
178
+ this.tasks.push(task);
179
+ }
180
+
181
+ private scheduleMonthlySummary(): void {
182
+ const task = cron.schedule(
183
+ this.timeToCron(this.config.monthlyTime!),
184
+ async () => {
185
+ await this.executeWithLock(() => this.monthlySummary());
186
+ }
187
+ );
188
+ this.tasks.push(task);
189
+ }
190
+
191
+ private async executeWithLock(task: () => Promise<void>): Promise<void> {
192
+ // 如果有任务正在执行,加入队列等待
193
+ while (this.isRunning) {
194
+ console.log('[Scheduler] Task in progress, waiting...');
195
+ await new Promise(resolve => setTimeout(resolve, 60000)); // 每分钟检查一次
196
+ }
197
+
198
+ this.isRunning = true;
199
+ try {
200
+ console.log('[Scheduler] Starting task');
201
+ await task();
202
+ console.log('[Scheduler] Task completed');
203
+ } catch (error) {
204
+ console.error('[Scheduler] Task failed:', error);
205
+ } finally {
206
+ this.isRunning = false;
207
+ this.processQueue();
208
+ }
209
+ }
210
+
211
+ private processQueue(): void {
212
+ const nextTask = this.taskQueue.shift();
213
+ if (nextTask) {
214
+ this.executeWithLock(nextTask);
215
+ }
216
+ }
217
+
218
+ private async deduplicate(): Promise<void> {
219
+ console.log('[Scheduler] Running deduplication...');
220
+ // TODO: 实现去重逻辑
221
+ }
222
+
223
+ private async dailySummary(): Promise<void> {
224
+ console.log('[Scheduler] Running daily summary...');
225
+ // TODO: 实现日总结逻辑
226
+ }
227
+
228
+ private async weeklySummary(): void {
229
+ console.log('[Scheduler] Running weekly summary...');
230
+ // TODO: 实现周总结逻辑
231
+ }
232
+
233
+ private async monthlySummary(): void {
234
+ console.log('[Scheduler] Running monthly summary...');
235
+ // TODO: 实现月总结逻辑
236
+ }
237
+
238
+ isEnabled(): boolean {
239
+ return this.config.enabled ?? true;
240
+ }
241
+ }
242
+ ```
243
+
244
+ **Step 4: 运行测试确认通过**
245
+
246
+ ```bash
247
+ npm test -- tests/unit/scheduler.test.ts 2>&1 || echo "No tests yet, continue"
248
+ ```
249
+
250
+ Expected: 通过或无测试
251
+
252
+ **Step 5: 提交**
253
+
254
+ ```bash
255
+ git add src/services/scheduler.ts
256
+ git commit -m "feat: add Scheduler service class"
257
+ ```
258
+
259
+ ---
260
+
261
+ ## Task 4: 读取现有 summarizer 服务
262
+
263
+ **Files:**
264
+ - Read: `src/services/summarizer.ts`
265
+
266
+ **Step 1: 读取 summarizer 服务**
267
+
268
+ ```bash
269
+ cat src/services/summarizer.ts
270
+ ```
271
+
272
+ 了解现有的总结生成逻辑,特别是:
273
+ - `generateDailySummary()` 方法
274
+ - `generateWeeklySummary()` 方法
275
+ - `generateMonthlySummary()` 方法
276
+ - `deduplicateMemories()` 方法
277
+
278
+ **Step 2: 记录关键方法签名**
279
+
280
+ 将方法签名记录下来供后续实现使用
281
+
282
+ ---
283
+
284
+ ## Task 5: 实现去重任务
285
+
286
+ **Files:**
287
+ - Modify: `src/services/scheduler.ts`
288
+
289
+ **Step 1: 实现 deduplicate 方法**
290
+
291
+ ```typescript
292
+ private async deduplicate(): Promise<void> {
293
+ console.log('[Scheduler] Running deduplication...');
294
+
295
+ // 获取所有未归档的记忆
296
+ const memories = this.db.prepare(`
297
+ SELECT id, content_path, importance
298
+ FROM memories
299
+ WHERE is_archived = FALSE AND is_duplicate = FALSE
300
+ ORDER BY created_at DESC
301
+ `).all() as any[];
302
+
303
+ const processed = new Set<string>();
304
+
305
+ for (const memory of memories) {
306
+ if (processed.has(memory.id)) continue;
307
+
308
+ // 查找相似的记忆
309
+ const similar = this.db.prepare(`
310
+ SELECT m2.id, m2.content_path, m2.importance
311
+ FROM memories m1
312
+ JOIN memory_entities me1 ON m1.id = me1.memory_id
313
+ JOIN memory_entities me2 ON me1.entity_id = me2.entity_id
314
+ JOIN memories m2 ON me2.memory_id = m2.id
315
+ WHERE m1.id = ? AND m2.id != m1.id
316
+ AND m2.is_archived = FALSE AND m2.is_duplicate = FALSE
317
+ `).all(memory.id) as any[];
318
+
319
+ for (const similarMem of similar) {
320
+ if (processed.has(similarMem.id)) continue;
321
+
322
+ // 标记为重复
323
+ this.db.prepare(`
324
+ UPDATE memories
325
+ SET is_duplicate = TRUE, duplicate_of = ?
326
+ WHERE id = ?
327
+ `).run(memory.id, similarMem.id);
328
+
329
+ // 合并重要性
330
+ const newImportance = Math.min(1, memory.importance + similarMem.importance * 0.5);
331
+ this.db.prepare(`
332
+ UPDATE memories SET importance = ? WHERE id = ?
333
+ `).run(newImportance, memory.id);
334
+
335
+ processed.add(similarMem.id);
336
+ console.log(`[Scheduler] Marked ${similarMem.id} as duplicate of ${memory.id}`);
337
+ }
338
+
339
+ processed.add(memory.id);
340
+ }
341
+
342
+ console.log('[Scheduler] Deduplication completed');
343
+ }
344
+ ```
345
+
346
+ **Step 2: 测试运行**
347
+
348
+ ```bash
349
+ npx ts-node -e "
350
+ import { Scheduler } from './src/services/scheduler.js';
351
+ const s = new Scheduler({ enabled: false });
352
+ console.log('Scheduler loaded successfully');
353
+ "
354
+ ```
355
+
356
+ Expected: 无报错
357
+
358
+ **Step 3: 提交**
359
+
360
+ ```bash
361
+ git add src/services/scheduler.ts
362
+ git commit -m "feat: implement deduplicate task in Scheduler"
363
+ ```
364
+
365
+ ---
366
+
367
+ ## Task 6: 实现每日总结任务
368
+
369
+ **Files:**
370
+ - Modify: `src/services/scheduler.ts`
371
+
372
+ **Step 1: 导入 summarizer**
373
+
374
+ 在文件顶部添加:
375
+ ```typescript
376
+ import { Summarizer } from './summarizer.js';
377
+ ```
378
+
379
+ **Step 2: 实现 dailySummary 方法**
380
+
381
+ ```typescript
382
+ private async dailySummary(): Promise<void> {
383
+ console.log('[Scheduler] Running daily summary...');
384
+
385
+ const yesterday = new Date();
386
+ yesterday.setDate(yesterday.getDate() - 1);
387
+ const dateStr = yesterday.toISOString().split('T')[0]; // YYYY-MM-DD
388
+
389
+ // 检查是否已有总结
390
+ const existing = this.db.prepare(`
391
+ SELECT summary FROM time_buckets WHERE date = ?
392
+ `).get(dateStr) as { summary?: string } | undefined;
393
+
394
+ if (existing?.summary) {
395
+ console.log(`[Scheduler] Daily summary for ${dateStr} already exists`);
396
+ return;
397
+ }
398
+
399
+ // 获取当天的记忆
400
+ const memories = this.db.prepare(`
401
+ SELECT id, content_path FROM memories
402
+ WHERE date(created_at) = date(?)
403
+ `).all(dateStr) as any[];
404
+
405
+ if (memories.length === 0) {
406
+ console.log(`[Scheduler] No memories for ${dateStr}, skipping`);
407
+ return;
408
+ }
409
+
410
+ // 读取记忆内容
411
+ const fs = await import('fs/promises');
412
+ const contents: string[] = [];
413
+
414
+ for (const mem of memories) {
415
+ try {
416
+ const content = await fs.readFile(mem.content_path, 'utf-8');
417
+ contents.push(content.slice(0, 1000)); // 限制长度
418
+ } catch (e) {
419
+ console.error(`[Scheduler] Failed to read ${mem.content_path}`);
420
+ }
421
+ }
422
+
423
+ // 调用 LLM 生成总结
424
+ const summarizer = new Summarizer(this.db);
425
+ const report = {
426
+ date: dateStr,
427
+ memoryCount: memories.length,
428
+ topTags: [],
429
+ topKeywords: [],
430
+ memories: contents
431
+ };
432
+
433
+ try {
434
+ const summary = await summarizer.generateDailySummary(report);
435
+
436
+ // 保存到 time_buckets
437
+ this.db.prepare(`
438
+ INSERT OR REPLACE INTO time_buckets (date, summary, summary_generated_at, memory_count)
439
+ VALUES (?, ?, datetime('now'), ?)
440
+ `).run(dateStr, summary, memories.length);
441
+
442
+ console.log(`[Scheduler] Daily summary generated for ${dateStr}`);
443
+ } catch (error) {
444
+ console.error('[Scheduler] Failed to generate daily summary:', error);
445
+ }
446
+ }
447
+ ```
448
+
449
+ **Step 3: 测试运行**
450
+
451
+ ```bash
452
+ npx ts-node -e "
453
+ import { Scheduler } from './src/services/scheduler.js';
454
+ const s = new Scheduler({ enabled: false });
455
+ console.log('Scheduler with daily summary loaded successfully');
456
+ "
457
+ ```
458
+
459
+ **Step 4: 提交**
460
+
461
+ ```bash
462
+ git add src/services/scheduler.ts
463
+ git commit -m "feat: implement daily summary task in Scheduler"
464
+ ```
465
+
466
+ ---
467
+
468
+ ## Task 7: 实现每周/每月总结任务
469
+
470
+ **Files:**
471
+ - Modify: `src/services/scheduler.ts`
472
+
473
+ **Step 1: 实现 weeklySummary 方法**
474
+
475
+ ```typescript
476
+ private async weeklySummary(): Promise<void> {
477
+ console.log('[Scheduler] Running weekly summary...');
478
+
479
+ // 获取本周第一天
480
+ const now = new Date();
481
+ const dayOfWeek = now.getDay();
482
+ const diff = now.getDate() - dayOfWeek + (dayOfWeek === 0 ? -6 : 1);
483
+ const weekStart = new Date(now.setDate(diff));
484
+ weekStart.setHours(0, 0, 0, 0);
485
+ const weekStartStr = weekStart.toISOString().split('T')[0];
486
+
487
+ // 检查是否已有总结
488
+ const existing = this.db.prepare(`
489
+ SELECT summary FROM time_buckets WHERE date = ?
490
+ `).get(weekStartStr) as { summary?: string } | undefined;
491
+
492
+ if (existing?.summary) {
493
+ console.log(`[Scheduler] Weekly summary for ${weekStartStr} already exists`);
494
+ return;
495
+ }
496
+
497
+ // 获取本周所有记忆
498
+ const memories = this.db.prepare(`
499
+ SELECT id, content_path FROM memories
500
+ WHERE date(created_at) >= date(?) AND date(created_at) <= date('now')
501
+ `).all(weekStartStr) as any[];
502
+
503
+ if (memories.length === 0) {
504
+ console.log(`[Scheduler] No memories for week ${weekStartStr}, skipping`);
505
+ return;
506
+ }
507
+
508
+ // 读取记忆内容并生成周总结
509
+ const fs = await import('fs/promises');
510
+ const contents: string[] = [];
511
+
512
+ for (const mem of memories.slice(0, 10)) { // 限制数量
513
+ try {
514
+ const content = await fs.readFile(mem.content_path, 'utf-8');
515
+ contents.push(content.slice(0, 500));
516
+ } catch (e) {
517
+ // skip
518
+ }
519
+ }
520
+
521
+ const summarizer = new Summarizer(this.db);
522
+ const report = {
523
+ period: 'week',
524
+ startDate: weekStartStr,
525
+ endDate: now.toISOString().split('T')[0],
526
+ memoryCount: memories.length,
527
+ memories: contents
528
+ };
529
+
530
+ try {
531
+ const summary = await summarizer.generateWeeklySummary(report);
532
+
533
+ this.db.prepare(`
534
+ INSERT OR REPLACE INTO time_buckets (date, summary, summary_generated_at, memory_count)
535
+ VALUES (?, ?, datetime('now'), ?)
536
+ `).run(weekStartStr, summary, memories.length);
537
+
538
+ console.log(`[Scheduler] Weekly summary generated for ${weekStartStr}`);
539
+ } catch (error) {
540
+ console.error('[Scheduler] Failed to generate weekly summary:', error);
541
+ }
542
+ }
543
+ ```
544
+
545
+ **Step 2: 实现 monthlySummary 方法**
546
+
547
+ ```typescript
548
+ private async monthlySummary(): Promise<void> {
549
+ console.log('[Scheduler] Running monthly summary...');
550
+
551
+ const now = new Date();
552
+ const monthStart = new Date(now.getFullYear(), now.getMonth(), 1);
553
+ const monthStartStr = monthStart.toISOString().split('T')[0];
554
+
555
+ // 检查是否已有总结
556
+ const existing = this.db.prepare(`
557
+ SELECT summary FROM time_buckets WHERE date = ?
558
+ `).get(monthStartStr) as { summary?: string } | undefined;
559
+
560
+ if (existing?.summary) {
561
+ console.log(`[Scheduler] Monthly summary for ${monthStartStr} already exists`);
562
+ return;
563
+ }
564
+
565
+ // 获取本月所有记忆
566
+ const memories = this.db.prepare(`
567
+ SELECT id FROM memories
568
+ WHERE date(created_at) >= date(?) AND date(created_at) <= date('now')
569
+ `).all(monthStartStr) as any[];
570
+
571
+ if (memories.length === 0) {
572
+ console.log(`[Scheduler] No memories for month ${monthStartStr}, skipping`);
573
+ return;
574
+ }
575
+
576
+ const summarizer = new Summarizer(this.db);
577
+ const report = {
578
+ period: 'month',
579
+ startDate: monthStartStr,
580
+ endDate: now.toISOString().split('T')[0],
581
+ memoryCount: memories.length
582
+ };
583
+
584
+ try {
585
+ const summary = await summarizer.generateMonthlySummary(report);
586
+
587
+ this.db.prepare(`
588
+ INSERT OR REPLACE INTO time_buckets (date, summary, summary_generated_at, memory_count)
589
+ VALUES (?, ?, datetime('now'), ?)
590
+ `).run(monthStartStr, summary, memories.length);
591
+
592
+ console.log(`[Scheduler] Monthly summary generated for ${monthStartStr}`);
593
+ } catch (error) {
594
+ console.error('[Scheduler] Failed to generate monthly summary:', error);
595
+ }
596
+ }
597
+ ```
598
+
599
+ **Step 3: 提交**
600
+
601
+ ```bash
602
+ git add src/services/scheduler.ts
603
+ git commit -m "feat: implement weekly and monthly summary tasks in Scheduler"
604
+ ```
605
+
606
+ ---
607
+
608
+ ## Task 8: 添加环境变量配置支持
609
+
610
+ **Files:**
611
+ - Modify: `src/services/scheduler.ts`
612
+
613
+ **Step 1: 修改构造函数读取环境变量**
614
+
615
+ ```typescript
616
+ constructor(config?: Partial<SchedulerConfig>) {
617
+ // 合并环境变量配置
618
+ const envConfig: SchedulerConfig = {
619
+ deduplicateTime: process.env.SCHEDULER_DEDUPE_TIME,
620
+ dailyTime: process.env.SCHEDULER_DAILY_TIME,
621
+ weeklyTime: process.env.SCHEDULER_WEEKLY_TIME,
622
+ monthlyTime: process.env.SCHEDULER_MONTHLY_TIME,
623
+ enabled: process.env.SCHEDULER_ENABLED === 'false' ? false : true
624
+ };
625
+
626
+ this.config = { ...DEFAULT_CONFIG, ...envConfig, ...config };
627
+ this.db = getDatabase();
628
+ }
629
+ ```
630
+
631
+ **Step 2: 验证编译**
632
+
633
+ ```bash
634
+ npm run build 2>&1 | head -20
635
+ ```
636
+
637
+ Expected: 无错误
638
+
639
+ **Step 3: 提交**
640
+
641
+ ```bash
642
+ git add src/services/scheduler.ts
643
+ git commit -m "feat: add environment variable config support for Scheduler"
644
+ ```
645
+
646
+ ---
647
+
648
+ ## Task 9: 集成 Scheduler 到 MCP Server
649
+
650
+ **Files:**
651
+ - Modify: `src/index.ts`
652
+
653
+ **Step 1: 导入 Scheduler**
654
+
655
+ 在文件顶部添加:
656
+ ```typescript
657
+ import { Scheduler } from './services/scheduler.js';
658
+ ```
659
+
660
+ **Step 2: 在 serve action 中初始化 Scheduler**
661
+
662
+ ```typescript
663
+ // 在 db 和 memoryService 初始化后添加
664
+ const scheduler = new Scheduler();
665
+ scheduler.start();
666
+ ```
667
+
668
+ **Step 3: 确保 Server 停止时 Scheduler 也停止**
669
+
670
+ 由于 MCP Server 使用 stdio 传输,通常是长期运行,不需要额外处理。
671
+
672
+ **Step 4: 测试编译**
673
+
674
+ ```bash
675
+ npm run build
676
+ ```
677
+
678
+ Expected: 编译成功
679
+
680
+ **Step 5: 提交**
681
+
682
+ ```bash
683
+ git add src/index.ts
684
+ git commit -m "feat: integrate Scheduler into MCP Server"
685
+ ```
686
+
687
+ ---
688
+
689
+ ## Task 10: 添加 CLI 参数支持
690
+
691
+ **Files:**
692
+ - Modify: `src/index.ts`
693
+
694
+ **Step 1: 添加 CLI 选项**
695
+
696
+ 在 serve 命令中添加:
697
+ ```typescript
698
+ .option('-s, --scheduler-disabled', 'Disable scheduler', false)
699
+ ```
700
+
701
+ **Step 2: 根据参数创建 Scheduler**
702
+
703
+ ```typescript
704
+ const scheduler = new Scheduler({
705
+ enabled: !options.schedulerDisabled
706
+ });
707
+ scheduler.start();
708
+ ```
709
+
710
+ **Step 3: 测试 CLI 参数**
711
+
712
+ ```bash
713
+ node dist/index.js serve --help
714
+ ```
715
+
716
+ Expected: 看到 --scheduler-disabled 选项
717
+
718
+ **Step 4: 提交**
719
+
720
+ ```bash
721
+ git add src/index.ts
722
+ git commit -m "feat: add CLI option to disable scheduler"
723
+ ```
724
+
725
+ ---
726
+
727
+ ## Task 11: 最终测试
728
+
729
+ **Step 1: 构建项目**
730
+
731
+ ```bash
732
+ npm run build
733
+ ```
734
+
735
+ **Step 2: 启动服务测试**
736
+
737
+ ```bash
738
+ timeout 5 node dist/index.js serve 2>&1 || true
739
+ ```
740
+
741
+ Expected: 看到 Scheduler 启动日志
742
+
743
+ **Step 3: 测试禁用 Scheduler**
744
+
745
+ ```bash
746
+ timeout 5 node dist/index.js serve --scheduler-disabled 2>&1 || true
747
+ ```
748
+
749
+ Expected: 看到 Scheduler disabled 日志
750
+
751
+ ---
752
+
753
+ ## Task 12: 合并到主分支
754
+
755
+ **Step 1: 切换到主分支**
756
+
757
+ ```bash
758
+ git checkout main
759
+ ```
760
+
761
+ **Step 2: 合并功能分支**
762
+
763
+ ```bash
764
+ git merge feature/scheduler
765
+ ```
766
+
767
+ **Step 3: 推送到远程**
768
+
769
+ ```bash
770
+ git push origin main
771
+ ```
772
+
773
+ **Step 4: 删除功能分支(可选)**
774
+
775
+ ```bash
776
+ git branch -d feature/scheduler
777
+ ```