@hanhnd/agent-kit 1.0.37 → 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 +241 -112
- package/dist/mcp/memory.js +1 -1
- package/dist/server.js +244 -115
- package/dist/services/digest/digest-processor.test.js +1 -3
- package/dist/services/memory/constants.d.ts +3 -0
- package/dist/services/memory/constants.js +6 -0
- package/dist/services/memory/indexer.js +37 -54
- package/dist/services/memory/indexer.test.js +229 -231
- package/dist/services/memory/store.d.ts +4 -11
- package/dist/services/memory/store.js +206 -72
- 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
|
@@ -15036,6 +15036,9 @@ var DEFAULT_MEMORY_CONFIG = {
|
|
|
15036
15036
|
var LOCK_RETRY_MS = 50;
|
|
15037
15037
|
var LOCK_TIMEOUT_MS = 500;
|
|
15038
15038
|
var RRF_K = 60;
|
|
15039
|
+
var FETCH_MULTIPLIER = 4;
|
|
15040
|
+
var RECENCY_WEIGHT = 0.5;
|
|
15041
|
+
var DENSE_SCORE_FLOOR = 0.2;
|
|
15039
15042
|
|
|
15040
15043
|
// src/core/config/index.ts
|
|
15041
15044
|
function resolveMemoryConfig(settings, workspaceRoot) {
|
|
@@ -15456,6 +15459,7 @@ var MemoryStore = class {
|
|
|
15456
15459
|
return 0;
|
|
15457
15460
|
}
|
|
15458
15461
|
upsert(chunks, embeddings) {
|
|
15462
|
+
const deleteChunk = this.db.prepare(`DELETE FROM memory_chunks WHERE id = ?`);
|
|
15459
15463
|
const insertChunk = this.db.prepare(`
|
|
15460
15464
|
INSERT OR REPLACE INTO memory_chunks
|
|
15461
15465
|
(id, source, source_type, heading, heading_level, content, line_start, line_end, indexed_at, file_mtime_at, embedding)
|
|
@@ -15466,7 +15470,7 @@ var MemoryStore = class {
|
|
|
15466
15470
|
const chunk = chunks[i];
|
|
15467
15471
|
try {
|
|
15468
15472
|
const vectorBinding = this.toVectorBinding(embeddings[i]);
|
|
15469
|
-
|
|
15473
|
+
deleteChunk.run(chunk.id);
|
|
15470
15474
|
insertChunk.run(
|
|
15471
15475
|
chunk.id,
|
|
15472
15476
|
chunk.source,
|
|
@@ -15495,15 +15499,10 @@ var MemoryStore = class {
|
|
|
15495
15499
|
if (!embedding) throw new Error("missing embedding");
|
|
15496
15500
|
if (!(embedding instanceof Float32Array)) throw new Error("missing embedding");
|
|
15497
15501
|
if (embedding.length !== this.config.vectorDimension) throw new Error("embedding dimension mismatch");
|
|
15498
|
-
|
|
15499
|
-
if (!Number.isFinite(
|
|
15500
|
-
|
|
15501
|
-
|
|
15502
|
-
return JSON.stringify(values);
|
|
15503
|
-
}
|
|
15504
|
-
normalizeVectorDistance(distance) {
|
|
15505
|
-
if (distance <= 0) return 1;
|
|
15506
|
-
return Math.max(0, Math.min(1, 1 - distance));
|
|
15502
|
+
for (let i = 0; i < embedding.length; i++) {
|
|
15503
|
+
if (!Number.isFinite(embedding[i])) throw new Error("embedding contains non-finite value");
|
|
15504
|
+
}
|
|
15505
|
+
return Buffer.from(embedding.buffer, embedding.byteOffset, embedding.byteLength);
|
|
15507
15506
|
}
|
|
15508
15507
|
hashesBySource(source) {
|
|
15509
15508
|
const rows = this.db.prepare(`SELECT id FROM memory_chunks WHERE source = ?`).all(source);
|
|
@@ -15541,61 +15540,12 @@ var MemoryStore = class {
|
|
|
15541
15540
|
});
|
|
15542
15541
|
deleteAll();
|
|
15543
15542
|
}
|
|
15544
|
-
|
|
15545
|
-
try {
|
|
15546
|
-
const queryVector = this.toVectorBinding(embedding);
|
|
15547
|
-
const rows = this.db.prepare(
|
|
15548
|
-
`SELECT mc.id, vector_distance_cos(mc.embedding, vector32(?)) AS distance
|
|
15549
|
-
FROM vector_top_k('idx_memory_chunks_embedding', vector32(?), ?) AS vector_matches
|
|
15550
|
-
JOIN memory_chunks mc ON mc.rowid = vector_matches.id
|
|
15551
|
-
ORDER BY distance
|
|
15552
|
-
LIMIT ?`
|
|
15553
|
-
).all(queryVector, queryVector, limit, limit);
|
|
15554
|
-
return rows.map((r) => ({
|
|
15555
|
-
id: r.id,
|
|
15556
|
-
score: this.normalizeVectorDistance(r.distance)
|
|
15557
|
-
}));
|
|
15558
|
-
} catch (err) {
|
|
15559
|
-
console.warn("[memory-store] Dense search failed:", err);
|
|
15560
|
-
return [];
|
|
15561
|
-
}
|
|
15562
|
-
}
|
|
15563
|
-
searchBm25(query, limit) {
|
|
15564
|
-
if (!query.trim()) return [];
|
|
15543
|
+
buildFtsQuery(query) {
|
|
15565
15544
|
const tokens = query.trim().split(/\s+/).map((t) => t.replace(/[^a-zA-Z0-9_]/g, "").toLowerCase()).filter((t) => t.length > 1);
|
|
15566
|
-
|
|
15567
|
-
if (!ftsQuery) return [];
|
|
15568
|
-
try {
|
|
15569
|
-
const rows = this.db.prepare(
|
|
15570
|
-
`SELECT mc.id, bm25(memory_fts, 0.2, 2.0, 5.0) AS rank
|
|
15571
|
-
FROM memory_fts
|
|
15572
|
-
JOIN memory_chunks mc ON mc.rowid = memory_fts.rowid
|
|
15573
|
-
WHERE memory_fts MATCH ?
|
|
15574
|
-
ORDER BY rank
|
|
15575
|
-
LIMIT ?`
|
|
15576
|
-
).all(ftsQuery, limit);
|
|
15577
|
-
if (rows.length === 0) return [];
|
|
15578
|
-
const ranks = rows.map((r) => r.rank);
|
|
15579
|
-
const minRank = Math.min(...ranks);
|
|
15580
|
-
const maxRank = Math.max(...ranks);
|
|
15581
|
-
const range = maxRank - minRank;
|
|
15582
|
-
return rows.map((r) => ({
|
|
15583
|
-
id: r.id,
|
|
15584
|
-
score: range > 0 ? (maxRank - r.rank) / range : 1
|
|
15585
|
-
}));
|
|
15586
|
-
} catch (err) {
|
|
15587
|
-
console.warn("[memory-store] BM25 search failed:", err);
|
|
15588
|
-
return [];
|
|
15589
|
-
}
|
|
15545
|
+
return (0, import_stopword.removeStopwords)(tokens, import_stopword.eng).map((t) => `${t.replace(/s$/, "")}*`).join(" OR ");
|
|
15590
15546
|
}
|
|
15591
|
-
|
|
15592
|
-
|
|
15593
|
-
const placeholders = ids.map(() => "?").join(", ");
|
|
15594
|
-
const rows = this.db.prepare(
|
|
15595
|
-
`SELECT id, source, source_type, heading, heading_level, content, line_start, line_end, file_mtime_at
|
|
15596
|
-
FROM memory_chunks WHERE id IN (${placeholders})`
|
|
15597
|
-
).all(...ids);
|
|
15598
|
-
return rows.map((r) => ({
|
|
15547
|
+
mapMemoryChunkRow(r) {
|
|
15548
|
+
return {
|
|
15599
15549
|
id: r.id,
|
|
15600
15550
|
source: r.source,
|
|
15601
15551
|
sourceType: r.source_type,
|
|
@@ -15605,7 +15555,201 @@ var MemoryStore = class {
|
|
|
15605
15555
|
lineStart: r.line_start,
|
|
15606
15556
|
lineEnd: r.line_end,
|
|
15607
15557
|
fileMtimeAt: r.file_mtime_at
|
|
15608
|
-
}
|
|
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
|
+
}
|
|
15609
15753
|
}
|
|
15610
15754
|
get vecAvailable() {
|
|
15611
15755
|
return true;
|
|
@@ -15775,14 +15919,14 @@ var MemoryIndexer = class {
|
|
|
15775
15919
|
const existingChunkIds = this.store.hashesBySource(source);
|
|
15776
15920
|
let text;
|
|
15777
15921
|
try {
|
|
15778
|
-
text = fs6.
|
|
15922
|
+
text = await fs6.promises.readFile(absolutePath, "utf8");
|
|
15779
15923
|
} catch (err) {
|
|
15780
15924
|
console.warn(`[memory-indexer] Cannot read file ${absolutePath}:`, err);
|
|
15781
15925
|
return { indexed: 0, deleted: 0, skipped: existingChunkIds.size };
|
|
15782
15926
|
}
|
|
15783
15927
|
let fileMtimeAt;
|
|
15784
15928
|
try {
|
|
15785
|
-
fileMtimeAt = fs6.
|
|
15929
|
+
fileMtimeAt = (await fs6.promises.stat(absolutePath)).mtimeMs;
|
|
15786
15930
|
} catch (err) {
|
|
15787
15931
|
console.warn(`[memory-indexer] Cannot stat file ${absolutePath}:`, err);
|
|
15788
15932
|
fileMtimeAt = Date.now();
|
|
@@ -15815,22 +15959,30 @@ var MemoryIndexer = class {
|
|
|
15815
15959
|
}
|
|
15816
15960
|
async indexDirectory(rootDir, opts = {}) {
|
|
15817
15961
|
const totals = { indexed: 0, deleted: 0, skipped: 0 };
|
|
15818
|
-
|
|
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;
|
|
15819
15970
|
const relativeBase = opts.relativeBase ?? this.config.wikiDir;
|
|
15820
15971
|
const excludeFiles = new Set(opts.excludeFiles ?? []);
|
|
15821
15972
|
const files = [];
|
|
15822
|
-
const walk = (dirPath) => {
|
|
15973
|
+
const walk = async (dirPath) => {
|
|
15823
15974
|
let entries;
|
|
15824
15975
|
try {
|
|
15825
|
-
entries = fs6.
|
|
15976
|
+
entries = await fs6.promises.readdir(dirPath, { withFileTypes: true });
|
|
15826
15977
|
} catch (err) {
|
|
15827
15978
|
console.warn(`[memory-indexer] Cannot scan directory ${dirPath}:`, err);
|
|
15828
15979
|
return;
|
|
15829
15980
|
}
|
|
15981
|
+
entries.sort((a, b) => a.name.localeCompare(b.name));
|
|
15830
15982
|
for (const entry of entries) {
|
|
15831
15983
|
const entryPath = path8.join(dirPath, entry.name);
|
|
15832
15984
|
if (entry.isDirectory()) {
|
|
15833
|
-
walk(entryPath);
|
|
15985
|
+
await walk(entryPath);
|
|
15834
15986
|
continue;
|
|
15835
15987
|
}
|
|
15836
15988
|
if (entry.isFile() && /\.md$/i.test(entry.name) && !excludeFiles.has(entry.name)) {
|
|
@@ -15838,7 +15990,7 @@ var MemoryIndexer = class {
|
|
|
15838
15990
|
}
|
|
15839
15991
|
}
|
|
15840
15992
|
};
|
|
15841
|
-
walk(rootDir);
|
|
15993
|
+
await walk(rootDir);
|
|
15842
15994
|
const currentSources = new Set(files.map((file) => path8.relative(relativeBase, file)));
|
|
15843
15995
|
for (const stale of this.store.indexedSources()) {
|
|
15844
15996
|
if (!currentSources.has(stale)) {
|
|
@@ -15855,58 +16007,35 @@ var MemoryIndexer = class {
|
|
|
15855
16007
|
return totals;
|
|
15856
16008
|
}
|
|
15857
16009
|
async search(query, topK) {
|
|
15858
|
-
const fetchLimit = topK *
|
|
15859
|
-
let
|
|
16010
|
+
const fetchLimit = topK * FETCH_MULTIPLIER;
|
|
16011
|
+
let embedding;
|
|
15860
16012
|
if (this.store.vecAvailable) {
|
|
15861
16013
|
try {
|
|
15862
|
-
const
|
|
15863
|
-
|
|
16014
|
+
const embeddings = await this.embedder.embed([query]);
|
|
16015
|
+
embedding = embeddings[0];
|
|
15864
16016
|
} catch (err) {
|
|
15865
16017
|
console.warn("[memory-indexer] Dense search embedding failed:", err);
|
|
15866
16018
|
}
|
|
15867
16019
|
}
|
|
15868
|
-
const
|
|
15869
|
-
|
|
15870
|
-
|
|
15871
|
-
|
|
15872
|
-
|
|
15873
|
-
|
|
15874
|
-
|
|
15875
|
-
|
|
15876
|
-
});
|
|
15877
|
-
bm25Results.forEach((r, rank) => {
|
|
15878
|
-
const entry = scoreMap.get(r.id) ?? { dense: 0, bm25: 0 };
|
|
15879
|
-
entry.bm25 = 1 / (RRF_K + rank + 1);
|
|
15880
|
-
scoreMap.set(r.id, entry);
|
|
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
|
|
15881
16028
|
});
|
|
15882
|
-
|
|
15883
|
-
const numRetrievers = hasDense ? 2 : 1;
|
|
15884
|
-
const maxScore = numRetrievers / (RRF_K + 1);
|
|
15885
|
-
const ranked = [...scoreMap.entries()].map(([id, scores]) => ({
|
|
15886
|
-
id,
|
|
15887
|
-
totalScore: scores.dense + scores.bm25,
|
|
15888
|
-
hasDense: scores.dense > 0,
|
|
15889
|
-
hasBm25: scores.bm25 > 0
|
|
15890
|
-
})).sort((a, b) => b.totalScore - a.totalScore);
|
|
15891
|
-
const chunks = this.store.getChunksByIds(ranked.map((r) => r.id));
|
|
15892
|
-
const chunkMap = new Map(chunks.map((c) => [c.id, c]));
|
|
16029
|
+
if (rows.length === 0) return [];
|
|
15893
16030
|
const results = [];
|
|
15894
|
-
const
|
|
15895
|
-
for (const r of ranked) {
|
|
15896
|
-
if (results.length >= topK) break;
|
|
15897
|
-
const chunk = chunkMap.get(r.id);
|
|
15898
|
-
if (!chunk) continue;
|
|
15899
|
-
if (seenSources.has(chunk.source)) continue;
|
|
15900
|
-
seenSources.add(chunk.source);
|
|
15901
|
-
const normalizedScore = maxScore > 0 ? r.totalScore / maxScore : 0;
|
|
15902
|
-
const retriever = r.hasDense && r.hasBm25 ? "both" : r.hasDense ? "dense" : "bm25";
|
|
16031
|
+
for (const row of rows) {
|
|
15903
16032
|
let contentSource = "file";
|
|
15904
16033
|
try {
|
|
15905
|
-
chunk.content = fs6.
|
|
16034
|
+
row.chunk.content = await fs6.promises.readFile(path8.join(this.config.wikiDir, row.chunk.source), "utf8");
|
|
15906
16035
|
} catch {
|
|
15907
16036
|
contentSource = "fallback";
|
|
15908
16037
|
}
|
|
15909
|
-
results.push({ chunk, score:
|
|
16038
|
+
results.push({ chunk: row.chunk, score: row.score, retriever: row.retriever, contentSource });
|
|
15910
16039
|
}
|
|
15911
16040
|
return results;
|
|
15912
16041
|
}
|
|
@@ -15915,13 +16044,13 @@ var MemoryIndexer = class {
|
|
|
15915
16044
|
const rawDir = path8.join(this.config.wikiDir, "raw");
|
|
15916
16045
|
const savePath = path8.join(rawDir, `conv_save_${datePart}.md`);
|
|
15917
16046
|
const lockPath = `${savePath}.lock`;
|
|
15918
|
-
fs6.
|
|
16047
|
+
await fs6.promises.mkdir(rawDir, { recursive: true });
|
|
15919
16048
|
const acquired = await acquireLock(lockPath, LOCK_TIMEOUT_MS, LOCK_RETRY_MS);
|
|
15920
16049
|
if (!acquired) {
|
|
15921
16050
|
console.warn("[memory-indexer] Could not acquire write lock, writing anyway (fail-open)");
|
|
15922
16051
|
}
|
|
15923
16052
|
try {
|
|
15924
|
-
fs6.
|
|
16053
|
+
await fs6.promises.appendFile(savePath, `
|
|
15925
16054
|
${content}
|
|
15926
16055
|
`, "utf8");
|
|
15927
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
|
}
|