@melandlabs/memory-store 0.2.6 → 0.3.0
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-2Q092MUz.d.ts +470 -0
- package/dist/http.d.ts +1 -1
- package/dist/http.js +386 -105
- package/dist/index.d.ts +3 -3
- package/dist/index.js +745 -107
- package/dist/mcp.d.ts +1 -1
- package/dist/mcp.js +389 -105
- package/dist/search/unified-search.d.ts +3 -101
- package/dist/search/unified-search.js +387 -106
- package/dist/server/cli-http.js +395 -105
- package/dist/server/cli-mcp.js +398 -105
- package/dist/server/cli-shared.d.ts +1 -1
- package/dist/server/cli-shared.js +17 -5
- package/dist/storage/postgres-raw-message-factory.d.ts +1 -1
- package/dist/storage/raw-message-store.d.ts +3 -1
- package/dist/storage/raw-message-store.js +20 -9
- package/dist/storage/sqlite-raw-message-store.d.ts +6 -3
- package/dist/storage/sqlite-raw-message-store.js +4 -3
- package/dist/storage/sqlite-vector-index.d.ts +1 -1
- package/dist/storage/sqlite-vector-index.js +1 -1
- package/package.json +10 -9
- package/dist/config-BYfTzG3h.d.ts +0 -170
package/dist/index.js
CHANGED
|
@@ -114,16 +114,17 @@ __export(sqlite_raw_message_store_exports, {
|
|
|
114
114
|
lexicalSearchRawMessages: () => lexicalSearchRawMessages,
|
|
115
115
|
resolveSQLiteRawMessageDbPath: () => resolveSQLiteRawMessageDbPath
|
|
116
116
|
});
|
|
117
|
-
function resolveSQLiteRawMessageDbPath() {
|
|
117
|
+
function resolveSQLiteRawMessageDbPath(dbPath) {
|
|
118
|
+
if (dbPath && dbPath.length > 0) return dbPath;
|
|
118
119
|
const fromEnv = process.env.MEMORY_STORE_DB_PATH?.trim();
|
|
119
120
|
return fromEnv && fromEnv.length > 0 ? fromEnv : DEFAULT_SQLITE_PATH;
|
|
120
121
|
}
|
|
121
122
|
function isSQLiteRawMessageStorageAvailable(_env) {
|
|
122
123
|
return true;
|
|
123
124
|
}
|
|
124
|
-
async function getSQLiteRawMessageManager(
|
|
125
|
+
async function getSQLiteRawMessageManager(options = {}) {
|
|
125
126
|
if (!manager) {
|
|
126
|
-
const dbPath = resolveSQLiteRawMessageDbPath();
|
|
127
|
+
const dbPath = resolveSQLiteRawMessageDbPath(options.dbPath);
|
|
127
128
|
mkdirSync(dirname(dbPath), { recursive: true });
|
|
128
129
|
manager = new SQLiteRawMessageManager({
|
|
129
130
|
dbPath,
|
|
@@ -169,7 +170,7 @@ __export(raw_message_store_exports, {
|
|
|
169
170
|
getRawMessageStorageBackend: () => getRawMessageStorageBackend,
|
|
170
171
|
isRawMessageStorageAvailable: () => isRawMessageStorageAvailable
|
|
171
172
|
});
|
|
172
|
-
function resolveBackend(
|
|
173
|
+
function resolveBackend(_env) {
|
|
173
174
|
const explicit = process.env.OPENCONTEXT_MEMORY_STORE_BACKEND?.trim().toLowerCase();
|
|
174
175
|
if (explicit === "postgres") return "postgres";
|
|
175
176
|
return "sqlite";
|
|
@@ -196,33 +197,43 @@ function createRawMessageStore(options = {}) {
|
|
|
196
197
|
}
|
|
197
198
|
return pg;
|
|
198
199
|
}
|
|
199
|
-
return await getSQLiteRawMessageManager(
|
|
200
|
+
return await getSQLiteRawMessageManager({
|
|
201
|
+
dbPath: options.dbPath
|
|
202
|
+
});
|
|
200
203
|
},
|
|
201
204
|
async close() {
|
|
202
205
|
await closeSQLiteRawMessageManager();
|
|
203
206
|
}
|
|
204
207
|
};
|
|
205
208
|
}
|
|
209
|
+
function resolveDbPath(config) {
|
|
210
|
+
if (!config) return void 0;
|
|
211
|
+
if (config.dbPath) return config.dbPath;
|
|
212
|
+
if (config.db && typeof config.db === "object" && "path" in config.db) {
|
|
213
|
+
return config.db.path;
|
|
214
|
+
}
|
|
215
|
+
return void 0;
|
|
216
|
+
}
|
|
206
217
|
function configureRawMessageStore(config) {
|
|
207
218
|
moduleConfig = config;
|
|
208
|
-
moduleStore = createRawMessageStore({ env: config.env });
|
|
219
|
+
moduleStore = createRawMessageStore({ env: config.env, dbPath: resolveDbPath(config) });
|
|
209
220
|
return moduleStore;
|
|
210
221
|
}
|
|
211
222
|
function getRawMessageStorageBackend() {
|
|
212
223
|
if (!moduleStore) {
|
|
213
|
-
moduleStore = createRawMessageStore({ env: moduleConfig?.env });
|
|
224
|
+
moduleStore = createRawMessageStore({ env: moduleConfig?.env, dbPath: resolveDbPath(moduleConfig) });
|
|
214
225
|
}
|
|
215
226
|
return moduleStore.getBackend();
|
|
216
227
|
}
|
|
217
228
|
function isRawMessageStorageAvailable() {
|
|
218
229
|
if (!moduleStore) {
|
|
219
|
-
moduleStore = createRawMessageStore({ env: moduleConfig?.env });
|
|
230
|
+
moduleStore = createRawMessageStore({ env: moduleConfig?.env, dbPath: resolveDbPath(moduleConfig) });
|
|
220
231
|
}
|
|
221
232
|
return moduleStore.isAvailable();
|
|
222
233
|
}
|
|
223
234
|
async function getRawMessageManager() {
|
|
224
235
|
if (!moduleStore) {
|
|
225
|
-
moduleStore = createRawMessageStore({ env: moduleConfig?.env });
|
|
236
|
+
moduleStore = createRawMessageStore({ env: moduleConfig?.env, dbPath: resolveDbPath(moduleConfig) });
|
|
226
237
|
}
|
|
227
238
|
return moduleStore.getManager();
|
|
228
239
|
}
|
|
@@ -292,6 +303,12 @@ function mergeUnifiedMemorySearchResults(results, limit, options = {}) {
|
|
|
292
303
|
function normalizeUnifiedMemoryMergeStrategy(value) {
|
|
293
304
|
return value === "rrf" ? "rrf" : "similarity";
|
|
294
305
|
}
|
|
306
|
+
function normalizeUnifiedMemoryReasoningStrategy(value, defaultStrategy = "none") {
|
|
307
|
+
if (value === "rewrite" || value === "iterative") {
|
|
308
|
+
return value;
|
|
309
|
+
}
|
|
310
|
+
return defaultStrategy;
|
|
311
|
+
}
|
|
295
312
|
var DEFAULT_RRF_K = 60;
|
|
296
313
|
function mergeUnifiedMemorySearchResultsRrf(lists, limit, k = DEFAULT_RRF_K) {
|
|
297
314
|
if (!Array.isArray(lists) || lists.length === 0) {
|
|
@@ -314,7 +331,13 @@ function mergeUnifiedMemorySearchResultsRrf(lists, limit, k = DEFAULT_RRF_K) {
|
|
|
314
331
|
}
|
|
315
332
|
}
|
|
316
333
|
}
|
|
317
|
-
const ranked = order.map((key) =>
|
|
334
|
+
const ranked = order.map((key) => {
|
|
335
|
+
const entry = scores.get(key);
|
|
336
|
+
if (!entry) {
|
|
337
|
+
throw new Error(`Invariant violation: missing RRF score for key "${key}"`);
|
|
338
|
+
}
|
|
339
|
+
return entry;
|
|
340
|
+
}).sort((a, b) => {
|
|
318
341
|
if (b.rrf !== a.rrf) {
|
|
319
342
|
return b.rrf - a.rrf;
|
|
320
343
|
}
|
|
@@ -359,14 +382,222 @@ function isRawMemorySemanticResult(result) {
|
|
|
359
382
|
function deriveLexicalKeywords(query) {
|
|
360
383
|
return query.toLowerCase().split(/[^\p{L}\p{N}]+/u).filter((token) => token.length >= 2).slice(0, 16);
|
|
361
384
|
}
|
|
385
|
+
function parseDateBoundary(value, role) {
|
|
386
|
+
const trimmed = value.trim();
|
|
387
|
+
if (!trimmed) {
|
|
388
|
+
return void 0;
|
|
389
|
+
}
|
|
390
|
+
const dateOnlyMatch = trimmed.match(/^(\d{4})-(\d{2})-(\d{2})$/);
|
|
391
|
+
if (dateOnlyMatch) {
|
|
392
|
+
const year = Number.parseInt(dateOnlyMatch[1], 10);
|
|
393
|
+
const month = Number.parseInt(dateOnlyMatch[2], 10) - 1;
|
|
394
|
+
const day = Number.parseInt(dateOnlyMatch[3], 10);
|
|
395
|
+
if (role === "from") {
|
|
396
|
+
return Date.UTC(year, month, day);
|
|
397
|
+
}
|
|
398
|
+
return Date.UTC(year, month, day + 1) - 1;
|
|
399
|
+
}
|
|
400
|
+
const parsed = Date.parse(trimmed);
|
|
401
|
+
if (Number.isNaN(parsed)) {
|
|
402
|
+
return void 0;
|
|
403
|
+
}
|
|
404
|
+
return parsed;
|
|
405
|
+
}
|
|
406
|
+
function getCandidateTimestamp(metadata) {
|
|
407
|
+
for (const key of ["timestamp", "createdAt", "time"]) {
|
|
408
|
+
const value = metadata[key];
|
|
409
|
+
if (typeof value === "number") {
|
|
410
|
+
return value;
|
|
411
|
+
}
|
|
412
|
+
if (typeof value === "string") {
|
|
413
|
+
const parsed = Date.parse(value);
|
|
414
|
+
if (!Number.isNaN(parsed)) {
|
|
415
|
+
return parsed;
|
|
416
|
+
}
|
|
417
|
+
}
|
|
418
|
+
}
|
|
419
|
+
return void 0;
|
|
420
|
+
}
|
|
421
|
+
function filterByDateRange(items, dateFrom, dateTo) {
|
|
422
|
+
if (!dateFrom && !dateTo) {
|
|
423
|
+
return items;
|
|
424
|
+
}
|
|
425
|
+
const fromMs = dateFrom ? parseDateBoundary(dateFrom, "from") : void 0;
|
|
426
|
+
const toMs = dateTo ? parseDateBoundary(dateTo, "to") : void 0;
|
|
427
|
+
if (fromMs === void 0 && toMs === void 0) {
|
|
428
|
+
return items;
|
|
429
|
+
}
|
|
430
|
+
return items.filter((item) => {
|
|
431
|
+
const ts = getCandidateTimestamp(item.metadata);
|
|
432
|
+
if (ts === void 0) {
|
|
433
|
+
return true;
|
|
434
|
+
}
|
|
435
|
+
if (fromMs !== void 0 && ts < fromMs) {
|
|
436
|
+
return false;
|
|
437
|
+
}
|
|
438
|
+
if (toMs !== void 0 && ts > toMs) {
|
|
439
|
+
return false;
|
|
440
|
+
}
|
|
441
|
+
return true;
|
|
442
|
+
});
|
|
443
|
+
}
|
|
444
|
+
async function embedQueryVariant(embedQuery, input, query) {
|
|
445
|
+
return embedQuery({
|
|
446
|
+
userId: input.userId,
|
|
447
|
+
query,
|
|
448
|
+
authToken: input.authToken
|
|
449
|
+
});
|
|
450
|
+
}
|
|
451
|
+
async function runSemanticSearchForEmbedding(deps, input, queryEmbedding, limit, threshold, logger) {
|
|
452
|
+
const filters = input.botIds && input.botIds.length > 0 ? input.botIds.map((botId) => ({ botId })) : [{}];
|
|
453
|
+
let semantic = [];
|
|
454
|
+
if (isRawMessageChromaEnabled()) {
|
|
455
|
+
try {
|
|
456
|
+
semantic = (await Promise.all(
|
|
457
|
+
filters.map((filter) => {
|
|
458
|
+
const botId = "botId" in filter ? filter.botId : void 0;
|
|
459
|
+
return searchRawMessagesWithChroma({
|
|
460
|
+
userId: input.userId,
|
|
461
|
+
queryEmbedding,
|
|
462
|
+
limit,
|
|
463
|
+
threshold,
|
|
464
|
+
botId
|
|
465
|
+
});
|
|
466
|
+
})
|
|
467
|
+
)).flat().map(toMemoryResult);
|
|
468
|
+
logger.log?.("[memory-store] Raw message semantic search completed", {
|
|
469
|
+
backend: "chroma",
|
|
470
|
+
dimensions: queryEmbedding.length,
|
|
471
|
+
count: semantic.length
|
|
472
|
+
});
|
|
473
|
+
} catch (error) {
|
|
474
|
+
logger.warn?.(
|
|
475
|
+
"[memory-store] Chroma raw message search failed; falling back to database search:",
|
|
476
|
+
error
|
|
477
|
+
);
|
|
478
|
+
}
|
|
479
|
+
} else if (typeof deps.searchRawMessagesAnn === "function") {
|
|
480
|
+
const searchRawMessagesAnn = deps.searchRawMessagesAnn;
|
|
481
|
+
semantic = (await Promise.all(
|
|
482
|
+
filters.map(
|
|
483
|
+
(filter) => searchRawMessagesAnn({
|
|
484
|
+
userId: input.userId,
|
|
485
|
+
queryEmbedding,
|
|
486
|
+
limit,
|
|
487
|
+
threshold,
|
|
488
|
+
botId: "botId" in filter ? filter.botId : void 0
|
|
489
|
+
})
|
|
490
|
+
)
|
|
491
|
+
)).flat().filter(isRawMemorySemanticResult).map(toMemoryResult);
|
|
492
|
+
}
|
|
493
|
+
if (semantic.length === 0) {
|
|
494
|
+
try {
|
|
495
|
+
const { getRawMessageManager: getRawMessageManager2 } = await Promise.resolve().then(() => (init_raw_message_store(), raw_message_store_exports));
|
|
496
|
+
const manager2 = await getRawMessageManager2();
|
|
497
|
+
if (typeof manager2.searchMessagesSemantically === "function") {
|
|
498
|
+
const results = await manager2.searchMessagesSemantically({
|
|
499
|
+
userId: input.userId,
|
|
500
|
+
queryEmbedding,
|
|
501
|
+
limit,
|
|
502
|
+
threshold
|
|
503
|
+
});
|
|
504
|
+
semantic = results.map((r) => ({
|
|
505
|
+
type: "memory",
|
|
506
|
+
id: r.id,
|
|
507
|
+
content: r.content,
|
|
508
|
+
similarity: r.similarity,
|
|
509
|
+
metadata: r.metadata ?? {}
|
|
510
|
+
}));
|
|
511
|
+
}
|
|
512
|
+
} catch (error) {
|
|
513
|
+
logger.warn?.("[memory-store] SQLite semantic search failed:", error);
|
|
514
|
+
}
|
|
515
|
+
}
|
|
516
|
+
return semantic;
|
|
517
|
+
}
|
|
518
|
+
function mergeByMaxScore(lists) {
|
|
519
|
+
const best = /* @__PURE__ */ new Map();
|
|
520
|
+
for (const list of lists) {
|
|
521
|
+
for (const hit of list) {
|
|
522
|
+
const existing = best.get(hit.id);
|
|
523
|
+
if (!existing || hit.similarity > existing.similarity) {
|
|
524
|
+
best.set(hit.id, hit);
|
|
525
|
+
}
|
|
526
|
+
}
|
|
527
|
+
}
|
|
528
|
+
return Array.from(best.values()).sort((a, b) => b.similarity - a.similarity);
|
|
529
|
+
}
|
|
530
|
+
function toIterativeRecallCandidate(hit) {
|
|
531
|
+
return {
|
|
532
|
+
id: hit.id,
|
|
533
|
+
content: hit.content,
|
|
534
|
+
similarity: hit.similarity,
|
|
535
|
+
metadata: hit.metadata
|
|
536
|
+
};
|
|
537
|
+
}
|
|
538
|
+
async function runLexicalSearchForKeywords(deps, input, keywords, limit, logger) {
|
|
539
|
+
if (keywords.length === 0) {
|
|
540
|
+
return [];
|
|
541
|
+
}
|
|
542
|
+
if (typeof deps.searchRawMessagesLexical === "function") {
|
|
543
|
+
try {
|
|
544
|
+
const filters = input.botIds && input.botIds.length > 0 ? input.botIds : [void 0];
|
|
545
|
+
const searchRawMessagesLexical = deps.searchRawMessagesLexical;
|
|
546
|
+
return (await Promise.all(
|
|
547
|
+
filters.map(
|
|
548
|
+
(botId) => searchRawMessagesLexical({
|
|
549
|
+
userId: input.userId,
|
|
550
|
+
keywords,
|
|
551
|
+
limit: Math.ceil(limit / filters.length),
|
|
552
|
+
botId
|
|
553
|
+
})
|
|
554
|
+
)
|
|
555
|
+
)).flat().filter(isRawMemorySemanticResult).map((hit) => ({
|
|
556
|
+
...toMemoryResult(hit),
|
|
557
|
+
metadata: { ...hit.metadata, scoring: "bm25" }
|
|
558
|
+
}));
|
|
559
|
+
} catch (error) {
|
|
560
|
+
logger.warn?.("[memory-store] lexical memory search failed:", error);
|
|
561
|
+
}
|
|
562
|
+
}
|
|
563
|
+
try {
|
|
564
|
+
const { lexicalSearchRawMessages: lexicalSearchRawMessages2 } = await Promise.resolve().then(() => (init_sqlite_raw_message_store(), sqlite_raw_message_store_exports));
|
|
565
|
+
const filters = input.botIds && input.botIds.length > 0 ? input.botIds : [void 0];
|
|
566
|
+
return (await Promise.all(
|
|
567
|
+
filters.map(
|
|
568
|
+
(botId) => lexicalSearchRawMessages2({
|
|
569
|
+
userId: input.userId,
|
|
570
|
+
keywords,
|
|
571
|
+
limit: Math.ceil(limit / filters.length),
|
|
572
|
+
botId
|
|
573
|
+
})
|
|
574
|
+
)
|
|
575
|
+
)).flat().filter(Boolean).map(
|
|
576
|
+
(r) => toMemoryResult(
|
|
577
|
+
r
|
|
578
|
+
)
|
|
579
|
+
);
|
|
580
|
+
} catch (error) {
|
|
581
|
+
logger.warn?.("[memory-store] SQLite lexical search failed:", error);
|
|
582
|
+
}
|
|
583
|
+
return [];
|
|
584
|
+
}
|
|
362
585
|
function createUnifiedSearch(deps = {}) {
|
|
363
586
|
const logger = deps.logger ?? console;
|
|
587
|
+
function resolveReasoningStrategy(input) {
|
|
588
|
+
const requested = normalizeUnifiedMemoryReasoningStrategy(input.reasoningStrategy);
|
|
589
|
+
if (requested !== "none") {
|
|
590
|
+
return requested;
|
|
591
|
+
}
|
|
592
|
+
return normalizeUnifiedMemoryReasoningStrategy(deps.reasoning?.defaultStrategy, "none");
|
|
593
|
+
}
|
|
364
594
|
async function searchUnifiedMemory(input) {
|
|
365
595
|
const query = input.query.trim();
|
|
366
596
|
const sources = normalizeUnifiedMemorySearchSources(input.sources);
|
|
367
597
|
const limit = clampUnifiedMemorySearchLimit(input.limit);
|
|
368
598
|
const threshold = clampUnifiedMemorySearchThreshold(input.threshold);
|
|
369
599
|
const mergeStrategy = normalizeUnifiedMemoryMergeStrategy(input.mergeStrategy);
|
|
600
|
+
const reasoningStrategy = resolveReasoningStrategy(input);
|
|
370
601
|
const warnings = [];
|
|
371
602
|
if (!query) {
|
|
372
603
|
return {
|
|
@@ -378,10 +609,21 @@ function createUnifiedSearch(deps = {}) {
|
|
|
378
609
|
};
|
|
379
610
|
}
|
|
380
611
|
let memorySubs;
|
|
612
|
+
const reasoningInfo = { strategy: reasoningStrategy };
|
|
613
|
+
if (input.dateFrom || input.dateTo) {
|
|
614
|
+
reasoningInfo.dateRange = { from: input.dateFrom, to: input.dateTo };
|
|
615
|
+
}
|
|
381
616
|
if (sources.includes("memory")) {
|
|
382
617
|
if (isRawMessageStorageAvailable()) {
|
|
383
618
|
try {
|
|
384
|
-
memorySubs = await runMemorySource(
|
|
619
|
+
memorySubs = await runMemorySource(
|
|
620
|
+
input,
|
|
621
|
+
limit,
|
|
622
|
+
threshold,
|
|
623
|
+
warnings,
|
|
624
|
+
reasoningStrategy,
|
|
625
|
+
reasoningInfo
|
|
626
|
+
);
|
|
385
627
|
} catch (error) {
|
|
386
628
|
logger.warn?.("[memory-store] memory source failed:", error);
|
|
387
629
|
warnings.push({
|
|
@@ -460,120 +702,155 @@ function createUnifiedSearch(deps = {}) {
|
|
|
460
702
|
limit,
|
|
461
703
|
strategy: mergeStrategy
|
|
462
704
|
});
|
|
463
|
-
|
|
705
|
+
const output = {
|
|
464
706
|
query: input.query,
|
|
465
707
|
sources,
|
|
466
708
|
results: merged,
|
|
467
709
|
count: merged.length,
|
|
468
710
|
warnings
|
|
469
711
|
};
|
|
712
|
+
if (reasoningInfo && (reasoningInfo.strategy !== "none" || reasoningInfo.dateRange !== void 0)) {
|
|
713
|
+
output.reasoning = reasoningInfo;
|
|
714
|
+
}
|
|
715
|
+
return output;
|
|
470
716
|
}
|
|
471
|
-
async function runMemorySource(input, limit, threshold, warnings) {
|
|
717
|
+
async function runMemorySource(input, limit, threshold, warnings, reasoningStrategy = "none", reasoningInfo) {
|
|
718
|
+
if (reasoningStrategy === "iterative" && !deps.reasoning?.iterativePlanner) {
|
|
719
|
+
warnings.push({
|
|
720
|
+
source: "memory",
|
|
721
|
+
code: "memory_iterative_planner_not_configured",
|
|
722
|
+
message: "Iterative reasoning requested but no iterativePlanner is configured; falling back to default search."
|
|
723
|
+
});
|
|
724
|
+
if (reasoningInfo) reasoningInfo.degraded = true;
|
|
725
|
+
}
|
|
726
|
+
if (reasoningStrategy === "rewrite" && !deps.reasoning?.queryRewriter) {
|
|
727
|
+
warnings.push({
|
|
728
|
+
source: "memory",
|
|
729
|
+
code: "memory_query_rewrite_not_configured",
|
|
730
|
+
message: "Rewrite reasoning requested but no queryRewriter is configured; falling back to default search."
|
|
731
|
+
});
|
|
732
|
+
if (reasoningInfo) reasoningInfo.degraded = true;
|
|
733
|
+
}
|
|
734
|
+
if (reasoningStrategy === "iterative" && deps.reasoning?.iterativePlanner) {
|
|
735
|
+
const planner = deps.reasoning.iterativePlanner;
|
|
736
|
+
const result = await planner.plan({
|
|
737
|
+
query: input.query,
|
|
738
|
+
dateFrom: input.dateFrom,
|
|
739
|
+
dateTo: input.dateTo,
|
|
740
|
+
executor: {
|
|
741
|
+
search: async (request) => {
|
|
742
|
+
const keywords2 = request.keywords.length > 0 ? request.keywords : deriveLexicalKeywords(input.query);
|
|
743
|
+
const lexicalHits = await runLexicalSearchForKeywords(deps, input, keywords2, limit, logger);
|
|
744
|
+
let semanticHits = [];
|
|
745
|
+
if (typeof deps.embedQuery === "function") {
|
|
746
|
+
try {
|
|
747
|
+
const semanticQuery = keywords2.join(" ");
|
|
748
|
+
const queryEmbedding = await embedQueryVariant(deps.embedQuery, input, semanticQuery);
|
|
749
|
+
semanticHits = await runSemanticSearchForEmbedding(
|
|
750
|
+
deps,
|
|
751
|
+
input,
|
|
752
|
+
queryEmbedding,
|
|
753
|
+
limit,
|
|
754
|
+
threshold,
|
|
755
|
+
logger
|
|
756
|
+
);
|
|
757
|
+
} catch (error) {
|
|
758
|
+
logger.warn?.(
|
|
759
|
+
"[memory-store] Iterative semantic search failed; using lexical results only:",
|
|
760
|
+
error
|
|
761
|
+
);
|
|
762
|
+
}
|
|
763
|
+
}
|
|
764
|
+
const candidates = filterByDateRange(
|
|
765
|
+
mergeByMaxScore([
|
|
766
|
+
lexicalHits.map(toIterativeRecallCandidate),
|
|
767
|
+
semanticHits.map(toIterativeRecallCandidate)
|
|
768
|
+
]),
|
|
769
|
+
request.dateFrom,
|
|
770
|
+
request.dateTo
|
|
771
|
+
);
|
|
772
|
+
return { candidates };
|
|
773
|
+
}
|
|
774
|
+
}
|
|
775
|
+
});
|
|
776
|
+
if (reasoningInfo) {
|
|
777
|
+
reasoningInfo.iterations = result.stats.iterations;
|
|
778
|
+
reasoningInfo.evidenceCount = result.evidence.length;
|
|
779
|
+
if (planner.lastDegraded?.()) {
|
|
780
|
+
reasoningInfo.degraded = true;
|
|
781
|
+
}
|
|
782
|
+
}
|
|
783
|
+
const semantic2 = result.evidence.map((candidate) => ({
|
|
784
|
+
type: "memory",
|
|
785
|
+
id: candidate.id,
|
|
786
|
+
content: candidate.content,
|
|
787
|
+
similarity: candidate.similarity,
|
|
788
|
+
metadata: { ...candidate.metadata, reasoning: "iterative" }
|
|
789
|
+
}));
|
|
790
|
+
return { semantic: semantic2, lexical: [] };
|
|
791
|
+
}
|
|
472
792
|
if (typeof deps.embedQuery !== "function") {
|
|
473
793
|
warnings.push({
|
|
474
794
|
source: "memory",
|
|
475
795
|
code: "memory_lexical_search_fallback",
|
|
476
796
|
message: "Semantic search not configured, using keyword search as fallback"
|
|
477
797
|
});
|
|
478
|
-
const
|
|
479
|
-
const
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
const results = await Promise.all(
|
|
485
|
-
filters2.map(
|
|
486
|
-
(botId) => lexicalSearchRawMessages2({
|
|
487
|
-
userId: input.userId,
|
|
488
|
-
keywords,
|
|
489
|
-
limit: Math.ceil(limit / filters2.length),
|
|
490
|
-
botId
|
|
491
|
-
})
|
|
492
|
-
)
|
|
493
|
-
);
|
|
494
|
-
lexical2 = results.flat().filter(Boolean).map(toMemoryResult);
|
|
495
|
-
} catch (error) {
|
|
496
|
-
logger.warn?.("[memory-store] Default lexical search failed:", error);
|
|
497
|
-
}
|
|
498
|
-
}
|
|
798
|
+
const keywords2 = deriveLexicalKeywords(input.query);
|
|
799
|
+
const lexical2 = filterByDateRange(
|
|
800
|
+
await runLexicalSearchForKeywords(deps, input, keywords2, limit, logger),
|
|
801
|
+
input.dateFrom,
|
|
802
|
+
input.dateTo
|
|
803
|
+
);
|
|
499
804
|
return { semantic: [], lexical: lexical2 };
|
|
500
805
|
}
|
|
501
|
-
const queryEmbedding = await deps.embedQuery({
|
|
502
|
-
userId: input.userId,
|
|
503
|
-
query: input.query,
|
|
504
|
-
authToken: input.authToken
|
|
505
|
-
});
|
|
506
|
-
const filters = input.botIds && input.botIds.length > 0 ? input.botIds.map((botId) => ({ botId })) : [{}];
|
|
507
806
|
let semantic = [];
|
|
508
|
-
|
|
807
|
+
const embedQuery = deps.embedQuery;
|
|
808
|
+
if (reasoningStrategy === "rewrite" && deps.reasoning?.queryRewriter) {
|
|
509
809
|
try {
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
userId: input.userId,
|
|
515
|
-
queryEmbedding,
|
|
516
|
-
limit,
|
|
517
|
-
threshold,
|
|
518
|
-
botId
|
|
519
|
-
});
|
|
520
|
-
})
|
|
521
|
-
)).flat().map(toMemoryResult);
|
|
522
|
-
logger.log?.("[memory-store] Raw message semantic search completed", {
|
|
523
|
-
backend: "chroma",
|
|
524
|
-
dimensions: queryEmbedding.length,
|
|
525
|
-
count: semantic.length
|
|
810
|
+
const variants = await deps.reasoning.queryRewriter.rewrite({
|
|
811
|
+
query: input.query,
|
|
812
|
+
userId: input.userId,
|
|
813
|
+
authToken: input.authToken
|
|
526
814
|
});
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
|
|
815
|
+
if (reasoningInfo) {
|
|
816
|
+
reasoningInfo.rewrittenQueries = variants;
|
|
817
|
+
if (deps.reasoning.queryRewriter.lastDegraded?.()) {
|
|
818
|
+
reasoningInfo.degraded = true;
|
|
819
|
+
}
|
|
820
|
+
}
|
|
821
|
+
const embeddings = await Promise.all(
|
|
822
|
+
variants.map((variant) => embedQueryVariant(embedQuery, input, variant))
|
|
531
823
|
);
|
|
824
|
+
const lists = await Promise.all(
|
|
825
|
+
embeddings.map(
|
|
826
|
+
(embedding) => runSemanticSearchForEmbedding(deps, input, embedding, limit, threshold, logger)
|
|
827
|
+
)
|
|
828
|
+
);
|
|
829
|
+
semantic = mergeByMaxScore(lists);
|
|
830
|
+
} catch (error) {
|
|
831
|
+
logger.warn?.("[memory-store] Query rewriting failed; falling back to original query:", error);
|
|
832
|
+
warnings.push({
|
|
833
|
+
source: "memory",
|
|
834
|
+
code: "memory_query_rewrite_failed",
|
|
835
|
+
message: `Query rewriting failed: ${error.message ?? "Unknown error"}. Using original query.`
|
|
836
|
+
});
|
|
837
|
+
if (reasoningInfo) reasoningInfo.degraded = true;
|
|
532
838
|
}
|
|
533
|
-
} else if (typeof deps.searchRawMessagesAnn === "function") {
|
|
534
|
-
semantic = (await Promise.all(
|
|
535
|
-
filters.map(
|
|
536
|
-
(filter) => deps.searchRawMessagesAnn({
|
|
537
|
-
userId: input.userId,
|
|
538
|
-
queryEmbedding,
|
|
539
|
-
limit,
|
|
540
|
-
threshold,
|
|
541
|
-
botId: "botId" in filter ? filter.botId : void 0
|
|
542
|
-
})
|
|
543
|
-
)
|
|
544
|
-
)).flat().filter(isRawMemorySemanticResult).map(toMemoryResult);
|
|
545
839
|
}
|
|
546
840
|
if (semantic.length === 0) {
|
|
547
|
-
|
|
548
|
-
|
|
549
|
-
const manager2 = await getRawMessageManager2();
|
|
550
|
-
if (typeof manager2.searchMessagesSemantically === "function") {
|
|
551
|
-
const results = await manager2.searchMessagesSemantically({
|
|
552
|
-
userId: input.userId,
|
|
553
|
-
queryEmbedding,
|
|
554
|
-
limit
|
|
555
|
-
});
|
|
556
|
-
semantic = results.map((r) => ({
|
|
557
|
-
type: "memory",
|
|
558
|
-
id: r.id,
|
|
559
|
-
content: r.content,
|
|
560
|
-
similarity: r.similarity,
|
|
561
|
-
metadata: r.metadata ?? {}
|
|
562
|
-
}));
|
|
563
|
-
}
|
|
564
|
-
} catch (error) {
|
|
565
|
-
logger.warn?.("[memory-store] SQLite semantic search failed:", error);
|
|
566
|
-
}
|
|
841
|
+
const queryEmbedding = await embedQueryVariant(embedQuery, input, input.query);
|
|
842
|
+
semantic = await runSemanticSearchForEmbedding(deps, input, queryEmbedding, limit, threshold, logger);
|
|
567
843
|
}
|
|
568
844
|
let lexical = [];
|
|
569
|
-
|
|
570
|
-
|
|
571
|
-
if (
|
|
845
|
+
const keywords = deriveLexicalKeywords(input.query);
|
|
846
|
+
if (keywords.length > 0) {
|
|
847
|
+
if (typeof deps.searchRawMessagesLexical === "function") {
|
|
572
848
|
try {
|
|
573
849
|
const lexFilters = input.botIds && input.botIds.length > 0 ? input.botIds : [void 0];
|
|
850
|
+
const searchRawMessagesLexical = deps.searchRawMessagesLexical;
|
|
574
851
|
lexical = (await Promise.all(
|
|
575
852
|
lexFilters.map(
|
|
576
|
-
(botId) =>
|
|
853
|
+
(botId) => searchRawMessagesLexical({
|
|
577
854
|
userId: input.userId,
|
|
578
855
|
keywords,
|
|
579
856
|
limit,
|
|
@@ -592,20 +869,24 @@ function createUnifiedSearch(deps = {}) {
|
|
|
592
869
|
message: error.message ?? "memory_lexical_search_failed"
|
|
593
870
|
});
|
|
594
871
|
}
|
|
872
|
+
} else if (input.mergeStrategy === "rrf") {
|
|
873
|
+
warnings.push({
|
|
874
|
+
source: "memory",
|
|
875
|
+
code: "memory_lexical_search_not_configured",
|
|
876
|
+
message: "RRF merge requested but lexical search is not configured; falling back to semantic-only RRF."
|
|
877
|
+
});
|
|
595
878
|
}
|
|
596
|
-
} else if (input.mergeStrategy === "rrf") {
|
|
597
|
-
warnings.push({
|
|
598
|
-
source: "memory",
|
|
599
|
-
code: "memory_lexical_search_not_configured",
|
|
600
|
-
message: "RRF merge requested but lexical search is not configured; falling back to semantic-only RRF."
|
|
601
|
-
});
|
|
602
879
|
}
|
|
603
|
-
return {
|
|
880
|
+
return {
|
|
881
|
+
semantic: filterByDateRange(semantic, input.dateFrom, input.dateTo),
|
|
882
|
+
lexical: filterByDateRange(lexical, input.dateFrom, input.dateTo)
|
|
883
|
+
};
|
|
604
884
|
}
|
|
605
885
|
async function searchRawMemorySemantically(input) {
|
|
606
886
|
const limit = clampUnifiedMemorySearchLimit(input.limit);
|
|
607
887
|
const threshold = clampUnifiedMemorySearchThreshold(input.threshold);
|
|
608
|
-
const
|
|
888
|
+
const reasoningStrategy = resolveReasoningStrategy(input);
|
|
889
|
+
const sub = await runMemorySource(input, limit, threshold, [], reasoningStrategy);
|
|
609
890
|
return mergeAcrossSources({
|
|
610
891
|
memorySubs: sub,
|
|
611
892
|
insightHits: [],
|
|
@@ -650,8 +931,365 @@ function mergeAcrossSources(input) {
|
|
|
650
931
|
init_raw_message_store();
|
|
651
932
|
init_raw_message_store();
|
|
652
933
|
init_postgres_raw_message_factory();
|
|
934
|
+
|
|
935
|
+
// src/search/query-rewriter.ts
|
|
936
|
+
var SYSTEM_PROMPT = `You are a memory retrieval assistant.
|
|
937
|
+
|
|
938
|
+
The memory store contains a user's past messages, written in the first person (e.g. "I told you about my sister's wedding", "I prefer dark mode").
|
|
939
|
+
|
|
940
|
+
Your job is NOT to answer the question. Your job is to rewrite the assistant's question into a short, natural question that the USER would ask their own memory log to find the relevant entry.
|
|
941
|
+
|
|
942
|
+
Rules:
|
|
943
|
+
- Use first person: "Did I tell you about ...?", "What did I say about ...?", "Have I mentioned ...?"
|
|
944
|
+
- Do NOT address the user as "you".
|
|
945
|
+
- Do NOT answer the question.
|
|
946
|
+
- Output each rephrasing on its own line, prefixed with "- ".
|
|
947
|
+
- Keep each rephrasing under 20 words.
|
|
948
|
+
- Output exactly the requested number of lines.`;
|
|
949
|
+
function buildPrompt(query, numVariants) {
|
|
950
|
+
const label = numVariants === 1 ? "1 alternative rephrasing" : `${numVariants} alternative rephrasings`;
|
|
951
|
+
return `${SYSTEM_PROMPT}
|
|
952
|
+
|
|
953
|
+
Output exactly ${label}.
|
|
954
|
+
|
|
955
|
+
Example:
|
|
956
|
+
Question: What is the name of my cat?
|
|
957
|
+
- Did I tell you the name of my cat?
|
|
958
|
+
|
|
959
|
+
Question: ${query}
|
|
960
|
+
Output:`;
|
|
961
|
+
}
|
|
962
|
+
function sanitizeLine(text) {
|
|
963
|
+
return text.replace(/^\s*["'`]+|["'`]+\s*$/g, "").replace(/^Rewritten:\s*/i, "").trim();
|
|
964
|
+
}
|
|
965
|
+
function parseVariants(raw, maxVariants, original) {
|
|
966
|
+
const lines = raw.split(/\r?\n/).map((line) => sanitizeLine(line.replace(/^\s*-\s+/, ""))).filter((line) => line.length > 0);
|
|
967
|
+
if (lines.length === 0) return [];
|
|
968
|
+
const seen = /* @__PURE__ */ new Set([original.toLowerCase()]);
|
|
969
|
+
const variants = [];
|
|
970
|
+
for (const line of lines) {
|
|
971
|
+
if (variants.length >= maxVariants) break;
|
|
972
|
+
const key = line.toLowerCase();
|
|
973
|
+
if (seen.has(key)) continue;
|
|
974
|
+
seen.add(key);
|
|
975
|
+
variants.push(line);
|
|
976
|
+
}
|
|
977
|
+
return variants;
|
|
978
|
+
}
|
|
979
|
+
function createUserVoiceRewriter(options) {
|
|
980
|
+
const { complete, maxVariants = 1, disabled = false } = options;
|
|
981
|
+
const wantedVariants = Math.max(0, Math.floor(maxVariants));
|
|
982
|
+
let lastDegraded = false;
|
|
983
|
+
return {
|
|
984
|
+
async rewrite(input) {
|
|
985
|
+
if (disabled || !input.query || !input.query.trim()) {
|
|
986
|
+
lastDegraded = false;
|
|
987
|
+
return [input.query];
|
|
988
|
+
}
|
|
989
|
+
const original = input.query.trim();
|
|
990
|
+
if (wantedVariants === 0) {
|
|
991
|
+
lastDegraded = false;
|
|
992
|
+
return [original];
|
|
993
|
+
}
|
|
994
|
+
try {
|
|
995
|
+
const raw = await complete(buildPrompt(original, wantedVariants));
|
|
996
|
+
const variants = parseVariants(raw, wantedVariants, original);
|
|
997
|
+
if (variants.length === 0) {
|
|
998
|
+
lastDegraded = true;
|
|
999
|
+
return [original];
|
|
1000
|
+
}
|
|
1001
|
+
lastDegraded = false;
|
|
1002
|
+
return [original, ...variants];
|
|
1003
|
+
} catch {
|
|
1004
|
+
lastDegraded = true;
|
|
1005
|
+
return [original];
|
|
1006
|
+
}
|
|
1007
|
+
},
|
|
1008
|
+
lastDegraded() {
|
|
1009
|
+
return lastDegraded;
|
|
1010
|
+
}
|
|
1011
|
+
};
|
|
1012
|
+
}
|
|
1013
|
+
function createIdentityRewriter() {
|
|
1014
|
+
return {
|
|
1015
|
+
async rewrite(input) {
|
|
1016
|
+
return [input.query];
|
|
1017
|
+
}
|
|
1018
|
+
};
|
|
1019
|
+
}
|
|
1020
|
+
|
|
1021
|
+
// src/search/iterative-recall.ts
|
|
1022
|
+
var SYSTEM_PROMPT2 = `You are a research assistant collecting evidence from a user's conversation history. You are NOT answering the question; only gather the most useful memory.
|
|
1023
|
+
|
|
1024
|
+
Actions:
|
|
1025
|
+
search \u2014 search by concise keywords. Previously returned chunks are automatically excluded.
|
|
1026
|
+
Action Input: {"keywords":["keyword1","keyword2"],"date_from":"YYYY-MM-DD","date_to":"YYYY-MM-DD"}
|
|
1027
|
+
The date fields are optional; omit them unless an absolute date range is useful.
|
|
1028
|
+
|
|
1029
|
+
note \u2014 save relevant results from the LAST search by their 1-based result number.
|
|
1030
|
+
Action Input: {"indices":[1,3]}
|
|
1031
|
+
|
|
1032
|
+
finish \u2014 finish collecting evidence.
|
|
1033
|
+
Action Input: {}
|
|
1034
|
+
|
|
1035
|
+
Workflow: search -> note -> search again -> note -> finish.
|
|
1036
|
+
Review every result. Call note before another search because the prior result list is replaced. Use diverse keywords on later searches. Never answer the question.
|
|
1037
|
+
|
|
1038
|
+
Output exactly:
|
|
1039
|
+
Thought: one short sentence
|
|
1040
|
+
Action: one action name
|
|
1041
|
+
Action Input: valid JSON object`;
|
|
1042
|
+
function deriveKeywordsFromQuery(query) {
|
|
1043
|
+
return query.toLowerCase().split(/[^\p{L}\p{N}]+/u).filter((token) => token.length >= 2);
|
|
1044
|
+
}
|
|
1045
|
+
function formatDateHint(dateFrom, dateTo) {
|
|
1046
|
+
if (!dateFrom && !dateTo) {
|
|
1047
|
+
return void 0;
|
|
1048
|
+
}
|
|
1049
|
+
const fromText = dateFrom ?? "the beginning";
|
|
1050
|
+
const toText = dateTo ?? "now";
|
|
1051
|
+
return `Restrict searches to the date range ${fromText} to ${toText}. You may still emit narrower date_from/date_to bounds when useful.`;
|
|
1052
|
+
}
|
|
1053
|
+
function buildInitialPrompt(query, dateHint) {
|
|
1054
|
+
const hintText = dateHint ? `
|
|
1055
|
+
${dateHint}` : "";
|
|
1056
|
+
return `${SYSTEM_PROMPT2}
|
|
1057
|
+
|
|
1058
|
+
Question to research: ${query}${hintText}
|
|
1059
|
+
Search the conversation history and collect all relevant evidence. Start with a broad keyword search.`;
|
|
1060
|
+
}
|
|
1061
|
+
function buildObservationPrompt(hits) {
|
|
1062
|
+
if (hits.length === 0) {
|
|
1063
|
+
return "Observation: No matching memory found.\nRespond with the next action only.";
|
|
1064
|
+
}
|
|
1065
|
+
const blocks = hits.map((hit, index) => {
|
|
1066
|
+
const lines = [`Result ${index + 1} | score=${hit.similarity.toFixed(4)}`];
|
|
1067
|
+
for (const line of hit.content.split("\n")) {
|
|
1068
|
+
if (line.trim()) lines.push(line);
|
|
1069
|
+
}
|
|
1070
|
+
return lines.join("\n");
|
|
1071
|
+
});
|
|
1072
|
+
return `Observation:
|
|
1073
|
+
${blocks.join("\n\n")}
|
|
1074
|
+
|
|
1075
|
+
Respond with the next action only.`;
|
|
1076
|
+
}
|
|
1077
|
+
function stripCodeFences(text) {
|
|
1078
|
+
return text.replace(/```(?:json)?\s*([\s\S]*?)\s*```/g, "$1");
|
|
1079
|
+
}
|
|
1080
|
+
function extractJsonObject(text) {
|
|
1081
|
+
const start = text.indexOf("{");
|
|
1082
|
+
if (start === -1) {
|
|
1083
|
+
return void 0;
|
|
1084
|
+
}
|
|
1085
|
+
let depth = 0;
|
|
1086
|
+
let inString = false;
|
|
1087
|
+
let escaped = false;
|
|
1088
|
+
for (let i = start; i < text.length; i += 1) {
|
|
1089
|
+
const ch = text[i];
|
|
1090
|
+
if (escaped) {
|
|
1091
|
+
escaped = false;
|
|
1092
|
+
continue;
|
|
1093
|
+
}
|
|
1094
|
+
if (inString) {
|
|
1095
|
+
if (ch === "\\") {
|
|
1096
|
+
escaped = true;
|
|
1097
|
+
} else if (ch === '"') {
|
|
1098
|
+
inString = false;
|
|
1099
|
+
}
|
|
1100
|
+
continue;
|
|
1101
|
+
}
|
|
1102
|
+
if (ch === '"') {
|
|
1103
|
+
inString = true;
|
|
1104
|
+
} else if (ch === "{") {
|
|
1105
|
+
depth += 1;
|
|
1106
|
+
} else if (ch === "}") {
|
|
1107
|
+
depth -= 1;
|
|
1108
|
+
if (depth === 0) {
|
|
1109
|
+
return text.slice(start, i + 1);
|
|
1110
|
+
}
|
|
1111
|
+
}
|
|
1112
|
+
}
|
|
1113
|
+
return void 0;
|
|
1114
|
+
}
|
|
1115
|
+
function parseAction(text) {
|
|
1116
|
+
const cleaned = stripCodeFences(text);
|
|
1117
|
+
const actionMatch = cleaned.match(/Action:\s*([\w_]+)/);
|
|
1118
|
+
if (!actionMatch) return null;
|
|
1119
|
+
const action = actionMatch[1].trim().toLowerCase();
|
|
1120
|
+
if (action === "finish") {
|
|
1121
|
+
return { type: "finish" };
|
|
1122
|
+
}
|
|
1123
|
+
const inputMatch = cleaned.match(/Action Input:\s*/s);
|
|
1124
|
+
if (!inputMatch || inputMatch.index === void 0) return null;
|
|
1125
|
+
const jsonText = extractJsonObject(cleaned.slice(inputMatch.index + inputMatch[0].length));
|
|
1126
|
+
if (!jsonText) return null;
|
|
1127
|
+
let parsedJson = null;
|
|
1128
|
+
for (const candidate of [jsonText, jsonText.replace(/'/g, '"')]) {
|
|
1129
|
+
try {
|
|
1130
|
+
parsedJson = JSON.parse(candidate);
|
|
1131
|
+
break;
|
|
1132
|
+
} catch {
|
|
1133
|
+
}
|
|
1134
|
+
}
|
|
1135
|
+
if (!parsedJson) return null;
|
|
1136
|
+
if (action === "search") {
|
|
1137
|
+
const keywords = parsedJson.keywords;
|
|
1138
|
+
if (!Array.isArray(keywords) || !keywords.every((k) => typeof k === "string")) {
|
|
1139
|
+
return null;
|
|
1140
|
+
}
|
|
1141
|
+
return {
|
|
1142
|
+
type: "search",
|
|
1143
|
+
keywords,
|
|
1144
|
+
dateFrom: typeof parsedJson.date_from === "string" ? parsedJson.date_from : void 0,
|
|
1145
|
+
dateTo: typeof parsedJson.date_to === "string" ? parsedJson.date_to : void 0
|
|
1146
|
+
};
|
|
1147
|
+
}
|
|
1148
|
+
if (action === "note") {
|
|
1149
|
+
const indices = parsedJson.indices;
|
|
1150
|
+
if (!Array.isArray(indices) || !indices.every((i) => typeof i === "number")) {
|
|
1151
|
+
return null;
|
|
1152
|
+
}
|
|
1153
|
+
return { type: "note", indices };
|
|
1154
|
+
}
|
|
1155
|
+
return null;
|
|
1156
|
+
}
|
|
1157
|
+
function createIterativeRecallPlanner(deps) {
|
|
1158
|
+
const { complete, options = {} } = deps;
|
|
1159
|
+
const defaults = {
|
|
1160
|
+
maxIterations: 4,
|
|
1161
|
+
searchTopK: 5,
|
|
1162
|
+
disabled: false,
|
|
1163
|
+
fallbackToBaseline: true
|
|
1164
|
+
};
|
|
1165
|
+
let lastDegraded = false;
|
|
1166
|
+
let iterationsWithValidAction = 0;
|
|
1167
|
+
return {
|
|
1168
|
+
lastDegraded: () => lastDegraded,
|
|
1169
|
+
async plan(input) {
|
|
1170
|
+
const opts = {
|
|
1171
|
+
...defaults,
|
|
1172
|
+
...options,
|
|
1173
|
+
...input.options
|
|
1174
|
+
};
|
|
1175
|
+
lastDegraded = false;
|
|
1176
|
+
iterationsWithValidAction = 0;
|
|
1177
|
+
if (opts.disabled || !input.query.trim()) {
|
|
1178
|
+
return { evidence: [], stats: { iterations: 0, searches: 0, notes: 0 } };
|
|
1179
|
+
}
|
|
1180
|
+
const messages = [
|
|
1181
|
+
{ role: "system", content: SYSTEM_PROMPT2 },
|
|
1182
|
+
{
|
|
1183
|
+
role: "user",
|
|
1184
|
+
content: buildInitialPrompt(input.query, formatDateHint(input.dateFrom, input.dateTo))
|
|
1185
|
+
}
|
|
1186
|
+
];
|
|
1187
|
+
const evidence = /* @__PURE__ */ new Map();
|
|
1188
|
+
const excludedIds = /* @__PURE__ */ new Set();
|
|
1189
|
+
let lastHits = [];
|
|
1190
|
+
let searches = 0;
|
|
1191
|
+
let notes = 0;
|
|
1192
|
+
let iterations = 0;
|
|
1193
|
+
let completeThrew = false;
|
|
1194
|
+
for (let i = 0; i < opts.maxIterations; i += 1) {
|
|
1195
|
+
iterations = i + 1;
|
|
1196
|
+
let reply;
|
|
1197
|
+
try {
|
|
1198
|
+
reply = await complete(messages.map((m) => `${m.role}: ${m.content}`).join("\n\n"));
|
|
1199
|
+
} catch {
|
|
1200
|
+
completeThrew = true;
|
|
1201
|
+
break;
|
|
1202
|
+
}
|
|
1203
|
+
messages.push({ role: "assistant", content: reply });
|
|
1204
|
+
const action = parseAction(reply);
|
|
1205
|
+
if (!action) {
|
|
1206
|
+
messages.push({
|
|
1207
|
+
role: "user",
|
|
1208
|
+
content: "Observation: Use exactly one valid action: search, note, or finish. Respond with the next action only."
|
|
1209
|
+
});
|
|
1210
|
+
continue;
|
|
1211
|
+
}
|
|
1212
|
+
iterationsWithValidAction += 1;
|
|
1213
|
+
if (action.type === "finish") {
|
|
1214
|
+
break;
|
|
1215
|
+
}
|
|
1216
|
+
if (action.type === "search") {
|
|
1217
|
+
const result = await input.executor.search({
|
|
1218
|
+
keywords: action.keywords,
|
|
1219
|
+
dateFrom: action.dateFrom ?? input.dateFrom,
|
|
1220
|
+
dateTo: action.dateTo ?? input.dateTo
|
|
1221
|
+
});
|
|
1222
|
+
lastHits = result.candidates.filter((c) => !excludedIds.has(c.id)).slice(0, opts.searchTopK);
|
|
1223
|
+
for (const hit of lastHits) {
|
|
1224
|
+
excludedIds.add(hit.id);
|
|
1225
|
+
}
|
|
1226
|
+
searches += 1;
|
|
1227
|
+
messages.push({ role: "user", content: buildObservationPrompt(lastHits) });
|
|
1228
|
+
continue;
|
|
1229
|
+
}
|
|
1230
|
+
if (action.type === "note") {
|
|
1231
|
+
let saved = 0;
|
|
1232
|
+
for (const index of action.indices) {
|
|
1233
|
+
if (index >= 1 && index <= lastHits.length) {
|
|
1234
|
+
const hit = lastHits[index - 1];
|
|
1235
|
+
if (!evidence.has(hit.id)) {
|
|
1236
|
+
evidence.set(hit.id, hit);
|
|
1237
|
+
saved += 1;
|
|
1238
|
+
}
|
|
1239
|
+
}
|
|
1240
|
+
}
|
|
1241
|
+
notes += saved;
|
|
1242
|
+
messages.push({
|
|
1243
|
+
role: "user",
|
|
1244
|
+
content: `Observation: Saved ${saved} new result(s). Total notes: ${evidence.size}.
|
|
1245
|
+
Respond with the next action only.`
|
|
1246
|
+
});
|
|
1247
|
+
}
|
|
1248
|
+
}
|
|
1249
|
+
let fallbackRan = false;
|
|
1250
|
+
if (evidence.size === 0 && opts.fallbackToBaseline) {
|
|
1251
|
+
fallbackRan = true;
|
|
1252
|
+
if (lastHits.length > 0) {
|
|
1253
|
+
for (const hit of lastHits) {
|
|
1254
|
+
if (!evidence.has(hit.id)) {
|
|
1255
|
+
evidence.set(hit.id, hit);
|
|
1256
|
+
}
|
|
1257
|
+
}
|
|
1258
|
+
} else {
|
|
1259
|
+
const baseline = await input.executor.search({
|
|
1260
|
+
keywords: deriveKeywordsFromQuery(input.query),
|
|
1261
|
+
dateFrom: input.dateFrom,
|
|
1262
|
+
dateTo: input.dateTo
|
|
1263
|
+
});
|
|
1264
|
+
searches += 1;
|
|
1265
|
+
for (const hit of baseline.candidates.slice(0, opts.searchTopK)) {
|
|
1266
|
+
if (!evidence.has(hit.id)) {
|
|
1267
|
+
evidence.set(hit.id, hit);
|
|
1268
|
+
}
|
|
1269
|
+
}
|
|
1270
|
+
}
|
|
1271
|
+
}
|
|
1272
|
+
lastDegraded = completeThrew || iterationsWithValidAction === 0 || fallbackRan;
|
|
1273
|
+
return {
|
|
1274
|
+
evidence: Array.from(evidence.values()),
|
|
1275
|
+
stats: { iterations, searches, notes }
|
|
1276
|
+
};
|
|
1277
|
+
}
|
|
1278
|
+
};
|
|
1279
|
+
}
|
|
1280
|
+
function createIdentityIterativePlanner() {
|
|
1281
|
+
return {
|
|
1282
|
+
lastDegraded: () => false,
|
|
1283
|
+
async plan() {
|
|
1284
|
+
return { evidence: [], stats: { iterations: 0, searches: 0, notes: 0 } };
|
|
1285
|
+
}
|
|
1286
|
+
};
|
|
1287
|
+
}
|
|
1288
|
+
|
|
1289
|
+
// src/index.ts
|
|
653
1290
|
async function createMemoryStore(config = {}) {
|
|
654
|
-
const
|
|
1291
|
+
const dbPath = config.dbPath ?? (config.db && "path" in config.db ? config.db.path : void 0);
|
|
1292
|
+
const raw = createRawMessageStore({ env: config.env, dbPath });
|
|
655
1293
|
const search = createUnifiedSearch(config.unified);
|
|
656
1294
|
configureRawMessageStore(config);
|
|
657
1295
|
return {
|
|
@@ -663,4 +1301,4 @@ async function createMemoryStore(config = {}) {
|
|
|
663
1301
|
};
|
|
664
1302
|
}
|
|
665
1303
|
|
|
666
|
-
export { clampUnifiedMemorySearchLimit, clampUnifiedMemorySearchThreshold, clearPostgresFactory, closeRawMessageStore, createMemoryStore, createRawMessageStore, createUnifiedSearch, getRawMessageManager, getRawMessageStorageBackend, hasPostgresFactory, isRawMemorySemanticResult, isRawMessageStorageAvailable, mergeUnifiedMemorySearchResults, normalizeUnifiedMemorySearchSources, registerPostgresFactory, resolvePostgresFactory, toKnowledgeResult, toMemoryResult };
|
|
1304
|
+
export { clampUnifiedMemorySearchLimit, clampUnifiedMemorySearchThreshold, clearPostgresFactory, closeRawMessageStore, createIdentityIterativePlanner, createIdentityRewriter, createIterativeRecallPlanner, createMemoryStore, createRawMessageStore, createUnifiedSearch, createUserVoiceRewriter, getRawMessageManager, getRawMessageStorageBackend, hasPostgresFactory, isRawMemorySemanticResult, isRawMessageStorageAvailable, mergeUnifiedMemorySearchResults, normalizeUnifiedMemoryReasoningStrategy, normalizeUnifiedMemorySearchSources, registerPostgresFactory, resolvePostgresFactory, toKnowledgeResult, toMemoryResult };
|