@hanhnd/agent-kit 1.0.38 → 1.0.39
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/cli.js +231 -125
- package/dist/mcp/memory.js +1 -1
- package/dist/server.js +234 -128
- package/dist/services/digest/digest-processor.test.js +1 -3
- package/dist/services/memory/indexer.js +35 -86
- package/dist/services/memory/indexer.test.js +229 -586
- package/dist/services/memory/store.d.ts +4 -11
- package/dist/services/memory/store.js +200 -66
- package/dist/services/memory/store.test.js +359 -108
- package/dist/services/memory/types.d.ts +28 -0
- package/package.json +1 -1
package/dist/cli.js
CHANGED
|
@@ -15504,10 +15504,6 @@ var MemoryStore = class {
|
|
|
15504
15504
|
}
|
|
15505
15505
|
return Buffer.from(embedding.buffer, embedding.byteOffset, embedding.byteLength);
|
|
15506
15506
|
}
|
|
15507
|
-
normalizeVectorDistance(distance) {
|
|
15508
|
-
if (distance <= 0) return 1;
|
|
15509
|
-
return Math.max(0, Math.min(1, 1 - distance));
|
|
15510
|
-
}
|
|
15511
15507
|
hashesBySource(source) {
|
|
15512
15508
|
const rows = this.db.prepare(`SELECT id FROM memory_chunks WHERE source = ?`).all(source);
|
|
15513
15509
|
return new Set(rows.map((r) => r.id));
|
|
@@ -15544,61 +15540,12 @@ var MemoryStore = class {
|
|
|
15544
15540
|
});
|
|
15545
15541
|
deleteAll();
|
|
15546
15542
|
}
|
|
15547
|
-
|
|
15548
|
-
try {
|
|
15549
|
-
const queryVector = this.toVectorBinding(embedding);
|
|
15550
|
-
const rows = this.db.prepare(
|
|
15551
|
-
`SELECT mc.id, vector_distance_cos(mc.embedding, vector32(?)) AS distance
|
|
15552
|
-
FROM vector_top_k('idx_memory_chunks_embedding', vector32(?), ?) AS vector_matches
|
|
15553
|
-
JOIN memory_chunks mc ON mc.rowid = vector_matches.id
|
|
15554
|
-
ORDER BY distance
|
|
15555
|
-
LIMIT ?`
|
|
15556
|
-
).all(queryVector, queryVector, limit, limit);
|
|
15557
|
-
return rows.map((r) => ({
|
|
15558
|
-
id: r.id,
|
|
15559
|
-
score: this.normalizeVectorDistance(r.distance)
|
|
15560
|
-
}));
|
|
15561
|
-
} catch (err) {
|
|
15562
|
-
console.warn("[memory-store] Dense search failed:", err);
|
|
15563
|
-
return [];
|
|
15564
|
-
}
|
|
15565
|
-
}
|
|
15566
|
-
searchBm25(query, limit) {
|
|
15567
|
-
if (!query.trim()) return [];
|
|
15543
|
+
buildFtsQuery(query) {
|
|
15568
15544
|
const tokens = query.trim().split(/\s+/).map((t) => t.replace(/[^a-zA-Z0-9_]/g, "").toLowerCase()).filter((t) => t.length > 1);
|
|
15569
|
-
|
|
15570
|
-
if (!ftsQuery) return [];
|
|
15571
|
-
try {
|
|
15572
|
-
const rows = this.db.prepare(
|
|
15573
|
-
`SELECT mc.id, bm25(memory_fts, 0.2, 2.0, 5.0) AS rank
|
|
15574
|
-
FROM memory_fts
|
|
15575
|
-
JOIN memory_chunks mc ON mc.rowid = memory_fts.rowid
|
|
15576
|
-
WHERE memory_fts MATCH ?
|
|
15577
|
-
ORDER BY rank
|
|
15578
|
-
LIMIT ?`
|
|
15579
|
-
).all(ftsQuery, limit);
|
|
15580
|
-
if (rows.length === 0) return [];
|
|
15581
|
-
const ranks = rows.map((r) => r.rank);
|
|
15582
|
-
const minRank = Math.min(...ranks);
|
|
15583
|
-
const maxRank = Math.max(...ranks);
|
|
15584
|
-
const range = maxRank - minRank;
|
|
15585
|
-
return rows.map((r) => ({
|
|
15586
|
-
id: r.id,
|
|
15587
|
-
score: range > 0 ? (maxRank - r.rank) / range : 1
|
|
15588
|
-
}));
|
|
15589
|
-
} catch (err) {
|
|
15590
|
-
console.warn("[memory-store] BM25 search failed:", err);
|
|
15591
|
-
return [];
|
|
15592
|
-
}
|
|
15545
|
+
return (0, import_stopword.removeStopwords)(tokens, import_stopword.eng).map((t) => `${t.replace(/s$/, "")}*`).join(" OR ");
|
|
15593
15546
|
}
|
|
15594
|
-
|
|
15595
|
-
|
|
15596
|
-
const placeholders = ids.map(() => "?").join(", ");
|
|
15597
|
-
const rows = this.db.prepare(
|
|
15598
|
-
`SELECT id, source, source_type, heading, heading_level, content, line_start, line_end, file_mtime_at
|
|
15599
|
-
FROM memory_chunks WHERE id IN (${placeholders})`
|
|
15600
|
-
).all(...ids);
|
|
15601
|
-
return rows.map((r) => ({
|
|
15547
|
+
mapMemoryChunkRow(r) {
|
|
15548
|
+
return {
|
|
15602
15549
|
id: r.id,
|
|
15603
15550
|
source: r.source,
|
|
15604
15551
|
sourceType: r.source_type,
|
|
@@ -15608,7 +15555,201 @@ var MemoryStore = class {
|
|
|
15608
15555
|
lineStart: r.line_start,
|
|
15609
15556
|
lineEnd: r.line_end,
|
|
15610
15557
|
fileMtimeAt: r.file_mtime_at
|
|
15611
|
-
}
|
|
15558
|
+
};
|
|
15559
|
+
}
|
|
15560
|
+
searchHybrid(options) {
|
|
15561
|
+
const {
|
|
15562
|
+
query,
|
|
15563
|
+
embedding,
|
|
15564
|
+
topK,
|
|
15565
|
+
fetchLimit,
|
|
15566
|
+
denseScoreFloor,
|
|
15567
|
+
recencyWeight,
|
|
15568
|
+
rrfK,
|
|
15569
|
+
sourceType,
|
|
15570
|
+
sinceMtimeAt,
|
|
15571
|
+
includeDebug
|
|
15572
|
+
} = options;
|
|
15573
|
+
const ftsQuery = this.buildFtsQuery(query);
|
|
15574
|
+
const hasDense = embedding !== void 0;
|
|
15575
|
+
const hasBm25 = ftsQuery.length > 0;
|
|
15576
|
+
if (!hasDense && !hasBm25) return [];
|
|
15577
|
+
const hasFilters = sourceType !== void 0 || sinceMtimeAt !== void 0;
|
|
15578
|
+
try {
|
|
15579
|
+
const params = [];
|
|
15580
|
+
let denseCte = "";
|
|
15581
|
+
let denseRankedCte = "";
|
|
15582
|
+
if (hasDense) {
|
|
15583
|
+
const queryBuf = this.toVectorBinding(embedding);
|
|
15584
|
+
if (hasFilters) {
|
|
15585
|
+
const filterClauses = [];
|
|
15586
|
+
if (sourceType) {
|
|
15587
|
+
filterClauses.push("mc.source_type = ?");
|
|
15588
|
+
params.push(sourceType);
|
|
15589
|
+
}
|
|
15590
|
+
if (sinceMtimeAt !== void 0) {
|
|
15591
|
+
filterClauses.push("mc.file_mtime_at >= ?");
|
|
15592
|
+
params.push(sinceMtimeAt);
|
|
15593
|
+
}
|
|
15594
|
+
const whereClause = filterClauses.length > 0 ? `WHERE ${filterClauses.join(" AND ")}` : "";
|
|
15595
|
+
params.push(queryBuf);
|
|
15596
|
+
params.push(fetchLimit);
|
|
15597
|
+
denseCte = `dense_data AS (
|
|
15598
|
+
SELECT mc.id, mc.source, mc.source_type, mc.file_mtime_at,
|
|
15599
|
+
mc.heading, mc.heading_level, mc.content, mc.line_start, mc.line_end,
|
|
15600
|
+
vector_distance_cos(mc.embedding, vector32(?)) AS dense_dist
|
|
15601
|
+
FROM memory_chunks mc
|
|
15602
|
+
${whereClause}
|
|
15603
|
+
ORDER BY dense_dist
|
|
15604
|
+
LIMIT ?
|
|
15605
|
+
)`;
|
|
15606
|
+
} else {
|
|
15607
|
+
params.push(queryBuf);
|
|
15608
|
+
params.push(queryBuf);
|
|
15609
|
+
params.push(fetchLimit);
|
|
15610
|
+
denseCte = `dense_data AS (
|
|
15611
|
+
SELECT mc.id, mc.source, mc.source_type, mc.file_mtime_at,
|
|
15612
|
+
mc.heading, mc.heading_level, mc.content, mc.line_start, mc.line_end,
|
|
15613
|
+
vector_distance_cos(mc.embedding, vector32(?)) AS dense_dist
|
|
15614
|
+
FROM vector_top_k('idx_memory_chunks_embedding', vector32(?), ?) AS vtk
|
|
15615
|
+
JOIN memory_chunks mc ON mc.rowid = vtk.id
|
|
15616
|
+
)`;
|
|
15617
|
+
}
|
|
15618
|
+
denseRankedCte = `dense_ranked AS (
|
|
15619
|
+
SELECT *, ROW_NUMBER() OVER (ORDER BY dense_dist, id) AS dense_rank
|
|
15620
|
+
FROM dense_data
|
|
15621
|
+
)`;
|
|
15622
|
+
}
|
|
15623
|
+
let bm25Cte = "";
|
|
15624
|
+
let bm25RankedCte = "";
|
|
15625
|
+
if (hasBm25) {
|
|
15626
|
+
const bm25FilterClauses = [];
|
|
15627
|
+
if (sourceType) bm25FilterClauses.push("mc.source_type = ?");
|
|
15628
|
+
if (sinceMtimeAt !== void 0) bm25FilterClauses.push("mc.file_mtime_at >= ?");
|
|
15629
|
+
const bm25WhereExtra = bm25FilterClauses.length > 0 ? ` AND ${bm25FilterClauses.join(" AND ")}` : "";
|
|
15630
|
+
params.push(ftsQuery);
|
|
15631
|
+
if (sourceType) params.push(sourceType);
|
|
15632
|
+
if (sinceMtimeAt !== void 0) params.push(sinceMtimeAt);
|
|
15633
|
+
params.push(fetchLimit);
|
|
15634
|
+
bm25Cte = `bm25_data AS (
|
|
15635
|
+
SELECT mc.id, mc.source, mc.source_type, mc.file_mtime_at,
|
|
15636
|
+
mc.heading, mc.heading_level, mc.content, mc.line_start, mc.line_end,
|
|
15637
|
+
bm25(memory_fts, 0.2, 2.0, 5.0) AS bm25_neg
|
|
15638
|
+
FROM memory_fts
|
|
15639
|
+
JOIN memory_chunks mc ON mc.rowid = memory_fts.rowid
|
|
15640
|
+
WHERE memory_fts MATCH ?${bm25WhereExtra}
|
|
15641
|
+
LIMIT ?
|
|
15642
|
+
)`;
|
|
15643
|
+
bm25RankedCte = `bm25_ranked AS (
|
|
15644
|
+
SELECT *, ROW_NUMBER() OVER (ORDER BY bm25_neg, id) AS bm25_rank
|
|
15645
|
+
FROM bm25_data
|
|
15646
|
+
)`;
|
|
15647
|
+
}
|
|
15648
|
+
let candidatesCte = "";
|
|
15649
|
+
if (hasDense && hasBm25) {
|
|
15650
|
+
candidatesCte = `all_candidates AS (
|
|
15651
|
+
SELECT d.id, d.source, d.source_type, d.file_mtime_at,
|
|
15652
|
+
d.heading, d.heading_level, d.content, d.line_start, d.line_end,
|
|
15653
|
+
CAST(d.dense_rank AS INTEGER) AS dense_rank, d.dense_dist,
|
|
15654
|
+
b.bm25_rank,
|
|
15655
|
+
CASE WHEN b.id IS NOT NULL THEN 'both' ELSE 'dense' END AS retriever
|
|
15656
|
+
FROM dense_ranked d
|
|
15657
|
+
LEFT JOIN bm25_ranked b ON b.id = d.id
|
|
15658
|
+
UNION ALL
|
|
15659
|
+
SELECT b.id, b.source, b.source_type, b.file_mtime_at,
|
|
15660
|
+
b.heading, b.heading_level, b.content, b.line_start, b.line_end,
|
|
15661
|
+
NULL AS dense_rank, NULL AS dense_dist,
|
|
15662
|
+
CAST(b.bm25_rank AS INTEGER) AS bm25_rank,
|
|
15663
|
+
'bm25' AS retriever
|
|
15664
|
+
FROM bm25_ranked b
|
|
15665
|
+
LEFT JOIN dense_ranked d ON d.id = b.id
|
|
15666
|
+
WHERE d.id IS NULL
|
|
15667
|
+
)`;
|
|
15668
|
+
} else if (hasDense) {
|
|
15669
|
+
candidatesCte = `all_candidates AS (
|
|
15670
|
+
SELECT id, source, source_type, file_mtime_at,
|
|
15671
|
+
heading, heading_level, content, line_start, line_end,
|
|
15672
|
+
CAST(dense_rank AS INTEGER) AS dense_rank, dense_dist,
|
|
15673
|
+
NULL AS bm25_rank, 'dense' AS retriever
|
|
15674
|
+
FROM dense_ranked
|
|
15675
|
+
)`;
|
|
15676
|
+
} else {
|
|
15677
|
+
candidatesCte = `all_candidates AS (
|
|
15678
|
+
SELECT id, source, source_type, file_mtime_at,
|
|
15679
|
+
heading, heading_level, content, line_start, line_end,
|
|
15680
|
+
NULL AS dense_rank, NULL AS dense_dist,
|
|
15681
|
+
CAST(bm25_rank AS INTEGER) AS bm25_rank, 'bm25' AS retriever
|
|
15682
|
+
FROM bm25_ranked
|
|
15683
|
+
)`;
|
|
15684
|
+
}
|
|
15685
|
+
params.push(rrfK, rrfK, recencyWeight, rrfK, denseScoreFloor, topK);
|
|
15686
|
+
const sql = `
|
|
15687
|
+
WITH
|
|
15688
|
+
${[denseCte, denseRankedCte, bm25Cte, bm25RankedCte, candidatesCte].filter(Boolean).join(",\n")}
|
|
15689
|
+
,
|
|
15690
|
+
recency_ranked AS (
|
|
15691
|
+
SELECT id, ROW_NUMBER() OVER (ORDER BY file_mtime_at DESC, source, id) AS recency_rank
|
|
15692
|
+
FROM all_candidates
|
|
15693
|
+
),
|
|
15694
|
+
scored AS (
|
|
15695
|
+
SELECT c.id, c.source, c.source_type, c.file_mtime_at,
|
|
15696
|
+
c.heading, c.heading_level, c.content, c.line_start, c.line_end,
|
|
15697
|
+
c.dense_rank, c.dense_dist, c.bm25_rank, r.recency_rank,
|
|
15698
|
+
c.retriever,
|
|
15699
|
+
COALESCE(1.0 / (? + c.dense_rank), 0.0) +
|
|
15700
|
+
COALESCE(1.0 / (? + c.bm25_rank), 0.0) +
|
|
15701
|
+
? * (1.0 / (? + r.recency_rank)) AS total_score
|
|
15702
|
+
FROM all_candidates c
|
|
15703
|
+
JOIN recency_ranked r ON r.id = c.id
|
|
15704
|
+
WHERE c.retriever != 'dense' OR (1.0 - c.dense_dist) >= ?
|
|
15705
|
+
),
|
|
15706
|
+
best_per_source AS (
|
|
15707
|
+
SELECT *,
|
|
15708
|
+
ROW_NUMBER() OVER (PARTITION BY source ORDER BY total_score DESC, source, id) AS source_rank
|
|
15709
|
+
FROM scored
|
|
15710
|
+
)
|
|
15711
|
+
SELECT id, source, source_type, file_mtime_at, heading, heading_level, content, line_start, line_end,
|
|
15712
|
+
dense_rank, dense_dist, bm25_rank, recency_rank, total_score, retriever
|
|
15713
|
+
FROM best_per_source
|
|
15714
|
+
WHERE source_rank = 1
|
|
15715
|
+
ORDER BY total_score DESC, source, id
|
|
15716
|
+
LIMIT ?
|
|
15717
|
+
`;
|
|
15718
|
+
const rows = this.db.prepare(sql).all(...params);
|
|
15719
|
+
if (rows.length === 0) return [];
|
|
15720
|
+
const denseActive = hasDense;
|
|
15721
|
+
const bm25Active = hasBm25;
|
|
15722
|
+
const recencyActive = rows.length > 0;
|
|
15723
|
+
const maxScore = ((denseActive ? 1 : 0) + (bm25Active ? 1 : 0) + (recencyActive ? recencyWeight : 0)) / (rrfK + 1);
|
|
15724
|
+
return rows.map((r) => {
|
|
15725
|
+
const chunk = this.mapMemoryChunkRow(r);
|
|
15726
|
+
const normalizedScore = maxScore > 0 ? r.total_score / maxScore : 0;
|
|
15727
|
+
const row = {
|
|
15728
|
+
chunk,
|
|
15729
|
+
score: normalizedScore,
|
|
15730
|
+
retriever: r.retriever
|
|
15731
|
+
};
|
|
15732
|
+
if (includeDebug) {
|
|
15733
|
+
const denseScore = r.dense_dist !== null ? Math.max(0, Math.min(1, 1 - r.dense_dist)) : void 0;
|
|
15734
|
+
const bm25Score = r.bm25_rank !== null ? 1 / (rrfK + r.bm25_rank) : void 0;
|
|
15735
|
+
const recencyScore = recencyWeight * (1 / (rrfK + r.recency_rank));
|
|
15736
|
+
const debug = {
|
|
15737
|
+
denseRank: r.dense_rank ?? void 0,
|
|
15738
|
+
denseScore,
|
|
15739
|
+
bm25Rank: r.bm25_rank ?? void 0,
|
|
15740
|
+
bm25Score,
|
|
15741
|
+
recencyRank: r.recency_rank,
|
|
15742
|
+
recencyScore,
|
|
15743
|
+
totalScore: r.total_score
|
|
15744
|
+
};
|
|
15745
|
+
row.debug = debug;
|
|
15746
|
+
}
|
|
15747
|
+
return row;
|
|
15748
|
+
});
|
|
15749
|
+
} catch (err) {
|
|
15750
|
+
console.warn("[memory-store] Hybrid search failed:", err);
|
|
15751
|
+
return [];
|
|
15752
|
+
}
|
|
15612
15753
|
}
|
|
15613
15754
|
get vecAvailable() {
|
|
15614
15755
|
return true;
|
|
@@ -15778,14 +15919,14 @@ var MemoryIndexer = class {
|
|
|
15778
15919
|
const existingChunkIds = this.store.hashesBySource(source);
|
|
15779
15920
|
let text;
|
|
15780
15921
|
try {
|
|
15781
|
-
text = fs6.
|
|
15922
|
+
text = await fs6.promises.readFile(absolutePath, "utf8");
|
|
15782
15923
|
} catch (err) {
|
|
15783
15924
|
console.warn(`[memory-indexer] Cannot read file ${absolutePath}:`, err);
|
|
15784
15925
|
return { indexed: 0, deleted: 0, skipped: existingChunkIds.size };
|
|
15785
15926
|
}
|
|
15786
15927
|
let fileMtimeAt;
|
|
15787
15928
|
try {
|
|
15788
|
-
fileMtimeAt = fs6.
|
|
15929
|
+
fileMtimeAt = (await fs6.promises.stat(absolutePath)).mtimeMs;
|
|
15789
15930
|
} catch (err) {
|
|
15790
15931
|
console.warn(`[memory-indexer] Cannot stat file ${absolutePath}:`, err);
|
|
15791
15932
|
fileMtimeAt = Date.now();
|
|
@@ -15818,22 +15959,30 @@ var MemoryIndexer = class {
|
|
|
15818
15959
|
}
|
|
15819
15960
|
async indexDirectory(rootDir, opts = {}) {
|
|
15820
15961
|
const totals = { indexed: 0, deleted: 0, skipped: 0 };
|
|
15821
|
-
|
|
15962
|
+
let rootExists;
|
|
15963
|
+
try {
|
|
15964
|
+
await fs6.promises.stat(rootDir);
|
|
15965
|
+
rootExists = true;
|
|
15966
|
+
} catch {
|
|
15967
|
+
rootExists = false;
|
|
15968
|
+
}
|
|
15969
|
+
if (!rootExists) return totals;
|
|
15822
15970
|
const relativeBase = opts.relativeBase ?? this.config.wikiDir;
|
|
15823
15971
|
const excludeFiles = new Set(opts.excludeFiles ?? []);
|
|
15824
15972
|
const files = [];
|
|
15825
|
-
const walk = (dirPath) => {
|
|
15973
|
+
const walk = async (dirPath) => {
|
|
15826
15974
|
let entries;
|
|
15827
15975
|
try {
|
|
15828
|
-
entries = fs6.
|
|
15976
|
+
entries = await fs6.promises.readdir(dirPath, { withFileTypes: true });
|
|
15829
15977
|
} catch (err) {
|
|
15830
15978
|
console.warn(`[memory-indexer] Cannot scan directory ${dirPath}:`, err);
|
|
15831
15979
|
return;
|
|
15832
15980
|
}
|
|
15981
|
+
entries.sort((a, b) => a.name.localeCompare(b.name));
|
|
15833
15982
|
for (const entry of entries) {
|
|
15834
15983
|
const entryPath = path8.join(dirPath, entry.name);
|
|
15835
15984
|
if (entry.isDirectory()) {
|
|
15836
|
-
walk(entryPath);
|
|
15985
|
+
await walk(entryPath);
|
|
15837
15986
|
continue;
|
|
15838
15987
|
}
|
|
15839
15988
|
if (entry.isFile() && /\.md$/i.test(entry.name) && !excludeFiles.has(entry.name)) {
|
|
@@ -15841,7 +15990,7 @@ var MemoryIndexer = class {
|
|
|
15841
15990
|
}
|
|
15842
15991
|
}
|
|
15843
15992
|
};
|
|
15844
|
-
walk(rootDir);
|
|
15993
|
+
await walk(rootDir);
|
|
15845
15994
|
const currentSources = new Set(files.map((file) => path8.relative(relativeBase, file)));
|
|
15846
15995
|
for (const stale of this.store.indexedSources()) {
|
|
15847
15996
|
if (!currentSources.has(stale)) {
|
|
@@ -15859,77 +16008,34 @@ var MemoryIndexer = class {
|
|
|
15859
16008
|
}
|
|
15860
16009
|
async search(query, topK) {
|
|
15861
16010
|
const fetchLimit = topK * FETCH_MULTIPLIER;
|
|
15862
|
-
let
|
|
16011
|
+
let embedding;
|
|
15863
16012
|
if (this.store.vecAvailable) {
|
|
15864
16013
|
try {
|
|
15865
|
-
const
|
|
15866
|
-
|
|
16014
|
+
const embeddings = await this.embedder.embed([query]);
|
|
16015
|
+
embedding = embeddings[0];
|
|
15867
16016
|
} catch (err) {
|
|
15868
16017
|
console.warn("[memory-indexer] Dense search embedding failed:", err);
|
|
15869
16018
|
}
|
|
15870
16019
|
}
|
|
15871
|
-
const
|
|
15872
|
-
|
|
15873
|
-
|
|
15874
|
-
|
|
15875
|
-
|
|
15876
|
-
|
|
15877
|
-
|
|
15878
|
-
|
|
15879
|
-
const unionIndexOf = new Map(unionArray.map((id, idx) => [id, idx]));
|
|
15880
|
-
const recencyOrdered = [...unionArray].sort((a, b) => {
|
|
15881
|
-
const mtimeA = chunkMap.get(a)?.fileMtimeAt ?? 0;
|
|
15882
|
-
const mtimeB = chunkMap.get(b)?.fileMtimeAt ?? 0;
|
|
15883
|
-
if (mtimeB !== mtimeA) return mtimeB - mtimeA;
|
|
15884
|
-
return (unionIndexOf.get(a) ?? 0) - (unionIndexOf.get(b) ?? 0);
|
|
15885
|
-
});
|
|
15886
|
-
const scoreMap = /* @__PURE__ */ new Map();
|
|
15887
|
-
denseResults.forEach((r, rank) => {
|
|
15888
|
-
scoreMap.set(r.id, { dense: 1 / (RRF_K + rank + 1), bm25: 0, recency: 0 });
|
|
15889
|
-
});
|
|
15890
|
-
bm25Results.forEach((r, rank) => {
|
|
15891
|
-
const entry = scoreMap.get(r.id) ?? { dense: 0, bm25: 0, recency: 0 };
|
|
15892
|
-
entry.bm25 = 1 / (RRF_K + rank + 1);
|
|
15893
|
-
scoreMap.set(r.id, entry);
|
|
15894
|
-
});
|
|
15895
|
-
recencyOrdered.forEach((id, rank) => {
|
|
15896
|
-
const entry = scoreMap.get(id);
|
|
15897
|
-
if (!entry) return;
|
|
15898
|
-
entry.recency = RECENCY_WEIGHT * (1 / (RRF_K + rank + 1));
|
|
16020
|
+
const rows = this.store.searchHybrid({
|
|
16021
|
+
query,
|
|
16022
|
+
embedding,
|
|
16023
|
+
topK,
|
|
16024
|
+
fetchLimit,
|
|
16025
|
+
denseScoreFloor: DENSE_SCORE_FLOOR,
|
|
16026
|
+
recencyWeight: RECENCY_WEIGHT,
|
|
16027
|
+
rrfK: RRF_K
|
|
15899
16028
|
});
|
|
15900
|
-
|
|
15901
|
-
const bm25Active = bm25Results.length > 0;
|
|
15902
|
-
const recencyActive = unionIds.size > 0;
|
|
15903
|
-
const maxScore = ((denseActive ? 1 : 0) + (bm25Active ? 1 : 0) + (recencyActive ? RECENCY_WEIGHT : 0)) / (RRF_K + 1);
|
|
15904
|
-
const ranked = [...scoreMap.entries()].map(([id, scores]) => ({
|
|
15905
|
-
id,
|
|
15906
|
-
totalScore: scores.dense + scores.bm25 + scores.recency,
|
|
15907
|
-
hasDense: scores.dense > 0,
|
|
15908
|
-
hasBm25: scores.bm25 > 0,
|
|
15909
|
-
isDenseOnly: denseIds.has(id) && !bm25Ids.has(id)
|
|
15910
|
-
})).filter((candidate) => {
|
|
15911
|
-
if (!candidate.isDenseOnly) return true;
|
|
15912
|
-
const retrieval = denseRetrievalScore.get(candidate.id);
|
|
15913
|
-
if (retrieval === void 0) return true;
|
|
15914
|
-
return retrieval >= DENSE_SCORE_FLOOR;
|
|
15915
|
-
}).sort((a, b) => b.totalScore - a.totalScore);
|
|
16029
|
+
if (rows.length === 0) return [];
|
|
15916
16030
|
const results = [];
|
|
15917
|
-
const
|
|
15918
|
-
for (const r of ranked) {
|
|
15919
|
-
if (results.length >= topK) break;
|
|
15920
|
-
const chunk = chunkMap.get(r.id);
|
|
15921
|
-
if (!chunk) continue;
|
|
15922
|
-
if (seenSources.has(chunk.source)) continue;
|
|
15923
|
-
seenSources.add(chunk.source);
|
|
15924
|
-
const normalizedScore = maxScore > 0 ? r.totalScore / maxScore : 0;
|
|
15925
|
-
const retriever = r.hasDense && r.hasBm25 ? "both" : r.hasDense ? "dense" : "bm25";
|
|
16031
|
+
for (const row of rows) {
|
|
15926
16032
|
let contentSource = "file";
|
|
15927
16033
|
try {
|
|
15928
|
-
chunk.content = fs6.
|
|
16034
|
+
row.chunk.content = await fs6.promises.readFile(path8.join(this.config.wikiDir, row.chunk.source), "utf8");
|
|
15929
16035
|
} catch {
|
|
15930
16036
|
contentSource = "fallback";
|
|
15931
16037
|
}
|
|
15932
|
-
results.push({ chunk, score:
|
|
16038
|
+
results.push({ chunk: row.chunk, score: row.score, retriever: row.retriever, contentSource });
|
|
15933
16039
|
}
|
|
15934
16040
|
return results;
|
|
15935
16041
|
}
|
|
@@ -15938,13 +16044,13 @@ var MemoryIndexer = class {
|
|
|
15938
16044
|
const rawDir = path8.join(this.config.wikiDir, "raw");
|
|
15939
16045
|
const savePath = path8.join(rawDir, `conv_save_${datePart}.md`);
|
|
15940
16046
|
const lockPath = `${savePath}.lock`;
|
|
15941
|
-
fs6.
|
|
16047
|
+
await fs6.promises.mkdir(rawDir, { recursive: true });
|
|
15942
16048
|
const acquired = await acquireLock(lockPath, LOCK_TIMEOUT_MS, LOCK_RETRY_MS);
|
|
15943
16049
|
if (!acquired) {
|
|
15944
16050
|
console.warn("[memory-indexer] Could not acquire write lock, writing anyway (fail-open)");
|
|
15945
16051
|
}
|
|
15946
16052
|
try {
|
|
15947
|
-
fs6.
|
|
16053
|
+
await fs6.promises.appendFile(savePath, `
|
|
15948
16054
|
${content}
|
|
15949
16055
|
`, "utf8");
|
|
15950
16056
|
} finally {
|
package/dist/mcp/memory.js
CHANGED
|
@@ -51,7 +51,7 @@ function registerMemoryToolHandlers(server, indexer, store, config, workspaceRoo
|
|
|
51
51
|
const blocks = [];
|
|
52
52
|
for (const row of rows) {
|
|
53
53
|
try {
|
|
54
|
-
const content = fs.
|
|
54
|
+
const content = await fs.promises.readFile(path.join(config.wikiDir, row.source), 'utf8');
|
|
55
55
|
const displaySource = row.source.replace(/^compiled\//, '');
|
|
56
56
|
blocks.push(`### ${displaySource}\n${content}`);
|
|
57
57
|
}
|