|
|
import fs from 'fs/promises';
|
|
|
import path from 'path';
|
|
|
import {fileURLToPath} from 'url';
|
|
|
|
|
|
const __dirname = path.dirname(fileURLToPath(import.meta.url));
|
|
|
|
|
|
export class MemoryManager {
|
|
|
constructor(memoryFileName = './memory.json') {
|
|
|
this.memoryFilePath = path.resolve(__dirname, memoryFileName);
|
|
|
}
|
|
|
|
|
|
async loadMemories() {
|
|
|
try {
|
|
|
const data = await fs.readFile(this.memoryFilePath, 'utf-8');
|
|
|
const json = JSON.parse(data);
|
|
|
|
|
|
|
|
|
if (!json.memories) {
|
|
|
const upgraded = {memories: [], conversationHistory: []};
|
|
|
|
|
|
if (Array.isArray(json.facts)) {
|
|
|
for (const f of json.facts) {
|
|
|
upgraded.memories.push({
|
|
|
type: 'fact',
|
|
|
key: this.extractKey(f.content),
|
|
|
value: this.extractValue(f.content),
|
|
|
source: 'migration',
|
|
|
timestamp: f.timestamp || new Date().toISOString()
|
|
|
});
|
|
|
}
|
|
|
}
|
|
|
|
|
|
if (json.preferences && typeof json.preferences === 'object') {
|
|
|
for (const [key, val] of Object.entries(json.preferences)) {
|
|
|
upgraded.memories.push({
|
|
|
type: 'preference',
|
|
|
key,
|
|
|
value: this.extractValue(val),
|
|
|
source: 'migration',
|
|
|
timestamp: new Date().toISOString()
|
|
|
});
|
|
|
}
|
|
|
}
|
|
|
|
|
|
await this.saveMemories(upgraded);
|
|
|
return upgraded;
|
|
|
}
|
|
|
|
|
|
if (!Array.isArray(json.memories)) json.memories = [];
|
|
|
if (!Array.isArray(json.conversationHistory)) json.conversationHistory = [];
|
|
|
|
|
|
return json;
|
|
|
} catch {
|
|
|
return {memories: [], conversationHistory: []};
|
|
|
}
|
|
|
}
|
|
|
|
|
|
async saveMemories(memories) {
|
|
|
await fs.writeFile(this.memoryFilePath, JSON.stringify(memories, null, 2));
|
|
|
}
|
|
|
|
|
|
|
|
|
async addMemory({type, key, value, source = 'user'}) {
|
|
|
const data = await this.loadMemories();
|
|
|
|
|
|
|
|
|
const normType = type.trim().toLowerCase();
|
|
|
const normKey = key.trim().toLowerCase();
|
|
|
const normValue = value.trim();
|
|
|
|
|
|
|
|
|
const existingIndex = data.memories.findIndex(
|
|
|
m => m.type === normType && m.key.toLowerCase() === normKey
|
|
|
);
|
|
|
|
|
|
if (existingIndex >= 0) {
|
|
|
const existing = data.memories[existingIndex];
|
|
|
|
|
|
if (existing.value !== normValue) {
|
|
|
existing.value = normValue;
|
|
|
existing.timestamp = new Date().toISOString();
|
|
|
existing.source = source;
|
|
|
console.log(`Updated memory: ${normKey} → ${normValue}`);
|
|
|
} else {
|
|
|
console.log(`Skipped duplicate memory: ${normKey}`);
|
|
|
}
|
|
|
} else {
|
|
|
|
|
|
data.memories.push({
|
|
|
type: normType,
|
|
|
key: normKey,
|
|
|
value: normValue,
|
|
|
source,
|
|
|
timestamp: new Date().toISOString()
|
|
|
});
|
|
|
console.log(`Added memory: ${normKey} = ${normValue}`);
|
|
|
}
|
|
|
|
|
|
await this.saveMemories(data);
|
|
|
}
|
|
|
|
|
|
async getMemorySummary() {
|
|
|
const data = await this.loadMemories();
|
|
|
const facts = Array.isArray(data.memories)
|
|
|
? data.memories.filter(m => m.type === 'fact')
|
|
|
: [];
|
|
|
const prefs = Array.isArray(data.memories)
|
|
|
? data.memories.filter(m => m.type === 'preference')
|
|
|
: [];
|
|
|
|
|
|
let summary = "\n=== LONG-TERM MEMORY ===\n";
|
|
|
|
|
|
if (facts.length > 0) {
|
|
|
summary += "\nKnown Facts:\n";
|
|
|
for (const f of facts) summary += `- ${f.key}: ${f.value}\n`;
|
|
|
}
|
|
|
|
|
|
if (prefs.length > 0) {
|
|
|
summary += "\nUser Preferences:\n";
|
|
|
for (const p of prefs) summary += `- ${p.key}: ${p.value}\n`;
|
|
|
}
|
|
|
|
|
|
return summary;
|
|
|
}
|
|
|
|
|
|
extractKey(content) {
|
|
|
if (typeof content !== 'string') return 'unknown';
|
|
|
const [key] = content.split(':').map(s => s.trim());
|
|
|
return key || 'unknown';
|
|
|
}
|
|
|
|
|
|
extractValue(content) {
|
|
|
if (typeof content !== 'string') return '';
|
|
|
const parts = content.split(':').map(s => s.trim());
|
|
|
return parts.length > 1 ? parts.slice(1).join(':') : content;
|
|
|
}
|
|
|
} |