import {
  analyzeSessionIntegrity
} from "./chunk-YRMVARQP.js";
import {
  log
} from "./chunk-2ODBA7MQ.js";

// src/transcript.ts
import { appendFile, mkdir, readdir, readFile, stat, unlink, writeFile } from "fs/promises";
import path from "path";
var TranscriptManager = class _TranscriptManager {
  transcriptsDir;
  checkpointPath;
  stateDir;
  toolUsageDir;
  config;
  sessionFootprintCache = /* @__PURE__ */ new Map();
  /** Default checkpoint TTL in hours */
  static DEFAULT_CHECKPOINT_TTL_HOURS = 24;
  /** Approximate characters per token for rough estimation */
  static CHARS_PER_TOKEN = 4;
  constructor(config) {
    this.config = config;
    this.transcriptsDir = path.join(config.memoryDir, "transcripts");
    this.stateDir = path.join(config.memoryDir, "state");
    this.checkpointPath = path.join(this.stateDir, "checkpoint.json");
    this.toolUsageDir = path.join(this.stateDir, "tool-usage");
  }
  /**
   * Parse a sessionKey to extract channel type and ID.
   *
   * SessionKey patterns:
   *   - agent:<agent-id>:main → type="main", id="default"
   *   - agent:<agent-id>:discord:channel:<channel-id> → type="discord", id="<channel-id>"
   *   - agent:<agent-id>:cron:<job-id> → type="cron", id="<job-id>"
   *   - agent:<agent-id>:slack:channel:<channel-id> → type="slack", id="<channel-id>"
   *
   * @returns Object with dir (channel type/channel id) and file (YYYY-MM-DD.jsonl)
   */
  getTranscriptPath(sessionKey) {
    const parts = sessionKey.split(":");
    let channelType = "other";
    let channelId = "default";
    if (parts.length >= 3) {
      channelType = parts[2];
      if (channelType === "main") {
        channelId = "default";
      } else if (channelType === "discord" && parts.length >= 5 && parts[3] === "channel") {
        channelId = parts[4];
      } else if (channelType === "slack" && parts.length >= 5 && parts[3] === "channel") {
        channelId = parts[4];
      } else if (channelType === "cron" && parts.length >= 4) {
        channelId = parts[3];
      } else if (parts.length >= 4) {
        channelId = parts[3];
      }
    }
    const today = (/* @__PURE__ */ new Date()).toISOString().slice(0, 10);
    return {
      dir: path.join(channelType, channelId),
      file: `${today}.jsonl`
    };
  }
  /**
   * Initialize the transcript manager by ensuring directories exist.
   */
  async initialize() {
    await mkdir(this.transcriptsDir, { recursive: true });
    await mkdir(this.stateDir, { recursive: true });
    await mkdir(this.toolUsageDir, { recursive: true });
    log.info("transcript manager initialized");
  }
  /**
   * Best-effort list of sessionKeys that have transcript files on disk.
   * This is used by cron-style tooling (hourly summaries, conversation indexing)
   * to iterate across "active" sessions.
   */
  async listSessionKeys() {
    const transcriptDir = this.transcriptsDir;
    const sessionKeys = /* @__PURE__ */ new Set();
    try {
      const typeEntries = await readdir(transcriptDir, { withFileTypes: true });
      for (const typeEnt of typeEntries) {
        if (!typeEnt.isDirectory()) continue;
        const typeDir = path.join(transcriptDir, typeEnt.name);
        const idEntries = await readdir(typeDir, { withFileTypes: true });
        for (const idEnt of idEntries) {
          if (!idEnt.isDirectory()) continue;
          const chanDir = path.join(typeDir, idEnt.name);
          const files = (await readdir(chanDir)).filter((f) => f.endsWith(".jsonl")).sort();
          const last = files[files.length - 1];
          if (!last) continue;
          try {
            const raw = await readFile(path.join(chanDir, last), "utf-8");
            const firstLine = raw.split("\n").find((l) => l.trim().length > 0);
            if (!firstLine) continue;
            const entry = JSON.parse(firstLine);
            if (typeof entry.sessionKey === "string" && entry.sessionKey.length > 0) {
              sessionKeys.add(entry.sessionKey);
            }
          } catch {
          }
        }
      }
    } catch {
      return [];
    }
    return Array.from(sessionKeys);
  }
  getToolUsagePath(sessionKey) {
    const p = this.getTranscriptPath(sessionKey);
    return { dir: p.dir, file: p.file };
  }
  async appendToolUse(entry) {
    const { dir, file } = this.getToolUsagePath(entry.sessionKey);
    const channelDir = path.join(this.toolUsageDir, dir);
    await mkdir(channelDir, { recursive: true });
    const filePath = path.join(channelDir, file);
    await appendFile(filePath, JSON.stringify(entry) + "\n", "utf-8");
  }
  async readToolUse(sessionKey, startTime, endTime) {
    const { dir } = this.getToolUsagePath(sessionKey);
    const channelDir = path.join(this.toolUsageDir, dir);
    try {
      const files = await readdir(channelDir);
      const out = [];
      for (const file of files) {
        if (!file.endsWith(".jsonl")) continue;
        const fp = path.join(channelDir, file);
        const raw = await readFile(fp, "utf-8");
        for (const line of raw.split("\n")) {
          if (!line.trim()) continue;
          try {
            const obj = JSON.parse(line);
            const ts = new Date(String(obj.timestamp ?? "")).getTime();
            if (!Number.isFinite(ts)) continue;
            if (ts >= startTime.getTime() && ts < endTime.getTime()) {
              if (typeof obj.tool === "string" && typeof obj.sessionKey === "string") {
                out.push({ timestamp: obj.timestamp, sessionKey: obj.sessionKey, tool: obj.tool });
              }
            }
          } catch {
          }
        }
      }
      return out;
    } catch {
      return [];
    }
  }
  async estimateSessionFootprint(sessionKey) {
    const { dir } = this.getTranscriptPath(sessionKey);
    const channelDir = path.join(this.transcriptsDir, dir);
    let bytes = 0;
    try {
      const files = (await readdir(channelDir)).filter((file) => file.endsWith(".jsonl")).sort();
      const cached = this.sessionFootprintCache.get(sessionKey);
      if (!cached) {
        const fileBytes = /* @__PURE__ */ new Map();
        const fileSizes = /* @__PURE__ */ new Map();
        for (const file of files) {
          try {
            const fullPath = path.join(channelDir, file);
            const fileInfo = await stat(fullPath);
            const sessionBytes = await this.estimateSessionBytesInFile(
              fullPath,
              sessionKey
            );
            fileBytes.set(file, sessionBytes);
            fileSizes.set(file, Math.max(0, fileInfo.size));
            bytes += sessionBytes;
          } catch {
          }
        }
        this.sessionFootprintCache.set(sessionKey, { totalBytes: bytes, fileBytes, fileSizes });
      } else {
        bytes = cached.totalBytes;
        const seen = new Set(files);
        for (const [cachedFile, cachedSessionBytes] of cached.fileBytes.entries()) {
          if (!seen.has(cachedFile)) {
            bytes -= cachedSessionBytes;
            cached.fileBytes.delete(cachedFile);
            cached.fileSizes.delete(cachedFile);
          }
        }
        for (const file of files) {
          if (cached.fileBytes.has(file)) continue;
          try {
            const fullPath = path.join(channelDir, file);
            const fileInfo = await stat(fullPath);
            const sessionBytes = await this.estimateSessionBytesInFile(fullPath, sessionKey);
            cached.fileBytes.set(file, sessionBytes);
            cached.fileSizes.set(file, Math.max(0, fileInfo.size));
            bytes += sessionBytes;
          } catch {
          }
        }
        const newestFile = files[files.length - 1];
        if (newestFile) {
          try {
            const newestPath = path.join(channelDir, newestFile);
            const fileInfo = await stat(newestPath);
            const size = Math.max(0, fileInfo.size);
            const previousSessionBytes = cached.fileBytes.get(newestFile) ?? 0;
            const previousSize = cached.fileSizes.get(newestFile) ?? -1;
            if (size !== previousSize) {
              const sessionBytes = await this.estimateSessionBytesInFile(newestPath, sessionKey);
              cached.fileBytes.set(newestFile, sessionBytes);
              cached.fileSizes.set(newestFile, size);
              bytes += sessionBytes - previousSessionBytes;
            }
          } catch {
          }
        }
        if (bytes < 0) bytes = 0;
        cached.totalBytes = bytes;
      }
    } catch {
      this.sessionFootprintCache.delete(sessionKey);
    }
    return {
      bytes,
      tokens: Math.floor(bytes / _TranscriptManager.CHARS_PER_TOKEN)
    };
  }
  async estimateSessionBytesInFile(filePath, sessionKey) {
    try {
      const raw = await readFile(filePath, "utf-8");
      let total = 0;
      for (const line of raw.split("\n")) {
        if (!line.trim()) continue;
        try {
          const parsed = JSON.parse(line);
          if (parsed.sessionKey === sessionKey) {
            total += Buffer.byteLength(`${line}
`, "utf-8");
          }
        } catch {
        }
      }
      return total;
    } catch {
      return 0;
    }
  }
  /**
   * Check if a file is a legacy flat transcript file (YYYY-MM-DD.jsonl format).
   */
  isLegacyTranscriptFile(filename) {
    return /^\d{4}-\d{2}-\d{2}\.jsonl$/.test(filename);
  }
  /**
   * Append a turn to the appropriate transcript file.
   * Files are stored hierarchically: transcripts/{channelType}/{channelId}.jsonl
   *
   * Skips channel types in config.transcriptSkipChannelTypes (e.g., "cron").
   */
  async append(entry) {
    try {
      const { dir, file } = this.getTranscriptPath(entry.sessionKey);
      const channelType = dir.split(path.sep)[0] ?? dir;
      if (this.config.transcriptSkipChannelTypes.includes(channelType)) {
        return;
      }
      const channelDir = path.join(this.transcriptsDir, dir);
      const filePath = path.join(channelDir, file);
      await mkdir(channelDir, { recursive: true });
      const line = JSON.stringify(entry) + "\n";
      await appendFile(filePath, line, "utf-8");
      log.debug(`appended transcript entry for ${entry.sessionKey}: ${entry.turnId}`);
    } catch (err) {
      log.error("failed to append transcript entry:", err);
      throw err;
    }
  }
  /**
   * Get all transcript files from the hierarchical directory structure.
   * Recursively finds all .jsonl files in transcripts/{channelType}/{channelId}/ subdirectories.
   */
  async getAllTranscriptFiles() {
    const files = [];
    try {
      const entries = await readdir(this.transcriptsDir, { withFileTypes: true });
      for (const entry of entries) {
        if (entry.isDirectory()) {
          const channelTypeDir = path.join(this.transcriptsDir, entry.name);
          try {
            const channelTypeEntries = await readdir(channelTypeDir, { withFileTypes: true });
            for (const channelTypeEntry of channelTypeEntries) {
              if (channelTypeEntry.isDirectory()) {
                const channelDir = path.join(channelTypeDir, channelTypeEntry.name);
                try {
                  const channelFiles = await readdir(channelDir);
                  for (const file of channelFiles) {
                    if (file.endsWith(".jsonl")) {
                      files.push(path.join(entry.name, channelTypeEntry.name, file));
                    }
                  }
                } catch {
                }
              } else if (channelTypeEntry.isFile() && channelTypeEntry.name.endsWith(".jsonl")) {
                files.push(path.join(entry.name, channelTypeEntry.name));
              }
            }
          } catch {
          }
        } else if (entry.isFile() && entry.name.endsWith(".jsonl")) {
          files.push(entry.name);
        }
      }
    } catch {
    }
    return files;
  }
  /**
   * Read transcript entries for a date range.
   * Returns entries within the time range, optionally filtered by sessionKey.
   * Reads from all channel subdirectories in the hierarchical structure.
   */
  async readRange(startTime, endTime, sessionKey) {
    const start = new Date(startTime);
    const end = new Date(endTime);
    const entries = [];
    try {
      const transcriptFiles = await this.getAllTranscriptFiles();
      for (const relativePath of transcriptFiles) {
        const filePath = path.join(this.transcriptsDir, relativePath);
        try {
          const content = await readFile(filePath, "utf-8");
          const lines = content.trim().split("\n").filter(Boolean);
          for (const line of lines) {
            try {
              const entry = JSON.parse(line);
              const entryTime = new Date(entry.timestamp);
              if (entryTime >= start && entryTime <= end) {
                if (!sessionKey || entry.sessionKey === sessionKey) {
                  entries.push(entry);
                }
              }
            } catch {
              log.debug(`skipped malformed transcript line in ${relativePath}`);
            }
          }
        } catch {
        }
      }
      entries.sort((a, b) => new Date(a.timestamp).getTime() - new Date(b.timestamp).getTime());
      log.debug(`read ${entries.length} transcript entries from ${transcriptFiles.length} file(s)`);
      return entries;
    } catch (err) {
      log.error("failed to read transcript range:", err);
      return [];
    }
  }
  /**
   * Read the last N hours of transcript.
   *
   * Fast path: when sessionKey is given, reads only the 1-2 daily files for that
   * specific channel instead of scanning all 95+ transcript files across all channels.
   */
  async readRecent(hours, sessionKey) {
    const end = /* @__PURE__ */ new Date();
    const start = new Date(end.getTime() - hours * 60 * 60 * 1e3);
    if (sessionKey) {
      return this.readRecentForSession(start, end, sessionKey);
    }
    return this.readRange(start.toISOString(), end.toISOString(), void 0);
  }
  /**
   * Optimized read for a specific session: only looks in that session's channel
   * directory and only reads files whose date falls within the lookback window.
   */
  async readRecentForSession(start, end, sessionKey) {
    const { dir } = this.getTranscriptPath(sessionKey);
    const channelDir = path.join(this.transcriptsDir, dir);
    const dateStrings = /* @__PURE__ */ new Set();
    const cursor = new Date(start);
    while (cursor <= end) {
      dateStrings.add(cursor.toISOString().slice(0, 10));
      cursor.setDate(cursor.getDate() + 1);
    }
    dateStrings.add(end.toISOString().slice(0, 10));
    const entries = [];
    let files;
    try {
      files = (await readdir(channelDir)).filter((f) => f.endsWith(".jsonl"));
    } catch {
      return [];
    }
    for (const file of files) {
      const dateStr = file.slice(0, 10);
      if (!dateStrings.has(dateStr)) continue;
      try {
        const content = await readFile(path.join(channelDir, file), "utf-8");
        for (const line of content.split("\n")) {
          if (!line.trim()) continue;
          try {
            const entry = JSON.parse(line);
            const ts = new Date(entry.timestamp);
            if (ts >= start && ts <= end && entry.sessionKey === sessionKey) {
              entries.push(entry);
            }
          } catch {
          }
        }
      } catch {
      }
    }
    entries.sort((a, b) => new Date(a.timestamp).getTime() - new Date(b.timestamp).getTime());
    log.debug(`readRecentForSession: ${entries.length} entries from ${files.length} file(s) in ${dir}`);
    return entries;
  }
  /**
   * Cleanup old transcript entries that are older than retentionDays.
   * For hierarchical structure, reads each file and rewrites without old entries.
   * Legacy flat files are deleted if their date is older than retentionDays.
   * Returns the number of files processed (cleaned or deleted).
   */
  async cleanup(retentionDays) {
    if (retentionDays <= 0) {
      log.warn("cleanup called with invalid retentionDays:", retentionDays);
      return 0;
    }
    const cutoff = /* @__PURE__ */ new Date();
    cutoff.setDate(cutoff.getDate() - retentionDays);
    cutoff.setHours(0, 0, 0, 0);
    let processed = 0;
    try {
      const entries = await readdir(this.transcriptsDir, { withFileTypes: true });
      for (const entry of entries) {
        if (entry.isDirectory()) {
          const channelTypeDir = path.join(this.transcriptsDir, entry.name);
          try {
            const channelTypeEntries = await readdir(channelTypeDir, { withFileTypes: true });
            for (const channelTypeEntry of channelTypeEntries) {
              if (channelTypeEntry.isDirectory()) {
                const channelDir = path.join(channelTypeDir, channelTypeEntry.name);
                try {
                  const channelFiles = await readdir(channelDir);
                  for (const file of channelFiles) {
                    if (!file.endsWith(".jsonl")) continue;
                    const filePath = path.join(channelDir, file);
                    if (this.isLegacyTranscriptFile(file)) {
                      const dateStr = file.slice(0, 10);
                      const fileDate = new Date(dateStr);
                      if (!isNaN(fileDate.getTime()) && fileDate < cutoff) {
                        try {
                          await unlink(filePath);
                          processed++;
                          log.debug(`deleted old daily transcript file: ${entry.name}/${channelTypeEntry.name}/${file}`);
                        } catch (err) {
                          log.error(`failed to delete transcript file ${filePath}:`, err);
                        }
                      }
                    } else {
                      const cleaned = await this.cleanupTranscriptFile(filePath, cutoff);
                      if (cleaned) {
                        processed++;
                      }
                    }
                  }
                } catch (err) {
                  log.debug(`failed to process channel directory ${entry.name}/${channelTypeEntry.name}:`, err);
                }
              } else if (channelTypeEntry.isFile() && channelTypeEntry.name.endsWith(".jsonl")) {
                const filePath = path.join(channelTypeDir, channelTypeEntry.name);
                const cleaned = await this.cleanupTranscriptFile(filePath, cutoff);
                if (cleaned) {
                  processed++;
                }
              }
            }
          } catch (err) {
            log.debug(`failed to process channel type directory ${entry.name}:`, err);
          }
        } else if (entry.isFile() && entry.name.endsWith(".jsonl")) {
          if (this.isLegacyTranscriptFile(entry.name)) {
            const dateStr = entry.name.slice(0, 10);
            const fileDate = new Date(dateStr);
            if (!isNaN(fileDate.getTime()) && fileDate < cutoff) {
              const filePath = path.join(this.transcriptsDir, entry.name);
              try {
                await unlink(filePath);
                processed++;
                log.debug(`deleted old legacy transcript file: ${entry.name}`);
              } catch (err) {
                log.error(`failed to delete legacy transcript file ${entry.name}:`, err);
              }
            }
          }
        }
      }
      if (processed > 0) {
        log.info(`cleaned up ${processed} transcript file(s) older than ${retentionDays} days`);
      }
      return processed;
    } catch (err) {
      log.error("failed to cleanup old transcripts:", err);
      return 0;
    }
  }
  /**
   * Clean up old entries from a single transcript file.
   * Reads the file, filters out entries older than cutoff, and rewrites if needed.
   * Returns true if the file was processed (cleaned or deleted).
   */
  async cleanupTranscriptFile(filePath, cutoff) {
    try {
      const content = await readFile(filePath, "utf-8");
      const lines = content.trim().split("\n").filter(Boolean);
      const validLines = [];
      let hasOldEntries = false;
      for (const line of lines) {
        try {
          const entry = JSON.parse(line);
          const entryTime = new Date(entry.timestamp);
          if (entryTime >= cutoff) {
            validLines.push(line);
          } else {
            hasOldEntries = true;
          }
        } catch {
          validLines.push(line);
        }
      }
      if (validLines.length === 0) {
        try {
          await unlink(filePath);
          log.debug(`deleted empty transcript file: ${filePath}`);
          return true;
        } catch (err) {
          log.error(`failed to delete empty transcript file ${filePath}:`, err);
          return false;
        }
      }
      if (hasOldEntries) {
        await writeFile(filePath, validLines.join("\n") + "\n", "utf-8");
        log.debug(`cleaned old entries from transcript file: ${filePath}`);
        return true;
      }
      return false;
    } catch (err) {
      return false;
    }
  }
  /**
   * Save a checkpoint to preserve conversation context.
   * Called when compaction is detected.
   */
  async saveCheckpoint(checkpoint) {
    try {
      await writeFile(this.checkpointPath, JSON.stringify(checkpoint, null, 2), "utf-8");
      log.info(`saved checkpoint for session ${checkpoint.sessionKey} with ${checkpoint.turns.length} turn(s)`);
    } catch (err) {
      log.error("failed to save checkpoint:", err);
      throw err;
    }
  }
  /**
   * Load a checkpoint if one exists and is not expired.
   * Returns null if no checkpoint exists or if it has expired.
   */
  async loadCheckpoint(sessionKey) {
    try {
      const raw = await readFile(this.checkpointPath, "utf-8");
      const checkpoint = JSON.parse(raw);
      if (!checkpoint.sessionKey || !checkpoint.capturedAt || !checkpoint.ttl || !Array.isArray(checkpoint.turns)) {
        log.warn("checkpoint file has invalid structure");
        return null;
      }
      if (sessionKey && checkpoint.sessionKey !== sessionKey) {
        log.debug(`checkpoint session mismatch: ${checkpoint.sessionKey} vs ${sessionKey}`);
        return null;
      }
      const ttl = new Date(checkpoint.ttl);
      if (isNaN(ttl.getTime())) {
        log.warn("checkpoint has invalid TTL format");
        return null;
      }
      if (ttl < /* @__PURE__ */ new Date()) {
        log.info(`checkpoint expired at ${checkpoint.ttl}`);
        return null;
      }
      log.info(`loaded checkpoint with ${checkpoint.turns.length} turn(s), expires at ${checkpoint.ttl}`);
      return checkpoint;
    } catch (err) {
      log.debug("no valid checkpoint found");
      return null;
    }
  }
  /**
   * Clear (delete) the checkpoint file.
   * Called after successful injection of checkpoint context.
   */
  async clearCheckpoint() {
    try {
      await unlink(this.checkpointPath);
      log.info("cleared checkpoint");
    } catch (err) {
      log.debug("no checkpoint to clear");
    }
  }
  /**
   * Format entries for recall injection.
   * Returns a formatted string suitable for injecting into agent context.
   *
   * Format:
   * ## Recent Conversation (last X hours)
   * [10:32] User: message content
   * [10:33] Assistant: response content
   *
   * Content is trimmed to approximately maxTokens.
   */
  formatForRecall(entries, maxTokens) {
    if (entries.length === 0) {
      return "";
    }
    const maxChars = maxTokens * _TranscriptManager.CHARS_PER_TOKEN;
    const lines = [];
    const firstEntry = new Date(entries[0].timestamp);
    const lastEntry = new Date(entries[entries.length - 1].timestamp);
    const hoursDiff = Math.round((lastEntry.getTime() - firstEntry.getTime()) / (60 * 60 * 1e3));
    if (hoursDiff < 1) {
      lines.push("## Recent Conversation (last few minutes)");
    } else {
      lines.push(`## Recent Conversation (last ${hoursDiff} hour${hoursDiff === 1 ? "" : "s"})`);
    }
    lines.push("");
    const formattedEntries = [];
    for (const entry of entries) {
      const time = new Date(entry.timestamp);
      const timeStr = time.toLocaleTimeString("en-US", {
        hour: "2-digit",
        minute: "2-digit",
        hour12: false
      });
      const roleLabel = entry.role === "user" ? "User" : "Assistant";
      formattedEntries.push(`[${timeStr}] ${roleLabel}: ${entry.content}`);
    }
    let totalChars = lines.join("\n").length;
    const selectedEntries = [];
    for (let i = formattedEntries.length - 1; i >= 0; i--) {
      const entry = formattedEntries[i];
      const entryChars = entry.length + 1;
      if (totalChars + entryChars > maxChars && selectedEntries.length > 0) {
        break;
      }
      selectedEntries.unshift(entry);
      totalChars += entryChars;
    }
    lines.push(...selectedEntries);
    lines.push("");
    const result = lines.join("\n");
    log.debug(`formatted ${selectedEntries.length}/${entries.length} transcript entries for recall (~${result.length} chars)`);
    return result;
  }
  /**
   * Create a checkpoint from the current buffer state.
   * Helper method for creating checkpoints before compaction.
   */
  createCheckpoint(sessionKey, turns, ttlHours) {
    const ttl = ttlHours ?? _TranscriptManager.DEFAULT_CHECKPOINT_TTL_HOURS;
    const expiresAt = /* @__PURE__ */ new Date();
    expiresAt.setHours(expiresAt.getHours() + ttl);
    return {
      sessionKey,
      capturedAt: (/* @__PURE__ */ new Date()).toISOString(),
      turns: [...turns],
      // Copy turns to avoid mutation
      ttl: expiresAt.toISOString()
    };
  }
  /**
   * Get statistics about stored transcripts.
   * Returns counts from the hierarchical directory structure.
   */
  async getStats() {
    try {
      const allFiles = await this.getAllTranscriptFiles();
      if (allFiles.length === 0) {
        return {
          totalFiles: 0,
          totalEntries: 0,
          oldestFile: null,
          newestFile: null,
          channelTypes: {}
        };
      }
      const sortedFiles = allFiles.sort();
      let totalEntries = 0;
      const channelTypes = {};
      for (const relativePath of allFiles) {
        const filePath = path.join(this.transcriptsDir, relativePath);
        try {
          const content = await readFile(filePath, "utf-8");
          const lines = content.trim().split("\n").filter(Boolean);
          totalEntries += lines.length;
          const channelType = relativePath.includes(path.sep) ? relativePath.split(path.sep)[0] : "legacy";
          channelTypes[channelType] = (channelTypes[channelType] || 0) + 1;
        } catch {
        }
      }
      return {
        totalFiles: allFiles.length,
        totalEntries,
        oldestFile: sortedFiles[0],
        newestFile: sortedFiles[sortedFiles.length - 1],
        channelTypes
      };
    } catch (err) {
      log.error("failed to get transcript stats:", err);
      return {
        totalFiles: 0,
        totalEntries: 0,
        oldestFile: null,
        newestFile: null,
        channelTypes: {}
      };
    }
  }
  async analyzeIntegrity() {
    return analyzeSessionIntegrity({ memoryDir: this.config.memoryDir });
  }
  async getRecoverySummary(sessionKey) {
    const report = await this.analyzeIntegrity();
    const selectedSessions = sessionKey ? report.sessions.filter((session) => session.sessionKey === sessionKey) : report.sessions;
    const incompleteTurns = selectedSessions.reduce((sum, session) => sum + session.incompleteTurns, 0);
    const brokenChains = selectedSessions.reduce((sum, session) => sum + session.brokenChains, 0);
    const filteredIssues = report.issues.filter((issue) => !sessionKey || issue.sessionKey === sessionKey);
    const issueCount = filteredIssues.length;
    const severeIssueCount = filteredIssues.filter((issue) => issue.severity !== "info").length;
    return {
      generatedAt: report.generatedAt,
      sessionKey,
      healthy: sessionKey ? severeIssueCount === 0 && report.checkpoint.healthy : report.healthy,
      issueCount,
      incompleteTurns,
      brokenChains,
      checkpointHealthy: report.checkpoint.healthy
    };
  }
};

export {
  TranscriptManager
};
//# sourceMappingURL=chunk-E6K4NIEU.js.map