@lotargo/memory_plugin 1.1.5 → 1.1.6
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/README.md +243 -234
- package/mcp-server/admin/server.js +228 -228
- package/mcp-server/admin/snapshot.js +303 -303
- package/mcp-server/benchmarks/fetch_real_corpus.js +351 -351
- package/mcp-server/benchmarks/gpu_profile_benchmark.js +170 -0
- package/mcp-server/benchmarks/stress_ingestion.js +195 -193
- package/mcp-server/benchmarks/test_dual_layer.js +140 -140
- package/mcp-server/cli.js +293 -7
- package/mcp-server/config/config_manager.js +11 -7
- package/mcp-server/db/database.js +43 -43
- package/mcp-server/graph/graph_extractor.js +72 -72
- package/mcp-server/graph/knowledge_linker.js +102 -102
- package/mcp-server/index.js +454 -454
- package/mcp-server/ingest/chunker.js +337 -337
- package/mcp-server/ingest/exporter.js +80 -80
- package/mcp-server/ingest/normalizer.js +104 -104
- package/mcp-server/ingest/pipeline.js +22 -7
- package/mcp-server/ingest/sentence_segmenter.js +74 -74
- package/mcp-server/memory.js +72 -72
- package/mcp-server/ml/gpu_monitor.js +166 -0
- package/mcp-server/ml/model_manager.js +321 -17
- package/mcp-server/preinstall.js +44 -44
- package/mcp-server/retrieval/retriever.js +10 -5
- package/mcp-server/setup.js +148 -148
- package/mcp-server/storage/blob_store.js +62 -62
- package/opencode-plugin/index.js +244 -244
- package/package.json +58 -54
- package/skills/using-memory/SKILL.md +122 -122
|
@@ -1,30 +1,49 @@
|
|
|
1
|
+
import os from "node:os";
|
|
2
|
+
import fs from "node:fs";
|
|
3
|
+
import path from "node:path";
|
|
1
4
|
import { MODELS_DIR } from "../db/database.js";
|
|
2
5
|
|
|
3
6
|
let extractorInstance = null;
|
|
4
7
|
let loadedModelName = null;
|
|
8
|
+
let loadedDevice = null;
|
|
5
9
|
|
|
6
10
|
let rerankerInstance = null;
|
|
7
11
|
let loadedRerankerName = null;
|
|
8
12
|
|
|
9
13
|
import { getConfig } from "../config/config_manager.js";
|
|
14
|
+
import { GpuMonitor, ExecutionTracer } from "./gpu_monitor.js";
|
|
15
|
+
export { GpuMonitor, ExecutionTracer };
|
|
16
|
+
|
|
17
|
+
function getOptimalThreadCount() {
|
|
18
|
+
const userSetting = getConfig().onnxThreads;
|
|
19
|
+
if (typeof userSetting === "number" && userSetting > 0) {
|
|
20
|
+
return userSetting;
|
|
21
|
+
}
|
|
22
|
+
const totalCores = os.availableParallelism ? os.availableParallelism() : os.cpus().length;
|
|
23
|
+
return Math.max(1, Math.min(totalCores, 8));
|
|
24
|
+
}
|
|
10
25
|
|
|
11
26
|
export async function getExtractor(modelName = null, progressCallback = null) {
|
|
12
27
|
const targetModel = modelName || getConfig().embeddingModel || "Xenova/multilingual-e5-small";
|
|
13
28
|
|
|
14
|
-
|
|
15
|
-
|
|
29
|
+
// Resolve target device BEFORE cache check so comparison works correctly
|
|
30
|
+
const rawDevice = (getConfig().executionDevice || "cpu").toLowerCase();
|
|
31
|
+
let targetDevice = "cpu";
|
|
32
|
+
if (rawDevice === "webgpu" || rawDevice === "gpu" || rawDevice === "dml" || rawDevice === "cuda") {
|
|
33
|
+
if (process.platform === "win32") {
|
|
34
|
+
targetDevice = "dml";
|
|
35
|
+
} else if (process.platform === "linux") {
|
|
36
|
+
targetDevice = "cuda";
|
|
37
|
+
} else {
|
|
38
|
+
targetDevice = "webgpu";
|
|
39
|
+
}
|
|
16
40
|
}
|
|
17
41
|
|
|
18
|
-
|
|
42
|
+
if (extractorInstance && loadedModelName === targetModel && loadedDevice === targetDevice) {
|
|
43
|
+
return extractorInstance;
|
|
44
|
+
}
|
|
19
45
|
|
|
20
|
-
|
|
21
|
-
try {
|
|
22
|
-
const { executionProviders } = await import("@xenova/transformers/src/backends/onnx.js");
|
|
23
|
-
const wasmIdx = executionProviders.indexOf("wasm");
|
|
24
|
-
if (wasmIdx !== -1) {
|
|
25
|
-
executionProviders.splice(wasmIdx, 1);
|
|
26
|
-
}
|
|
27
|
-
} catch {}
|
|
46
|
+
const { pipeline, env } = await import("@huggingface/transformers");
|
|
28
47
|
|
|
29
48
|
env.cacheDir = MODELS_DIR;
|
|
30
49
|
env.allowLocalModels = true;
|
|
@@ -33,7 +52,32 @@ export async function getExtractor(modelName = null, progressCallback = null) {
|
|
|
33
52
|
env.remotePathTemplate = "{model}/resolve/{revision}/";
|
|
34
53
|
env.sharp = false;
|
|
35
54
|
|
|
36
|
-
const
|
|
55
|
+
const numThreads = getOptimalThreadCount();
|
|
56
|
+
if (env.backends?.onnx?.wasm) {
|
|
57
|
+
env.backends.onnx.wasm.numThreads = numThreads;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
const sessionOptions = {
|
|
61
|
+
graphOptimizationLevel: "all",
|
|
62
|
+
executionMode: targetDevice !== "cpu" ? "parallel" : "sequential",
|
|
63
|
+
};
|
|
64
|
+
|
|
65
|
+
if (targetDevice !== "cpu") {
|
|
66
|
+
sessionOptions.enableCpuMemArena = false;
|
|
67
|
+
sessionOptions.enableMemPattern = false;
|
|
68
|
+
if (targetDevice === "dml") {
|
|
69
|
+
sessionOptions.executionProviders = [{ name: "dml", device_id: 0 }];
|
|
70
|
+
} else if (targetDevice === "cuda") {
|
|
71
|
+
sessionOptions.executionProviders = [{ name: "cuda", device_id: 0 }];
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
const pipelineOpts = {
|
|
76
|
+
quantized: true,
|
|
77
|
+
dtype: "q8",
|
|
78
|
+
device: targetDevice,
|
|
79
|
+
session_options: sessionOptions,
|
|
80
|
+
};
|
|
37
81
|
if (progressCallback) {
|
|
38
82
|
pipelineOpts.progress_callback = progressCallback;
|
|
39
83
|
}
|
|
@@ -41,11 +85,26 @@ export async function getExtractor(modelName = null, progressCallback = null) {
|
|
|
41
85
|
try {
|
|
42
86
|
extractorInstance = await pipeline("feature-extraction", targetModel, pipelineOpts);
|
|
43
87
|
loadedModelName = targetModel;
|
|
88
|
+
loadedDevice = targetDevice;
|
|
89
|
+
|
|
90
|
+
if (targetDevice !== "cpu") {
|
|
91
|
+
try {
|
|
92
|
+
await extractorInstance("GPU VRAM Warmup Init", { pooling: "mean", normalize: true });
|
|
93
|
+
} catch {}
|
|
94
|
+
}
|
|
44
95
|
} catch (err) {
|
|
45
|
-
|
|
46
|
-
|
|
96
|
+
if (targetDevice !== "cpu") {
|
|
97
|
+
console.warn(`[GPU Engine] GPU initialization (${targetDevice}) failed: ${err.message}. Falling back to CPU...`);
|
|
98
|
+
pipelineOpts.device = "cpu";
|
|
99
|
+
pipelineOpts.session_options.executionMode = "sequential";
|
|
100
|
+
extractorInstance = await pipeline("feature-extraction", targetModel, pipelineOpts);
|
|
101
|
+
loadedModelName = targetModel;
|
|
102
|
+
loadedDevice = "cpu";
|
|
103
|
+
} else if (targetModel !== "Xenova/multilingual-e5-small") {
|
|
104
|
+
console.warn(`HuggingFace model load failed for ${targetModel}: ${err.message}. Falling back to default Xenova/multilingual-e5-small...`);
|
|
47
105
|
extractorInstance = await pipeline("feature-extraction", "Xenova/multilingual-e5-small", pipelineOpts);
|
|
48
106
|
loadedModelName = "Xenova/multilingual-e5-small";
|
|
107
|
+
loadedDevice = targetDevice;
|
|
49
108
|
} else {
|
|
50
109
|
throw err;
|
|
51
110
|
}
|
|
@@ -54,6 +113,12 @@ export async function getExtractor(modelName = null, progressCallback = null) {
|
|
|
54
113
|
return extractorInstance;
|
|
55
114
|
}
|
|
56
115
|
|
|
116
|
+
export function resetExtractor() {
|
|
117
|
+
extractorInstance = null;
|
|
118
|
+
loadedModelName = null;
|
|
119
|
+
loadedDevice = null;
|
|
120
|
+
}
|
|
121
|
+
|
|
57
122
|
export function formatInputText(text, isQuery = false, modelName = null, instruction = null) {
|
|
58
123
|
if (!text) return "";
|
|
59
124
|
const targetModel = modelName || getConfig().embeddingModel || "Xenova/multilingual-e5-small";
|
|
@@ -66,6 +131,11 @@ export function formatInputText(text, isQuery = false, modelName = null, instruc
|
|
|
66
131
|
cleanText = cleanText.substring(7).trim();
|
|
67
132
|
}
|
|
68
133
|
|
|
134
|
+
// Safety trim long text to prevent ONNX tensor padding explosions (>4000 chars ~1000 tokens)
|
|
135
|
+
if (cleanText.length > 4000) {
|
|
136
|
+
cleanText = cleanText.substring(0, 4000);
|
|
137
|
+
}
|
|
138
|
+
|
|
69
139
|
// 1. E5 Model Family (multilingual-e5-small, multilingual-e5-large, etc.)
|
|
70
140
|
if (name.includes("e5")) {
|
|
71
141
|
const isInstructModel = name.includes("-instruct");
|
|
@@ -98,12 +168,127 @@ export async function embedText(text, isQuery = false, modelName = null, progres
|
|
|
98
168
|
const extractor = await getExtractor(targetModel, progressCallback);
|
|
99
169
|
const formattedText = formatInputText(text, isQuery, targetModel, instruction);
|
|
100
170
|
|
|
171
|
+
const isBgeM3 = targetModel.toLowerCase().includes("bge-m3");
|
|
172
|
+
const maxLen = isBgeM3 ? 1024 : 512;
|
|
173
|
+
|
|
101
174
|
const output = await extractor(formattedText, {
|
|
102
175
|
pooling: "mean",
|
|
103
176
|
normalize: true,
|
|
177
|
+
truncation: true,
|
|
178
|
+
max_length: maxLen,
|
|
104
179
|
});
|
|
105
180
|
|
|
106
|
-
|
|
181
|
+
const result = output.data.slice();
|
|
182
|
+
if (typeof output.dispose === 'function') {
|
|
183
|
+
output.dispose();
|
|
184
|
+
}
|
|
185
|
+
return result;
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
export async function embedBatch(texts, isQuery = false, modelName = null, progressCallback = null, instruction = null, traceOptions = {}) {
|
|
189
|
+
if (!texts || texts.length === 0) return [];
|
|
190
|
+
const targetModel = modelName || getConfig().embeddingModel || "Xenova/multilingual-e5-small";
|
|
191
|
+
|
|
192
|
+
const rawDevice = (getConfig().executionDevice || "cpu").toLowerCase();
|
|
193
|
+
const isGpu = rawDevice === "webgpu" || rawDevice === "gpu" || rawDevice === "dml" || rawDevice === "cuda";
|
|
194
|
+
|
|
195
|
+
const tracer = traceOptions.enableTrace ? new ExecutionTracer(`Embed Batch (${texts.length} items)`) : null;
|
|
196
|
+
const monitor = (isGpu && traceOptions.enableMonitor) ? new GpuMonitor(50) : null;
|
|
197
|
+
if (monitor) monitor.start();
|
|
198
|
+
|
|
199
|
+
try {
|
|
200
|
+
if (tracer) tracer.startStage("Model Initialization & Session", "CPU");
|
|
201
|
+
const extractor = await getExtractor(targetModel, progressCallback);
|
|
202
|
+
|
|
203
|
+
if (tracer) tracer.startStage("Text Formatting & Tokenization Preprocessing", "CPU");
|
|
204
|
+
const formattedTexts = texts.map((text) => formatInputText(text, isQuery, targetModel, instruction));
|
|
205
|
+
|
|
206
|
+
const userBatch = getConfig().batchSize || 12;
|
|
207
|
+
const isBgeM3 = targetModel.toLowerCase().includes("bge-m3");
|
|
208
|
+
const isLarge = isBgeM3 || targetModel.toLowerCase().includes("large");
|
|
209
|
+
|
|
210
|
+
// DYNAMIC ATTENTION BUDGET SAMPLER (PyTorch-style Token Budgeting):
|
|
211
|
+
// Attention memory scales quadratically O(seq_len^2).
|
|
212
|
+
// Target max GPU attention budget per ONNX pass: ~2,000,000 token-squared units (~1.5 GB VRAM peak).
|
|
213
|
+
// Short texts (50 tokens) dynamically scale UP to full userBatch (32-64 items per pass) for max GPU compute.
|
|
214
|
+
// Long texts (1000 tokens) dynamically scale DOWN to 2-4 items per pass, keeping VRAM strictly <1.5 GB.
|
|
215
|
+
const configuredBudget = getConfig().gpuAttentionBudget || 2000000;
|
|
216
|
+
const maxAttentionBudget = isGpu
|
|
217
|
+
? (isLarge ? Math.min(configuredBudget, 2000000) : configuredBudget)
|
|
218
|
+
: 32000000;
|
|
219
|
+
|
|
220
|
+
const CHAR_TO_TOKEN = 3.5;
|
|
221
|
+
const subBatches = [];
|
|
222
|
+
let currentSubBatch = [];
|
|
223
|
+
let currentSubBatchCost = 0;
|
|
224
|
+
|
|
225
|
+
for (const text of formattedTexts) {
|
|
226
|
+
const estimatedTokens = Math.max(1, Math.ceil(text.length / CHAR_TO_TOKEN));
|
|
227
|
+
const cost = estimatedTokens * estimatedTokens;
|
|
228
|
+
|
|
229
|
+
if (
|
|
230
|
+
currentSubBatch.length > 0 &&
|
|
231
|
+
(currentSubBatch.length >= userBatch || currentSubBatchCost + cost > maxAttentionBudget)
|
|
232
|
+
) {
|
|
233
|
+
subBatches.push(currentSubBatch);
|
|
234
|
+
currentSubBatch = [];
|
|
235
|
+
currentSubBatchCost = 0;
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
currentSubBatch.push(text);
|
|
239
|
+
currentSubBatchCost += cost;
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
if (currentSubBatch.length > 0) {
|
|
243
|
+
subBatches.push(currentSubBatch);
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
if (tracer) tracer.startStage("ONNX Model Tensor Execution", isGpu ? "GPU" : "CPU");
|
|
247
|
+
|
|
248
|
+
const allResults = [];
|
|
249
|
+
const maxLen = isBgeM3 ? 1024 : 512;
|
|
250
|
+
|
|
251
|
+
for (const batchTexts of subBatches) {
|
|
252
|
+
const output = await extractor(batchTexts, {
|
|
253
|
+
pooling: "mean",
|
|
254
|
+
normalize: true,
|
|
255
|
+
truncation: true,
|
|
256
|
+
padding: isGpu ? "max_length" : true,
|
|
257
|
+
max_length: maxLen,
|
|
258
|
+
});
|
|
259
|
+
|
|
260
|
+
const dims = output.dims;
|
|
261
|
+
const batchSize = dims[0];
|
|
262
|
+
const vectorDim = dims[dims.length - 1];
|
|
263
|
+
const rawData = output.data;
|
|
264
|
+
|
|
265
|
+
for (let i = 0; i < batchSize; i++) {
|
|
266
|
+
const byteOffset = i * vectorDim;
|
|
267
|
+
allResults.push(rawData.slice(byteOffset, byteOffset + vectorDim));
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
if (typeof output.dispose === 'function') {
|
|
271
|
+
output.dispose();
|
|
272
|
+
}
|
|
273
|
+
|
|
274
|
+
if (isGpu && global.gc) {
|
|
275
|
+
global.gc({ type: 'minor' });
|
|
276
|
+
}
|
|
277
|
+
}
|
|
278
|
+
|
|
279
|
+
if (tracer) tracer.endStage();
|
|
280
|
+
|
|
281
|
+
const gpuStats = monitor ? monitor.stop() : null;
|
|
282
|
+
|
|
283
|
+
if (tracer && traceOptions.verboseTrace) {
|
|
284
|
+
tracer.printTraceReport(gpuStats);
|
|
285
|
+
}
|
|
286
|
+
|
|
287
|
+
return allResults;
|
|
288
|
+
} catch (err) {
|
|
289
|
+
if (monitor) monitor.stop();
|
|
290
|
+
throw err;
|
|
291
|
+
}
|
|
107
292
|
}
|
|
108
293
|
|
|
109
294
|
export async function getReranker(modelName = "Xenova/bge-reranker-base", progressCallback = null) {
|
|
@@ -111,7 +296,7 @@ export async function getReranker(modelName = "Xenova/bge-reranker-base", progre
|
|
|
111
296
|
return rerankerInstance;
|
|
112
297
|
}
|
|
113
298
|
|
|
114
|
-
const { pipeline, env } = await import("@
|
|
299
|
+
const { pipeline, env } = await import("@huggingface/transformers");
|
|
115
300
|
env.cacheDir = MODELS_DIR;
|
|
116
301
|
env.allowLocalModels = true;
|
|
117
302
|
env.allowRemoteModels = true;
|
|
@@ -119,7 +304,32 @@ export async function getReranker(modelName = "Xenova/bge-reranker-base", progre
|
|
|
119
304
|
env.remotePathTemplate = "{model}/resolve/{revision}/";
|
|
120
305
|
env.sharp = false;
|
|
121
306
|
|
|
122
|
-
const
|
|
307
|
+
const rawDevice = (getConfig().executionDevice || "cpu").toLowerCase();
|
|
308
|
+
let targetDevice = "cpu";
|
|
309
|
+
if (rawDevice === "webgpu" || rawDevice === "gpu" || rawDevice === "dml" || rawDevice === "cuda") {
|
|
310
|
+
targetDevice = process.platform === "win32" ? "dml" : (process.platform === "linux" ? "cuda" : "webgpu");
|
|
311
|
+
}
|
|
312
|
+
|
|
313
|
+
const sessionOptions = {
|
|
314
|
+
graphOptimizationLevel: "all",
|
|
315
|
+
};
|
|
316
|
+
|
|
317
|
+
if (targetDevice !== "cpu") {
|
|
318
|
+
sessionOptions.enableCpuMemArena = true;
|
|
319
|
+
sessionOptions.enableMemPattern = true;
|
|
320
|
+
if (targetDevice === "dml") {
|
|
321
|
+
sessionOptions.executionProviders = [{ name: "dml", device_id: 0 }];
|
|
322
|
+
} else if (targetDevice === "cuda") {
|
|
323
|
+
sessionOptions.executionProviders = [{ name: "cuda", device_id: 0 }];
|
|
324
|
+
}
|
|
325
|
+
}
|
|
326
|
+
|
|
327
|
+
const pipelineOpts = {
|
|
328
|
+
quantized: true,
|
|
329
|
+
dtype: "q8",
|
|
330
|
+
device: targetDevice,
|
|
331
|
+
session_options: sessionOptions,
|
|
332
|
+
};
|
|
123
333
|
if (progressCallback) {
|
|
124
334
|
pipelineOpts.progress_callback = progressCallback;
|
|
125
335
|
}
|
|
@@ -189,3 +399,97 @@ export function cosineSimilarity(vecA, vecB) {
|
|
|
189
399
|
if (normA === 0 || normB === 0) return 0;
|
|
190
400
|
return dotProduct / (Math.sqrt(normA) * Math.sqrt(normB));
|
|
191
401
|
}
|
|
402
|
+
|
|
403
|
+
export function getModelStorageInfo(modelName) {
|
|
404
|
+
if (!modelName || modelName === "none") return { status: "not_downloaded", sizeMB: "0.00", bytes: 0 };
|
|
405
|
+
const parts = modelName.split("/");
|
|
406
|
+
const modelDir = path.join(MODELS_DIR, ...parts);
|
|
407
|
+
|
|
408
|
+
if (!fs.existsSync(modelDir)) {
|
|
409
|
+
return { status: "not_downloaded", sizeMB: "0.00", bytes: 0, dir: modelDir };
|
|
410
|
+
}
|
|
411
|
+
|
|
412
|
+
let totalBytes = 0;
|
|
413
|
+
let hasConfig = false;
|
|
414
|
+
let hasTokenizer = false;
|
|
415
|
+
let hasOnnxWeights = false;
|
|
416
|
+
|
|
417
|
+
function scan(dir) {
|
|
418
|
+
try {
|
|
419
|
+
const entries = fs.readdirSync(dir, { withFileTypes: true });
|
|
420
|
+
for (const entry of entries) {
|
|
421
|
+
const fullPath = path.join(dir, entry.name);
|
|
422
|
+
if (entry.isDirectory()) {
|
|
423
|
+
scan(fullPath);
|
|
424
|
+
} else if (entry.isFile()) {
|
|
425
|
+
const stat = fs.statSync(fullPath);
|
|
426
|
+
totalBytes += stat.size;
|
|
427
|
+
if (entry.name === "config.json") hasConfig = true;
|
|
428
|
+
if (entry.name.includes("tokenizer")) hasTokenizer = true;
|
|
429
|
+
// ONNX weights must be substantial (>5MB) or external data (.onnx_data)
|
|
430
|
+
if ((entry.name.endsWith(".onnx") || entry.name.endsWith(".onnx_data")) && stat.size > 5 * 1024 * 1024) {
|
|
431
|
+
hasOnnxWeights = true;
|
|
432
|
+
}
|
|
433
|
+
}
|
|
434
|
+
}
|
|
435
|
+
} catch {}
|
|
436
|
+
}
|
|
437
|
+
|
|
438
|
+
scan(modelDir);
|
|
439
|
+
|
|
440
|
+
const sizeMB = (totalBytes / (1024 * 1024)).toFixed(2);
|
|
441
|
+
|
|
442
|
+
if (totalBytes === 0) {
|
|
443
|
+
return { status: "not_downloaded", sizeMB: "0.00", bytes: 0, dir: modelDir };
|
|
444
|
+
}
|
|
445
|
+
|
|
446
|
+
// Model is ready only if it has config, tokenizer, ONNX weights >5MB, and total folder size >10MB
|
|
447
|
+
if (hasConfig && hasTokenizer && hasOnnxWeights && totalBytes > 10 * 1024 * 1024) {
|
|
448
|
+
return { status: "downloaded", sizeMB, bytes: totalBytes, dir: modelDir };
|
|
449
|
+
}
|
|
450
|
+
|
|
451
|
+
return { status: "partial", sizeMB, bytes: totalBytes, dir: modelDir };
|
|
452
|
+
}
|
|
453
|
+
|
|
454
|
+
export function deleteModelCache(modelName) {
|
|
455
|
+
const info = getModelStorageInfo(modelName);
|
|
456
|
+
if (info.status === "not_downloaded" || !fs.existsSync(info.dir)) {
|
|
457
|
+
return { deleted: false, reason: "Model directory not found" };
|
|
458
|
+
}
|
|
459
|
+
|
|
460
|
+
try {
|
|
461
|
+
fs.rmSync(info.dir, { recursive: true, force: true });
|
|
462
|
+
const parentDir = path.dirname(info.dir);
|
|
463
|
+
if (fs.existsSync(parentDir) && fs.readdirSync(parentDir).length === 0) {
|
|
464
|
+
fs.rmdirSync(parentDir);
|
|
465
|
+
}
|
|
466
|
+
resetExtractor();
|
|
467
|
+
return { deleted: true, modelName, freedMB: info.sizeMB };
|
|
468
|
+
} catch (err) {
|
|
469
|
+
return { deleted: false, reason: err.message };
|
|
470
|
+
}
|
|
471
|
+
}
|
|
472
|
+
|
|
473
|
+
export function listAllCachedModels() {
|
|
474
|
+
const result = [];
|
|
475
|
+
if (!fs.existsSync(MODELS_DIR)) return result;
|
|
476
|
+
|
|
477
|
+
try {
|
|
478
|
+
const orgs = fs.readdirSync(MODELS_DIR, { withFileTypes: true });
|
|
479
|
+
for (const org of orgs) {
|
|
480
|
+
if (org.isDirectory()) {
|
|
481
|
+
const orgDir = path.join(MODELS_DIR, org.name);
|
|
482
|
+
const models = fs.readdirSync(orgDir, { withFileTypes: true });
|
|
483
|
+
for (const model of models) {
|
|
484
|
+
if (model.isDirectory()) {
|
|
485
|
+
const modelName = `${org.name}/${model.name}`;
|
|
486
|
+
const info = getModelStorageInfo(modelName);
|
|
487
|
+
result.push({ modelName, ...info });
|
|
488
|
+
}
|
|
489
|
+
}
|
|
490
|
+
}
|
|
491
|
+
}
|
|
492
|
+
} catch {}
|
|
493
|
+
|
|
494
|
+
return result;
|
|
495
|
+
}
|
package/mcp-server/preinstall.js
CHANGED
|
@@ -1,44 +1,44 @@
|
|
|
1
|
-
import { execSync } from "child_process";
|
|
2
|
-
|
|
3
|
-
// Only run graceful process termination during explicit global npm updates
|
|
4
|
-
// Skip if running interactively or inside active MCP sessions to avoid EOF drops
|
|
5
|
-
if (process.env.npm_config_global === "true" || process.env.MEMORY_PREINSTALL_FORCE === "true") {
|
|
6
|
-
try {
|
|
7
|
-
const currentPid = process.pid;
|
|
8
|
-
const ppid = process.ppid;
|
|
9
|
-
|
|
10
|
-
if (process.platform === "win32") {
|
|
11
|
-
try {
|
|
12
|
-
const psCmd = `Get-CimInstance Win32_Process | Where-Object { ($_.CommandLine -like '*mcp-server/index.js*' -or $_.CommandLine -like '*mcp-server\\\\index.js*') -and $_.CommandLine -notlike '*install*' -and $_.ProcessId -ne ${currentPid} -and $_.ProcessId -ne ${ppid} } | ForEach-Object { Stop-Process -Id $_.ProcessId -Force }`;
|
|
13
|
-
execSync(`powershell -NoProfile -NonInteractive -Command "${psCmd}"`, { stdio: "ignore" });
|
|
14
|
-
} catch {}
|
|
15
|
-
} else {
|
|
16
|
-
try {
|
|
17
|
-
const psOutput = execSync(`ps -eo pid,ppid,args 2>/dev/null || true`, { encoding: "utf-8" });
|
|
18
|
-
const lines = psOutput.split("\n");
|
|
19
|
-
for (const line of lines) {
|
|
20
|
-
const trimmed = line.trim();
|
|
21
|
-
if (!trimmed) continue;
|
|
22
|
-
const parts = trimmed.split(/\s+/);
|
|
23
|
-
const pid = parseInt(parts[0], 10);
|
|
24
|
-
const parentPid = parseInt(parts[1], 10);
|
|
25
|
-
const cmd = parts.slice(2).join(" ");
|
|
26
|
-
|
|
27
|
-
if (!pid || pid === currentPid || pid === ppid || parentPid === currentPid) continue;
|
|
28
|
-
|
|
29
|
-
const isServer = cmd.includes("mcp-server/index.js") || cmd.includes("mcp-server/index.js");
|
|
30
|
-
const isInstaller = /npm|npx|yarn|pnpm|preinstall|install/i.test(cmd);
|
|
31
|
-
|
|
32
|
-
if (isServer && !isInstaller) {
|
|
33
|
-
try {
|
|
34
|
-
process.kill(pid, "SIGTERM");
|
|
35
|
-
} catch {}
|
|
36
|
-
}
|
|
37
|
-
}
|
|
38
|
-
} catch {}
|
|
39
|
-
}
|
|
40
|
-
} catch (e) {
|
|
41
|
-
// Ignore errors
|
|
42
|
-
}
|
|
43
|
-
}
|
|
44
|
-
|
|
1
|
+
import { execSync } from "child_process";
|
|
2
|
+
|
|
3
|
+
// Only run graceful process termination during explicit global npm updates
|
|
4
|
+
// Skip if running interactively or inside active MCP sessions to avoid EOF drops
|
|
5
|
+
if (process.env.npm_config_global === "true" || process.env.MEMORY_PREINSTALL_FORCE === "true") {
|
|
6
|
+
try {
|
|
7
|
+
const currentPid = process.pid;
|
|
8
|
+
const ppid = process.ppid;
|
|
9
|
+
|
|
10
|
+
if (process.platform === "win32") {
|
|
11
|
+
try {
|
|
12
|
+
const psCmd = `Get-CimInstance Win32_Process | Where-Object { ($_.CommandLine -like '*mcp-server/index.js*' -or $_.CommandLine -like '*mcp-server\\\\index.js*') -and $_.CommandLine -notlike '*install*' -and $_.ProcessId -ne ${currentPid} -and $_.ProcessId -ne ${ppid} } | ForEach-Object { Stop-Process -Id $_.ProcessId -Force }`;
|
|
13
|
+
execSync(`powershell -NoProfile -NonInteractive -Command "${psCmd}"`, { stdio: "ignore" });
|
|
14
|
+
} catch {}
|
|
15
|
+
} else {
|
|
16
|
+
try {
|
|
17
|
+
const psOutput = execSync(`ps -eo pid,ppid,args 2>/dev/null || true`, { encoding: "utf-8" });
|
|
18
|
+
const lines = psOutput.split("\n");
|
|
19
|
+
for (const line of lines) {
|
|
20
|
+
const trimmed = line.trim();
|
|
21
|
+
if (!trimmed) continue;
|
|
22
|
+
const parts = trimmed.split(/\s+/);
|
|
23
|
+
const pid = parseInt(parts[0], 10);
|
|
24
|
+
const parentPid = parseInt(parts[1], 10);
|
|
25
|
+
const cmd = parts.slice(2).join(" ");
|
|
26
|
+
|
|
27
|
+
if (!pid || pid === currentPid || pid === ppid || parentPid === currentPid) continue;
|
|
28
|
+
|
|
29
|
+
const isServer = cmd.includes("mcp-server/index.js") || cmd.includes("mcp-server/index.js");
|
|
30
|
+
const isInstaller = /npm|npx|yarn|pnpm|preinstall|install/i.test(cmd);
|
|
31
|
+
|
|
32
|
+
if (isServer && !isInstaller) {
|
|
33
|
+
try {
|
|
34
|
+
process.kill(pid, "SIGTERM");
|
|
35
|
+
} catch {}
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
} catch {}
|
|
39
|
+
}
|
|
40
|
+
} catch (e) {
|
|
41
|
+
// Ignore errors
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { getDatabase } from "../db/database.js";
|
|
2
|
-
import { embedText,
|
|
2
|
+
import { embedText, cosineSimilarity, rerankHits } from "../ml/model_manager.js";
|
|
3
3
|
import { getRelatedSymbols } from "../graph/graph_extractor.js";
|
|
4
4
|
import { getConfig } from "../config/config_manager.js";
|
|
5
5
|
|
|
@@ -40,17 +40,22 @@ export function bm25Search(db, query, limit = 30) {
|
|
|
40
40
|
export function vectorSearch(db, queryVector, limit = 30, minSim = 0.25) {
|
|
41
41
|
if (!queryVector || queryVector.length === 0) return [];
|
|
42
42
|
|
|
43
|
+
const vectorDim = queryVector.length;
|
|
44
|
+
const tempBuf = new ArrayBuffer(vectorDim * 4);
|
|
45
|
+
const tempView = new Uint8Array(tempBuf);
|
|
46
|
+
const tempVec = new Float32Array(tempBuf);
|
|
47
|
+
|
|
43
48
|
const stmt = db.prepare(`
|
|
44
49
|
SELECT m.id, m.section_id, m.doc_id, m.content, m.vector, s.breadcrumbs
|
|
45
50
|
FROM micro_chunks m
|
|
46
51
|
JOIN sections s ON m.section_id = s.id;
|
|
47
52
|
`);
|
|
48
|
-
const rows = stmt.all();
|
|
49
53
|
|
|
50
54
|
const scored = [];
|
|
51
|
-
for (const r of
|
|
52
|
-
|
|
53
|
-
|
|
55
|
+
for (const r of stmt.iterate()) {
|
|
56
|
+
tempView.set(r.vector.subarray(0, vectorDim * 4));
|
|
57
|
+
|
|
58
|
+
const sim = cosineSimilarity(queryVector, tempVec);
|
|
54
59
|
if (!isNaN(sim) && sim >= minSim) {
|
|
55
60
|
scored.push({
|
|
56
61
|
id: r.id,
|