import type { ExtensionAPI } from "@earendil-works/pi-coding-agent"; import { MemoryStore } from "../store/memory-store.js"; import { resolveProjectStore, type ProjectStoreRef } from "../project-context.js"; /** Startup/first-use repair for writable hosts; read-only children never call this. */ export async function recoverOversizedMemory( store: MemoryStore, targets: readonly ("memory" | "user" | "failure")[], report: (message: string) => void = console.warn, ): Promise { for (const target of targets) { if (store.capacityUsage(target) <= store.capacityGoal(target)) continue; try { const result = await store.recover(target, true, undefined, { onlyIfOversized: true }); if (result.success) { // Refresh even if a peer repaired it first: our frozen snapshot may still be oversized. await store.loadFromDisk(); if (result.archive_path) { report(`Memory auto-recovery: ${target} ${result.before_chars} → ${result.after_chars} chars; original: ${result.archive_path}. Archives are not searched automatically.${result.warning ? ` Warning: ${result.warning}` : ""}`); } } else { report(`Memory auto-recovery failed for ${target}: ${result.error}`); } } catch (error) { // A failed archive must preserve the active data, not block the VPS startup. report(`Memory auto-recovery failed for ${target}: ${error instanceof Error ? error.message : String(error)}`); } } } export function registerRecoverCommand(pi: ExtensionAPI, store: MemoryStore, projectStore: ProjectStoreRef): void { pi.registerCommand("memory-recover", { description: "Preview local archival recovery; --apply archives originals and bounds active memory (no LLM)", handler: async (args, ctx) => { const argument = args.trim(); if (argument && argument !== "--apply") { ctx.ui.notify("Usage: /memory-recover [--apply]", "warning"); return; } const apply = argument === "--apply"; if (apply && ctx.hasUI && !await ctx.ui.confirm("Recover memory?", "Full originals will be archived; only newest whole entries within 75% of each limit remain active. Archived entries are not searched automatically. No LLM is called.")) return; const targets: Array<[string, MemoryStore, "memory" | "user" | "failure"]> = [ ["global", store, "memory"], ["user", store, "user"], ["failure", store, "failure"], ]; const project = resolveProjectStore(projectStore); if (project) targets.push(["project", project, "memory"]); for (const [label, candidate, target] of targets) { try { const result = await candidate.recover(target, apply, ctx.signal); ctx.ui.notify(`${label}: ${JSON.stringify(result)}`, result.success && !result.warning ? "info" : "warning"); } catch (error) { ctx.ui.notify(`${label}: recovery failed: ${error instanceof Error ? error.message : String(error)}`, "error"); } } ctx.ui.notify(apply ? "Reload all sessions to refresh frozen memory context. Archives are preserved and not searched automatically; inspect per-target results above." : "Preview only. Use /memory-recover --apply to archive originals and trim active stores. Archives are not searched automatically.", "info"); }, }); }