UNPKG

@sema-agent/core

Version:

Stateless, task-oriented AI agent core

152 lines (151 loc) • 7.32 kB
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 { FileStrategyStore } from "./strategy-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 { FileStrategyStore } from "./strategy-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; strategyStore; 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 ?? {}); const strategiesRoot = join(this.root, "strategies"); this.strategyStore = new FileStrategyStore({ root: strategiesRoot, ...(opts.onCorruptRead !== undefined ? { onIncident: (i) => opts.onCorruptRead?.({ kind: "file", path: i.path ?? strategiesRoot, reason: `strategy ${i.op}: ${i.error}` }), } : {}), }); 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(); } }