/** * Local storage for the standalone harness (sessions, models, vision stills). * Override with AGENT_HARNESS_HOME. */ const path = require('path'); const fs = require('fs'); const os = require('os'); function getHome() { return ( process.env.AGENT_HARNESS_HOME || path.join(os.homedir(), '.agent-harness') ); } function getStorageRoot() { return getHome(); } function getQvacRoot() { return path.join(getStorageRoot(), 'qvac'); } function getAgentRoot() { return path.join(getStorageRoot(), 'agent'); } function ensureDir(dir) { try { fs.mkdirSync(dir, { recursive: true }); } catch (_) {} return dir; } function ensureQvacRoot() { return ensureDir(getQvacRoot()); } function ensureAgentRoot() { return ensureDir(getAgentRoot()); } function sanitizeId(id) { return String(id) .replace(/[^a-zA-Z0-9._-]/g, '_') .slice(0, 128); } function originHash(origin) { const s = String(origin || 'local'); let h = 5381; for (let i = 0; i < s.length; i++) h = ((h << 5) + h + s.charCodeAt(i)) | 0; return Math.abs(h).toString(16); } function getAgentOriginRoot(origin) { return path.join(ensureAgentRoot(), 'workspaces', sanitizeId(originHash(origin) || 'default')); } function isPathInside(root, candidate) { const absRoot = path.resolve(root); const abs = path.resolve(candidate); const rel = path.relative(absRoot, abs); return !(rel.startsWith('..') || path.isAbsolute(rel)); } function resolveUnderRoot(root, userPath) { if (!userPath || typeof userPath !== 'string') throw new Error('path is required'); const absRoot = path.resolve(ensureDir(root)); const resolved = path.isAbsolute(userPath) ? path.resolve(userPath) : path.resolve(absRoot, userPath.replace(/^\/+/, '')); const rel = path.relative(absRoot, resolved); if (rel.startsWith('..') || path.isAbsolute(rel)) { throw new Error('path escapes allowlisted root'); } return resolved; } function resolveUnderAnyRoot(roots, userPath, fallbackRoot) { if (!userPath || typeof userPath !== 'string') throw new Error('path is required'); const list = (roots || []).filter(Boolean).map((r) => path.resolve(r)); if (fallbackRoot) list.unshift(path.resolve(fallbackRoot)); if (!list.length) throw new Error('no allowlisted roots'); if (path.isAbsolute(userPath)) { const resolved = path.resolve(userPath); for (const root of list) { if (isPathInside(root, resolved)) return resolved; } throw new Error('path escapes allowlisted roots'); } return resolveUnderRoot(list[0], userPath); } module.exports = { getHome, getStorageRoot, getQvacRoot, getAgentRoot, getAgentOriginRoot, originHash, ensureDir, ensureQvacRoot, ensureAgentRoot, sanitizeId, isPathInside, resolveUnderRoot, resolveUnderAnyRoot, };