@sema-agent/core
Version:
Stateless, task-oriented AI agent core
83 lines (82 loc) • 4.66 kB
JavaScript
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);
}
};
}