Files
PaperClipAI/server/src/services/work-folder-checkpointer.ts
T
Dotta a13bb6b395 Add sandbox work-folder persistence and shared lifecycle
Introduce company-scoped task, agent, user and project files, recoverable trash, object-backed repository checkpoints, and a shared 180-second lifecycle for sandbox execution. Wire file browsing and save status into the owner surfaces. This is an implementation checkpoint; staging acceptance and the complete merge gate remain outstanding.
2026-09-07 11:52:58 -05:00

39 lines
1.3 KiB
TypeScript

import { WORK_FOLDER_SYNC_INTERVAL_MS } from "@paperclipai/shared";
/** One scheduler for both execution generations. A tick never queues a backlog. */
export function startWorkFolderCheckpointer(input: {
checkpoint(): Promise<void>;
onError(error: unknown): Promise<void>;
}) {
let active: Promise<void> | null = null;
let stopped = false;
function checkpoint() {
const pending = (async () => {
try { await input.checkpoint(); }
catch (error) { await input.onError(error); throw error; }
})();
active = pending;
void pending.finally(() => { if (active === pending) active = null; }).catch(() => {});
return pending;
}
const timer = setInterval(() => {
if (!stopped && !active) void checkpoint().catch(() => {});
}, WORK_FOLDER_SYNC_INTERVAL_MS);
timer.unref();
// Explicit flushes serialize too. They must checkpoint once more after an
// in-flight tick, since the agent may have edited during that tick.
let flushTail: Promise<void> = Promise.resolve();
function flush() {
const pending = flushTail.catch(() => {}).then(async () => {
await active?.catch(() => {});
await checkpoint();
});
flushTail = pending;
return pending;
}
return {
flush,
async stop() { stopped = true; clearInterval(timer); await flush(); },
};
}