@tencentdb-agent-memory/memory-tencentdb
Version:
Four-layer local memory system plugin for OpenClaw — auto-captures, structures, and profiles conversational knowledge using local LLM + SQLite vector search (L0→L1→L2→L3 pipeline)
135 lines (134 loc) • 4.32 kB
JavaScript
import { t as StoragePaths } from "./types-ChShWHv_.mjs";
//#region src/core/scene/scene-format.ts
const META_START = "-----META-START-----";
const META_END = "-----META-END-----";
/**
* Parse a Scene Block file into structured data.
*/
function parseSceneBlock(raw, filename) {
const startIdx = raw.indexOf(META_START);
const endIdx = raw.indexOf(META_END);
if (startIdx === -1 || endIdx === -1) return {
filename,
meta: {
created: "",
updated: "",
summary: "",
heat: 0
},
content: raw.trim()
};
const metaBlock = raw.slice(startIdx + 20, endIdx).trim();
const content = raw.slice(endIdx + 18).trim();
return {
filename,
meta: {
created: extractMetaField(metaBlock, "created"),
updated: extractMetaField(metaBlock, "updated"),
summary: extractMetaField(metaBlock, "summary"),
heat: parseInt(extractMetaField(metaBlock, "heat"), 10) || 0
},
content
};
}
function extractMetaField(metaBlock, field) {
const re = new RegExp(`^${field}:\\s*(.*)$`, "m");
const m = metaBlock.match(re);
return m ? m[1].trim() : "";
}
//#endregion
//#region src/core/scene/scene-index.ts
/**
* Scene Index: maintains a JSON index of all scene blocks for quick lookup.
*/
async function fsReadFile(absPath) {
const fs = await import("node:fs/promises");
try {
return await fs.default.readFile(absPath, "utf-8");
} catch {
return null;
}
}
async function fsWriteFile(absPath, content) {
const fs = await import("node:fs/promises");
const path = await import("node:path");
await fs.default.mkdir(path.default.dirname(absPath), { recursive: true });
await fs.default.writeFile(absPath, content, "utf-8");
}
async function fsReaddir(absDir, suffix) {
const fs = await import("node:fs/promises");
try {
return (await fs.default.readdir(absDir)).filter((f) => f.endsWith(suffix));
} catch {
return [];
}
}
/**
* Read the scene index from disk.
*
* The index is written exclusively by syncSceneIndex() (engineering side).
* The LLM is sandboxed to scene_blocks/ and cannot access this file.
*/
async function readSceneIndex(dataDir, storage) {
try {
let raw;
if (storage) raw = await storage.readFile(StoragePaths.sceneIndex);
else raw = await fsReadFile((await import("node:path")).default.join(dataDir, ".metadata", "scene_index.json"));
if (!raw) return [];
const parsed = JSON.parse(raw);
if (!Array.isArray(parsed)) return [];
const entries = [];
for (const item of parsed) {
if (!item || typeof item !== "object") continue;
const filename = typeof item.filename === "string" ? item.filename : "";
if (!filename) continue;
entries.push({
filename,
summary: typeof item.summary === "string" ? item.summary : "",
heat: typeof item.heat === "number" ? item.heat : 0,
created: typeof item.created === "string" ? item.created : "",
updated: typeof item.updated === "string" ? item.updated : ""
});
}
return entries;
} catch {
return [];
}
}
/**
* Write the scene index to disk.
*/
async function writeSceneIndex(dataDir, entries, storage) {
const content = JSON.stringify(entries, null, 2);
if (storage) await storage.writeFile(StoragePaths.sceneIndex, content);
else await fsWriteFile((await import("node:path")).default.join(dataDir, ".metadata", "scene_index.json"), content);
}
/**
* Rebuild scene index by scanning all .md files in the scene_blocks directory.
*/
async function syncSceneIndex(dataDir, storage) {
let files;
if (storage) files = await storage.readdirNames(StoragePaths.sceneBlocksDir, ".md");
else files = await fsReaddir((await import("node:path")).default.join(dataDir, "scene_blocks"), ".md");
const entries = [];
for (const file of files) try {
let raw;
if (storage) raw = await storage.readFile(`${StoragePaths.sceneBlocksDir}${file}`);
else raw = await fsReadFile((await import("node:path")).default.join(dataDir, "scene_blocks", file));
if (!raw) continue;
const block = parseSceneBlock(raw, file);
entries.push({
filename: file,
summary: block.meta.summary,
heat: block.meta.heat,
created: block.meta.created,
updated: block.meta.updated
});
} catch {
continue;
}
await writeSceneIndex(dataDir, entries, storage);
return entries;
}
//#endregion
export { parseSceneBlock as i, syncSceneIndex as n, writeSceneIndex as r, readSceneIndex as t };