framework
Version:
The (AI) Framework: turnkey, zero-config AI orchestration that wraps a coding-agent CLI (Claude Code) as a black box and takes you from an idea to a running app. Vite for AI.
45 lines • 2.18 kB
JavaScript
import { join } from 'node:path';
import { FRAMEWORK_DIR, nodeStoreFs } from './run-store.js';
/** Where a project records the runs its daemon suspended (#923). Transient, like the run logs. */
export const SUSPENDED_FILE = 'suspended.json';
/**
* How long a suspended run stays resumable, ms (#923). A daemon restart within the day picks
* the work back up; a machine that has been off for a week does not wake up spending a day's
* quota on work whose repo has moved on. Older entries are dropped, not resumed.
*/
export const SUSPEND_MAX_AGE_MS = 24 * 60 * 60 * 1000;
function suspendedPath(cwd) {
return join(cwd, FRAMEWORK_DIR, SUSPENDED_FILE);
}
/** The runs this project's daemon suspended, or `[]` when there are none / the file is unreadable. */
export async function readSuspendedRuns(cwd, fs = nodeStoreFs()) {
try {
const parsed = JSON.parse(await fs.read(suspendedPath(cwd)));
if (!Array.isArray(parsed))
return [];
return parsed.filter((entry) => typeof entry === 'object' && entry !== null && typeof entry.runId === 'string');
}
catch {
// absent / unreadable / malformed -> nothing to resume
return [];
}
}
/** Record the runs a shutting-down daemon stopped. Replaces the list; an empty list clears it. */
export async function writeSuspendedRuns(cwd, runs, fs = nodeStoreFs()) {
await fs.mkdir(join(cwd, FRAMEWORK_DIR));
await fs.write(suspendedPath(cwd), JSON.stringify(runs, null, 2) + '\n');
}
/**
* The entries a booting daemon should pick back up: suspended recently enough to still be worth
* resuming (#923). Pure, so the cutoff is testable without a clock. An entry with an unparseable
* timestamp is dropped rather than resumed, since its age is exactly what the rule turns on; a
* timestamp in the future (a clock that moved) counts as recent, and the entry is consumed by
* that boot either way.
*/
export function resumableRuns(runs, now, maxAgeMs = SUSPEND_MAX_AGE_MS) {
return runs.filter(run => {
const at = Date.parse(run.suspendedAt);
return Number.isFinite(at) && now - at <= maxAgeMs;
});
}
//# sourceMappingURL=suspend.js.map