@sema-agent/core
Version:
Stateless, task-oriented AI agent core
140 lines (139 loc) • 6.73 kB
JavaScript
import { join } from "node:path";
import { guardedMemoryStore } from "../../core/memory.js";
import { TtlSessionStore } from "../../core/session-store.js";
import { BootLock, createFileConsolidationLock, resolveDataRoot } from "./fs-atomic.js";
import { assertAdoptionBootGate } from "./adoption/marker.js";
import { FileCheckpointStore } from "./checkpoint-store.js";
import { FileMemoryStore } from "./memory-store.js";
import { FileSessionRepo } from "./session-store.js";
import { FileToolResultStore } from "./tool-result-store.js";
import { FileSessionPolicyStore } from "./session-policy-store.js";
import { FileWorkflowJournalStore } from "./workflow-journal-store.js";
import { FileUsageWindowStore } from "./usage-window-store.js";
import { FileBackgroundAgentStore } from "./background-agent-store.js";
import { FileMailboxStore } from "./mailbox-store.js";
import { FileRosterStore } from "../../agents/roster-store.js";
import { FileFileHistoryStore } from "./file-history-store.js";
export { FileCheckpointStore } from "./checkpoint-store.js";
export { FileMemoryStore } from "./memory-store.js";
export { FileSessionRepo } from "./session-store.js";
export { FileToolResultStore } from "./tool-result-store.js";
export { FileSessionPolicyStore } from "./session-policy-store.js";
export { FileFileHistoryStore } from "./file-history-store.js";
export { FileWorkflowJournalStore, MAX_JOURNAL_RESULT_BYTES, oversizeJournalResult } from "./workflow-journal-store.js";
export { FileUsageWindowStore } from "./usage-window-store.js";
export { resolveDataRoot, sanitizeScope, sanitizePathComponent, createFileConsolidationLock } from "./fs-atomic.js";
export { FileStoreLockError } from "./fs-atomic.js";
export { atomicWriteFile, writeThenLink, ensureDir, readJsonlRecords, AppendLog } from "./fs-atomic.js";
export { processFingerprint, isProcessLive } from "./fs-atomic.js";
export { AdoptionError, assertAdoptionBootGate, readRootAdoptionFile, writeRootAdoptionFile, ROOT_ADOPTION_FILE, } from "./adoption/marker.js";
export { adoptLocalDataRoot, ackAdoptionConfig, witnessAdoptionConfig, listAdoptionQuarantine, readAdoptionStatus, } from "./adoption/adopt.js";
export class FileStorageBackend {
root;
sessionStore;
checkpointStore;
memoryStore;
toolResultStore;
sessionPolicyStore;
workflowJournalStore;
usageWindowStore;
backgroundAgentStore;
mailboxStore;
rosterStore;
fileHistoryStore;
consolidationLock;
lock;
fileAgentRows;
fileMailbox;
fileCheckpoints;
fileMemory;
ttl;
fileSessions;
fileWorkflowJournal;
constructor(opts = {}) {
this.root = resolveDataRoot(opts.root);
assertAdoptionBootGate(this.root, "FileStorageBackend");
this.lock = new BootLock(join(this.root, "LOCK"));
this.lock.acquire();
const built = [];
try {
const corruptRead = opts.onCorruptRead !== undefined ? { onCorruptRead: opts.onCorruptRead } : undefined;
const repo = new FileSessionRepo(this.root, corruptRead);
this.fileSessions = repo;
this.ttl = new TtlSessionStore({ repo, evict: opts.evict ?? "forget", durability: "durable", placements: { subagent: { durability: "durable" } } });
this.sessionStore = this.ttl;
this.fileCheckpoints = new FileCheckpointStore(this.root, { ...opts.checkpoint, ...corruptRead });
built.push(() => this.fileCheckpoints.close());
this.checkpointStore = this.fileCheckpoints;
this.fileMemory = new FileMemoryStore(this.root, opts.embedder ? { embedder: opts.embedder } : {});
built.push(() => this.fileMemory.close());
this.memoryStore = guardedMemoryStore(this.fileMemory, opts.utilityGate);
this.toolResultStore = new FileToolResultStore(this.root, corruptRead);
this.sessionPolicyStore = new FileSessionPolicyStore(this.root, { ...opts.sessionPolicy, ...corruptRead });
this.fileWorkflowJournal = new FileWorkflowJournalStore(this.root, true, corruptRead);
built.push(() => this.fileWorkflowJournal.dispose());
this.workflowJournalStore = this.fileWorkflowJournal;
this.usageWindowStore = new FileUsageWindowStore(this.root, corruptRead);
this.fileAgentRows = new FileBackgroundAgentStore(this.root, corruptRead ?? {});
built.push(() => this.fileAgentRows.close());
this.backgroundAgentStore = this.fileAgentRows;
this.fileMailbox = new FileMailboxStore(this.root, corruptRead ?? {});
built.push(() => this.fileMailbox.close());
this.mailboxStore = this.fileMailbox;
this.rosterStore = new FileRosterStore(join(this.root, "roster.json"), corruptRead ?? {});
this.fileHistoryStore = new FileFileHistoryStore(this.root, { ...(opts.fileHistory ?? {}), ...(corruptRead ?? {}) });
this.consolidationLock = createFileConsolidationLock(join(this.root, "consolidation-locks"));
}
catch (err) {
for (const undo of built.reverse()) {
try {
undo();
}
catch {
}
}
if (!this.lock.release())
this.discloseRetainedBootLock();
throw err;
}
}
discloseRetainedBootLock() {
console.warn(`[sema] file store: this backend could not hand back its boot lock at ${join(this.root, "LOCK")} — the ` +
`removal could not be proven, so a lock file naming this live process (pid ${process.pid}) may still be ` +
`there. While it is, another process opening this data root is refused by name; a later open of this root ` +
`in THIS process finishes the cleanup or refuses by name. After this process exits the file is reclaimed ` +
`as a stale lock ONLY if it is readable and its directory writable — otherwise it has to be removed by hand.`);
}
async dispose() {
try {
await this.ttl.dispose();
}
catch {
}
try {
await this.fileSessions.dispose();
}
catch {
}
this.fileCheckpoints.close();
try {
await this.fileMemory.flushEmbeds();
}
catch {
}
this.fileMemory.close();
this.fileWorkflowJournal.dispose();
try {
this.fileAgentRows.close();
}
catch {
}
try {
this.fileMailbox.close();
}
catch {
}
if (!this.lock.release())
this.discloseRetainedBootLock();
}
}