@phren/cli
Version:
Knowledge layer for AI agents - CLI, MCP server, and data layer
65 lines (64 loc) • 2.44 kB
JavaScript
import { pollStore } from "./pull.js";
/** One running pull and one pending wakeup per store; also respects sibling-process locks. */
export class ImmediatePulls {
pull;
intervalMs;
states = new Map();
stopped = false;
constructor(pull = store => pollStore(store, 0, undefined, Date.now(), true), intervalMs = 10_000) {
this.pull = pull;
this.intervalMs = intervalMs;
}
request(store) {
if (this.stopped)
return;
let state = this.states.get(store);
if (!state) {
state = { pending: false, running: false, last: -Infinity, retries: 0 };
this.states.set(store, state);
}
state.pending = true;
state.retries = 0;
if (state.running || state.timer)
return;
const run = async () => {
state.timer = undefined;
if (this.stopped)
return;
state.pending = false;
state.running = true;
state.last = Date.now();
try {
const result = await this.pull(store);
// A sibling process may own either coordination lock. Keep one wakeup until it releases.
if (result.status === "deferred" && (result.detail === "Coordinated pull is running."
|| result.detail === "Periodic pull deferred: another Phren Git operation is running."
|| result.detail === "Periodic pull deferred: a Git operation is in progress.")) {
if (state.retries < 2) {
state.retries++;
state.pending = true;
}
}
else
state.retries = 0;
}
catch { /* The next nudge or fallback poll retries failures. */ }
finally {
state.running = false;
if (state.pending && !this.stopped)
schedule();
}
};
const schedule = () => {
const delay = Math.max(0, state.last + this.intervalMs * Math.max(1, state.retries) - Date.now());
state.timer = setTimeout(() => { void run(); }, delay);
state.timer.unref();
};
schedule();
}
stop() {
this.stopped = true;
for (const state of this.states.values())
clearTimeout(state.timer);
}
}