claude-memory-layer 1.0.7 → 1.0.9
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 +10 -1
- package/.claude-memory/test.sqlite +0 -0
- package/.history/package_20260201192048.json +47 -0
- package/.history/package_20260202114053.json +49 -0
- package/HANDOFF.md +92 -0
- package/dist/cli/index.js +1711 -102
- package/dist/cli/index.js.map +4 -4
- package/dist/core/index.js +1257 -84
- package/dist/core/index.js.map +4 -4
- package/dist/hooks/post-tool-use.js +5589 -0
- package/dist/hooks/post-tool-use.js.map +7 -0
- package/dist/hooks/session-end.js +1382 -85
- package/dist/hooks/session-end.js.map +4 -4
- package/dist/hooks/session-start.js +1377 -84
- package/dist/hooks/session-start.js.map +4 -4
- package/dist/hooks/stop.js +1383 -86
- package/dist/hooks/stop.js.map +4 -4
- package/dist/hooks/user-prompt-submit.js +1412 -84
- package/dist/hooks/user-prompt-submit.js.map +4 -4
- package/dist/server/api/index.js +1576 -136
- package/dist/server/api/index.js.map +4 -4
- package/dist/server/index.js +1585 -143
- package/dist/server/index.js.map +4 -4
- package/dist/services/memory-service.js +1392 -84
- package/dist/services/memory-service.js.map +4 -4
- package/dist/ui/app.js +304 -0
- package/dist/ui/index.html +202 -715
- package/dist/ui/style.css +595 -0
- package/package.json +4 -1
- package/scripts/build.ts +5 -2
- package/src/cli/index.ts +226 -0
- package/src/core/db-wrapper.ts +8 -1
- package/src/core/event-store.ts +70 -3
- package/src/core/graduation-worker.ts +171 -0
- package/src/core/graduation.ts +15 -2
- package/src/core/index.ts +4 -0
- package/src/core/retriever.ts +21 -0
- package/src/core/sqlite-event-store.ts +849 -0
- package/src/core/sqlite-wrapper.ts +108 -0
- package/src/core/sync-worker.ts +228 -0
- package/src/core/vector-worker.ts +44 -14
- package/src/hooks/user-prompt-submit.ts +53 -4
- package/src/server/api/citations.ts +7 -3
- package/src/server/api/events.ts +7 -3
- package/src/server/api/search.ts +7 -3
- package/src/server/api/sessions.ts +7 -3
- package/src/server/api/stats.ts +159 -12
- package/src/server/index.ts +18 -9
- package/src/services/memory-service.ts +263 -46
- package/src/ui/app.js +304 -0
- package/src/ui/index.html +202 -715
- package/src/ui/style.css +595 -0
- package/test_access.js +49 -0
|
@@ -9,6 +9,8 @@ import * as fs from 'fs';
|
|
|
9
9
|
import * as crypto from 'crypto';
|
|
10
10
|
|
|
11
11
|
import { EventStore } from '../core/event-store.js';
|
|
12
|
+
import { SQLiteEventStore } from '../core/sqlite-event-store.js';
|
|
13
|
+
import { SyncWorker } from '../core/sync-worker.js';
|
|
12
14
|
import { VectorStore } from '../core/vector-store.js';
|
|
13
15
|
import { Embedder, getDefaultEmbedder } from '../core/embedder.js';
|
|
14
16
|
import { VectorWorker, createVectorWorker } from '../core/vector-worker.js';
|
|
@@ -42,10 +44,14 @@ import { WorkingSetStore, createWorkingSetStore } from '../core/working-set-stor
|
|
|
42
44
|
import { ConsolidatedStore, createConsolidatedStore } from '../core/consolidated-store.js';
|
|
43
45
|
import { ConsolidationWorker, createConsolidationWorker } from '../core/consolidation-worker.js';
|
|
44
46
|
import { ContinuityManager, createContinuityManager } from '../core/continuity-manager.js';
|
|
47
|
+
import { GraduationWorker, createGraduationWorker, GraduationRunResult } from '../core/graduation-worker.js';
|
|
45
48
|
|
|
46
49
|
export interface MemoryServiceConfig {
|
|
47
50
|
storagePath: string;
|
|
48
51
|
embeddingModel?: string;
|
|
52
|
+
readOnly?: boolean;
|
|
53
|
+
/** Enable DuckDB analytics store (default: true for server, false for hooks) */
|
|
54
|
+
analyticsEnabled?: boolean;
|
|
49
55
|
}
|
|
50
56
|
|
|
51
57
|
// ============================================================
|
|
@@ -163,13 +169,19 @@ export function getSessionProject(sessionId: string): SessionRegistryEntry | nul
|
|
|
163
169
|
}
|
|
164
170
|
|
|
165
171
|
export class MemoryService {
|
|
166
|
-
|
|
172
|
+
// Primary store: SQLite (WAL mode) - for hooks, always available
|
|
173
|
+
private readonly sqliteStore: SQLiteEventStore;
|
|
174
|
+
// Analytics store: DuckDB - for server reads (optional, synced from SQLite)
|
|
175
|
+
private readonly analyticsStore: EventStore | null;
|
|
176
|
+
private syncWorker: SyncWorker | null = null;
|
|
177
|
+
|
|
167
178
|
private readonly vectorStore: VectorStore;
|
|
168
179
|
private readonly embedder: Embedder;
|
|
169
180
|
private readonly matcher: Matcher;
|
|
170
181
|
private readonly retriever: Retriever;
|
|
171
182
|
private readonly graduation: GraduationPipeline;
|
|
172
183
|
private vectorWorker: VectorWorker | null = null;
|
|
184
|
+
private graduationWorker: GraduationWorker | null = null;
|
|
173
185
|
private initialized = false;
|
|
174
186
|
|
|
175
187
|
// Endless Mode components
|
|
@@ -187,11 +199,14 @@ export class MemoryService {
|
|
|
187
199
|
private sharedStoreConfig: SharedStoreConfig | null = null;
|
|
188
200
|
private projectHash: string | null = null;
|
|
189
201
|
|
|
202
|
+
private readonly readOnly: boolean;
|
|
203
|
+
|
|
190
204
|
constructor(config: MemoryServiceConfig & { projectHash?: string; sharedStoreConfig?: SharedStoreConfig }) {
|
|
191
205
|
const storagePath = this.expandPath(config.storagePath);
|
|
206
|
+
this.readOnly = config.readOnly ?? false;
|
|
192
207
|
|
|
193
|
-
// Ensure storage directory exists
|
|
194
|
-
if (!fs.existsSync(storagePath)) {
|
|
208
|
+
// Ensure storage directory exists (only if not read-only)
|
|
209
|
+
if (!this.readOnly && !fs.existsSync(storagePath)) {
|
|
195
210
|
fs.mkdirSync(storagePath, { recursive: true });
|
|
196
211
|
}
|
|
197
212
|
|
|
@@ -200,20 +215,52 @@ export class MemoryService {
|
|
|
200
215
|
// Default: shared store enabled
|
|
201
216
|
this.sharedStoreConfig = config.sharedStoreConfig ?? { enabled: true };
|
|
202
217
|
|
|
203
|
-
// Initialize
|
|
204
|
-
|
|
218
|
+
// Initialize PRIMARY store: SQLite (WAL mode)
|
|
219
|
+
// This is always used for writes and is the source of truth
|
|
220
|
+
this.sqliteStore = new SQLiteEventStore(
|
|
221
|
+
path.join(storagePath, 'events.sqlite'),
|
|
222
|
+
{ readonly: this.readOnly }
|
|
223
|
+
);
|
|
224
|
+
|
|
225
|
+
// Initialize ANALYTICS store: DuckDB (optional, for server reads)
|
|
226
|
+
// Hooks set analyticsEnabled=false to avoid DuckDB lock conflicts
|
|
227
|
+
const analyticsEnabled = config.analyticsEnabled ?? this.readOnly; // Default: enabled only for read-only (server)
|
|
228
|
+
|
|
229
|
+
if (!analyticsEnabled) {
|
|
230
|
+
// Hook mode: skip DuckDB entirely to avoid lock conflicts
|
|
231
|
+
this.analyticsStore = null;
|
|
232
|
+
} else if (this.readOnly) {
|
|
233
|
+
// Server mode: try to use DuckDB for analytics, will fallback to SQLite
|
|
234
|
+
try {
|
|
235
|
+
this.analyticsStore = new EventStore(
|
|
236
|
+
path.join(storagePath, 'analytics.duckdb'),
|
|
237
|
+
{ readOnly: true }
|
|
238
|
+
);
|
|
239
|
+
} catch {
|
|
240
|
+
// DuckDB not available, will use SQLite for reads
|
|
241
|
+
this.analyticsStore = null;
|
|
242
|
+
}
|
|
243
|
+
} else {
|
|
244
|
+
// Writer mode with analytics: create DuckDB for sync target
|
|
245
|
+
this.analyticsStore = new EventStore(
|
|
246
|
+
path.join(storagePath, 'analytics.duckdb'),
|
|
247
|
+
{ readOnly: false }
|
|
248
|
+
);
|
|
249
|
+
}
|
|
250
|
+
|
|
205
251
|
this.vectorStore = new VectorStore(path.join(storagePath, 'vectors'));
|
|
206
252
|
this.embedder = config.embeddingModel
|
|
207
253
|
? new Embedder(config.embeddingModel)
|
|
208
254
|
: getDefaultEmbedder();
|
|
209
255
|
this.matcher = getDefaultMatcher();
|
|
256
|
+
// Retriever uses SQLite as primary (always available)
|
|
210
257
|
this.retriever = createRetriever(
|
|
211
|
-
this.
|
|
258
|
+
this.sqliteStore as unknown as EventStore, // Interface compatible
|
|
212
259
|
this.vectorStore,
|
|
213
260
|
this.embedder,
|
|
214
261
|
this.matcher
|
|
215
262
|
);
|
|
216
|
-
this.graduation = createGraduationPipeline(this.
|
|
263
|
+
this.graduation = createGraduationPipeline(this.sqliteStore as unknown as EventStore);
|
|
217
264
|
}
|
|
218
265
|
|
|
219
266
|
/**
|
|
@@ -222,28 +269,63 @@ export class MemoryService {
|
|
|
222
269
|
async initialize(): Promise<void> {
|
|
223
270
|
if (this.initialized) return;
|
|
224
271
|
|
|
225
|
-
|
|
272
|
+
// Initialize PRIMARY store: SQLite (always)
|
|
273
|
+
await this.sqliteStore.initialize();
|
|
274
|
+
|
|
275
|
+
// Initialize analytics store if available (DuckDB)
|
|
276
|
+
if (this.analyticsStore) {
|
|
277
|
+
try {
|
|
278
|
+
await this.analyticsStore.initialize();
|
|
279
|
+
} catch (error) {
|
|
280
|
+
console.warn('[MemoryService] Analytics store (DuckDB) initialization failed, using SQLite for reads:', error);
|
|
281
|
+
// Continue without analytics - SQLite will be used for reads
|
|
282
|
+
}
|
|
283
|
+
}
|
|
284
|
+
|
|
226
285
|
await this.vectorStore.initialize();
|
|
227
286
|
await this.embedder.initialize();
|
|
228
287
|
|
|
229
|
-
//
|
|
230
|
-
this.
|
|
231
|
-
|
|
232
|
-
this.
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
288
|
+
// Skip write-related workers in read-only mode
|
|
289
|
+
if (!this.readOnly) {
|
|
290
|
+
// Start vector worker (uses SQLite as source)
|
|
291
|
+
this.vectorWorker = createVectorWorker(
|
|
292
|
+
this.sqliteStore as unknown as EventStore,
|
|
293
|
+
this.vectorStore,
|
|
294
|
+
this.embedder
|
|
295
|
+
);
|
|
296
|
+
this.vectorWorker.start();
|
|
236
297
|
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
if (savedMode === 'endless') {
|
|
240
|
-
this.endlessMode = 'endless';
|
|
241
|
-
await this.initializeEndlessMode();
|
|
242
|
-
}
|
|
298
|
+
// Connect graduation pipeline to retriever for access tracking
|
|
299
|
+
this.retriever.setGraduationPipeline(this.graduation);
|
|
243
300
|
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
301
|
+
// Start graduation worker for automatic level promotion
|
|
302
|
+
this.graduationWorker = createGraduationWorker(
|
|
303
|
+
this.sqliteStore as unknown as EventStore,
|
|
304
|
+
this.graduation
|
|
305
|
+
);
|
|
306
|
+
this.graduationWorker.start();
|
|
307
|
+
|
|
308
|
+
// Start sync worker (SQLite -> DuckDB) if analytics store is available
|
|
309
|
+
if (this.analyticsStore) {
|
|
310
|
+
this.syncWorker = new SyncWorker(
|
|
311
|
+
this.sqliteStore,
|
|
312
|
+
this.analyticsStore,
|
|
313
|
+
{ intervalMs: 30000, batchSize: 500 }
|
|
314
|
+
);
|
|
315
|
+
this.syncWorker.start();
|
|
316
|
+
}
|
|
317
|
+
|
|
318
|
+
// Load endless mode setting
|
|
319
|
+
const savedMode = await this.sqliteStore.getEndlessConfig('mode') as MemoryMode | null;
|
|
320
|
+
if (savedMode === 'endless') {
|
|
321
|
+
this.endlessMode = 'endless';
|
|
322
|
+
await this.initializeEndlessMode();
|
|
323
|
+
}
|
|
324
|
+
|
|
325
|
+
// Initialize shared store (enabled by default)
|
|
326
|
+
if (this.sharedStoreConfig?.enabled !== false) {
|
|
327
|
+
await this.initializeSharedStore();
|
|
328
|
+
}
|
|
247
329
|
}
|
|
248
330
|
|
|
249
331
|
this.initialized = true;
|
|
@@ -290,7 +372,7 @@ export class MemoryService {
|
|
|
290
372
|
async startSession(sessionId: string, projectPath?: string): Promise<void> {
|
|
291
373
|
await this.initialize();
|
|
292
374
|
|
|
293
|
-
await this.
|
|
375
|
+
await this.sqliteStore.upsertSession({
|
|
294
376
|
id: sessionId,
|
|
295
377
|
startedAt: new Date(),
|
|
296
378
|
projectPath
|
|
@@ -303,7 +385,7 @@ export class MemoryService {
|
|
|
303
385
|
async endSession(sessionId: string, summary?: string): Promise<void> {
|
|
304
386
|
await this.initialize();
|
|
305
387
|
|
|
306
|
-
await this.
|
|
388
|
+
await this.sqliteStore.upsertSession({
|
|
307
389
|
id: sessionId,
|
|
308
390
|
endedAt: new Date(),
|
|
309
391
|
summary
|
|
@@ -320,7 +402,7 @@ export class MemoryService {
|
|
|
320
402
|
): Promise<AppendResult> {
|
|
321
403
|
await this.initialize();
|
|
322
404
|
|
|
323
|
-
const result = await this.
|
|
405
|
+
const result = await this.sqliteStore.append({
|
|
324
406
|
eventType: 'user_prompt',
|
|
325
407
|
sessionId,
|
|
326
408
|
timestamp: new Date(),
|
|
@@ -330,7 +412,7 @@ export class MemoryService {
|
|
|
330
412
|
|
|
331
413
|
// Enqueue for embedding if new
|
|
332
414
|
if (result.success && !result.isDuplicate) {
|
|
333
|
-
await this.
|
|
415
|
+
await this.sqliteStore.enqueueForEmbedding(result.eventId, content);
|
|
334
416
|
}
|
|
335
417
|
|
|
336
418
|
return result;
|
|
@@ -346,7 +428,7 @@ export class MemoryService {
|
|
|
346
428
|
): Promise<AppendResult> {
|
|
347
429
|
await this.initialize();
|
|
348
430
|
|
|
349
|
-
const result = await this.
|
|
431
|
+
const result = await this.sqliteStore.append({
|
|
350
432
|
eventType: 'agent_response',
|
|
351
433
|
sessionId,
|
|
352
434
|
timestamp: new Date(),
|
|
@@ -356,7 +438,7 @@ export class MemoryService {
|
|
|
356
438
|
|
|
357
439
|
// Enqueue for embedding if new
|
|
358
440
|
if (result.success && !result.isDuplicate) {
|
|
359
|
-
await this.
|
|
441
|
+
await this.sqliteStore.enqueueForEmbedding(result.eventId, content);
|
|
360
442
|
}
|
|
361
443
|
|
|
362
444
|
return result;
|
|
@@ -371,7 +453,7 @@ export class MemoryService {
|
|
|
371
453
|
): Promise<AppendResult> {
|
|
372
454
|
await this.initialize();
|
|
373
455
|
|
|
374
|
-
const result = await this.
|
|
456
|
+
const result = await this.sqliteStore.append({
|
|
375
457
|
eventType: 'session_summary',
|
|
376
458
|
sessionId,
|
|
377
459
|
timestamp: new Date(),
|
|
@@ -379,7 +461,7 @@ export class MemoryService {
|
|
|
379
461
|
});
|
|
380
462
|
|
|
381
463
|
if (result.success && !result.isDuplicate) {
|
|
382
|
-
await this.
|
|
464
|
+
await this.sqliteStore.enqueueForEmbedding(result.eventId, summary);
|
|
383
465
|
}
|
|
384
466
|
|
|
385
467
|
return result;
|
|
@@ -397,7 +479,7 @@ export class MemoryService {
|
|
|
397
479
|
// Create content for storage (JSON stringified payload)
|
|
398
480
|
const content = JSON.stringify(payload);
|
|
399
481
|
|
|
400
|
-
const result = await this.
|
|
482
|
+
const result = await this.sqliteStore.append({
|
|
401
483
|
eventType: 'tool_observation',
|
|
402
484
|
sessionId,
|
|
403
485
|
timestamp: new Date(),
|
|
@@ -415,7 +497,7 @@ export class MemoryService {
|
|
|
415
497
|
payload.metadata || {},
|
|
416
498
|
payload.success
|
|
417
499
|
);
|
|
418
|
-
await this.
|
|
500
|
+
await this.sqliteStore.enqueueForEmbedding(result.eventId, embeddingContent);
|
|
419
501
|
}
|
|
420
502
|
|
|
421
503
|
return result;
|
|
@@ -457,7 +539,7 @@ export class MemoryService {
|
|
|
457
539
|
*/
|
|
458
540
|
async getSessionHistory(sessionId: string): Promise<MemoryEvent[]> {
|
|
459
541
|
await this.initialize();
|
|
460
|
-
return this.
|
|
542
|
+
return this.sqliteStore.getSessionEvents(sessionId);
|
|
461
543
|
}
|
|
462
544
|
|
|
463
545
|
/**
|
|
@@ -465,7 +547,7 @@ export class MemoryService {
|
|
|
465
547
|
*/
|
|
466
548
|
async getRecentEvents(limit: number = 100): Promise<MemoryEvent[]> {
|
|
467
549
|
await this.initialize();
|
|
468
|
-
return this.
|
|
550
|
+
return this.sqliteStore.getRecentEvents(limit);
|
|
469
551
|
}
|
|
470
552
|
|
|
471
553
|
/**
|
|
@@ -478,7 +560,7 @@ export class MemoryService {
|
|
|
478
560
|
}> {
|
|
479
561
|
await this.initialize();
|
|
480
562
|
|
|
481
|
-
const recentEvents = await this.
|
|
563
|
+
const recentEvents = await this.sqliteStore.getRecentEvents(10000);
|
|
482
564
|
const vectorCount = await this.vectorStore.count();
|
|
483
565
|
const levelStats = await this.graduation.getStats();
|
|
484
566
|
|
|
@@ -499,6 +581,22 @@ export class MemoryService {
|
|
|
499
581
|
return 0;
|
|
500
582
|
}
|
|
501
583
|
|
|
584
|
+
/**
|
|
585
|
+
* Get events by memory level
|
|
586
|
+
*/
|
|
587
|
+
async getEventsByLevel(level: string, options?: { limit?: number; offset?: number }): Promise<MemoryEvent[]> {
|
|
588
|
+
await this.initialize();
|
|
589
|
+
return this.sqliteStore.getEventsByLevel(level, options);
|
|
590
|
+
}
|
|
591
|
+
|
|
592
|
+
/**
|
|
593
|
+
* Get memory level for a specific event
|
|
594
|
+
*/
|
|
595
|
+
async getEventLevel(eventId: string): Promise<string | null> {
|
|
596
|
+
await this.initialize();
|
|
597
|
+
return this.sqliteStore.getEventLevel(eventId);
|
|
598
|
+
}
|
|
599
|
+
|
|
502
600
|
/**
|
|
503
601
|
* Format retrieval results as context for Claude
|
|
504
602
|
*/
|
|
@@ -609,14 +707,14 @@ export class MemoryService {
|
|
|
609
707
|
async initializeEndlessMode(): Promise<void> {
|
|
610
708
|
const config = await this.getEndlessConfig();
|
|
611
709
|
|
|
612
|
-
this.workingSetStore = createWorkingSetStore(this.
|
|
613
|
-
this.consolidatedStore = createConsolidatedStore(this.
|
|
710
|
+
this.workingSetStore = createWorkingSetStore(this.sqliteStore, config);
|
|
711
|
+
this.consolidatedStore = createConsolidatedStore(this.sqliteStore);
|
|
614
712
|
this.consolidationWorker = createConsolidationWorker(
|
|
615
713
|
this.workingSetStore,
|
|
616
714
|
this.consolidatedStore,
|
|
617
715
|
config
|
|
618
716
|
);
|
|
619
|
-
this.continuityManager = createContinuityManager(this.
|
|
717
|
+
this.continuityManager = createContinuityManager(this.sqliteStore, config);
|
|
620
718
|
|
|
621
719
|
// Start consolidation worker
|
|
622
720
|
this.consolidationWorker.start();
|
|
@@ -626,7 +724,7 @@ export class MemoryService {
|
|
|
626
724
|
* Get Endless Mode configuration
|
|
627
725
|
*/
|
|
628
726
|
async getEndlessConfig(): Promise<EndlessModeConfig> {
|
|
629
|
-
const savedConfig = await this.
|
|
727
|
+
const savedConfig = await this.sqliteStore.getEndlessConfig('config') as EndlessModeConfig | null;
|
|
630
728
|
return savedConfig || this.getDefaultEndlessConfig();
|
|
631
729
|
}
|
|
632
730
|
|
|
@@ -636,7 +734,7 @@ export class MemoryService {
|
|
|
636
734
|
async setEndlessConfig(config: Partial<EndlessModeConfig>): Promise<void> {
|
|
637
735
|
const current = await this.getEndlessConfig();
|
|
638
736
|
const merged = { ...current, ...config };
|
|
639
|
-
await this.
|
|
737
|
+
await this.sqliteStore.setEndlessConfig('config', merged);
|
|
640
738
|
}
|
|
641
739
|
|
|
642
740
|
/**
|
|
@@ -648,7 +746,7 @@ export class MemoryService {
|
|
|
648
746
|
if (mode === this.endlessMode) return;
|
|
649
747
|
|
|
650
748
|
this.endlessMode = mode;
|
|
651
|
-
await this.
|
|
749
|
+
await this.sqliteStore.setEndlessConfig('mode', mode);
|
|
652
750
|
|
|
653
751
|
if (mode === 'endless') {
|
|
654
752
|
await this.initializeEndlessMode();
|
|
@@ -713,6 +811,67 @@ export class MemoryService {
|
|
|
713
811
|
return this.consolidatedStore.getAll({ limit });
|
|
714
812
|
}
|
|
715
813
|
|
|
814
|
+
/**
|
|
815
|
+
* Increment access count for memories that were used in prompts
|
|
816
|
+
*/
|
|
817
|
+
async incrementMemoryAccess(eventIds: string[]): Promise<void> {
|
|
818
|
+
if (eventIds.length === 0) return;
|
|
819
|
+
|
|
820
|
+
// Use SQLite event store if available
|
|
821
|
+
if (this.sqliteStore) {
|
|
822
|
+
await this.sqliteStore.incrementAccessCount(eventIds);
|
|
823
|
+
} else if (this.eventStore) {
|
|
824
|
+
// Fallback to regular event store (which has a stub implementation)
|
|
825
|
+
await this.eventStore.incrementAccessCount(eventIds);
|
|
826
|
+
}
|
|
827
|
+
}
|
|
828
|
+
|
|
829
|
+
/**
|
|
830
|
+
* Get most accessed memories from events
|
|
831
|
+
*/
|
|
832
|
+
async getMostAccessedMemories(limit: number = 10): Promise<any[]> {
|
|
833
|
+
console.log('[getMostAccessedMemories] sqliteStore available:', !!this.sqliteStore);
|
|
834
|
+
|
|
835
|
+
// Try to get from SQLite event store if available
|
|
836
|
+
if (this.sqliteStore) {
|
|
837
|
+
const events = await this.sqliteStore.getMostAccessed(limit);
|
|
838
|
+
console.log('[getMostAccessedMemories] Got events from SQLite:', events.length);
|
|
839
|
+
return events.map(event => ({
|
|
840
|
+
memoryId: event.id,
|
|
841
|
+
summary: event.content.substring(0, 200) + (event.content.length > 200 ? '...' : ''),
|
|
842
|
+
topics: [], // Could extract topics from content if needed
|
|
843
|
+
accessCount: (event as any).access_count || 0,
|
|
844
|
+
lastAccessed: (event as any).last_accessed_at || null,
|
|
845
|
+
confidence: 1.0,
|
|
846
|
+
createdAt: event.timestamp
|
|
847
|
+
}));
|
|
848
|
+
}
|
|
849
|
+
|
|
850
|
+
// Fallback to consolidated store if available
|
|
851
|
+
if (this.consolidatedStore) {
|
|
852
|
+
const consolidated = await this.consolidatedStore.getMostAccessed(limit);
|
|
853
|
+
return consolidated.map(m => ({
|
|
854
|
+
memoryId: m.memoryId,
|
|
855
|
+
summary: m.summary,
|
|
856
|
+
topics: m.topics,
|
|
857
|
+
accessCount: m.accessCount,
|
|
858
|
+
lastAccessed: m.accessedAt,
|
|
859
|
+
confidence: m.confidence,
|
|
860
|
+
createdAt: m.createdAt
|
|
861
|
+
}));
|
|
862
|
+
}
|
|
863
|
+
|
|
864
|
+
return [];
|
|
865
|
+
}
|
|
866
|
+
|
|
867
|
+
/**
|
|
868
|
+
* Mark a consolidated memory as accessed
|
|
869
|
+
*/
|
|
870
|
+
async markMemoryAccessed(memoryId: string): Promise<void> {
|
|
871
|
+
if (!this.consolidatedStore) return;
|
|
872
|
+
await this.consolidatedStore.markAccessed(memoryId);
|
|
873
|
+
}
|
|
874
|
+
|
|
716
875
|
/**
|
|
717
876
|
* Calculate continuity score for current context
|
|
718
877
|
*/
|
|
@@ -822,10 +981,32 @@ export class MemoryService {
|
|
|
822
981
|
return parts.join('\n');
|
|
823
982
|
}
|
|
824
983
|
|
|
984
|
+
/**
|
|
985
|
+
* Force a graduation evaluation run
|
|
986
|
+
*/
|
|
987
|
+
async forceGraduation(): Promise<GraduationRunResult> {
|
|
988
|
+
if (!this.graduationWorker) {
|
|
989
|
+
return { evaluated: 0, graduated: 0, byLevel: {} };
|
|
990
|
+
}
|
|
991
|
+
return this.graduationWorker.forceRun();
|
|
992
|
+
}
|
|
993
|
+
|
|
994
|
+
/**
|
|
995
|
+
* Record access to a memory event (for graduation scoring)
|
|
996
|
+
*/
|
|
997
|
+
recordMemoryAccess(eventId: string, sessionId: string, confidence: number = 1.0): void {
|
|
998
|
+
this.graduation.recordAccess(eventId, sessionId, confidence);
|
|
999
|
+
}
|
|
1000
|
+
|
|
825
1001
|
/**
|
|
826
1002
|
* Shutdown service
|
|
827
1003
|
*/
|
|
828
1004
|
async shutdown(): Promise<void> {
|
|
1005
|
+
// Stop graduation worker
|
|
1006
|
+
if (this.graduationWorker) {
|
|
1007
|
+
this.graduationWorker.stop();
|
|
1008
|
+
}
|
|
1009
|
+
|
|
829
1010
|
// Stop endless mode components
|
|
830
1011
|
if (this.consolidationWorker) {
|
|
831
1012
|
this.consolidationWorker.stop();
|
|
@@ -835,12 +1016,23 @@ export class MemoryService {
|
|
|
835
1016
|
this.vectorWorker.stop();
|
|
836
1017
|
}
|
|
837
1018
|
|
|
1019
|
+
// Stop sync worker
|
|
1020
|
+
if (this.syncWorker) {
|
|
1021
|
+
this.syncWorker.stop();
|
|
1022
|
+
}
|
|
1023
|
+
|
|
838
1024
|
// Close shared store
|
|
839
1025
|
if (this.sharedEventStore) {
|
|
840
1026
|
await this.sharedEventStore.close();
|
|
841
1027
|
}
|
|
842
1028
|
|
|
843
|
-
|
|
1029
|
+
// Close primary store (SQLite)
|
|
1030
|
+
await this.sqliteStore.close();
|
|
1031
|
+
|
|
1032
|
+
// Close analytics store (DuckDB)
|
|
1033
|
+
if (this.analyticsStore) {
|
|
1034
|
+
await this.analyticsStore.close();
|
|
1035
|
+
}
|
|
844
1036
|
}
|
|
845
1037
|
|
|
846
1038
|
/**
|
|
@@ -861,23 +1053,46 @@ export class MemoryService {
|
|
|
861
1053
|
// Instance cache: Map from project hash (or '__global__') to MemoryService
|
|
862
1054
|
const serviceCache = new Map<string, MemoryService>();
|
|
863
1055
|
const GLOBAL_KEY = '__global__';
|
|
1056
|
+
const GLOBAL_READONLY_KEY = '__global_readonly__';
|
|
864
1057
|
|
|
865
1058
|
/**
|
|
866
1059
|
* Get the global memory service (backward compatibility)
|
|
867
1060
|
* Use this for operations not tied to a specific project
|
|
1061
|
+
* Note: analyticsEnabled=false and sharedStore disabled to avoid DuckDB lock conflicts
|
|
868
1062
|
*/
|
|
869
1063
|
export function getDefaultMemoryService(): MemoryService {
|
|
870
1064
|
if (!serviceCache.has(GLOBAL_KEY)) {
|
|
871
1065
|
serviceCache.set(GLOBAL_KEY, new MemoryService({
|
|
872
|
-
storagePath: '~/.claude-code/memory'
|
|
1066
|
+
storagePath: '~/.claude-code/memory',
|
|
1067
|
+
analyticsEnabled: false, // Hooks don't need DuckDB
|
|
1068
|
+
sharedStoreConfig: { enabled: false } // Shared store uses DuckDB too
|
|
873
1069
|
}));
|
|
874
1070
|
}
|
|
875
1071
|
return serviceCache.get(GLOBAL_KEY)!;
|
|
876
1072
|
}
|
|
877
1073
|
|
|
1074
|
+
/**
|
|
1075
|
+
* Get a read-only global memory service
|
|
1076
|
+
* Use this for web server/dashboard that only needs to read data
|
|
1077
|
+
* Creates a fresh connection each time to avoid blocking the main writer process
|
|
1078
|
+
* Uses SQLite (WAL mode) which supports concurrent readers
|
|
1079
|
+
*/
|
|
1080
|
+
export function getReadOnlyMemoryService(): MemoryService {
|
|
1081
|
+
// Don't cache - create fresh instance each time to avoid holding locks
|
|
1082
|
+
// The connection will be closed when the request completes
|
|
1083
|
+
// Uses SQLite which supports concurrent readers via WAL mode
|
|
1084
|
+
return new MemoryService({
|
|
1085
|
+
storagePath: '~/.claude-code/memory',
|
|
1086
|
+
readOnly: true,
|
|
1087
|
+
analyticsEnabled: false, // Use SQLite for reads (WAL supports concurrent readers)
|
|
1088
|
+
sharedStoreConfig: { enabled: false } // Skip shared store for now
|
|
1089
|
+
});
|
|
1090
|
+
}
|
|
1091
|
+
|
|
878
1092
|
/**
|
|
879
1093
|
* Get memory service for a specific project path
|
|
880
1094
|
* Creates isolated storage at ~/.claude-code/memory/projects/{hash}/
|
|
1095
|
+
* Note: analyticsEnabled=false and sharedStore disabled to avoid DuckDB lock conflicts
|
|
881
1096
|
*/
|
|
882
1097
|
export function getMemoryServiceForProject(
|
|
883
1098
|
projectPath: string,
|
|
@@ -890,7 +1105,9 @@ export function getMemoryServiceForProject(
|
|
|
890
1105
|
serviceCache.set(hash, new MemoryService({
|
|
891
1106
|
storagePath,
|
|
892
1107
|
projectHash: hash,
|
|
893
|
-
|
|
1108
|
+
// Override shared store config - hooks don't need DuckDB
|
|
1109
|
+
sharedStoreConfig: sharedStoreConfig ?? { enabled: false },
|
|
1110
|
+
analyticsEnabled: false // Hooks don't need DuckDB
|
|
894
1111
|
}));
|
|
895
1112
|
}
|
|
896
1113
|
|