@hanhnd/agent-kit 1.0.39 → 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 +55 -15
- package/dist/entrypoints/server.js +3 -3
- package/dist/mcp/memory.d.ts +4 -9
- package/dist/mcp/memory.js +42 -18
- package/dist/server.js +212 -85
- 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 +36 -15
- package/dist/services/memory/indexer.test.js +132 -0
- package/dist/services/memory/memory.test.js +58 -0
- package/dist/services/memory/subsystem.d.ts +19 -0
- package/dist/services/memory/subsystem.js +67 -0
- package/dist/services/memory/types.d.ts +20 -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,10 +25923,10 @@ 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);
|
|
@@ -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,9 +37043,19 @@ 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);
|
|
37013
37048
|
}
|
|
37014
|
-
|
|
37049
|
+
makeSkippedMutation(source, skipped) {
|
|
37050
|
+
return {
|
|
37051
|
+
source,
|
|
37052
|
+
chunksToUpsert: [],
|
|
37053
|
+
embeddings: [],
|
|
37054
|
+
idsToDelete: [],
|
|
37055
|
+
stats: { indexed: 0, deleted: 0, skipped }
|
|
37056
|
+
};
|
|
37057
|
+
}
|
|
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;
|
|
@@ -37019,7 +37063,7 @@ var MemoryIndexer = class {
|
|
|
37019
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 {
|
|
@@ -37039,21 +37083,30 @@ 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;
|
|
@@ -37095,8 +37148,13 @@ var MemoryIndexer = class {
|
|
|
37095
37148
|
totals.deleted += 1;
|
|
37096
37149
|
}
|
|
37097
37150
|
}
|
|
37098
|
-
|
|
37099
|
-
|
|
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);
|
|
37100
37158
|
totals.indexed += stats.indexed;
|
|
37101
37159
|
totals.deleted += stats.deleted;
|
|
37102
37160
|
totals.skipped += stats.skipped;
|
|
@@ -37609,45 +37667,103 @@ var MemoryStore = class {
|
|
|
37609
37667
|
}
|
|
37610
37668
|
};
|
|
37611
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
|
+
|
|
37612
37728
|
// src/services/digest/processor.ts
|
|
37613
37729
|
import * as fs10 from "node:fs";
|
|
37614
|
-
import * as
|
|
37730
|
+
import * as path14 from "node:path";
|
|
37615
37731
|
|
|
37616
37732
|
// src/services/digest/constants.ts
|
|
37617
|
-
import * as
|
|
37733
|
+
import * as path11 from "node:path";
|
|
37618
37734
|
var DEFAULT_DIGEST_MODEL_ID = "base" /* BASE */;
|
|
37619
37735
|
var DEFAULT_DIGEST_MAX_INPUT_CHARS = 1e4;
|
|
37620
37736
|
var DEFAULT_DIGEST_TIMEOUT_MS = 12e4;
|
|
37621
37737
|
var KIT_DIR = ".agent-kit";
|
|
37622
|
-
var WIKI_DIR =
|
|
37623
|
-
var WIKI_RAW_DIR =
|
|
37624
|
-
var PROVISIONAL_DIGEST_DIR =
|
|
37625
|
-
var DIGEST_WORKER_DIR =
|
|
37626
|
-
var DIGEST_LOCKFILE_REL_PATH =
|
|
37627
|
-
var DIGEST_WORKER_STATUS_REL_PATH =
|
|
37628
|
-
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");
|
|
37629
37745
|
var LLAMA_CONTEXT_SIZE = 8192;
|
|
37630
37746
|
var LLAMA_MAX_GENERATED_TOKENS = 512;
|
|
37631
37747
|
var LLAMA_TEMPERATURE = 0.1;
|
|
37632
37748
|
|
|
37633
37749
|
// src/services/digest/files.ts
|
|
37634
37750
|
import * as fs8 from "node:fs";
|
|
37635
|
-
import * as
|
|
37751
|
+
import * as path12 from "node:path";
|
|
37636
37752
|
function defaultProvisionalDigestDir(workspaceRoot2) {
|
|
37637
|
-
return
|
|
37753
|
+
return path12.join(workspaceRoot2, PROVISIONAL_DIGEST_DIR);
|
|
37638
37754
|
}
|
|
37639
37755
|
function readConversationDigestInput(workspaceRoot2, inputPath) {
|
|
37640
|
-
const absoluteInput =
|
|
37756
|
+
const absoluteInput = path12.isAbsolute(inputPath) ? inputPath : path12.resolve(workspaceRoot2, inputPath);
|
|
37641
37757
|
const content = fs8.readFileSync(absoluteInput, "utf8");
|
|
37642
37758
|
return {
|
|
37643
|
-
sourcePath:
|
|
37759
|
+
sourcePath: path12.relative(workspaceRoot2, absoluteInput),
|
|
37644
37760
|
content,
|
|
37645
37761
|
contentHash: sha256Hex(content)
|
|
37646
37762
|
};
|
|
37647
37763
|
}
|
|
37648
37764
|
function provisionalDigestPath(outDir, input) {
|
|
37649
|
-
const safeName =
|
|
37650
|
-
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`);
|
|
37651
37767
|
}
|
|
37652
37768
|
function findExistingProvisionalDigest(outDir, input) {
|
|
37653
37769
|
const markdownPath = provisionalDigestPath(outDir, input);
|
|
@@ -37722,25 +37838,8 @@ function getDigestModelSpec(modelId) {
|
|
|
37722
37838
|
|
|
37723
37839
|
// src/services/digest/providers/llama-local.ts
|
|
37724
37840
|
import * as fs9 from "node:fs";
|
|
37725
|
-
import * as
|
|
37841
|
+
import * as path13 from "node:path";
|
|
37726
37842
|
import { getLlama, LlamaChatSession, createModelDownloader } from "node-llama-cpp";
|
|
37727
|
-
|
|
37728
|
-
// src/utils/async.ts
|
|
37729
|
-
async function withTimeout(promise, timeoutMs, createError) {
|
|
37730
|
-
let timer;
|
|
37731
|
-
const timeout = new Promise((_resolve, reject) => {
|
|
37732
|
-
timer = setTimeout(() => {
|
|
37733
|
-
reject(createError());
|
|
37734
|
-
}, timeoutMs);
|
|
37735
|
-
});
|
|
37736
|
-
try {
|
|
37737
|
-
return await Promise.race([promise, timeout]);
|
|
37738
|
-
} finally {
|
|
37739
|
-
if (timer) clearTimeout(timer);
|
|
37740
|
-
}
|
|
37741
|
-
}
|
|
37742
|
-
|
|
37743
|
-
// src/services/digest/providers/llama-local.ts
|
|
37744
37843
|
var LlamaLocalDigestProviderError = class extends Error {
|
|
37745
37844
|
constructor(message) {
|
|
37746
37845
|
super(message);
|
|
@@ -37748,7 +37847,7 @@ var LlamaLocalDigestProviderError = class extends Error {
|
|
|
37748
37847
|
}
|
|
37749
37848
|
};
|
|
37750
37849
|
function titleFromSource(sourcePath) {
|
|
37751
|
-
return
|
|
37850
|
+
return path13.basename(sourcePath).replace(/\.[^.]+$/, "").replace(/[_-]+/g, " ");
|
|
37752
37851
|
}
|
|
37753
37852
|
function trimConversationExport(content, maxInputChars) {
|
|
37754
37853
|
const trimmed = content.trim();
|
|
@@ -37791,7 +37890,7 @@ function sanitizeConversationDigestMarkdown(generatedText) {
|
|
|
37791
37890
|
}
|
|
37792
37891
|
async function createLlamaLocalDigestProvider(modelId) {
|
|
37793
37892
|
const spec = getDigestModelSpec(modelId);
|
|
37794
|
-
const modelCacheDir =
|
|
37893
|
+
const modelCacheDir = path13.join(MODEL_CACHE_DIR, "llama");
|
|
37795
37894
|
fs9.mkdirSync(modelCacheDir, { recursive: true });
|
|
37796
37895
|
let modelFilePath;
|
|
37797
37896
|
try {
|
|
@@ -37860,13 +37959,13 @@ ${sanitized}`;
|
|
|
37860
37959
|
|
|
37861
37960
|
// src/services/digest/processor.ts
|
|
37862
37961
|
function digestLockPath(workspaceRoot2) {
|
|
37863
|
-
return
|
|
37962
|
+
return path14.join(workspaceRoot2, DIGEST_LOCKFILE_REL_PATH);
|
|
37864
37963
|
}
|
|
37865
37964
|
function discoverPendingConversationFiles(workspaceRoot2) {
|
|
37866
|
-
const rawDir =
|
|
37965
|
+
const rawDir = path14.join(workspaceRoot2, WIKI_RAW_DIR);
|
|
37867
37966
|
let files;
|
|
37868
37967
|
try {
|
|
37869
|
-
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);
|
|
37870
37969
|
} catch (err) {
|
|
37871
37970
|
if (err.code === "ENOENT") {
|
|
37872
37971
|
files = [];
|
|
@@ -37894,7 +37993,7 @@ async function indexProvisionalDigestFile(workspaceRoot2, markdownPath, indexer)
|
|
|
37894
37993
|
const settings = loadGlobalSettings();
|
|
37895
37994
|
const config2 = resolveMemoryConfig(settings, workspaceRoot2);
|
|
37896
37995
|
if (config2.enabled !== true) return { indexed: false };
|
|
37897
|
-
store = new MemoryStore(
|
|
37996
|
+
store = new MemoryStore(path14.join(config2.wikiDir, "index.db"), config2);
|
|
37898
37997
|
const embedder = new Embedder(config2.embeddingModel);
|
|
37899
37998
|
await embedder.initialize();
|
|
37900
37999
|
await new MemoryIndexer(store, embedder, config2).indexFile(markdownPath);
|
|
@@ -37906,7 +38005,7 @@ async function indexProvisionalDigestFile(workspaceRoot2, markdownPath, indexer)
|
|
|
37906
38005
|
}
|
|
37907
38006
|
}
|
|
37908
38007
|
async function digestConversationFile(options) {
|
|
37909
|
-
const outDir = options.outDir ?
|
|
38008
|
+
const outDir = options.outDir ? path14.resolve(options.workspaceRoot, options.outDir) : defaultProvisionalDigestDir(options.workspaceRoot);
|
|
37910
38009
|
const input = readConversationDigestInput(options.workspaceRoot, options.inputPath);
|
|
37911
38010
|
const existingPath = findExistingProvisionalDigest(outDir, input);
|
|
37912
38011
|
if (existingPath) {
|
|
@@ -37961,7 +38060,7 @@ async function digestPendingConversations({
|
|
|
37961
38060
|
let indexer;
|
|
37962
38061
|
const memoryConfig = resolveMemoryConfig(loadGlobalSettings(), workspaceRoot2);
|
|
37963
38062
|
if (memoryConfig.enabled === true) {
|
|
37964
|
-
const store = new MemoryStore(
|
|
38063
|
+
const store = new MemoryStore(path14.join(memoryConfig.wikiDir, "index.db"), memoryConfig);
|
|
37965
38064
|
const embedder = new Embedder(memoryConfig.embeddingModel);
|
|
37966
38065
|
indexer = new MemoryIndexer(store, embedder, memoryConfig);
|
|
37967
38066
|
closeIndexer = () => store.close();
|
|
@@ -38042,7 +38141,8 @@ async function initializeConversationDigestModel(input) {
|
|
|
38042
38141
|
}
|
|
38043
38142
|
|
|
38044
38143
|
// src/mcp/memory.ts
|
|
38045
|
-
function registerMemoryToolHandlers(server2,
|
|
38144
|
+
function registerMemoryToolHandlers(server2, subsystem, workspaceRoot2 = process.cwd()) {
|
|
38145
|
+
const { config: config2 } = subsystem;
|
|
38046
38146
|
server2.tool(
|
|
38047
38147
|
"kit_memory_search",
|
|
38048
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.",
|
|
@@ -38052,10 +38152,15 @@ function registerMemoryToolHandlers(server2, indexer, store, config2, workspaceR
|
|
|
38052
38152
|
},
|
|
38053
38153
|
async ({ query, top_k }) => {
|
|
38054
38154
|
try {
|
|
38155
|
+
if (!subsystem.indexer || !subsystem.store) {
|
|
38156
|
+
return mcpText(formatInitializationFailure(subsystem));
|
|
38157
|
+
}
|
|
38158
|
+
const { indexer, store } = subsystem;
|
|
38055
38159
|
const results = await indexer.search(query, top_k ?? config2.topK);
|
|
38160
|
+
const lifecycleNote = formatLifecycleNote(subsystem);
|
|
38056
38161
|
const degradedNote = !store.vecAvailable ? "\u26A0\uFE0F Vector search unavailable \u2014 showing keyword-only results.\n\n" : "";
|
|
38057
38162
|
if (results.length === 0) {
|
|
38058
|
-
return mcpText(`${degradedNote}No memories found for query: "${query}"`);
|
|
38163
|
+
return mcpText(`${lifecycleNote}${degradedNote}No memories found for query: "${query}"`);
|
|
38059
38164
|
}
|
|
38060
38165
|
const formatted = results.map((r) => {
|
|
38061
38166
|
const displaySource = r.chunk.source.replace(/^compiled\//, "");
|
|
@@ -38064,7 +38169,7 @@ ${r.chunk.content}` : r.chunk.content;
|
|
|
38064
38169
|
return `### ${displaySource} (score: ${r.score.toFixed(3)})
|
|
38065
38170
|
${content}`;
|
|
38066
38171
|
}).join("\n\n---\n\n");
|
|
38067
|
-
return mcpText(`${degradedNote}${formatted}`);
|
|
38172
|
+
return mcpText(`${lifecycleNote}${degradedNote}${formatted}`);
|
|
38068
38173
|
} catch (err) {
|
|
38069
38174
|
return mcpText(`kit_memory_search failed: ${formatError2(err)}`);
|
|
38070
38175
|
}
|
|
@@ -38079,12 +38184,16 @@ ${content}`;
|
|
|
38079
38184
|
},
|
|
38080
38185
|
async ({ n, source_type }) => {
|
|
38081
38186
|
try {
|
|
38187
|
+
if (!subsystem.store) {
|
|
38188
|
+
return mcpText(formatInitializationFailure(subsystem));
|
|
38189
|
+
}
|
|
38190
|
+
const store = subsystem.store;
|
|
38082
38191
|
const rows = store.getRecentSources({ limit: n ?? 5, sourceType: source_type });
|
|
38083
38192
|
if (rows.length === 0) return mcpText("No recent sources found.");
|
|
38084
38193
|
const blocks = [];
|
|
38085
38194
|
for (const row of rows) {
|
|
38086
38195
|
try {
|
|
38087
|
-
const content = await fs11.promises.readFile(
|
|
38196
|
+
const content = await fs11.promises.readFile(path15.join(config2.wikiDir, row.source), "utf8");
|
|
38088
38197
|
const displaySource = row.source.replace(/^compiled\//, "");
|
|
38089
38198
|
blocks.push(`### ${displaySource}
|
|
38090
38199
|
${content}`);
|
|
@@ -38107,6 +38216,13 @@ ${content}`);
|
|
|
38107
38216
|
},
|
|
38108
38217
|
async ({ content }) => {
|
|
38109
38218
|
try {
|
|
38219
|
+
if (!subsystem.indexer) {
|
|
38220
|
+
return mcpJson({
|
|
38221
|
+
saved: false,
|
|
38222
|
+
error: formatInitializationFailure(subsystem)
|
|
38223
|
+
});
|
|
38224
|
+
}
|
|
38225
|
+
const indexer = subsystem.indexer;
|
|
38110
38226
|
await indexer.save(content);
|
|
38111
38227
|
return mcpJson({
|
|
38112
38228
|
saved: true,
|
|
@@ -38149,17 +38265,28 @@ ${content}`);
|
|
|
38149
38265
|
}
|
|
38150
38266
|
);
|
|
38151
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
|
+
}
|
|
38152
38283
|
function registerMemoryTools(server2, workspaceRoot2, overrides) {
|
|
38153
38284
|
const settings = overrides?.settings ?? loadGlobalSettings();
|
|
38154
38285
|
if (settings.memory?.enabled !== true) return null;
|
|
38155
|
-
const
|
|
38156
|
-
|
|
38157
|
-
|
|
38158
|
-
|
|
38159
|
-
return new MemoryIndexer(store, embedder, config2);
|
|
38160
|
-
})();
|
|
38161
|
-
registerMemoryToolHandlers(server2, indexer, store, config2, workspaceRoot2);
|
|
38162
|
-
return indexer;
|
|
38286
|
+
const subsystem = createMemorySubsystem(workspaceRoot2, { ...overrides, settings });
|
|
38287
|
+
if (!subsystem) return null;
|
|
38288
|
+
registerMemoryToolHandlers(server2, subsystem, workspaceRoot2);
|
|
38289
|
+
return subsystem;
|
|
38163
38290
|
}
|
|
38164
38291
|
|
|
38165
38292
|
// src/entrypoints/server.ts
|
|
@@ -38171,9 +38298,9 @@ registerIntegrationTools(server);
|
|
|
38171
38298
|
registerCoreTools(server);
|
|
38172
38299
|
registerAgentTools(server);
|
|
38173
38300
|
var workspaceRoot = getWorkspaceRoot();
|
|
38174
|
-
var
|
|
38301
|
+
var memorySubsystem = registerMemoryTools(server, workspaceRoot);
|
|
38175
38302
|
var transport = new StdioServerTransport();
|
|
38176
38303
|
await server.connect(transport);
|
|
38177
|
-
if (
|
|
38178
|
-
void
|
|
38304
|
+
if (memorySubsystem) {
|
|
38305
|
+
void memorySubsystem.startWarmup();
|
|
38179
38306
|
}
|
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
export { MemoryStore, StoreError } from './store.js';
|
|
2
2
|
export { Embedder, EmbedderError } from './embedder.js';
|
|
3
3
|
export { MemoryIndexer } from './indexer.js';
|
|
4
|
-
export
|
|
4
|
+
export { createMemorySubsystem } from './subsystem.js';
|
|
5
|
+
export type { MemorySubsystem, MemorySubsystemOverrides } from './subsystem.js';
|
|
6
|
+
export type { MemoryChunk, SearchResult, MemoryConfig, IndexStats, SourceType, RecentSource, MemoryLifecycleState, MemoryLifecycleStatus, IndexDirectoryOptions, PreparedIndexMutation, } from './types.js';
|
|
5
7
|
export { DEFAULT_MEMORY_CONFIG } from './constants.js';
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
export { MemoryStore, StoreError } from './store.js';
|
|
2
2
|
export { Embedder, EmbedderError } from './embedder.js';
|
|
3
3
|
export { MemoryIndexer } from './indexer.js';
|
|
4
|
+
export { createMemorySubsystem } from './subsystem.js';
|
|
4
5
|
export { DEFAULT_MEMORY_CONFIG } from './constants.js';
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import type { MemoryStore } from './store.js';
|
|
2
|
-
import type { IndexStats, MemoryConfig, SearchResult, SourceType } from './types.js';
|
|
2
|
+
import type { IndexDirectoryOptions, IndexStats, MemoryConfig, SearchResult, SourceType } from './types.js';
|
|
3
3
|
interface TextEmbedder {
|
|
4
4
|
embed(texts: string[]): Promise<Float32Array[]>;
|
|
5
5
|
initialize(): Promise<void>;
|
|
@@ -12,11 +12,10 @@ export declare class MemoryIndexer {
|
|
|
12
12
|
private _ready;
|
|
13
13
|
constructor(store: MemoryStore, embedder: TextEmbedder, config: MemoryConfig);
|
|
14
14
|
indexFile(absolutePath: string): Promise<IndexStats>;
|
|
15
|
-
private
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
}): Promise<IndexStats>;
|
|
15
|
+
private makeSkippedMutation;
|
|
16
|
+
private prepareIndexFileRelativeTo;
|
|
17
|
+
private commitPreparedMutation;
|
|
18
|
+
indexDirectory(rootDir: string, opts?: IndexDirectoryOptions): Promise<IndexStats>;
|
|
20
19
|
search(query: string, topK: number): Promise<SearchResult[]>;
|
|
21
20
|
save(content: string): Promise<IndexStats>;
|
|
22
21
|
startupIndex(): Promise<void>;
|
|
@@ -2,6 +2,7 @@ import * as fs from 'fs';
|
|
|
2
2
|
import * as path from 'path';
|
|
3
3
|
import { chunkMarkdown } from './chunker.js';
|
|
4
4
|
import { DENSE_SCORE_FLOOR, FETCH_MULTIPLIER, LOCK_RETRY_MS, LOCK_TIMEOUT_MS, RECENCY_WEIGHT, RRF_K, } from './constants.js';
|
|
5
|
+
import { mapLimit } from '../../utils/async.js';
|
|
5
6
|
import { acquireLock, releaseLock } from '../../utils/files.js';
|
|
6
7
|
export function deriveSourceType(source) {
|
|
7
8
|
if (source.startsWith('compiled/provisional/conversation-digests/'))
|
|
@@ -25,9 +26,19 @@ export class MemoryIndexer {
|
|
|
25
26
|
this.config = config;
|
|
26
27
|
}
|
|
27
28
|
async indexFile(absolutePath) {
|
|
28
|
-
|
|
29
|
+
const mutation = await this.prepareIndexFileRelativeTo(absolutePath, this.config.wikiDir);
|
|
30
|
+
return this.commitPreparedMutation(mutation);
|
|
29
31
|
}
|
|
30
|
-
|
|
32
|
+
makeSkippedMutation(source, skipped) {
|
|
33
|
+
return {
|
|
34
|
+
source,
|
|
35
|
+
chunksToUpsert: [],
|
|
36
|
+
embeddings: [],
|
|
37
|
+
idsToDelete: [],
|
|
38
|
+
stats: { indexed: 0, deleted: 0, skipped },
|
|
39
|
+
};
|
|
40
|
+
}
|
|
41
|
+
async prepareIndexFileRelativeTo(absolutePath, sourceRoot) {
|
|
31
42
|
const source = path.relative(sourceRoot, absolutePath);
|
|
32
43
|
const existingChunkIds = this.store.hashesBySource(source);
|
|
33
44
|
let text;
|
|
@@ -36,7 +47,7 @@ export class MemoryIndexer {
|
|
|
36
47
|
}
|
|
37
48
|
catch (err) {
|
|
38
49
|
console.warn(`[memory-indexer] Cannot read file ${absolutePath}:`, err);
|
|
39
|
-
return
|
|
50
|
+
return this.makeSkippedMutation(source, existingChunkIds.size);
|
|
40
51
|
}
|
|
41
52
|
let fileMtimeAt;
|
|
42
53
|
try {
|
|
@@ -58,21 +69,30 @@ export class MemoryIndexer {
|
|
|
58
69
|
}
|
|
59
70
|
catch (err) {
|
|
60
71
|
console.warn(`[memory-indexer] Embedding failed for ${absolutePath}:`, err);
|
|
61
|
-
return
|
|
72
|
+
return this.makeSkippedMutation(source, existingChunkIds.size);
|
|
62
73
|
}
|
|
63
74
|
}
|
|
64
|
-
if (toIndex.length > 0) {
|
|
65
|
-
this.store.upsert(toIndex, embeddings);
|
|
66
|
-
}
|
|
67
|
-
if (toDelete.length > 0) {
|
|
68
|
-
this.store.deleteByIds(toDelete);
|
|
69
|
-
}
|
|
70
75
|
return {
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
76
|
+
source,
|
|
77
|
+
chunksToUpsert: toIndex,
|
|
78
|
+
embeddings,
|
|
79
|
+
idsToDelete: toDelete,
|
|
80
|
+
stats: {
|
|
81
|
+
indexed: toIndex.length,
|
|
82
|
+
deleted: toDelete.length,
|
|
83
|
+
skipped: allChunks.length - toIndex.length,
|
|
84
|
+
},
|
|
74
85
|
};
|
|
75
86
|
}
|
|
87
|
+
commitPreparedMutation(mutation) {
|
|
88
|
+
if (mutation.chunksToUpsert.length > 0) {
|
|
89
|
+
this.store.upsert(mutation.chunksToUpsert, mutation.embeddings);
|
|
90
|
+
}
|
|
91
|
+
if (mutation.idsToDelete.length > 0) {
|
|
92
|
+
this.store.deleteByIds(mutation.idsToDelete);
|
|
93
|
+
}
|
|
94
|
+
return mutation.stats;
|
|
95
|
+
}
|
|
76
96
|
async indexDirectory(rootDir, opts = {}) {
|
|
77
97
|
const totals = { indexed: 0, deleted: 0, skipped: 0 };
|
|
78
98
|
let rootExists;
|
|
@@ -119,8 +139,9 @@ export class MemoryIndexer {
|
|
|
119
139
|
totals.deleted += 1;
|
|
120
140
|
}
|
|
121
141
|
}
|
|
122
|
-
|
|
123
|
-
|
|
142
|
+
const preparedMutations = await mapLimit(files, opts.fileConcurrency ?? 2, (file) => this.prepareIndexFileRelativeTo(file, relativeBase));
|
|
143
|
+
for (const mutation of preparedMutations) {
|
|
144
|
+
const stats = this.commitPreparedMutation(mutation);
|
|
124
145
|
totals.indexed += stats.indexed;
|
|
125
146
|
totals.deleted += stats.deleted;
|
|
126
147
|
totals.skipped += stats.skipped;
|