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; onError(error: unknown): Promise; }) { let active: Promise | 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 = 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(); }, }; }