"use strict";
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.extractLearningCandidate = extractLearningCandidate;
exports.appendLearningCandidate = appendLearningCandidate;
exports.listLearningCandidates = listLearningCandidates;
exports.listLearningCandidateReviews = listLearningCandidateReviews;
exports.listReviewedLearningCandidates = listReviewedLearningCandidates;
exports.reviewLearningCandidate = reviewLearningCandidate;
exports.evaluatePromotionGateForCandidate = evaluatePromotionGateForCandidate;
exports.getLearningCandidateProvenance = getLearningCandidateProvenance;
exports.promoteLearningCandidate = promoteLearningCandidate;
const fs = __importStar(require("fs/promises"));
const path = __importStar(require("path"));
const sessionStorage_1 = require("../persistence/sessionStorage");
const atomicFile_1 = require("../persistence/atomicFile");
const promotionGate_1 = require("./promotionGate");
const evalTrace_1 = require("./evalTrace");
const silentFailureSink_1 = require("../observability/silentFailureSink");
const DEFAULT_MIN_TOOL_SUCCESS_RATE = 0.75;
const DEFAULT_MIN_QUALITY_SCORE = 0.6;
function extractLearningCandidate(sessionId, events, options = {}) {
    const messages = events.filter((event) => event.data.kind === 'message');
    const toolResults = events.filter((event) => event.data.kind === 'tool_result');
    const userMessage = messages.find((event) => event.data.kind === 'message' &&
        event.data.message &&
        typeof event.data.message === 'object' &&
        event.data.message.role === 'user');
    const assistantMessage = [...messages].reverse().find((event) => event.data.kind === 'message' &&
        event.data.message &&
        typeof event.data.message === 'object' &&
        event.data.message.role === 'assistant');
    const prompt = userMessage?.data.kind === 'message' && userMessage.data.message ? userMessage.data.message.content ?? '' : '';
    const outcome = assistantMessage?.data.kind === 'message' && assistantMessage.data.message ? assistantMessage.data.message.content ?? '' : '';
    const toolNames = Array.from(new Set(toolResults.map((event) => event.data.kind === 'tool_result' && event.data.call ? event.data.call.name ?? '' : ''))).filter(Boolean);
    const successfulTools = toolResults.filter((event) => event.data.kind === 'tool_result' && event.data.result && event.data.result.success).length;
    const toolSuccessRate = toolResults.length > 0 ? successfulTools / toolResults.length : 1;
    const qualityScore = scoreCandidate(prompt, outcome, toolSuccessRate, toolNames.length);
    const rejectionReasons = [];
    if (prompt.trim().length === 0)
        rejectionReasons.push('missing user prompt');
    if (outcome.trim().length === 0)
        rejectionReasons.push('missing assistant outcome');
    if (toolSuccessRate < (options.minToolSuccessRate ?? DEFAULT_MIN_TOOL_SUCCESS_RATE)) {
        rejectionReasons.push('tool success rate below threshold');
    }
    if (qualityScore < (options.minQualityScore ?? DEFAULT_MIN_QUALITY_SCORE)) {
        rejectionReasons.push('quality score below threshold');
    }
    return {
        id: `${sessionId}:${events.at(-1)?.id ?? 'empty'}`,
        sessionId,
        createdAt: new Date().toISOString(),
        prompt: prompt.slice(0, 2000),
        outcome: outcome.slice(0, 2000),
        toolNames,
        sourceEventIds: events.map((event) => event.id),
        qualityScore,
        accepted: rejectionReasons.length === 0,
        rejectionReasons,
    };
}
async function appendLearningCandidate(projectDir, candidate) {
    const filePath = path.join(projectDir, '.harness', 'learning', 'session-candidates.jsonl');
    await fs.mkdir(path.dirname(filePath), { recursive: true });
    await fs.appendFile(filePath, JSON.stringify(candidate) + '\n', 'utf-8');
    return filePath;
}
async function listLearningCandidates(projectDir, limit = 100) {
    const filePath = path.join(projectDir, '.harness', 'learning', 'session-candidates.jsonl');
    try {
        const raw = await fs.readFile(filePath, 'utf-8');
        return raw.trim().split('\n')
            .filter(Boolean)
            .map((line) => JSON.parse(line))
            .slice(-limit);
    }
    catch (err) {
        (0, silentFailureSink_1.recordSwallowed)('sessionLearning.listCandidates', err);
        return [];
    }
}
async function listLearningCandidateReviews(projectDir, limit = 1000) {
    const filePath = candidateReviewsPath(projectDir);
    try {
        const raw = await fs.readFile(filePath, 'utf-8');
        return raw.trim().split('\n')
            .filter(Boolean)
            .map((line) => JSON.parse(line))
            .slice(-limit);
    }
    catch (err) {
        (0, silentFailureSink_1.recordSwallowed)('sessionLearning.listCandidateReviews', err);
        return [];
    }
}
async function listReviewedLearningCandidates(projectDir, limit = 100) {
    const candidates = await listLearningCandidates(projectDir, limit);
    const reviews = await listLearningCandidateReviews(projectDir);
    const latestReview = new Map();
    for (const review of reviews) {
        latestReview.set(review.candidateId, review);
    }
    return candidates.map((candidate) => {
        const review = latestReview.get(candidate.id);
        return {
            ...candidate,
            review,
            reviewStatus: review?.action ?? 'pending',
        };
    });
}
async function reviewLearningCandidate(projectDir, candidateId, action, reason) {
    const candidate = (await listLearningCandidates(projectDir, 1000)).find((item) => item.id === candidateId);
    if (!candidate) {
        throw new Error(`Learning candidate not found: ${candidateId}`);
    }
    let memoryPath;
    if (action === 'promote') {
        // Optional Claw-Eval-style gate. Off by default so existing flows
        // are unchanged; enabling it requires HARNESS_PROMOTION_GATE_ENABLED.
        if (process.env.HARNESS_PROMOTION_GATE_ENABLED === '1') {
            const gate = await evaluatePromotionGateForCandidate(projectDir, candidateId);
            if (!gate.allowed) {
                throw new Error(`Promotion blocked by gate: ${gate.reason}`);
            }
        }
        const promoted = await promoteLearningCandidate(projectDir, candidate);
        if (!promoted) {
            throw new Error('Only accepted candidates can be promoted');
        }
        memoryPath = promoted.memoryPath;
    }
    const review = {
        candidateId,
        action,
        reviewedAt: new Date().toISOString(),
        reason,
        memoryPath,
    };
    const filePath = candidateReviewsPath(projectDir);
    await fs.mkdir(path.dirname(filePath), { recursive: true });
    await fs.appendFile(filePath, JSON.stringify(review) + '\n', 'utf-8');
    return review;
}
/**
 * Stand-alone wrapper used by the promotion gate REST endpoint and by
 * `reviewLearningCandidate` above. Looks up the candidate by id, pulls
 * the recent eval-run history, and runs the pure gate evaluator.
 */
async function evaluatePromotionGateForCandidate(projectDir, candidateId, config) {
    const candidate = (await listLearningCandidates(projectDir, 1000)).find((item) => item.id === candidateId);
    if (!candidate) {
        return {
            candidateId,
            candidateFound: false,
            allowed: false,
            reason: `Learning candidate not found: ${candidateId}`,
            passCount: 0,
            consideredRuns: 0,
            requiredPasses: config?.requiredPasses ?? 3,
            safetyViolations: [],
            passAtAll: false,
        };
    }
    const recentEvalRuns = await (0, evalTrace_1.listEvalTraceRuns)(projectDir, 50).catch(() => []);
    const safetyRules = await (0, promotionGate_1.loadSafetyRules)(projectDir).catch(() => undefined);
    const verdict = (0, promotionGate_1.evaluatePromotionGate)({
        candidate,
        recentEvalRuns,
        config: { ...config, safetyRules: safetyRules ?? config?.safetyRules },
    });
    return { candidateId, candidateFound: true, ...verdict };
}
async function getLearningCandidateProvenance(projectDir, candidateId, eventLimit = 25) {
    const candidate = (await listReviewedLearningCandidates(projectDir, 1000)).find((item) => item.id === candidateId);
    if (!candidate) {
        throw new Error(`Learning candidate not found: ${candidateId}`);
    }
    const storage = new sessionStorage_1.SessionStorage(projectDir, 'unknown', candidate.sessionId);
    const events = await storage.readAll();
    const eventById = new Map(events.map((event) => [event.id, event]));
    const sourceEvents = candidate.sourceEventIds
        .map((id) => eventById.get(id))
        .filter((event) => Boolean(event))
        .slice(-eventLimit);
    return {
        candidate,
        events: sourceEvents.map(summarizeSourceEvent),
        missingEventIds: candidate.sourceEventIds.filter((id) => !eventById.has(id)),
    };
}
async function promoteLearningCandidate(projectDir, candidate) {
    if (!candidate.accepted) {
        return null;
    }
    const memoryPath = path.join(projectDir, '.harness', 'memory', 'patterns.md');
    await fs.mkdir(path.dirname(memoryPath), { recursive: true });
    await (0, atomicFile_1.withFileLock)(memoryPath, async () => {
        const existing = await fs.readFile(memoryPath, 'utf-8').catch(() => '# Learned Patterns\n');
        const entry = [
            '',
            `## Session Candidate ${candidate.id}`,
            '',
            `* Promoted: ${new Date().toISOString()}`,
            `* Quality: ${Math.round(candidate.qualityScore * 100)}%`,
            `* Tools: ${candidate.toolNames.length ? candidate.toolNames.join(', ') : 'none'}`,
            '',
            '### Prompt',
            '',
            candidate.prompt || '[empty]',
            '',
            '### Outcome',
            '',
            candidate.outcome || '[empty]',
            '',
        ].join('\n');
        await (0, atomicFile_1.atomicWriteFile)(memoryPath, existing.trimEnd() + entry);
    });
    return { id: candidate.id, promotedAt: new Date().toISOString(), memoryPath };
}
function scoreCandidate(prompt, outcome, toolSuccessRate, toolCount) {
    const hasPrompt = prompt.trim().length > 0 ? 0.3 : 0;
    const hasOutcome = outcome.trim().length > 0 ? 0.3 : 0;
    const toolSignal = toolCount > 0 ? 0.15 : 0.05;
    const successSignal = Math.max(0, Math.min(1, toolSuccessRate)) * 0.25;
    return Number((hasPrompt + hasOutcome + toolSignal + successSignal).toFixed(3));
}
function summarizeSourceEvent(event) {
    return {
        id: event.id,
        timestamp: event.timestamp,
        type: event.type,
        kind: event.data.kind,
        summary: eventSummaryText(event).slice(0, 500),
    };
}
function eventSummaryText(event) {
    if (event.data.kind === 'message') {
        return `${event.data.message.role}: ${event.data.message.content ?? ''}`;
    }
    if (event.data.kind === 'tool_call') {
        return `tool call ${event.data.call.name}: ${JSON.stringify(event.data.call.input ?? {})}`;
    }
    if (event.data.kind === 'tool_result') {
        return `tool result ${event.data.call.name}: ${event.data.result.success ? 'success' : 'failure'} ${event.data.result.output ?? ''}`;
    }
    if (event.data.kind === 'compact_boundary') {
        return `compact boundary: ${event.data.summary}`;
    }
    if (event.data.kind === 'continuity_checkpoint') {
        return `continuity checkpoint: ${event.data.checkpoint.currentGoal}`;
    }
    return event.data.content;
}
function candidateReviewsPath(projectDir) {
    return path.join(projectDir, '.harness', 'learning', 'session-candidate-reviews.jsonl');
}
//# sourceMappingURL=sessionLearning.js.map