@sema-agent/core
Version:
Stateless, task-oriented AI agent core
59 lines (58 loc) • 2.4 kB
JavaScript
import { isSessionConflict } from "../session.js";
import { thrownText } from "../thrown-value.js";
import { settleTeardownLeg } from "./teardown-bounded.js";
export function detectConflicts(storage, ref, own) {
const guard = (fn, landed) => async (...args) => {
try {
const out = await fn(...args);
landed(args);
return out;
}
catch (err) {
if (isSessionConflict(err)) {
ref.hit = true;
}
throw err;
}
};
return new Proxy(storage, {
get(target, prop, receiver) {
const value = Reflect.get(target, prop, receiver);
if (typeof value !== "function") {
return value;
}
const bound = value.bind(target);
if (prop === "appendEntry")
return guard(bound, (args) => own.ids?.add(args[0].id));
if (prop === "setLeafId")
return guard(bound, () => undefined);
return bound;
},
});
}
export async function writeBranchBack(branch, sessionId, signal) {
let now;
try {
now = await branch.storage.getLeafId();
if (now === branch.leafBefore)
return;
const ids = branch.own.ids ?? new Set();
for (let at = now, steps = 0; at !== branch.leafSet; steps++) {
if (at === null || steps > ids.size || !ids.has(at))
throw new Error("another writer moved the leaf past the entries this request wrote");
at = (await branch.storage.getEntry(at))?.parentId ?? null;
}
if (signal?.aborted === true)
return;
await branch.storage.setLeafId(branch.leafBefore, { expectedLeafId: now });
}
catch (err) {
const shown = (id) => (id === undefined ? "(unreadable)" : id === null ? "(empty)" : `"${id}"`);
throw new Error(`resumeAt: prepare refused this request but could not put session "${sessionId}" back — its leaf stays at ${shown(now)} (the request found it at ${shown(branch.leafBefore)}), so the next turn continues from there: ${thrownText(err)}`);
}
}
export async function settleBranchWriteBack(branch, sessionId, label, report) {
const ac = new AbortController();
await settleTeardownLeg(() => writeBranchBack(branch, sessionId, ac.signal), label, report);
ac.abort();
}