@lotargo/memory_plugin 1.2.0 → 1.2.2
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/cli.js
CHANGED
|
@@ -1109,13 +1109,26 @@ export async function runCli() {
|
|
|
1109
1109
|
break;
|
|
1110
1110
|
}
|
|
1111
1111
|
|
|
1112
|
-
const docItems = docs.map((doc) =>
|
|
1113
|
-
|
|
1114
|
-
|
|
1115
|
-
|
|
1116
|
-
|
|
1117
|
-
|
|
1118
|
-
|
|
1112
|
+
const docItems = docs.map((doc) => {
|
|
1113
|
+
const rawDate = doc.created_at || doc.updated_at || "";
|
|
1114
|
+
let formattedDate = "";
|
|
1115
|
+
if (rawDate) {
|
|
1116
|
+
try {
|
|
1117
|
+
const d = typeof rawDate === "number" ? new Date(rawDate) : new Date(String(rawDate));
|
|
1118
|
+
formattedDate = isNaN(d.getTime()) ? String(rawDate).substring(0, 16) : d.toISOString().replace("T", " ").substring(0, 16);
|
|
1119
|
+
} catch (e) {
|
|
1120
|
+
formattedDate = String(rawDate).substring(0, 16);
|
|
1121
|
+
}
|
|
1122
|
+
}
|
|
1123
|
+
const docIdStr = doc.id != null ? String(doc.id) : "";
|
|
1124
|
+
return {
|
|
1125
|
+
label: doc.title || doc.path || "Untitled Document",
|
|
1126
|
+
badge: formattedDate,
|
|
1127
|
+
hint: docIdStr ? `ID: ${docIdStr.substring(0, 8)}...` : "",
|
|
1128
|
+
info: `Path: ${doc.path || "N/A"}`,
|
|
1129
|
+
value: doc,
|
|
1130
|
+
};
|
|
1131
|
+
});
|
|
1119
1132
|
docItems.push({ label: "< Back to Main Menu", value: "back" });
|
|
1120
1133
|
|
|
1121
1134
|
const docRes = await selectSimpleMenu({
|
|
@@ -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,56 @@ 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
|
+
resetExtractor();
|
|
152
|
+
try {
|
|
153
|
+
const fallbackOpts = {
|
|
154
|
+
quantized: true,
|
|
155
|
+
dtype: "q8",
|
|
156
|
+
device: "cpu",
|
|
157
|
+
session_options: { graphOptimizationLevel: "all", executionMode: "sequential" },
|
|
158
|
+
};
|
|
159
|
+
if (progressCallback) fallbackOpts.progress_callback = progressCallback;
|
|
160
|
+
|
|
161
|
+
extractorInstance = await pipeline("feature-extraction", "Xenova/multilingual-e5-small", fallbackOpts);
|
|
162
|
+
loadedModelName = "Xenova/multilingual-e5-small";
|
|
163
|
+
loadedDevice = "cpu";
|
|
164
|
+
} catch (err3) {
|
|
165
|
+
console.error(`[Fatal] Could not load default fallback model: ${err3.message}`);
|
|
166
|
+
throw err3;
|
|
167
|
+
}
|
|
108
168
|
} else {
|
|
109
169
|
throw err;
|
|
110
170
|
}
|
|
@@ -119,6 +179,38 @@ export function resetExtractor() {
|
|
|
119
179
|
loadedDevice = null;
|
|
120
180
|
}
|
|
121
181
|
|
|
182
|
+
export function deleteModelCache(modelName) {
|
|
183
|
+
const info = getModelStorageInfo(modelName);
|
|
184
|
+
if (info.status === "not_downloaded" || !fs.existsSync(info.dir)) {
|
|
185
|
+
return { deleted: false, reason: "Model directory not found" };
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
resetExtractor();
|
|
189
|
+
if (global.gc) {
|
|
190
|
+
try { global.gc(); } catch (e) {}
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
try {
|
|
194
|
+
fs.rmSync(info.dir, { recursive: true, force: true });
|
|
195
|
+
const parentDir = path.dirname(info.dir);
|
|
196
|
+
if (fs.existsSync(parentDir) && fs.readdirSync(parentDir).length === 0) {
|
|
197
|
+
fs.rmdirSync(parentDir);
|
|
198
|
+
}
|
|
199
|
+
return { deleted: true, modelName, freedMB: info.sizeMB };
|
|
200
|
+
} catch (err) {
|
|
201
|
+
try {
|
|
202
|
+
const corruptPath = `${info.dir}.corrupt_${Date.now()}`;
|
|
203
|
+
fs.renameSync(info.dir, corruptPath);
|
|
204
|
+
setTimeout(() => {
|
|
205
|
+
try { fs.rmSync(corruptPath, { recursive: true, force: true }); } catch (e) {}
|
|
206
|
+
}, 1000);
|
|
207
|
+
return { deleted: true, modelName, freedMB: info.sizeMB };
|
|
208
|
+
} catch (renameErr) {
|
|
209
|
+
return { deleted: false, reason: `${err.message} (Rename fallback: ${renameErr.message})` };
|
|
210
|
+
}
|
|
211
|
+
}
|
|
212
|
+
}
|
|
213
|
+
|
|
122
214
|
export function formatInputText(text, isQuery = false, modelName = null, instruction = null) {
|
|
123
215
|
if (!text) return "";
|
|
124
216
|
const targetModel = modelName || getConfig().embeddingModel || "Xenova/multilingual-e5-small";
|
|
@@ -296,6 +388,8 @@ export async function getReranker(modelName = "Xenova/bge-reranker-base", progre
|
|
|
296
388
|
return rerankerInstance;
|
|
297
389
|
}
|
|
298
390
|
|
|
391
|
+
checkAndSelfHealModel(modelName);
|
|
392
|
+
|
|
299
393
|
const { pipeline, env } = await import("@huggingface/transformers");
|
|
300
394
|
env.cacheDir = MODELS_DIR;
|
|
301
395
|
env.allowLocalModels = true;
|
|
@@ -338,7 +432,8 @@ export async function getReranker(modelName = "Xenova/bge-reranker-base", progre
|
|
|
338
432
|
rerankerInstance = await pipeline("text-classification", modelName, pipelineOpts);
|
|
339
433
|
loadedRerankerName = modelName;
|
|
340
434
|
} catch (err) {
|
|
341
|
-
console.warn(`Failed to load reranker model ${modelName}: ${err.message}
|
|
435
|
+
console.warn(`Failed to load reranker model ${modelName}: ${err.message}. Purging corrupt files...`);
|
|
436
|
+
deleteModelCache(modelName);
|
|
342
437
|
return null;
|
|
343
438
|
}
|
|
344
439
|
return rerankerInstance;
|
|
@@ -451,25 +546,6 @@ export function getModelStorageInfo(modelName) {
|
|
|
451
546
|
return { status: "partial", sizeMB, bytes: totalBytes, dir: modelDir };
|
|
452
547
|
}
|
|
453
548
|
|
|
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
549
|
export function listAllCachedModels() {
|
|
474
550
|
const result = [];
|
|
475
551
|
if (!fs.existsSync(MODELS_DIR)) return result;
|
|
@@ -8,7 +8,9 @@ const END_MARKER = "<!-- END MEMORY AGENT PROMPT -->";
|
|
|
8
8
|
|
|
9
9
|
export const PROMPT_BLOCK = `${START_MARKER}
|
|
10
10
|
[SYSTEM INSTRUCTION: PERSONAL CONTEXT & MEMORY AGENT]
|
|
11
|
-
|
|
11
|
+
1. MANDATORY FIRST STEP (SESSION INITIALIZATION): At the very start of every session or conversation, your VERY FIRST ACTION MUST BE to execute \`recall\` from \`memory-agent\` to load all saved facts, user preferences, personal context, and project guidelines BEFORE performing any other tasks or reading code.
|
|
12
|
+
2. PROACTIVE SAVING DIRECTIVE: You MUST automatically and proactively call \`remember\` from \`memory-agent\` whenever the user shares durable facts, personal preferences, coding guidelines, technology choices, or project architecture decisions. Do NOT wait for explicit user commands like "remember this".
|
|
13
|
+
3. SIGNAL FILTER: Save only high-signal facts (name, language, roles, constraints, tech stack preferences, architecture decisions, conventions). Translate facts into clear, concise English when saving. Do NOT save transient details or one-off conversation turns.
|
|
12
14
|
${END_MARKER}`;
|
|
13
15
|
|
|
14
16
|
export function getGlobalPromptTargets() {
|
package/opencode-plugin/index.js
CHANGED
|
@@ -74,12 +74,15 @@ async function notify(client, message, variant = "success") {
|
|
|
74
74
|
}
|
|
75
75
|
|
|
76
76
|
const MEMORY_INSTRUCTION =
|
|
77
|
+
"MANDATORY FIRST STEP (READ MEMORIES FIRST):\n" +
|
|
78
|
+
"At the start of every session, you MUST thoroughly review all saved memories provided below BEFORE performing any user task or editing code.\n" +
|
|
79
|
+
"PROACTIVE MEMORY DIRECTIVE:\n" +
|
|
80
|
+
"You MUST automatically and proactively call `remember` whenever the user shares durable facts, personal preferences, coding guidelines, tech stack choices, architecture decisions, or project conventions.\n" +
|
|
81
|
+
"Do NOT wait for explicit user commands like \"remember this\". Automatically capture high-signal facts in real time.\n" +
|
|
77
82
|
"Use `remember` only for important, durable facts about the user and project.\n" +
|
|
78
|
-
"Save high-signal
|
|
79
|
-
"
|
|
80
|
-
"
|
|
81
|
-
"or anything unlikely to be useful in future sessions.\n" +
|
|
82
|
-
"When saving, translate the fact into English and keep it concise.\n" +
|
|
83
|
+
"Save high-signal items: user role, goals, constraints, tech stack preferences, architecture decisions, project conventions.\n" +
|
|
84
|
+
"DO NOT save: transient details, one-off statements, full conversation turns, or anything unlikely to be useful in future sessions.\n" +
|
|
85
|
+
"When saving, translate the fact into clear, concise English.\n" +
|
|
83
86
|
"Use `scope: \"global\"` for personal facts, `scope: \"project\"` for project-specific facts.";
|
|
84
87
|
|
|
85
88
|
function buildMemoryContext(globalFacts, projectFacts, projectKey) {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@lotargo/memory_plugin",
|
|
3
|
-
"version": "1.2.
|
|
3
|
+
"version": "1.2.2",
|
|
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",
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: using-memory
|
|
3
|
-
description: Comprehensive guide for using the Memory & Hybrid RAG Knowledge Engine tools (remember, recall, forget, ingest_document, query_knowledge_base, manage_knowledge_base). Trigger whenever
|
|
3
|
+
description: Comprehensive guide for using the Memory & Hybrid RAG Knowledge Engine tools (remember, recall, forget, link_knowledge, ingest_document, query_knowledge_base, manage_knowledge_base). Trigger proactively whenever user preferences, project conventions, technology stack choices, or architecture decisions are introduced, or when querying ingested documentation, indexing files/repos, or managing persistent knowledge.
|
|
4
4
|
---
|
|
5
5
|
|
|
6
6
|
# Using Memory & Hybrid RAG Knowledge Engine
|
|
@@ -116,7 +116,8 @@ In such cases, use the **Full Raw Document Reading** mechanism:
|
|
|
116
116
|
|
|
117
117
|
## 4. Core Directives for AI Agents
|
|
118
118
|
|
|
119
|
-
1. **
|
|
120
|
-
2. **
|
|
121
|
-
3. **
|
|
122
|
-
4. **
|
|
119
|
+
1. **Read Memories First (MANDATORY)**: At the very start of any session or conversation, your VERY FIRST STEP MUST BE to execute `recall` to load all saved facts, user context, and project guidelines BEFORE performing any other task or code analysis.
|
|
120
|
+
2. **Be Proactive**: When the user mentions a durable preference, personal fact, or constraint, save it immediately using `remember`. Do not wait for explicit user commands.
|
|
121
|
+
3. **Check Knowledge Base First**: If a user asks how a specific module, API, or project architecture works, call `query_knowledge_base` using concept-dense search phrases.
|
|
122
|
+
4. **Inspect Ambiguous Docs Directly**: If querying produces low relevance scores on abstractly-named documents, call `manage_knowledge_base(action: "read_document")` to inspect the full text directly.
|
|
123
|
+
5. **Keep Memory Clean**: If a preference changes, call `forget` on the outdated entry before saving the new one.
|