Files
gnome-jarvis/vendor/agent-harness/agent/sessions.js
T
2026-09-11 13:41:22 -04:00

200 lines
4.7 KiB
JavaScript

/**
* JSONL session store under $AGENT_HARNESS_HOME/agent/sessions/.
*/
const path = require('path');
const fs = require('fs');
const { ensureAgentRoot, ensureDir, sanitizeId } = require('../lib/paths.js');
function sessionsRoot() {
return ensureDir(path.join(ensureAgentRoot(), 'sessions'));
}
function sessionDir(id) {
return ensureDir(path.join(sessionsRoot(), sanitizeId(id)));
}
function readJsonl(file) {
try {
const raw = fs.readFileSync(file, 'utf8');
return raw
.split('\n')
.map((l) => l.trim())
.filter(Boolean)
.map((l) => {
try {
return JSON.parse(l);
} catch (_) {
return null;
}
})
.filter(Boolean);
} catch (_) {
return [];
}
}
function appendJsonl(file, obj) {
fs.appendFileSync(file, JSON.stringify(obj) + '\n');
}
function makeId() {
return 'sess_' + Date.now().toString(36) + '_' + Math.random().toString(36).slice(2, 10);
}
function create(meta) {
const id = meta.sessionId || makeId();
const dir = sessionDir(id);
const summary = {
id,
origin: meta.origin || '',
cwd: meta.cwd || '',
model: meta.model || '',
title: meta.title || 'New session',
hostWorkspace: meta.hostWorkspace !== false,
workspace: meta.workspace || meta.cwd || '',
builtinTools: meta.builtinTools,
createdAt: Date.now(),
updatedAt: Date.now(),
plan: [],
planMode: meta.planMode || { state: 'inactive', planPath: 'plan.md', reminderCount: 0 },
goal: meta.goal || null,
};
fs.writeFileSync(path.join(dir, 'summary.json'), JSON.stringify(summary, null, 2));
fs.writeFileSync(path.join(dir, 'chat_history.jsonl'), '');
return summary;
}
function load(id, opts) {
const dir = sessionDir(id);
let summary;
try {
summary = JSON.parse(fs.readFileSync(path.join(dir, 'summary.json'), 'utf8'));
} catch (_) {
throw new Error('session not found: ' + id);
}
summary.history = readJsonl(path.join(dir, 'chat_history.jsonl'));
summary.updates = opts && opts.updates ? readJsonl(path.join(dir, 'updates.jsonl')) : [];
return summary;
}
function saveSummary(summary) {
summary.updatedAt = Date.now();
const copy = Object.assign({}, summary);
delete copy.history;
delete copy.updates;
fs.writeFileSync(path.join(sessionDir(summary.id), 'summary.json'), JSON.stringify(copy, null, 2));
}
function appendHistory(id, msg) {
appendJsonl(path.join(sessionDir(id), 'chat_history.jsonl'), Object.assign({ ts: Date.now() }, msg));
}
function appendUpdate(id, update) {
appendJsonl(path.join(sessionDir(id), 'updates.jsonl'), Object.assign({ ts: Date.now() }, update));
}
function replaceHistory(id, history) {
const file = path.join(sessionDir(id), 'chat_history.jsonl');
const body = (history || []).map((m) => JSON.stringify(m)).join('\n');
fs.writeFileSync(file, body ? body + '\n' : '');
}
function planFile(id) {
return path.join(sessionDir(id), 'plan.md');
}
function readPlan(id) {
try {
return fs.readFileSync(planFile(id), 'utf8');
} catch (_) {
return '';
}
}
function writePlan(id, text) {
const file = planFile(id);
fs.writeFileSync(file, String(text != null ? text : ''));
return file;
}
function rmDeep(dir) {
let ents = [];
try {
ents = fs.readdirSync(dir, { withFileTypes: true });
} catch (_) {
return;
}
for (const ent of ents) {
const child = path.join(dir, ent.name);
const isDir = typeof ent.isDirectory === 'function' ? ent.isDirectory() : false;
if (isDir) rmDeep(child);
else {
try {
fs.unlinkSync(child);
} catch (_) {}
}
}
try {
fs.rmdirSync(dir);
} catch (_) {}
}
function remove(id) {
if (!id) return false;
const dir = path.join(sessionsRoot(), sanitizeId(id));
try {
if (typeof fs.rmSync === 'function') fs.rmSync(dir, { recursive: true, force: true });
else rmDeep(dir);
return true;
} catch (_) {
return false;
}
}
function exists(id) {
if (!id) return false;
try {
fs.statSync(path.join(sessionsRoot(), sanitizeId(id), 'summary.json'));
return true;
} catch (_) {
return false;
}
}
function list() {
const root = sessionsRoot();
let names = [];
try {
names = fs.readdirSync(root);
} catch (_) {
return [];
}
const out = [];
for (const name of names) {
try {
const summary = JSON.parse(fs.readFileSync(path.join(root, name, 'summary.json'), 'utf8'));
out.push(summary);
} catch (_) {}
}
out.sort((a, b) => (b.updatedAt || 0) - (a.updatedAt || 0));
return out;
}
module.exports = {
create,
load,
saveSummary,
appendHistory,
appendUpdate,
replaceHistory,
sessionDir,
planFile,
readPlan,
writePlan,
list,
remove,
exists,
makeId,
};