import { existsSync, mkdirSync, readdirSync, readFileSync, renameSync, statSync, writeFileSync } from "node:fs"; import { resolve as resolvePath } from "node:path"; import { subagentsStateRoot } from "./agent-paths"; import type { SubagentRunDetails } from "./types"; /** * Compute the per-run artifacts directory under the agent home state/ folder. * Does not create the directory. */ export function artifactsDirFor(runId: string): string { return resolvePath(subagentsStateRoot(), runId); } /** * Ensure the artifacts directory for this run exists. */ export function ensureArtifactsDir(runId: string): string { const dir = artifactsDirFor(runId); mkdirSync(dir, { recursive: true }); return dir; } /** * Atomically write run.json to the given artifacts directory. * Writes to run.json.tmp then renames so a mid-write SIGKILL cannot corrupt * the canonical file. */ export function writeRunState(artifactsDir: string, details: SubagentRunDetails): void { mkdirSync(artifactsDir, { recursive: true }); const tmp = resolvePath(artifactsDir, "run.json.tmp"); const final = resolvePath(artifactsDir, "run.json"); writeFileSync(tmp, JSON.stringify(details, null, 2), "utf8"); renameSync(tmp, final); } /** * Read run.json. Throws on missing or invalid JSON. */ export function readRunState(artifactsDir: string): SubagentRunDetails { const file = resolvePath(artifactsDir, "run.json"); const raw = readFileSync(file, "utf8"); return JSON.parse(raw) as SubagentRunDetails; } /** * Best-effort read; returns undefined on any failure. */ export function tryReadRunState(artifactsDir: string): SubagentRunDetails | undefined { try { return readRunState(artifactsDir); } catch { return undefined; } } /** * Enumerate all run directories under the agent home state/subagents/. Returns * absolute paths sorted by directory mtime descending (newest first). Does not * validate directory contents. */ export function listRunDirs(): string[] { const root = subagentsStateRoot(); if (!existsSync(root)) return []; try { return readdirSync(root, { withFileTypes: true }) .filter((entry) => entry.isDirectory()) .map((entry) => resolvePath(root, entry.name)) .sort((a, b) => { try { return statSync(b).mtimeMs - statSync(a).mtimeMs; } catch { return 0; } }); } catch { return []; } }