@sema-agent/core
Version:
Stateless, task-oriented AI agent core
189 lines (188 loc) • 7.55 kB
JavaScript
import { mkdirSync, promises as fs } from "node:fs";
import path from "node:path";
import { createSafeNotifier, observeThenableRejection } from "../core/safe-notify.js";
import { verifyPromptArtifact } from "./artifact.js";
export function refusalClause(refusal) {
return refusal.code === "unreadable" ? `unreadable (${refusal.reason})` : `refused by this engine (${refusal.code}: ${refusal.reason})`;
}
export function storedArtifactLine(artifactDigest, refusal, consequence) {
return `center prompt artifact ${artifactDigest.slice(0, 23)}… ${refusal !== undefined ? `is ${refusalClause(refusal)}` : "has no stored copy"} — ${consequence}`;
}
function tellRefused(onRefused, refusal) {
const notifier = createSafeNotifier();
notifier.notify(() => observeThenableRejection(onRefused?.(refusal), notifier, "artifact-store.onRefused"), "artifact-store.onRefused");
return undefined;
}
function verifiedRead(bytes, artifactDigest, onRefused) {
const refuse = (refusal) => tellRefused(onRefused, refusal);
let parsed;
try {
parsed = JSON.parse(bytes);
}
catch {
return refuse({ code: "shape", reason: "the stored bytes are not JSON" });
}
const v = verifyPromptArtifact(parsed);
if (!v.ok)
return refuse({ code: v.code, reason: v.reason });
return v.artifact.artifactDigest === artifactDigest ? v.artifact : refuse({ code: "digest", reason: "the stored artifact is not the one its digest names" });
}
export class MemoryPromptArtifactStore {
byDigest = new Map();
async get(artifactDigest, onRefused) {
const bytes = this.byDigest.get(artifactDigest);
return bytes === undefined ? undefined : verifiedRead(bytes, artifactDigest, onRefused);
}
async put(artifact) {
this.byDigest.set(artifact.artifactDigest, JSON.stringify(artifact));
}
}
export class FilePromptArtifactStore {
dir;
constructor(dir) {
this.dir = dir;
mkdirSync(path.join(dir, "artifacts"), { recursive: true });
}
fileFor(artifactDigest) {
const m = /^sha256:([0-9a-f]{64})$/.exec(artifactDigest);
return m ? path.join(this.dir, "artifacts", `${m[1]}.json`) : undefined;
}
async get(artifactDigest, onRefused) {
const file = this.fileFor(artifactDigest);
if (file === undefined)
return undefined;
let bytes;
try {
bytes = await fs.readFile(file, "utf8");
}
catch (err) {
const code = typeof err === "object" && err !== null ? err.code : undefined;
if (code === "ENOENT")
return undefined;
return tellRefused(onRefused, { code: "unreadable", reason: `the store could not read its file: ${typeof code === "string" ? code : "an error with no code"}` });
}
return verifiedRead(bytes, artifactDigest, onRefused);
}
async put(artifact) {
const file = this.fileFor(artifact.artifactDigest);
if (file === undefined)
throw new Error(`prompt-artifact store: malformed digest ${JSON.stringify(artifact.artifactDigest).slice(0, 120)}`);
const tmp = `${file}.tmp-${process.pid}-${Date.now()}`;
await fs.writeFile(tmp, JSON.stringify(artifact), "utf8");
await fs.rename(tmp, file);
}
}
export class MemoryPromptSourceStateStore {
state;
async load() {
return this.state;
}
async save(state) {
this.state = state;
}
}
export class FilePromptSourceStateStore {
file;
constructor(dir) {
mkdirSync(dir, { recursive: true });
this.file = path.join(dir, "source-state.json");
}
async load() {
let bytes;
try {
bytes = await fs.readFile(this.file, "utf8");
}
catch {
return undefined;
}
try {
const raw = JSON.parse(bytes);
if (raw?.state === "active" && typeof raw.artifactDigest === "string" && typeof raw.sourceRevision === "string") {
return { state: "active", artifactDigest: raw.artifactDigest, sourceRevision: raw.sourceRevision };
}
if (raw?.state === "disabled" && typeof raw.sourceRevision === "string") {
return { state: "disabled", sourceRevision: raw.sourceRevision };
}
}
catch {
}
return undefined;
}
async save(state) {
const tmp = `${this.file}.tmp-${process.pid}-${Date.now()}`;
await fs.writeFile(tmp, JSON.stringify(state), "utf8");
await fs.rename(tmp, this.file);
}
}
export class CenterPromptSource {
store;
stateStore;
onError;
notifier = createSafeNotifier();
candidate;
state = "unavailable";
tail = Promise.resolve();
exclusive(fn) {
const run = this.tail.then(fn, fn);
this.tail = run.then(() => undefined, () => undefined);
return run;
}
constructor(opts) {
this.store = opts.store;
this.stateStore = opts.stateStore ?? new MemoryPromptSourceStateStore();
this.onError = opts.onError;
}
current() {
return this.candidate;
}
async get(artifactDigest, onRefused) {
if (this.candidate?.artifactDigest === artifactDigest)
return this.candidate;
return this.store.get(artifactDigest, onRefused);
}
async restore() {
return this.exclusive(async () => {
const state = await this.stateStore.load();
if (state?.state === "disabled") {
this.candidate = undefined;
this.state = "disabled";
return;
}
if (state?.state !== "active") {
this.candidate = undefined;
this.state = "unavailable";
return;
}
const refused = {};
const stored = await this.store.get(state.artifactDigest, (r) => void (refused.current = r));
this.candidate = stored !== undefined ? { ...stored, sourceRevision: state.sourceRevision } : undefined;
this.state = stored !== undefined ? "active" : "unavailable";
if (stored === undefined) {
const line = new Error(storedArtifactLine(state.artifactDigest, refused.current, "the source boots unavailable: a new session has no center layer until a verified artifact is applied"));
this.notifier.notify(() => observeThenableRejection(this.onError?.(line, { phase: "prompt-constitution", sessionId: "", classification: "center-source-restore" }), this.notifier, "center-source.onError"), "center-source.onError");
}
});
}
sourceState() {
return this.state;
}
async applySnapshot(raw) {
const v = verifyPromptArtifact(raw);
if (!v.ok)
return v;
return this.exclusive(async () => {
await this.store.put(v.artifact);
await this.stateStore.save({ state: "active", artifactDigest: v.artifact.artifactDigest, sourceRevision: v.artifact.sourceRevision });
this.candidate = v.artifact;
this.state = "active";
return { ok: true, artifactDigest: v.artifact.artifactDigest };
});
}
async applyDisabled(sourceRevision) {
return this.exclusive(async () => {
await this.stateStore.save({ state: "disabled", sourceRevision });
this.candidate = undefined;
this.state = "disabled";
});
}
}