"use strict";
// =============================================================================
// Pure Agent - Memory Store
// Ported from Hermes tools/memory_tool.py (MemoryStore class)
// File-based persistent memory with locking, dedup, drift detection
// =============================================================================
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
    if (k2 === undefined) k2 = k;
    var desc = Object.getOwnPropertyDescriptor(m, k);
    if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
      desc = { enumerable: true, get: function() { return m[k]; } };
    }
    Object.defineProperty(o, k2, desc);
}) : (function(o, m, k, k2) {
    if (k2 === undefined) k2 = k;
    o[k2] = m[k];
}));
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
    Object.defineProperty(o, "default", { enumerable: true, value: v });
}) : function(o, v) {
    o["default"] = v;
});
var __importStar = (this && this.__importStar) || (function () {
    var ownKeys = function(o) {
        ownKeys = Object.getOwnPropertyNames || function (o) {
            var ar = [];
            for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
            return ar;
        };
        return ownKeys(o);
    };
    return function (mod) {
        if (mod && mod.__esModule) return mod;
        var result = {};
        if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
        __setModuleDefault(result, mod);
        return result;
    };
})();
Object.defineProperty(exports, "__esModule", { value: true });
exports.MemoryStore = exports.ENTRY_DELIMITER = void 0;
exports.getMemoryStore = getMemoryStore;
exports.resetMemoryStore = resetMemoryStore;
const fs = __importStar(require("fs"));
const path = __importStar(require("path"));
const constants_1 = require("./constants");
const utils_1 = require("./utils");
const logger_1 = require("./logger");
// ---------------------------------------------------------------------------
// Constants
// ---------------------------------------------------------------------------
exports.ENTRY_DELIMITER = '\n§\n';
const MEMORY_CHAR_LIMIT = 2200;
const USER_CHAR_LIMIT = 1375;
const INJECTION_PATTERNS = [
    'ignore previous instructions',
    'ignore all previous',
    'you are now',
    'disregard your',
    'forget your instructions',
    'new instructions:',
    'system prompt:',
    '<system>',
    ']]>',
];
// ---------------------------------------------------------------------------
// Threat scanning (simplified from Hermes tools/threat_patterns.py)
// ---------------------------------------------------------------------------
function scanForThreats(content) {
    const lower = content.toLowerCase();
    for (const pattern of INJECTION_PATTERNS) {
        if (lower.includes(pattern)) {
            return `Blocked: contains potentially unsafe pattern "${pattern}"`;
        }
    }
    // Check for encoded secrets
    if (/\b(sk-[a-zA-Z0-9]{20,}|AKIA[0-9A-Z]{16}|ghp_[a-zA-Z0-9]{36})\b/.test(content)) {
        return 'Blocked: contains potential API key or credential';
    }
    return null;
}
// ---------------------------------------------------------------------------
// File locking (cross-platform)
// ---------------------------------------------------------------------------
function withFileLock(lockPath, fn) {
    // Simple lock using exclusive file creation
    const maxRetries = 10;
    const retryDelay = 50; // ms
    for (let i = 0; i < maxRetries; i++) {
        try {
            // Try to create lock file exclusively
            const fd = fs.openSync(lockPath, 'wx');
            try {
                const result = fn();
                fs.closeSync(fd);
                try {
                    fs.unlinkSync(lockPath);
                }
                catch { /* ignore */ }
                return result;
            }
            catch (err) {
                fs.closeSync(fd);
                try {
                    fs.unlinkSync(lockPath);
                }
                catch { /* ignore */ }
                throw err;
            }
        }
        catch (err) {
            if (err.code === 'EEXIST') {
                // Lock file exists, wait and retry
                const start = Date.now();
                while (Date.now() - start < retryDelay) { /* busy wait */ }
                continue;
            }
            throw err;
        }
    }
    // Last attempt without lock
    (0, logger_1.getLogger)().warn('File lock timeout, proceeding without lock');
    return fn();
}
// ---------------------------------------------------------------------------
// MemoryStore class
// ---------------------------------------------------------------------------
class MemoryStore {
    _memoryEntries = [];
    _userEntries = [];
    _systemPromptSnapshot = { memory: '', user: '' };
    _memoryCharLimit;
    _userCharLimit;
    constructor(memoryCharLimit = MEMORY_CHAR_LIMIT, userCharLimit = USER_CHAR_LIMIT) {
        this._memoryCharLimit = memoryCharLimit;
        this._userCharLimit = userCharLimit;
        this.loadFromDisk();
    }
    // -------------------------------------------------------------------------
    // Disk I/O
    // -------------------------------------------------------------------------
    loadFromDisk() {
        const memoryPath = this._pathFor('memory');
        const userPath = this._pathFor('user');
        this._memoryEntries = this._readFile(memoryPath);
        this._userEntries = this._readFile(userPath);
        // Freeze snapshot for prefix cache stability
        this._systemPromptSnapshot = {
            memory: this._renderBlock('memory', this._sanitizeForSnapshot(this._memoryEntries, 'MEMORY.md')),
            user: this._renderBlock('user', this._sanitizeForSnapshot(this._userEntries, 'USER.md')),
        };
    }
    saveToDisk(target) {
        const entries = this._entriesFor(target);
        const filePath = this._pathFor(target);
        const lockPath = filePath + '.lock';
        withFileLock(lockPath, () => {
            this._writeFile(filePath, entries);
        });
    }
    // -------------------------------------------------------------------------
    // CRUD operations
    // -------------------------------------------------------------------------
    add(target, content) {
        // Threat scan
        const threat = scanForThreats(content);
        if (threat)
            return this._errorResponse(target, threat);
        const entries = this._entriesFor(target);
        const limit = this._charLimit(target);
        const newCharCount = this._charCount(target) + content.length + exports.ENTRY_DELIMITER.length;
        if (newCharCount > limit) {
            return this._errorResponse(target, `Character limit exceeded. Current: ${this._charCount(target)}, Adding: ${content.length}, Limit: ${limit}`);
        }
        entries.push(content);
        this._setEntries(target, entries);
        this.saveToDisk(target);
        return this._successResponse(target, `Added new entry to ${target}`);
    }
    replace(target, oldText, newContent) {
        // Threat scan on new content
        const threat = scanForThreats(newContent);
        if (threat)
            return this._errorResponse(target, threat);
        const entries = this._entriesFor(target);
        const matches = entries.filter(e => e.includes(oldText));
        if (matches.length === 0) {
            return this._errorResponse(target, `No entry found containing "${oldText.slice(0, 80)}"`);
        }
        if (matches.length > 1) {
            // Check if all matches are identical (duplicates)
            const unique = new Set(matches);
            if (unique.size > 1) {
                return this._errorResponse(target, `Found ${matches.length} matching entries (not identical). Be more specific.\n` +
                    matches.map((m, i) => `  ${i + 1}. ${m.slice(0, 80)}...`).join('\n'));
            }
        }
        // Replace first match
        const idx = entries.findIndex(e => e.includes(oldText));
        if (idx < 0)
            return this._errorResponse(target, 'Internal error: match not found');
        // Check new size
        const oldLen = entries[idx].length;
        const delta = newContent.length - oldLen;
        if (this._charCount(target) + delta > this._charLimit(target)) {
            return this._errorResponse(target, `Replacement would exceed char limit. Delta: ${delta > 0 ? '+' : ''}${delta}`);
        }
        entries[idx] = newContent;
        this._setEntries(target, entries);
        this.saveToDisk(target);
        return this._successResponse(target, `Replaced entry in ${target}`);
    }
    remove(target, oldText) {
        const entries = this._entriesFor(target);
        const matches = entries.filter(e => e.includes(oldText));
        if (matches.length === 0) {
            return this._errorResponse(target, `No entry found containing "${oldText.slice(0, 80)}"`);
        }
        if (matches.length > 1) {
            const unique = new Set(matches);
            if (unique.size > 1) {
                return this._errorResponse(target, `Found ${matches.length} matching entries (not identical). Be more specific.`);
            }
        }
        const idx = entries.findIndex(e => e.includes(oldText));
        if (idx < 0)
            return this._errorResponse(target, 'Internal error: match not found');
        entries.splice(idx, 1);
        this._setEntries(target, entries);
        this.saveToDisk(target);
        return this._successResponse(target, `Removed entry from ${target}`);
    }
    // -------------------------------------------------------------------------
    // System prompt integration
    // -------------------------------------------------------------------------
    formatForSystemPrompt(target) {
        const block = target === 'user'
            ? this._systemPromptSnapshot.user
            : this._systemPromptSnapshot.memory;
        if (!block || block.trim().length === 0)
            return null;
        return block;
    }
    // -------------------------------------------------------------------------
    // Getters
    // -------------------------------------------------------------------------
    get memoryEntries() { return [...this._memoryEntries]; }
    get userEntries() { return [...this._userEntries]; }
    getMemoryUsage() {
        return {
            memory: `${Math.round(this._charCount('memory') / this._charLimit('memory') * 100)}% -- ${this._charCount('memory')}/${this._charLimit('memory')} chars`,
            user: `${Math.round(this._charCount('user') / this._charLimit('user') * 100)}% -- ${this._charCount('user')}/${this._charLimit('user')} chars`,
        };
    }
    // -------------------------------------------------------------------------
    // Internal helpers
    // -------------------------------------------------------------------------
    _pathFor(target) {
        const home = (0, constants_1.getPureAgentHome)();
        return target === 'user'
            ? path.join(home, 'USER.md')
            : path.join(home, 'MEMORY.md');
    }
    _entriesFor(target) {
        return target === 'user' ? this._userEntries : this._memoryEntries;
    }
    _setEntries(target, entries) {
        if (target === 'user') {
            this._userEntries = entries;
        }
        else {
            this._memoryEntries = entries;
        }
    }
    _charCount(target) {
        const entries = this._entriesFor(target);
        if (entries.length === 0)
            return 0;
        return entries.join(exports.ENTRY_DELIMITER).length;
    }
    _charLimit(target) {
        return target === 'user' ? this._userCharLimit : this._memoryCharLimit;
    }
    _readFile(filePath) {
        try {
            if (!fs.existsSync(filePath))
                return [];
            const content = fs.readFileSync(filePath, 'utf-8').trim();
            if (!content)
                return [];
            const entries = content.split(exports.ENTRY_DELIMITER)
                .map(e => e.trim())
                .filter(e => e.length > 0);
            // Deduplicate preserving first occurrence
            const seen = new Set();
            const deduped = [];
            for (const entry of entries) {
                if (!seen.has(entry)) {
                    seen.add(entry);
                    deduped.push(entry);
                }
            }
            return deduped;
        }
        catch (err) {
            (0, logger_1.getLogger)().warn(`Failed to read memory file ${filePath}: ${err.message}`);
            return [];
        }
    }
    _writeFile(filePath, entries) {
        const dir = path.dirname(filePath);
        if (!fs.existsSync(dir)) {
            fs.mkdirSync(dir, { recursive: true });
        }
        const content = entries.join(exports.ENTRY_DELIMITER);
        (0, utils_1.atomicWrite)(filePath, content);
    }
    _sanitizeForSnapshot(entries, filename) {
        return entries.map(entry => {
            const threat = scanForThreats(entry);
            if (threat) {
                (0, logger_1.getLogger)().warn(`Blocked entry in ${filename}: ${threat}`);
                return `[BLOCKED: ${threat}]`;
            }
            return entry;
        });
    }
    _renderBlock(target, entries) {
        if (entries.length === 0)
            return '';
        const label = target === 'user' ? 'USER PROFILE' : 'MEMORY (your personal notes)';
        const charCount = entries.join(exports.ENTRY_DELIMITER).length;
        const limit = this._charLimit(target);
        const percent = Math.round(charCount / limit * 100);
        const header = '════════════════════════════════════════════════';
        return `${header}\n${label} [${percent}% -- ${charCount}/${limit} chars]\n${header}\n${entries.join(exports.ENTRY_DELIMITER)}`;
    }
    _successResponse(target, message) {
        return {
            success: true,
            target,
            entries: this._entriesFor(target),
            usage: `${Math.round(this._charCount(target) / this._charLimit(target) * 100)}% -- ${this._charCount(target)}/${this._charLimit(target)} chars`,
            entry_count: this._entriesFor(target).length,
            message,
        };
    }
    _errorResponse(target, error) {
        return {
            success: false,
            target,
            error,
            entries: this._entriesFor(target),
            usage: `${Math.round(this._charCount(target) / this._charLimit(target) * 100)}% -- ${this._charCount(target)}/${this._charLimit(target)} chars`,
        };
    }
}
exports.MemoryStore = MemoryStore;
// ---------------------------------------------------------------------------
// Singleton
// ---------------------------------------------------------------------------
let _instance = null;
function getMemoryStore() {
    if (!_instance) {
        _instance = new MemoryStore();
    }
    return _instance;
}
function resetMemoryStore() {
    _instance = null;
}
//# sourceMappingURL=memory-store.js.map