"use strict";
// =============================================================================
// Pure Agent - Cron Scheduler
// Ported from Hermes cron/scheduler.py
// =============================================================================
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.setCronAgentConfig = setCronAgentConfig;
exports.tick = tick;
exports.startCronTicker = startCronTicker;
exports.stopCronTicker = stopCronTicker;
const fs = __importStar(require("fs"));
const path = __importStar(require("path"));
const child_process_1 = require("child_process");
const constants_1 = require("../constants");
const logger_1 = require("../logger");
const pure_agent_1 = require("../pure-agent");
const jobs_1 = require("./jobs");
// ---------------------------------------------------------------------------
// Constants
// ---------------------------------------------------------------------------
const SILENT_MARKER = '[SILENT]';
const DEFAULT_SCRIPT_TIMEOUT = 120; // seconds
const TICK_LOCK_FILE = path.join((0, constants_1.getPureAgentHome)(), 'cron', '.tick.lock');
// ---------------------------------------------------------------------------
// Injection scanning (simplified from Hermes)
// ---------------------------------------------------------------------------
const CRON_INJECTION_PATTERNS = [
    'ignore previous instructions',
    'ignore all previous',
    'you are now',
    'system prompt:',
    '<system>',
    ']]>',
];
function scanPrompt(prompt) {
    const lower = prompt.toLowerCase();
    for (const pattern of CRON_INJECTION_PATTERNS) {
        if (lower.includes(pattern)) {
            return `Blocked: contains pattern "${pattern}"`;
        }
    }
    return null;
}
// ---------------------------------------------------------------------------
// File lock
// ---------------------------------------------------------------------------
function withTickLock(fn) {
    const lockPath = TICK_LOCK_FILE;
    const dir = path.dirname(lockPath);
    if (!fs.existsSync(dir))
        fs.mkdirSync(dir, { recursive: true });
    // Simple lock: write PID to lock file exclusively
    try {
        const fd = fs.openSync(lockPath, 'wx');
        fs.writeSync(fd, String(process.pid));
        fs.closeSync(fd);
        try {
            return fn();
        }
        finally {
            try {
                fs.unlinkSync(lockPath);
            }
            catch { /* ignore */ }
        }
    }
    catch {
        // Lock exists - another tick is running, skip
        return 0;
    }
}
// ---------------------------------------------------------------------------
// Script execution
// ---------------------------------------------------------------------------
function runJobScript(scriptPath, timeout = DEFAULT_SCRIPT_TIMEOUT) {
    // Security: only allow scripts under ~/.pure-agent/scripts/
    const scriptsDir = path.join((0, constants_1.getPureAgentHome)(), 'scripts');
    const resolved = path.resolve(scriptPath);
    if (!resolved.startsWith(scriptsDir)) {
        return Promise.resolve({ success: false, output: `Script path must be under ${scriptsDir}` });
    }
    return new Promise((resolve) => {
        const proc = (0, child_process_1.spawn)('node', [resolved], {
            timeout: timeout * 1000,
            windowsHide: true,
            stdio: ['ignore', 'pipe', 'pipe'],
        });
        let stdout = '';
        let stderr = '';
        proc.stdout?.on('data', (data) => {
            stdout += data.toString('utf-8');
            if (stdout.length > 50000)
                proc.kill();
        });
        proc.stderr?.on('data', (data) => {
            stderr += data.toString('utf-8');
            if (stderr.length > 10000)
                proc.kill();
        });
        proc.on('close', (code) => {
            resolve({
                success: code === 0,
                output: code === 0 ? stdout.trim() : `Exit ${code}: ${stderr.trim() || stdout.trim()}`,
            });
        });
        proc.on('error', (err) => {
            resolve({ success: false, output: `Script error: ${err.message}` });
        });
    });
}
// ---------------------------------------------------------------------------
// Build job prompt
// ---------------------------------------------------------------------------
function buildJobPrompt(job) {
    const parts = [];
    if (job.prompt) {
        parts.push(job.prompt);
    }
    if (job.skills && job.skills.length > 0) {
        parts.push(`\nRelevant skills: ${job.skills.join(', ')}`);
    }
    if (job.script) {
        parts.push(`\nScript: ${job.script}`);
    }
    return parts.join('\n');
}
// ---------------------------------------------------------------------------
// Agent config for cron jobs
// ---------------------------------------------------------------------------
let _agentConfig = null;
function setCronAgentConfig(config) {
    _agentConfig = config;
}
// ---------------------------------------------------------------------------
// Run a single job
// ---------------------------------------------------------------------------
async function runJob(job) {
    const logger = (0, logger_1.getLogger)();
    logger.info(`Running cron job ${job.id}: ${job.name}`);
    const startTime = Date.now();
    try {
        // Security scan
        const prompt = buildJobPrompt(job);
        const threat = scanPrompt(prompt);
        if (threat) {
            logger.error(`Cron job ${job.id} blocked: ${threat}`);
            return { success: false, output: '', response: '', error: threat };
        }
        // Script pre-execution
        let scriptOutput = '';
        if (job.script) {
            const scriptResult = await runJobScript(job.script);
            scriptOutput = scriptResult.output;
            if (!scriptResult.success && job.no_agent) {
                return { success: false, output: scriptOutput, response: '', error: scriptOutput };
            }
        }
        // Script-only mode (no_agent)
        if (job.no_agent) {
            (0, jobs_1.saveJobOutput)(job.id, scriptOutput);
            return { success: true, output: scriptOutput, response: scriptOutput, error: null };
        }
        // Agent mode: create PureAgent and run
        if (!_agentConfig) {
            logger.error(`Cron job ${job.id}: no agent config set`);
            return { success: false, output: '', response: '', error: 'No agent config set for cron jobs' };
        }
        const fullPrompt = scriptOutput
            ? `${prompt}\n\nScript output:\n${scriptOutput}`
            : prompt;
        const agent = new pure_agent_1.PureAgent({
            ..._agentConfig,
            model: job.model || _agentConfig.model,
            workingDir: job.workdir || _agentConfig.workingDir,
            sessionId: `cron_${job.id}_${Date.now()}`,
            maxIterations: 50,
        });
        let response = '';
        try {
            for await (const event of agent.runConversation(fullPrompt)) {
                if (event.event === 'content' && event.data.delta) {
                    response += event.data.delta;
                }
                if (event.event === 'done' || event.event === 'error')
                    break;
            }
        }
        finally {
            agent.close();
        }
        const output = response || '(no response)';
        (0, jobs_1.saveJobOutput)(job.id, output);
        const duration = Math.round((Date.now() - startTime) / 1000);
        logger.info(`Cron job ${job.id} completed in ${duration}s, ${response.length} chars`);
        return { success: true, output, response: output, error: null };
    }
    catch (err) {
        logger.error(`Cron job ${job.id} failed: ${err.message}`);
        return { success: false, output: '', response: '', error: err.message };
    }
}
// ---------------------------------------------------------------------------
// Tick: check and run due jobs
// ---------------------------------------------------------------------------
let _tickRunning = false;
async function tick() {
    if (_tickRunning)
        return 0;
    _tickRunning = true;
    const logger = (0, logger_1.getLogger)();
    try {
        const dueJobs = (0, jobs_1.getDueJobs)();
        if (dueJobs.length === 0)
            return 0;
        logger.info(`Cron tick: ${dueJobs.length} job(s) due`);
        let executed = 0;
        for (const job of dueJobs) {
            // Advance next_run before execution (at-most-once semantics)
            (0, jobs_1.advanceNextRun)(job.id);
            try {
                const result = await runJob(job);
                (0, jobs_1.markJobRun)(job.id, result.success, result.error || undefined);
                executed++;
                // Save delivery output if not silent
                if (result.response && !result.response.includes(SILENT_MARKER)) {
                    (0, jobs_1.saveJobOutput)(job.id, result.response);
                }
            }
            catch (err) {
                logger.error(`Cron job ${job.id} execution error: ${err.message}`);
                (0, jobs_1.markJobRun)(job.id, false, err.message);
            }
        }
        return executed;
    }
    catch (err) {
        logger.error(`Cron tick error: ${err.message}`);
        return 0;
    }
    finally {
        _tickRunning = false;
    }
}
// ---------------------------------------------------------------------------
// Cron ticker (periodic)
// ---------------------------------------------------------------------------
let _tickerInterval = null;
function startCronTicker(intervalMs = 60000) {
    if (_tickerInterval)
        return;
    (0, logger_1.getLogger)().info(`Starting cron ticker (interval: ${intervalMs}ms)`);
    // Run immediately
    tick().catch(err => (0, logger_1.getLogger)().error(`Cron tick error: ${err.message}`));
    _tickerInterval = setInterval(() => {
        tick().catch(err => (0, logger_1.getLogger)().error(`Cron tick error: ${err.message}`));
    }, intervalMs);
    // Unref so it doesn't keep the process alive
    if (_tickerInterval.unref)
        _tickerInterval.unref();
}
function stopCronTicker() {
    if (_tickerInterval) {
        clearInterval(_tickerInterval);
        _tickerInterval = null;
        (0, logger_1.getLogger)().info('Cron ticker stopped');
    }
}
//# sourceMappingURL=scheduler.js.map