Merge Memory V2 runtime facade

This commit is contained in:
john
2026-07-03 09:46:03 +08:00
parent 7005c2a834
commit 20116b161f
69 changed files with 11869 additions and 27 deletions
+126
View File
@@ -0,0 +1,126 @@
import assert from 'node:assert/strict';
import test from 'node:test';
import {
MEMORY_V2_PLUGIN_BACKEND_SPECS,
createMemoryV2PluginBackends,
createUnavailableMemoryBackend,
} from './memory-v2-plugin-backends.mjs';
import { createMemoryV2 } from './memory-v2.mjs';
function silentLogger() {
return { warn() {} };
}
test('Memory V2 plugin specs cover optional future backend families', () => {
assert.deepEqual(
MEMORY_V2_PLUGIN_BACKEND_SPECS.map((spec) => [spec.name, spec.category]),
[
['pgvector', 'semantic'],
['qdrant', 'semantic'],
['weaviate', 'semantic'],
['mem0', 'extraction'],
['letta', 'lifecycle'],
['neo4j', 'behavior'],
['redis-streams', 'behavior'],
['langgraph', 'policy'],
],
);
});
test('createUnavailableMemoryBackend exposes declared capabilities without availability', async () => {
const backend = createUnavailableMemoryBackend({
name: 'mem0',
category: 'extraction',
role: 'automatic-memory-generation',
capabilities: ['write', 'compact'],
flag: 'MEMORY_MEM0_ENABLED',
});
assert.equal(backend.name, 'mem0');
assert.equal(backend.category, 'extraction');
assert.equal(backend.role, 'automatic-memory-generation');
assert.equal(backend.flag, 'MEMORY_MEM0_ENABLED');
assert.equal(backend.isAvailable(), false);
assert.equal(backend.getUnavailableReason(), 'not_configured');
assert.equal(typeof backend.resolve, 'undefined');
assert.deepEqual(await backend.write({}), { saved: 0, analyzed: 0, memories: 0 });
assert.deepEqual(await backend.compact({}), { analyzed: 0, memories: 0 });
});
test('createMemoryV2PluginBackends can exclude a real injected backend', () => {
const backends = createMemoryV2PluginBackends({ exclude: ['pgvector'] });
assert.equal(backends.some((backend) => backend.name === 'pgvector'), false);
assert.equal(backends.some((backend) => backend.name === 'qdrant'), true);
});
test('Memory V2 default status lists future plugin slots without selecting them', () => {
const memory = createMemoryV2({
logger: silentLogger(),
legacyMemoryService: {
async listMemories() {
return [];
},
},
env: {
MEMORY_ENABLED: '1',
MEMORY_BACKEND: 'legacy',
},
});
const status = memory.getStatus();
const byName = new Map(status.backends.map((backend) => [backend.name, backend]));
assert.equal(status.selectedBackend, 'legacy-conversation-memory');
assert.equal(byName.get('legacy-conversation-memory').available, true);
assert.deepEqual(
[...byName.keys()],
[
'legacy-conversation-memory',
'pgvector',
'qdrant',
'weaviate',
'mem0',
'letta',
'neo4j',
'redis-streams',
'langgraph',
],
);
assert.deepEqual(byName.get('qdrant'), {
name: 'qdrant',
available: false,
supports: {
resolve: true,
write: false,
compact: false,
},
category: 'semantic',
role: 'scale-out-vector-store',
flag: 'MEMORY_QDRANT_ENABLED',
reason: 'not_configured',
});
});
test('Memory V2 falls back to legacy when a future plugin is configured but unavailable', async () => {
const memory = createMemoryV2({
logger: silentLogger(),
legacyMemoryService: {
async listMemories() {
return [{ label: 'fact', text: 'legacy remains the safe path' }];
},
},
env: {
MEMORY_ENABLED: '1',
MEMORY_BACKEND: 'qdrant',
},
});
const result = await memory.resolve({ userId: 'u1', query: 'memory-chain' });
assert.equal(memory.getStatus().selectedBackend, 'legacy-conversation-memory');
assert.equal(result.source, 'legacy-conversation-memory');
assert.deepEqual(result.memories, [
{ label: 'fact', text: 'legacy remains the safe path' },
]);
});