claude-memory-layer 1.0.8 → 1.0.10
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 +7 -1
- package/.claude-memory/test.sqlite +0 -0
- package/.history/package_20260202114053.json +49 -0
- package/.history/package_20260202121115.json +49 -0
- package/HANDOFF.md +92 -0
- package/dist/cli/index.js +1257 -74
- package/dist/cli/index.js.map +4 -4
- package/dist/core/index.js +1111 -47
- package/dist/core/index.js.map +4 -4
- package/dist/hooks/post-tool-use.js +5693 -0
- package/dist/hooks/post-tool-use.js.map +7 -0
- package/dist/hooks/session-end.js +1224 -67
- package/dist/hooks/session-end.js.map +4 -4
- package/dist/hooks/session-start.js +1219 -66
- package/dist/hooks/session-start.js.map +4 -4
- package/dist/hooks/stop.js +1224 -67
- package/dist/hooks/stop.js.map +4 -4
- package/dist/hooks/user-prompt-submit.js +1252 -98
- package/dist/hooks/user-prompt-submit.js.map +4 -4
- package/dist/server/api/index.js +1252 -73
- package/dist/server/api/index.js.map +4 -4
- package/dist/server/index.js +1252 -73
- package/dist/server/index.js.map +4 -4
- package/dist/services/memory-service.js +1246 -68
- package/dist/services/memory-service.js.map +4 -4
- package/dist/ui/app.js +304 -0
- package/dist/ui/index.html +195 -1188
- package/dist/ui/style.css +595 -0
- package/package.json +3 -1
- package/scripts/build.ts +2 -0
- package/src/core/event-store.ts +18 -0
- package/src/core/index.ts +3 -0
- package/src/core/retriever.ts +4 -1
- package/src/core/sqlite-event-store.ts +947 -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 +40 -17
- package/src/server/api/stats.ts +37 -7
- package/src/services/memory-service.ts +239 -43
- package/src/ui/app.js +304 -0
- package/src/ui/index.html +195 -1188
- 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';
|
|
@@ -48,6 +50,10 @@ export interface MemoryServiceConfig {
|
|
|
48
50
|
storagePath: string;
|
|
49
51
|
embeddingModel?: string;
|
|
50
52
|
readOnly?: boolean;
|
|
53
|
+
/** Enable DuckDB analytics store (default: true for server, false for hooks) */
|
|
54
|
+
analyticsEnabled?: boolean;
|
|
55
|
+
/** Lightweight mode for hooks - skip heavy initialization (default: false) */
|
|
56
|
+
lightweightMode?: boolean;
|
|
51
57
|
}
|
|
52
58
|
|
|
53
59
|
// ============================================================
|
|
@@ -165,7 +171,12 @@ export function getSessionProject(sessionId: string): SessionRegistryEntry | nul
|
|
|
165
171
|
}
|
|
166
172
|
|
|
167
173
|
export class MemoryService {
|
|
168
|
-
|
|
174
|
+
// Primary store: SQLite (WAL mode) - for hooks, always available
|
|
175
|
+
private readonly sqliteStore: SQLiteEventStore;
|
|
176
|
+
// Analytics store: DuckDB - for server reads (optional, synced from SQLite)
|
|
177
|
+
private readonly analyticsStore: EventStore | null;
|
|
178
|
+
private syncWorker: SyncWorker | null = null;
|
|
179
|
+
|
|
169
180
|
private readonly vectorStore: VectorStore;
|
|
170
181
|
private readonly embedder: Embedder;
|
|
171
182
|
private readonly matcher: Matcher;
|
|
@@ -191,10 +202,12 @@ export class MemoryService {
|
|
|
191
202
|
private projectHash: string | null = null;
|
|
192
203
|
|
|
193
204
|
private readonly readOnly: boolean;
|
|
205
|
+
private readonly lightweightMode: boolean;
|
|
194
206
|
|
|
195
207
|
constructor(config: MemoryServiceConfig & { projectHash?: string; sharedStoreConfig?: SharedStoreConfig }) {
|
|
196
208
|
const storagePath = this.expandPath(config.storagePath);
|
|
197
209
|
this.readOnly = config.readOnly ?? false;
|
|
210
|
+
this.lightweightMode = config.lightweightMode ?? false;
|
|
198
211
|
|
|
199
212
|
// Ensure storage directory exists (only if not read-only)
|
|
200
213
|
if (!this.readOnly && !fs.existsSync(storagePath)) {
|
|
@@ -206,20 +219,52 @@ export class MemoryService {
|
|
|
206
219
|
// Default: shared store enabled
|
|
207
220
|
this.sharedStoreConfig = config.sharedStoreConfig ?? { enabled: true };
|
|
208
221
|
|
|
209
|
-
// Initialize
|
|
210
|
-
|
|
222
|
+
// Initialize PRIMARY store: SQLite (WAL mode)
|
|
223
|
+
// This is always used for writes and is the source of truth
|
|
224
|
+
this.sqliteStore = new SQLiteEventStore(
|
|
225
|
+
path.join(storagePath, 'events.sqlite'),
|
|
226
|
+
{ readonly: this.readOnly }
|
|
227
|
+
);
|
|
228
|
+
|
|
229
|
+
// Initialize ANALYTICS store: DuckDB (optional, for server reads)
|
|
230
|
+
// Hooks set analyticsEnabled=false to avoid DuckDB lock conflicts
|
|
231
|
+
const analyticsEnabled = config.analyticsEnabled ?? this.readOnly; // Default: enabled only for read-only (server)
|
|
232
|
+
|
|
233
|
+
if (!analyticsEnabled) {
|
|
234
|
+
// Hook mode: skip DuckDB entirely to avoid lock conflicts
|
|
235
|
+
this.analyticsStore = null;
|
|
236
|
+
} else if (this.readOnly) {
|
|
237
|
+
// Server mode: try to use DuckDB for analytics, will fallback to SQLite
|
|
238
|
+
try {
|
|
239
|
+
this.analyticsStore = new EventStore(
|
|
240
|
+
path.join(storagePath, 'analytics.duckdb'),
|
|
241
|
+
{ readOnly: true }
|
|
242
|
+
);
|
|
243
|
+
} catch {
|
|
244
|
+
// DuckDB not available, will use SQLite for reads
|
|
245
|
+
this.analyticsStore = null;
|
|
246
|
+
}
|
|
247
|
+
} else {
|
|
248
|
+
// Writer mode with analytics: create DuckDB for sync target
|
|
249
|
+
this.analyticsStore = new EventStore(
|
|
250
|
+
path.join(storagePath, 'analytics.duckdb'),
|
|
251
|
+
{ readOnly: false }
|
|
252
|
+
);
|
|
253
|
+
}
|
|
254
|
+
|
|
211
255
|
this.vectorStore = new VectorStore(path.join(storagePath, 'vectors'));
|
|
212
256
|
this.embedder = config.embeddingModel
|
|
213
257
|
? new Embedder(config.embeddingModel)
|
|
214
258
|
: getDefaultEmbedder();
|
|
215
259
|
this.matcher = getDefaultMatcher();
|
|
260
|
+
// Retriever uses SQLite as primary (always available)
|
|
216
261
|
this.retriever = createRetriever(
|
|
217
|
-
this.
|
|
262
|
+
this.sqliteStore as unknown as EventStore, // Interface compatible
|
|
218
263
|
this.vectorStore,
|
|
219
264
|
this.embedder,
|
|
220
265
|
this.matcher
|
|
221
266
|
);
|
|
222
|
-
this.graduation = createGraduationPipeline(this.
|
|
267
|
+
this.graduation = createGraduationPipeline(this.sqliteStore as unknown as EventStore);
|
|
223
268
|
}
|
|
224
269
|
|
|
225
270
|
/**
|
|
@@ -228,15 +273,34 @@ export class MemoryService {
|
|
|
228
273
|
async initialize(): Promise<void> {
|
|
229
274
|
if (this.initialized) return;
|
|
230
275
|
|
|
231
|
-
|
|
276
|
+
// Initialize PRIMARY store: SQLite (always)
|
|
277
|
+
await this.sqliteStore.initialize();
|
|
278
|
+
|
|
279
|
+
// Lightweight mode: only SQLite, no embedder/vector/workers
|
|
280
|
+
// Used for hooks that just need to store data quickly
|
|
281
|
+
if (this.lightweightMode) {
|
|
282
|
+
this.initialized = true;
|
|
283
|
+
return;
|
|
284
|
+
}
|
|
285
|
+
|
|
286
|
+
// Initialize analytics store if available (DuckDB)
|
|
287
|
+
if (this.analyticsStore) {
|
|
288
|
+
try {
|
|
289
|
+
await this.analyticsStore.initialize();
|
|
290
|
+
} catch (error) {
|
|
291
|
+
console.warn('[MemoryService] Analytics store (DuckDB) initialization failed, using SQLite for reads:', error);
|
|
292
|
+
// Continue without analytics - SQLite will be used for reads
|
|
293
|
+
}
|
|
294
|
+
}
|
|
295
|
+
|
|
232
296
|
await this.vectorStore.initialize();
|
|
233
297
|
await this.embedder.initialize();
|
|
234
298
|
|
|
235
299
|
// Skip write-related workers in read-only mode
|
|
236
300
|
if (!this.readOnly) {
|
|
237
|
-
// Start vector worker
|
|
301
|
+
// Start vector worker (uses SQLite as source)
|
|
238
302
|
this.vectorWorker = createVectorWorker(
|
|
239
|
-
this.
|
|
303
|
+
this.sqliteStore as unknown as EventStore,
|
|
240
304
|
this.vectorStore,
|
|
241
305
|
this.embedder
|
|
242
306
|
);
|
|
@@ -247,13 +311,23 @@ export class MemoryService {
|
|
|
247
311
|
|
|
248
312
|
// Start graduation worker for automatic level promotion
|
|
249
313
|
this.graduationWorker = createGraduationWorker(
|
|
250
|
-
this.
|
|
314
|
+
this.sqliteStore as unknown as EventStore,
|
|
251
315
|
this.graduation
|
|
252
316
|
);
|
|
253
317
|
this.graduationWorker.start();
|
|
254
318
|
|
|
319
|
+
// Start sync worker (SQLite -> DuckDB) if analytics store is available
|
|
320
|
+
if (this.analyticsStore) {
|
|
321
|
+
this.syncWorker = new SyncWorker(
|
|
322
|
+
this.sqliteStore,
|
|
323
|
+
this.analyticsStore,
|
|
324
|
+
{ intervalMs: 30000, batchSize: 500 }
|
|
325
|
+
);
|
|
326
|
+
this.syncWorker.start();
|
|
327
|
+
}
|
|
328
|
+
|
|
255
329
|
// Load endless mode setting
|
|
256
|
-
const savedMode = await this.
|
|
330
|
+
const savedMode = await this.sqliteStore.getEndlessConfig('mode') as MemoryMode | null;
|
|
257
331
|
if (savedMode === 'endless') {
|
|
258
332
|
this.endlessMode = 'endless';
|
|
259
333
|
await this.initializeEndlessMode();
|
|
@@ -309,7 +383,7 @@ export class MemoryService {
|
|
|
309
383
|
async startSession(sessionId: string, projectPath?: string): Promise<void> {
|
|
310
384
|
await this.initialize();
|
|
311
385
|
|
|
312
|
-
await this.
|
|
386
|
+
await this.sqliteStore.upsertSession({
|
|
313
387
|
id: sessionId,
|
|
314
388
|
startedAt: new Date(),
|
|
315
389
|
projectPath
|
|
@@ -322,7 +396,7 @@ export class MemoryService {
|
|
|
322
396
|
async endSession(sessionId: string, summary?: string): Promise<void> {
|
|
323
397
|
await this.initialize();
|
|
324
398
|
|
|
325
|
-
await this.
|
|
399
|
+
await this.sqliteStore.upsertSession({
|
|
326
400
|
id: sessionId,
|
|
327
401
|
endedAt: new Date(),
|
|
328
402
|
summary
|
|
@@ -339,7 +413,7 @@ export class MemoryService {
|
|
|
339
413
|
): Promise<AppendResult> {
|
|
340
414
|
await this.initialize();
|
|
341
415
|
|
|
342
|
-
const result = await this.
|
|
416
|
+
const result = await this.sqliteStore.append({
|
|
343
417
|
eventType: 'user_prompt',
|
|
344
418
|
sessionId,
|
|
345
419
|
timestamp: new Date(),
|
|
@@ -349,7 +423,7 @@ export class MemoryService {
|
|
|
349
423
|
|
|
350
424
|
// Enqueue for embedding if new
|
|
351
425
|
if (result.success && !result.isDuplicate) {
|
|
352
|
-
await this.
|
|
426
|
+
await this.sqliteStore.enqueueForEmbedding(result.eventId, content);
|
|
353
427
|
}
|
|
354
428
|
|
|
355
429
|
return result;
|
|
@@ -365,7 +439,7 @@ export class MemoryService {
|
|
|
365
439
|
): Promise<AppendResult> {
|
|
366
440
|
await this.initialize();
|
|
367
441
|
|
|
368
|
-
const result = await this.
|
|
442
|
+
const result = await this.sqliteStore.append({
|
|
369
443
|
eventType: 'agent_response',
|
|
370
444
|
sessionId,
|
|
371
445
|
timestamp: new Date(),
|
|
@@ -375,7 +449,7 @@ export class MemoryService {
|
|
|
375
449
|
|
|
376
450
|
// Enqueue for embedding if new
|
|
377
451
|
if (result.success && !result.isDuplicate) {
|
|
378
|
-
await this.
|
|
452
|
+
await this.sqliteStore.enqueueForEmbedding(result.eventId, content);
|
|
379
453
|
}
|
|
380
454
|
|
|
381
455
|
return result;
|
|
@@ -390,7 +464,7 @@ export class MemoryService {
|
|
|
390
464
|
): Promise<AppendResult> {
|
|
391
465
|
await this.initialize();
|
|
392
466
|
|
|
393
|
-
const result = await this.
|
|
467
|
+
const result = await this.sqliteStore.append({
|
|
394
468
|
eventType: 'session_summary',
|
|
395
469
|
sessionId,
|
|
396
470
|
timestamp: new Date(),
|
|
@@ -398,7 +472,7 @@ export class MemoryService {
|
|
|
398
472
|
});
|
|
399
473
|
|
|
400
474
|
if (result.success && !result.isDuplicate) {
|
|
401
|
-
await this.
|
|
475
|
+
await this.sqliteStore.enqueueForEmbedding(result.eventId, summary);
|
|
402
476
|
}
|
|
403
477
|
|
|
404
478
|
return result;
|
|
@@ -416,7 +490,7 @@ export class MemoryService {
|
|
|
416
490
|
// Create content for storage (JSON stringified payload)
|
|
417
491
|
const content = JSON.stringify(payload);
|
|
418
492
|
|
|
419
|
-
const result = await this.
|
|
493
|
+
const result = await this.sqliteStore.append({
|
|
420
494
|
eventType: 'tool_observation',
|
|
421
495
|
sessionId,
|
|
422
496
|
timestamp: new Date(),
|
|
@@ -434,7 +508,7 @@ export class MemoryService {
|
|
|
434
508
|
payload.metadata || {},
|
|
435
509
|
payload.success
|
|
436
510
|
);
|
|
437
|
-
await this.
|
|
511
|
+
await this.sqliteStore.enqueueForEmbedding(result.eventId, embeddingContent);
|
|
438
512
|
}
|
|
439
513
|
|
|
440
514
|
return result;
|
|
@@ -454,10 +528,8 @@ export class MemoryService {
|
|
|
454
528
|
): Promise<UnifiedRetrievalResult> {
|
|
455
529
|
await this.initialize();
|
|
456
530
|
|
|
457
|
-
//
|
|
458
|
-
|
|
459
|
-
await this.vectorWorker.processAll();
|
|
460
|
-
}
|
|
531
|
+
// Note: Pending embeddings are processed by the background worker
|
|
532
|
+
// Don't block retrieval - search with whatever vectors are available
|
|
461
533
|
|
|
462
534
|
// Use unified retrieval if shared search is requested
|
|
463
535
|
if (options?.includeShared && this.sharedStore) {
|
|
@@ -471,12 +543,44 @@ export class MemoryService {
|
|
|
471
543
|
return this.retriever.retrieve(query, options);
|
|
472
544
|
}
|
|
473
545
|
|
|
546
|
+
/**
|
|
547
|
+
* Fast keyword search using SQLite FTS5
|
|
548
|
+
* Much faster than vector search - no embedding model needed
|
|
549
|
+
*/
|
|
550
|
+
async keywordSearch(
|
|
551
|
+
query: string,
|
|
552
|
+
options?: { topK?: number; minScore?: number }
|
|
553
|
+
): Promise<Array<{event: MemoryEvent; score: number}>> {
|
|
554
|
+
await this.initialize();
|
|
555
|
+
|
|
556
|
+
const results = await this.sqliteStore.keywordSearch(query, options?.topK ?? 10);
|
|
557
|
+
|
|
558
|
+
// Normalize FTS5 rank to a score (0-1 range)
|
|
559
|
+
// FTS5 rank is negative (higher is worse), so we convert it
|
|
560
|
+
const maxRank = Math.min(...results.map(r => r.rank), -0.001);
|
|
561
|
+
const minRank = Math.max(...results.map(r => r.rank), -1000);
|
|
562
|
+
const rankRange = maxRank - minRank || 1;
|
|
563
|
+
|
|
564
|
+
return results.map(r => ({
|
|
565
|
+
event: r.event,
|
|
566
|
+
score: 1 - (r.rank - minRank) / rankRange // Normalize to 0-1
|
|
567
|
+
})).filter(r => !options?.minScore || r.score >= options.minScore);
|
|
568
|
+
}
|
|
569
|
+
|
|
570
|
+
/**
|
|
571
|
+
* Rebuild FTS index (call after database upgrade)
|
|
572
|
+
*/
|
|
573
|
+
async rebuildFtsIndex(): Promise<number> {
|
|
574
|
+
await this.initialize();
|
|
575
|
+
return this.sqliteStore.rebuildFtsIndex();
|
|
576
|
+
}
|
|
577
|
+
|
|
474
578
|
/**
|
|
475
579
|
* Get session history
|
|
476
580
|
*/
|
|
477
581
|
async getSessionHistory(sessionId: string): Promise<MemoryEvent[]> {
|
|
478
582
|
await this.initialize();
|
|
479
|
-
return this.
|
|
583
|
+
return this.sqliteStore.getSessionEvents(sessionId);
|
|
480
584
|
}
|
|
481
585
|
|
|
482
586
|
/**
|
|
@@ -484,7 +588,7 @@ export class MemoryService {
|
|
|
484
588
|
*/
|
|
485
589
|
async getRecentEvents(limit: number = 100): Promise<MemoryEvent[]> {
|
|
486
590
|
await this.initialize();
|
|
487
|
-
return this.
|
|
591
|
+
return this.sqliteStore.getRecentEvents(limit);
|
|
488
592
|
}
|
|
489
593
|
|
|
490
594
|
/**
|
|
@@ -497,7 +601,7 @@ export class MemoryService {
|
|
|
497
601
|
}> {
|
|
498
602
|
await this.initialize();
|
|
499
603
|
|
|
500
|
-
const recentEvents = await this.
|
|
604
|
+
const recentEvents = await this.sqliteStore.getRecentEvents(10000);
|
|
501
605
|
const vectorCount = await this.vectorStore.count();
|
|
502
606
|
const levelStats = await this.graduation.getStats();
|
|
503
607
|
|
|
@@ -523,7 +627,7 @@ export class MemoryService {
|
|
|
523
627
|
*/
|
|
524
628
|
async getEventsByLevel(level: string, options?: { limit?: number; offset?: number }): Promise<MemoryEvent[]> {
|
|
525
629
|
await this.initialize();
|
|
526
|
-
return this.
|
|
630
|
+
return this.sqliteStore.getEventsByLevel(level, options);
|
|
527
631
|
}
|
|
528
632
|
|
|
529
633
|
/**
|
|
@@ -531,7 +635,7 @@ export class MemoryService {
|
|
|
531
635
|
*/
|
|
532
636
|
async getEventLevel(eventId: string): Promise<string | null> {
|
|
533
637
|
await this.initialize();
|
|
534
|
-
return this.
|
|
638
|
+
return this.sqliteStore.getEventLevel(eventId);
|
|
535
639
|
}
|
|
536
640
|
|
|
537
641
|
/**
|
|
@@ -644,14 +748,14 @@ export class MemoryService {
|
|
|
644
748
|
async initializeEndlessMode(): Promise<void> {
|
|
645
749
|
const config = await this.getEndlessConfig();
|
|
646
750
|
|
|
647
|
-
this.workingSetStore = createWorkingSetStore(this.
|
|
648
|
-
this.consolidatedStore = createConsolidatedStore(this.
|
|
751
|
+
this.workingSetStore = createWorkingSetStore(this.sqliteStore, config);
|
|
752
|
+
this.consolidatedStore = createConsolidatedStore(this.sqliteStore);
|
|
649
753
|
this.consolidationWorker = createConsolidationWorker(
|
|
650
754
|
this.workingSetStore,
|
|
651
755
|
this.consolidatedStore,
|
|
652
756
|
config
|
|
653
757
|
);
|
|
654
|
-
this.continuityManager = createContinuityManager(this.
|
|
758
|
+
this.continuityManager = createContinuityManager(this.sqliteStore, config);
|
|
655
759
|
|
|
656
760
|
// Start consolidation worker
|
|
657
761
|
this.consolidationWorker.start();
|
|
@@ -661,7 +765,7 @@ export class MemoryService {
|
|
|
661
765
|
* Get Endless Mode configuration
|
|
662
766
|
*/
|
|
663
767
|
async getEndlessConfig(): Promise<EndlessModeConfig> {
|
|
664
|
-
const savedConfig = await this.
|
|
768
|
+
const savedConfig = await this.sqliteStore.getEndlessConfig('config') as EndlessModeConfig | null;
|
|
665
769
|
return savedConfig || this.getDefaultEndlessConfig();
|
|
666
770
|
}
|
|
667
771
|
|
|
@@ -671,7 +775,7 @@ export class MemoryService {
|
|
|
671
775
|
async setEndlessConfig(config: Partial<EndlessModeConfig>): Promise<void> {
|
|
672
776
|
const current = await this.getEndlessConfig();
|
|
673
777
|
const merged = { ...current, ...config };
|
|
674
|
-
await this.
|
|
778
|
+
await this.sqliteStore.setEndlessConfig('config', merged);
|
|
675
779
|
}
|
|
676
780
|
|
|
677
781
|
/**
|
|
@@ -683,7 +787,7 @@ export class MemoryService {
|
|
|
683
787
|
if (mode === this.endlessMode) return;
|
|
684
788
|
|
|
685
789
|
this.endlessMode = mode;
|
|
686
|
-
await this.
|
|
790
|
+
await this.sqliteStore.setEndlessConfig('mode', mode);
|
|
687
791
|
|
|
688
792
|
if (mode === 'endless') {
|
|
689
793
|
await this.initializeEndlessMode();
|
|
@@ -749,11 +853,56 @@ export class MemoryService {
|
|
|
749
853
|
}
|
|
750
854
|
|
|
751
855
|
/**
|
|
752
|
-
*
|
|
856
|
+
* Increment access count for memories that were used in prompts
|
|
753
857
|
*/
|
|
754
|
-
async
|
|
755
|
-
if (
|
|
756
|
-
|
|
858
|
+
async incrementMemoryAccess(eventIds: string[]): Promise<void> {
|
|
859
|
+
if (eventIds.length === 0) return;
|
|
860
|
+
|
|
861
|
+
// Use SQLite event store if available
|
|
862
|
+
if (this.sqliteStore) {
|
|
863
|
+
await this.sqliteStore.incrementAccessCount(eventIds);
|
|
864
|
+
} else if (this.eventStore) {
|
|
865
|
+
// Fallback to regular event store (which has a stub implementation)
|
|
866
|
+
await this.eventStore.incrementAccessCount(eventIds);
|
|
867
|
+
}
|
|
868
|
+
}
|
|
869
|
+
|
|
870
|
+
/**
|
|
871
|
+
* Get most accessed memories from events
|
|
872
|
+
*/
|
|
873
|
+
async getMostAccessedMemories(limit: number = 10): Promise<any[]> {
|
|
874
|
+
console.log('[getMostAccessedMemories] sqliteStore available:', !!this.sqliteStore);
|
|
875
|
+
|
|
876
|
+
// Try to get from SQLite event store if available
|
|
877
|
+
if (this.sqliteStore) {
|
|
878
|
+
const events = await this.sqliteStore.getMostAccessed(limit);
|
|
879
|
+
console.log('[getMostAccessedMemories] Got events from SQLite:', events.length);
|
|
880
|
+
return events.map(event => ({
|
|
881
|
+
memoryId: event.id,
|
|
882
|
+
summary: event.content.substring(0, 200) + (event.content.length > 200 ? '...' : ''),
|
|
883
|
+
topics: [], // Could extract topics from content if needed
|
|
884
|
+
accessCount: (event as any).access_count || 0,
|
|
885
|
+
lastAccessed: (event as any).last_accessed_at || null,
|
|
886
|
+
confidence: 1.0,
|
|
887
|
+
createdAt: event.timestamp
|
|
888
|
+
}));
|
|
889
|
+
}
|
|
890
|
+
|
|
891
|
+
// Fallback to consolidated store if available
|
|
892
|
+
if (this.consolidatedStore) {
|
|
893
|
+
const consolidated = await this.consolidatedStore.getMostAccessed(limit);
|
|
894
|
+
return consolidated.map(m => ({
|
|
895
|
+
memoryId: m.memoryId,
|
|
896
|
+
summary: m.summary,
|
|
897
|
+
topics: m.topics,
|
|
898
|
+
accessCount: m.accessCount,
|
|
899
|
+
lastAccessed: m.accessedAt,
|
|
900
|
+
confidence: m.confidence,
|
|
901
|
+
createdAt: m.createdAt
|
|
902
|
+
}));
|
|
903
|
+
}
|
|
904
|
+
|
|
905
|
+
return [];
|
|
757
906
|
}
|
|
758
907
|
|
|
759
908
|
/**
|
|
@@ -908,12 +1057,23 @@ export class MemoryService {
|
|
|
908
1057
|
this.vectorWorker.stop();
|
|
909
1058
|
}
|
|
910
1059
|
|
|
1060
|
+
// Stop sync worker
|
|
1061
|
+
if (this.syncWorker) {
|
|
1062
|
+
this.syncWorker.stop();
|
|
1063
|
+
}
|
|
1064
|
+
|
|
911
1065
|
// Close shared store
|
|
912
1066
|
if (this.sharedEventStore) {
|
|
913
1067
|
await this.sharedEventStore.close();
|
|
914
1068
|
}
|
|
915
1069
|
|
|
916
|
-
|
|
1070
|
+
// Close primary store (SQLite)
|
|
1071
|
+
await this.sqliteStore.close();
|
|
1072
|
+
|
|
1073
|
+
// Close analytics store (DuckDB)
|
|
1074
|
+
if (this.analyticsStore) {
|
|
1075
|
+
await this.analyticsStore.close();
|
|
1076
|
+
}
|
|
917
1077
|
}
|
|
918
1078
|
|
|
919
1079
|
/**
|
|
@@ -939,11 +1099,14 @@ const GLOBAL_READONLY_KEY = '__global_readonly__';
|
|
|
939
1099
|
/**
|
|
940
1100
|
* Get the global memory service (backward compatibility)
|
|
941
1101
|
* Use this for operations not tied to a specific project
|
|
1102
|
+
* Note: analyticsEnabled=false and sharedStore disabled to avoid DuckDB lock conflicts
|
|
942
1103
|
*/
|
|
943
1104
|
export function getDefaultMemoryService(): MemoryService {
|
|
944
1105
|
if (!serviceCache.has(GLOBAL_KEY)) {
|
|
945
1106
|
serviceCache.set(GLOBAL_KEY, new MemoryService({
|
|
946
|
-
storagePath: '~/.claude-code/memory'
|
|
1107
|
+
storagePath: '~/.claude-code/memory',
|
|
1108
|
+
analyticsEnabled: false, // Hooks don't need DuckDB
|
|
1109
|
+
sharedStoreConfig: { enabled: false } // Shared store uses DuckDB too
|
|
947
1110
|
}));
|
|
948
1111
|
}
|
|
949
1112
|
return serviceCache.get(GLOBAL_KEY)!;
|
|
@@ -953,19 +1116,24 @@ export function getDefaultMemoryService(): MemoryService {
|
|
|
953
1116
|
* Get a read-only global memory service
|
|
954
1117
|
* Use this for web server/dashboard that only needs to read data
|
|
955
1118
|
* Creates a fresh connection each time to avoid blocking the main writer process
|
|
1119
|
+
* Uses SQLite (WAL mode) which supports concurrent readers
|
|
956
1120
|
*/
|
|
957
1121
|
export function getReadOnlyMemoryService(): MemoryService {
|
|
958
1122
|
// Don't cache - create fresh instance each time to avoid holding locks
|
|
959
1123
|
// The connection will be closed when the request completes
|
|
1124
|
+
// Uses SQLite which supports concurrent readers via WAL mode
|
|
960
1125
|
return new MemoryService({
|
|
961
1126
|
storagePath: '~/.claude-code/memory',
|
|
962
|
-
readOnly: true
|
|
1127
|
+
readOnly: true,
|
|
1128
|
+
analyticsEnabled: false, // Use SQLite for reads (WAL supports concurrent readers)
|
|
1129
|
+
sharedStoreConfig: { enabled: false } // Skip shared store for now
|
|
963
1130
|
});
|
|
964
1131
|
}
|
|
965
1132
|
|
|
966
1133
|
/**
|
|
967
1134
|
* Get memory service for a specific project path
|
|
968
1135
|
* Creates isolated storage at ~/.claude-code/memory/projects/{hash}/
|
|
1136
|
+
* Note: analyticsEnabled=false and sharedStore disabled to avoid DuckDB lock conflicts
|
|
969
1137
|
*/
|
|
970
1138
|
export function getMemoryServiceForProject(
|
|
971
1139
|
projectPath: string,
|
|
@@ -978,7 +1146,9 @@ export function getMemoryServiceForProject(
|
|
|
978
1146
|
serviceCache.set(hash, new MemoryService({
|
|
979
1147
|
storagePath,
|
|
980
1148
|
projectHash: hash,
|
|
981
|
-
|
|
1149
|
+
// Override shared store config - hooks don't need DuckDB
|
|
1150
|
+
sharedStoreConfig: sharedStoreConfig ?? { enabled: false },
|
|
1151
|
+
analyticsEnabled: false // Hooks don't need DuckDB
|
|
982
1152
|
}));
|
|
983
1153
|
}
|
|
984
1154
|
|
|
@@ -1000,6 +1170,32 @@ export function getMemoryServiceForSession(sessionId: string): MemoryService {
|
|
|
1000
1170
|
return getDefaultMemoryService();
|
|
1001
1171
|
}
|
|
1002
1172
|
|
|
1173
|
+
/**
|
|
1174
|
+
* Get a lightweight memory service for hooks
|
|
1175
|
+
* Only initializes SQLite - no embedder, no vector store, no workers
|
|
1176
|
+
* This is FAST (<100ms) compared to full initialization (3-5s)
|
|
1177
|
+
*/
|
|
1178
|
+
export function getLightweightMemoryService(sessionId: string): MemoryService {
|
|
1179
|
+
const projectInfo = getSessionProject(sessionId);
|
|
1180
|
+
const key = projectInfo ? `lightweight_${projectInfo.projectHash}` : 'lightweight_global';
|
|
1181
|
+
|
|
1182
|
+
if (!serviceCache.has(key)) {
|
|
1183
|
+
const storagePath = projectInfo
|
|
1184
|
+
? getProjectStoragePath(projectInfo.projectPath)
|
|
1185
|
+
: path.join(os.homedir(), '.claude-code', 'memory');
|
|
1186
|
+
|
|
1187
|
+
serviceCache.set(key, new MemoryService({
|
|
1188
|
+
storagePath,
|
|
1189
|
+
projectHash: projectInfo?.projectHash,
|
|
1190
|
+
lightweightMode: true, // Skip embedder/vector/workers
|
|
1191
|
+
analyticsEnabled: false,
|
|
1192
|
+
sharedStoreConfig: { enabled: false }
|
|
1193
|
+
}));
|
|
1194
|
+
}
|
|
1195
|
+
|
|
1196
|
+
return serviceCache.get(key)!;
|
|
1197
|
+
}
|
|
1198
|
+
|
|
1003
1199
|
export function createMemoryService(config: MemoryServiceConfig): MemoryService {
|
|
1004
1200
|
return new MemoryService(config);
|
|
1005
1201
|
}
|