@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,529 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Scheduler Service
|
|
3
|
+
* Manages scheduled tasks for deduplication and summary generation
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import Database from 'better-sqlite3';
|
|
7
|
+
import cron, { ScheduledTask } from 'node-cron';
|
|
8
|
+
import { generateSummaryWithLLM } from '../config/llm.js';
|
|
9
|
+
import { Summarizer } from './summarizer.js';
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* Scheduler configuration interface
|
|
13
|
+
*/
|
|
14
|
+
export interface SchedulerConfig {
|
|
15
|
+
/** Time to run deduplication task (HH:mm format, e.g., "01:00") */
|
|
16
|
+
deduplicateTime: string;
|
|
17
|
+
/** Time to run daily summary task (HH:mm format) */
|
|
18
|
+
dailyTime: string;
|
|
19
|
+
/** Time to run weekly summary task (HH:mm format) */
|
|
20
|
+
weeklyTime: string;
|
|
21
|
+
/** Time to run monthly summary task (HH:mm format) */
|
|
22
|
+
monthlyTime: string;
|
|
23
|
+
/** Whether the scheduler is enabled */
|
|
24
|
+
enabled: boolean;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
/**
|
|
28
|
+
* Default scheduler configuration
|
|
29
|
+
*/
|
|
30
|
+
export const DEFAULT_CONFIG: SchedulerConfig = {
|
|
31
|
+
deduplicateTime: '01:00',
|
|
32
|
+
dailyTime: '02:00',
|
|
33
|
+
weeklyTime: '03:00',
|
|
34
|
+
monthlyTime: '04:00',
|
|
35
|
+
enabled: true
|
|
36
|
+
};
|
|
37
|
+
|
|
38
|
+
/**
|
|
39
|
+
* Task type enum
|
|
40
|
+
*/
|
|
41
|
+
type TaskType = 'deduplicate' | 'daily' | 'weekly' | 'monthly';
|
|
42
|
+
|
|
43
|
+
/**
|
|
44
|
+
* Queued task item
|
|
45
|
+
*/
|
|
46
|
+
interface QueuedTask {
|
|
47
|
+
type: TaskType;
|
|
48
|
+
scheduledTime: Date;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
/**
|
|
52
|
+
* Scheduler Service Class
|
|
53
|
+
* Manages scheduled tasks with execution locking and task queue
|
|
54
|
+
*/
|
|
55
|
+
export class Scheduler {
|
|
56
|
+
private config: SchedulerConfig;
|
|
57
|
+
private isRunning: boolean;
|
|
58
|
+
private taskQueue: QueuedTask[];
|
|
59
|
+
private tasks: Map<TaskType, ScheduledTask>;
|
|
60
|
+
private db: Database.Database;
|
|
61
|
+
|
|
62
|
+
/**
|
|
63
|
+
* Creates a new Scheduler instance
|
|
64
|
+
* @param db - Database instance
|
|
65
|
+
* @param config - Optional configuration (uses DEFAULT_CONFIG if not provided)
|
|
66
|
+
*/
|
|
67
|
+
constructor(db: Database.Database, config?: Partial<SchedulerConfig>) {
|
|
68
|
+
// 合并环境变量配置
|
|
69
|
+
const envConfig: Partial<SchedulerConfig> = {};
|
|
70
|
+
|
|
71
|
+
if (process.env.SCHEDULER_DEDUPE_TIME) {
|
|
72
|
+
envConfig.deduplicateTime = process.env.SCHEDULER_DEDUPE_TIME;
|
|
73
|
+
}
|
|
74
|
+
if (process.env.SCHEDULER_DAILY_TIME) {
|
|
75
|
+
envConfig.dailyTime = process.env.SCHEDULER_DAILY_TIME;
|
|
76
|
+
}
|
|
77
|
+
if (process.env.SCHEDULER_WEEKLY_TIME) {
|
|
78
|
+
envConfig.weeklyTime = process.env.SCHEDULER_WEEKLY_TIME;
|
|
79
|
+
}
|
|
80
|
+
if (process.env.SCHEDULER_MONTHLY_TIME) {
|
|
81
|
+
envConfig.monthlyTime = process.env.SCHEDULER_MONTHLY_TIME;
|
|
82
|
+
}
|
|
83
|
+
if (process.env.SCHEDULER_ENABLED !== undefined) {
|
|
84
|
+
envConfig.enabled = process.env.SCHEDULER_ENABLED !== 'false';
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
this.db = db;
|
|
88
|
+
this.config = { ...DEFAULT_CONFIG, ...envConfig, ...config };
|
|
89
|
+
this.isRunning = false;
|
|
90
|
+
this.taskQueue = [];
|
|
91
|
+
this.tasks = new Map();
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
/**
|
|
95
|
+
* Starts the scheduler and all scheduled tasks
|
|
96
|
+
*/
|
|
97
|
+
start(): void {
|
|
98
|
+
if (this.isRunning) {
|
|
99
|
+
console.log('[Scheduler] Already running');
|
|
100
|
+
return;
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
if (!this.config.enabled) {
|
|
104
|
+
console.log('[Scheduler] Scheduler is disabled');
|
|
105
|
+
return;
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
console.log('[Scheduler] Starting scheduler...');
|
|
109
|
+
|
|
110
|
+
this.scheduleDeduplicate();
|
|
111
|
+
this.scheduleDailySummary();
|
|
112
|
+
this.scheduleWeeklySummary();
|
|
113
|
+
this.scheduleMonthlySummary();
|
|
114
|
+
|
|
115
|
+
this.isRunning = true;
|
|
116
|
+
console.log('[Scheduler] Scheduler started successfully');
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
/**
|
|
120
|
+
* Stops the scheduler and all scheduled tasks
|
|
121
|
+
*/
|
|
122
|
+
stop(): void {
|
|
123
|
+
if (!this.isRunning) {
|
|
124
|
+
console.log('[Scheduler] Not running');
|
|
125
|
+
return;
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
console.log('[Scheduler] Stopping scheduler...');
|
|
129
|
+
|
|
130
|
+
for (const [type, task] of this.tasks) {
|
|
131
|
+
task.stop();
|
|
132
|
+
console.log(`[Scheduler] Stopped ${type} task`);
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
this.tasks.clear();
|
|
136
|
+
this.taskQueue = [];
|
|
137
|
+
this.isRunning = false;
|
|
138
|
+
|
|
139
|
+
console.log('[Scheduler] Scheduler stopped');
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
/**
|
|
143
|
+
* Checks if the scheduler is enabled
|
|
144
|
+
* @returns true if enabled, false otherwise
|
|
145
|
+
*/
|
|
146
|
+
isEnabled(): boolean {
|
|
147
|
+
return this.config.enabled;
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
/**
|
|
151
|
+
* Checks if the scheduler is currently running
|
|
152
|
+
* @returns true if running, false otherwise
|
|
153
|
+
*/
|
|
154
|
+
getIsRunning(): boolean {
|
|
155
|
+
return this.isRunning;
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
/**
|
|
159
|
+
* Converts HH:mm time format to cron expression
|
|
160
|
+
* @param time - Time in HH:mm format
|
|
161
|
+
* @returns Cron expression
|
|
162
|
+
*/
|
|
163
|
+
private timeToCron(time: string): string {
|
|
164
|
+
const [hour, minute] = time.split(':');
|
|
165
|
+
return `${minute} ${hour} * * *`;
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
/**
|
|
169
|
+
* Schedules the deduplicate task
|
|
170
|
+
*/
|
|
171
|
+
private scheduleDeduplicate(): void {
|
|
172
|
+
const cronExpression = this.timeToCron(this.config.deduplicateTime);
|
|
173
|
+
const task = cron.schedule(cronExpression, () => {
|
|
174
|
+
this.executeWithLock('deduplicate');
|
|
175
|
+
});
|
|
176
|
+
this.tasks.set('deduplicate', task);
|
|
177
|
+
console.log(`[Scheduler] Scheduled deduplicate task at ${this.config.deduplicateTime}`);
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
/**
|
|
181
|
+
* Schedules the daily summary task
|
|
182
|
+
*/
|
|
183
|
+
private scheduleDailySummary(): void {
|
|
184
|
+
const cronExpression = this.timeToCron(this.config.dailyTime);
|
|
185
|
+
const task = cron.schedule(cronExpression, () => {
|
|
186
|
+
this.executeWithLock('daily');
|
|
187
|
+
});
|
|
188
|
+
this.tasks.set('daily', task);
|
|
189
|
+
console.log(`[Scheduler] Scheduled daily summary task at ${this.config.dailyTime}`);
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
/**
|
|
193
|
+
* Schedules the weekly summary task
|
|
194
|
+
*/
|
|
195
|
+
private scheduleWeeklySummary(): void {
|
|
196
|
+
const cronExpression = this.timeToCron(this.config.weeklyTime);
|
|
197
|
+
const task = cron.schedule(cronExpression, () => {
|
|
198
|
+
this.executeWithLock('weekly');
|
|
199
|
+
});
|
|
200
|
+
this.tasks.set('weekly', task);
|
|
201
|
+
console.log(`[Scheduler] Scheduled weekly summary task at ${this.config.weeklyTime}`);
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
/**
|
|
205
|
+
* Schedules the monthly summary task
|
|
206
|
+
*/
|
|
207
|
+
private scheduleMonthlySummary(): void {
|
|
208
|
+
const cronExpression = this.timeToCron(this.config.monthlyTime);
|
|
209
|
+
const task = cron.schedule(cronExpression, () => {
|
|
210
|
+
this.executeWithLock('monthly');
|
|
211
|
+
});
|
|
212
|
+
this.tasks.set('monthly', task);
|
|
213
|
+
console.log(`[Scheduler] Scheduled monthly summary task at ${this.config.monthlyTime}`);
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
/**
|
|
217
|
+
* Executes a task with locking mechanism and queue support
|
|
218
|
+
* @param type - Task type
|
|
219
|
+
*/
|
|
220
|
+
private executeWithLock(type: TaskType): void {
|
|
221
|
+
// Add to queue
|
|
222
|
+
const queuedTask: QueuedTask = {
|
|
223
|
+
type,
|
|
224
|
+
scheduledTime: new Date()
|
|
225
|
+
};
|
|
226
|
+
this.taskQueue.push(queuedTask);
|
|
227
|
+
console.log(`[Scheduler] Queued ${type} task (queue size: ${this.taskQueue.length})`);
|
|
228
|
+
|
|
229
|
+
// Process queue
|
|
230
|
+
// Note: Not awaiting async processQueue to avoid blocking the cron scheduler
|
|
231
|
+
this.processQueue().catch(err => {
|
|
232
|
+
console.error(`[Scheduler] Error processing queue:`, err);
|
|
233
|
+
});
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
/**
|
|
237
|
+
* Processes the task queue
|
|
238
|
+
*/
|
|
239
|
+
private async processQueue(): Promise<void> {
|
|
240
|
+
if (this.taskQueue.length === 0) {
|
|
241
|
+
return;
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
const task = this.taskQueue.shift();
|
|
245
|
+
if (!task) return;
|
|
246
|
+
|
|
247
|
+
console.log(`[Scheduler] Processing ${task.type} task...`);
|
|
248
|
+
|
|
249
|
+
switch (task.type) {
|
|
250
|
+
case 'deduplicate':
|
|
251
|
+
await this.deduplicate();
|
|
252
|
+
break;
|
|
253
|
+
case 'daily':
|
|
254
|
+
await this.dailySummary();
|
|
255
|
+
break;
|
|
256
|
+
case 'weekly':
|
|
257
|
+
await this.weeklySummary();
|
|
258
|
+
break;
|
|
259
|
+
case 'monthly':
|
|
260
|
+
await this.monthlySummary();
|
|
261
|
+
break;
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
// Continue processing if more tasks in queue
|
|
265
|
+
if (this.taskQueue.length > 0) {
|
|
266
|
+
setTimeout(() => this.processQueue(), 1000);
|
|
267
|
+
}
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
/**
|
|
271
|
+
* Executes deduplication task - finds and marks duplicate memories
|
|
272
|
+
*/
|
|
273
|
+
private async deduplicate(): Promise<void> {
|
|
274
|
+
console.log('[Scheduler] Running deduplication...');
|
|
275
|
+
|
|
276
|
+
// Get all non-archived memories that are not already marked as duplicates
|
|
277
|
+
const memories = this.db.prepare(`
|
|
278
|
+
SELECT id, content_path, importance
|
|
279
|
+
FROM memories
|
|
280
|
+
WHERE is_archived = FALSE AND is_duplicate = FALSE
|
|
281
|
+
ORDER BY created_at DESC
|
|
282
|
+
`).all() as any[];
|
|
283
|
+
|
|
284
|
+
const processed = new Set<string>();
|
|
285
|
+
|
|
286
|
+
for (const memory of memories) {
|
|
287
|
+
if (processed.has(memory.id)) continue;
|
|
288
|
+
|
|
289
|
+
// Find similar memories (via shared entities)
|
|
290
|
+
const similar = this.db.prepare(`
|
|
291
|
+
SELECT m2.id, m2.content_path, m2.importance
|
|
292
|
+
FROM memories m1
|
|
293
|
+
JOIN memory_entities me1 ON m1.id = me1.memory_id
|
|
294
|
+
JOIN memory_entities me2 ON me1.entity_id = me2.entity_id
|
|
295
|
+
JOIN memories m2 ON me2.memory_id = m2.id
|
|
296
|
+
WHERE m1.id = ? AND m2.id != m1.id
|
|
297
|
+
AND m2.is_archived = FALSE AND m2.is_duplicate = FALSE
|
|
298
|
+
`).all(memory.id) as any[];
|
|
299
|
+
|
|
300
|
+
for (const similarMem of similar) {
|
|
301
|
+
if (processed.has(similarMem.id)) continue;
|
|
302
|
+
|
|
303
|
+
// Mark as duplicate
|
|
304
|
+
this.db.prepare(`
|
|
305
|
+
UPDATE memories
|
|
306
|
+
SET is_duplicate = TRUE, duplicate_of = ?
|
|
307
|
+
WHERE id = ?
|
|
308
|
+
`).run(memory.id, similarMem.id);
|
|
309
|
+
|
|
310
|
+
// Merge importance
|
|
311
|
+
const newImportance = Math.min(1, memory.importance + similarMem.importance * 0.5);
|
|
312
|
+
this.db.prepare(`
|
|
313
|
+
UPDATE memories SET importance = ? WHERE id = ?
|
|
314
|
+
`).run(newImportance, memory.id);
|
|
315
|
+
|
|
316
|
+
processed.add(similarMem.id);
|
|
317
|
+
console.log(`[Scheduler] Marked ${similarMem.id} as duplicate of ${memory.id}`);
|
|
318
|
+
}
|
|
319
|
+
|
|
320
|
+
processed.add(memory.id);
|
|
321
|
+
}
|
|
322
|
+
|
|
323
|
+
console.log('[Scheduler] Deduplication completed');
|
|
324
|
+
}
|
|
325
|
+
|
|
326
|
+
/**
|
|
327
|
+
* Executes daily summary task - generates summary for previous day's memories
|
|
328
|
+
*/
|
|
329
|
+
private async dailySummary(): Promise<void> {
|
|
330
|
+
console.log('[Scheduler] Running daily summary...');
|
|
331
|
+
|
|
332
|
+
const yesterday = new Date();
|
|
333
|
+
yesterday.setDate(yesterday.getDate() - 1);
|
|
334
|
+
const dateStr = yesterday.toISOString().split('T')[0];
|
|
335
|
+
|
|
336
|
+
// 检查是否已有总结
|
|
337
|
+
const existing = this.db.prepare(`
|
|
338
|
+
SELECT summary FROM time_buckets WHERE date = ?
|
|
339
|
+
`).get(dateStr) as { summary?: string } | undefined;
|
|
340
|
+
|
|
341
|
+
if (existing?.summary) {
|
|
342
|
+
console.log(`[Scheduler] Daily summary for ${dateStr} already exists`);
|
|
343
|
+
return;
|
|
344
|
+
}
|
|
345
|
+
|
|
346
|
+
// 获取当天的记忆
|
|
347
|
+
const memories = this.db.prepare(`
|
|
348
|
+
SELECT id, content_path FROM memories
|
|
349
|
+
WHERE date(created_at) = date(?)
|
|
350
|
+
`).all(dateStr) as any[];
|
|
351
|
+
|
|
352
|
+
if (memories.length === 0) {
|
|
353
|
+
console.log(`[Scheduler] No memories for ${dateStr}, skipping`);
|
|
354
|
+
return;
|
|
355
|
+
}
|
|
356
|
+
|
|
357
|
+
// 读取记忆内容
|
|
358
|
+
const fs = await import('fs/promises');
|
|
359
|
+
const contents: string[] = [];
|
|
360
|
+
|
|
361
|
+
for (const mem of memories) {
|
|
362
|
+
try {
|
|
363
|
+
const content = await fs.readFile(mem.content_path, 'utf-8');
|
|
364
|
+
contents.push(content.slice(0, 1000));
|
|
365
|
+
} catch (e) {
|
|
366
|
+
console.error(`[Scheduler] Failed to read ${mem.content_path}`);
|
|
367
|
+
}
|
|
368
|
+
}
|
|
369
|
+
|
|
370
|
+
// 构建报告字符串并调用 LLM 生成总结
|
|
371
|
+
const reportString = `日期: ${dateStr}\n记忆数量: ${memories.length}\n\n记忆内容:\n${contents.join('\n---\n')}`;
|
|
372
|
+
|
|
373
|
+
try {
|
|
374
|
+
const summary = await generateSummaryWithLLM(reportString);
|
|
375
|
+
|
|
376
|
+
// 保存到 time_buckets
|
|
377
|
+
this.db.prepare(`
|
|
378
|
+
INSERT OR REPLACE INTO time_buckets (date, summary, summary_generated_at, memory_count)
|
|
379
|
+
VALUES (?, ?, datetime('now'), ?)
|
|
380
|
+
`).run(dateStr, summary, memories.length);
|
|
381
|
+
|
|
382
|
+
console.log(`[Scheduler] Daily summary generated for ${dateStr}`);
|
|
383
|
+
} catch (error) {
|
|
384
|
+
console.error('[Scheduler] Failed to generate daily summary:', error);
|
|
385
|
+
}
|
|
386
|
+
}
|
|
387
|
+
|
|
388
|
+
/**
|
|
389
|
+
* Executes weekly summary task
|
|
390
|
+
*/
|
|
391
|
+
private async weeklySummary(): Promise<void> {
|
|
392
|
+
console.log('[Scheduler] Running weekly summary...');
|
|
393
|
+
|
|
394
|
+
const now = new Date();
|
|
395
|
+
const dayOfWeek = now.getDay();
|
|
396
|
+
const diff = now.getDate() - dayOfWeek + (dayOfWeek === 0 ? -6 : 1);
|
|
397
|
+
const weekStart = new Date(now.getFullYear(), now.getMonth(), diff);
|
|
398
|
+
weekStart.setHours(0, 0, 0, 0);
|
|
399
|
+
const weekStartStr = weekStart.toISOString().split('T')[0];
|
|
400
|
+
|
|
401
|
+
// Check if summary already exists
|
|
402
|
+
const existing = this.db.prepare(`
|
|
403
|
+
SELECT summary FROM time_buckets WHERE date = ?
|
|
404
|
+
`).get(weekStartStr) as { summary?: string } | undefined;
|
|
405
|
+
|
|
406
|
+
if (existing?.summary) {
|
|
407
|
+
console.log(`[Scheduler] Weekly summary for ${weekStartStr} already exists`);
|
|
408
|
+
return;
|
|
409
|
+
}
|
|
410
|
+
|
|
411
|
+
// Get all memories for this week
|
|
412
|
+
const memories = this.db.prepare(`
|
|
413
|
+
SELECT id, content_path FROM memories
|
|
414
|
+
WHERE date(created_at) >= date(?) AND date(created_at) <= date('now')
|
|
415
|
+
`).all(weekStartStr) as any[];
|
|
416
|
+
|
|
417
|
+
if (memories.length === 0) {
|
|
418
|
+
console.log(`[Scheduler] No memories for week ${weekStartStr}, skipping`);
|
|
419
|
+
return;
|
|
420
|
+
}
|
|
421
|
+
|
|
422
|
+
// Read memory contents and generate weekly summary
|
|
423
|
+
const fs = await import('fs/promises');
|
|
424
|
+
const contents: string[] = [];
|
|
425
|
+
|
|
426
|
+
for (const mem of memories.slice(0, 10)) {
|
|
427
|
+
try {
|
|
428
|
+
const content = await fs.readFile(mem.content_path, 'utf-8');
|
|
429
|
+
contents.push(content.slice(0, 500));
|
|
430
|
+
} catch (e) {
|
|
431
|
+
// Skip files that can't be read
|
|
432
|
+
}
|
|
433
|
+
}
|
|
434
|
+
|
|
435
|
+
const summarizer = new Summarizer(this.db);
|
|
436
|
+
const report = {
|
|
437
|
+
period: 'week',
|
|
438
|
+
startDate: weekStartStr,
|
|
439
|
+
endDate: now.toISOString().split('T')[0],
|
|
440
|
+
memoryCount: memories.length,
|
|
441
|
+
memories: contents
|
|
442
|
+
};
|
|
443
|
+
|
|
444
|
+
try {
|
|
445
|
+
const summary = await summarizer.generateWeeklySummary(report);
|
|
446
|
+
this.db.prepare(`
|
|
447
|
+
INSERT OR REPLACE INTO time_buckets (date, summary, summary_generated_at, memory_count)
|
|
448
|
+
VALUES (?, ?, datetime('now'), ?)
|
|
449
|
+
`).run(weekStartStr, summary, memories.length);
|
|
450
|
+
console.log(`[Scheduler] Weekly summary generated for ${weekStartStr}`);
|
|
451
|
+
} catch (error) {
|
|
452
|
+
console.error('[Scheduler] Failed to generate weekly summary:', error);
|
|
453
|
+
}
|
|
454
|
+
}
|
|
455
|
+
|
|
456
|
+
/**
|
|
457
|
+
* Executes monthly summary task
|
|
458
|
+
*/
|
|
459
|
+
private async monthlySummary(): Promise<void> {
|
|
460
|
+
console.log('[Scheduler] Running monthly summary...');
|
|
461
|
+
|
|
462
|
+
const now = new Date();
|
|
463
|
+
const monthStart = new Date(now.getFullYear(), now.getMonth(), 1);
|
|
464
|
+
const monthStartStr = monthStart.toISOString().split('T')[0];
|
|
465
|
+
|
|
466
|
+
const existing = this.db.prepare(`
|
|
467
|
+
SELECT summary FROM time_buckets WHERE date = ?
|
|
468
|
+
`).get(monthStartStr) as { summary?: string } | undefined;
|
|
469
|
+
|
|
470
|
+
if (existing?.summary) {
|
|
471
|
+
console.log(`[Scheduler] Monthly summary for ${monthStartStr} already exists`);
|
|
472
|
+
return;
|
|
473
|
+
}
|
|
474
|
+
|
|
475
|
+
const memories = this.db.prepare(`
|
|
476
|
+
SELECT id FROM memories
|
|
477
|
+
WHERE date(created_at) >= date(?) AND date(created_at) <= date('now')
|
|
478
|
+
`).all(monthStartStr) as any[];
|
|
479
|
+
|
|
480
|
+
if (memories.length === 0) {
|
|
481
|
+
console.log(`[Scheduler] No memories for month ${monthStartStr}, skipping`);
|
|
482
|
+
return;
|
|
483
|
+
}
|
|
484
|
+
|
|
485
|
+
const summarizer = new Summarizer(this.db);
|
|
486
|
+
const report = {
|
|
487
|
+
period: 'month',
|
|
488
|
+
startDate: monthStartStr,
|
|
489
|
+
endDate: now.toISOString().split('T')[0],
|
|
490
|
+
memoryCount: memories.length
|
|
491
|
+
};
|
|
492
|
+
|
|
493
|
+
try {
|
|
494
|
+
const summary = await summarizer.generateMonthlySummary(report);
|
|
495
|
+
this.db.prepare(`
|
|
496
|
+
INSERT OR REPLACE INTO time_buckets (date, summary, summary_generated_at, memory_count)
|
|
497
|
+
VALUES (?, ?, datetime('now'), ?)
|
|
498
|
+
`).run(monthStartStr, summary, memories.length);
|
|
499
|
+
console.log(`[Scheduler] Monthly summary generated for ${monthStartStr}`);
|
|
500
|
+
} catch (error) {
|
|
501
|
+
console.error('[Scheduler] Failed to generate monthly summary:', error);
|
|
502
|
+
}
|
|
503
|
+
}
|
|
504
|
+
|
|
505
|
+
/**
|
|
506
|
+
* Updates the scheduler configuration
|
|
507
|
+
* @param config - New configuration (partial)
|
|
508
|
+
*/
|
|
509
|
+
updateConfig(config: Partial<SchedulerConfig>): void {
|
|
510
|
+
this.config = { ...this.config, ...config };
|
|
511
|
+
console.log('[Scheduler] Configuration updated');
|
|
512
|
+
}
|
|
513
|
+
|
|
514
|
+
/**
|
|
515
|
+
* Gets the current configuration
|
|
516
|
+
* @returns Current scheduler configuration
|
|
517
|
+
*/
|
|
518
|
+
getConfig(): SchedulerConfig {
|
|
519
|
+
return { ...this.config };
|
|
520
|
+
}
|
|
521
|
+
|
|
522
|
+
/**
|
|
523
|
+
* Gets the current queue size
|
|
524
|
+
* @returns Number of pending tasks in queue
|
|
525
|
+
*/
|
|
526
|
+
getQueueSize(): number {
|
|
527
|
+
return this.taskQueue.length;
|
|
528
|
+
}
|
|
529
|
+
}
|