| 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; |
| }, |
| }; |
|
|