UNPKG

@jovan158/vantage

Version:

See and control what your coding agent does

84 lines (83 loc) • 3.07 kB
// `vantage sessions prune`: remove sessions nobody looked at for a while. // // A session's age is the time since its log was last written. Kept whatever // their age: sessions still running, and isolated sessions whose worktree is // still there (their branch holds work not merged or discarded yet — removing // the session would leave it without `vantage discard`). import fs from "node:fs"; import path from "node:path"; import { sessionDir, sessionEventsPath } from "./events.js"; import { sessionRunning } from "./home.js"; import { readMeta } from "./session-meta.js"; const UNIT_MS = { h: 3_600_000, d: 86_400_000, w: 7 * 86_400_000 }; // "30d", "12h", "2w", or a plain number of days. export function parseAge(raw) { const m = /^(\d+(?:\.\d+)?)([hdw]?)$/i.exec((raw ?? "").trim()); if (!m) return null; const n = Number(m[1]); if (!(n > 0)) return null; return n * UNIT_MS[(m[2] || "d").toLowerCase()]; } function dirBytes(dir) { let total = 0; try { for (const entry of fs.readdirSync(dir, { withFileTypes: true })) { const p = path.join(dir, entry.name); total += entry.isDirectory() ? dirBytes(p) : fs.statSync(p).size; } } catch { /* gone or unreadable: counts as nothing */ } return total; } export function planPrune(refs, olderThanMs, nowMs = Date.now()) { const plan = { remove: [], kept: [] }; for (const ref of refs) { let lastActivityMs; try { lastActivityMs = fs.statSync(sessionEventsPath(ref.cwd, ref.sessionId)).mtimeMs; } catch { continue; } if (nowMs - lastActivityMs < olderThanMs) continue; if (sessionRunning(ref, nowMs)) { plan.kept.push({ ref, reason: "still running" }); continue; } const meta = readMeta(ref.cwd, ref.sessionId); if (meta?.isolated && meta.worktreePath && fs.existsSync(meta.worktreePath)) { const branch = meta.branch ? `${meta.branch} ` : ""; plan.kept.push({ ref, reason: `isolation branch ${branch}still there — merge it or \`vantage discard ${ref.sessionId}\`` }); continue; } plan.remove.push({ ref, lastActivityMs, bytes: dirBytes(sessionDir(ref.cwd, ref.sessionId)) }); } plan.remove.sort((a, z) => a.lastActivityMs - z.lastActivityMs); return plan; } // Removes the planned sessions' folders; returns how many went. export function applyPrune(plan) { let removed = 0; for (const item of plan.remove) { try { fs.rmSync(sessionDir(item.ref.cwd, item.ref.sessionId), { recursive: true, force: true }); removed++; } catch { /* in use (Windows) or no permission: left for next time */ } } return removed; } export function formatBytes(n) { if (n < 1024) return `${n} B`; if (n < 1024 * 1024) return `${(n / 1024).toFixed(1)} KB`; return `${(n / 1024 / 1024).toFixed(1)} MB`; }