formatCompactionReport function

List<String> formatCompactionReport(
  1. AutoCompactorPass pass, {
  2. required bool auto,
  3. bool details = false,
})

Auto/manual compaction run methods (moved from agent_cli.dart under the repo's 2800-line size gate). Same library, so private state is in scope. Formats the in-chat compaction report block (issue #276): tokens before → after, freed count/percent, WHICH ENGINE did the summarizing (the smol/main role — review major 3: a report that doesn't name the engine can't be judged), how many records were hidden vs summarized, and — when the pass actually wrote summary text — the summary in a fenced block so the user can eyeball — and copy — what the transcript was condensed to. A pass with no summary text (all evictions went to hide, or the model returned blank — issue #578) omits the block: an empty ```-fence says nothing and reads as a bug. Pure; _AgentCliCompactionReportPrinter.print renders it.

gh-1636: the internal checkpoint text is debug output, not conversation. By default (details false) the report is ONE short marker line — auto-compacted · ENGINE: N → M tokens (F freed · P%); the records: line and the fenced summary only render with details: true (the tui.showCompactionDetails opt-in, or the manual /compact — the on-demand expansion). The summary itself is never lost: it stays in the session file, reachable via compact_expand.

Implementation

/// Formats the in-chat compaction report block (issue #276): tokens
/// before → after, freed count/percent, WHICH ENGINE did the summarizing
/// (the `smol`/`main` role — review major 3: a report that doesn't name
/// the engine can't be judged), how many records were hidden vs
/// summarized, and — when the pass actually wrote summary text — the
/// summary in a fenced block so the user can eyeball — and copy — what
/// the transcript was condensed to. A pass with no summary text (all
/// evictions went to hide, or the model returned blank — issue #578)
/// omits the block: an empty ```-fence says nothing and reads as a bug.
/// Pure; [_AgentCliCompactionReportPrinter.print] renders it.
///
/// gh-1636: the internal checkpoint text is debug output, not
/// conversation. By default ([details] false) the report is ONE short
/// marker line — `auto-compacted · ENGINE: N → M tokens (F freed ·
/// P%)`; the `records:` line and the fenced summary only render with
/// `details: true` (the `tui.showCompactionDetails` opt-in, or the
/// manual `/compact` — the on-demand expansion). The summary itself is
/// never lost: it stays in the session file, reachable via
/// `compact_expand`.
List<String> formatCompactionReport(
  AutoCompactorPass pass, {
  required bool auto,
  bool details = false,
}) {
  final freedRaw = pass.tokensBefore - pass.tokensAfter;
  // A restamped estimator can report a slightly larger after-count (same
  // clamp as onDone's HEP end frame): "-30 freed" reads as a bug.
  final freed = freedRaw < 0 ? 0 : freedRaw;
  final pct = pass.tokensBefore == 0
      ? 0
      : (freed * 100 / pass.tokensBefore).round();
  final engine = pass.fallback == null ? '' : ' · ${pass.fallback}';
  final passSuffix = pass.pass == 1 ? '' : ' · pass ${pass.pass}';
  final header = '${auto ? 'auto-compacted' : 'compacted'}$engine$passSuffix';
  // gh-1636: the default report — one line, no checkpoint text.
  if (!details) {
    return [
      '$header: ${pass.tokensBefore} → ${pass.tokensAfter} tokens '
          '($freed freed · $pct%)',
    ];
  }
  final summary = pass.summary?.trim();
  return [
    header,
    'tokens: ${pass.tokensBefore} → ${pass.tokensAfter} '
        '($freed freed · $pct%)',
    // Issue #673 AC3: the local-trim valve frees tokens WITHOUT hiding
    // session records or folding a summary — a bare "0 hidden · 0
    // summarized" next to a big freed count cannot describe a real
    // compaction. Name the in-memory drop so the line is truthful for
    // BOTH engines.
    if (pass.droppedMessages > 0)
      'records: ${pass.droppedMessages} dropped (in-memory) · '
          '${pass.hiddenRecords} hidden · '
          '${pass.summarizedMessages} summarized'
    else
      'records: ${pass.hiddenRecords} hidden · '
          '${pass.summarizedMessages} summarized',
    if (summary != null && summary.isNotEmpty) ...[
      'summary:',
      '```',
      _balanceSummaryFences(summary),
      '```',
    ],
  ];
}