"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.SessionStorage = void 0;
const fs = __importStar(require("fs/promises"));
const path = __importStar(require("path"));
const crypto = __importStar(require("crypto"));
const atomicFile_1 = require("./atomicFile");
class SessionStorage {
    transcriptPath;
    metaPath;
    meta;
    constructor(projectDir, model, sessionId) {
        const id = sessionId ?? crypto.randomUUID();
        const sessionsDir = path.join(projectDir, '.harness', 'sessions');
        this.transcriptPath = path.join(sessionsDir, `${id}.jsonl`);
        this.metaPath = path.join(sessionsDir, `${id}.meta.json`);
        this.meta = {
            sessionId: id,
            createdAt: new Date().toISOString(),
            updatedAt: new Date().toISOString(),
            model,
            projectDir,
            status: 'running',
            checkpointCount: 0,
        };
    }
    async initialize() {
        await fs.mkdir(path.dirname(this.transcriptPath), { recursive: true });
        await fs.writeFile(this.metaPath, JSON.stringify(this.meta, null, 2), 'utf-8');
    }
    setMeta(key, value) {
        this.meta[key] = value;
    }
    async append(type, data) {
        const event = {
            id: crypto.randomUUID(),
            timestamp: new Date().toISOString(),
            type,
            data,
        };
        const line = JSON.stringify(event) + '\n';
        await fs.appendFile(this.transcriptPath, line, 'utf-8');
        const patch = { updatedAt: event.timestamp };
        if (data.kind === 'continuity_checkpoint') {
            patch.checkpointCount = (this.meta.checkpointCount ?? 0) + 1;
            patch.lastCheckpointAt = event.timestamp;
            patch.title = this.meta.title ?? data.checkpoint.currentGoal.slice(0, 80);
        }
        else if (data.kind === 'message' && data.message.role === 'user' && !this.meta.title) {
            patch.title = data.message.content?.slice(0, 80) ?? 'Untitled session';
        }
        await this.updateMeta(patch);
    }
    async updateMeta(patch) {
        await fs.mkdir(path.dirname(this.metaPath), { recursive: true });
        return (0, atomicFile_1.withFileLock)(this.metaPath, async () => {
            // Re-read from disk inside the lock to pick up concurrent changes
            const fresh = await this.getMeta();
            this.meta = { ...fresh, ...patch, updatedAt: patch.updatedAt ?? new Date().toISOString() };
            await fs.writeFile(this.metaPath, JSON.stringify(this.meta, null, 2), 'utf-8');
            return this.meta;
        });
    }
    async markStatus(status, lastError) {
        await this.updateMeta({ status, lastError });
    }
    async readAll() {
        try {
            const content = await fs.readFile(this.transcriptPath, 'utf-8');
            return content
                .trim()
                .split('\n')
                .filter((line) => line.length > 0)
                .map((line) => JSON.parse(line));
        }
        catch {
            return [];
        }
    }
    async getMeta() {
        try {
            const content = await fs.readFile(this.metaPath, 'utf-8');
            return JSON.parse(content);
        }
        catch {
            return this.meta;
        }
    }
    getSessionId() {
        return this.meta.sessionId;
    }
    getTranscriptPath() {
        return this.transcriptPath;
    }
    static async listSessions(projectDir) {
        const sessionsDir = path.join(projectDir, '.harness', 'sessions');
        try {
            const files = await fs.readdir(sessionsDir);
            const metaFiles = files.filter((f) => f.endsWith('.meta.json'));
            const metas = [];
            for (const file of metaFiles) {
                try {
                    const content = await fs.readFile(path.join(sessionsDir, file), 'utf-8');
                    metas.push(JSON.parse(content));
                }
                catch {
                    // Skip corrupt meta files
                }
            }
            return metas.sort((a, b) => (b.updatedAt ?? b.createdAt).localeCompare(a.updatedAt ?? a.createdAt));
        }
        catch {
            return [];
        }
    }
    static async listRecoverableSessions(projectDir) {
        const recoverableStatuses = new Set(['running', 'error', 'aborted']);
        const sessions = await SessionStorage.listSessions(projectDir);
        return sessions.filter((session) => typeof session.status === 'string' && recoverableStatuses.has(session.status));
    }
    static async markStaleRunningSessions(projectDir, staleBeforeMs = Date.now()) {
        const sessionsDir = path.join(projectDir, '.harness', 'sessions');
        let files;
        try {
            files = await fs.readdir(sessionsDir);
        }
        catch {
            return 0;
        }
        let marked = 0;
        for (const file of files.filter((name) => name.endsWith('.meta.json'))) {
            const metaPath = path.join(sessionsDir, file);
            try {
                const meta = JSON.parse(await fs.readFile(metaPath, 'utf-8'));
                if (meta.status !== 'running')
                    continue;
                const updatedMs = Date.parse(meta.updatedAt ?? meta.createdAt);
                if (Number.isFinite(updatedMs) && updatedMs >= staleBeforeMs)
                    continue;
                const next = {
                    ...meta,
                    status: 'aborted',
                    updatedAt: new Date().toISOString(),
                    lastError: meta.lastError ?? 'Server restarted before this run completed.',
                };
                await fs.writeFile(metaPath, JSON.stringify(next, null, 2), 'utf-8');
                marked++;
            }
            catch {
                // Skip corrupt meta files; listSessions follows the same tolerant policy.
            }
        }
        return marked;
    }
}
exports.SessionStorage = SessionStorage;
//# sourceMappingURL=sessionStorage.js.map