import { LocalArchiveStore } from "./index.js"; import type { BlobArchiveAdapter, PlanArchiveSnapshot, RunSnapshot, TraceDetail, TraceRecord, TraceSummary, } from "./types.js"; function blobKey(kind: "plan" | "run" | "trace", id: string): string { return `plasm/archives/${kind}/${id}.json`; } function archivePrefix(kind: "plan" | "run" | "trace"): string { return `plasm/archives/${kind}/`; } /** Durable archive store: local FS cache + Blob bodies (Eve-aligned, no KV index). */ export class ProdArchiveStore { readonly local: LocalArchiveStore; constructor( agentRoot: string, private readonly blob: BlobArchiveAdapter, ) { this.local = LocalArchiveStore.fromAgentRoot(agentRoot); } get paths() { return this.local.paths; } async bootstrap(): Promise { await this.local.bootstrap(); } private async putJson(key: string, value: unknown): Promise { await this.blob.put(key, JSON.stringify(value)); } private async getJson(key: string): Promise { const bytes = await this.blob.get(key); if (!bytes) return null; return JSON.parse(Buffer.from(bytes).toString("utf8")) as T; } private async listJson(kind: "plan" | "run" | "trace"): Promise { const paths = await this.blob.list(archivePrefix(kind)); const items: T[] = []; for (const key of paths) { if (!key.endsWith(".json")) continue; const item = await this.getJson(key); if (item) items.push(item); } return items; } async recordToolEvent( tenantId: string, traceId: string, kind: string, name: string, attributes?: Record, ): Promise { await this.local.recordToolEvent(tenantId, traceId, kind, name, attributes); } async finalizeTrace(detail: TraceDetail): Promise { await this.local.finalizeTrace(detail); await this.putJson(blobKey("trace", detail.summary.trace_id), detail); } async writePlanArchive(snapshot: PlanArchiveSnapshot): Promise { await this.local.writePlanArchive(snapshot); await this.putJson(blobKey("plan", snapshot.plan_commit_ref), snapshot); } async writeRunSnapshot(snapshot: RunSnapshot): Promise { await this.local.writeRunSnapshot(snapshot); await this.putJson(blobKey("run", snapshot.run_id), snapshot); } async listTraces(tenantId: string, limit = 50): Promise { const items = await this.listJson("trace"); if (items.length) { const summaries = items .map((detail) => detail.summary) .filter((summary) => summary.tenant_id === tenantId); summaries.sort((a, b) => b.started_at_ms - a.started_at_ms); return summaries.slice(0, limit); } return this.local.listTraces(tenantId, limit); } async getTrace(tenantId: string, traceId: string): Promise { void tenantId; const detail = await this.getJson(blobKey("trace", traceId)); if (detail) return detail; return this.local.getTrace(tenantId, traceId); } async listPlans(limit = 50): Promise { const items = await this.listJson("plan"); if (items.length) { items.sort((a, b) => b.archived_at.localeCompare(a.archived_at)); return items.slice(0, limit); } return this.local.listPlans(limit); } async listRuns(limit = 50): Promise { const items = await this.listJson("run"); if (items.length) { items.sort((a, b) => b.archived_at.localeCompare(a.archived_at)); return items.slice(0, limit); } return this.local.listRuns(limit); } async listArchives(limit = 50) { const [plans, runs] = await Promise.all([this.listPlans(limit), this.listRuns(limit)]); return { plans, runs, paths: this.local.paths }; } } export type { TraceRecord };