@aitytech/agentkits-memory 1.0.1 → 2.0.1
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/README.md +54 -5
- package/dist/better-sqlite3-backend.d.ts +192 -0
- package/dist/better-sqlite3-backend.d.ts.map +1 -0
- package/dist/better-sqlite3-backend.js +801 -0
- package/dist/better-sqlite3-backend.js.map +1 -0
- package/dist/cli/save.js +0 -0
- package/dist/cli/setup.d.ts +6 -2
- package/dist/cli/setup.d.ts.map +1 -1
- package/dist/cli/setup.js +289 -42
- package/dist/cli/setup.js.map +1 -1
- package/dist/cli/viewer.js +25 -56
- package/dist/cli/viewer.js.map +1 -1
- package/dist/cli/web-viewer.d.ts +2 -1
- package/dist/cli/web-viewer.d.ts.map +1 -1
- package/dist/cli/web-viewer.js +791 -141
- package/dist/cli/web-viewer.js.map +1 -1
- package/dist/embeddings/embedding-cache.d.ts +131 -0
- package/dist/embeddings/embedding-cache.d.ts.map +1 -0
- package/dist/embeddings/embedding-cache.js +217 -0
- package/dist/embeddings/embedding-cache.js.map +1 -0
- package/dist/embeddings/index.d.ts +11 -0
- package/dist/embeddings/index.d.ts.map +1 -0
- package/dist/embeddings/index.js +11 -0
- package/dist/embeddings/index.js.map +1 -0
- package/dist/embeddings/local-embeddings.d.ts +140 -0
- package/dist/embeddings/local-embeddings.d.ts.map +1 -0
- package/dist/embeddings/local-embeddings.js +293 -0
- package/dist/embeddings/local-embeddings.js.map +1 -0
- package/dist/hooks/context.d.ts +6 -1
- package/dist/hooks/context.d.ts.map +1 -1
- package/dist/hooks/context.js +12 -2
- package/dist/hooks/context.js.map +1 -1
- package/dist/hooks/observation.d.ts +6 -1
- package/dist/hooks/observation.d.ts.map +1 -1
- package/dist/hooks/observation.js +12 -2
- package/dist/hooks/observation.js.map +1 -1
- package/dist/hooks/service.d.ts +1 -6
- package/dist/hooks/service.d.ts.map +1 -1
- package/dist/hooks/service.js +33 -85
- package/dist/hooks/service.js.map +1 -1
- package/dist/hooks/session-init.d.ts +6 -1
- package/dist/hooks/session-init.d.ts.map +1 -1
- package/dist/hooks/session-init.js +12 -2
- package/dist/hooks/session-init.js.map +1 -1
- package/dist/hooks/summarize.d.ts +6 -1
- package/dist/hooks/summarize.d.ts.map +1 -1
- package/dist/hooks/summarize.js +12 -2
- package/dist/hooks/summarize.js.map +1 -1
- package/dist/index.d.ts +10 -17
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +172 -94
- package/dist/index.js.map +1 -1
- package/dist/mcp/server.js +17 -3
- package/dist/mcp/server.js.map +1 -1
- package/dist/migration.js +3 -3
- package/dist/migration.js.map +1 -1
- package/dist/search/hybrid-search.d.ts +262 -0
- package/dist/search/hybrid-search.d.ts.map +1 -0
- package/dist/search/hybrid-search.js +688 -0
- package/dist/search/hybrid-search.js.map +1 -0
- package/dist/search/index.d.ts +13 -0
- package/dist/search/index.d.ts.map +1 -0
- package/dist/search/index.js +13 -0
- package/dist/search/index.js.map +1 -0
- package/dist/search/token-economics.d.ts +161 -0
- package/dist/search/token-economics.d.ts.map +1 -0
- package/dist/search/token-economics.js +239 -0
- package/dist/search/token-economics.js.map +1 -0
- package/dist/types.d.ts +0 -68
- package/dist/types.d.ts.map +1 -1
- package/dist/types.js.map +1 -1
- package/package.json +6 -4
- package/src/__tests__/better-sqlite3-backend.test.ts +1466 -0
- package/src/__tests__/cache-manager.test.ts +499 -0
- package/src/__tests__/embedding-integration.test.ts +481 -0
- package/src/__tests__/hnsw-index.test.ts +727 -0
- package/src/__tests__/index.test.ts +432 -0
- package/src/better-sqlite3-backend.ts +1000 -0
- package/src/cli/setup.ts +358 -47
- package/src/cli/viewer.ts +28 -63
- package/src/cli/web-viewer.ts +936 -182
- package/src/embeddings/__tests__/embedding-cache.test.ts +269 -0
- package/src/embeddings/__tests__/local-embeddings.test.ts +495 -0
- package/src/embeddings/embedding-cache.ts +318 -0
- package/src/embeddings/index.ts +20 -0
- package/src/embeddings/local-embeddings.ts +419 -0
- package/src/hooks/__tests__/handlers.test.ts +58 -17
- package/src/hooks/__tests__/integration.test.ts +77 -26
- package/src/hooks/context.ts +13 -2
- package/src/hooks/observation.ts +13 -2
- package/src/hooks/service.ts +39 -100
- package/src/hooks/session-init.ts +13 -2
- package/src/hooks/summarize.ts +13 -2
- package/src/index.ts +210 -116
- package/src/mcp/server.ts +20 -3
- package/src/search/__tests__/hybrid-search.test.ts +669 -0
- package/src/search/__tests__/token-economics.test.ts +276 -0
- package/src/search/hybrid-search.ts +968 -0
- package/src/search/index.ts +29 -0
- package/src/search/token-economics.ts +367 -0
- package/src/types.ts +0 -96
- package/src/__tests__/sqljs-backend.test.ts +0 -410
- package/src/migration.ts +0 -574
- package/src/sql.js.d.ts +0 -70
- package/src/sqljs-backend.ts +0 -789
|
@@ -405,3 +405,435 @@ describe('DEFAULT_NAMESPACES', () => {
|
|
|
405
405
|
expect(DEFAULT_NAMESPACES.ERRORS).toBe('errors');
|
|
406
406
|
});
|
|
407
407
|
});
|
|
408
|
+
|
|
409
|
+
describe('ProjectMemoryService with embeddings', () => {
|
|
410
|
+
let service: ProjectMemoryService;
|
|
411
|
+
let testDir: string;
|
|
412
|
+
const mockEmbeddingGenerator = vi.fn().mockImplementation(async (content: string) => {
|
|
413
|
+
// Simple mock embedding based on content length
|
|
414
|
+
const embedding = new Float32Array(8);
|
|
415
|
+
for (let i = 0; i < 8; i++) {
|
|
416
|
+
embedding[i] = (content.charCodeAt(i % content.length) / 255) - 0.5;
|
|
417
|
+
}
|
|
418
|
+
return embedding;
|
|
419
|
+
});
|
|
420
|
+
|
|
421
|
+
beforeEach(async () => {
|
|
422
|
+
testDir = path.join(tmpdir(), `embedding-test-${Date.now()}`);
|
|
423
|
+
fs.mkdirSync(testDir, { recursive: true });
|
|
424
|
+
|
|
425
|
+
service = new ProjectMemoryService({
|
|
426
|
+
baseDir: testDir,
|
|
427
|
+
dbFilename: 'test.db',
|
|
428
|
+
cacheEnabled: true,
|
|
429
|
+
enableVectorIndex: true,
|
|
430
|
+
dimensions: 8,
|
|
431
|
+
embeddingGenerator: mockEmbeddingGenerator,
|
|
432
|
+
});
|
|
433
|
+
await service.initialize();
|
|
434
|
+
});
|
|
435
|
+
|
|
436
|
+
afterEach(async () => {
|
|
437
|
+
await service.shutdown();
|
|
438
|
+
fs.rmSync(testDir, { recursive: true, force: true });
|
|
439
|
+
vi.clearAllMocks();
|
|
440
|
+
});
|
|
441
|
+
|
|
442
|
+
it('should generate embeddings when storing entries', async () => {
|
|
443
|
+
await service.storeEntry({
|
|
444
|
+
key: 'embed-test',
|
|
445
|
+
content: 'Test content for embedding',
|
|
446
|
+
namespace: 'test',
|
|
447
|
+
});
|
|
448
|
+
|
|
449
|
+
expect(mockEmbeddingGenerator).toHaveBeenCalledWith('Test content for embedding');
|
|
450
|
+
});
|
|
451
|
+
|
|
452
|
+
it('should perform semantic search', async () => {
|
|
453
|
+
await service.storeEntry({
|
|
454
|
+
key: 'auth-pattern',
|
|
455
|
+
content: 'Use JWT for authentication',
|
|
456
|
+
namespace: 'patterns',
|
|
457
|
+
});
|
|
458
|
+
|
|
459
|
+
await service.storeEntry({
|
|
460
|
+
key: 'db-pattern',
|
|
461
|
+
content: 'Use PostgreSQL for database',
|
|
462
|
+
namespace: 'patterns',
|
|
463
|
+
});
|
|
464
|
+
|
|
465
|
+
const results = await service.semanticSearch('authentication', 5);
|
|
466
|
+
expect(results.length).toBeGreaterThan(0);
|
|
467
|
+
});
|
|
468
|
+
|
|
469
|
+
it('should throw error for semantic search without embedding generator', async () => {
|
|
470
|
+
const noEmbedService = new ProjectMemoryService({
|
|
471
|
+
baseDir: path.join(testDir, 'no-embed'),
|
|
472
|
+
dbFilename: 'test.db',
|
|
473
|
+
});
|
|
474
|
+
await noEmbedService.initialize();
|
|
475
|
+
|
|
476
|
+
await expect(noEmbedService.semanticSearch('test', 5)).rejects.toThrow(
|
|
477
|
+
'Embedding generator not configured'
|
|
478
|
+
);
|
|
479
|
+
|
|
480
|
+
await noEmbedService.shutdown();
|
|
481
|
+
});
|
|
482
|
+
|
|
483
|
+
it('should use HNSW index for search with vector index enabled', async () => {
|
|
484
|
+
await service.storeEntry({
|
|
485
|
+
key: 'v1',
|
|
486
|
+
content: 'First vector content',
|
|
487
|
+
namespace: 'vectors',
|
|
488
|
+
});
|
|
489
|
+
|
|
490
|
+
await service.storeEntry({
|
|
491
|
+
key: 'v2',
|
|
492
|
+
content: 'Second vector content',
|
|
493
|
+
namespace: 'vectors',
|
|
494
|
+
});
|
|
495
|
+
|
|
496
|
+
const embedding = await mockEmbeddingGenerator('First');
|
|
497
|
+
const results = await service.search(embedding, { k: 2 });
|
|
498
|
+
|
|
499
|
+
expect(results.length).toBeLessThanOrEqual(2);
|
|
500
|
+
});
|
|
501
|
+
|
|
502
|
+
it('should update embedding when content changes', async () => {
|
|
503
|
+
const entry = await service.storeEntry({
|
|
504
|
+
key: 'update-embed',
|
|
505
|
+
content: 'Original content',
|
|
506
|
+
namespace: 'test',
|
|
507
|
+
});
|
|
508
|
+
|
|
509
|
+
const callsBefore = mockEmbeddingGenerator.mock.calls.length;
|
|
510
|
+
|
|
511
|
+
await service.update(entry.id, { content: 'Updated content' });
|
|
512
|
+
|
|
513
|
+
// Should have generated new embedding for updated content
|
|
514
|
+
expect(mockEmbeddingGenerator.mock.calls.length).toBeGreaterThan(callsBefore);
|
|
515
|
+
});
|
|
516
|
+
|
|
517
|
+
it('should handle embedding generator failure gracefully', async () => {
|
|
518
|
+
const failingGenerator = vi.fn().mockRejectedValue(new Error('Embedding failed'));
|
|
519
|
+
const failService = new ProjectMemoryService({
|
|
520
|
+
baseDir: path.join(testDir, 'fail-embed'),
|
|
521
|
+
dbFilename: 'test.db',
|
|
522
|
+
embeddingGenerator: failingGenerator,
|
|
523
|
+
verbose: true,
|
|
524
|
+
});
|
|
525
|
+
await failService.initialize();
|
|
526
|
+
|
|
527
|
+
// Should not throw, just log warning
|
|
528
|
+
const entry = await failService.storeEntry({
|
|
529
|
+
key: 'fail-test',
|
|
530
|
+
content: 'Content that will fail embedding',
|
|
531
|
+
namespace: 'test',
|
|
532
|
+
});
|
|
533
|
+
|
|
534
|
+
expect(entry.id).toBeDefined();
|
|
535
|
+
await failService.shutdown();
|
|
536
|
+
});
|
|
537
|
+
|
|
538
|
+
it('should remove from vector index when deleting', async () => {
|
|
539
|
+
const entry = await service.storeEntry({
|
|
540
|
+
key: 'delete-vector',
|
|
541
|
+
content: 'Content to delete',
|
|
542
|
+
namespace: 'test',
|
|
543
|
+
});
|
|
544
|
+
|
|
545
|
+
// Verify entry exists in vector search
|
|
546
|
+
const embedding = await mockEmbeddingGenerator('Content to delete');
|
|
547
|
+
const beforeDelete = await service.search(embedding, { k: 5 });
|
|
548
|
+
const foundBefore = beforeDelete.some(r => r.entry.id === entry.id);
|
|
549
|
+
expect(foundBefore).toBe(true);
|
|
550
|
+
|
|
551
|
+
await service.delete(entry.id);
|
|
552
|
+
|
|
553
|
+
// Entry should no longer be found
|
|
554
|
+
const afterDelete = await service.search(embedding, { k: 5 });
|
|
555
|
+
const foundAfter = afterDelete.some(r => r.entry.id === entry.id);
|
|
556
|
+
expect(foundAfter).toBe(false);
|
|
557
|
+
});
|
|
558
|
+
|
|
559
|
+
it('should apply threshold in search results', async () => {
|
|
560
|
+
await service.storeEntry({
|
|
561
|
+
key: 'similar',
|
|
562
|
+
content: 'Very similar content',
|
|
563
|
+
namespace: 'test',
|
|
564
|
+
});
|
|
565
|
+
|
|
566
|
+
await service.storeEntry({
|
|
567
|
+
key: 'different',
|
|
568
|
+
content: 'Completely different words here',
|
|
569
|
+
namespace: 'test',
|
|
570
|
+
});
|
|
571
|
+
|
|
572
|
+
const embedding = await mockEmbeddingGenerator('Very similar');
|
|
573
|
+
const results = await service.search(embedding, { k: 10, threshold: 0.9 });
|
|
574
|
+
|
|
575
|
+
// High threshold should filter out dissimilar results
|
|
576
|
+
expect(results.length).toBeLessThanOrEqual(2);
|
|
577
|
+
});
|
|
578
|
+
|
|
579
|
+
it('should include HNSW stats in getStats', async () => {
|
|
580
|
+
await service.storeEntry({
|
|
581
|
+
key: 'stats-test',
|
|
582
|
+
content: 'Content for stats',
|
|
583
|
+
namespace: 'test',
|
|
584
|
+
});
|
|
585
|
+
|
|
586
|
+
const stats = await service.getStats();
|
|
587
|
+
expect(stats.hnswStats).toBeDefined();
|
|
588
|
+
expect(stats.hnswStats!.vectorCount).toBe(1);
|
|
589
|
+
});
|
|
590
|
+
|
|
591
|
+
it('should include cache stats in getStats', async () => {
|
|
592
|
+
await service.storeEntry({
|
|
593
|
+
key: 'cache-test',
|
|
594
|
+
content: 'Content for cache',
|
|
595
|
+
namespace: 'test',
|
|
596
|
+
});
|
|
597
|
+
|
|
598
|
+
// Trigger a cache hit
|
|
599
|
+
await service.get((await service.query({ type: 'hybrid', limit: 1 }))[0].id);
|
|
600
|
+
|
|
601
|
+
const stats = await service.getStats();
|
|
602
|
+
expect(stats.cacheStats).toBeDefined();
|
|
603
|
+
});
|
|
604
|
+
});
|
|
605
|
+
|
|
606
|
+
describe('ProjectMemoryService with cache', () => {
|
|
607
|
+
let service: ProjectMemoryService;
|
|
608
|
+
let testDir: string;
|
|
609
|
+
|
|
610
|
+
beforeEach(async () => {
|
|
611
|
+
testDir = path.join(tmpdir(), `cache-test-${Date.now()}`);
|
|
612
|
+
fs.mkdirSync(testDir, { recursive: true });
|
|
613
|
+
|
|
614
|
+
service = new ProjectMemoryService({
|
|
615
|
+
baseDir: testDir,
|
|
616
|
+
dbFilename: 'test.db',
|
|
617
|
+
cacheEnabled: true,
|
|
618
|
+
cacheSize: 100,
|
|
619
|
+
cacheTtl: 60000,
|
|
620
|
+
});
|
|
621
|
+
await service.initialize();
|
|
622
|
+
});
|
|
623
|
+
|
|
624
|
+
afterEach(async () => {
|
|
625
|
+
await service.shutdown();
|
|
626
|
+
fs.rmSync(testDir, { recursive: true, force: true });
|
|
627
|
+
});
|
|
628
|
+
|
|
629
|
+
it('should cache entries on get', async () => {
|
|
630
|
+
const entry = await service.storeEntry({
|
|
631
|
+
key: 'cache-hit',
|
|
632
|
+
content: 'Cached content',
|
|
633
|
+
namespace: 'test',
|
|
634
|
+
});
|
|
635
|
+
|
|
636
|
+
// First get populates cache
|
|
637
|
+
const first = await service.get(entry.id);
|
|
638
|
+
expect(first).not.toBeNull();
|
|
639
|
+
|
|
640
|
+
// Second get should hit cache
|
|
641
|
+
const second = await service.get(entry.id);
|
|
642
|
+
expect(second).not.toBeNull();
|
|
643
|
+
expect(second!.id).toBe(first!.id);
|
|
644
|
+
});
|
|
645
|
+
|
|
646
|
+
it('should cache entries on getByKey', async () => {
|
|
647
|
+
await service.storeEntry({
|
|
648
|
+
key: 'cache-key',
|
|
649
|
+
content: 'Cached by key',
|
|
650
|
+
namespace: 'ns',
|
|
651
|
+
});
|
|
652
|
+
|
|
653
|
+
// First get populates cache
|
|
654
|
+
const first = await service.getByKey('ns', 'cache-key');
|
|
655
|
+
expect(first).not.toBeNull();
|
|
656
|
+
|
|
657
|
+
// Second get should hit cache
|
|
658
|
+
const second = await service.getByKey('ns', 'cache-key');
|
|
659
|
+
expect(second).not.toBeNull();
|
|
660
|
+
});
|
|
661
|
+
|
|
662
|
+
it('should update cache on update', async () => {
|
|
663
|
+
const entry = await service.storeEntry({
|
|
664
|
+
key: 'update-cache',
|
|
665
|
+
content: 'Original',
|
|
666
|
+
namespace: 'test',
|
|
667
|
+
});
|
|
668
|
+
|
|
669
|
+
await service.update(entry.id, { content: 'Updated' });
|
|
670
|
+
|
|
671
|
+
const retrieved = await service.get(entry.id);
|
|
672
|
+
expect(retrieved!.content).toBe('Updated');
|
|
673
|
+
});
|
|
674
|
+
|
|
675
|
+
it('should invalidate cache on delete', async () => {
|
|
676
|
+
const entry = await service.storeEntry({
|
|
677
|
+
key: 'delete-cache',
|
|
678
|
+
content: 'To delete',
|
|
679
|
+
namespace: 'test',
|
|
680
|
+
});
|
|
681
|
+
|
|
682
|
+
// Populate cache
|
|
683
|
+
await service.get(entry.id);
|
|
684
|
+
|
|
685
|
+
// Delete
|
|
686
|
+
await service.delete(entry.id);
|
|
687
|
+
|
|
688
|
+
// Should not find in cache or backend
|
|
689
|
+
const retrieved = await service.get(entry.id);
|
|
690
|
+
expect(retrieved).toBeNull();
|
|
691
|
+
});
|
|
692
|
+
|
|
693
|
+
it('should invalidate cache pattern on clearNamespace', async () => {
|
|
694
|
+
await service.storeEntry({ key: 'c1', content: 'C1', namespace: 'clear-ns' });
|
|
695
|
+
await service.storeEntry({ key: 'c2', content: 'C2', namespace: 'clear-ns' });
|
|
696
|
+
await service.storeEntry({ key: 'k1', content: 'K1', namespace: 'keep-ns' });
|
|
697
|
+
|
|
698
|
+
await service.clearNamespace('clear-ns');
|
|
699
|
+
|
|
700
|
+
expect(await service.count('clear-ns')).toBe(0);
|
|
701
|
+
expect(await service.count('keep-ns')).toBe(1);
|
|
702
|
+
});
|
|
703
|
+
});
|
|
704
|
+
|
|
705
|
+
describe('ProjectMemoryService error handling', () => {
|
|
706
|
+
it('should throw error when not initialized', async () => {
|
|
707
|
+
const service = new ProjectMemoryService({
|
|
708
|
+
baseDir: path.join(tmpdir(), 'not-init-test'),
|
|
709
|
+
});
|
|
710
|
+
|
|
711
|
+
await expect(service.get('some-id')).rejects.toThrow('not initialized');
|
|
712
|
+
await expect(service.query({ type: 'hybrid', limit: 10 })).rejects.toThrow('not initialized');
|
|
713
|
+
});
|
|
714
|
+
|
|
715
|
+
it('should handle shutdown with active session', async () => {
|
|
716
|
+
const testDir = path.join(tmpdir(), `session-shutdown-${Date.now()}`);
|
|
717
|
+
fs.mkdirSync(testDir, { recursive: true });
|
|
718
|
+
|
|
719
|
+
const service = new ProjectMemoryService({ baseDir: testDir });
|
|
720
|
+
await service.initialize();
|
|
721
|
+
|
|
722
|
+
await service.startSession();
|
|
723
|
+
|
|
724
|
+
// Shutdown should end the session automatically
|
|
725
|
+
await service.shutdown();
|
|
726
|
+
|
|
727
|
+
// Verify session was ended
|
|
728
|
+
expect(service.getCurrentSession()).toBeNull();
|
|
729
|
+
|
|
730
|
+
fs.rmSync(testDir, { recursive: true, force: true });
|
|
731
|
+
});
|
|
732
|
+
|
|
733
|
+
it('should handle double initialization', async () => {
|
|
734
|
+
const testDir = path.join(tmpdir(), `double-init-${Date.now()}`);
|
|
735
|
+
fs.mkdirSync(testDir, { recursive: true });
|
|
736
|
+
|
|
737
|
+
const service = new ProjectMemoryService({ baseDir: testDir });
|
|
738
|
+
await service.initialize();
|
|
739
|
+
await service.initialize(); // Should not throw
|
|
740
|
+
|
|
741
|
+
await service.shutdown();
|
|
742
|
+
fs.rmSync(testDir, { recursive: true, force: true });
|
|
743
|
+
});
|
|
744
|
+
|
|
745
|
+
it('should handle double shutdown', async () => {
|
|
746
|
+
const testDir = path.join(tmpdir(), `double-shutdown-${Date.now()}`);
|
|
747
|
+
fs.mkdirSync(testDir, { recursive: true });
|
|
748
|
+
|
|
749
|
+
const service = new ProjectMemoryService({ baseDir: testDir });
|
|
750
|
+
await service.initialize();
|
|
751
|
+
await service.shutdown();
|
|
752
|
+
await service.shutdown(); // Should not throw
|
|
753
|
+
|
|
754
|
+
fs.rmSync(testDir, { recursive: true, force: true });
|
|
755
|
+
});
|
|
756
|
+
|
|
757
|
+
it('should return null when ending non-existent session', async () => {
|
|
758
|
+
const testDir = path.join(tmpdir(), `no-session-${Date.now()}`);
|
|
759
|
+
fs.mkdirSync(testDir, { recursive: true });
|
|
760
|
+
|
|
761
|
+
const service = new ProjectMemoryService({ baseDir: testDir });
|
|
762
|
+
await service.initialize();
|
|
763
|
+
|
|
764
|
+
const result = await service.endSession('summary');
|
|
765
|
+
expect(result).toBeNull();
|
|
766
|
+
|
|
767
|
+
await service.shutdown();
|
|
768
|
+
fs.rmSync(testDir, { recursive: true, force: true });
|
|
769
|
+
});
|
|
770
|
+
|
|
771
|
+
it('should handle delete of non-existent entry', async () => {
|
|
772
|
+
const testDir = path.join(tmpdir(), `delete-none-${Date.now()}`);
|
|
773
|
+
fs.mkdirSync(testDir, { recursive: true });
|
|
774
|
+
|
|
775
|
+
const service = new ProjectMemoryService({ baseDir: testDir });
|
|
776
|
+
await service.initialize();
|
|
777
|
+
|
|
778
|
+
const result = await service.delete('non-existent-id');
|
|
779
|
+
expect(result).toBe(false);
|
|
780
|
+
|
|
781
|
+
await service.shutdown();
|
|
782
|
+
fs.rmSync(testDir, { recursive: true, force: true });
|
|
783
|
+
});
|
|
784
|
+
|
|
785
|
+
it('should handle getRecentSessions with invalid JSON', async () => {
|
|
786
|
+
const testDir = path.join(tmpdir(), `invalid-session-${Date.now()}`);
|
|
787
|
+
fs.mkdirSync(testDir, { recursive: true });
|
|
788
|
+
|
|
789
|
+
const service = new ProjectMemoryService({ baseDir: testDir });
|
|
790
|
+
await service.initialize();
|
|
791
|
+
|
|
792
|
+
// Store invalid session data
|
|
793
|
+
await service.storeEntry({
|
|
794
|
+
key: 'session:invalid',
|
|
795
|
+
content: 'not valid json {{{',
|
|
796
|
+
namespace: DEFAULT_NAMESPACES.SESSION,
|
|
797
|
+
tags: ['session'],
|
|
798
|
+
});
|
|
799
|
+
|
|
800
|
+
// Should filter out invalid entries
|
|
801
|
+
const sessions = await service.getRecentSessions();
|
|
802
|
+
expect(sessions.every(s => s.id !== undefined)).toBe(true);
|
|
803
|
+
|
|
804
|
+
await service.shutdown();
|
|
805
|
+
fs.rmSync(testDir, { recursive: true, force: true });
|
|
806
|
+
});
|
|
807
|
+
});
|
|
808
|
+
|
|
809
|
+
describe('createEmbeddingMemory factory', () => {
|
|
810
|
+
let testDir: string;
|
|
811
|
+
|
|
812
|
+
beforeEach(() => {
|
|
813
|
+
testDir = path.join(tmpdir(), `embed-factory-${Date.now()}`);
|
|
814
|
+
});
|
|
815
|
+
|
|
816
|
+
afterEach(() => {
|
|
817
|
+
fs.rmSync(testDir, { recursive: true, force: true });
|
|
818
|
+
});
|
|
819
|
+
|
|
820
|
+
it('should create memory service with embedding support', async () => {
|
|
821
|
+
const { createEmbeddingMemory } = await import('../index.js');
|
|
822
|
+
|
|
823
|
+
const mockGenerator = vi.fn().mockResolvedValue(new Float32Array(128));
|
|
824
|
+
|
|
825
|
+
const service = createEmbeddingMemory(testDir, mockGenerator, 128);
|
|
826
|
+
await service.initialize();
|
|
827
|
+
|
|
828
|
+
// Store an entry to trigger embedding generation
|
|
829
|
+
await service.storeEntry({
|
|
830
|
+
key: 'embed-test',
|
|
831
|
+
content: 'Test content',
|
|
832
|
+
namespace: 'test',
|
|
833
|
+
});
|
|
834
|
+
|
|
835
|
+
expect(mockGenerator).toHaveBeenCalled();
|
|
836
|
+
|
|
837
|
+
await service.shutdown();
|
|
838
|
+
});
|
|
839
|
+
});
|