mona-agent / apps /desktop /src /tools /memory.js
mo
feat(tools): persistent memory + web research tools for the agent
3046dfd
Raw History Blame Contribute Delete
2.52 kB
// Persistent memory — the agent remembers across tasks and restarts.
// Plain markdown files under ~/.mona-agent/memory/.
import fs from 'node:fs';
import path from 'node:path';
import os from 'node:os';
const MEM_DIR = process.env.MONA_MEMORY_DIR || path.join(os.homedir(), '.mona-agent', 'memory');
const MAX_NOTE = 4000;
function ensureDir() {
fs.mkdirSync(MEM_DIR, { recursive: true });
}
function todayFile() {
const d = new Date();
const ymd = `${d.getFullYear()}-${String(d.getMonth() + 1).padStart(2, '0')}-${String(d.getDate()).padStart(2, '0')}`;
return path.join(MEM_DIR, `${ymd}.md`);
}
function allFiles() {
ensureDir();
return fs.readdirSync(MEM_DIR).filter((f) => f.endsWith('.md')).map((f) => path.join(MEM_DIR, f));
}
export const memory = {
name: 'memory',
description: 'Persistent memory across tasks: remember facts, recall/search them, list recent notes.',
args: {
action: 'string — remember | recall | list',
note: 'string — text to remember (for remember)',
query: 'string — search terms (for recall)',
},
platform: 'any',
async run(args) {
const action = String(args.action || 'recall').toLowerCase();
if (action === 'remember') {
const note = String(args.note || '').trim().slice(0, MAX_NOTE);
if (!note) return { error: 'note required' };
ensureDir();
const stamp = new Date().toISOString();
fs.appendFileSync(todayFile(), `\n## ${stamp}\n${note}\n`);
return { ok: true, stored: todayFile(), chars: note.length };
}
if (action === 'list') {
ensureDir();
const files = allFiles().map((f) => ({
file: path.basename(f),
bytes: fs.statSync(f).size,
}));
return { files: files.reverse().slice(0, 14) };
}
// recall (default): substring search across memory files, newest first
const query = String(args.query || '').trim();
const files = allFiles().sort().reverse();
const hits = [];
for (const f of files.slice(0, 40)) {
let content;
try { content = fs.readFileSync(f, 'utf8'); } catch { continue; }
const lines = content.split('\n');
for (let i = 0; i < lines.length; i++) {
if (!query || lines[i].toLowerCase().includes(query.toLowerCase())) {
hits.push({ file: path.basename(f), line: i + 1, text: lines[i].slice(0, 300) });
if (hits.length >= 20) break;
}
}
if (hits.length >= 20) break;
}
return { query: query || '(all recent)', hits };
},
};