UNPKG

@sema-agent/core

Version:

Stateless, task-oriented AI agent core

59 lines (58 loc) • 2.4 kB
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(); }