claude-memory-layer 1.0.8 → 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 +7 -1
- package/.claude-memory/test.sqlite +0 -0
- package/.history/package_20260202114053.json +49 -0
- package/HANDOFF.md +92 -0
- package/dist/cli/index.js +1150 -71
- package/dist/cli/index.js.map +4 -4
- package/dist/core/index.js +1033 -47
- 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 +1117 -64
- package/dist/hooks/session-end.js.map +4 -4
- package/dist/hooks/session-start.js +1112 -63
- package/dist/hooks/session-start.js.map +4 -4
- package/dist/hooks/stop.js +1117 -64
- package/dist/hooks/stop.js.map +4 -4
- package/dist/hooks/user-prompt-submit.js +1151 -67
- package/dist/hooks/user-prompt-submit.js.map +4 -4
- package/dist/server/api/index.js +1145 -70
- package/dist/server/api/index.js.map +4 -4
- package/dist/server/index.js +1145 -70
- package/dist/server/index.js.map +4 -4
- package/dist/services/memory-service.js +1122 -65
- 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 +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/stats.ts +37 -7
- package/src/services/memory-service.ts +168 -39
- 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,8 @@ 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;
|
|
51
55
|
}
|
|
52
56
|
|
|
53
57
|
// ============================================================
|
|
@@ -165,7 +169,12 @@ export function getSessionProject(sessionId: string): SessionRegistryEntry | nul
|
|
|
165
169
|
}
|
|
166
170
|
|
|
167
171
|
export class MemoryService {
|
|
168
|
-
|
|
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
|
+
|
|
169
178
|
private readonly vectorStore: VectorStore;
|
|
170
179
|
private readonly embedder: Embedder;
|
|
171
180
|
private readonly matcher: Matcher;
|
|
@@ -206,20 +215,52 @@ export class MemoryService {
|
|
|
206
215
|
// Default: shared store enabled
|
|
207
216
|
this.sharedStoreConfig = config.sharedStoreConfig ?? { enabled: true };
|
|
208
217
|
|
|
209
|
-
// Initialize
|
|
210
|
-
|
|
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
|
+
|
|
211
251
|
this.vectorStore = new VectorStore(path.join(storagePath, 'vectors'));
|
|
212
252
|
this.embedder = config.embeddingModel
|
|
213
253
|
? new Embedder(config.embeddingModel)
|
|
214
254
|
: getDefaultEmbedder();
|
|
215
255
|
this.matcher = getDefaultMatcher();
|
|
256
|
+
// Retriever uses SQLite as primary (always available)
|
|
216
257
|
this.retriever = createRetriever(
|
|
217
|
-
this.
|
|
258
|
+
this.sqliteStore as unknown as EventStore, // Interface compatible
|
|
218
259
|
this.vectorStore,
|
|
219
260
|
this.embedder,
|
|
220
261
|
this.matcher
|
|
221
262
|
);
|
|
222
|
-
this.graduation = createGraduationPipeline(this.
|
|
263
|
+
this.graduation = createGraduationPipeline(this.sqliteStore as unknown as EventStore);
|
|
223
264
|
}
|
|
224
265
|
|
|
225
266
|
/**
|
|
@@ -228,15 +269,27 @@ export class MemoryService {
|
|
|
228
269
|
async initialize(): Promise<void> {
|
|
229
270
|
if (this.initialized) return;
|
|
230
271
|
|
|
231
|
-
|
|
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
|
+
|
|
232
285
|
await this.vectorStore.initialize();
|
|
233
286
|
await this.embedder.initialize();
|
|
234
287
|
|
|
235
288
|
// Skip write-related workers in read-only mode
|
|
236
289
|
if (!this.readOnly) {
|
|
237
|
-
// Start vector worker
|
|
290
|
+
// Start vector worker (uses SQLite as source)
|
|
238
291
|
this.vectorWorker = createVectorWorker(
|
|
239
|
-
this.
|
|
292
|
+
this.sqliteStore as unknown as EventStore,
|
|
240
293
|
this.vectorStore,
|
|
241
294
|
this.embedder
|
|
242
295
|
);
|
|
@@ -247,13 +300,23 @@ export class MemoryService {
|
|
|
247
300
|
|
|
248
301
|
// Start graduation worker for automatic level promotion
|
|
249
302
|
this.graduationWorker = createGraduationWorker(
|
|
250
|
-
this.
|
|
303
|
+
this.sqliteStore as unknown as EventStore,
|
|
251
304
|
this.graduation
|
|
252
305
|
);
|
|
253
306
|
this.graduationWorker.start();
|
|
254
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
|
+
|
|
255
318
|
// Load endless mode setting
|
|
256
|
-
const savedMode = await this.
|
|
319
|
+
const savedMode = await this.sqliteStore.getEndlessConfig('mode') as MemoryMode | null;
|
|
257
320
|
if (savedMode === 'endless') {
|
|
258
321
|
this.endlessMode = 'endless';
|
|
259
322
|
await this.initializeEndlessMode();
|
|
@@ -309,7 +372,7 @@ export class MemoryService {
|
|
|
309
372
|
async startSession(sessionId: string, projectPath?: string): Promise<void> {
|
|
310
373
|
await this.initialize();
|
|
311
374
|
|
|
312
|
-
await this.
|
|
375
|
+
await this.sqliteStore.upsertSession({
|
|
313
376
|
id: sessionId,
|
|
314
377
|
startedAt: new Date(),
|
|
315
378
|
projectPath
|
|
@@ -322,7 +385,7 @@ export class MemoryService {
|
|
|
322
385
|
async endSession(sessionId: string, summary?: string): Promise<void> {
|
|
323
386
|
await this.initialize();
|
|
324
387
|
|
|
325
|
-
await this.
|
|
388
|
+
await this.sqliteStore.upsertSession({
|
|
326
389
|
id: sessionId,
|
|
327
390
|
endedAt: new Date(),
|
|
328
391
|
summary
|
|
@@ -339,7 +402,7 @@ export class MemoryService {
|
|
|
339
402
|
): Promise<AppendResult> {
|
|
340
403
|
await this.initialize();
|
|
341
404
|
|
|
342
|
-
const result = await this.
|
|
405
|
+
const result = await this.sqliteStore.append({
|
|
343
406
|
eventType: 'user_prompt',
|
|
344
407
|
sessionId,
|
|
345
408
|
timestamp: new Date(),
|
|
@@ -349,7 +412,7 @@ export class MemoryService {
|
|
|
349
412
|
|
|
350
413
|
// Enqueue for embedding if new
|
|
351
414
|
if (result.success && !result.isDuplicate) {
|
|
352
|
-
await this.
|
|
415
|
+
await this.sqliteStore.enqueueForEmbedding(result.eventId, content);
|
|
353
416
|
}
|
|
354
417
|
|
|
355
418
|
return result;
|
|
@@ -365,7 +428,7 @@ export class MemoryService {
|
|
|
365
428
|
): Promise<AppendResult> {
|
|
366
429
|
await this.initialize();
|
|
367
430
|
|
|
368
|
-
const result = await this.
|
|
431
|
+
const result = await this.sqliteStore.append({
|
|
369
432
|
eventType: 'agent_response',
|
|
370
433
|
sessionId,
|
|
371
434
|
timestamp: new Date(),
|
|
@@ -375,7 +438,7 @@ export class MemoryService {
|
|
|
375
438
|
|
|
376
439
|
// Enqueue for embedding if new
|
|
377
440
|
if (result.success && !result.isDuplicate) {
|
|
378
|
-
await this.
|
|
441
|
+
await this.sqliteStore.enqueueForEmbedding(result.eventId, content);
|
|
379
442
|
}
|
|
380
443
|
|
|
381
444
|
return result;
|
|
@@ -390,7 +453,7 @@ export class MemoryService {
|
|
|
390
453
|
): Promise<AppendResult> {
|
|
391
454
|
await this.initialize();
|
|
392
455
|
|
|
393
|
-
const result = await this.
|
|
456
|
+
const result = await this.sqliteStore.append({
|
|
394
457
|
eventType: 'session_summary',
|
|
395
458
|
sessionId,
|
|
396
459
|
timestamp: new Date(),
|
|
@@ -398,7 +461,7 @@ export class MemoryService {
|
|
|
398
461
|
});
|
|
399
462
|
|
|
400
463
|
if (result.success && !result.isDuplicate) {
|
|
401
|
-
await this.
|
|
464
|
+
await this.sqliteStore.enqueueForEmbedding(result.eventId, summary);
|
|
402
465
|
}
|
|
403
466
|
|
|
404
467
|
return result;
|
|
@@ -416,7 +479,7 @@ export class MemoryService {
|
|
|
416
479
|
// Create content for storage (JSON stringified payload)
|
|
417
480
|
const content = JSON.stringify(payload);
|
|
418
481
|
|
|
419
|
-
const result = await this.
|
|
482
|
+
const result = await this.sqliteStore.append({
|
|
420
483
|
eventType: 'tool_observation',
|
|
421
484
|
sessionId,
|
|
422
485
|
timestamp: new Date(),
|
|
@@ -434,7 +497,7 @@ export class MemoryService {
|
|
|
434
497
|
payload.metadata || {},
|
|
435
498
|
payload.success
|
|
436
499
|
);
|
|
437
|
-
await this.
|
|
500
|
+
await this.sqliteStore.enqueueForEmbedding(result.eventId, embeddingContent);
|
|
438
501
|
}
|
|
439
502
|
|
|
440
503
|
return result;
|
|
@@ -476,7 +539,7 @@ export class MemoryService {
|
|
|
476
539
|
*/
|
|
477
540
|
async getSessionHistory(sessionId: string): Promise<MemoryEvent[]> {
|
|
478
541
|
await this.initialize();
|
|
479
|
-
return this.
|
|
542
|
+
return this.sqliteStore.getSessionEvents(sessionId);
|
|
480
543
|
}
|
|
481
544
|
|
|
482
545
|
/**
|
|
@@ -484,7 +547,7 @@ export class MemoryService {
|
|
|
484
547
|
*/
|
|
485
548
|
async getRecentEvents(limit: number = 100): Promise<MemoryEvent[]> {
|
|
486
549
|
await this.initialize();
|
|
487
|
-
return this.
|
|
550
|
+
return this.sqliteStore.getRecentEvents(limit);
|
|
488
551
|
}
|
|
489
552
|
|
|
490
553
|
/**
|
|
@@ -497,7 +560,7 @@ export class MemoryService {
|
|
|
497
560
|
}> {
|
|
498
561
|
await this.initialize();
|
|
499
562
|
|
|
500
|
-
const recentEvents = await this.
|
|
563
|
+
const recentEvents = await this.sqliteStore.getRecentEvents(10000);
|
|
501
564
|
const vectorCount = await this.vectorStore.count();
|
|
502
565
|
const levelStats = await this.graduation.getStats();
|
|
503
566
|
|
|
@@ -523,7 +586,7 @@ export class MemoryService {
|
|
|
523
586
|
*/
|
|
524
587
|
async getEventsByLevel(level: string, options?: { limit?: number; offset?: number }): Promise<MemoryEvent[]> {
|
|
525
588
|
await this.initialize();
|
|
526
|
-
return this.
|
|
589
|
+
return this.sqliteStore.getEventsByLevel(level, options);
|
|
527
590
|
}
|
|
528
591
|
|
|
529
592
|
/**
|
|
@@ -531,7 +594,7 @@ export class MemoryService {
|
|
|
531
594
|
*/
|
|
532
595
|
async getEventLevel(eventId: string): Promise<string | null> {
|
|
533
596
|
await this.initialize();
|
|
534
|
-
return this.
|
|
597
|
+
return this.sqliteStore.getEventLevel(eventId);
|
|
535
598
|
}
|
|
536
599
|
|
|
537
600
|
/**
|
|
@@ -644,14 +707,14 @@ export class MemoryService {
|
|
|
644
707
|
async initializeEndlessMode(): Promise<void> {
|
|
645
708
|
const config = await this.getEndlessConfig();
|
|
646
709
|
|
|
647
|
-
this.workingSetStore = createWorkingSetStore(this.
|
|
648
|
-
this.consolidatedStore = createConsolidatedStore(this.
|
|
710
|
+
this.workingSetStore = createWorkingSetStore(this.sqliteStore, config);
|
|
711
|
+
this.consolidatedStore = createConsolidatedStore(this.sqliteStore);
|
|
649
712
|
this.consolidationWorker = createConsolidationWorker(
|
|
650
713
|
this.workingSetStore,
|
|
651
714
|
this.consolidatedStore,
|
|
652
715
|
config
|
|
653
716
|
);
|
|
654
|
-
this.continuityManager = createContinuityManager(this.
|
|
717
|
+
this.continuityManager = createContinuityManager(this.sqliteStore, config);
|
|
655
718
|
|
|
656
719
|
// Start consolidation worker
|
|
657
720
|
this.consolidationWorker.start();
|
|
@@ -661,7 +724,7 @@ export class MemoryService {
|
|
|
661
724
|
* Get Endless Mode configuration
|
|
662
725
|
*/
|
|
663
726
|
async getEndlessConfig(): Promise<EndlessModeConfig> {
|
|
664
|
-
const savedConfig = await this.
|
|
727
|
+
const savedConfig = await this.sqliteStore.getEndlessConfig('config') as EndlessModeConfig | null;
|
|
665
728
|
return savedConfig || this.getDefaultEndlessConfig();
|
|
666
729
|
}
|
|
667
730
|
|
|
@@ -671,7 +734,7 @@ export class MemoryService {
|
|
|
671
734
|
async setEndlessConfig(config: Partial<EndlessModeConfig>): Promise<void> {
|
|
672
735
|
const current = await this.getEndlessConfig();
|
|
673
736
|
const merged = { ...current, ...config };
|
|
674
|
-
await this.
|
|
737
|
+
await this.sqliteStore.setEndlessConfig('config', merged);
|
|
675
738
|
}
|
|
676
739
|
|
|
677
740
|
/**
|
|
@@ -683,7 +746,7 @@ export class MemoryService {
|
|
|
683
746
|
if (mode === this.endlessMode) return;
|
|
684
747
|
|
|
685
748
|
this.endlessMode = mode;
|
|
686
|
-
await this.
|
|
749
|
+
await this.sqliteStore.setEndlessConfig('mode', mode);
|
|
687
750
|
|
|
688
751
|
if (mode === 'endless') {
|
|
689
752
|
await this.initializeEndlessMode();
|
|
@@ -749,11 +812,56 @@ export class MemoryService {
|
|
|
749
812
|
}
|
|
750
813
|
|
|
751
814
|
/**
|
|
752
|
-
*
|
|
815
|
+
* Increment access count for memories that were used in prompts
|
|
753
816
|
*/
|
|
754
|
-
async
|
|
755
|
-
if (
|
|
756
|
-
|
|
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 [];
|
|
757
865
|
}
|
|
758
866
|
|
|
759
867
|
/**
|
|
@@ -908,12 +1016,23 @@ export class MemoryService {
|
|
|
908
1016
|
this.vectorWorker.stop();
|
|
909
1017
|
}
|
|
910
1018
|
|
|
1019
|
+
// Stop sync worker
|
|
1020
|
+
if (this.syncWorker) {
|
|
1021
|
+
this.syncWorker.stop();
|
|
1022
|
+
}
|
|
1023
|
+
|
|
911
1024
|
// Close shared store
|
|
912
1025
|
if (this.sharedEventStore) {
|
|
913
1026
|
await this.sharedEventStore.close();
|
|
914
1027
|
}
|
|
915
1028
|
|
|
916
|
-
|
|
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
|
+
}
|
|
917
1036
|
}
|
|
918
1037
|
|
|
919
1038
|
/**
|
|
@@ -939,11 +1058,14 @@ const GLOBAL_READONLY_KEY = '__global_readonly__';
|
|
|
939
1058
|
/**
|
|
940
1059
|
* Get the global memory service (backward compatibility)
|
|
941
1060
|
* Use this for operations not tied to a specific project
|
|
1061
|
+
* Note: analyticsEnabled=false and sharedStore disabled to avoid DuckDB lock conflicts
|
|
942
1062
|
*/
|
|
943
1063
|
export function getDefaultMemoryService(): MemoryService {
|
|
944
1064
|
if (!serviceCache.has(GLOBAL_KEY)) {
|
|
945
1065
|
serviceCache.set(GLOBAL_KEY, new MemoryService({
|
|
946
|
-
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
|
|
947
1069
|
}));
|
|
948
1070
|
}
|
|
949
1071
|
return serviceCache.get(GLOBAL_KEY)!;
|
|
@@ -953,19 +1075,24 @@ export function getDefaultMemoryService(): MemoryService {
|
|
|
953
1075
|
* Get a read-only global memory service
|
|
954
1076
|
* Use this for web server/dashboard that only needs to read data
|
|
955
1077
|
* Creates a fresh connection each time to avoid blocking the main writer process
|
|
1078
|
+
* Uses SQLite (WAL mode) which supports concurrent readers
|
|
956
1079
|
*/
|
|
957
1080
|
export function getReadOnlyMemoryService(): MemoryService {
|
|
958
1081
|
// Don't cache - create fresh instance each time to avoid holding locks
|
|
959
1082
|
// The connection will be closed when the request completes
|
|
1083
|
+
// Uses SQLite which supports concurrent readers via WAL mode
|
|
960
1084
|
return new MemoryService({
|
|
961
1085
|
storagePath: '~/.claude-code/memory',
|
|
962
|
-
readOnly: true
|
|
1086
|
+
readOnly: true,
|
|
1087
|
+
analyticsEnabled: false, // Use SQLite for reads (WAL supports concurrent readers)
|
|
1088
|
+
sharedStoreConfig: { enabled: false } // Skip shared store for now
|
|
963
1089
|
});
|
|
964
1090
|
}
|
|
965
1091
|
|
|
966
1092
|
/**
|
|
967
1093
|
* Get memory service for a specific project path
|
|
968
1094
|
* Creates isolated storage at ~/.claude-code/memory/projects/{hash}/
|
|
1095
|
+
* Note: analyticsEnabled=false and sharedStore disabled to avoid DuckDB lock conflicts
|
|
969
1096
|
*/
|
|
970
1097
|
export function getMemoryServiceForProject(
|
|
971
1098
|
projectPath: string,
|
|
@@ -978,7 +1105,9 @@ export function getMemoryServiceForProject(
|
|
|
978
1105
|
serviceCache.set(hash, new MemoryService({
|
|
979
1106
|
storagePath,
|
|
980
1107
|
projectHash: hash,
|
|
981
|
-
|
|
1108
|
+
// Override shared store config - hooks don't need DuckDB
|
|
1109
|
+
sharedStoreConfig: sharedStoreConfig ?? { enabled: false },
|
|
1110
|
+
analyticsEnabled: false // Hooks don't need DuckDB
|
|
982
1111
|
}));
|
|
983
1112
|
}
|
|
984
1113
|
|