UNPKG

@sema-agent/core

Version:

Stateless, task-oriented AI agent core

83 lines (82 loc) • 4.66 kB
import { join } from "node:path"; import { inlineUntrusted } from "../untrusted-text.js"; import { thrownText } from "../thrown-value.js"; import { CONSOLIDATION_WRITE_REFUSED_CODE, ConsolidationRefusedError } from "./consolidation.js"; export const CONSOLIDATION_WRITE_GATE_REQUIRED_CODE = "config.memory_consolidation_write_gate"; export function requireWriteGate(gate, verb) { if (typeof gate === "function") return; throw new ConsolidationRefusedError(CONSOLIDATION_WRITE_GATE_REQUIRED_CODE, `${verb} needs a write gate (opts.writeGate), got ${typeof gate}: it applies a plan's writes, each asked of the gate where it lands — an absent gate is refused, never read as "no rules apply"`); } async function askConsolidationWriteGate(gate, scope, file, homeOf) { let path; try { path = join(homeOf(), file); } catch (err) { throw new ConsolidationRefusedError(CONSOLIDATION_WRITE_REFUSED_CODE, `memory consolidation refused for scope ${inlineUntrusted(scope, 80)}: where this plan would write could not be derived (${inlineUntrusted(thrownText(err), 200)}), so whether a standing rule forbids it is unknown`); } try { await gate({ scope, path }); } catch (err) { if (err instanceof ConsolidationRefusedError) throw err; throw new ConsolidationRefusedError(CONSOLIDATION_WRITE_REFUSED_CODE, `memory consolidation refused for scope ${inlineUntrusted(scope, 80)}: the write gate failed (${inlineUntrusted(thrownText(err), 200)}), so whether a standing rule forbids writing ${inlineUntrusted(path, 200)} is unknown`); } } export async function directedLandings(directed, getByIds) { if (directed.length === 0) return []; const held = new Map((await getByIds(directed.map((d) => d.id))).map((e) => [e.id, e])); return directed.flatMap((d) => { const at = held.get(d.id); const from = at !== undefined ? [{ id: d.id, scope: at.scope, slug: at.slug }] : []; if (d.op === "delete" || d.entry === undefined) return from; const to = { id: d.id, scope: d.entry.scope, slug: d.entry.slug }; return [...from.filter((f) => f.scope !== to.scope || f.slug !== to.slug), to]; }); } export async function planWritten(plan, backend, committed) { try { await backend.applyPatches([]); const held = new Map((await committed.getByIds([...plan.products.map((p) => p.id), ...plan.directed.map((d) => d.id)])).map((e) => [e.id, e])); const products = plan.products.flatMap((p) => { const e = held.get(p.id); return e === undefined ? [] : [{ id: p.id, scope: e.scope, slug: e.slug }]; }); const standing = (id) => (held.get(id)?.frontmatter.deleted === true ? undefined : held.get(id)); const directed = plan.directed.filter((d) => (d.op === "delete" ? standing(d.id) === undefined : standing(d.id)?.rev === d.plannedPostRev)).map((d) => d.id); return { products, directed }; } catch { return undefined; } } export function refusalNamingWritten(err, written) { const reasonLines = err.reasons.length > 0 ? `\n${err.reasons.map((r) => ` - ${r}`).join("\n")}` : ""; const why = err.message.slice(0, err.message.length - reasonLines.length); const stays = [ ...(written !== undefined && written.products.length > 0 ? [`${written.products.length} of its products — ${written.products.map((l) => inlineUntrusted(`${l.id} (${l.scope}/${l.slug}.md)`, 200)).join(", ")}`] : []), ...(written !== undefined && written.directed.length > 0 ? [`${written.directed.length} of its directed changes, already in their planned state — ${written.directed.map((id) => inlineUntrusted(id, 80)).join(", ")}`] : []), ]; const outcome = written === undefined ? "nothing further of it was written, and whether any of it had been written already could not be read" : stays.length === 0 ? "nothing of it was written" : `nothing further of it was written; what had already landed stays: ${stays.join("; ")}`; return new ConsolidationRefusedError(err.code, `${why} — this plan is discarded and ${outcome}.`, err.reasons); } export function admitLandingsThrough(gate, homeOf) { const admitted = new Set(); return async (landings) => { for (const l of landings) { const key = `${l.scope}\u0000${l.slug}`; if (admitted.has(key)) continue; await askConsolidationWriteGate(gate, l.scope, `${l.slug}.md`, () => homeOf(l.scope)); admitted.add(key); } }; }