@lotargo/memory_plugin 1.2.902 → 1.3.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/cli.js +64 -13
- package/mcp-server/config/config_manager.js +3 -3
- package/mcp-server/fact_format.js +177 -0
- package/mcp-server/index.js +218 -41
- package/mcp-server/ingest/pipeline.js +8 -0
- package/mcp-server/memory.js +203 -188
- package/mcp-server/ml/model_manager.js +2 -2
- package/opencode-plugin/index.js +200 -47
- package/package.json +2 -1
- package/skills/using-memory/SKILL.md +71 -11
package/mcp-server/memory.js
CHANGED
|
@@ -1,188 +1,203 @@
|
|
|
1
|
-
import { readFile, writeFile, mkdir, unlink, readdir } from "fs/promises";
|
|
2
|
-
import { existsSync } from "fs";
|
|
3
|
-
import { join, basename, resolve } from "path";
|
|
4
|
-
import { homedir } from "os";
|
|
5
|
-
|
|
6
|
-
function resolveMemoryDir() {
|
|
7
|
-
if (process.env.MEMORY_DIR) return process.env.MEMORY_DIR;
|
|
8
|
-
if (process.env.OPENCODE_CONFIG_DIR) return join(process.env.OPENCODE_CONFIG_DIR, "memory");
|
|
9
|
-
|
|
10
|
-
const legacyDir = join(homedir(), ".config", "opencode", "memory");
|
|
11
|
-
if (existsSync(legacyDir)) {
|
|
12
|
-
return legacyDir;
|
|
13
|
-
}
|
|
14
|
-
|
|
15
|
-
if (process.platform === "win32") {
|
|
16
|
-
const appData = process.env.LOCALAPPDATA || process.env.APPDATA || join(homedir(), "AppData", "Local");
|
|
17
|
-
return join(appData, "opencode", "memory");
|
|
18
|
-
}
|
|
19
|
-
|
|
20
|
-
const configHome = process.env.XDG_CONFIG_HOME || join(homedir(), ".config");
|
|
21
|
-
return join(configHome, "opencode", "memory");
|
|
22
|
-
}
|
|
23
|
-
|
|
24
|
-
export const MEMORY_DIR = resolveMemoryDir();
|
|
25
|
-
export const GLOBAL_KEY = "global";
|
|
26
|
-
|
|
27
|
-
export async function ensureDir() {
|
|
28
|
-
if (!existsSync(MEMORY_DIR)) await mkdir(MEMORY_DIR, { recursive: true });
|
|
29
|
-
const storageDir = join(MEMORY_DIR, "storage");
|
|
30
|
-
const blobsDir = join(storageDir, "blobs");
|
|
31
|
-
const modelsDir = join(storageDir, "models");
|
|
32
|
-
const exportsDir = join(MEMORY_DIR, "exports");
|
|
33
|
-
if (!existsSync(blobsDir)) await mkdir(blobsDir, { recursive: true });
|
|
34
|
-
if (!existsSync(modelsDir)) await mkdir(modelsDir, { recursive: true });
|
|
35
|
-
if (!existsSync(exportsDir)) await mkdir(exportsDir, { recursive: true });
|
|
36
|
-
}
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
}
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
const
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
}
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
}
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
return
|
|
181
|
-
}
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
const
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
}
|
|
1
|
+
import { readFile, writeFile, mkdir, unlink, readdir } from "fs/promises";
|
|
2
|
+
import { existsSync, mkdirSync } from "fs";
|
|
3
|
+
import { join, basename, resolve } from "path";
|
|
4
|
+
import { homedir } from "os";
|
|
5
|
+
|
|
6
|
+
function resolveMemoryDir() {
|
|
7
|
+
if (process.env.MEMORY_DIR) return process.env.MEMORY_DIR;
|
|
8
|
+
if (process.env.OPENCODE_CONFIG_DIR) return join(process.env.OPENCODE_CONFIG_DIR, "memory");
|
|
9
|
+
|
|
10
|
+
const legacyDir = join(homedir(), ".config", "opencode", "memory");
|
|
11
|
+
if (existsSync(legacyDir)) {
|
|
12
|
+
return legacyDir;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
if (process.platform === "win32") {
|
|
16
|
+
const appData = process.env.LOCALAPPDATA || process.env.APPDATA || join(homedir(), "AppData", "Local");
|
|
17
|
+
return join(appData, "opencode", "memory");
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
const configHome = process.env.XDG_CONFIG_HOME || join(homedir(), ".config");
|
|
21
|
+
return join(configHome, "opencode", "memory");
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
export const MEMORY_DIR = resolveMemoryDir();
|
|
25
|
+
export const GLOBAL_KEY = "global";
|
|
26
|
+
|
|
27
|
+
export async function ensureDir() {
|
|
28
|
+
if (!existsSync(MEMORY_DIR)) await mkdir(MEMORY_DIR, { recursive: true });
|
|
29
|
+
const storageDir = join(MEMORY_DIR, "storage");
|
|
30
|
+
const blobsDir = join(storageDir, "blobs");
|
|
31
|
+
const modelsDir = join(storageDir, "models");
|
|
32
|
+
const exportsDir = join(MEMORY_DIR, "exports");
|
|
33
|
+
if (!existsSync(blobsDir)) await mkdir(blobsDir, { recursive: true });
|
|
34
|
+
if (!existsSync(modelsDir)) await mkdir(modelsDir, { recursive: true });
|
|
35
|
+
if (!existsSync(exportsDir)) await mkdir(exportsDir, { recursive: true });
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
export function ensureDirSync() {
|
|
39
|
+
if (!existsSync(MEMORY_DIR)) mkdirSync(MEMORY_DIR, { recursive: true });
|
|
40
|
+
const storageDir = join(MEMORY_DIR, "storage");
|
|
41
|
+
const blobsDir = join(storageDir, "blobs");
|
|
42
|
+
const modelsDir = join(storageDir, "models");
|
|
43
|
+
const exportsDir = join(MEMORY_DIR, "exports");
|
|
44
|
+
if (!existsSync(blobsDir)) mkdirSync(blobsDir, { recursive: true });
|
|
45
|
+
if (!existsSync(modelsDir)) mkdirSync(modelsDir, { recursive: true });
|
|
46
|
+
if (!existsSync(exportsDir)) mkdirSync(exportsDir, { recursive: true });
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
// Canonical absolute path key: forward slashes, lowercase drive letter on win32.
|
|
50
|
+
export function canonicalPath(dir) {
|
|
51
|
+
let p = resolve(dir || process.cwd());
|
|
52
|
+
if (process.platform === "win32") {
|
|
53
|
+
p = p.replace(/\\/g, "/").replace(/^([a-zA-Z]):/, (_, d) => `${d.toLowerCase()}:`);
|
|
54
|
+
}
|
|
55
|
+
return p;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
// Project store key = full directory path. This removes basename collisions and
|
|
59
|
+
// binds each store to the real project location.
|
|
60
|
+
export function projectKey(worktree, directory) {
|
|
61
|
+
return canonicalPath(worktree || directory);
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
// Display label for a project (basename of the resolved directory).
|
|
65
|
+
export function projectName(worktree, directory) {
|
|
66
|
+
const dir = worktree || directory || process.cwd();
|
|
67
|
+
return dir ? basename(resolve(dir)) : "default";
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
export function scopeKey(scope, worktree, directory) {
|
|
71
|
+
return scope === "global" ? GLOBAL_KEY : projectKey(worktree, directory);
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
function slugify(key) {
|
|
75
|
+
return key.replace(/[^a-zA-Z0-9_-]/g, "_");
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
function memoryPath(key) {
|
|
79
|
+
return join(MEMORY_DIR, `${slugify(key)}.md`);
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
export function memoryFileName(key) {
|
|
83
|
+
return basename(memoryPath(key));
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
export function storeFilePath(key) {
|
|
87
|
+
return memoryPath(key);
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
function parseMeta(content) {
|
|
91
|
+
const m = content.match(/<!-- path: (.+?) -->/);
|
|
92
|
+
return { path: m ? m[1].trim() : null };
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
function isSimpleKey(key) {
|
|
96
|
+
return /^[a-zA-Z0-9_-]+$/.test(key);
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
// Lazy migration: when reading a project path store that doesn't exist yet but a
|
|
100
|
+
// legacy <basename>.md store (without path binding) does, claim it under the path.
|
|
101
|
+
async function maybeMigrateLegacy(key) {
|
|
102
|
+
if (key === GLOBAL_KEY || isSimpleKey(key)) return null;
|
|
103
|
+
const legacyBasename = basename(key);
|
|
104
|
+
if (!legacyBasename) return null;
|
|
105
|
+
const legacyFp = join(MEMORY_DIR, `${legacyBasename}.md`);
|
|
106
|
+
if (slugify(key) === legacyBasename || !existsSync(legacyFp)) return null;
|
|
107
|
+
const content = await readFile(legacyFp, "utf-8");
|
|
108
|
+
if (parseMeta(content).path) return null; // already bound to another project
|
|
109
|
+
// Collision guard: a different path with the same basename is already bound,
|
|
110
|
+
// so this legacy store is ambiguous and must not be silently claimed.
|
|
111
|
+
const files = await readdir(MEMORY_DIR).catch(() => []);
|
|
112
|
+
for (const f of files) {
|
|
113
|
+
if (!f.endsWith(".md") || f === `${legacyBasename}.md` || f === `${GLOBAL_KEY}.md`) continue;
|
|
114
|
+
try {
|
|
115
|
+
const other = parseMeta(await readFile(join(MEMORY_DIR, f), "utf-8")).path;
|
|
116
|
+
if (other && basename(other) === legacyBasename) return null;
|
|
117
|
+
} catch (e) {}
|
|
118
|
+
}
|
|
119
|
+
const facts = content.split("\n").filter((l) => l.startsWith("- ["));
|
|
120
|
+
await writeMemory(key, facts);
|
|
121
|
+
try {
|
|
122
|
+
await unlink(legacyFp);
|
|
123
|
+
} catch (e) {}
|
|
124
|
+
return facts;
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
export async function readMemory(key) {
|
|
128
|
+
const fp = memoryPath(key);
|
|
129
|
+
if (existsSync(fp)) {
|
|
130
|
+
const content = await readFile(fp, "utf-8");
|
|
131
|
+
return content.split("\n").filter((l) => l.startsWith("- ["));
|
|
132
|
+
}
|
|
133
|
+
const migrated = await maybeMigrateLegacy(key);
|
|
134
|
+
return migrated || [];
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
export async function readMemoryRaw(key) {
|
|
138
|
+
return (await readMemory(key)).map((e) => e.slice(2));
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
export async function writeMemory(key, entries) {
|
|
142
|
+
const lines = [];
|
|
143
|
+
if (key === GLOBAL_KEY) {
|
|
144
|
+
lines.push("# Global Memory", "");
|
|
145
|
+
} else {
|
|
146
|
+
lines.push(`# Memory: ${basename(key) || key}`, "");
|
|
147
|
+
if (!isSimpleKey(key)) {
|
|
148
|
+
lines.push(`<!-- path: ${key} -->`, "");
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
const content = lines.join("\n") + "\n" + (entries.length ? entries.join("\n") + "\n" : "");
|
|
152
|
+
await writeFile(memoryPath(key), content);
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
export async function listProjectStores() {
|
|
156
|
+
const stores = [];
|
|
157
|
+
const files = await readdir(MEMORY_DIR).catch(() => []);
|
|
158
|
+
for (const f of files) {
|
|
159
|
+
if (!f.endsWith(".md") || f === `${GLOBAL_KEY}.md`) continue;
|
|
160
|
+
const fp = join(MEMORY_DIR, f);
|
|
161
|
+
let content = "";
|
|
162
|
+
try {
|
|
163
|
+
content = await readFile(fp, "utf-8");
|
|
164
|
+
} catch (e) {
|
|
165
|
+
continue;
|
|
166
|
+
}
|
|
167
|
+
const facts = content.split("\n").filter((l) => l.startsWith("- ["));
|
|
168
|
+
const meta = parseMeta(content);
|
|
169
|
+
const key = meta.path || f.slice(0, -3);
|
|
170
|
+
stores.push({
|
|
171
|
+
key,
|
|
172
|
+
path: meta.path,
|
|
173
|
+
basename: basename(meta.path || key) || key,
|
|
174
|
+
file: f,
|
|
175
|
+
count: facts.length,
|
|
176
|
+
legacy: !meta.path,
|
|
177
|
+
});
|
|
178
|
+
}
|
|
179
|
+
stores.sort((a, b) => a.basename.localeCompare(b.basename));
|
|
180
|
+
return stores;
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
// Bind an unbound legacy store (e.g. "comfy-meta-viewer") to a directory path.
|
|
184
|
+
export async function migrateLegacyStore(legacyKey, targetDir) {
|
|
185
|
+
const legacyFp = join(MEMORY_DIR, `${legacyKey.replace(/[^a-zA-Z0-9_-]/g, "_")}.md`);
|
|
186
|
+
if (!existsSync(legacyFp)) return { ok: false, reason: "not_found", key: legacyKey };
|
|
187
|
+
const content = await readFile(legacyFp, "utf-8");
|
|
188
|
+
if (parseMeta(content).path) return { ok: false, reason: "already_bound", key: legacyKey };
|
|
189
|
+
const targetKey = projectKey(targetDir, null);
|
|
190
|
+
const facts = content.split("\n").filter((l) => l.startsWith("- ["));
|
|
191
|
+
await writeMemory(targetKey, facts);
|
|
192
|
+
try {
|
|
193
|
+
await unlink(legacyFp);
|
|
194
|
+
} catch (e) {}
|
|
195
|
+
return { ok: true, key: targetKey, file: memoryPath(targetKey), facts: facts.length };
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
export function today() {
|
|
199
|
+
const d = new Date();
|
|
200
|
+
const date = `${d.getFullYear()}-${String(d.getMonth() + 1).padStart(2, "0")}-${String(d.getDate()).padStart(2, "0")}`;
|
|
201
|
+
const time = `${String(d.getHours()).padStart(2, "0")}:${String(d.getMinutes()).padStart(2, "0")}`;
|
|
202
|
+
return `${date} ${time}`;
|
|
203
|
+
}
|
|
@@ -388,10 +388,10 @@ export async function getReranker(modelName = "Xenova/bge-reranker-base", progre
|
|
|
388
388
|
return rerankerInstance;
|
|
389
389
|
}
|
|
390
390
|
|
|
391
|
-
|
|
391
|
+
const cacheDir = ensureValidModelDirectory();
|
|
392
392
|
|
|
393
393
|
const { pipeline, env } = await import("@huggingface/transformers");
|
|
394
|
-
env.cacheDir =
|
|
394
|
+
env.cacheDir = cacheDir;
|
|
395
395
|
env.allowLocalModels = true;
|
|
396
396
|
env.allowRemoteModels = true;
|
|
397
397
|
env.remoteHost = "https://huggingface.co";
|