@lotargo/memory_plugin 1.2.0 → 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.
@@ -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 = MODELS_DIR;
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: ${err.message}. Falling back to CPU...`);
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
- 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...`);
105
- extractorInstance = await pipeline("feature-extraction", "Xenova/multilingual-e5-small", pipelineOpts);
106
- loadedModelName = "Xenova/multilingual-e5-small";
107
- loadedDevice = targetDevice;
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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@lotargo/memory_plugin",
3
- "version": "1.2.0",
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",