@lotargo/memory_plugin 1.2.9 → 1.2.902
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/index.js
CHANGED
|
@@ -32,6 +32,18 @@ const server = new McpServer({
|
|
|
32
32
|
version: "1.0.0",
|
|
33
33
|
});
|
|
34
34
|
|
|
35
|
+
// Optional string/number that tolerates null (some tool-call layers fill omitted
|
|
36
|
+
// optional args with null). Linking fields must NEVER be mandatory.
|
|
37
|
+
const optStr = () => z.string().optional().nullable();
|
|
38
|
+
const optNum = () => z.number().optional().nullable();
|
|
39
|
+
const defStr = (fallback) =>
|
|
40
|
+
z
|
|
41
|
+
.string()
|
|
42
|
+
.nullish()
|
|
43
|
+
.transform((v) => (v === null || v === undefined || v === "" ? fallback : v));
|
|
44
|
+
const defBool = (fallback) => z.boolean().nullish().transform((v) => (v === null || v === undefined ? fallback : v));
|
|
45
|
+
const defNum = (fallback) => z.number().nullish().transform((v) => (v === null || v === undefined ? fallback : v));
|
|
46
|
+
|
|
35
47
|
// --- Legacy Key-Value Memory Tools ---
|
|
36
48
|
|
|
37
49
|
// --- Legacy Key-Value Memory Tools & Agent Graph Linking ---
|
|
@@ -42,16 +54,17 @@ server.registerTool(
|
|
|
42
54
|
description:
|
|
43
55
|
"Save an important, durable fact to memory. Only use for high-signal information " +
|
|
44
56
|
"(name, goals, constraints, tech preferences, project conventions). " +
|
|
45
|
-
"
|
|
57
|
+
"docId/startLine/endLine/relationType are OPTIONAL and only used to link the fact to a " +
|
|
58
|
+
"Knowledge Base document or line range; omit them when no linking is needed. " +
|
|
46
59
|
"Translate the fact into English and keep it concise. " +
|
|
47
60
|
"scope: 'project' (default) or 'global'",
|
|
48
61
|
inputSchema: z.object({
|
|
49
62
|
fact: z.string().describe("The fact to remember, written in English"),
|
|
50
|
-
scope:
|
|
51
|
-
docId:
|
|
52
|
-
startLine:
|
|
53
|
-
endLine:
|
|
54
|
-
relationType:
|
|
63
|
+
scope: defStr("project").describe("'project' (default) or 'global'"),
|
|
64
|
+
docId: optStr().describe("Optional document ID, title, or path to link this fact to"),
|
|
65
|
+
startLine: optNum().describe("Optional starting line number in target document"),
|
|
66
|
+
endLine: optNum().describe("Optional ending line number in target document"),
|
|
67
|
+
relationType: defStr("LINKS_TO").describe("Relation type (e.g. 'RULES_FOR', 'IMPLEMENTS', 'REFERENCES')"),
|
|
55
68
|
}),
|
|
56
69
|
},
|
|
57
70
|
async ({ fact, scope, docId, startLine, endLine, relationType }) => {
|
|
@@ -97,8 +110,8 @@ server.registerTool(
|
|
|
97
110
|
"scope: 'project', 'global', 'all' (default), or 'list_projects'. " +
|
|
98
111
|
"Use project: '<directory path>' with scope 'project'/'all' to read facts of a specific project from any working directory.",
|
|
99
112
|
inputSchema: z.object({
|
|
100
|
-
scope:
|
|
101
|
-
project:
|
|
113
|
+
scope: defStr("all").describe("'project', 'global', 'all', or 'list_projects'"),
|
|
114
|
+
project: optStr().describe("Directory path of the project to read facts from (e.g. 'F:/projects/plugins/memory')"),
|
|
102
115
|
}),
|
|
103
116
|
},
|
|
104
117
|
async ({ scope, project }) => {
|
|
@@ -165,20 +178,30 @@ server.registerTool(
|
|
|
165
178
|
server.registerTool(
|
|
166
179
|
"forget",
|
|
167
180
|
{
|
|
168
|
-
description:
|
|
181
|
+
description:
|
|
182
|
+
"Delete a fact by number (from recall), by range (e.g. '3-30', inclusive), or by text search",
|
|
169
183
|
inputSchema: z.object({
|
|
170
|
-
query: z.string().describe("Number or text to search for"),
|
|
171
|
-
scope:
|
|
184
|
+
query: z.string().describe("Number, range like '3-30', or text to search for"),
|
|
185
|
+
scope: defStr("project").describe("'project' (default) or 'global'"),
|
|
172
186
|
}),
|
|
173
187
|
},
|
|
174
188
|
async ({ query, scope }) => {
|
|
175
189
|
const key = scopeKey(scope, null, null);
|
|
176
190
|
const entries = await readMemory(key);
|
|
191
|
+
const rangeMatch = /^\s*(\d+)\s*-\s*(\d+)\s*$/.exec(query);
|
|
177
192
|
const num = parseInt(query, 10);
|
|
178
193
|
let removed;
|
|
179
|
-
if (
|
|
194
|
+
if (rangeMatch) {
|
|
195
|
+
const from = parseInt(rangeMatch[1], 10);
|
|
196
|
+
const to = parseInt(rangeMatch[2], 10);
|
|
197
|
+
if (from > 0 && to >= from && to <= entries.length) {
|
|
198
|
+
removed = entries.splice(from - 1, to - from + 1);
|
|
199
|
+
}
|
|
200
|
+
}
|
|
201
|
+
if (!removed && !isNaN(num) && num > 0 && num <= entries.length) {
|
|
180
202
|
removed = entries.splice(num - 1, 1);
|
|
181
|
-
}
|
|
203
|
+
}
|
|
204
|
+
if (!removed) {
|
|
182
205
|
const filtered = entries.filter((e) => !e.toLowerCase().includes(query.toLowerCase()));
|
|
183
206
|
removed = entries.filter((e) => e.toLowerCase().includes(query.toLowerCase()));
|
|
184
207
|
entries.length = 0;
|
|
@@ -197,13 +220,13 @@ server.registerTool(
|
|
|
197
220
|
"Explicitly link a Notebook memory fact to a Knowledge Base document, section, or line range. " +
|
|
198
221
|
"Creates Agent-driven Graph Edges connecting memory to RAG documents.",
|
|
199
222
|
inputSchema: z.object({
|
|
200
|
-
action: z.enum(["link", "list_links", "get_doc_links"]).
|
|
201
|
-
factText:
|
|
202
|
-
docId:
|
|
203
|
-
scope:
|
|
204
|
-
startLine:
|
|
205
|
-
endLine:
|
|
206
|
-
relationType:
|
|
223
|
+
action: z.enum(["link", "list_links", "get_doc_links"]).nullish().transform((v) => v || "link").describe("Action type"),
|
|
224
|
+
factText: optStr().describe("Memory fact text or keyword"),
|
|
225
|
+
docId: optStr().describe("Document ID, title, or file path"),
|
|
226
|
+
scope: defStr("project").describe("'project' (default) or 'global'"),
|
|
227
|
+
startLine: optNum().describe("Starting line number in target document"),
|
|
228
|
+
endLine: optNum().describe("Ending line number in target document"),
|
|
229
|
+
relationType: defStr("LINKS_TO").describe("Relation type (e.g. 'RULES_FOR', 'IMPLEMENTS', 'EXPLAINS')"),
|
|
207
230
|
}),
|
|
208
231
|
},
|
|
209
232
|
async ({ action, factText, docId, scope, startLine, endLine, relationType }) => {
|
|
@@ -254,14 +277,15 @@ server.registerTool(
|
|
|
254
277
|
description:
|
|
255
278
|
"Ingest a document into the RAG knowledge base. " +
|
|
256
279
|
"Accepts local file paths, web URLs, or raw Markdown/text content. " +
|
|
280
|
+
"For type='url' the page is fetched and its content is indexed (not just the URL). " +
|
|
257
281
|
"Processes document through 3-tier hierarchy chunking (Big/Medium/Small), " +
|
|
258
282
|
"computes dense vectors, and extracts GraphRAG code symbols.",
|
|
259
283
|
inputSchema: z.object({
|
|
260
284
|
content: z.string().describe("Raw text content, file path, or web URL"),
|
|
261
|
-
type: z.enum(["text", "file", "url"]).
|
|
262
|
-
title:
|
|
263
|
-
path:
|
|
264
|
-
generateEmbeddings:
|
|
285
|
+
type: z.enum(["text", "file", "url"]).nullish().transform((v) => v || "text").describe("Input content type: 'text', 'file', or 'url' (url fetches the page content)"),
|
|
286
|
+
title: optStr().describe("Document title"),
|
|
287
|
+
path: optStr().describe("Original document file path"),
|
|
288
|
+
generateEmbeddings: defBool(true).describe("Compute dense vector embeddings"),
|
|
265
289
|
}),
|
|
266
290
|
},
|
|
267
291
|
async ({ content, type, title, path, generateEmbeddings }) => {
|
|
@@ -303,15 +327,12 @@ server.registerTool(
|
|
|
303
327
|
"Returns top-ranked candidate document sections with breadcrumbs, GraphRAG defined code symbols, and relevance scores.",
|
|
304
328
|
inputSchema: z.object({
|
|
305
329
|
query: z.string().describe("Search query in natural language or symbol name"),
|
|
306
|
-
limit:
|
|
307
|
-
instruction:
|
|
308
|
-
.
|
|
309
|
-
.
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
"Recommended when using E5/BGE models for domain-specific queries."
|
|
313
|
-
),
|
|
314
|
-
generateEmbeddings: z.boolean().default(true).describe("Use vector search alongside BM25"),
|
|
330
|
+
limit: defNum(5).describe("Maximum number of sections to return"),
|
|
331
|
+
instruction: optStr().describe(
|
|
332
|
+
"Optional task-specific retrieval instruction shaping embedding focus (e.g. 'Retrieve code snippets', 'Find user preferences'). " +
|
|
333
|
+
"Recommended when using E5/BGE models for domain-specific queries."
|
|
334
|
+
),
|
|
335
|
+
generateEmbeddings: defBool(true).describe("Use vector search alongside BM25"),
|
|
315
336
|
}),
|
|
316
337
|
},
|
|
317
338
|
async ({ query, limit, instruction, generateEmbeddings }) => {
|
|
@@ -364,8 +385,8 @@ server.registerTool(
|
|
|
364
385
|
"Manage the RAG knowledge base: inspect stats, list documents, read full raw document, delete documents, or export/import snapshots.",
|
|
365
386
|
inputSchema: z.object({
|
|
366
387
|
action: z.enum(["stats", "list", "read_document", "delete", "export_snapshot", "import_snapshot"]).describe("Management action"),
|
|
367
|
-
docId:
|
|
368
|
-
snapshotPath:
|
|
388
|
+
docId: optStr().describe("Document ID, title, or path (required for read_document and delete)"),
|
|
389
|
+
snapshotPath: optStr().describe("File path for snapshot export/import"),
|
|
369
390
|
}),
|
|
370
391
|
},
|
|
371
392
|
async ({ action, docId, snapshotPath }) => {
|
|
@@ -27,6 +27,48 @@ export function cleanHtml(html) {
|
|
|
27
27
|
return cleaned;
|
|
28
28
|
}
|
|
29
29
|
|
|
30
|
+
// Fetch a web page and convert it to Markdown/text. Used by the 'url' ingestion type
|
|
31
|
+
// so the RAG store gets the page CONTENT, not just the URL string.
|
|
32
|
+
export async function fetchUrlContent(url) {
|
|
33
|
+
if (typeof url !== "string" || !/^https?:\/\//i.test(url.trim())) {
|
|
34
|
+
throw new Error(`Unsupported URL for ingestion: '${url}'. Only http/https URLs are supported.`);
|
|
35
|
+
}
|
|
36
|
+
let res;
|
|
37
|
+
try {
|
|
38
|
+
res = await fetch(url.trim(), {
|
|
39
|
+
headers: {
|
|
40
|
+
"User-Agent": "memory-agent-rag/1.0",
|
|
41
|
+
Accept: "text/html,application/xhtml+xml,application/json,text/plain,*/*",
|
|
42
|
+
},
|
|
43
|
+
redirect: "follow",
|
|
44
|
+
signal: AbortSignal.timeout(15000),
|
|
45
|
+
});
|
|
46
|
+
} catch (err) {
|
|
47
|
+
throw new Error(`Failed to fetch URL '${url}': ${err.message}`);
|
|
48
|
+
}
|
|
49
|
+
if (!res.ok) {
|
|
50
|
+
throw new Error(`Failed to fetch URL '${url}': HTTP ${res.status} ${res.statusText}`);
|
|
51
|
+
}
|
|
52
|
+
const raw = await res.text();
|
|
53
|
+
const contentType = (res.headers.get("content-type") || "").toLowerCase();
|
|
54
|
+
const looksLikeHtml = /<html|<body|<div|<article|<main|<!doctype/i.test(raw.slice(0, 4096));
|
|
55
|
+
let markdown;
|
|
56
|
+
if (contentType.includes("html") || looksLikeHtml) {
|
|
57
|
+
markdown = cleanHtml(raw);
|
|
58
|
+
} else if (contentType.includes("json") || /^[\[{]/.test(raw.trim())) {
|
|
59
|
+
try {
|
|
60
|
+
markdown = JSON.stringify(JSON.parse(raw), null, 2);
|
|
61
|
+
} catch {
|
|
62
|
+
markdown = raw.trim();
|
|
63
|
+
}
|
|
64
|
+
} else {
|
|
65
|
+
markdown = raw.trim();
|
|
66
|
+
}
|
|
67
|
+
const titleMatch = raw.match(/<title[^>]*>([\s\S]*?)<\/title>/i);
|
|
68
|
+
const title = titleMatch ? titleMatch[1].replace(/\s+/g, " ").trim() : null;
|
|
69
|
+
return { markdown, title: title || null, finalUrl: res.url || url.trim() };
|
|
70
|
+
}
|
|
71
|
+
|
|
30
72
|
export function extractTitle(markdown, fallbackName = "Untitled Document") {
|
|
31
73
|
const h1Match = markdown.match(/^#\s+(.+)$/m);
|
|
32
74
|
if (h1Match && h1Match[1].trim()) {
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { randomUUID } from "node:crypto";
|
|
2
2
|
import { getDatabase, BLOBS_DIR } from "../db/database.js";
|
|
3
3
|
import { saveBlob, deleteBlob } from "../storage/blob_store.js";
|
|
4
|
-
import { normalizeContent } from "./normalizer.js";
|
|
4
|
+
import { normalizeContent, fetchUrlContent } from "./normalizer.js";
|
|
5
5
|
import { buildTripleHierarchy } from "./chunker.js";
|
|
6
6
|
import { embedText, embedBatch, vectorToBuffer } from "../ml/model_manager.js";
|
|
7
7
|
import { buildGraphEdges, saveGraphEdges } from "../graph/graph_extractor.js";
|
|
@@ -18,13 +18,26 @@ export async function ingestDocument({
|
|
|
18
18
|
}) {
|
|
19
19
|
const db = customDb || getDatabase();
|
|
20
20
|
|
|
21
|
-
|
|
21
|
+
let effectiveType = type;
|
|
22
|
+
let effectivePath = path;
|
|
23
|
+
let effectiveTitle = title;
|
|
24
|
+
|
|
25
|
+
if (type === "url") {
|
|
26
|
+
const fetched = await fetchUrlContent(String(content));
|
|
27
|
+
content = fetched.markdown;
|
|
28
|
+
effectiveType = "text";
|
|
29
|
+
effectiveTitle = title || fetched.title;
|
|
30
|
+
effectivePath = path || fetched.finalUrl || content;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
const { markdown, title: docTitle, metadata } = normalizeContent({ content, type: effectiveType, path: effectivePath, title: effectiveTitle });
|
|
34
|
+
if (type === "url") metadata.source_type = "url";
|
|
22
35
|
|
|
23
36
|
const blobRes = await saveBlob(markdown, customBlobDir);
|
|
24
37
|
const blobHash = blobRes.hash;
|
|
25
38
|
|
|
26
39
|
const docId = `doc_${randomUUID().replace(/-/g, "").substring(0, 12)}`;
|
|
27
|
-
const docPath =
|
|
40
|
+
const docPath = effectivePath || `virtual://${type}/${docId}`;
|
|
28
41
|
const now = Date.now();
|
|
29
42
|
|
|
30
43
|
const hierarchy = buildTripleHierarchy(markdown, docId, docTitle);
|
|
@@ -151,6 +164,14 @@ export async function deleteDocument(docIdOrPath, customDb = null, customBlobDir
|
|
|
151
164
|
|
|
152
165
|
const microChunks = db.prepare("SELECT id FROM micro_chunks WHERE doc_id = ?").all(doc.id);
|
|
153
166
|
|
|
167
|
+
// Collect every id owned by this document so we can purge dangling graph edges
|
|
168
|
+
// (graph_edges has no FK constraints, so section/chunk/doc references would otherwise leak).
|
|
169
|
+
const ownedIds = [doc.id];
|
|
170
|
+
for (const table of ["sections", "medium_chunks", "micro_chunks"]) {
|
|
171
|
+
const rows = db.prepare(`SELECT id FROM ${table} WHERE doc_id = ?`).all(doc.id);
|
|
172
|
+
for (const r of rows) ownedIds.push(r.id);
|
|
173
|
+
}
|
|
174
|
+
|
|
154
175
|
db.exec("BEGIN IMMEDIATE;");
|
|
155
176
|
try {
|
|
156
177
|
for (const mc of microChunks) {
|
|
@@ -159,7 +180,16 @@ export async function deleteDocument(docIdOrPath, customDb = null, customBlobDir
|
|
|
159
180
|
} catch {}
|
|
160
181
|
}
|
|
161
182
|
|
|
162
|
-
|
|
183
|
+
// Auto-clean Agent knowledge graph links pointing at this document.
|
|
184
|
+
db.prepare("DELETE FROM knowledge_links WHERE doc_id = ?").run(doc.id);
|
|
185
|
+
|
|
186
|
+
for (const id of ownedIds) {
|
|
187
|
+
// GLOB: '*' suffix is exact (unlike LIKE, '_' stays literal in ids like doc_xxx).
|
|
188
|
+
db.prepare(
|
|
189
|
+
"DELETE FROM graph_edges WHERE source_id = ? OR target_id = ? OR source_id GLOB ? OR target_id GLOB ?"
|
|
190
|
+
).run(id, id, `${id}*`, `${id}*`);
|
|
191
|
+
}
|
|
192
|
+
|
|
163
193
|
db.prepare("DELETE FROM documents WHERE id = ?").run(doc.id);
|
|
164
194
|
|
|
165
195
|
db.exec("COMMIT;");
|
|
@@ -175,5 +205,5 @@ export async function deleteDocument(docIdOrPath, customDb = null, customBlobDir
|
|
|
175
205
|
}
|
|
176
206
|
}
|
|
177
207
|
|
|
178
|
-
return { deleted: true, docId: doc.id, title: doc.title };
|
|
208
|
+
return { deleted: true, docId: doc.id, title: doc.title, linksCleaned: true };
|
|
179
209
|
}
|
package/opencode-plugin/index.js
CHANGED
|
@@ -240,7 +240,7 @@ const MCP_SERVERS = [
|
|
|
240
240
|
|
|
241
241
|
export const MemoryPlugin = async ({ directory, worktree, client }) => {
|
|
242
242
|
await ensureDir();
|
|
243
|
-
const
|
|
243
|
+
const activeProjectKey = scopeKey("project", worktree, directory);
|
|
244
244
|
|
|
245
245
|
return {
|
|
246
246
|
"experimental.chat.messages.transform": async (_input, output) => {
|
|
@@ -252,10 +252,10 @@ export const MemoryPlugin = async ({ directory, worktree, client }) => {
|
|
|
252
252
|
|
|
253
253
|
const [globalFacts, projectFacts] = await Promise.all([
|
|
254
254
|
readMemoryRaw(GLOBAL_KEY),
|
|
255
|
-
readMemoryRaw(
|
|
255
|
+
readMemoryRaw(activeProjectKey),
|
|
256
256
|
]);
|
|
257
257
|
|
|
258
|
-
const context = buildMemoryContext(globalFacts, projectFacts,
|
|
258
|
+
const context = buildMemoryContext(globalFacts, projectFacts, activeProjectKey);
|
|
259
259
|
const ref = firstUser.parts[0];
|
|
260
260
|
firstUser.parts.unshift({ ...ref, type: "text", text: context });
|
|
261
261
|
},
|
|
@@ -288,7 +288,8 @@ export const MemoryPlugin = async ({ directory, worktree, client }) => {
|
|
|
288
288
|
description:
|
|
289
289
|
"Save an important, durable fact to memory. Only use for high-signal information " +
|
|
290
290
|
"(name, goals, constraints, tech preferences, project conventions). " +
|
|
291
|
-
"
|
|
291
|
+
"docId/startLine/endLine/relationType are OPTIONAL and only used to link the fact to a " +
|
|
292
|
+
"Knowledge Base document or line range; omit them when no linking is needed. " +
|
|
292
293
|
"Translate the fact into English and keep it concise. " +
|
|
293
294
|
"scope: 'project' (default) or 'global'",
|
|
294
295
|
args: {
|
|
@@ -415,9 +416,9 @@ export const MemoryPlugin = async ({ directory, worktree, client }) => {
|
|
|
415
416
|
},
|
|
416
417
|
},
|
|
417
418
|
"forget": {
|
|
418
|
-
description: "Удалить факт по номеру (см. recall) или тексту",
|
|
419
|
+
description: "Удалить факт по номеру (см. recall), по диапазону (например '3-30', включительно) или тексту",
|
|
419
420
|
args: {
|
|
420
|
-
query: { type: "string", description: "Номер
|
|
421
|
+
query: { type: "string", description: "Номер факта, диапазон вида '3-30' или текст для поиска" },
|
|
421
422
|
scope: {
|
|
422
423
|
type: "string",
|
|
423
424
|
description: "project (по умолчанию) или global",
|
|
@@ -427,11 +428,20 @@ export const MemoryPlugin = async ({ directory, worktree, client }) => {
|
|
|
427
428
|
async execute({ query, scope }, { worktree, directory }) {
|
|
428
429
|
const key = scopeKey(scope || "project", worktree, directory);
|
|
429
430
|
const entries = await readMemory(key);
|
|
431
|
+
const rangeMatch = /^\s*(\d+)\s*-\s*(\d+)\s*$/.exec(query);
|
|
430
432
|
const num = parseInt(query, 10);
|
|
431
433
|
let removed;
|
|
432
|
-
if (
|
|
434
|
+
if (rangeMatch) {
|
|
435
|
+
const from = parseInt(rangeMatch[1], 10);
|
|
436
|
+
const to = parseInt(rangeMatch[2], 10);
|
|
437
|
+
if (from > 0 && to >= from && to <= entries.length) {
|
|
438
|
+
removed = entries.splice(from - 1, to - from + 1);
|
|
439
|
+
}
|
|
440
|
+
}
|
|
441
|
+
if (!removed && !isNaN(num) && num > 0 && num <= entries.length) {
|
|
433
442
|
removed = entries.splice(num - 1, 1);
|
|
434
|
-
}
|
|
443
|
+
}
|
|
444
|
+
if (!removed) {
|
|
435
445
|
const filtered = entries.filter((e) => !e.toLowerCase().includes(query.toLowerCase()));
|
|
436
446
|
removed = entries.filter((e) => e.toLowerCase().includes(query.toLowerCase()));
|
|
437
447
|
entries.length = 0;
|
|
@@ -502,11 +512,12 @@ export const MemoryPlugin = async ({ directory, worktree, client }) => {
|
|
|
502
512
|
description:
|
|
503
513
|
"Ingest a document into the RAG knowledge base. " +
|
|
504
514
|
"Accepts local file paths, web URLs, or raw Markdown/text content. " +
|
|
515
|
+
"For type='url' the page is fetched and its content is indexed (not just the URL). " +
|
|
505
516
|
"Processes document through 3-tier hierarchy chunking (Big/Medium/Small), " +
|
|
506
517
|
"computes dense vectors, and extracts GraphRAG code symbols.",
|
|
507
518
|
args: {
|
|
508
519
|
content: { type: "string", description: "Raw text content, file path, or web URL" },
|
|
509
|
-
type: { type: "string", description: "Input content type: 'text', 'file', 'url'", default: "text" },
|
|
520
|
+
type: { type: "string", description: "Input content type: 'text', 'file', 'url' (url fetches the page content)", default: "text" },
|
|
510
521
|
title: { type: "string", description: "Document title" },
|
|
511
522
|
path: { type: "string", description: "Original document file path" },
|
|
512
523
|
generateEmbeddings: { type: "boolean", description: "Compute dense vector embeddings", default: true },
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@lotargo/memory_plugin",
|
|
3
|
-
"version": "1.2.
|
|
3
|
+
"version": "1.2.902",
|
|
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",
|
|
@@ -59,11 +59,12 @@ Use this tool when adding technical documentation, API specs, architectural docu
|
|
|
59
59
|
- **Hierarchy Chunking**: The engine automatically creates 3-tier chunks (Big Document -> Medium Section -> Small Micro-Chunk) and extracts GraphRAG code symbols.
|
|
60
60
|
- **Auto Vector Embeddings**: Dense ONNX vectors (`multilingual-e5-small`) are automatically computed and indexed in SQLite.
|
|
61
61
|
- **CRITICAL Schema Usage & Parameters**:
|
|
62
|
-
- `content` (required, string):
|
|
63
|
-
- `type` (optional, enum: `"text"`, `"file"`, `"url"`):
|
|
64
|
-
- `path` (optional, string): Provide the absolute file path (e.g. `f:\projects\plugins\memory\README.md`).
|
|
65
|
-
- `title` (optional, string): Provide document title (e.g. `README.md`).
|
|
66
|
-
- **Correct Example**: `ingest_document(content: "
|
|
62
|
+
- `content` (required, string): For `type: "text"`/`"file"` it must be the **actual raw text or markdown content** of the document, NOT just a file path! For `type: "url"` it must be the **page URL** — the page is fetched automatically and its content is indexed (not just the URL).
|
|
63
|
+
- `type` (optional, enum: `"text"`, `"file"`, `"url"`): `"text"` (default), `"file"`, or `"url"` (fetches the web page and indexes its content).
|
|
64
|
+
- `path` (optional, string): Provide the absolute file path (e.g. `f:\projects\plugins\memory\README.md`). For URLs the final URL is used for deduplication.
|
|
65
|
+
- `title` (optional, string): Provide document title (e.g. `README.md`). If omitted for a URL, the page `<title>` is used.
|
|
66
|
+
- **Correct Example (URL)**: `ingest_document(content: "https://docs.example.com/guide", type: "url", title: "Example Guide")`
|
|
67
|
+
- **Correct Example (text)**: `ingest_document(content: "<full text content>", path: "f:/path/to/file.md", title: "file.md", type: "file")`
|
|
67
68
|
- ❌ **Common Error**: `ingest_document(content: "f:/path/to/file.md")` — this causes validation failures because `content` is missing the text content.
|
|
68
69
|
|
|
69
70
|
- **CLI/Script Execution Note**: When writing batch node scripts to call `ingestDocument`, remember that `@lotargo/memory_plugin` uses ES Modules (`"type": "module"`). Use `import` syntax instead of `require()`.
|