UNPKG

@sema-agent/core

Version:

Stateless, task-oriented AI agent core

91 lines (90 loc) • 6.5 kB
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; }