@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.
- package/.claude/settings.local.json +68 -0
- package/README.md +323 -0
- package/dist/config/llm.d.ts +13 -0
- package/dist/config/llm.d.ts.map +1 -0
- package/dist/config/llm.js +96 -0
- package/dist/config/llm.js.map +1 -0
- package/dist/config/plugin.d.ts +15 -0
- package/dist/config/plugin.d.ts.map +1 -0
- package/dist/config/plugin.js +32 -0
- package/dist/config/plugin.js.map +1 -0
- package/dist/db/entityRepository.d.ts +21 -0
- package/dist/db/entityRepository.d.ts.map +1 -0
- package/dist/db/entityRepository.js +55 -0
- package/dist/db/entityRepository.js.map +1 -0
- package/dist/db/repository.d.ts +22 -0
- package/dist/db/repository.d.ts.map +1 -0
- package/dist/db/repository.js +77 -0
- package/dist/db/repository.js.map +1 -0
- package/dist/db/schema.d.ts +5 -0
- package/dist/db/schema.d.ts.map +1 -0
- package/dist/db/schema.js +112 -0
- package/dist/db/schema.js.map +1 -0
- package/dist/db/todoRepository.d.ts +26 -0
- package/dist/db/todoRepository.d.ts.map +1 -0
- package/dist/db/todoRepository.js +54 -0
- package/dist/db/todoRepository.js.map +1 -0
- package/dist/hooks/bootstrap.d.ts +3 -0
- package/dist/hooks/bootstrap.d.ts.map +1 -0
- package/dist/hooks/bootstrap.js +28 -0
- package/dist/hooks/bootstrap.js.map +1 -0
- package/dist/hooks/message.d.ts +18 -0
- package/dist/hooks/message.d.ts.map +1 -0
- package/dist/hooks/message.js +52 -0
- package/dist/hooks/message.js.map +1 -0
- package/dist/index.d.ts +3 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +46 -0
- package/dist/index.js.map +1 -0
- package/dist/mcp/tools.d.ts +26 -0
- package/dist/mcp/tools.d.ts.map +1 -0
- package/dist/mcp/tools.js +360 -0
- package/dist/mcp/tools.js.map +1 -0
- package/dist/plugin.d.ts +18 -0
- package/dist/plugin.d.ts.map +1 -0
- package/dist/plugin.js +62 -0
- package/dist/plugin.js.map +1 -0
- package/dist/services/entityGraphService.d.ts +87 -0
- package/dist/services/entityGraphService.d.ts.map +1 -0
- package/dist/services/entityGraphService.js +271 -0
- package/dist/services/entityGraphService.js.map +1 -0
- package/dist/services/memory.d.ts +26 -0
- package/dist/services/memory.d.ts.map +1 -0
- package/dist/services/memory.js +281 -0
- package/dist/services/memory.js.map +1 -0
- package/dist/services/memoryIndex.d.ts +34 -0
- package/dist/services/memoryIndex.d.ts.map +1 -0
- package/dist/services/memoryIndex.js +100 -0
- package/dist/services/memoryIndex.js.map +1 -0
- package/dist/services/metadataExtractor.d.ts +16 -0
- package/dist/services/metadataExtractor.d.ts.map +1 -0
- package/dist/services/metadataExtractor.js +75 -0
- package/dist/services/metadataExtractor.js.map +1 -0
- package/dist/services/retrieval.d.ts +24 -0
- package/dist/services/retrieval.d.ts.map +1 -0
- package/dist/services/retrieval.js +40 -0
- package/dist/services/retrieval.js.map +1 -0
- package/dist/services/scheduler.d.ts +122 -0
- package/dist/services/scheduler.d.ts.map +1 -0
- package/dist/services/scheduler.js +434 -0
- package/dist/services/scheduler.js.map +1 -0
- package/dist/services/summarizer.d.ts +43 -0
- package/dist/services/summarizer.d.ts.map +1 -0
- package/dist/services/summarizer.js +252 -0
- package/dist/services/summarizer.js.map +1 -0
- package/dist/services/tagService.d.ts +64 -0
- package/dist/services/tagService.d.ts.map +1 -0
- package/dist/services/tagService.js +281 -0
- package/dist/services/tagService.js.map +1 -0
- package/dist/tools/memory.d.ts +3 -0
- package/dist/tools/memory.d.ts.map +1 -0
- package/dist/tools/memory.js +114 -0
- package/dist/tools/memory.js.map +1 -0
- package/dist/types.d.ts +128 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/types.js +6 -0
- package/dist/types.js.map +1 -0
- package/docs/plans/2026-03-02-claw-memory-design.md +445 -0
- package/docs/plans/2026-03-02-incremental-summary-design.md +157 -0
- package/docs/plans/2026-03-02-incremental-summary-implementation.md +468 -0
- package/docs/plans/2026-03-02-memory-index-design.md +163 -0
- package/docs/plans/2026-03-02-memory-index-implementation.md +836 -0
- package/docs/plans/2026-03-02-mvp-implementation.md +1703 -0
- package/docs/plans/2026-03-02-testing-implementation.md +395 -0
- package/docs/plans/2026-03-02-testing-plan.md +93 -0
- package/docs/plans/2026-03-03-claw-memory-openclaw-plugin-design.md +285 -0
- package/docs/plans/2026-03-03-claw-memory-plugin-implementation.md +642 -0
- package/docs/plans/2026-03-03-entity-graph-design.md +121 -0
- package/docs/plans/2026-03-03-entity-graph-implementation.md +687 -0
- package/docs/plans/2026-03-03-llm-generic-config-design.md +43 -0
- package/docs/plans/2026-03-03-llm-generic-config-implementation.md +186 -0
- package/docs/plans/2026-03-03-memory-e2e-stress-test-design.md +110 -0
- package/docs/plans/2026-03-03-memory-e2e-stress-test-implementation.md +464 -0
- package/docs/plans/2026-03-03-minimax-llm-fix.md +156 -0
- package/docs/plans/2026-03-03-scheduler-design.md +165 -0
- package/docs/plans/2026-03-03-scheduler-implementation.md +777 -0
- package/docs/plans/2026-03-03-tags-visualization-design.md +73 -0
- package/docs/plans/2026-03-03-tags-visualization-implementation.md +539 -0
- package/openclaw.plugin.json +11 -0
- package/package.json +41 -0
- package/src/config/llm.ts +129 -0
- package/src/config/plugin.ts +47 -0
- package/src/db/entityRepository.ts +80 -0
- package/src/db/repository.ts +106 -0
- package/src/db/schema.ts +121 -0
- package/src/db/todoRepository.ts +76 -0
- package/src/hooks/bootstrap.ts +36 -0
- package/src/hooks/message.ts +84 -0
- package/src/index.ts +50 -0
- package/src/plugin.ts +85 -0
- package/src/services/entityGraphService.ts +367 -0
- package/src/services/memory.ts +338 -0
- package/src/services/memoryIndex.ts +140 -0
- package/src/services/metadataExtractor.ts +89 -0
- package/src/services/retrieval.ts +71 -0
- package/src/services/scheduler.ts +529 -0
- package/src/services/summarizer.ts +318 -0
- package/src/services/tagService.ts +335 -0
- package/src/tools/memory.ts +137 -0
- package/src/types.ts +139 -0
- package/tsconfig.json +20 -0
- 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
|
+
```
|