@sema-agent/core
Version:
Stateless, task-oriented AI agent core
91 lines (90 loc) • 6.5 kB
JavaScript
export function resolveFileHistoryRequest(spec, deps) {
const onTrackFailure = deps.onTrackFailure === undefined ? "refuse" : deps.onTrackFailure;
if (onTrackFailure !== "refuse" && onTrackFailure !== "proceed-unprotected") {
const e = new Error(`RunnerDeps.onTrackFailure must be "refuse" or "proceed-unprotected"; got ${JSON.stringify(deps.onTrackFailure)} — refusing prepare rather than silently defaulting a first-touch-failure policy.`);
e.code = "config.invalid_on_track_failure";
throw e;
}
const wiredSeat = resolveWiredFileHistoryStore(deps.fileHistoryStore);
const runDeclaredOff = resolveRunFileHistoryWord(spec.fileHistory);
const captureOff = resolveFileHistoryCaptureWord(deps.fileHistoryCapture, wiredSeat);
const declaredOff = wiredSeat === "disabled" || runDeclaredOff;
const fileHistoryStore = declaredOff ? undefined : wiredSeat;
const captureStore = captureOff ? undefined : fileHistoryStore;
const legacyRewindFiles = spec.rewindFiles;
if (legacyRewindFiles === true) {
if (spec.resumeAt !== undefined) {
const e = new Error(`rewind-files: TaskSpec.rewindFiles is retired (design/381 — rewind now converges the per-edited-file history, not a whole-tree snapshot). This request pairs it with resumeAt, i.e. the RESTORE sense: set restoreFiles: true instead. (The capture sense needs no request anymore — first-touch tracking is on whenever a RunnerDeps.fileHistoryStore is wired and neither RunnerDeps.fileHistoryCapture nor TaskSpec.fileHistory turns it off.)`);
e.code = "rewind.rewind_files_retired";
throw e;
}
}
if (spec.restoreFiles === true && spec.resumeAt === undefined) {
const e = new Error(`rewind-files: restoreFiles: true needs a resumeAt anchor — restoring files means converging them to a specific prior entry's boundary, and this spec names none. Use resumeAt + restoreFiles to move conversation and files together, or rewindFilesTo alone for a code-only restore.`);
e.code = "rewind.invalid_spec";
throw e;
}
if (spec.resumeAt !== undefined && spec.rewindFilesTo !== undefined) {
const e = new Error(`rewind-files: resumeAt ("${spec.resumeAt}") and rewindFilesTo ("${spec.rewindFilesTo}") were both set — resumeAt already anchors the file restore when restoreFiles is true, so a separate rewindFilesTo target is a conflicting request. Drop one of the two.`);
e.code = "rewind.conflicting_targets";
throw e;
}
const rewindTarget = spec.resumeAt !== undefined ? (spec.restoreFiles === true ? spec.resumeAt : undefined) : spec.rewindFilesTo;
const rewindBefore = rewindTarget !== undefined && spec.resumeAt !== undefined && spec.resumeAtMode === "before";
if (rewindTarget !== undefined && fileHistoryStore === undefined) {
if (runDeclaredOff)
throw captureDisabledRefusal(rewindTarget);
const e = new Error(wiredSeat === "disabled"
? `rewind-files: restoring files to entry "${rewindTarget}" requires a history backend, but this deployment declared file history disabled (RunnerDeps.fileHistoryStore: "disabled"), so there is no history to restore — the files were NOT rewound`
: `rewind-files: restoring files to entry "${rewindTarget}" requires a history backend, but this deployment wired no RunnerDeps.fileHistoryStore — no file history was ever recorded, so the files were NOT rewound`);
e.code = "rewind.store_unconfigured";
throw e;
}
return { onTrackFailure, wiredSeat, runDeclaredOff, captureOff, declaredOff, fileHistoryStore, captureStore, rewindTarget, rewindBefore };
}
function resolveWiredFileHistoryStore(candidate) {
if (candidate === undefined || candidate === "disabled")
return candidate;
const verbs = ["trackEdit", "annulTrack", "snapshot", "restore", "canRestore", "reap", "adoptScope"];
const verbOf = (v) => {
try {
return candidate[v];
}
catch {
return undefined;
}
};
const probeable = (typeof candidate === "object" && candidate !== null) || typeof candidate === "function";
const missing = probeable ? verbs.filter((v) => typeof verbOf(v) !== "function") : [...verbs];
if (missing.length > 0) {
const shape = candidate === null ? "null" : probeable ? "a value that is not a FileHistoryStore" : `a ${typeof candidate}`;
const e = new Error(`RunnerDeps.fileHistoryStore was wired with ${shape} — missing contract verbs: ${missing.join(", ")}. Refusing prepare rather than half-wiring the rewind seat (a present-but-unusable store makes every Edit/Write/NotebookEdit fail its first-touch backup while restore requests report "no store wired"). Write "disabled" (or omit the key) to run without file history.`);
e.code = "config.invalid_file_history_store";
throw e;
}
return candidate;
}
function resolveRunFileHistoryWord(word) {
if (word === undefined || word === "disabled")
return word === "disabled";
const e = new Error(`rewind-files: TaskSpec.fileHistory holds ${shownWord(word)} — the only value is "disabled"; omit the key to leave the deployment's setting in force`);
e.code = "rewind.invalid_spec";
throw e;
}
function resolveFileHistoryCaptureWord(word, wiredSeat) {
if (word === undefined || (word === "off" && wiredSeat !== undefined && wiredSeat !== "disabled"))
return word === "off";
const e = new Error(word === "off"
? `RunnerDeps.fileHistoryCapture: "off" requires a wired RunnerDeps.fileHistoryStore — nothing to switch off; to drop history entirely write fileHistoryStore: "disabled"`
: `RunnerDeps.fileHistoryCapture holds ${shownWord(word)} — the only value is "off"; omit the key to record whenever a fileHistoryStore is wired`);
e.code = "config.invalid_file_history_capture";
throw e;
}
function shownWord(word) {
return typeof word === "string" ? JSON.stringify(word.slice(0, 40)) : word === null || typeof word === "boolean" || typeof word === "number" ? String(word) : `a ${typeof word}`;
}
function captureDisabledRefusal(target) {
const e = new Error(`rewind-files: this run set TaskSpec.fileHistory: "disabled", so it neither records nor restores file history — restoring files to entry "${target}" was refused and the files were NOT rewound`);
e.code = "rewind.capture_disabled";
return e;
}