import { existsSync, mkdirSync, readFileSync, writeFileSync } from "node:fs";
import { join } from "node:path";
//#region extensions/crypto/src/services/agent-memory.ts
/**
* Agent Memory Service — file-backed declarative memory with frozen snapshots.
*
* Inspired by Hermes Agent's memory system. Two stores:
*   MEMORY.md  — agent's own notes (environment facts, tool quirks, lessons)
*   USER_{id}.md — per-user profile (preferences, communication style, habits)
*
* The "frozen snapshot" pattern:
*   1. At session start, MEMORY.md + USER_{id}.md are read from disk
*   2. The contents are injected into the system prompt via before_prompt_build
*   3. Mid-session writes update disk immediately but do NOT change the active
*      prompt — this preserves the LLM prefix cache
*   4. Next session sees the updated memory
*
* Entries are §-delimited strings inside the markdown files.
* Character limits prevent unbounded growth.
*
* All writes are scanned for prompt injection and credential leaks.
*/
const INJECTION_PATTERNS = [
	/ignore\s+(all\s+)?previous\s+instructions/i,
	/you\s+are\s+now\s+(a|an|the)\s+/i,
	/forget\s+(everything|all|your)/i,
	/\bsystem\s*:\s*you\s+(must|should|will)/i,
	/<\/?system[-_]?(message|prompt|instruction)>/i,
	/\bdo\s+not\s+(tell|reveal|mention|show)\s+(the\s+)?user\b/i,
	/\bact\s+as\s+if\s+(you|the)\s+(are|is|have)\b/i,
	/\b(CLAWNCHER_PRIVATE_KEY|BANKR_API_KEY|PRIVATE_KEY|WALLET_PASSWORD)\b/i,
	/\b(mnemonic|seed\s*phrase|recovery\s*phrase|secret\s*words)\b/i,
	/[\u200B-\u200F\u202A-\u202E\uFEFF]/
];
function containsInjection(text) {
	for (const pattern of INJECTION_PATTERNS) if (pattern.test(text)) return `Blocked: content matches injection pattern "${pattern.source.slice(0, 40)}"`;
	return null;
}
const ENTRY_DELIM_REGEX = /\n§ /g;
function parseEntries(content) {
	if (!content.trim()) return [];
	return content.split(ENTRY_DELIM_REGEX).map((e) => e.replace(/^§\s*/, "").trim()).filter(Boolean);
}
function serializeEntries(entries) {
	if (entries.length === 0) return "";
	return entries.map((e) => `§ ${e}`).join("\n");
}
var MemoryStore = class {
	entries = [];
	filePath;
	charLimit;
	storeType;
	constructor(filePath, charLimit, storeType) {
		this.filePath = filePath;
		this.charLimit = charLimit;
		this.storeType = storeType;
		this.load();
	}
	load() {
		try {
			if (existsSync(this.filePath)) this.entries = parseEntries(readFileSync(this.filePath, "utf8"));
		} catch {
			this.entries = [];
		}
	}
	getEntries() {
		return [...this.entries];
	}
	getContent() {
		return serializeEntries(this.entries);
	}
	getTotalChars() {
		return this.getContent().length;
	}
	add(entry) {
		const trimmed = entry.trim();
		if (!trimmed) return {
			ok: false,
			error: "Empty entry"
		};
		const injectionResult = containsInjection(trimmed);
		if (injectionResult) return {
			ok: false,
			error: injectionResult
		};
		const newContent = serializeEntries([...this.entries, trimmed]);
		if (newContent.length > this.charLimit) return {
			ok: false,
			error: `Entry would exceed ${this.storeType} memory limit (${newContent.length}/${this.charLimit} chars). Try removing old entries first with the "remove" action.`
		};
		if (this.entries.some((e) => e.toLowerCase() === trimmed.toLowerCase())) return {
			ok: false,
			error: "Duplicate entry already exists"
		};
		this.entries.push(trimmed);
		this.persist();
		return { ok: true };
	}
	replace(oldSubstring, newContent) {
		const trimmedNew = newContent.trim();
		if (!trimmedNew) return {
			ok: false,
			error: "Replacement content is empty"
		};
		const injectionResult = containsInjection(trimmedNew);
		if (injectionResult) return {
			ok: false,
			error: injectionResult
		};
		const idx = this.entries.findIndex((e) => e.toLowerCase().includes(oldSubstring.toLowerCase()));
		if (idx === -1) return {
			ok: false,
			error: `No entry containing "${oldSubstring.slice(0, 50)}" found`
		};
		const updated = [...this.entries];
		updated[idx] = trimmedNew;
		const newSerialized = serializeEntries(updated);
		if (newSerialized.length > this.charLimit) return {
			ok: false,
			error: `Replacement would exceed memory limit (${newSerialized.length}/${this.charLimit} chars)`
		};
		this.entries = updated;
		this.persist();
		return { ok: true };
	}
	remove(substring) {
		const idx = this.entries.findIndex((e) => e.toLowerCase().includes(substring.toLowerCase()));
		if (idx === -1) return {
			ok: false,
			error: `No entry containing "${substring.slice(0, 50)}" found`
		};
		const removed = this.entries.splice(idx, 1)[0];
		this.persist();
		return {
			ok: true,
			removed
		};
	}
	clear() {
		this.entries = [];
		this.persist();
	}
	persist() {
		try {
			const dir = this.filePath.replace(/\/[^/]+$/, "");
			if (!existsSync(dir)) mkdirSync(dir, { recursive: true });
			writeFileSync(this.filePath, this.getContent(), "utf8");
		} catch {}
	}
};
var AgentMemoryService = class AgentMemoryService {
	config;
	agentStore;
	userStores = /* @__PURE__ */ new Map();
	frozenSnapshots = /* @__PURE__ */ new Map();
	static MAX_SNAPSHOTS = 50;
	static SNAPSHOT_TTL_MS = 14400 * 1e3;
	constructor(config = {}) {
		this.config = {
			agentCharLimit: config.agentCharLimit ?? 2200,
			userCharLimit: config.userCharLimit ?? 1375,
			baseDir: config.baseDir ?? join(process.env.HOME ?? "/tmp", ".openclawnch", "memory")
		};
		this.agentStore = new MemoryStore(join(this.config.baseDir, "MEMORY.md"), this.config.agentCharLimit, "agent");
	}
	/**
	* Take a frozen snapshot for a session.
	* Call this at session start (before_prompt_build). The snapshot is
	* immutable for the duration of the session.
	*/
	freezeSnapshot(sessionKey, userId) {
		const parts = [];
		const agentContent = this.agentStore.getContent();
		if (agentContent) parts.push("## Agent Memory\n" + agentContent);
		if (userId) {
			const userContent = this.getUserStore(userId).getContent();
			if (userContent) parts.push("## User Profile\n" + userContent);
		}
		const snapshot = parts.join("\n\n");
		this.evictStaleSnapshots();
		this.frozenSnapshots.set(sessionKey, {
			content: snapshot,
			createdAt: Date.now()
		});
		return snapshot;
	}
	/**
	* Get the frozen snapshot for a session (returns empty string if none/expired).
	*/
	getSnapshot(sessionKey) {
		const entry = this.frozenSnapshots.get(sessionKey);
		if (!entry) return "";
		if (Date.now() - entry.createdAt > AgentMemoryService.SNAPSHOT_TTL_MS) {
			this.frozenSnapshots.delete(sessionKey);
			return "";
		}
		return entry.content;
	}
	/**
	* Clear the frozen snapshot when a session ends.
	*/
	clearSnapshot(sessionKey) {
		this.frozenSnapshots.delete(sessionKey);
	}
	/**
	* Evict stale/oversized snapshots to prevent unbounded memory growth.
	*/
	evictStaleSnapshots() {
		const now = Date.now();
		for (const [key, entry] of this.frozenSnapshots) if (now - entry.createdAt > AgentMemoryService.SNAPSHOT_TTL_MS) this.frozenSnapshots.delete(key);
		while (this.frozenSnapshots.size >= AgentMemoryService.MAX_SNAPSHOTS) {
			const oldestKey = this.frozenSnapshots.keys().next().value;
			if (oldestKey !== void 0) this.frozenSnapshots.delete(oldestKey);
			else break;
		}
	}
	addAgentMemory(entry) {
		return this.agentStore.add(entry);
	}
	replaceAgentMemory(oldSubstring, newContent) {
		return this.agentStore.replace(oldSubstring, newContent);
	}
	removeAgentMemory(substring) {
		return this.agentStore.remove(substring);
	}
	getAgentMemory() {
		return this.agentStore.getEntries();
	}
	getAgentMemoryStats() {
		return {
			entries: this.agentStore.getEntries().length,
			chars: this.agentStore.getTotalChars(),
			limit: this.config.agentCharLimit
		};
	}
	getUserStore(userId) {
		const safeId = userId.replace(/[^a-zA-Z0-9_-]/g, "_").slice(0, 64);
		let store = this.userStores.get(safeId);
		if (!store) {
			store = new MemoryStore(join(this.config.baseDir, `USER_${safeId}.md`), this.config.userCharLimit, "user");
			this.userStores.set(safeId, store);
		}
		return store;
	}
	addUserMemory(userId, entry) {
		return this.getUserStore(userId).add(entry);
	}
	replaceUserMemory(userId, oldSubstring, newContent) {
		return this.getUserStore(userId).replace(oldSubstring, newContent);
	}
	removeUserMemory(userId, substring) {
		return this.getUserStore(userId).remove(substring);
	}
	getUserMemory(userId) {
		return this.getUserStore(userId).getEntries();
	}
	getUserMemoryStats(userId) {
		const store = this.getUserStore(userId);
		return {
			entries: store.getEntries().length,
			chars: store.getTotalChars(),
			limit: this.config.userCharLimit
		};
	}
	getStatus() {
		return {
			agentEntries: this.agentStore.getEntries().length,
			agentChars: this.agentStore.getTotalChars(),
			agentCharLimit: this.config.agentCharLimit,
			userStoreCount: this.userStores.size,
			frozenSnapshotCount: this.frozenSnapshots.size
		};
	}
};
let _instance = null;
function getAgentMemory(config) {
	if (!_instance) _instance = new AgentMemoryService(config);
	return _instance;
}
function resetAgentMemory() {
	_instance = null;
}
//#endregion
export { getAgentMemory, resetAgentMemory };

//# sourceMappingURL=agent-memory.mjs.map