@melandlabs/memory-store 0.1.6 → 0.2.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/dist/{config-D6MzksfT.d.ts → config-BYfTzG3h.d.ts} +17 -0
- package/dist/contracts.d.ts +7 -0
- package/dist/contracts.js +0 -2
- package/dist/http.d.ts +1 -1
- package/dist/http.js +355 -67
- package/dist/index.d.ts +2 -2
- package/dist/index.js +287 -52
- package/dist/mcp.d.ts +1 -1
- package/dist/mcp.js +313 -60
- package/dist/policies/memory-graph-correction-policy.js +0 -2
- package/dist/policies/memory-graph-write-policy.js +0 -2
- package/dist/search/unified-search.d.ts +39 -3
- package/dist/search/unified-search.js +311 -56
- package/dist/server/cli-http.js +360 -66
- package/dist/server/cli-mcp.js +318 -59
- package/dist/server/cli-shared.d.ts +1 -1
- package/dist/server/cli-shared.js +0 -2
- package/dist/storage/chroma-memory-index.js +0 -2
- package/dist/storage/postgres-raw-message-factory.d.ts +1 -1
- package/dist/storage/postgres-raw-message-factory.js +0 -2
- package/dist/storage/raw-message-store.d.ts +9 -1
- package/dist/storage/raw-message-store.js +0 -2
- package/dist/storage/sqlite-raw-message-store.d.ts +15 -2
- package/dist/storage/sqlite-raw-message-store.js +8 -3
- package/dist/storage/sqlite-vector-index.d.ts +1 -1
- package/dist/storage/sqlite-vector-index.js +0 -2
- package/package.json +81 -93
- package/dist/contracts.js.map +0 -1
- package/dist/http.js.map +0 -1
- package/dist/index.js.map +0 -1
- package/dist/mcp.js.map +0 -1
- package/dist/policies/memory-graph-correction-policy.js.map +0 -1
- package/dist/policies/memory-graph-write-policy.js.map +0 -1
- package/dist/search/unified-search.js.map +0 -1
- package/dist/server/cli-http.js.map +0 -1
- package/dist/server/cli-mcp.js.map +0 -1
- package/dist/server/cli-shared.js.map +0 -1
- package/dist/storage/chroma-memory-index.js.map +0 -1
- package/dist/storage/postgres-raw-message-factory.js.map +0 -1
- package/dist/storage/raw-message-store.js.map +0 -1
- package/dist/storage/sqlite-raw-message-store.js.map +0 -1
- package/dist/storage/sqlite-vector-index.js.map +0 -1
package/dist/mcp.js
CHANGED
|
@@ -1,14 +1,22 @@
|
|
|
1
|
-
import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
|
|
2
|
-
import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';
|
|
3
|
-
import { z } from 'zod';
|
|
4
1
|
import { buildMemoryRecordEmbeddingDocument } from '@melandlabs/ai/memory';
|
|
5
2
|
import { ChromaVectorStore } from '@melandlabs/rag';
|
|
6
3
|
import { mkdirSync } from 'fs';
|
|
7
4
|
import { homedir } from 'os';
|
|
8
|
-
import {
|
|
5
|
+
import { dirname, join } from 'path';
|
|
9
6
|
import { SQLiteRawMessageManager } from '@melandlabs/sqlite';
|
|
7
|
+
import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
|
|
8
|
+
import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';
|
|
9
|
+
import { z } from 'zod';
|
|
10
10
|
|
|
11
|
-
|
|
11
|
+
var __defProp = Object.defineProperty;
|
|
12
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
13
|
+
var __esm = (fn, res) => function __init() {
|
|
14
|
+
return fn && (res = (0, fn[__getOwnPropNames(fn)[0]])(fn = 0)), res;
|
|
15
|
+
};
|
|
16
|
+
var __export = (target, all) => {
|
|
17
|
+
for (var name in all)
|
|
18
|
+
__defProp(target, name, { get: all[name], enumerable: true });
|
|
19
|
+
};
|
|
12
20
|
|
|
13
21
|
// src/contracts.ts
|
|
14
22
|
function normalizeTimestampToMs(value) {
|
|
@@ -32,14 +40,10 @@ function rawMessageToMemoryRecord(message) {
|
|
|
32
40
|
embeddingContentHash: message.embeddingContentHash
|
|
33
41
|
};
|
|
34
42
|
}
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
"RAW_MESSAGE_VECTOR_STORE_BACKEND",
|
|
40
|
-
"MEMORY_VECTOR_STORE_BACKEND",
|
|
41
|
-
"VECTOR_STORE_BACKEND"
|
|
42
|
-
];
|
|
43
|
+
var init_contracts = __esm({
|
|
44
|
+
"src/contracts.ts"() {
|
|
45
|
+
}
|
|
46
|
+
});
|
|
43
47
|
function getBackend(keys) {
|
|
44
48
|
for (const key of keys) {
|
|
45
49
|
const value = process.env[key]?.trim().toLowerCase();
|
|
@@ -142,21 +146,51 @@ async function searchRawMessagesWithChroma(input) {
|
|
|
142
146
|
metadata: result.metadata ?? {}
|
|
143
147
|
})).slice(0, input.limit);
|
|
144
148
|
}
|
|
149
|
+
var DEFAULT_RAW_MESSAGES_COLLECTION, RAW_VECTOR_BACKEND_ENV_KEYS;
|
|
150
|
+
var init_chroma_memory_index = __esm({
|
|
151
|
+
"src/storage/chroma-memory-index.ts"() {
|
|
152
|
+
init_contracts();
|
|
153
|
+
DEFAULT_RAW_MESSAGES_COLLECTION = "opencontext_raw_messages";
|
|
154
|
+
RAW_VECTOR_BACKEND_ENV_KEYS = [
|
|
155
|
+
"RAW_MESSAGE_VECTOR_STORE_BACKEND",
|
|
156
|
+
"MEMORY_VECTOR_STORE_BACKEND",
|
|
157
|
+
"VECTOR_STORE_BACKEND"
|
|
158
|
+
];
|
|
159
|
+
}
|
|
160
|
+
});
|
|
145
161
|
|
|
146
162
|
// src/storage/postgres-raw-message-factory.ts
|
|
147
|
-
var factory = null;
|
|
148
163
|
async function resolvePostgresFactory(env) {
|
|
149
|
-
return null;
|
|
164
|
+
if (!factory) return null;
|
|
165
|
+
return await factory(env);
|
|
150
166
|
}
|
|
151
167
|
function hasPostgresFactory() {
|
|
152
168
|
return factory !== null;
|
|
153
169
|
}
|
|
154
|
-
var
|
|
155
|
-
var
|
|
170
|
+
var factory;
|
|
171
|
+
var init_postgres_raw_message_factory = __esm({
|
|
172
|
+
"src/storage/postgres-raw-message-factory.ts"() {
|
|
173
|
+
factory = null;
|
|
174
|
+
}
|
|
175
|
+
});
|
|
176
|
+
|
|
177
|
+
// src/storage/sqlite-raw-message-store.ts
|
|
178
|
+
var sqlite_raw_message_store_exports = {};
|
|
179
|
+
__export(sqlite_raw_message_store_exports, {
|
|
180
|
+
__resetSQLiteRawMessageManagerForTests: () => __resetSQLiteRawMessageManagerForTests,
|
|
181
|
+
closeSQLiteRawMessageManager: () => closeSQLiteRawMessageManager,
|
|
182
|
+
getSQLiteRawMessageManager: () => getSQLiteRawMessageManager,
|
|
183
|
+
isSQLiteRawMessageStorageAvailable: () => isSQLiteRawMessageStorageAvailable,
|
|
184
|
+
lexicalSearchRawMessages: () => lexicalSearchRawMessages,
|
|
185
|
+
resolveSQLiteRawMessageDbPath: () => resolveSQLiteRawMessageDbPath
|
|
186
|
+
});
|
|
156
187
|
function resolveSQLiteRawMessageDbPath() {
|
|
157
188
|
const fromEnv = process.env.MEMORY_STORE_DB_PATH?.trim();
|
|
158
189
|
return fromEnv && fromEnv.length > 0 ? fromEnv : DEFAULT_SQLITE_PATH;
|
|
159
190
|
}
|
|
191
|
+
function isSQLiteRawMessageStorageAvailable(_env) {
|
|
192
|
+
return true;
|
|
193
|
+
}
|
|
160
194
|
async function getSQLiteRawMessageManager(_env) {
|
|
161
195
|
if (!manager) {
|
|
162
196
|
const dbPath = resolveSQLiteRawMessageDbPath();
|
|
@@ -176,8 +210,35 @@ async function closeSQLiteRawMessageManager() {
|
|
|
176
210
|
await manager.close();
|
|
177
211
|
manager = null;
|
|
178
212
|
}
|
|
213
|
+
function __resetSQLiteRawMessageManagerForTests() {
|
|
214
|
+
manager = null;
|
|
215
|
+
}
|
|
216
|
+
async function lexicalSearchRawMessages(input) {
|
|
217
|
+
const mgr = await getSQLiteRawMessageManager();
|
|
218
|
+
if (typeof mgr.lexicalSearchMessages !== "function") {
|
|
219
|
+
return [];
|
|
220
|
+
}
|
|
221
|
+
return mgr.lexicalSearchMessages(input);
|
|
222
|
+
}
|
|
223
|
+
var manager, DEFAULT_SQLITE_PATH;
|
|
224
|
+
var init_sqlite_raw_message_store = __esm({
|
|
225
|
+
"src/storage/sqlite-raw-message-store.ts"() {
|
|
226
|
+
init_chroma_memory_index();
|
|
227
|
+
manager = null;
|
|
228
|
+
DEFAULT_SQLITE_PATH = join(homedir(), ".opencontext", "memory", "store.db");
|
|
229
|
+
}
|
|
230
|
+
});
|
|
179
231
|
|
|
180
232
|
// src/storage/raw-message-store.ts
|
|
233
|
+
var raw_message_store_exports = {};
|
|
234
|
+
__export(raw_message_store_exports, {
|
|
235
|
+
closeRawMessageStore: () => closeRawMessageStore,
|
|
236
|
+
configureRawMessageStore: () => configureRawMessageStore,
|
|
237
|
+
createRawMessageStore: () => createRawMessageStore,
|
|
238
|
+
getRawMessageManager: () => getRawMessageManager,
|
|
239
|
+
getRawMessageStorageBackend: () => getRawMessageStorageBackend,
|
|
240
|
+
isRawMessageStorageAvailable: () => isRawMessageStorageAvailable
|
|
241
|
+
});
|
|
181
242
|
function resolveBackend(env) {
|
|
182
243
|
const explicit = process.env.OPENCONTEXT_MEMORY_STORE_BACKEND?.trim().toLowerCase();
|
|
183
244
|
if (explicit === "postgres") return "postgres";
|
|
@@ -187,7 +248,7 @@ function resolveEnv(_env) {
|
|
|
187
248
|
return {};
|
|
188
249
|
}
|
|
189
250
|
function createRawMessageStore(options = {}) {
|
|
190
|
-
resolveEnv(options.env);
|
|
251
|
+
const env = resolveEnv(options.env);
|
|
191
252
|
return {
|
|
192
253
|
getBackend() {
|
|
193
254
|
return resolveBackend();
|
|
@@ -197,7 +258,7 @@ function createRawMessageStore(options = {}) {
|
|
|
197
258
|
},
|
|
198
259
|
async getManager() {
|
|
199
260
|
if (resolveBackend() === "postgres") {
|
|
200
|
-
const pg = await resolvePostgresFactory();
|
|
261
|
+
const pg = await resolvePostgresFactory(env);
|
|
201
262
|
if (!pg) {
|
|
202
263
|
throw new Error(
|
|
203
264
|
"OPENCONTEXT_MEMORY_STORE_BACKEND=postgres but no Postgres factory is registered. Call registerPostgresFactory() at startup, or unset the env var to use the default SQLite backend."
|
|
@@ -212,14 +273,47 @@ function createRawMessageStore(options = {}) {
|
|
|
212
273
|
}
|
|
213
274
|
};
|
|
214
275
|
}
|
|
215
|
-
|
|
216
|
-
|
|
276
|
+
function configureRawMessageStore(config) {
|
|
277
|
+
moduleConfig = config;
|
|
278
|
+
moduleStore = createRawMessageStore({ env: config.env });
|
|
279
|
+
return moduleStore;
|
|
280
|
+
}
|
|
281
|
+
function getRawMessageStorageBackend() {
|
|
282
|
+
if (!moduleStore) {
|
|
283
|
+
moduleStore = createRawMessageStore({ env: moduleConfig?.env });
|
|
284
|
+
}
|
|
285
|
+
return moduleStore.getBackend();
|
|
286
|
+
}
|
|
217
287
|
function isRawMessageStorageAvailable() {
|
|
218
288
|
if (!moduleStore) {
|
|
219
289
|
moduleStore = createRawMessageStore({ env: moduleConfig?.env });
|
|
220
290
|
}
|
|
221
291
|
return moduleStore.isAvailable();
|
|
222
292
|
}
|
|
293
|
+
async function getRawMessageManager() {
|
|
294
|
+
if (!moduleStore) {
|
|
295
|
+
moduleStore = createRawMessageStore({ env: moduleConfig?.env });
|
|
296
|
+
}
|
|
297
|
+
return moduleStore.getManager();
|
|
298
|
+
}
|
|
299
|
+
async function closeRawMessageStore() {
|
|
300
|
+
if (!moduleStore) return;
|
|
301
|
+
await moduleStore.close();
|
|
302
|
+
moduleStore = null;
|
|
303
|
+
}
|
|
304
|
+
var moduleStore, moduleConfig;
|
|
305
|
+
var init_raw_message_store = __esm({
|
|
306
|
+
"src/storage/raw-message-store.ts"() {
|
|
307
|
+
init_postgres_raw_message_factory();
|
|
308
|
+
init_sqlite_raw_message_store();
|
|
309
|
+
moduleStore = null;
|
|
310
|
+
moduleConfig = null;
|
|
311
|
+
}
|
|
312
|
+
});
|
|
313
|
+
|
|
314
|
+
// src/search/unified-search.ts
|
|
315
|
+
init_chroma_memory_index();
|
|
316
|
+
init_raw_message_store();
|
|
223
317
|
|
|
224
318
|
// src/search/utilities.ts
|
|
225
319
|
var DEFAULT_LIMIT = 10;
|
|
@@ -249,7 +343,14 @@ function clampUnifiedMemorySearchThreshold(threshold) {
|
|
|
249
343
|
}
|
|
250
344
|
return Math.min(1, Math.max(-1, parsed));
|
|
251
345
|
}
|
|
252
|
-
function mergeUnifiedMemorySearchResults(results, limit) {
|
|
346
|
+
function mergeUnifiedMemorySearchResults(results, limit, options = {}) {
|
|
347
|
+
const strategy = normalizeUnifiedMemoryMergeStrategy(options.strategy);
|
|
348
|
+
if (strategy === "rrf") {
|
|
349
|
+
const lists = options.rankedLists ?? [
|
|
350
|
+
{ name: "memory-semantic", hits: results }
|
|
351
|
+
];
|
|
352
|
+
return mergeUnifiedMemorySearchResultsRrf(lists, limit);
|
|
353
|
+
}
|
|
253
354
|
return [...results].sort((a, b) => {
|
|
254
355
|
const scoreDelta = b.similarity - a.similarity;
|
|
255
356
|
if (scoreDelta !== 0) {
|
|
@@ -258,6 +359,42 @@ function mergeUnifiedMemorySearchResults(results, limit) {
|
|
|
258
359
|
return a.type.localeCompare(b.type) || a.id.localeCompare(b.id);
|
|
259
360
|
}).slice(0, limit);
|
|
260
361
|
}
|
|
362
|
+
function normalizeUnifiedMemoryMergeStrategy(value) {
|
|
363
|
+
return value === "rrf" ? "rrf" : "similarity";
|
|
364
|
+
}
|
|
365
|
+
var DEFAULT_RRF_K = 60;
|
|
366
|
+
function mergeUnifiedMemorySearchResultsRrf(lists, limit, k = DEFAULT_RRF_K) {
|
|
367
|
+
if (!Array.isArray(lists) || lists.length === 0) {
|
|
368
|
+
return [];
|
|
369
|
+
}
|
|
370
|
+
const safeK = Number.isFinite(k) && k > 0 ? k : DEFAULT_RRF_K;
|
|
371
|
+
const scores = /* @__PURE__ */ new Map();
|
|
372
|
+
const order = [];
|
|
373
|
+
for (const list of lists) {
|
|
374
|
+
for (let index = 0; index < list.hits.length; index += 1) {
|
|
375
|
+
const hit = list.hits[index];
|
|
376
|
+
const key = `${hit.type}::${hit.id}`;
|
|
377
|
+
const contribution = 1 / (safeK + index + 1);
|
|
378
|
+
const existing = scores.get(key);
|
|
379
|
+
if (existing) {
|
|
380
|
+
existing.rrf += contribution;
|
|
381
|
+
} else {
|
|
382
|
+
scores.set(key, { hit, rrf: contribution });
|
|
383
|
+
order.push(key);
|
|
384
|
+
}
|
|
385
|
+
}
|
|
386
|
+
}
|
|
387
|
+
const ranked = order.map((key) => scores.get(key)).sort((a, b) => {
|
|
388
|
+
if (b.rrf !== a.rrf) {
|
|
389
|
+
return b.rrf - a.rrf;
|
|
390
|
+
}
|
|
391
|
+
return a.hit.type.localeCompare(b.hit.type) || a.hit.id.localeCompare(b.hit.id);
|
|
392
|
+
}).slice(0, limit);
|
|
393
|
+
return ranked.map(({ hit, rrf }) => ({
|
|
394
|
+
...hit,
|
|
395
|
+
metadata: { ...hit.metadata, rrfScore: rrf }
|
|
396
|
+
}));
|
|
397
|
+
}
|
|
261
398
|
function toKnowledgeResult(result) {
|
|
262
399
|
return {
|
|
263
400
|
type: "knowledge",
|
|
@@ -289,6 +426,9 @@ function isRawMemorySemanticResult(result) {
|
|
|
289
426
|
}
|
|
290
427
|
|
|
291
428
|
// src/search/unified-search.ts
|
|
429
|
+
function deriveLexicalKeywords(query) {
|
|
430
|
+
return query.toLowerCase().split(/[^\p{L}\p{N}]+/u).filter((token) => token.length >= 2).slice(0, 16);
|
|
431
|
+
}
|
|
292
432
|
function createUnifiedSearch(deps = {}) {
|
|
293
433
|
const logger = deps.logger ?? console;
|
|
294
434
|
async function searchUnifiedMemory(input) {
|
|
@@ -296,8 +436,8 @@ function createUnifiedSearch(deps = {}) {
|
|
|
296
436
|
const sources = normalizeUnifiedMemorySearchSources(input.sources);
|
|
297
437
|
const limit = clampUnifiedMemorySearchLimit(input.limit);
|
|
298
438
|
const threshold = clampUnifiedMemorySearchThreshold(input.threshold);
|
|
439
|
+
const mergeStrategy = normalizeUnifiedMemoryMergeStrategy(input.mergeStrategy);
|
|
299
440
|
const warnings = [];
|
|
300
|
-
const results = [];
|
|
301
441
|
if (!query) {
|
|
302
442
|
return {
|
|
303
443
|
query,
|
|
@@ -307,17 +447,17 @@ function createUnifiedSearch(deps = {}) {
|
|
|
307
447
|
warnings
|
|
308
448
|
};
|
|
309
449
|
}
|
|
450
|
+
let memorySubs;
|
|
310
451
|
if (sources.includes("memory")) {
|
|
311
452
|
if (isRawMessageStorageAvailable()) {
|
|
312
453
|
try {
|
|
313
|
-
|
|
314
|
-
results.push(...memoryHits);
|
|
454
|
+
memorySubs = await runMemorySource(input, limit, threshold, warnings);
|
|
315
455
|
} catch (error) {
|
|
316
456
|
logger.warn?.("[memory-store] memory source failed:", error);
|
|
317
457
|
warnings.push({
|
|
318
458
|
source: "memory",
|
|
319
459
|
code: "memory_search_failed",
|
|
320
|
-
message: error.message ?? "
|
|
460
|
+
message: `Memory search failed: ${error.message ?? "Unknown error"}. Using keyword search fallback.`
|
|
321
461
|
});
|
|
322
462
|
}
|
|
323
463
|
} else {
|
|
@@ -328,9 +468,10 @@ function createUnifiedSearch(deps = {}) {
|
|
|
328
468
|
});
|
|
329
469
|
}
|
|
330
470
|
}
|
|
471
|
+
const insightHits = [];
|
|
331
472
|
if (sources.includes("insights") && typeof deps.searchInsights === "function") {
|
|
332
473
|
try {
|
|
333
|
-
const
|
|
474
|
+
const hits = await deps.searchInsights({
|
|
334
475
|
userId: input.userId,
|
|
335
476
|
query: input.query,
|
|
336
477
|
limit,
|
|
@@ -339,8 +480,8 @@ function createUnifiedSearch(deps = {}) {
|
|
|
339
480
|
includeArchived: input.includeArchivedInsights,
|
|
340
481
|
authToken: input.authToken
|
|
341
482
|
});
|
|
342
|
-
for (const hit of
|
|
343
|
-
|
|
483
|
+
for (const hit of hits) {
|
|
484
|
+
insightHits.push({
|
|
344
485
|
type: "insight",
|
|
345
486
|
id: hit.id,
|
|
346
487
|
content: hit.content,
|
|
@@ -356,16 +497,11 @@ function createUnifiedSearch(deps = {}) {
|
|
|
356
497
|
message: error.message ?? "insights_search_failed"
|
|
357
498
|
});
|
|
358
499
|
}
|
|
359
|
-
} else if (sources.includes("insights"))
|
|
360
|
-
|
|
361
|
-
source: "insights",
|
|
362
|
-
code: "insights_search_not_configured",
|
|
363
|
-
message: "Insights search is not configured for this host."
|
|
364
|
-
});
|
|
365
|
-
}
|
|
500
|
+
} else if (sources.includes("insights")) ;
|
|
501
|
+
const knowledgeHits = [];
|
|
366
502
|
if (sources.includes("knowledge") && typeof deps.searchKnowledge === "function") {
|
|
367
503
|
try {
|
|
368
|
-
const
|
|
504
|
+
const hits = await deps.searchKnowledge({
|
|
369
505
|
userId: input.userId,
|
|
370
506
|
query: input.query,
|
|
371
507
|
options: {
|
|
@@ -375,8 +511,8 @@ function createUnifiedSearch(deps = {}) {
|
|
|
375
511
|
},
|
|
376
512
|
authToken: input.authToken
|
|
377
513
|
});
|
|
378
|
-
for (const hit of
|
|
379
|
-
|
|
514
|
+
for (const hit of hits) {
|
|
515
|
+
knowledgeHits.push(toKnowledgeResult(hit));
|
|
380
516
|
}
|
|
381
517
|
} catch (error) {
|
|
382
518
|
logger.warn?.("[memory-store] knowledge source failed:", error);
|
|
@@ -386,14 +522,14 @@ function createUnifiedSearch(deps = {}) {
|
|
|
386
522
|
message: error.message ?? "knowledge_search_failed"
|
|
387
523
|
});
|
|
388
524
|
}
|
|
389
|
-
} else if (sources.includes("knowledge"))
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
525
|
+
} else if (sources.includes("knowledge")) ;
|
|
526
|
+
const merged = mergeAcrossSources({
|
|
527
|
+
memorySubs,
|
|
528
|
+
insightHits,
|
|
529
|
+
knowledgeHits,
|
|
530
|
+
limit,
|
|
531
|
+
strategy: mergeStrategy
|
|
532
|
+
});
|
|
397
533
|
return {
|
|
398
534
|
query: input.query,
|
|
399
535
|
sources,
|
|
@@ -402,9 +538,35 @@ function createUnifiedSearch(deps = {}) {
|
|
|
402
538
|
warnings
|
|
403
539
|
};
|
|
404
540
|
}
|
|
405
|
-
async function runMemorySource(input, limit, threshold) {
|
|
541
|
+
async function runMemorySource(input, limit, threshold, warnings) {
|
|
406
542
|
if (typeof deps.embedQuery !== "function") {
|
|
407
|
-
|
|
543
|
+
warnings.push({
|
|
544
|
+
source: "memory",
|
|
545
|
+
code: "memory_lexical_search_fallback",
|
|
546
|
+
message: "Semantic search not configured, using keyword search as fallback"
|
|
547
|
+
});
|
|
548
|
+
const { lexicalSearchRawMessages: lexicalSearchRawMessages2 } = await Promise.resolve().then(() => (init_sqlite_raw_message_store(), sqlite_raw_message_store_exports));
|
|
549
|
+
const keywords = deriveLexicalKeywords(input.query);
|
|
550
|
+
let lexical2 = [];
|
|
551
|
+
if (keywords.length > 0) {
|
|
552
|
+
try {
|
|
553
|
+
const filters2 = input.botIds && input.botIds.length > 0 ? input.botIds : [void 0];
|
|
554
|
+
const results = await Promise.all(
|
|
555
|
+
filters2.map(
|
|
556
|
+
(botId) => lexicalSearchRawMessages2({
|
|
557
|
+
userId: input.userId,
|
|
558
|
+
keywords,
|
|
559
|
+
limit: Math.ceil(limit / filters2.length),
|
|
560
|
+
botId
|
|
561
|
+
})
|
|
562
|
+
)
|
|
563
|
+
);
|
|
564
|
+
lexical2 = results.flat().filter(Boolean).map(toMemoryResult);
|
|
565
|
+
} catch (error) {
|
|
566
|
+
logger.warn?.("[memory-store] Default lexical search failed:", error);
|
|
567
|
+
}
|
|
568
|
+
}
|
|
569
|
+
return { semantic: [], lexical: lexical2 };
|
|
408
570
|
}
|
|
409
571
|
const queryEmbedding = await deps.embedQuery({
|
|
410
572
|
userId: input.userId,
|
|
@@ -412,9 +574,10 @@ function createUnifiedSearch(deps = {}) {
|
|
|
412
574
|
authToken: input.authToken
|
|
413
575
|
});
|
|
414
576
|
const filters = input.botIds && input.botIds.length > 0 ? input.botIds.map((botId) => ({ botId })) : [{}];
|
|
577
|
+
let semantic = [];
|
|
415
578
|
if (isRawMessageChromaEnabled()) {
|
|
416
579
|
try {
|
|
417
|
-
|
|
580
|
+
semantic = (await Promise.all(
|
|
418
581
|
filters.map((filter) => {
|
|
419
582
|
const botId = "botId" in filter ? filter.botId : void 0;
|
|
420
583
|
return searchRawMessagesWithChroma({
|
|
@@ -429,18 +592,16 @@ function createUnifiedSearch(deps = {}) {
|
|
|
429
592
|
logger.log?.("[memory-store] Raw message semantic search completed", {
|
|
430
593
|
backend: "chroma",
|
|
431
594
|
dimensions: queryEmbedding.length,
|
|
432
|
-
count:
|
|
595
|
+
count: semantic.length
|
|
433
596
|
});
|
|
434
|
-
return chromaHits;
|
|
435
597
|
} catch (error) {
|
|
436
598
|
logger.warn?.(
|
|
437
599
|
"[memory-store] Chroma raw message search failed; falling back to database search:",
|
|
438
600
|
error
|
|
439
601
|
);
|
|
440
602
|
}
|
|
441
|
-
}
|
|
442
|
-
|
|
443
|
-
const rows = (await Promise.all(
|
|
603
|
+
} else if (typeof deps.searchRawMessagesAnn === "function") {
|
|
604
|
+
semantic = (await Promise.all(
|
|
444
605
|
filters.map(
|
|
445
606
|
(filter) => deps.searchRawMessagesAnn({
|
|
446
607
|
userId: input.userId,
|
|
@@ -451,19 +612,113 @@ function createUnifiedSearch(deps = {}) {
|
|
|
451
612
|
})
|
|
452
613
|
)
|
|
453
614
|
)).flat().filter(isRawMemorySemanticResult).map(toMemoryResult);
|
|
454
|
-
return rows;
|
|
455
615
|
}
|
|
456
|
-
|
|
616
|
+
if (semantic.length === 0) {
|
|
617
|
+
try {
|
|
618
|
+
const { getRawMessageManager: getRawMessageManager2 } = await Promise.resolve().then(() => (init_raw_message_store(), raw_message_store_exports));
|
|
619
|
+
const manager2 = await getRawMessageManager2();
|
|
620
|
+
if (typeof manager2.searchMessagesSemantically === "function") {
|
|
621
|
+
const results = await manager2.searchMessagesSemantically({
|
|
622
|
+
userId: input.userId,
|
|
623
|
+
queryEmbedding,
|
|
624
|
+
limit
|
|
625
|
+
});
|
|
626
|
+
semantic = results.map((r) => ({
|
|
627
|
+
type: "memory",
|
|
628
|
+
id: r.id,
|
|
629
|
+
content: r.content,
|
|
630
|
+
similarity: r.similarity,
|
|
631
|
+
metadata: r.metadata ?? {}
|
|
632
|
+
}));
|
|
633
|
+
}
|
|
634
|
+
} catch (error) {
|
|
635
|
+
logger.warn?.("[memory-store] SQLite semantic search failed:", error);
|
|
636
|
+
}
|
|
637
|
+
}
|
|
638
|
+
let lexical = [];
|
|
639
|
+
if (typeof deps.searchRawMessagesLexical === "function") {
|
|
640
|
+
const keywords = deriveLexicalKeywords(input.query);
|
|
641
|
+
if (keywords.length > 0) {
|
|
642
|
+
try {
|
|
643
|
+
const lexFilters = input.botIds && input.botIds.length > 0 ? input.botIds : [void 0];
|
|
644
|
+
lexical = (await Promise.all(
|
|
645
|
+
lexFilters.map(
|
|
646
|
+
(botId) => deps.searchRawMessagesLexical({
|
|
647
|
+
userId: input.userId,
|
|
648
|
+
keywords,
|
|
649
|
+
limit,
|
|
650
|
+
botId
|
|
651
|
+
})
|
|
652
|
+
)
|
|
653
|
+
)).flat().filter(isRawMemorySemanticResult).map((hit) => ({
|
|
654
|
+
...toMemoryResult(hit),
|
|
655
|
+
metadata: { ...hit.metadata, scoring: "bm25" }
|
|
656
|
+
}));
|
|
657
|
+
} catch (error) {
|
|
658
|
+
logger.warn?.("[memory-store] lexical memory search failed:", error);
|
|
659
|
+
warnings.push({
|
|
660
|
+
source: "memory",
|
|
661
|
+
code: "memory_lexical_search_failed",
|
|
662
|
+
message: error.message ?? "memory_lexical_search_failed"
|
|
663
|
+
});
|
|
664
|
+
}
|
|
665
|
+
}
|
|
666
|
+
} else if (input.mergeStrategy === "rrf") {
|
|
667
|
+
warnings.push({
|
|
668
|
+
source: "memory",
|
|
669
|
+
code: "memory_lexical_search_not_configured",
|
|
670
|
+
message: "RRF merge requested but lexical search is not configured; falling back to semantic-only RRF."
|
|
671
|
+
});
|
|
672
|
+
}
|
|
673
|
+
return { semantic, lexical };
|
|
457
674
|
}
|
|
458
675
|
async function searchRawMemorySemantically(input) {
|
|
459
676
|
const limit = clampUnifiedMemorySearchLimit(input.limit);
|
|
460
677
|
const threshold = clampUnifiedMemorySearchThreshold(input.threshold);
|
|
461
|
-
|
|
678
|
+
const sub = await runMemorySource(input, limit, threshold, []);
|
|
679
|
+
return mergeAcrossSources({
|
|
680
|
+
memorySubs: sub,
|
|
681
|
+
insightHits: [],
|
|
682
|
+
knowledgeHits: [],
|
|
683
|
+
limit,
|
|
684
|
+
strategy: normalizeUnifiedMemoryMergeStrategy(input.mergeStrategy)
|
|
685
|
+
});
|
|
462
686
|
}
|
|
463
687
|
return { searchUnifiedMemory, searchRawMemorySemantically };
|
|
464
688
|
}
|
|
689
|
+
function mergeAcrossSources(input) {
|
|
690
|
+
const memorySubs = input.memorySubs ?? { semantic: [], lexical: [] };
|
|
691
|
+
const all = [
|
|
692
|
+
...memorySubs.semantic,
|
|
693
|
+
...memorySubs.lexical,
|
|
694
|
+
...input.insightHits,
|
|
695
|
+
...input.knowledgeHits
|
|
696
|
+
];
|
|
697
|
+
if (input.strategy !== "rrf") {
|
|
698
|
+
return mergeUnifiedMemorySearchResults(all, input.limit);
|
|
699
|
+
}
|
|
700
|
+
const lists = [];
|
|
701
|
+
if (memorySubs.semantic.length > 0) {
|
|
702
|
+
lists.push({ name: "memory-semantic", hits: memorySubs.semantic });
|
|
703
|
+
}
|
|
704
|
+
if (memorySubs.lexical.length > 0) {
|
|
705
|
+
lists.push({ name: "memory-bm25", hits: memorySubs.lexical });
|
|
706
|
+
}
|
|
707
|
+
if (input.insightHits.length > 0) {
|
|
708
|
+
lists.push({ name: "insights", hits: input.insightHits });
|
|
709
|
+
}
|
|
710
|
+
if (input.knowledgeHits.length > 0) {
|
|
711
|
+
lists.push({ name: "knowledge", hits: input.knowledgeHits });
|
|
712
|
+
}
|
|
713
|
+
if (lists.length <= 1) {
|
|
714
|
+
return mergeUnifiedMemorySearchResults(all, input.limit);
|
|
715
|
+
}
|
|
716
|
+
return mergeUnifiedMemorySearchResultsRrf(lists, input.limit);
|
|
717
|
+
}
|
|
465
718
|
|
|
466
719
|
// src/mcp.ts
|
|
720
|
+
init_chroma_memory_index();
|
|
721
|
+
init_raw_message_store();
|
|
467
722
|
var DEFAULT_NAME = "@melandlabs/memory-store";
|
|
468
723
|
var DEFAULT_VERSION = "0.1.0";
|
|
469
724
|
async function embedMissingMessages(messages, deps) {
|
|
@@ -623,5 +878,3 @@ async function startMcpServer(options = {}) {
|
|
|
623
878
|
}
|
|
624
879
|
|
|
625
880
|
export { startMcpServer };
|
|
626
|
-
//# sourceMappingURL=mcp.js.map
|
|
627
|
-
//# sourceMappingURL=mcp.js.map
|
|
@@ -74,5 +74,3 @@ function resolveUntrustedRawMemoryGraphWritePolicy() {
|
|
|
74
74
|
}
|
|
75
75
|
|
|
76
76
|
export { CHAT_MEMORY_EVIDENCE_ID_PREFIX, MEMORY_GRAPH_SUMMARY_ID_PREFIXES, isReservedChatMemoryEvidenceId, isReservedMemoryGraphSummaryId, resolveAllowlistedMemoryGraphPolicy, resolveMemoryGraphWritePolicy, resolveUntrustedRawMemoryGraphWritePolicy, sanitizeUntrustedMemoryMetadata };
|
|
77
|
-
//# sourceMappingURL=memory-graph-write-policy.js.map
|
|
78
|
-
//# sourceMappingURL=memory-graph-write-policy.js.map
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { d as UnifiedSearchDeps } from '../config-
|
|
1
|
+
import { d as UnifiedSearchDeps } from '../config-BYfTzG3h.js';
|
|
2
2
|
|
|
3
3
|
/**
|
|
4
4
|
* Pure helpers used by the unified memory search pipeline.
|
|
@@ -23,6 +23,30 @@ interface UnifiedMemorySearchInput {
|
|
|
23
23
|
includeArchivedInsights?: boolean;
|
|
24
24
|
botIds?: string[];
|
|
25
25
|
documentIds?: string[];
|
|
26
|
+
/**
|
|
27
|
+
* Optional ISO-8601 timestamp. When set, graph-aware retrieval and other
|
|
28
|
+
* snapshot consumers drop nodes / edges whose `applicability.validFrom` /
|
|
29
|
+
* `validUntil` window is closed at that instant. Backward-compatible:
|
|
30
|
+
* omitted `asOf` keeps the legacy behaviour exactly.
|
|
31
|
+
*/
|
|
32
|
+
asOf?: string;
|
|
33
|
+
/**
|
|
34
|
+
* How to merge per-source result lists. Defaults to `"similarity"` —
|
|
35
|
+
* the legacy global sort by similarity, then type/id tie-break. The
|
|
36
|
+
* alternate value `"rrf"` invokes reciprocal-rank fusion across the
|
|
37
|
+
* lists fed via the (internal) `rankedLists` option.
|
|
38
|
+
*/
|
|
39
|
+
mergeStrategy?: UnifiedMemoryMergeStrategy;
|
|
40
|
+
}
|
|
41
|
+
type UnifiedMemoryMergeStrategy = "similarity" | "rrf";
|
|
42
|
+
/**
|
|
43
|
+
* A single ranked list from one retrieval channel. Used by the RRF merge to
|
|
44
|
+
* combine multiple signals (semantic + lexical + insights + knowledge) without
|
|
45
|
+
* flattening them into a single similarity score first.
|
|
46
|
+
*/
|
|
47
|
+
interface UnifiedMemoryRankedList {
|
|
48
|
+
name: "memory-bm25" | "memory-semantic" | "insights" | "knowledge";
|
|
49
|
+
hits: UnifiedMemorySearchResult[];
|
|
26
50
|
}
|
|
27
51
|
interface UnifiedMemorySearchResult {
|
|
28
52
|
type: "memory" | "insight" | "knowledge";
|
|
@@ -41,7 +65,19 @@ interface UnifiedMemorySearchOutput {
|
|
|
41
65
|
declare function normalizeUnifiedMemorySearchSources(sources: unknown): UnifiedMemorySearchSource[];
|
|
42
66
|
declare function clampUnifiedMemorySearchLimit(limit: unknown): number;
|
|
43
67
|
declare function clampUnifiedMemorySearchThreshold(threshold: unknown): number;
|
|
44
|
-
declare function mergeUnifiedMemorySearchResults(results: UnifiedMemorySearchResult[], limit: number
|
|
68
|
+
declare function mergeUnifiedMemorySearchResults(results: UnifiedMemorySearchResult[], limit: number, options?: {
|
|
69
|
+
strategy?: UnifiedMemoryMergeStrategy;
|
|
70
|
+
rankedLists?: UnifiedMemoryRankedList[];
|
|
71
|
+
}): UnifiedMemorySearchResult[];
|
|
72
|
+
declare function normalizeUnifiedMemoryMergeStrategy(value: unknown): UnifiedMemoryMergeStrategy;
|
|
73
|
+
/**
|
|
74
|
+
* Reciprocal Rank Fusion across multiple ranked lists. Each list contributes
|
|
75
|
+
* `1 / (k + rank + 1)` to a hit's accumulated score. Hits are de-duplicated by
|
|
76
|
+
* `(type, id)` so the same message surfaced by both lexical and semantic
|
|
77
|
+
* channels naturally outranks single-channel matches. Tie-break is
|
|
78
|
+
* deterministic: descending RRF score, then `(type, id)` lexical.
|
|
79
|
+
*/
|
|
80
|
+
declare function mergeUnifiedMemorySearchResultsRrf(lists: UnifiedMemoryRankedList[], limit: number, k?: number): UnifiedMemorySearchResult[];
|
|
45
81
|
declare function toKnowledgeResult(result: {
|
|
46
82
|
chunkId: string;
|
|
47
83
|
documentId: string;
|
|
@@ -78,4 +114,4 @@ interface UnifiedSearch {
|
|
|
78
114
|
}
|
|
79
115
|
declare function createUnifiedSearch(deps?: UnifiedSearchDeps): UnifiedSearch;
|
|
80
116
|
|
|
81
|
-
export { type UnifiedMemorySearchInput, type UnifiedMemorySearchOutput, type UnifiedMemorySearchResult, type UnifiedMemorySearchSource, type UnifiedMemorySearchWarning, type UnifiedSearch, clampUnifiedMemorySearchLimit, clampUnifiedMemorySearchThreshold, createUnifiedSearch, isRawMemorySemanticResult, mergeUnifiedMemorySearchResults, normalizeUnifiedMemorySearchSources, toKnowledgeResult, toMemoryResult };
|
|
117
|
+
export { type UnifiedMemoryMergeStrategy, type UnifiedMemoryRankedList, type UnifiedMemorySearchInput, type UnifiedMemorySearchOutput, type UnifiedMemorySearchResult, type UnifiedMemorySearchSource, type UnifiedMemorySearchWarning, type UnifiedSearch, clampUnifiedMemorySearchLimit, clampUnifiedMemorySearchThreshold, createUnifiedSearch, isRawMemorySemanticResult, mergeUnifiedMemorySearchResults, mergeUnifiedMemorySearchResultsRrf, normalizeUnifiedMemoryMergeStrategy, normalizeUnifiedMemorySearchSources, toKnowledgeResult, toMemoryResult };
|