import crypto from 'crypto'; import path from 'path'; import { loadEncryptedJson, saveEncryptedJson } from './cryptoUtils.js'; const DATA_ROOT = '/data/memories'; const INDEX_FILE = path.join(DATA_ROOT, 'index.json'); const MAX_MEMORY_LENGTH = 220; const state = { loaded: false, index: { memories: {}, }, }; function ensureOwner(owner) { if (!owner?.type || !owner?.id) throw new Error('Invalid memory owner'); return owner; } function nowIso() { return new Date().toISOString(); } function sanitizeText(text) { return String(text || '').replace(/\s+/g, ' ').trim().slice(0, MAX_MEMORY_LENGTH); } async function ensureLoaded() { if (state.loaded) return; const stored = await loadEncryptedJson(INDEX_FILE); state.index = { memories: stored?.memories || {}, }; state.loaded = true; } async function saveIndex() { await saveEncryptedJson(INDEX_FILE, state.index); } function matchesOwner(memory, owner) { return memory.ownerType === owner.type && memory.ownerId === owner.id; } function sanitize(memory) { return { id: memory.id, content: memory.content, source: memory.source || 'assistant', sessionId: memory.sessionId || null, createdAt: memory.createdAt, updatedAt: memory.updatedAt, }; } export const memoryStore = { async list(owner) { ensureOwner(owner); await ensureLoaded(); return Object.values(state.index.memories) .filter((memory) => matchesOwner(memory, owner)) .sort((a, b) => new Date(b.updatedAt).getTime() - new Date(a.updatedAt).getTime()) .map(sanitize); }, async create(owner, { content, sessionId = null, source = 'assistant' }) { ensureOwner(owner); await ensureLoaded(); const normalized = sanitizeText(content); if (!normalized) return null; const memory = { id: crypto.randomUUID(), ownerType: owner.type, ownerId: owner.id, content: normalized, sessionId, source, createdAt: nowIso(), updatedAt: nowIso(), }; state.index.memories[memory.id] = memory; await saveIndex(); return sanitize(memory); }, async update(owner, id, content) { ensureOwner(owner); await ensureLoaded(); const memory = state.index.memories[id]; if (!memory || !matchesOwner(memory, owner)) return null; const normalized = sanitizeText(content); if (!normalized) return null; memory.content = normalized; memory.updatedAt = nowIso(); await saveIndex(); return sanitize(memory); }, async delete(owner, id) { ensureOwner(owner); await ensureLoaded(); const memory = state.index.memories[id]; if (!memory || !matchesOwner(memory, owner)) return false; delete state.index.memories[id]; await saveIndex(); return true; }, };