import { existsSync, mkdirSync, readFileSync } from "node:fs";
import { mkdir, readFile, rename, writeFile } from "node:fs/promises";
import { join } from "node:path";
import lockfile from "proper-lockfile";
//#region src/agent/memory/builtin-memory-store.ts
/**
* Bounded curated memory: agent home `memories/MEMORY.md` + `USER.md`, §-delimited entries.
* Snapshot for system prompt is captured at load time and not mutated until next load.
*/
const MEMORY_ENTRY_DELIMITER = "\n§\n";
const DEFAULT_MEMORY_LIMIT = 2200;
const DEFAULT_USER_LIMIT = 1375;
var BuiltinMemoryStore = class {
	memoryEntries = [];
	userEntries = [];
	snapshot = {
		memory: "",
		user: ""
	};
	constructor(config) {
		this.config = config;
	}
	/** When false, USER.md is omitted from the snapshot and should not be edited via `curated_memory`. */
	isUserProfileEnabled() {
		return this.config.userProfileEnabled !== false;
	}
	/** Agent-scoped curated memory directory (`~/.xopc/agents/<id>/memories/`). */
	get memoriesDir() {
		return this.config.memoriesDir;
	}
	pathFor(target) {
		return join(this.memoriesDir, target === "memory" ? "MEMORY.md" : "USER.md");
	}
	/**
	* Load from disk and freeze {@link getSnapshot}. Sync for agent creation (prefix cache stability).
	*/
	loadFromDiskSync() {
		mkdirSync(this.memoriesDir, { recursive: true });
		this.memoryEntries = this.parseFileContent(this.readPathSync(join(this.memoriesDir, "MEMORY.md")));
		this.userEntries = this.config.userProfileEnabled === false ? [] : this.parseFileContent(this.readPathSync(join(this.memoriesDir, "USER.md")));
		this.memoryEntries = dedupePreserveOrder(this.memoryEntries);
		this.userEntries = dedupePreserveOrder(this.userEntries);
		this.snapshot = {
			memory: this.renderBlock("memory", this.memoryEntries),
			user: this.renderBlock("user", this.userEntries)
		};
	}
	readPathSync(path) {
		try {
			if (!existsSync(path)) return "";
			return readFileSync(path, { encoding: "utf-8" });
		} catch {
			return "";
		}
	}
	parseFileContent(raw) {
		if (!raw.trim()) return [];
		return raw.split(MEMORY_ENTRY_DELIMITER).map((e) => e.trim()).filter(Boolean);
	}
	getSnapshot() {
		return this.snapshot;
	}
	/**
	* Live entries for read/debug (may differ from snapshot after mutations).
	*/
	getLiveEntries(target) {
		return target === "memory" ? [...this.memoryEntries] : [...this.userEntries];
	}
	async add(target, content) {
		const trimmed = content.trim();
		if (!trimmed) return {
			success: false,
			error: "Content cannot be empty."
		};
		const scanError = scanForThreats(trimmed);
		if (scanError) return {
			success: false,
			error: scanError
		};
		return this.withFileLock(target, async () => {
			await this.reloadTargetFromDisk(target);
			const entries = target === "memory" ? this.memoryEntries : this.userEntries;
			const limit = this.charLimit(target);
			if (entries.includes(trimmed)) return {
				success: false,
				error: "Entry already exists"
			};
			if (calculateTotal([...entries, trimmed]) > limit) return {
				success: false,
				error: `Memory at ${calculateTotal(entries)}/${limit} chars. Adding would exceed limit.`
			};
			entries.push(trimmed);
			await this.flushTargetToDisk(target);
			return {
				success: true,
				message: "Entry added."
			};
		});
	}
	async replace(target, oldText, newContent) {
		const ot = oldText.trim();
		const nc = newContent.trim();
		if (!ot) return {
			success: false,
			error: "old_text cannot be empty."
		};
		if (!nc) return {
			success: false,
			error: "new_content cannot be empty. Use remove to delete entries."
		};
		const scanError = scanForThreats(nc);
		if (scanError) return {
			success: false,
			error: scanError
		};
		return this.withFileLock(target, async () => {
			await this.reloadTargetFromDisk(target);
			const entries = target === "memory" ? this.memoryEntries : this.userEntries;
			const matches = [];
			for (let i = 0; i < entries.length; i++) if (entries[i].includes(ot)) matches.push({
				index: i,
				entry: entries[i]
			});
			if (matches.length === 0) return {
				success: false,
				error: `No entry matched '${ot}'.`
			};
			if (matches.length > 1) {
				if (new Set(matches.map((m) => m.entry)).size > 1) return {
					success: false,
					error: `Multiple entries matched '${ot}'. Be more specific. Previews: ${matches.map((m) => m.entry.length > 80 ? `${m.entry.slice(0, 80)}...` : m.entry).join(" | ")}`
				};
			}
			const idx = matches[0].index;
			const limit = this.charLimit(target);
			const testEntries = [...entries];
			testEntries[idx] = nc;
			if (calculateTotal(testEntries) > limit) return {
				success: false,
				error: `Replacement would put memory at ${calculateTotal(testEntries)}/${limit} chars.`
			};
			entries[idx] = nc;
			await this.flushTargetToDisk(target);
			return {
				success: true,
				message: "Entry replaced."
			};
		});
	}
	async remove(target, oldText) {
		const ot = oldText.trim();
		if (!ot) return {
			success: false,
			error: "old_text cannot be empty."
		};
		return this.withFileLock(target, async () => {
			await this.reloadTargetFromDisk(target);
			const entries = target === "memory" ? this.memoryEntries : this.userEntries;
			const matches = [];
			for (let i = 0; i < entries.length; i++) if (entries[i].includes(ot)) matches.push(i);
			if (matches.length === 0) return {
				success: false,
				error: `No entry matched '${ot}'.`
			};
			if (matches.length > 1) {
				if (new Set(matches.map((i) => entries[i])).size > 1) return {
					success: false,
					error: `Multiple entries matched '${ot}'. Be more specific. Previews: ${matches.map((i) => {
						const e = entries[i];
						return e.length > 80 ? `${e.slice(0, 80)}...` : e;
					}).join(" | ")}`
				};
			}
			entries.splice(matches[0], 1);
			await this.flushTargetToDisk(target);
			return {
				success: true,
				message: "Entry removed."
			};
		});
	}
	async withFileLock(target, fn) {
		const filePath = this.pathFor(target);
		await mkdir(this.memoriesDir, { recursive: true });
		if (!existsSync(filePath)) await writeFile(filePath, "", "utf-8");
		await lockfile.lock(filePath, {
			retries: 3,
			stale: 1e4
		});
		try {
			return await fn();
		} finally {
			await lockfile.unlock(filePath);
		}
	}
	async reloadTargetFromDisk(target) {
		const path = this.pathFor(target);
		let raw = "";
		try {
			raw = await readFile(path, "utf-8");
		} catch {
			raw = "";
		}
		const parsed = dedupePreserveOrder(this.parseFileContent(raw));
		if (target === "memory") this.memoryEntries = parsed;
		else this.userEntries = parsed;
	}
	async flushTargetToDisk(target) {
		const entries = target === "memory" ? this.memoryEntries : this.userEntries;
		const filePath = this.pathFor(target);
		const content = entries.join(MEMORY_ENTRY_DELIMITER);
		const tmpPath = `${filePath}.tmp.${Date.now()}`;
		await writeFile(tmpPath, content, "utf-8");
		await rename(tmpPath, filePath);
	}
	charLimit(target) {
		return target === "memory" ? this.config.memoryCharLimit ?? DEFAULT_MEMORY_LIMIT : this.config.userCharLimit ?? DEFAULT_USER_LIMIT;
	}
	renderBlock(target, entries) {
		if (entries.length === 0) return "";
		const limit = this.charLimit(target);
		const content = entries.join(MEMORY_ENTRY_DELIMITER);
		const current = content.length;
		const pct = Math.min(100, Math.round(current / limit * 100));
		const header = target === "user" ? `USER PROFILE (who the user is) [${pct}% — ${current.toLocaleString()}/${limit.toLocaleString()} chars]` : `MEMORY (your personal notes) [${pct}% — ${current.toLocaleString()}/${limit.toLocaleString()} chars]`;
		const separator = "═".repeat(46);
		return `${separator}\n${header}\n${separator}\n${content}`;
	}
};
function dedupePreserveOrder(entries) {
	const seen = /* @__PURE__ */ new Set();
	const out = [];
	for (const e of entries) {
		if (seen.has(e)) continue;
		seen.add(e);
		out.push(e);
	}
	return out;
}
function calculateTotal(entries) {
	if (entries.length === 0) return 0;
	return entries.join(MEMORY_ENTRY_DELIMITER).length;
}
function scanForThreats(content) {
	for (const { regex, id } of [
		{
			regex: /ignore\s+(previous|all|above|prior)\s+instructions/i,
			id: "prompt_injection"
		},
		{
			regex: /you\s+are\s+now\s+/i,
			id: "role_hijack"
		},
		{
			regex: /do\s+not\s+tell\s+the\s+user/i,
			id: "deception_hide"
		},
		{
			regex: /system\s+prompt\s+override/i,
			id: "sys_prompt_override"
		},
		{
			regex: /disregard\s+(your|all|any)\s+(instructions|rules|guidelines)/i,
			id: "disregard_rules"
		},
		{
			regex: /act\s+as\s+(if|though)\s+you\s+(have\s+no|don't\s+have)\s+(restrictions|limits|rules)/i,
			id: "bypass_restrictions"
		},
		{
			regex: /curl\s+[^\n]*\$\{?\w*(KEY|TOKEN|SECRET|PASSWORD|CREDENTIAL|API)/i,
			id: "exfil_curl"
		},
		{
			regex: /wget\s+[^\n]*\$\{?\w*(KEY|TOKEN|SECRET|PASSWORD|CREDENTIAL|API)/i,
			id: "exfil_wget"
		},
		{
			regex: /cat\s+[^\n]*(\.env|credentials|\.netrc|\.pgpass|\.npmrc|\.pypirc)/i,
			id: "read_secrets"
		},
		{
			regex: /authorized_keys/i,
			id: "ssh_backdoor"
		},
		{
			regex: /\$HOME\/\.ssh|\~\/\.ssh/i,
			id: "ssh_access"
		}
	]) if (regex.test(content)) return `Blocked: content matches threat pattern '${id}'. Memory entries are injected into the system prompt and must not contain injection payloads.`;
	for (const char of [
		"​",
		"‌",
		"‍",
		"⁠",
		"﻿",
		"‪",
		"‫",
		"‬",
		"‭",
		"‮"
	]) if (content.includes(char)) return `Blocked: content contains invisible unicode character U+${char.charCodeAt(0).toString(16).toUpperCase().padStart(4, "0")} (possible injection).`;
	return null;
}
//#endregion
export { BuiltinMemoryStore, MEMORY_ENTRY_DELIMITER, scanForThreats };

//# sourceMappingURL=builtin-memory-store.js.map