mirror of
https://github.com/paperclipai/paperclip.git
synced 2026-10-28 12:04:41 +01:00
Bind periodic recovery admission to lifecycle tracking. Keep one physical pass active through cleanup, report skipped ticks and long-held ownership, and preserve direct scheduled delivery. Full build, typecheck, 32 focused tests, complete CI, and exact-head Greptile5/5 pass. Co-Authored-By: Senior Engineer Fable <dotta@paperclip.ing> Co-Authored-By: Dotta <bippadotta@protonmail.com> Co-Authored-By: Devin Foley <devin@paperclip.ing> Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Co-Authored-By: Paperclip <noreply@paperclip.ing>
52 lines
1.3 KiB
TypeScript
52 lines
1.3 KiB
TypeScript
export interface SingleFlightSkip {
|
|
started: false;
|
|
inFlightForMs: number;
|
|
}
|
|
|
|
export interface SingleFlightStart {
|
|
started: true;
|
|
settled: Promise<void>;
|
|
}
|
|
|
|
export type SingleFlightResult = SingleFlightSkip | SingleFlightStart;
|
|
|
|
export function createSingleFlight(now: () => number = Date.now) {
|
|
let inFlight: Promise<void> | null = null;
|
|
let startedAtMs = 0;
|
|
|
|
return {
|
|
get busy(): boolean {
|
|
return inFlight !== null;
|
|
},
|
|
run(work: () => Promise<unknown>): SingleFlightResult {
|
|
if (inFlight) {
|
|
return { started: false, inFlightForMs: now() - startedAtMs };
|
|
}
|
|
startedAtMs = now();
|
|
const settled = Promise.resolve(work())
|
|
.then(
|
|
() => undefined,
|
|
() => undefined,
|
|
)
|
|
.finally(() => {
|
|
inFlight = null;
|
|
});
|
|
inFlight = settled;
|
|
return { started: true, settled };
|
|
},
|
|
};
|
|
}
|
|
|
|
export function createTrackedSingleFlight(options: {
|
|
trackWork: (work: Promise<void>) => void;
|
|
onSkipped: (inFlightForMs: number) => void;
|
|
now?: () => number;
|
|
}) {
|
|
const gate = createSingleFlight(options.now);
|
|
return (work: () => Promise<unknown>) => {
|
|
const tick = gate.run(work);
|
|
if (tick.started) options.trackWork(tick.settled);
|
|
else options.onSkipped(tick.inFlightForMs);
|
|
};
|
|
}
|