@lotargo/memory_plugin 1.1.9 → 1.2.1
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/mcp-server/ml/model_manager.js +70 -11
- package/mcp-server/setup.js +15 -1
- package/package.json +1 -1
|
@@ -23,6 +23,25 @@ function getOptimalThreadCount() {
|
|
|
23
23
|
return Math.max(1, Math.min(totalCores, 8));
|
|
24
24
|
}
|
|
25
25
|
|
|
26
|
+
export function ensureValidModelDirectory() {
|
|
27
|
+
try {
|
|
28
|
+
if (!fs.existsSync(MODELS_DIR)) {
|
|
29
|
+
fs.mkdirSync(MODELS_DIR, { recursive: true });
|
|
30
|
+
}
|
|
31
|
+
const testFile = path.join(MODELS_DIR, ".path_check");
|
|
32
|
+
fs.writeFileSync(testFile, "ok");
|
|
33
|
+
fs.unlinkSync(testFile);
|
|
34
|
+
return MODELS_DIR;
|
|
35
|
+
} catch (err) {
|
|
36
|
+
console.warn(`[Self-Healing] Model directory path "${MODELS_DIR}" is inaccessible or invalid (${err.message}). Falling back to standard default model storage path...`);
|
|
37
|
+
const fallbackDir = path.join(MODELS_DIR, "..", "models");
|
|
38
|
+
try {
|
|
39
|
+
fs.mkdirSync(fallbackDir, { recursive: true });
|
|
40
|
+
} catch (e) {}
|
|
41
|
+
return fallbackDir;
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
|
|
26
45
|
export async function getExtractor(modelName = null, progressCallback = null) {
|
|
27
46
|
const targetModel = modelName || getConfig().embeddingModel || "Xenova/multilingual-e5-small";
|
|
28
47
|
|
|
@@ -43,9 +62,12 @@ export async function getExtractor(modelName = null, progressCallback = null) {
|
|
|
43
62
|
return extractorInstance;
|
|
44
63
|
}
|
|
45
64
|
|
|
65
|
+
// Ensure valid writable storage directory
|
|
66
|
+
const cacheDir = ensureValidModelDirectory();
|
|
67
|
+
|
|
46
68
|
const { pipeline, env } = await import("@huggingface/transformers");
|
|
47
69
|
|
|
48
|
-
env.cacheDir =
|
|
70
|
+
env.cacheDir = cacheDir;
|
|
49
71
|
env.allowLocalModels = true;
|
|
50
72
|
env.allowRemoteModels = true;
|
|
51
73
|
env.remoteHost = "https://huggingface.co";
|
|
@@ -93,18 +115,52 @@ export async function getExtractor(modelName = null, progressCallback = null) {
|
|
|
93
115
|
} catch {}
|
|
94
116
|
}
|
|
95
117
|
} catch (err) {
|
|
118
|
+
const isNetworkError = err.message && (
|
|
119
|
+
err.message.includes("fetch") ||
|
|
120
|
+
err.message.includes("network") ||
|
|
121
|
+
err.message.includes("ETIMEDOUT") ||
|
|
122
|
+
err.message.includes("ENOTFOUND") ||
|
|
123
|
+
err.message.includes("503") ||
|
|
124
|
+
err.message.includes("502") ||
|
|
125
|
+
err.message.includes("504")
|
|
126
|
+
);
|
|
127
|
+
|
|
128
|
+
if (isNetworkError) {
|
|
129
|
+
console.warn(`[Model Manager] ⚠️ Network interruption while loading "${targetModel}": ${err.message}. Retrying / Resuming download...`);
|
|
130
|
+
} else {
|
|
131
|
+
console.warn(`[Model Manager] ⚠️ Unrecoverable file error for "${targetModel}": ${err.message}. Purging corrupted cache...`);
|
|
132
|
+
deleteModelCache(targetModel);
|
|
133
|
+
}
|
|
134
|
+
|
|
96
135
|
if (targetDevice !== "cpu") {
|
|
97
|
-
console.warn(`[GPU Engine] GPU initialization (${targetDevice}) failed
|
|
136
|
+
console.warn(`[GPU Engine] GPU initialization (${targetDevice}) failed. Retrying on CPU...`);
|
|
98
137
|
pipelineOpts.device = "cpu";
|
|
99
138
|
pipelineOpts.session_options.executionMode = "sequential";
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
139
|
+
try {
|
|
140
|
+
extractorInstance = await pipeline("feature-extraction", targetModel, pipelineOpts);
|
|
141
|
+
loadedModelName = targetModel;
|
|
142
|
+
loadedDevice = "cpu";
|
|
143
|
+
return extractorInstance;
|
|
144
|
+
} catch (err2) {
|
|
145
|
+
if (!isNetworkError) deleteModelCache(targetModel);
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
if (targetModel !== "Xenova/multilingual-e5-small") {
|
|
150
|
+
console.warn(`[Fallback] Reverting to standard default model "Xenova/multilingual-e5-small"...`);
|
|
151
|
+
try {
|
|
152
|
+
extractorInstance = await pipeline("feature-extraction", "Xenova/multilingual-e5-small", {
|
|
153
|
+
quantized: true,
|
|
154
|
+
dtype: "q8",
|
|
155
|
+
device: "cpu",
|
|
156
|
+
session_options: { graphOptimizationLevel: "all", executionMode: "sequential" },
|
|
157
|
+
});
|
|
158
|
+
loadedModelName = "Xenova/multilingual-e5-small";
|
|
159
|
+
loadedDevice = "cpu";
|
|
160
|
+
} catch (err3) {
|
|
161
|
+
console.error(`[Fatal] Could not load default fallback model: ${err3.message}`);
|
|
162
|
+
throw err3;
|
|
163
|
+
}
|
|
108
164
|
} else {
|
|
109
165
|
throw err;
|
|
110
166
|
}
|
|
@@ -296,6 +352,8 @@ export async function getReranker(modelName = "Xenova/bge-reranker-base", progre
|
|
|
296
352
|
return rerankerInstance;
|
|
297
353
|
}
|
|
298
354
|
|
|
355
|
+
checkAndSelfHealModel(modelName);
|
|
356
|
+
|
|
299
357
|
const { pipeline, env } = await import("@huggingface/transformers");
|
|
300
358
|
env.cacheDir = MODELS_DIR;
|
|
301
359
|
env.allowLocalModels = true;
|
|
@@ -338,7 +396,8 @@ export async function getReranker(modelName = "Xenova/bge-reranker-base", progre
|
|
|
338
396
|
rerankerInstance = await pipeline("text-classification", modelName, pipelineOpts);
|
|
339
397
|
loadedRerankerName = modelName;
|
|
340
398
|
} catch (err) {
|
|
341
|
-
console.warn(`Failed to load reranker model ${modelName}: ${err.message}
|
|
399
|
+
console.warn(`Failed to load reranker model ${modelName}: ${err.message}. Purging corrupt files...`);
|
|
400
|
+
deleteModelCache(modelName);
|
|
342
401
|
return null;
|
|
343
402
|
}
|
|
344
403
|
return rerankerInstance;
|
package/mcp-server/setup.js
CHANGED
|
@@ -49,8 +49,22 @@ export async function runSetup() {
|
|
|
49
49
|
if (existsSync(legacyPluginFile)) {
|
|
50
50
|
try { const { unlink } = await import("fs/promises"); await unlink(legacyPluginFile); } catch (e) {}
|
|
51
51
|
}
|
|
52
|
+
|
|
53
|
+
// Purge stale OpenCode package cache for memory plugin so OpenCode downloads latest version
|
|
54
|
+
const opencodeCachePackages = join(home, ".cache", "opencode", "packages");
|
|
55
|
+
if (existsSync(opencodeCachePackages)) {
|
|
56
|
+
try {
|
|
57
|
+
const { rm } = await import("fs/promises");
|
|
58
|
+
const targets = ["@lotargo", "memory_plugin", "memory_plugin@latest", "opencode-memory-plugin", "opencode-memory-plugin@latest"];
|
|
59
|
+
for (const t of targets) {
|
|
60
|
+
const p = join(opencodeCachePackages, t);
|
|
61
|
+
if (existsSync(p)) await rm(p, { recursive: true, force: true });
|
|
62
|
+
}
|
|
63
|
+
} catch (e) {}
|
|
64
|
+
}
|
|
65
|
+
|
|
52
66
|
await writeFile(opencodeConfigPath, JSON.stringify(config, null, 2));
|
|
53
|
-
console.log(" [OK] OpenCode: configured plugin in ~/.config/opencode/opencode.json");
|
|
67
|
+
console.log(" [OK] OpenCode: configured plugin in ~/.config/opencode/opencode.json and cleared stale cache");
|
|
54
68
|
configuredCount++;
|
|
55
69
|
} catch (err) {
|
|
56
70
|
console.log(" [SKIP] OpenCode setup skipped:", err.message);
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@lotargo/memory_plugin",
|
|
3
|
-
"version": "1.1
|
|
3
|
+
"version": "1.2.1",
|
|
4
4
|
"description": "Persistent memory agent for coding AI tools — remembers user preferences and project context across sessions. Works with Antigravity, OpenCode, Claude Code, and Codex.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "opencode-plugin/index.js",
|