/** * JSONL session store under BRIDGE_SWARM_STORAGE/agent/sessions/. */ const path = require('bare-path'); const fs = require('bare-fs'); const { ensureAgentRoot, ensureDir, sanitizeId } = require('../capabilities/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) { 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 = 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 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, makeId, };