@hanhnd/agent-kit 1.0.38 → 1.0.40
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 +286 -140
- package/dist/entrypoints/server.js +3 -3
- package/dist/mcp/memory.d.ts +4 -9
- package/dist/mcp/memory.js +43 -19
- package/dist/server.js +445 -212
- package/dist/services/digest/digest-processor.test.js +1 -3
- package/dist/services/memory/index.d.ts +3 -1
- package/dist/services/memory/index.js +1 -0
- package/dist/services/memory/indexer.d.ts +5 -6
- package/dist/services/memory/indexer.js +71 -101
- package/dist/services/memory/indexer.test.js +347 -572
- package/dist/services/memory/memory.test.js +58 -0
- 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/subsystem.d.ts +19 -0
- package/dist/services/memory/subsystem.js +67 -0
- package/dist/services/memory/types.d.ts +48 -0
- package/dist/utils/async.d.ts +1 -0
- package/dist/utils/async.js +17 -0
- package/dist/utils/async.test.d.ts +1 -0
- package/dist/utils/async.test.js +48 -0
- package/package.json +1 -1
package/dist/server.js
CHANGED
|
@@ -3222,8 +3222,8 @@ var require_utils = __commonJS({
|
|
|
3222
3222
|
}
|
|
3223
3223
|
return ind;
|
|
3224
3224
|
}
|
|
3225
|
-
function removeDotSegments(
|
|
3226
|
-
let input =
|
|
3225
|
+
function removeDotSegments(path16) {
|
|
3226
|
+
let input = path16;
|
|
3227
3227
|
const output = [];
|
|
3228
3228
|
let nextSlash = -1;
|
|
3229
3229
|
let len = 0;
|
|
@@ -3422,8 +3422,8 @@ var require_schemes = __commonJS({
|
|
|
3422
3422
|
wsComponent.secure = void 0;
|
|
3423
3423
|
}
|
|
3424
3424
|
if (wsComponent.resourceName) {
|
|
3425
|
-
const [
|
|
3426
|
-
wsComponent.path =
|
|
3425
|
+
const [path16, query] = wsComponent.resourceName.split("?");
|
|
3426
|
+
wsComponent.path = path16 && path16 !== "/" ? path16 : void 0;
|
|
3427
3427
|
wsComponent.query = query;
|
|
3428
3428
|
wsComponent.resourceName = void 0;
|
|
3429
3429
|
}
|
|
@@ -22164,8 +22164,8 @@ function getErrorMap() {
|
|
|
22164
22164
|
|
|
22165
22165
|
// ../node_modules/zod/v3/helpers/parseUtil.js
|
|
22166
22166
|
var makeIssue = (params) => {
|
|
22167
|
-
const { data, path:
|
|
22168
|
-
const fullPath = [...
|
|
22167
|
+
const { data, path: path16, errorMaps, issueData } = params;
|
|
22168
|
+
const fullPath = [...path16, ...issueData.path || []];
|
|
22169
22169
|
const fullIssue = {
|
|
22170
22170
|
...issueData,
|
|
22171
22171
|
path: fullPath
|
|
@@ -22281,11 +22281,11 @@ var errorUtil;
|
|
|
22281
22281
|
|
|
22282
22282
|
// ../node_modules/zod/v3/types.js
|
|
22283
22283
|
var ParseInputLazyPath = class {
|
|
22284
|
-
constructor(parent, value,
|
|
22284
|
+
constructor(parent, value, path16, key) {
|
|
22285
22285
|
this._cachedPath = [];
|
|
22286
22286
|
this.parent = parent;
|
|
22287
22287
|
this.data = value;
|
|
22288
|
-
this._path =
|
|
22288
|
+
this._path = path16;
|
|
22289
22289
|
this._key = key;
|
|
22290
22290
|
}
|
|
22291
22291
|
get path() {
|
|
@@ -25923,15 +25923,15 @@ function assignProp(target, prop, value) {
|
|
|
25923
25923
|
configurable: true
|
|
25924
25924
|
});
|
|
25925
25925
|
}
|
|
25926
|
-
function getElementAtPath(obj,
|
|
25927
|
-
if (!
|
|
25926
|
+
function getElementAtPath(obj, path16) {
|
|
25927
|
+
if (!path16)
|
|
25928
25928
|
return obj;
|
|
25929
|
-
return
|
|
25929
|
+
return path16.reduce((acc, key) => acc?.[key], obj);
|
|
25930
25930
|
}
|
|
25931
25931
|
function promiseAllObject(promisesObj) {
|
|
25932
25932
|
const keys = Object.keys(promisesObj);
|
|
25933
|
-
const
|
|
25934
|
-
return Promise.all(
|
|
25933
|
+
const promises3 = keys.map((key) => promisesObj[key]);
|
|
25934
|
+
return Promise.all(promises3).then((results) => {
|
|
25935
25935
|
const resolvedObj = {};
|
|
25936
25936
|
for (let i = 0; i < keys.length; i++) {
|
|
25937
25937
|
resolvedObj[keys[i]] = results[i];
|
|
@@ -26246,11 +26246,11 @@ function aborted(x, startIndex = 0) {
|
|
|
26246
26246
|
}
|
|
26247
26247
|
return false;
|
|
26248
26248
|
}
|
|
26249
|
-
function prefixIssues(
|
|
26249
|
+
function prefixIssues(path16, issues) {
|
|
26250
26250
|
return issues.map((iss) => {
|
|
26251
26251
|
var _a;
|
|
26252
26252
|
(_a = iss).path ?? (_a.path = []);
|
|
26253
|
-
iss.path.unshift(
|
|
26253
|
+
iss.path.unshift(path16);
|
|
26254
26254
|
return iss;
|
|
26255
26255
|
});
|
|
26256
26256
|
}
|
|
@@ -36777,7 +36777,10 @@ ${ticket.fields.labels?.join(", ") || "None"}`;
|
|
|
36777
36777
|
|
|
36778
36778
|
// src/mcp/memory.ts
|
|
36779
36779
|
import * as fs11 from "fs";
|
|
36780
|
-
import * as
|
|
36780
|
+
import * as path15 from "path";
|
|
36781
|
+
|
|
36782
|
+
// src/services/memory/subsystem.ts
|
|
36783
|
+
import * as path10 from "path";
|
|
36781
36784
|
|
|
36782
36785
|
// src/services/memory/embedder.ts
|
|
36783
36786
|
var EmbedderError = class extends Error {
|
|
@@ -36993,6 +36996,37 @@ ${sectionText}` : sectionText;
|
|
|
36993
36996
|
return chunks;
|
|
36994
36997
|
}
|
|
36995
36998
|
|
|
36999
|
+
// src/utils/async.ts
|
|
37000
|
+
async function withTimeout(promise, timeoutMs, createError) {
|
|
37001
|
+
let timer;
|
|
37002
|
+
const timeout = new Promise((_resolve, reject) => {
|
|
37003
|
+
timer = setTimeout(() => {
|
|
37004
|
+
reject(createError());
|
|
37005
|
+
}, timeoutMs);
|
|
37006
|
+
});
|
|
37007
|
+
try {
|
|
37008
|
+
return await Promise.race([promise, timeout]);
|
|
37009
|
+
} finally {
|
|
37010
|
+
if (timer) clearTimeout(timer);
|
|
37011
|
+
}
|
|
37012
|
+
}
|
|
37013
|
+
async function mapLimit(items, limit, worker) {
|
|
37014
|
+
if (items.length === 0) return [];
|
|
37015
|
+
const concurrency = Math.max(1, Math.floor(limit));
|
|
37016
|
+
const results = new Array(items.length);
|
|
37017
|
+
let nextIndex = 0;
|
|
37018
|
+
async function runWorker() {
|
|
37019
|
+
while (nextIndex < items.length) {
|
|
37020
|
+
const currentIndex = nextIndex;
|
|
37021
|
+
nextIndex++;
|
|
37022
|
+
results[currentIndex] = await worker(items[currentIndex], currentIndex);
|
|
37023
|
+
}
|
|
37024
|
+
}
|
|
37025
|
+
const runners = Array.from({ length: Math.min(concurrency, items.length) }, () => runWorker());
|
|
37026
|
+
await Promise.all(runners);
|
|
37027
|
+
return results;
|
|
37028
|
+
}
|
|
37029
|
+
|
|
36996
37030
|
// src/services/memory/indexer.ts
|
|
36997
37031
|
function deriveSourceType(source) {
|
|
36998
37032
|
if (source.startsWith("compiled/provisional/conversation-digests/")) return "digest";
|
|
@@ -37009,21 +37043,31 @@ var MemoryIndexer = class {
|
|
|
37009
37043
|
}
|
|
37010
37044
|
_ready = false;
|
|
37011
37045
|
async indexFile(absolutePath) {
|
|
37012
|
-
|
|
37046
|
+
const mutation = await this.prepareIndexFileRelativeTo(absolutePath, this.config.wikiDir);
|
|
37047
|
+
return this.commitPreparedMutation(mutation);
|
|
37048
|
+
}
|
|
37049
|
+
makeSkippedMutation(source, skipped) {
|
|
37050
|
+
return {
|
|
37051
|
+
source,
|
|
37052
|
+
chunksToUpsert: [],
|
|
37053
|
+
embeddings: [],
|
|
37054
|
+
idsToDelete: [],
|
|
37055
|
+
stats: { indexed: 0, deleted: 0, skipped }
|
|
37056
|
+
};
|
|
37013
37057
|
}
|
|
37014
|
-
async
|
|
37058
|
+
async prepareIndexFileRelativeTo(absolutePath, sourceRoot) {
|
|
37015
37059
|
const source = path8.relative(sourceRoot, absolutePath);
|
|
37016
37060
|
const existingChunkIds = this.store.hashesBySource(source);
|
|
37017
37061
|
let text;
|
|
37018
37062
|
try {
|
|
37019
|
-
text = fs6.
|
|
37063
|
+
text = await fs6.promises.readFile(absolutePath, "utf8");
|
|
37020
37064
|
} catch (err) {
|
|
37021
37065
|
console.warn(`[memory-indexer] Cannot read file ${absolutePath}:`, err);
|
|
37022
|
-
return
|
|
37066
|
+
return this.makeSkippedMutation(source, existingChunkIds.size);
|
|
37023
37067
|
}
|
|
37024
37068
|
let fileMtimeAt;
|
|
37025
37069
|
try {
|
|
37026
|
-
fileMtimeAt = fs6.
|
|
37070
|
+
fileMtimeAt = (await fs6.promises.stat(absolutePath)).mtimeMs;
|
|
37027
37071
|
} catch (err) {
|
|
37028
37072
|
console.warn(`[memory-indexer] Cannot stat file ${absolutePath}:`, err);
|
|
37029
37073
|
fileMtimeAt = Date.now();
|
|
@@ -37039,39 +37083,56 @@ var MemoryIndexer = class {
|
|
|
37039
37083
|
embeddings = await this.embedder.embed(toIndex.map((c) => c.content));
|
|
37040
37084
|
} catch (err) {
|
|
37041
37085
|
console.warn(`[memory-indexer] Embedding failed for ${absolutePath}:`, err);
|
|
37042
|
-
return
|
|
37086
|
+
return this.makeSkippedMutation(source, existingChunkIds.size);
|
|
37043
37087
|
}
|
|
37044
37088
|
}
|
|
37045
|
-
if (toIndex.length > 0) {
|
|
37046
|
-
this.store.upsert(toIndex, embeddings);
|
|
37047
|
-
}
|
|
37048
|
-
if (toDelete.length > 0) {
|
|
37049
|
-
this.store.deleteByIds(toDelete);
|
|
37050
|
-
}
|
|
37051
37089
|
return {
|
|
37052
|
-
|
|
37053
|
-
|
|
37054
|
-
|
|
37090
|
+
source,
|
|
37091
|
+
chunksToUpsert: toIndex,
|
|
37092
|
+
embeddings,
|
|
37093
|
+
idsToDelete: toDelete,
|
|
37094
|
+
stats: {
|
|
37095
|
+
indexed: toIndex.length,
|
|
37096
|
+
deleted: toDelete.length,
|
|
37097
|
+
skipped: allChunks.length - toIndex.length
|
|
37098
|
+
}
|
|
37055
37099
|
};
|
|
37056
37100
|
}
|
|
37101
|
+
commitPreparedMutation(mutation) {
|
|
37102
|
+
if (mutation.chunksToUpsert.length > 0) {
|
|
37103
|
+
this.store.upsert(mutation.chunksToUpsert, mutation.embeddings);
|
|
37104
|
+
}
|
|
37105
|
+
if (mutation.idsToDelete.length > 0) {
|
|
37106
|
+
this.store.deleteByIds(mutation.idsToDelete);
|
|
37107
|
+
}
|
|
37108
|
+
return mutation.stats;
|
|
37109
|
+
}
|
|
37057
37110
|
async indexDirectory(rootDir, opts = {}) {
|
|
37058
37111
|
const totals = { indexed: 0, deleted: 0, skipped: 0 };
|
|
37059
|
-
|
|
37112
|
+
let rootExists;
|
|
37113
|
+
try {
|
|
37114
|
+
await fs6.promises.stat(rootDir);
|
|
37115
|
+
rootExists = true;
|
|
37116
|
+
} catch {
|
|
37117
|
+
rootExists = false;
|
|
37118
|
+
}
|
|
37119
|
+
if (!rootExists) return totals;
|
|
37060
37120
|
const relativeBase = opts.relativeBase ?? this.config.wikiDir;
|
|
37061
37121
|
const excludeFiles = new Set(opts.excludeFiles ?? []);
|
|
37062
37122
|
const files = [];
|
|
37063
|
-
const walk = (dirPath) => {
|
|
37123
|
+
const walk = async (dirPath) => {
|
|
37064
37124
|
let entries;
|
|
37065
37125
|
try {
|
|
37066
|
-
entries = fs6.
|
|
37126
|
+
entries = await fs6.promises.readdir(dirPath, { withFileTypes: true });
|
|
37067
37127
|
} catch (err) {
|
|
37068
37128
|
console.warn(`[memory-indexer] Cannot scan directory ${dirPath}:`, err);
|
|
37069
37129
|
return;
|
|
37070
37130
|
}
|
|
37131
|
+
entries.sort((a, b) => a.name.localeCompare(b.name));
|
|
37071
37132
|
for (const entry of entries) {
|
|
37072
37133
|
const entryPath = path8.join(dirPath, entry.name);
|
|
37073
37134
|
if (entry.isDirectory()) {
|
|
37074
|
-
walk(entryPath);
|
|
37135
|
+
await walk(entryPath);
|
|
37075
37136
|
continue;
|
|
37076
37137
|
}
|
|
37077
37138
|
if (entry.isFile() && /\.md$/i.test(entry.name) && !excludeFiles.has(entry.name)) {
|
|
@@ -37079,7 +37140,7 @@ var MemoryIndexer = class {
|
|
|
37079
37140
|
}
|
|
37080
37141
|
}
|
|
37081
37142
|
};
|
|
37082
|
-
walk(rootDir);
|
|
37143
|
+
await walk(rootDir);
|
|
37083
37144
|
const currentSources = new Set(files.map((file) => path8.relative(relativeBase, file)));
|
|
37084
37145
|
for (const stale of this.store.indexedSources()) {
|
|
37085
37146
|
if (!currentSources.has(stale)) {
|
|
@@ -37087,8 +37148,13 @@ var MemoryIndexer = class {
|
|
|
37087
37148
|
totals.deleted += 1;
|
|
37088
37149
|
}
|
|
37089
37150
|
}
|
|
37090
|
-
|
|
37091
|
-
|
|
37151
|
+
const preparedMutations = await mapLimit(
|
|
37152
|
+
files,
|
|
37153
|
+
opts.fileConcurrency ?? 2,
|
|
37154
|
+
(file) => this.prepareIndexFileRelativeTo(file, relativeBase)
|
|
37155
|
+
);
|
|
37156
|
+
for (const mutation of preparedMutations) {
|
|
37157
|
+
const stats = this.commitPreparedMutation(mutation);
|
|
37092
37158
|
totals.indexed += stats.indexed;
|
|
37093
37159
|
totals.deleted += stats.deleted;
|
|
37094
37160
|
totals.skipped += stats.skipped;
|
|
@@ -37097,77 +37163,34 @@ var MemoryIndexer = class {
|
|
|
37097
37163
|
}
|
|
37098
37164
|
async search(query, topK) {
|
|
37099
37165
|
const fetchLimit = topK * FETCH_MULTIPLIER;
|
|
37100
|
-
let
|
|
37166
|
+
let embedding;
|
|
37101
37167
|
if (this.store.vecAvailable) {
|
|
37102
37168
|
try {
|
|
37103
|
-
const
|
|
37104
|
-
|
|
37169
|
+
const embeddings = await this.embedder.embed([query]);
|
|
37170
|
+
embedding = embeddings[0];
|
|
37105
37171
|
} catch (err) {
|
|
37106
37172
|
console.warn("[memory-indexer] Dense search embedding failed:", err);
|
|
37107
37173
|
}
|
|
37108
37174
|
}
|
|
37109
|
-
const
|
|
37110
|
-
|
|
37111
|
-
|
|
37112
|
-
|
|
37113
|
-
|
|
37114
|
-
|
|
37115
|
-
|
|
37116
|
-
|
|
37117
|
-
const unionIndexOf = new Map(unionArray.map((id, idx) => [id, idx]));
|
|
37118
|
-
const recencyOrdered = [...unionArray].sort((a, b) => {
|
|
37119
|
-
const mtimeA = chunkMap.get(a)?.fileMtimeAt ?? 0;
|
|
37120
|
-
const mtimeB = chunkMap.get(b)?.fileMtimeAt ?? 0;
|
|
37121
|
-
if (mtimeB !== mtimeA) return mtimeB - mtimeA;
|
|
37122
|
-
return (unionIndexOf.get(a) ?? 0) - (unionIndexOf.get(b) ?? 0);
|
|
37175
|
+
const rows = this.store.searchHybrid({
|
|
37176
|
+
query,
|
|
37177
|
+
embedding,
|
|
37178
|
+
topK,
|
|
37179
|
+
fetchLimit,
|
|
37180
|
+
denseScoreFloor: DENSE_SCORE_FLOOR,
|
|
37181
|
+
recencyWeight: RECENCY_WEIGHT,
|
|
37182
|
+
rrfK: RRF_K
|
|
37123
37183
|
});
|
|
37124
|
-
|
|
37125
|
-
denseResults.forEach((r, rank) => {
|
|
37126
|
-
scoreMap.set(r.id, { dense: 1 / (RRF_K + rank + 1), bm25: 0, recency: 0 });
|
|
37127
|
-
});
|
|
37128
|
-
bm25Results.forEach((r, rank) => {
|
|
37129
|
-
const entry = scoreMap.get(r.id) ?? { dense: 0, bm25: 0, recency: 0 };
|
|
37130
|
-
entry.bm25 = 1 / (RRF_K + rank + 1);
|
|
37131
|
-
scoreMap.set(r.id, entry);
|
|
37132
|
-
});
|
|
37133
|
-
recencyOrdered.forEach((id, rank) => {
|
|
37134
|
-
const entry = scoreMap.get(id);
|
|
37135
|
-
if (!entry) return;
|
|
37136
|
-
entry.recency = RECENCY_WEIGHT * (1 / (RRF_K + rank + 1));
|
|
37137
|
-
});
|
|
37138
|
-
const denseActive = denseResults.length > 0;
|
|
37139
|
-
const bm25Active = bm25Results.length > 0;
|
|
37140
|
-
const recencyActive = unionIds.size > 0;
|
|
37141
|
-
const maxScore = ((denseActive ? 1 : 0) + (bm25Active ? 1 : 0) + (recencyActive ? RECENCY_WEIGHT : 0)) / (RRF_K + 1);
|
|
37142
|
-
const ranked = [...scoreMap.entries()].map(([id, scores]) => ({
|
|
37143
|
-
id,
|
|
37144
|
-
totalScore: scores.dense + scores.bm25 + scores.recency,
|
|
37145
|
-
hasDense: scores.dense > 0,
|
|
37146
|
-
hasBm25: scores.bm25 > 0,
|
|
37147
|
-
isDenseOnly: denseIds.has(id) && !bm25Ids.has(id)
|
|
37148
|
-
})).filter((candidate) => {
|
|
37149
|
-
if (!candidate.isDenseOnly) return true;
|
|
37150
|
-
const retrieval = denseRetrievalScore.get(candidate.id);
|
|
37151
|
-
if (retrieval === void 0) return true;
|
|
37152
|
-
return retrieval >= DENSE_SCORE_FLOOR;
|
|
37153
|
-
}).sort((a, b) => b.totalScore - a.totalScore);
|
|
37184
|
+
if (rows.length === 0) return [];
|
|
37154
37185
|
const results = [];
|
|
37155
|
-
const
|
|
37156
|
-
for (const r of ranked) {
|
|
37157
|
-
if (results.length >= topK) break;
|
|
37158
|
-
const chunk = chunkMap.get(r.id);
|
|
37159
|
-
if (!chunk) continue;
|
|
37160
|
-
if (seenSources.has(chunk.source)) continue;
|
|
37161
|
-
seenSources.add(chunk.source);
|
|
37162
|
-
const normalizedScore = maxScore > 0 ? r.totalScore / maxScore : 0;
|
|
37163
|
-
const retriever = r.hasDense && r.hasBm25 ? "both" : r.hasDense ? "dense" : "bm25";
|
|
37186
|
+
for (const row of rows) {
|
|
37164
37187
|
let contentSource = "file";
|
|
37165
37188
|
try {
|
|
37166
|
-
chunk.content = fs6.
|
|
37189
|
+
row.chunk.content = await fs6.promises.readFile(path8.join(this.config.wikiDir, row.chunk.source), "utf8");
|
|
37167
37190
|
} catch {
|
|
37168
37191
|
contentSource = "fallback";
|
|
37169
37192
|
}
|
|
37170
|
-
results.push({ chunk, score:
|
|
37193
|
+
results.push({ chunk: row.chunk, score: row.score, retriever: row.retriever, contentSource });
|
|
37171
37194
|
}
|
|
37172
37195
|
return results;
|
|
37173
37196
|
}
|
|
@@ -37176,13 +37199,13 @@ var MemoryIndexer = class {
|
|
|
37176
37199
|
const rawDir = path8.join(this.config.wikiDir, "raw");
|
|
37177
37200
|
const savePath = path8.join(rawDir, `conv_save_${datePart}.md`);
|
|
37178
37201
|
const lockPath = `${savePath}.lock`;
|
|
37179
|
-
fs6.
|
|
37202
|
+
await fs6.promises.mkdir(rawDir, { recursive: true });
|
|
37180
37203
|
const acquired = await acquireLock(lockPath, LOCK_TIMEOUT_MS, LOCK_RETRY_MS);
|
|
37181
37204
|
if (!acquired) {
|
|
37182
37205
|
console.warn("[memory-indexer] Could not acquire write lock, writing anyway (fail-open)");
|
|
37183
37206
|
}
|
|
37184
37207
|
try {
|
|
37185
|
-
fs6.
|
|
37208
|
+
await fs6.promises.appendFile(savePath, `
|
|
37186
37209
|
${content}
|
|
37187
37210
|
`, "utf8");
|
|
37188
37211
|
} finally {
|
|
@@ -37389,10 +37412,6 @@ var MemoryStore = class {
|
|
|
37389
37412
|
}
|
|
37390
37413
|
return Buffer.from(embedding.buffer, embedding.byteOffset, embedding.byteLength);
|
|
37391
37414
|
}
|
|
37392
|
-
normalizeVectorDistance(distance) {
|
|
37393
|
-
if (distance <= 0) return 1;
|
|
37394
|
-
return Math.max(0, Math.min(1, 1 - distance));
|
|
37395
|
-
}
|
|
37396
37415
|
hashesBySource(source) {
|
|
37397
37416
|
const rows = this.db.prepare(`SELECT id FROM memory_chunks WHERE source = ?`).all(source);
|
|
37398
37417
|
return new Set(rows.map((r) => r.id));
|
|
@@ -37429,61 +37448,12 @@ var MemoryStore = class {
|
|
|
37429
37448
|
});
|
|
37430
37449
|
deleteAll();
|
|
37431
37450
|
}
|
|
37432
|
-
|
|
37433
|
-
try {
|
|
37434
|
-
const queryVector = this.toVectorBinding(embedding);
|
|
37435
|
-
const rows = this.db.prepare(
|
|
37436
|
-
`SELECT mc.id, vector_distance_cos(mc.embedding, vector32(?)) AS distance
|
|
37437
|
-
FROM vector_top_k('idx_memory_chunks_embedding', vector32(?), ?) AS vector_matches
|
|
37438
|
-
JOIN memory_chunks mc ON mc.rowid = vector_matches.id
|
|
37439
|
-
ORDER BY distance
|
|
37440
|
-
LIMIT ?`
|
|
37441
|
-
).all(queryVector, queryVector, limit, limit);
|
|
37442
|
-
return rows.map((r) => ({
|
|
37443
|
-
id: r.id,
|
|
37444
|
-
score: this.normalizeVectorDistance(r.distance)
|
|
37445
|
-
}));
|
|
37446
|
-
} catch (err) {
|
|
37447
|
-
console.warn("[memory-store] Dense search failed:", err);
|
|
37448
|
-
return [];
|
|
37449
|
-
}
|
|
37450
|
-
}
|
|
37451
|
-
searchBm25(query, limit) {
|
|
37452
|
-
if (!query.trim()) return [];
|
|
37451
|
+
buildFtsQuery(query) {
|
|
37453
37452
|
const tokens = query.trim().split(/\s+/).map((t) => t.replace(/[^a-zA-Z0-9_]/g, "").toLowerCase()).filter((t) => t.length > 1);
|
|
37454
|
-
|
|
37455
|
-
if (!ftsQuery) return [];
|
|
37456
|
-
try {
|
|
37457
|
-
const rows = this.db.prepare(
|
|
37458
|
-
`SELECT mc.id, bm25(memory_fts, 0.2, 2.0, 5.0) AS rank
|
|
37459
|
-
FROM memory_fts
|
|
37460
|
-
JOIN memory_chunks mc ON mc.rowid = memory_fts.rowid
|
|
37461
|
-
WHERE memory_fts MATCH ?
|
|
37462
|
-
ORDER BY rank
|
|
37463
|
-
LIMIT ?`
|
|
37464
|
-
).all(ftsQuery, limit);
|
|
37465
|
-
if (rows.length === 0) return [];
|
|
37466
|
-
const ranks = rows.map((r) => r.rank);
|
|
37467
|
-
const minRank = Math.min(...ranks);
|
|
37468
|
-
const maxRank = Math.max(...ranks);
|
|
37469
|
-
const range = maxRank - minRank;
|
|
37470
|
-
return rows.map((r) => ({
|
|
37471
|
-
id: r.id,
|
|
37472
|
-
score: range > 0 ? (maxRank - r.rank) / range : 1
|
|
37473
|
-
}));
|
|
37474
|
-
} catch (err) {
|
|
37475
|
-
console.warn("[memory-store] BM25 search failed:", err);
|
|
37476
|
-
return [];
|
|
37477
|
-
}
|
|
37453
|
+
return (0, import_stopword.removeStopwords)(tokens, import_stopword.eng).map((t) => `${t.replace(/s$/, "")}*`).join(" OR ");
|
|
37478
37454
|
}
|
|
37479
|
-
|
|
37480
|
-
|
|
37481
|
-
const placeholders = ids.map(() => "?").join(", ");
|
|
37482
|
-
const rows = this.db.prepare(
|
|
37483
|
-
`SELECT id, source, source_type, heading, heading_level, content, line_start, line_end, file_mtime_at
|
|
37484
|
-
FROM memory_chunks WHERE id IN (${placeholders})`
|
|
37485
|
-
).all(...ids);
|
|
37486
|
-
return rows.map((r) => ({
|
|
37455
|
+
mapMemoryChunkRow(r) {
|
|
37456
|
+
return {
|
|
37487
37457
|
id: r.id,
|
|
37488
37458
|
source: r.source,
|
|
37489
37459
|
sourceType: r.source_type,
|
|
@@ -37493,7 +37463,201 @@ var MemoryStore = class {
|
|
|
37493
37463
|
lineStart: r.line_start,
|
|
37494
37464
|
lineEnd: r.line_end,
|
|
37495
37465
|
fileMtimeAt: r.file_mtime_at
|
|
37496
|
-
}
|
|
37466
|
+
};
|
|
37467
|
+
}
|
|
37468
|
+
searchHybrid(options) {
|
|
37469
|
+
const {
|
|
37470
|
+
query,
|
|
37471
|
+
embedding,
|
|
37472
|
+
topK,
|
|
37473
|
+
fetchLimit,
|
|
37474
|
+
denseScoreFloor,
|
|
37475
|
+
recencyWeight,
|
|
37476
|
+
rrfK,
|
|
37477
|
+
sourceType,
|
|
37478
|
+
sinceMtimeAt,
|
|
37479
|
+
includeDebug
|
|
37480
|
+
} = options;
|
|
37481
|
+
const ftsQuery = this.buildFtsQuery(query);
|
|
37482
|
+
const hasDense = embedding !== void 0;
|
|
37483
|
+
const hasBm25 = ftsQuery.length > 0;
|
|
37484
|
+
if (!hasDense && !hasBm25) return [];
|
|
37485
|
+
const hasFilters = sourceType !== void 0 || sinceMtimeAt !== void 0;
|
|
37486
|
+
try {
|
|
37487
|
+
const params = [];
|
|
37488
|
+
let denseCte = "";
|
|
37489
|
+
let denseRankedCte = "";
|
|
37490
|
+
if (hasDense) {
|
|
37491
|
+
const queryBuf = this.toVectorBinding(embedding);
|
|
37492
|
+
if (hasFilters) {
|
|
37493
|
+
const filterClauses = [];
|
|
37494
|
+
if (sourceType) {
|
|
37495
|
+
filterClauses.push("mc.source_type = ?");
|
|
37496
|
+
params.push(sourceType);
|
|
37497
|
+
}
|
|
37498
|
+
if (sinceMtimeAt !== void 0) {
|
|
37499
|
+
filterClauses.push("mc.file_mtime_at >= ?");
|
|
37500
|
+
params.push(sinceMtimeAt);
|
|
37501
|
+
}
|
|
37502
|
+
const whereClause = filterClauses.length > 0 ? `WHERE ${filterClauses.join(" AND ")}` : "";
|
|
37503
|
+
params.push(queryBuf);
|
|
37504
|
+
params.push(fetchLimit);
|
|
37505
|
+
denseCte = `dense_data AS (
|
|
37506
|
+
SELECT mc.id, mc.source, mc.source_type, mc.file_mtime_at,
|
|
37507
|
+
mc.heading, mc.heading_level, mc.content, mc.line_start, mc.line_end,
|
|
37508
|
+
vector_distance_cos(mc.embedding, vector32(?)) AS dense_dist
|
|
37509
|
+
FROM memory_chunks mc
|
|
37510
|
+
${whereClause}
|
|
37511
|
+
ORDER BY dense_dist
|
|
37512
|
+
LIMIT ?
|
|
37513
|
+
)`;
|
|
37514
|
+
} else {
|
|
37515
|
+
params.push(queryBuf);
|
|
37516
|
+
params.push(queryBuf);
|
|
37517
|
+
params.push(fetchLimit);
|
|
37518
|
+
denseCte = `dense_data AS (
|
|
37519
|
+
SELECT mc.id, mc.source, mc.source_type, mc.file_mtime_at,
|
|
37520
|
+
mc.heading, mc.heading_level, mc.content, mc.line_start, mc.line_end,
|
|
37521
|
+
vector_distance_cos(mc.embedding, vector32(?)) AS dense_dist
|
|
37522
|
+
FROM vector_top_k('idx_memory_chunks_embedding', vector32(?), ?) AS vtk
|
|
37523
|
+
JOIN memory_chunks mc ON mc.rowid = vtk.id
|
|
37524
|
+
)`;
|
|
37525
|
+
}
|
|
37526
|
+
denseRankedCte = `dense_ranked AS (
|
|
37527
|
+
SELECT *, ROW_NUMBER() OVER (ORDER BY dense_dist, id) AS dense_rank
|
|
37528
|
+
FROM dense_data
|
|
37529
|
+
)`;
|
|
37530
|
+
}
|
|
37531
|
+
let bm25Cte = "";
|
|
37532
|
+
let bm25RankedCte = "";
|
|
37533
|
+
if (hasBm25) {
|
|
37534
|
+
const bm25FilterClauses = [];
|
|
37535
|
+
if (sourceType) bm25FilterClauses.push("mc.source_type = ?");
|
|
37536
|
+
if (sinceMtimeAt !== void 0) bm25FilterClauses.push("mc.file_mtime_at >= ?");
|
|
37537
|
+
const bm25WhereExtra = bm25FilterClauses.length > 0 ? ` AND ${bm25FilterClauses.join(" AND ")}` : "";
|
|
37538
|
+
params.push(ftsQuery);
|
|
37539
|
+
if (sourceType) params.push(sourceType);
|
|
37540
|
+
if (sinceMtimeAt !== void 0) params.push(sinceMtimeAt);
|
|
37541
|
+
params.push(fetchLimit);
|
|
37542
|
+
bm25Cte = `bm25_data AS (
|
|
37543
|
+
SELECT mc.id, mc.source, mc.source_type, mc.file_mtime_at,
|
|
37544
|
+
mc.heading, mc.heading_level, mc.content, mc.line_start, mc.line_end,
|
|
37545
|
+
bm25(memory_fts, 0.2, 2.0, 5.0) AS bm25_neg
|
|
37546
|
+
FROM memory_fts
|
|
37547
|
+
JOIN memory_chunks mc ON mc.rowid = memory_fts.rowid
|
|
37548
|
+
WHERE memory_fts MATCH ?${bm25WhereExtra}
|
|
37549
|
+
LIMIT ?
|
|
37550
|
+
)`;
|
|
37551
|
+
bm25RankedCte = `bm25_ranked AS (
|
|
37552
|
+
SELECT *, ROW_NUMBER() OVER (ORDER BY bm25_neg, id) AS bm25_rank
|
|
37553
|
+
FROM bm25_data
|
|
37554
|
+
)`;
|
|
37555
|
+
}
|
|
37556
|
+
let candidatesCte = "";
|
|
37557
|
+
if (hasDense && hasBm25) {
|
|
37558
|
+
candidatesCte = `all_candidates AS (
|
|
37559
|
+
SELECT d.id, d.source, d.source_type, d.file_mtime_at,
|
|
37560
|
+
d.heading, d.heading_level, d.content, d.line_start, d.line_end,
|
|
37561
|
+
CAST(d.dense_rank AS INTEGER) AS dense_rank, d.dense_dist,
|
|
37562
|
+
b.bm25_rank,
|
|
37563
|
+
CASE WHEN b.id IS NOT NULL THEN 'both' ELSE 'dense' END AS retriever
|
|
37564
|
+
FROM dense_ranked d
|
|
37565
|
+
LEFT JOIN bm25_ranked b ON b.id = d.id
|
|
37566
|
+
UNION ALL
|
|
37567
|
+
SELECT b.id, b.source, b.source_type, b.file_mtime_at,
|
|
37568
|
+
b.heading, b.heading_level, b.content, b.line_start, b.line_end,
|
|
37569
|
+
NULL AS dense_rank, NULL AS dense_dist,
|
|
37570
|
+
CAST(b.bm25_rank AS INTEGER) AS bm25_rank,
|
|
37571
|
+
'bm25' AS retriever
|
|
37572
|
+
FROM bm25_ranked b
|
|
37573
|
+
LEFT JOIN dense_ranked d ON d.id = b.id
|
|
37574
|
+
WHERE d.id IS NULL
|
|
37575
|
+
)`;
|
|
37576
|
+
} else if (hasDense) {
|
|
37577
|
+
candidatesCte = `all_candidates AS (
|
|
37578
|
+
SELECT id, source, source_type, file_mtime_at,
|
|
37579
|
+
heading, heading_level, content, line_start, line_end,
|
|
37580
|
+
CAST(dense_rank AS INTEGER) AS dense_rank, dense_dist,
|
|
37581
|
+
NULL AS bm25_rank, 'dense' AS retriever
|
|
37582
|
+
FROM dense_ranked
|
|
37583
|
+
)`;
|
|
37584
|
+
} else {
|
|
37585
|
+
candidatesCte = `all_candidates AS (
|
|
37586
|
+
SELECT id, source, source_type, file_mtime_at,
|
|
37587
|
+
heading, heading_level, content, line_start, line_end,
|
|
37588
|
+
NULL AS dense_rank, NULL AS dense_dist,
|
|
37589
|
+
CAST(bm25_rank AS INTEGER) AS bm25_rank, 'bm25' AS retriever
|
|
37590
|
+
FROM bm25_ranked
|
|
37591
|
+
)`;
|
|
37592
|
+
}
|
|
37593
|
+
params.push(rrfK, rrfK, recencyWeight, rrfK, denseScoreFloor, topK);
|
|
37594
|
+
const sql = `
|
|
37595
|
+
WITH
|
|
37596
|
+
${[denseCte, denseRankedCte, bm25Cte, bm25RankedCte, candidatesCte].filter(Boolean).join(",\n")}
|
|
37597
|
+
,
|
|
37598
|
+
recency_ranked AS (
|
|
37599
|
+
SELECT id, ROW_NUMBER() OVER (ORDER BY file_mtime_at DESC, source, id) AS recency_rank
|
|
37600
|
+
FROM all_candidates
|
|
37601
|
+
),
|
|
37602
|
+
scored AS (
|
|
37603
|
+
SELECT c.id, c.source, c.source_type, c.file_mtime_at,
|
|
37604
|
+
c.heading, c.heading_level, c.content, c.line_start, c.line_end,
|
|
37605
|
+
c.dense_rank, c.dense_dist, c.bm25_rank, r.recency_rank,
|
|
37606
|
+
c.retriever,
|
|
37607
|
+
COALESCE(1.0 / (? + c.dense_rank), 0.0) +
|
|
37608
|
+
COALESCE(1.0 / (? + c.bm25_rank), 0.0) +
|
|
37609
|
+
? * (1.0 / (? + r.recency_rank)) AS total_score
|
|
37610
|
+
FROM all_candidates c
|
|
37611
|
+
JOIN recency_ranked r ON r.id = c.id
|
|
37612
|
+
WHERE c.retriever != 'dense' OR (1.0 - c.dense_dist) >= ?
|
|
37613
|
+
),
|
|
37614
|
+
best_per_source AS (
|
|
37615
|
+
SELECT *,
|
|
37616
|
+
ROW_NUMBER() OVER (PARTITION BY source ORDER BY total_score DESC, source, id) AS source_rank
|
|
37617
|
+
FROM scored
|
|
37618
|
+
)
|
|
37619
|
+
SELECT id, source, source_type, file_mtime_at, heading, heading_level, content, line_start, line_end,
|
|
37620
|
+
dense_rank, dense_dist, bm25_rank, recency_rank, total_score, retriever
|
|
37621
|
+
FROM best_per_source
|
|
37622
|
+
WHERE source_rank = 1
|
|
37623
|
+
ORDER BY total_score DESC, source, id
|
|
37624
|
+
LIMIT ?
|
|
37625
|
+
`;
|
|
37626
|
+
const rows = this.db.prepare(sql).all(...params);
|
|
37627
|
+
if (rows.length === 0) return [];
|
|
37628
|
+
const denseActive = hasDense;
|
|
37629
|
+
const bm25Active = hasBm25;
|
|
37630
|
+
const recencyActive = rows.length > 0;
|
|
37631
|
+
const maxScore = ((denseActive ? 1 : 0) + (bm25Active ? 1 : 0) + (recencyActive ? recencyWeight : 0)) / (rrfK + 1);
|
|
37632
|
+
return rows.map((r) => {
|
|
37633
|
+
const chunk = this.mapMemoryChunkRow(r);
|
|
37634
|
+
const normalizedScore = maxScore > 0 ? r.total_score / maxScore : 0;
|
|
37635
|
+
const row = {
|
|
37636
|
+
chunk,
|
|
37637
|
+
score: normalizedScore,
|
|
37638
|
+
retriever: r.retriever
|
|
37639
|
+
};
|
|
37640
|
+
if (includeDebug) {
|
|
37641
|
+
const denseScore = r.dense_dist !== null ? Math.max(0, Math.min(1, 1 - r.dense_dist)) : void 0;
|
|
37642
|
+
const bm25Score = r.bm25_rank !== null ? 1 / (rrfK + r.bm25_rank) : void 0;
|
|
37643
|
+
const recencyScore = recencyWeight * (1 / (rrfK + r.recency_rank));
|
|
37644
|
+
const debug = {
|
|
37645
|
+
denseRank: r.dense_rank ?? void 0,
|
|
37646
|
+
denseScore,
|
|
37647
|
+
bm25Rank: r.bm25_rank ?? void 0,
|
|
37648
|
+
bm25Score,
|
|
37649
|
+
recencyRank: r.recency_rank,
|
|
37650
|
+
recencyScore,
|
|
37651
|
+
totalScore: r.total_score
|
|
37652
|
+
};
|
|
37653
|
+
row.debug = debug;
|
|
37654
|
+
}
|
|
37655
|
+
return row;
|
|
37656
|
+
});
|
|
37657
|
+
} catch (err) {
|
|
37658
|
+
console.warn("[memory-store] Hybrid search failed:", err);
|
|
37659
|
+
return [];
|
|
37660
|
+
}
|
|
37497
37661
|
}
|
|
37498
37662
|
get vecAvailable() {
|
|
37499
37663
|
return true;
|
|
@@ -37503,45 +37667,103 @@ var MemoryStore = class {
|
|
|
37503
37667
|
}
|
|
37504
37668
|
};
|
|
37505
37669
|
|
|
37670
|
+
// src/services/memory/subsystem.ts
|
|
37671
|
+
var DefaultMemorySubsystem = class {
|
|
37672
|
+
constructor(config2, indexer, store, initialState, initialError) {
|
|
37673
|
+
this.config = config2;
|
|
37674
|
+
this.indexer = indexer;
|
|
37675
|
+
this.store = store;
|
|
37676
|
+
this.lifecycleStatus = makeStatus(initialState, initialError);
|
|
37677
|
+
}
|
|
37678
|
+
lifecycleStatus;
|
|
37679
|
+
get status() {
|
|
37680
|
+
return this.lifecycleStatus;
|
|
37681
|
+
}
|
|
37682
|
+
async startWarmup() {
|
|
37683
|
+
if (!this.indexer) {
|
|
37684
|
+
this.lifecycleStatus = makeStatus("failed", this.lifecycleStatus.error ?? "Memory indexer is unavailable");
|
|
37685
|
+
return;
|
|
37686
|
+
}
|
|
37687
|
+
this.lifecycleStatus = makeStatus("warming");
|
|
37688
|
+
try {
|
|
37689
|
+
await this.indexer.startupIndex();
|
|
37690
|
+
this.lifecycleStatus = makeStatus("ready");
|
|
37691
|
+
} catch (err) {
|
|
37692
|
+
this.lifecycleStatus = makeStatus(this.store ? "degraded" : "failed", formatError3(err));
|
|
37693
|
+
}
|
|
37694
|
+
}
|
|
37695
|
+
close() {
|
|
37696
|
+
this.store?.close();
|
|
37697
|
+
}
|
|
37698
|
+
};
|
|
37699
|
+
function makeStatus(state, error2) {
|
|
37700
|
+
return {
|
|
37701
|
+
state,
|
|
37702
|
+
ready: state === "ready",
|
|
37703
|
+
warming: state === "initializing" || state === "warming",
|
|
37704
|
+
degraded: state === "degraded" || state === "failed",
|
|
37705
|
+
error: error2
|
|
37706
|
+
};
|
|
37707
|
+
}
|
|
37708
|
+
function formatError3(err) {
|
|
37709
|
+
return err instanceof Error ? err.message : String(err);
|
|
37710
|
+
}
|
|
37711
|
+
function createMemorySubsystem(workspaceRoot2, overrides = {}) {
|
|
37712
|
+
const config2 = overrides.config ?? resolveMemoryConfig(overrides.settings ?? {}, workspaceRoot2);
|
|
37713
|
+
if (config2.enabled !== true) return null;
|
|
37714
|
+
let store = overrides.store ?? null;
|
|
37715
|
+
let indexer = overrides.indexer ?? null;
|
|
37716
|
+
try {
|
|
37717
|
+
store = store ?? new MemoryStore(path10.join(config2.wikiDir, "index.db"), config2);
|
|
37718
|
+
if (!indexer) {
|
|
37719
|
+
const embedder = new Embedder(config2.embeddingModel);
|
|
37720
|
+
indexer = new MemoryIndexer(store, embedder, config2);
|
|
37721
|
+
}
|
|
37722
|
+
return new DefaultMemorySubsystem(config2, indexer, store, "initializing");
|
|
37723
|
+
} catch (err) {
|
|
37724
|
+
return new DefaultMemorySubsystem(config2, indexer, store, "failed", formatError3(err));
|
|
37725
|
+
}
|
|
37726
|
+
}
|
|
37727
|
+
|
|
37506
37728
|
// src/services/digest/processor.ts
|
|
37507
37729
|
import * as fs10 from "node:fs";
|
|
37508
|
-
import * as
|
|
37730
|
+
import * as path14 from "node:path";
|
|
37509
37731
|
|
|
37510
37732
|
// src/services/digest/constants.ts
|
|
37511
|
-
import * as
|
|
37733
|
+
import * as path11 from "node:path";
|
|
37512
37734
|
var DEFAULT_DIGEST_MODEL_ID = "base" /* BASE */;
|
|
37513
37735
|
var DEFAULT_DIGEST_MAX_INPUT_CHARS = 1e4;
|
|
37514
37736
|
var DEFAULT_DIGEST_TIMEOUT_MS = 12e4;
|
|
37515
37737
|
var KIT_DIR = ".agent-kit";
|
|
37516
|
-
var WIKI_DIR =
|
|
37517
|
-
var WIKI_RAW_DIR =
|
|
37518
|
-
var PROVISIONAL_DIGEST_DIR =
|
|
37519
|
-
var DIGEST_WORKER_DIR =
|
|
37520
|
-
var DIGEST_LOCKFILE_REL_PATH =
|
|
37521
|
-
var DIGEST_WORKER_STATUS_REL_PATH =
|
|
37522
|
-
var DIGEST_WORKER_LOG_REL_PATH =
|
|
37738
|
+
var WIKI_DIR = path11.join(KIT_DIR, "wiki");
|
|
37739
|
+
var WIKI_RAW_DIR = path11.join(WIKI_DIR, "raw");
|
|
37740
|
+
var PROVISIONAL_DIGEST_DIR = path11.join(WIKI_DIR, "compiled", "provisional", "conversation-digests");
|
|
37741
|
+
var DIGEST_WORKER_DIR = path11.join(WIKI_DIR, "digest");
|
|
37742
|
+
var DIGEST_LOCKFILE_REL_PATH = path11.join(DIGEST_WORKER_DIR, "digest-worker.lock");
|
|
37743
|
+
var DIGEST_WORKER_STATUS_REL_PATH = path11.join(DIGEST_WORKER_DIR, "digest-worker.status.json");
|
|
37744
|
+
var DIGEST_WORKER_LOG_REL_PATH = path11.join(DIGEST_WORKER_DIR, "digest-worker.log");
|
|
37523
37745
|
var LLAMA_CONTEXT_SIZE = 8192;
|
|
37524
37746
|
var LLAMA_MAX_GENERATED_TOKENS = 512;
|
|
37525
37747
|
var LLAMA_TEMPERATURE = 0.1;
|
|
37526
37748
|
|
|
37527
37749
|
// src/services/digest/files.ts
|
|
37528
37750
|
import * as fs8 from "node:fs";
|
|
37529
|
-
import * as
|
|
37751
|
+
import * as path12 from "node:path";
|
|
37530
37752
|
function defaultProvisionalDigestDir(workspaceRoot2) {
|
|
37531
|
-
return
|
|
37753
|
+
return path12.join(workspaceRoot2, PROVISIONAL_DIGEST_DIR);
|
|
37532
37754
|
}
|
|
37533
37755
|
function readConversationDigestInput(workspaceRoot2, inputPath) {
|
|
37534
|
-
const absoluteInput =
|
|
37756
|
+
const absoluteInput = path12.isAbsolute(inputPath) ? inputPath : path12.resolve(workspaceRoot2, inputPath);
|
|
37535
37757
|
const content = fs8.readFileSync(absoluteInput, "utf8");
|
|
37536
37758
|
return {
|
|
37537
|
-
sourcePath:
|
|
37759
|
+
sourcePath: path12.relative(workspaceRoot2, absoluteInput),
|
|
37538
37760
|
content,
|
|
37539
37761
|
contentHash: sha256Hex(content)
|
|
37540
37762
|
};
|
|
37541
37763
|
}
|
|
37542
37764
|
function provisionalDigestPath(outDir, input) {
|
|
37543
|
-
const safeName =
|
|
37544
|
-
return
|
|
37765
|
+
const safeName = path12.basename(input.sourcePath).replace(/\.[^.]+$/, "").replace(/[^a-z0-9._-]+/gi, "-");
|
|
37766
|
+
return path12.join(outDir, `${input.contentHash.slice(0, 16)}-${safeName}.md`);
|
|
37545
37767
|
}
|
|
37546
37768
|
function findExistingProvisionalDigest(outDir, input) {
|
|
37547
37769
|
const markdownPath = provisionalDigestPath(outDir, input);
|
|
@@ -37616,25 +37838,8 @@ function getDigestModelSpec(modelId) {
|
|
|
37616
37838
|
|
|
37617
37839
|
// src/services/digest/providers/llama-local.ts
|
|
37618
37840
|
import * as fs9 from "node:fs";
|
|
37619
|
-
import * as
|
|
37841
|
+
import * as path13 from "node:path";
|
|
37620
37842
|
import { getLlama, LlamaChatSession, createModelDownloader } from "node-llama-cpp";
|
|
37621
|
-
|
|
37622
|
-
// src/utils/async.ts
|
|
37623
|
-
async function withTimeout(promise, timeoutMs, createError) {
|
|
37624
|
-
let timer;
|
|
37625
|
-
const timeout = new Promise((_resolve, reject) => {
|
|
37626
|
-
timer = setTimeout(() => {
|
|
37627
|
-
reject(createError());
|
|
37628
|
-
}, timeoutMs);
|
|
37629
|
-
});
|
|
37630
|
-
try {
|
|
37631
|
-
return await Promise.race([promise, timeout]);
|
|
37632
|
-
} finally {
|
|
37633
|
-
if (timer) clearTimeout(timer);
|
|
37634
|
-
}
|
|
37635
|
-
}
|
|
37636
|
-
|
|
37637
|
-
// src/services/digest/providers/llama-local.ts
|
|
37638
37843
|
var LlamaLocalDigestProviderError = class extends Error {
|
|
37639
37844
|
constructor(message) {
|
|
37640
37845
|
super(message);
|
|
@@ -37642,7 +37847,7 @@ var LlamaLocalDigestProviderError = class extends Error {
|
|
|
37642
37847
|
}
|
|
37643
37848
|
};
|
|
37644
37849
|
function titleFromSource(sourcePath) {
|
|
37645
|
-
return
|
|
37850
|
+
return path13.basename(sourcePath).replace(/\.[^.]+$/, "").replace(/[_-]+/g, " ");
|
|
37646
37851
|
}
|
|
37647
37852
|
function trimConversationExport(content, maxInputChars) {
|
|
37648
37853
|
const trimmed = content.trim();
|
|
@@ -37685,7 +37890,7 @@ function sanitizeConversationDigestMarkdown(generatedText) {
|
|
|
37685
37890
|
}
|
|
37686
37891
|
async function createLlamaLocalDigestProvider(modelId) {
|
|
37687
37892
|
const spec = getDigestModelSpec(modelId);
|
|
37688
|
-
const modelCacheDir =
|
|
37893
|
+
const modelCacheDir = path13.join(MODEL_CACHE_DIR, "llama");
|
|
37689
37894
|
fs9.mkdirSync(modelCacheDir, { recursive: true });
|
|
37690
37895
|
let modelFilePath;
|
|
37691
37896
|
try {
|
|
@@ -37754,13 +37959,13 @@ ${sanitized}`;
|
|
|
37754
37959
|
|
|
37755
37960
|
// src/services/digest/processor.ts
|
|
37756
37961
|
function digestLockPath(workspaceRoot2) {
|
|
37757
|
-
return
|
|
37962
|
+
return path14.join(workspaceRoot2, DIGEST_LOCKFILE_REL_PATH);
|
|
37758
37963
|
}
|
|
37759
37964
|
function discoverPendingConversationFiles(workspaceRoot2) {
|
|
37760
|
-
const rawDir =
|
|
37965
|
+
const rawDir = path14.join(workspaceRoot2, WIKI_RAW_DIR);
|
|
37761
37966
|
let files;
|
|
37762
37967
|
try {
|
|
37763
|
-
files = fs10.readdirSync(rawDir).filter((f) => f.startsWith("conv_") && f.endsWith(".md")).map((f) =>
|
|
37968
|
+
files = fs10.readdirSync(rawDir).filter((f) => f.startsWith("conv_") && f.endsWith(".md")).map((f) => path14.join(rawDir, f)).sort((a, b) => fs10.statSync(a).mtimeMs - fs10.statSync(b).mtimeMs);
|
|
37764
37969
|
} catch (err) {
|
|
37765
37970
|
if (err.code === "ENOENT") {
|
|
37766
37971
|
files = [];
|
|
@@ -37788,7 +37993,7 @@ async function indexProvisionalDigestFile(workspaceRoot2, markdownPath, indexer)
|
|
|
37788
37993
|
const settings = loadGlobalSettings();
|
|
37789
37994
|
const config2 = resolveMemoryConfig(settings, workspaceRoot2);
|
|
37790
37995
|
if (config2.enabled !== true) return { indexed: false };
|
|
37791
|
-
store = new MemoryStore(
|
|
37996
|
+
store = new MemoryStore(path14.join(config2.wikiDir, "index.db"), config2);
|
|
37792
37997
|
const embedder = new Embedder(config2.embeddingModel);
|
|
37793
37998
|
await embedder.initialize();
|
|
37794
37999
|
await new MemoryIndexer(store, embedder, config2).indexFile(markdownPath);
|
|
@@ -37800,7 +38005,7 @@ async function indexProvisionalDigestFile(workspaceRoot2, markdownPath, indexer)
|
|
|
37800
38005
|
}
|
|
37801
38006
|
}
|
|
37802
38007
|
async function digestConversationFile(options) {
|
|
37803
|
-
const outDir = options.outDir ?
|
|
38008
|
+
const outDir = options.outDir ? path14.resolve(options.workspaceRoot, options.outDir) : defaultProvisionalDigestDir(options.workspaceRoot);
|
|
37804
38009
|
const input = readConversationDigestInput(options.workspaceRoot, options.inputPath);
|
|
37805
38010
|
const existingPath = findExistingProvisionalDigest(outDir, input);
|
|
37806
38011
|
if (existingPath) {
|
|
@@ -37855,7 +38060,7 @@ async function digestPendingConversations({
|
|
|
37855
38060
|
let indexer;
|
|
37856
38061
|
const memoryConfig = resolveMemoryConfig(loadGlobalSettings(), workspaceRoot2);
|
|
37857
38062
|
if (memoryConfig.enabled === true) {
|
|
37858
|
-
const store = new MemoryStore(
|
|
38063
|
+
const store = new MemoryStore(path14.join(memoryConfig.wikiDir, "index.db"), memoryConfig);
|
|
37859
38064
|
const embedder = new Embedder(memoryConfig.embeddingModel);
|
|
37860
38065
|
indexer = new MemoryIndexer(store, embedder, memoryConfig);
|
|
37861
38066
|
closeIndexer = () => store.close();
|
|
@@ -37936,7 +38141,8 @@ async function initializeConversationDigestModel(input) {
|
|
|
37936
38141
|
}
|
|
37937
38142
|
|
|
37938
38143
|
// src/mcp/memory.ts
|
|
37939
|
-
function registerMemoryToolHandlers(server2,
|
|
38144
|
+
function registerMemoryToolHandlers(server2, subsystem, workspaceRoot2 = process.cwd()) {
|
|
38145
|
+
const { config: config2 } = subsystem;
|
|
37940
38146
|
server2.tool(
|
|
37941
38147
|
"kit_memory_search",
|
|
37942
38148
|
"Search persistent memory for factual or semantic matches (decisions, concepts, entities). Extract concise keywords only, removing stopwords and filler words from the user request before querying. Use kit_memory_recent for temporal or recency questions.",
|
|
@@ -37946,10 +38152,15 @@ function registerMemoryToolHandlers(server2, indexer, store, config2, workspaceR
|
|
|
37946
38152
|
},
|
|
37947
38153
|
async ({ query, top_k }) => {
|
|
37948
38154
|
try {
|
|
38155
|
+
if (!subsystem.indexer || !subsystem.store) {
|
|
38156
|
+
return mcpText(formatInitializationFailure(subsystem));
|
|
38157
|
+
}
|
|
38158
|
+
const { indexer, store } = subsystem;
|
|
37949
38159
|
const results = await indexer.search(query, top_k ?? config2.topK);
|
|
38160
|
+
const lifecycleNote = formatLifecycleNote(subsystem);
|
|
37950
38161
|
const degradedNote = !store.vecAvailable ? "\u26A0\uFE0F Vector search unavailable \u2014 showing keyword-only results.\n\n" : "";
|
|
37951
38162
|
if (results.length === 0) {
|
|
37952
|
-
return mcpText(`${degradedNote}No memories found for query: "${query}"`);
|
|
38163
|
+
return mcpText(`${lifecycleNote}${degradedNote}No memories found for query: "${query}"`);
|
|
37953
38164
|
}
|
|
37954
38165
|
const formatted = results.map((r) => {
|
|
37955
38166
|
const displaySource = r.chunk.source.replace(/^compiled\//, "");
|
|
@@ -37958,7 +38169,7 @@ ${r.chunk.content}` : r.chunk.content;
|
|
|
37958
38169
|
return `### ${displaySource} (score: ${r.score.toFixed(3)})
|
|
37959
38170
|
${content}`;
|
|
37960
38171
|
}).join("\n\n---\n\n");
|
|
37961
|
-
return mcpText(`${degradedNote}${formatted}`);
|
|
38172
|
+
return mcpText(`${lifecycleNote}${degradedNote}${formatted}`);
|
|
37962
38173
|
} catch (err) {
|
|
37963
38174
|
return mcpText(`kit_memory_search failed: ${formatError2(err)}`);
|
|
37964
38175
|
}
|
|
@@ -37973,12 +38184,16 @@ ${content}`;
|
|
|
37973
38184
|
},
|
|
37974
38185
|
async ({ n, source_type }) => {
|
|
37975
38186
|
try {
|
|
38187
|
+
if (!subsystem.store) {
|
|
38188
|
+
return mcpText(formatInitializationFailure(subsystem));
|
|
38189
|
+
}
|
|
38190
|
+
const store = subsystem.store;
|
|
37976
38191
|
const rows = store.getRecentSources({ limit: n ?? 5, sourceType: source_type });
|
|
37977
38192
|
if (rows.length === 0) return mcpText("No recent sources found.");
|
|
37978
38193
|
const blocks = [];
|
|
37979
38194
|
for (const row of rows) {
|
|
37980
38195
|
try {
|
|
37981
|
-
const content = fs11.
|
|
38196
|
+
const content = await fs11.promises.readFile(path15.join(config2.wikiDir, row.source), "utf8");
|
|
37982
38197
|
const displaySource = row.source.replace(/^compiled\//, "");
|
|
37983
38198
|
blocks.push(`### ${displaySource}
|
|
37984
38199
|
${content}`);
|
|
@@ -38001,6 +38216,13 @@ ${content}`);
|
|
|
38001
38216
|
},
|
|
38002
38217
|
async ({ content }) => {
|
|
38003
38218
|
try {
|
|
38219
|
+
if (!subsystem.indexer) {
|
|
38220
|
+
return mcpJson({
|
|
38221
|
+
saved: false,
|
|
38222
|
+
error: formatInitializationFailure(subsystem)
|
|
38223
|
+
});
|
|
38224
|
+
}
|
|
38225
|
+
const indexer = subsystem.indexer;
|
|
38004
38226
|
await indexer.save(content);
|
|
38005
38227
|
return mcpJson({
|
|
38006
38228
|
saved: true,
|
|
@@ -38043,17 +38265,28 @@ ${content}`);
|
|
|
38043
38265
|
}
|
|
38044
38266
|
);
|
|
38045
38267
|
}
|
|
38268
|
+
function formatLifecycleNote(subsystem) {
|
|
38269
|
+
if (subsystem.status.state === "warming" || subsystem.status.state === "initializing") {
|
|
38270
|
+
return "\u26A0\uFE0F Memory index is warming \u2014 results may be stale until startup indexing completes.\n\n";
|
|
38271
|
+
}
|
|
38272
|
+
if (subsystem.status.state === "degraded") {
|
|
38273
|
+
return `\u26A0\uFE0F Memory index is degraded${subsystem.status.error ? `: ${subsystem.status.error}` : ""}.
|
|
38274
|
+
|
|
38275
|
+
`;
|
|
38276
|
+
}
|
|
38277
|
+
return "";
|
|
38278
|
+
}
|
|
38279
|
+
function formatInitializationFailure(subsystem) {
|
|
38280
|
+
const detail = subsystem.status.error ? `: ${subsystem.status.error}` : "";
|
|
38281
|
+
return `Memory initialization failed${detail}`;
|
|
38282
|
+
}
|
|
38046
38283
|
function registerMemoryTools(server2, workspaceRoot2, overrides) {
|
|
38047
38284
|
const settings = overrides?.settings ?? loadGlobalSettings();
|
|
38048
38285
|
if (settings.memory?.enabled !== true) return null;
|
|
38049
|
-
const
|
|
38050
|
-
|
|
38051
|
-
|
|
38052
|
-
|
|
38053
|
-
return new MemoryIndexer(store, embedder, config2);
|
|
38054
|
-
})();
|
|
38055
|
-
registerMemoryToolHandlers(server2, indexer, store, config2, workspaceRoot2);
|
|
38056
|
-
return indexer;
|
|
38286
|
+
const subsystem = createMemorySubsystem(workspaceRoot2, { ...overrides, settings });
|
|
38287
|
+
if (!subsystem) return null;
|
|
38288
|
+
registerMemoryToolHandlers(server2, subsystem, workspaceRoot2);
|
|
38289
|
+
return subsystem;
|
|
38057
38290
|
}
|
|
38058
38291
|
|
|
38059
38292
|
// src/entrypoints/server.ts
|
|
@@ -38065,9 +38298,9 @@ registerIntegrationTools(server);
|
|
|
38065
38298
|
registerCoreTools(server);
|
|
38066
38299
|
registerAgentTools(server);
|
|
38067
38300
|
var workspaceRoot = getWorkspaceRoot();
|
|
38068
|
-
var
|
|
38301
|
+
var memorySubsystem = registerMemoryTools(server, workspaceRoot);
|
|
38069
38302
|
var transport = new StdioServerTransport();
|
|
38070
38303
|
await server.connect(transport);
|
|
38071
|
-
if (
|
|
38072
|
-
void
|
|
38304
|
+
if (memorySubsystem) {
|
|
38305
|
+
void memorySubsystem.startWarmup();
|
|
38073
38306
|
}
|